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