1 /*
2  *  OpenSCAD (www.openscad.org)
3  *  Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
4  *                          Marius Kintel <marius@kintel.net>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  As a special exception, you have permission to link this program
12  *  with the CGAL library and distribute executables, as long as you
13  *  follow the requirements of the GNU GPL in regard to all of the
14  *  software in the executable aside from CGAL.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  *
25  */
26 
27 %expect 0
28 
29 %{
30 
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #ifdef _MSC_VER
34 #define strdup _strdup
35 #else
36 #include <unistd.h>
37 #endif
38 
39 #include "FileModule.h"
40 #include "UserModule.h"
41 #include "ModuleInstantiation.h"
42 #include "Assignment.h"
43 #include "expression.h"
44 #include "value.h"
45 #include "function.h"
46 #include "printutils.h"
47 #include "memory.h"
48 #include <sstream>
49 #include <stack>
50 #include <boost/filesystem.hpp>
51 #include "boost-utils.h"
52 #include "feature.h"
53 
54 namespace fs = boost::filesystem;
55 
56 #define YYMAXDEPTH 20000
57 #define LOC(loc) Location(loc.first_line, loc.first_column, loc.last_line, loc.last_column, sourcefile())
58 #ifdef DEBUG
59 static Location debug_location(const std::string& info, const struct YYLTYPE& loc);
60 #define LOCD(str, loc) debug_location(str, loc)
61 #else
62 #define LOCD(str, loc) LOC(loc)
63 #endif
64 
65 int parser_error_pos = -1;
66 
67 int parserlex(void);
68 void yyerror(char const *s);
69 
70 int lexerget_lineno(void);
71 bool lexer_is_main_file();
72 std::shared_ptr<fs::path> sourcefile(void);
73 void lexer_set_parser_sourcefile(const fs::path& path);
74 int lexerlex_destroy(void);
75 int lexerlex(void);
76 static void handle_assignment(const std::string token, Expression *expr, const Location loc);
77 
78 std::stack<LocalScope *> scope_stack;
79 FileModule *rootmodule;
80 
81 extern void lexerdestroy();
82 extern FILE *lexerin;
83 const char *parser_input_buffer;
84 static fs::path mainFilePath;
85 static bool parsingMainFile;
86 static std::string sourcefile_folder;
87 
88 bool fileEnded=false;
89 %}
90 
91 %initial-action
92 {
93   @$.first_line = 1;
94   @$.first_column = 1;
95   @$.last_column = 1;
96   @$.last_line = 1;
97 };
98 
99 %union {
100   char *text;
101   double number;
102   class Value *value;
103   class Expression *expr;
104   class Vector *vec;
105   class ModuleInstantiation *inst;
106   class IfElseModuleInstantiation *ifelse;
107   class Assignment *arg;
108   AssignmentList *args;
109 }
110 
111 %token TOK_ERROR
112 
113 %token TOK_EOT
114 
115 %token TOK_MODULE
116 %token TOK_FUNCTION
117 %token TOK_IF
118 %token TOK_ELSE
119 %token TOK_FOR
120 %token TOK_LET
121 %token TOK_ASSERT
122 %token TOK_ECHO
123 %token TOK_EACH
124 
125 %token <text> TOK_ID
126 %token <text> TOK_STRING
127 %token <text> TOK_USE
128 %token <number> TOK_NUMBER
129 
130 %token TOK_TRUE
131 %token TOK_FALSE
132 %token TOK_UNDEF
133 
134 %token LE GE EQ NE AND OR
135 
136 %nonassoc NO_ELSE
137 %nonassoc TOK_ELSE
138 
139 %type <expr> expr
140 %type <expr> call
141 %type <expr> logic_or
142 %type <expr> logic_and
143 %type <expr> equality
144 %type <expr> comparison
145 %type <expr> addition
146 %type <expr> multiplication
147 %type <expr> exponent
148 %type <expr> unary
149 %type <expr> primary
150 %type <vec> vector_expr
151 %type <expr> list_comprehension_elements
152 %type <expr> list_comprehension_elements_p
153 %type <expr> list_comprehension_elements_or_expr
154 %type <expr> expr_or_empty
155 
156 %type <inst> module_instantiation
157 %type <ifelse> if_statement
158 %type <ifelse> ifelse_statement
159 %type <inst> single_module_instantiation
160 
161 %type <args> arguments_call
162 %type <args> arguments_decl
163 
164 %type <arg> argument_call
165 %type <arg> argument_decl
166 %type <text> module_id
167 
168 %debug
169 %locations
170 
171 %%
172 
173 input
174         : /* empty */
175         | input
176           TOK_USE
177             {
178               rootmodule->registerUse(std::string($2), lexer_is_main_file() && parsingMainFile ? LOC(@2) : Location::NONE);
179               free($2);
180             }
181         | input statement
182         ;
183 
184 statement
185         : ';'
186         | '{' inner_input '}'
187         | module_instantiation
188             {
189               if ($1) scope_stack.top()->addChild(shared_ptr<ModuleInstantiation>($1));
190             }
191         | assignment
192         | TOK_MODULE TOK_ID '(' arguments_decl optional_commas ')'
193             {
194               UserModule *newmodule = new UserModule($2, LOCD("module", @$));
195               newmodule->definition_arguments = *$4;
196               auto top = scope_stack.top();
197               scope_stack.push(&newmodule->scope);
198               top->addChild(shared_ptr<UserModule>(newmodule));
199               free($2);
200               delete $4;
201             }
202           statement
203             {
204                 scope_stack.pop();
205             }
206         | TOK_FUNCTION TOK_ID '(' arguments_decl optional_commas ')' '=' expr ';'
207             {
208               scope_stack.top()->addChild(
209                 make_shared<UserFunction>($2, *$4, shared_ptr<Expression>($8), LOCD("function", @$))
210               );
211               free($2);
212               delete $4;
213             }
214         | TOK_EOT
215             {
216                 fileEnded=true;
217             }
218         ;
219 
220 inner_input
221         : /* empty */
222         | statement inner_input
223         ;
224 
225 assignment
226         : TOK_ID '=' expr ';'
227             {
228                 handle_assignment($1, $3, LOCD("assignment", @$));
229                 free($1);
230             }
231         ;
232 
233 module_instantiation
234         : '!' module_instantiation
235             {
236                 $$ = $2;
237                 if ($$) $$->tag_root = true;
238             }
239         | '#' module_instantiation
240             {
241                 $$ = $2;
242                 if ($$) $$->tag_highlight = true;
243             }
244         | '%' module_instantiation
245             {
246                 $$ = $2;
247                 if ($$) $$->tag_background = true;
248             }
249         | '*' module_instantiation
250             {
251                 delete $2;
252                 $$ = NULL;
253             }
254         | single_module_instantiation
255             {
256                 $<inst>$ = $1;
257                 scope_stack.push(&$1->scope);
258             }
259           child_statement
260             {
261                 scope_stack.pop();
262                 $$ = $<inst>2;
263             }
264         | ifelse_statement
265             {
266                 $$ = $1;
267             }
268         ;
269 
270 ifelse_statement
271         : if_statement %prec NO_ELSE
272             {
273                 $$ = $1;
274             }
275         | if_statement TOK_ELSE
276             {
277                 scope_stack.push($1->makeElseScope());
278             }
279           child_statement
280             {
281                 scope_stack.pop();
282                 $$ = $1;
283             }
284         ;
285 
286 if_statement
287         : TOK_IF '(' expr ')'
288             {
289                 $<ifelse>$ = new IfElseModuleInstantiation(shared_ptr<Expression>($3), sourcefile_folder, LOCD("if", @$));
290                 scope_stack.push(&$<ifelse>$->scope);
291             }
292           child_statement
293             {
294                 scope_stack.pop();
295                 $$ = $<ifelse>5;
296             }
297         ;
298 
299 child_statements
300         : /* empty */
301         | child_statements child_statement
302         | child_statements assignment
303         ;
304 
305 child_statement
306         : ';'
307         | '{' child_statements '}'
308         | module_instantiation
309             {
310                 if ($1) scope_stack.top()->addChild(shared_ptr<ModuleInstantiation>($1));
311             }
312         ;
313 
314 // "for", "let" and "each" are valid module identifiers
315 module_id
316         : TOK_ID  { $$ = $1; }
317         | TOK_FOR { $$ = strdup("for"); }
318         | TOK_LET { $$ = strdup("let"); }
319         | TOK_ASSERT { $$ = strdup("assert"); }
320         | TOK_ECHO { $$ = strdup("echo"); }
321         | TOK_EACH { $$ = strdup("each"); }
322         ;
323 
324 single_module_instantiation
325         : module_id '(' arguments_call ')'
326             {
327                 $$ = new ModuleInstantiation($1, *$3, sourcefile_folder, LOCD("modulecall", @$));
328                 free($1);
329                 delete $3;
330             }
331         ;
332 
333 expr
334         : logic_or
335         | TOK_FUNCTION '(' arguments_decl optional_commas ')' expr %prec NO_ELSE
336             {
337               $$ = new FunctionDefinition($6, *$3, LOCD("anonfunc", @$));
338               delete $3;
339             }
340         | logic_or '?' expr ':' expr
341             {
342               $$ = new TernaryOp($1, $3, $5, LOCD("ternary", @$));
343             }
344         | TOK_LET '(' arguments_call ')' expr
345             {
346               $$ = FunctionCall::create("let", *$3, $5, LOCD("let", @$));
347               delete $3;
348             }
349         | TOK_ASSERT '(' arguments_call ')' expr_or_empty
350             {
351               $$ = FunctionCall::create("assert", *$3, $5, LOCD("assert", @$));
352               delete $3;
353             }
354         | TOK_ECHO '(' arguments_call ')' expr_or_empty
355             {
356               $$ = FunctionCall::create("echo", *$3, $5, LOCD("echo", @$));
357               delete $3;
358             }
359         ;
360 
361 logic_or
362         : logic_and
363         | logic_or OR logic_and
364             {
365               $$ = new BinaryOp($1, BinaryOp::Op::LogicalOr, $3, LOCD("or", @$));
366             }
367 		;
368 
369 logic_and
370         : equality
371         | logic_and AND equality
372             {
373               $$ = new BinaryOp($1, BinaryOp::Op::LogicalAnd, $3, LOCD("and", @$));
374             }
375 		;
376 
377 equality
378         : comparison
379         | equality EQ comparison
380             {
381               $$ = new BinaryOp($1, BinaryOp::Op::Equal, $3, LOCD("equal", @$));
382             }
383         | equality NE comparison
384             {
385               $$ = new BinaryOp($1, BinaryOp::Op::NotEqual, $3, LOCD("notequal", @$));
386             }
387 		;
388 
389 comparison
390         : addition
391         | comparison '>' addition
392             {
393               $$ = new BinaryOp($1, BinaryOp::Op::Greater, $3, LOCD("greater", @$));
394             }
395         | comparison GE addition
396             {
397               $$ = new BinaryOp($1, BinaryOp::Op::GreaterEqual, $3, LOCD("greaterequal", @$));
398             }
399         | comparison '<' addition
400             {
401               $$ = new BinaryOp($1, BinaryOp::Op::Less, $3, LOCD("less", @$));
402             }
403         | comparison LE addition
404             {
405               $$ = new BinaryOp($1, BinaryOp::Op::LessEqual, $3, LOCD("lessequal", @$));
406             }
407 		;
408 
409 addition
410         : multiplication
411         | addition '+' multiplication
412             {
413               $$ = new BinaryOp($1, BinaryOp::Op::Plus, $3, LOCD("addition", @$));
414             }
415         | addition '-' multiplication
416             {
417               $$ = new BinaryOp($1, BinaryOp::Op::Minus, $3, LOCD("subtraction", @$));
418             }
419 		;
420 
421 multiplication
422         : unary
423         | multiplication '*' unary
424             {
425               $$ = new BinaryOp($1, BinaryOp::Op::Multiply, $3, LOCD("multiply", @$));
426             }
427         | multiplication '/' unary
428             {
429               $$ = new BinaryOp($1, BinaryOp::Op::Divide, $3, LOCD("divide", @$));
430             }
431         | multiplication '%' unary
432             {
433               $$ = new BinaryOp($1, BinaryOp::Op::Modulo, $3, LOCD("modulo", @$));
434             }
435 		;
436 
437 
438 unary
439         : exponent
440         | '+' unary
441             {
442                 $$ = $2;
443             }
444         | '-' unary
445             {
446               $$ = new UnaryOp(UnaryOp::Op::Negate, $2, LOCD("negate", @$));
447             }
448         | '!' unary
449             {
450               $$ = new UnaryOp(UnaryOp::Op::Not, $2, LOCD("not", @$));
451             }
452 		;
453 
454 exponent
455        : call
456        | call '^' unary
457            {
458               $$ = new BinaryOp($1, BinaryOp::Op::Exponent, $3, LOCD("exponent", @$));
459            }
460        ;
461 
462 call
463         : primary
464         | call '(' arguments_call ')'
465             {
466               $$ = new FunctionCall($1, *$3, LOCD("functioncall", @$));
467               delete $3;
468             }
469         | call '[' expr ']'
470             {
471               $$ = new ArrayLookup($1, $3, LOCD("index", @$));
472             }
473         | call '.' TOK_ID
474             {
475               $$ = new MemberLookup($1, $3, LOCD("member", @$));
476               free($3);
477             }
478 		;
479 
480 primary
481         : TOK_TRUE
482             {
483               $$ = new Literal(true, LOCD("literal", @$));
484             }
485         | TOK_FALSE
486             {
487               $$ = new Literal(false, LOCD("literal", @$));
488             }
489         | TOK_UNDEF
490             {
491               $$ = new Literal(Value::undefined.clone(), LOCD("literal", @$));
492             }
493         | TOK_NUMBER
494             {
495               $$ = new Literal(Value($1), LOCD("literal", @$));
496             }
497         | TOK_STRING
498             {
499               $$ = new Literal(Value(std::string($1)), LOCD("string", @$));
500               free($1);
501             }
502         | TOK_ID
503             {
504               $$ = new Lookup($1, LOCD("variable", @$));
505               free($1);
506             }
507         | '(' expr ')'
508             {
509               $$ = $2;
510             }
511         | '[' expr ':' expr ']'
512             {
513               $$ = new Range($2, $4, LOCD("range", @$));
514             }
515         | '[' expr ':' expr ':' expr ']'
516             {
517               $$ = new Range($2, $4, $6, LOCD("range", @$));
518             }
519         | '[' optional_commas ']'
520             {
521               $$ = new Literal(VectorType::Empty(), LOCD("vector", @$));
522             }
523         | '[' vector_expr optional_commas ']'
524             {
525               $$ = $2;
526             }
527 		;
528 
529 expr_or_empty
530         : /* empty */
531             {
532               $$ = NULL;
533             }
534         | expr
535             {
536               $$ = $1;
537             }
538         ;
539 
540 /* The last set element may not be a "let" (as that would instead
541    be parsed as an expression) */
542 list_comprehension_elements
543         : TOK_LET '(' arguments_call ')' list_comprehension_elements_p
544             {
545               $$ = new LcLet(*$3, $5, LOCD("lclet", @$));
546               delete $3;
547             }
548         | TOK_EACH list_comprehension_elements_or_expr
549             {
550               $$ = new LcEach($2, LOCD("lceach", @$));
551             }
552         | TOK_FOR '(' arguments_call ')' list_comprehension_elements_or_expr
553             {
554                 $$ = $5;
555 
556                 /* transform for(i=...,j=...) -> for(i=...) for(j=...) */
557                 for (int i = $3->size()-1; i >= 0; i--) {
558                   AssignmentList arglist;
559                   arglist.push_back((*$3)[i]);
560                   Expression *e = new LcFor(arglist, $$, LOCD("lcfor", @$));
561                     $$ = e;
562                 }
563                 delete $3;
564             }
565         | TOK_FOR '(' arguments_call ';' expr ';' arguments_call ')' list_comprehension_elements_or_expr
566             {
567               $$ = new LcForC(*$3, *$7, $5, $9, LOCD("lcforc", @$));
568                 delete $3;
569                 delete $7;
570             }
571         | TOK_IF '(' expr ')' list_comprehension_elements_or_expr %prec NO_ELSE
572             {
573               $$ = new LcIf($3, $5, 0, LOCD("lcif", @$));
574             }
575         | TOK_IF '(' expr ')' list_comprehension_elements_or_expr TOK_ELSE list_comprehension_elements_or_expr
576             {
577               $$ = new LcIf($3, $5, $7, LOCD("lcifelse", @$));
578             }
579         ;
580 
581 // list_comprehension_elements with optional parenthesis
582 list_comprehension_elements_p
583         : list_comprehension_elements
584         | '(' list_comprehension_elements ')'
585             {
586                 $$ = $2;
587             }
588         ;
589 
590 list_comprehension_elements_or_expr
591         : list_comprehension_elements_p
592         | expr
593         ;
594 
595 optional_commas
596         : /* empty */
597 		| ',' optional_commas
598         ;
599 
600 vector_expr
601         : expr
602             {
603               $$ = new Vector(LOCD("vector", @$));
604               $$->emplace_back($1);
605             }
606         |  list_comprehension_elements
607             {
608               $$ = new Vector(LOCD("vector", @$));
609               $$->emplace_back($1);
610             }
611         | vector_expr ',' optional_commas list_comprehension_elements_or_expr
612             {
613               $$ = $1;
614               $$->emplace_back($4);
615             }
616         ;
617 
618 arguments_decl
619         : /* empty */
620             {
621                 $$ = new AssignmentList();
622             }
623         | argument_decl
624             {
625                 $$ = new AssignmentList();
626                 $$->emplace_back($1);
627             }
628         | arguments_decl ',' optional_commas argument_decl
629             {
630                 $$ = $1;
631                 $$->emplace_back($4);
632             }
633         ;
634 
635 argument_decl
636         : TOK_ID
637             {
638                 $$ = new Assignment($1, LOCD("assignment", @$));
639                 free($1);
640             }
641         | TOK_ID '=' expr
642             {
643               $$ = new Assignment($1, shared_ptr<Expression>($3), LOCD("assignment", @$));
644                 free($1);
645             }
646         ;
647 
648 arguments_call
649         : /* empty */
650             {
651                 $$ = new AssignmentList();
652             }
653         | argument_call
654             {
655                 $$ = new AssignmentList();
656                 $$->emplace_back($1);
657             }
658         | arguments_call ',' optional_commas argument_call
659             {
660                 $$ = $1;
661                 $$->emplace_back($4);
662             }
663         ;
664 
665 argument_call
666         : expr
667             {
668                 $$ = new Assignment("", shared_ptr<Expression>($1), LOCD("argumentcall", @$));
669             }
670         | TOK_ID '=' expr
671             {
672                 $$ = new Assignment($1, shared_ptr<Expression>($3), LOCD("argumentcall", @$));
673                 free($1);
674             }
675         ;
676 
677 %%
678 
679 int parserlex(void)
680 {
681   return lexerlex();
682 }
683 
yyerror(char const * s)684 void yyerror (char const *s)
685 {
686   // FIXME: We leak memory on parser errors...
687 	Location loc = Location(lexerget_lineno(), -1, -1, -1, sourcefile());
688 	LOG(message_group::Error, loc, "", "Parser error: %1$s", s);
689 }
690 
691 #ifdef DEBUG
debug_location(const std::string & info,const YYLTYPE & loc)692 static Location debug_location(const std::string& info, const YYLTYPE& loc)
693 {
694 	auto location = LOC(loc);
695 	PRINTDB("%3d, %3d - %3d, %3d | %s", loc.first_line % loc.first_column % loc.last_line % loc.last_column % info);
696 	return location;
697 }
698 #endif
699 
warn_reassignment(const Location & loc,const shared_ptr<Assignment> & assignment,const fs::path & path)700 static void warn_reassignment(const Location& loc, const shared_ptr<Assignment>& assignment, const fs::path& path)
701 {
702 	LOG(message_group::Warning,
703 			loc,
704 			path.parent_path().generic_string(),
705 			"%1$s was assigned on line %2$i but was overwritten",
706 			assignment->getName(),
707 			assignment->location().firstLine());
708 
709 }
710 
warn_reassignment(const Location & loc,const shared_ptr<Assignment> & assignment,const fs::path & path1,const fs::path & path2)711 static void warn_reassignment(const Location& loc, const shared_ptr<Assignment>& assignment, const fs::path& path1, const fs::path& path2)
712 {
713 	LOG(message_group::Warning,
714 			loc,
715 			path1.parent_path().generic_string(),
716 			"%1$s was assigned on line %2$i of %3$s but was overwritten",
717 			assignment->getName(),
718 			assignment->location().firstLine(),
719 			path2);
720 }
721 
handle_assignment(const std::string token,Expression * expr,const Location loc)722 void handle_assignment(const std::string token, Expression *expr, const Location loc)
723 {
724 	bool found = false;
725 	for (auto &assignment : scope_stack.top()->assignments) {
726 		if (assignment->getName() == token) {
727 			auto mainFile = mainFilePath.string();
728 			auto prevFile = assignment->location().fileName();
729 			auto currFile = loc.fileName();
730 
731 			const auto uncPathCurr = boostfs_uncomplete(currFile, mainFilePath.parent_path());
732 			const auto uncPathPrev = boostfs_uncomplete(prevFile, mainFilePath.parent_path());
733 			if (fileEnded) {
734 				//assignments via commandline
735 			} else if (prevFile == mainFile && currFile == mainFile) {
736 				//both assignments in the mainFile
737 				warn_reassignment(loc, assignment, mainFilePath);
738 			} else if (uncPathCurr == uncPathPrev) {
739 				//assignment overwritten within the same file
740 				//the line number being equal happens, when a file is included multiple times
741 				if (assignment->location().firstLine() != loc.firstLine()) {
742 					warn_reassignment(loc, assignment, mainFilePath, uncPathPrev);
743 				}
744 			} else if (prevFile == mainFile && currFile != mainFile) {
745 				//assignment from the mainFile overwritten by an include
746 				warn_reassignment(loc, assignment, mainFilePath, uncPathPrev);
747 			}
748 			assignment->setExpr(shared_ptr<Expression>(expr));
749 			assignment->setLocation(loc);
750 			found = true;
751 			break;
752 		}
753 	}
754 	if (!found) {
755 		scope_stack.top()->addChild(assignment(token, shared_ptr<Expression>(expr), loc));
756 	}
757 }
758 
parse(FileModule * & module,const std::string & text,const std::string & filename,const std::string & mainFile,int debug)759 bool parse(FileModule *&module, const std::string& text, const std::string &filename, const std::string &mainFile, int debug)
760 {
761   fs::path filepath = fs::absolute(fs::path(filename));
762   mainFilePath = fs::absolute(fs::path(mainFile));
763   parsingMainFile = mainFilePath == filepath;
764 
765   fs::path parser_sourcefile = fs::path(filepath).generic_string();
766   sourcefile_folder = parser_sourcefile.parent_path().string();
767   lexer_set_parser_sourcefile(parser_sourcefile);
768 
769   lexerin = NULL;
770   parser_error_pos = -1;
771   parser_input_buffer = text.c_str();
772   fileEnded = false;
773 
774   rootmodule = new FileModule(sourcefile_folder, parser_sourcefile.filename().string());
775   scope_stack.push(&rootmodule->scope);
776   //        PRINTB_NOCACHE("New module: %s %p", "root" % rootmodule);
777 
778   parserdebug = debug;
779   int parserretval = -1;
780   try{
781      parserretval = parserparse();
782   }catch (const HardWarningException &e) {
783     yyerror("stop on first warning");
784   }
785 
786   lexerdestroy();
787   lexerlex_destroy();
788 
789   module = rootmodule;
790   if (parserretval != 0) return false;
791 
792   parser_error_pos = -1;
793   parser_input_buffer = nullptr;
794   scope_stack.pop();
795 
796   return true;
797 }
798