Belle II Software development
b2parser.py
1
8import argparse
9from sly import Lexer, Parser
10
11
12def findMatchedParenthesis(string: str, openchar: str, closechar: str) -> int:
13 """
14 Finds matching control token in string and returns the offset.
15 The string's first character must match openchar. Otherwise, 0 is returned.
16
17 Args:
18 string (str): input
19 openchar (str): opening char e.g '{'
20 closechar (str): closing char e.g '}'
21
22 Returns:
23 int: position of matching closing char in string.
24 """
25 end = 1
26 if string[0] == openchar:
27 count = 1
28 while end < len(string) and count > 0:
29 if string[end] == openchar:
30 count += 1
31 elif string[end] == closechar:
32 count -= 1
33 end += 1
34 if count > 0:
35 raise SyntaxError("Matched parenthesis for metavariable could not be found.")
36 return end - 1
37
38
39class B2ParameterLexer(Lexer):
40 """
41 Lexer class responsible for changing the default scanning behavior.
42 It disables token scanning and captures
43 everything within the matched parenthesis.
44 Call pop_state to return to the default scanning state.
45 """
46
47 tokens = {ARGUMENTTUPLE, } # noqa: F821
49 @_(r"\‍(") # noqa: F821
50 def ARGUMENTTUPLE(self, t):
51 """
52 Capture metavariable parameters by finding the matched parenthesis.
53
54 Args:
55 t (sly.lex.token): token of type LPAREN
56
57 Returns:
58 t (sly.lex.token): ARGUMENTTUPLE token
59 """
60 # Find the offset of the matching parenthesis
61 pos = findMatchedParenthesis(self.text[self.index-1:], "(", ")")
62 # Set value to slice
63 t.value = self.text[self.index-1: self.index+pos]
64
65 self.index = self.index+pos
66 # Return to default B2Lexer state
67 self.pop_state()
68 # Return token
69 return t
70
71
72class B2Lexer(Lexer):
73 """
74 Class responsible for scanning the cut and generating a stream of tokens.
75 The token stream can be passed to `B2Parser` to generate a syntax tree.
76 """
77
78 def __init__(self):
79 """Initialize Lexer"""
80
83 self.control_token_stack = list()
85
86 cut_tokens = {
87 # structure tokens
88 RBRACK, LBRACK, # noqa: F821
89 # boolean operators
90 AND, OR, NOT, # noqa: F821
91 # comparison operators
92 EQUALEQUAL, GREATEREQUAL, LESSEQUAL, GREATER, LESS, # noqa: F821
93 NOTEQUAL, # noqa: F821
94 }
95
96 expression_tokens = {
97 LPAREN, RPAREN, # noqa: F821
98 # data types
99 DOUBLE, INTEGER, IDENTIFIER, BOOLEAN, # noqa: F821
100 # arithmetic operators
101 POWER, TIMES, DIVIDE, PLUS, MINUS # noqa: F821
102 }
103
104 tokens = expression_tokens.union(cut_tokens)
106 # Scanning Rules
107
108 ignore = " \t\n"
110 # Token definitions
111
112 literals = {r","}
114 # Comparison operator token definitions
115
116 EQUALEQUAL = r"=="
118 GREATEREQUAL = r">="
120 LESSEQUAL = r"<="
122 GREATER = r">"
124 LESS = r"<"
126 NOTEQUAL = r"!="
128 # Arithmetic operator token definitions
129
130 POWER = r"\*\*|\^"
132 TIMES = r"\*"
134 DIVIDE = r"/"
136 PLUS = r"\+"
138 MINUS = r"-"
140 # Scanning Functions for tokens which
141 # require additional operations
142 # regular expressions are supplied via @_ decorator
143
144 @_(r"\[") # noqa: F821
145 def LBRACK(self, t):
146 """
147 Scan opening bracket.
148
149 Parameters:
150 t (sly.lex.token): token of type LBRACK
151
152 Raises:
153 SyntaxError: if no following closing bracket is found
154 in the input.
155
156 Side Effect:
157 Pushes 'BRACK' onto control_token_stack
158
159 Returns:
160 sly.lex.Token
161 """
162 if "]" not in self.text[self.index:]:
163 raise SyntaxError("Unmatched '[' in cut.")
164 self.control_token_stack.append("BRACK")
165 return t
166
167 @_(r"\]") # noqa: F821
168 def RBRACK(self, t):
169 """
170 Scan closing bracket.
171
172 Parameters:
173 t (sly.lex.token): token of type RBRACK
174
175 Raises:
176 SyntaxError: 1. If control_token_stack is empty, which means
177 no bracket was opened previously.
178 2. If state of control_token_stack is 'PAREN', which
179 means a closing parenthesis is expected.
180
181 Side Effect:
182 Pops object from control_token_stack
183
184 Returns:
185 sly.lex.Token
186 """
187 try:
188 state = self.control_token_stack.pop()
189 except IndexError: # pop from empty list
190 raise SyntaxError("Unmatched ']' in cut.")
191 if state == "BRACK":
192 return t
193 elif state == "PAREN":
194 raise SyntaxError("Illegal ']', expected ')'.")
195
196 @_(r"\‍(") # noqa: F821
197 def LPAREN(self, t):
198 """
199 Scan opening parenthesis.
200
201 Parameters:
202 t (sly.lex.token): token of type LPAREN
203
204 Raises:
205 SyntaxError: if no following closing parenthesis is found
206 in the input.
207
208 Side Effect:
209 Pushes 'PAREN' onto control_token_stack
210
211 Returns:
212 sly.lex.Token
213 """
214 if ")" not in self.text[self.index:]:
215 raise SyntaxError("Unmatched '('")
216 self.control_token_stack.append("PAREN")
217 return t
218
219 @_(r"\‍)") # noqa: F821
220 def RPAREN(self, t):
221 """
222 Scan closing parenthesis.
223
224 Parameters:
225 t (sly.lex.token): token of type RPAREN
226
227 Raises:
228 SyntaxError: 1. If control_token_stack is empty, which means
229 no parenthesis was opened previously.
230 2. If state of control_token_stack is 'BRACK', which
231 means a closing bracket is expected.
232
233 Side Effect:
234 Pops state from control_token_stack
235
236 Returns:
237 sly.lex.Token
238 """
239 try:
240 state = self.control_token_stack.pop()
241 except IndexError: # pop from empty list
242 raise SyntaxError("Unmatched ')' in cut.")
243 if state == "BRACK":
244 raise SyntaxError("Illegal ')', expected ']'.")
245 elif state == "PAREN":
246 return t
247
248 @_(r"((\d+\.\d*|\d*\.\d+)(e(-|\+)?\d+|E(-|\+)?\d+)?|\d+(e(-|\+)?\d+|E(-|\+)?\d+))") # noqa: E501, F821
249 def DOUBLE(self, t):
250 """
251 Scanning function for double values
252
253 Parameters:
254 t (sly.lex.Token): initial token generated by the scanner library.
255 The value attribute is of type str initially, equals
256 the matched sequence and is casted to float.
257
258 Possible notations covered by this regular expression:
259 Normal decimal notation e.g 0.1
260 Hanging decimal seperator notation e.g 1.
261 Preceding decimal seperator notation e.g .1
262 Scientific notation with (signed) exponents e.g 1.0E4, 1.e-4, .1E+3
263 Exponents are case insensitive e.g 1.e4, 1.E4
264 Integer with exponent e.g 1E4
265
266 Returns:
267 sly.lex.Token
268 """
269 t.value = float(t.value)
270 return t
271
272 @_(r"(0(x|X)[0-9A-Fa-f]+)|\d+") # noqa: F821
273 def INTEGER(self, t):
274 """
275 Scanning function for integer values
276 Allows normal and hex notation (case insensitive)
277
278 Parameters:
279 t (sly.lex.Token): initial token generated by the scanner library.
280 The value attribute is of type str initially, equals
281 the matched sequence and is casted to int.
282
283 Warning:
284 python int-objects are converted
285 to the standard c++ int datatype (32bit).
286 Overflows can happen because numerical limits
287 of python int and c++ int datatypes differ.
288 If you need to input large values write it as double.
289
290 Returns:
291 sly.lex.Token
292 """
293 try:
294 t.value = int(t.value)
295 except ValueError:
296 # casting hex notation
297 t.value = int(t.value, base=16)
298 return t
299
300 @_(r"[a-zA-Z_][a-zA-Z_0-9]*") # noqa: F821
301 def IDENTIFIER(self, t):
302 """
303 Scaning function for identifiers
304
305 If a matched sequence equals reserved keywords of other tokens
306 the token type and value is remapped via the reserved dictionary.
307
308 Parameters:
309 t (sly.lex.Token): initial token generated by the scanner library.
310 value attribute equals the matched sequence.
311
312 Returns:
313 sly.lex.Token
314 """
315 reserved = {
316 "and": "AND",
317 "or": "OR",
318 "not": "NOT",
319 "True": "BOOLEAN",
320 "true": "BOOLEAN",
321 "False": "BOOLEAN",
322 "false": "BOOLEAN",
323 "nan": "DOUBLE",
324 "infinity": "DOUBLE",
325 "inf": "DOUBLE",
326 }
327 # Check for reserved words
328 t.type = reserved.get(t.value, "IDENTIFIER")
329
330 # Set value to bool if BOOLEAN type was returned from reserved dict.
331 if t.type == "BOOLEAN":
332 t.value = t.value == "True" or t.value == "true"
333 # Take care of special infinity and nan values.
334 if t.type == "DOUBLE":
335 t.value = float(t.value)
336 if t.type == "IDENTIFIER":
337 try:
338 if self.text[self.index] == "(":
339 # Check that closing parenthesis exists
340 if ")" not in self.text[self.index:]:
341 raise SyntaxError("Unmatched '('")
342 else:
343 self.push_state(B2ParameterLexer)
344 except IndexError:
345 pass
346 return t
347
348
349def parser_class_decorator(cls, parser_type):
350 """
351 Class decorator which allows creating a Parser class object
352 for the B2Parser and B2ExpressionParser without repeating the class body.
353
354 Args:
355 parser_type (str): choice of parser type, 'cut' or 'expression'
356
357 Returns:
358 (type): returns a parser class object
359 """
360 assert parser_type in (
361 "cut",
362 "expression",
363 ), "Invalid parser type, valid choices are 'cut' or 'expression'"
364
365 class B2ParserMixin(cls):
366 """
367 Parser class implementing the grammar specified below.
368
369 Full Grammar Specification:
370 <cut> ::= EMPTY
371 | <boolean_expression>
372
373 <boolean_expression> ::= <disjunction>
374
375 <disjunction> ::= <conjunction>
376 | <disjunction> OR <conjunction>
377
378 <conjunction> ::= <negation>
379 | <conjunction> AND <negation>
380
381 <negation> ::= <bracket_expression>
382 | NOT <negation>
383
384 <bracket_expression> ::= <relational_expression>
385 | LBRACK <boolean_expression> RBRACK
386
387 <relational_expression> ::= <expression>
388 | <expression> <comparison_operator> <expression>
389 | <expression> <comparison_operator> <expression>
390 <comparison_operator> <expression>
391
392 <comparison_operator> ::= EQUALEQUAL
393 | GREATER
394 | LESS
395 | GREATEREQUAL
396 | LESSEQUAL
397 | NOTEQUAL
398
399 <expression> ::= <sum>
400
401 <sum> ::= <term>
402 | <sum> PLUS <term>
403 | <sum> MINUS <term>
404
405 <term> ::= <factor>
406 | <term> TIMES <factor>
407 | <term> DIVIDE <factor>
408
409 <factor> ::= <power>
410 | PLUS <factor>
411 | MINUS <factor>
412
413 <power> ::= <primary>
414 | <primary> POWER <factor>
415
416 <primary> ::= LPAREN <expression> RPAREN
417 | <function>
418 | IDENTIFIER
419 | INTEGER
420 | BOOLEAN
421 | DOUBLE
422
423 <function> ::= IDENTIFIER ARGUMENTTUPLE
424 """
425
426 def __init__(self, verbose=False):
427 """
428 Initialize Parser
429 @param verbose run parser in verbose mode. The nodetype names in
430 the parsed tuple are written out and not encoded
431 as integers. Useful for debugging parsing errors.
432 """
433 super().__init__()
434
437 self.verbose = verbose
438
440 self.parameter_stack = list()
441
442 if parser_type == "cut":
443
444 tokens = B2Lexer.tokens.union(B2ParameterLexer.tokens)
445 else:
446
447 tokens = B2Lexer.expression_tokens.union(B2ParameterLexer.tokens)
448 start = parser_type
449 # Define precedence of operators starting with lowest precedence
450 # first element of tuple indicates associativity of operator
451 if parser_type == "cut":
452
453 precedence = ( # noqa: F841
454 ("left", "OR"),
455 ("left", "AND"),
456 ("nonassoc", "NOT"),
457 ("left", "EQUALEQUAL", "GREATER", "LESS",
458 "GREATEREQUAL", "LESSEQUAL", "NOTEQUAL"),
459 ("left", "PLUS", "MINUS"),
460 ("left", "TIMES", "DIVIDE"),
461 ("right", "POWER"),
462 )
463 else:
464
465 precedence = ( # noqa: F841
466 ("left", "PLUS", "MINUS"),
467 ("left", "TIMES", "DIVIDE"),
468 ("right", "POWER"),
469 )
470
472 node_types = {
473 "UnaryBooleanNode": 0,
474 "BinaryBooleanNode": 1,
475 "UnaryRelationalNode": 2,
476 "BinaryRelationalNode": 3,
477 "TernaryRelationalNode": 4,
478 "UnaryExpressionNode": 5,
479 "BinaryExpressionNode": 6,
480 "FunctionNode": 7,
481 "IdentifierNode": 8,
482 "DoubleNode": 9,
483 "IntegerNode": 10,
484 "BooleanNode": 11,
485 }
486
487
489 b_operator_types = {
490 "and": 0,
491 "or": 1,
492 }
493
494
496 c_operator_types = {
497 "==": 0,
498 ">=": 1,
499 "<=": 2,
500 ">": 3,
501 "<": 4,
502 "!=": 5,
503 }
504
505
507 a_operation_types = {
508 "+": 0,
509 "-": 1,
510 "*": 2,
511 "/": 3,
512 "**": 4,
513 "^": 4
514 }
515
516 def get_node_type(self, node_name: str):
517 """
518 Return the node type integer value
519 or node name if verbose setting is chosen.
520 """
521 return node_name if self.verbose else self.node_types[node_name]
522
523 def get_coper_type(self, coper_name: str):
524 """
525 Return the comparison operator type integer value
526 or comparison operator name if verbose setting is chosen.
527 """
528 return coper_name if self.verbose else self.c_operator_types[coper_name] # noqa: E501
529
530 def get_boper_type(self, boper_name: str):
531 """
532 Return the boolean operator type integer value
533 or boolean operator name if verbose setting is chosen.
534 """
535 return boper_name if self.verbose else self.b_operator_types[boper_name] # noqa: E501
536
537 def get_a_operation_type(self, operation_name: str):
538 """
539 Return the arithmetic operator type integer value
540 or arithmetic operator token if verbose setting is chosen.
541 """
542 return operation_name if self.verbose else self.a_operation_types[operation_name] # noqa: E501
543
544 if parser_type == "cut":
545 @_(r"", r"boolean_expression",) # noqa: F821
546 def cut(self, p):
547 """
548 Parsing function for <cut> nonterminal
549
550 Grammar rules:
551 <cut> ::= EMPTY
552 | <boolean_expression>
553 """
554 try:
555 return p.boolean_expression
556 except AttributeError:
557 return (
558 self.get_node_type("UnaryRelationalNode"),
559 (
560 self.get_node_type("BooleanNode"),
561 True
562 )
563 )
564
565 @_(r"disjunction") # noqa: F821
566 def boolean_expression(self, p):
567 """
568 Parsing function for <boolean_expression> nonterminal
569
570 Grammar rule:
571 <boolean_expression> ::= <disjunction>
572 """
573 return p.disjunction
574
575 @_(r"disjunction OR conjunction", r"conjunction") # noqa: F821
576 def disjunction(self, p):
577 """
578 Parsing function for <disjunction> nonterminal
579
580 Grammar rules:
581 <disjunction> ::= <conjunction>
582 | <disjunction> OR <conjunction>
583 """
584 try:
585 return (
586 self.get_node_type("BinaryBooleanNode"),
587 p.disjunction,
588 p.conjunction,
589 self.get_boper_type(p.OR),
590 )
591 except AttributeError:
592 return p.conjunction
593
594 @_(r"conjunction AND negation", r"negation") # noqa: F821
595 def conjunction(self, p):
596 """
597 Parsing function for <conjunction> nonterminal
598
599 Grammar rules:
600 <conjunction> ::= <negation>
601 | <conjunction> AND <negation>
602 """
603 try:
604 return (
605 self.get_node_type("BinaryBooleanNode"),
606 p.conjunction,
607 p.negation,
608 self.get_boper_type(p.AND),
609 )
610 except AttributeError:
611 return p.negation
612
613 @_(r"bracket_expression", r"NOT negation") # noqa: F821
614 def negation(self, p):
615 """
616 Parsing function for <negation> nonterminal
617
618 Grammar rules:
619 <negation> ::= <bracket_expression>
620 | NOT <negation>
621 """
622 try:
623 return p.bracket_expression
624 except AttributeError:
625 return (
626 self.get_node_type("UnaryBooleanNode"),
627 p.negation,
628 True,
629 False,
630 )
631
632 @_( # noqa: F821
633 r"relational_expression",
634 r"LBRACK boolean_expression RBRACK")
635 def bracket_expression(self, p):
636 """
637 Parsing function for <bracket_expression> nonterminal
638
639 Grammar rules:
640 <bracket_expression> ::= <relational_expression>
641 | LBRACK <boolean_expression> RBRACK
642 """
643 try:
644 return p.relational_expression
645 except AttributeError:
646 return (
647 self.get_node_type("UnaryBooleanNode"),
648 p.boolean_expression,
649 False,
650 True,
651 )
652
653 @_(r"expression") # noqa: F821
654 def relational_expression(self, p): # noqa: F811
655 """
656 Parsing function for <relational_expression> nonterminal
657
658 Grammar rule:
659 <relational_expression> ::= <expression>
660 """
661
662 return (self.get_node_type("UnaryRelationalNode"), p.expression)
663
664 @_(r"expression comparison_operator expression") # noqa: F821
665 def relational_expression(self, p): # noqa: F811
666 """
667 Parsing function for <relational_expression> nonterminal
668
669 Grammar rule:
670 <relational_expression> ::= <expression> <comparison_operator>
671 <expression>
672 """
673 return (
674 self.get_node_type("BinaryRelationalNode"),
675 p.expression0,
676 p.expression1,
677 self.get_coper_type(p.comparison_operator),
678 )
679
680 @_(r"expression comparison_operator expression comparison_operator expression") # noqa: F821, E501
681 def relational_expression(self, p): # noqa: F811
682 """
683 Parsing function for <relational_expression> nonterminal
684
685 Grammar rule:
686 <relational_expression> ::= expression> <comparison_operator>
687 <expression> <comparison_operator> <expression>
688 """
689 return (
690 self.get_node_type("TernaryRelationalNode"),
691 p.expression0,
692 p.expression1,
693 p.expression2,
694 self.get_coper_type(p.comparison_operator0),
695 self.get_coper_type(p.comparison_operator1),
696 )
697
698 @_( # noqa: F821
699 r"EQUALEQUAL",
700 r"GREATER",
701 r"LESS",
702 r"GREATEREQUAL",
703 r"LESSEQUAL",
704 r"NOTEQUAL",
705 )
706 def comparison_operator(self, p):
707 """
708 Parsing function for <comparison_operator> nonterminal
709
710 Grammar rules:
711 <comparison_operator> ::= EQUALEQUAL
712 | GREATER
713 | LESS
714 | GREATEREQUAL
715 | LESSEQUAL
716 | NOTEQUAL
717 """
718 return p[0]
719
720 @_(r"sum") # noqa: F821
721 def expression(self, p):
722 """
723 Parsing function for <expression> nonterminal
724
725 Grammar rule:
726 <expression> ::= <sum>
727 """
728 return p.sum
729
730 @_(r"sum PLUS term", r"sum MINUS term", r"term") # noqa: F821
731 def sum(self, p):
732 """
733 Parsing function for <sum> nonterminal
734
735 Grammar rules:
736 <sum> ::= <term>
737 | <sum> PLUS <term>
738 | <sum> MINUS <term>
739 """
740 try:
741 return (
742 self.get_node_type("BinaryExpressionNode"),
743 p.sum,
744 p.term,
745 self.get_a_operation_type(p[1]),
746 )
747 except AttributeError:
748 return p.term
749
750 @_(r"term TIMES factor", r"term DIVIDE factor", r"factor") # noqa: F821, E501
751 def term(self, p):
752 """
753 Parsing function for <term> nonterminal
754
755 Grammar rules:
756 <term> ::= <factor>
757 | <term> TIMES <factor>
758 | <term> DIVIDE <factor>
759 """
760 try:
761 return (
762 self.get_node_type("BinaryExpressionNode"),
763 p.term,
764 p.factor,
765 self.get_a_operation_type(p[1]),
766 )
767 except AttributeError:
768 return p.factor
769
770 @_(r"power") # noqa: F821
771 def factor(self, p):
772 """
773 Parsing function for <power> nonterminal
774
775 Grammar rule:
776 <factor> ::= <power>
777 """
778 return p.power
779
780 @_(r"PLUS factor") # noqa: F821
781 def factor(self, p): # noqa: F811
782 """
783 Parsing function for <factor> nonterminal
784
785 Grammar rules:
786 <factor> ::= PLUS <factor>
787 """
788 return (
789 self.get_node_type("UnaryExpressionNode"),
790 p.factor,
791 False,
792 False,
793 )
794
795 @_(r"MINUS factor") # noqa: F821
796 def factor(self, p): # noqa: F811
797 """
798 Parsing function for <factor> nonterminal
799
800 Grammar rule:
801 <factor> ::= MINUS factor
802 """
803 return (
804 self.get_node_type("UnaryExpressionNode"),
805 p.factor,
806 True,
807 False,
808 )
809
810 @_(r"primary") # noqa: F821
811 def power(self, p):
812 """
813 Parsing function for <power> nonterminal
814
815 Grammar rule:
816 <power> ::= <primary>
817 """
818 return p.primary
819
820 @_(r"primary POWER factor") # noqa: F821
821 def power(self, p): # noqa: F811
822 """
823 Parsing function for <power> nonterminal
824
825 Grammar rule:
826 <power> ::= <primary> POWER <factor>
827 """
828 return (
829 self.get_node_type("BinaryExpressionNode"),
830 p.primary,
831 p.factor,
832 self.get_a_operation_type(p.POWER),
833 )
834
835 @_(r"function") # noqa: F821
836 def primary(self, p):
837 """
838 Parsing function for <primary> nonterminal
839
840 Grammar rule:
841 <primary> ::= <function>
842 """
843 return p.function
844
845 @_(r"LPAREN expression RPAREN") # noqa: F821
846 def primary(self, p): # noqa: F811
847 """
848 Parsing function for <primary> nonterminal
849
850 Grammar rule:
851 <primary> ::= LPAREN <expression> RPAREN
852 """
853 return (
854 self.get_node_type("UnaryExpressionNode"),
855 p.expression,
856 False,
857 True,
858 )
859
860 @_(r"INTEGER") # noqa: F821
861 def primary(self, p): # noqa: F811
862 """
863 Parsing function for <primary> nonterminal
864
865 Grammar rule:
866 <primary> ::= INTEGER
867 """
868 return (self.get_node_type("IntegerNode"), p.INTEGER)
869
870 @_(r"DOUBLE") # noqa: F821
871 def primary(self, p): # noqa: F811
872 """
873 Parsing function for <primary> nonterminal
874
875 Grammar rule:
876 <primary> ::= DOUBLE
877 """
878 return (self.get_node_type("DoubleNode"), p.DOUBLE)
879
880 @_(r"BOOLEAN") # noqa: F821
881 def primary(self, p): # noqa: F811
882 """
883 Parsing function for <primary> nonterminal
884
885 Grammar rule:
886 <primary> ::= BOOLEAN
887 """
888 return (self.get_node_type("BooleanNode"), p.BOOLEAN)
889
890 @_(r"IDENTIFIER") # noqa: F821
891 def primary(self, p): # noqa: F811
892 """
893 Parsing function for <primary> nonterminal
894
895 Grammar rule:
896 <primary> ::= IDENTIFIER
897 """
898 if self.parameter_stack:
899 return (
900 self.get_node_type("IdentifierNode"),
901 p.IDENTIFIER,
902 )
903 else:
904 return (
905 self.get_node_type("IdentifierNode"),
906 p.IDENTIFIER,
907 )
908
909 @_(r"IDENTIFIER ARGUMENTTUPLE") # noqa: F821
910 def function(self, p):
911 """
912 Parsing function for <function> nonterminal
913
914 Grammar rule:
915 <function> ::= IDENTIFIER LPAREN <parameters> RPAREN
916 """
917 if self.parameter_stack:
918 return (
919 self.get_node_type("FunctionNode"),
920 p.IDENTIFIER,
921 p.ARGUMENTTUPLE[1:-1],
922 )
923 else:
924 return (
925 self.get_node_type("FunctionNode"),
926 p.IDENTIFIER,
927 p.ARGUMENTTUPLE[1:-1],
928 )
929
930 def error(self, p):
931 """
932 Error function, called immediately if syntax error is detected
933 @param p (sly.token) offending token p
934 p is None if syntax error occurs at EOF.
935 """
936 try:
937 # Get error position of offending token in cut.
938 error_pos = p.index
939 except AttributeError: # syntax error at EOF, p is None
940 # Set error position to length of cut minus one.
941 error_pos = len(self.cut) - 1
942 try:
943 # Get error token type
944 error_token = p.type
945 except AttributeError:
946 # syntax error at EOF get last token from stack
947 error_token = self.symstack[-1].type
948
949 # Format error message
950 error_msg = f"detected at:\n{self.cut}\n{' '*error_pos}^\n"
951 error_msg += f"Unexpected token '{error_token}'"
952 raise SyntaxError(error_msg)
953
954 def parse(self, cut: str, token_generator) -> tuple:
955 """
956 Overwrite sly.Parser parse function.
957 @param cut unparsed cut input which is used to
958 indicate where the error occurred
959 @param token_generator generator object which yields tokens.
960 Produced by the lexer from the cut input.
961 """
962
963 self.cut = cut
964 return super().parse(token_generator)
965
966 return B2ParserMixin
967
968
969B2Parser = parser_class_decorator(Parser, parser_type="cut")
970
971B2ExpressionParser = parser_class_decorator(Parser, parser_type="expression")
972
973
974def parse(cut: str, verbose=False) -> tuple:
975 """
976 Initialize a parser and lexer object and parse cut
977 @param cut cut string which should be parsed
978 @param verbose provide verbose parsing output for
979 parser debugging purposes, not to be set true in production
980 """
981 lexer = B2Lexer()
982 parser = B2Parser(verbose)
983 return parser.parse(cut, lexer.tokenize(cut))
984
985
986def parse_expression(cut: str, verbose=False) -> tuple:
987 """
988 Initialize a parser and lexer object and parse cut
989 @param cut cut string which should be parsed
990 @param verbose provide verbose parsing output for
991 parser debugging purposes, not to be set true in production
992 """
993 lexer = B2Lexer()
994 parser = B2ExpressionParser(verbose)
995 return parser.parse(cut, lexer.tokenize(cut))
996
997
998if __name__ == "__main__":
999 argparser = argparse.ArgumentParser()
1000 argparser.add_argument(
1001 "-e", "--expression", action="store_const", default=0, const=1
1002 )
1003 args = argparser.parse_args()
1004 if args.expression:
1005 cut = input("Please input expression:\n")
1006 print(parse_expression(cut))
1007 else:
1008 cut = input("Please input cut:\n")
1009 print(parse(cut))
1010
def LBRACK(self, t)
Definition: b2parser.py:146
def LPAREN(self, t)
Definition: b2parser.py:198
def __init__(self)
Definition: b2parser.py:79
control_token_stack
control_token_stack (list): stack for keeping track of seen brackets and parenthesis.
Definition: b2parser.py:84
def RBRACK(self, t)
Definition: b2parser.py:169
def ARGUMENTTUPLE(self, t)
Definition: b2parser.py:51
index
Increment current scanning position.
Definition: b2parser.py:66