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