2525O_LEVEL = "OLEVEL"
2626O_FLAG = "OFLAG"
2727
28- # Operators : priority (lower number -> highest priority)
28+ # Operators : precedence (lower number -> highest priority)
2929IF_OPERATORS : Final [MappingProxyType [FN , int ]] = MappingProxyType (
3030 {
3131 FN .OP_NMUL : 3 ,
5454REQUIRED = (REG_REPLACE , REG_WITH , O_LEVEL , O_FLAG )
5555
5656
57+ class PeepholeParserSyntaxError (SyntaxError ):
58+ pass
59+
60+
5761def simplify_expr (expr : list [Any ]) -> list [Any ]:
5862 """Simplifies ("unnest") a list, removing redundant brackets.
5963 i.e. [[x, [[y]]] becomes [x, [y]]
@@ -79,23 +83,35 @@ class DefineLine(NamedTuple):
7983 expr : Evaluator
8084
8185
82- def parse_ifline (if_line : str , lineno : int ) -> TreeType | None :
83- """Given a line from within a IF region (i.e. $1 == "af'")
84- returns it as a list of tokens ['$1', '==', "af'"]
85- """
86- stack : list [TreeType ] = []
87- expr : TreeType = []
88- paren = 0
89- error_ = False
86+ class Tokenizer :
87+ def __init__ (self , source : str , lineno : int ) -> None :
88+ self .source = source
89+ self .lineno = lineno
9090
91- while not error_ and if_line :
92- if_line = if_line .strip ()
93- if not if_line :
94- break
95- qq = RE_IFPARSE .match (if_line )
91+ def get_token (self ) -> str :
92+ """Returns next token, or "" as EOL"""
93+ tok = self .lookahead ()
94+ self .source = self .source [len (tok ) :]
95+ return tok
96+
97+ def get_next_token (self ) -> str :
98+ if self .has_finished ():
99+ raise PeepholeParserSyntaxError ("Unexpected EOL" )
100+
101+ return self .get_token ()
102+
103+ def has_finished (self ) -> bool :
104+ return self .source == ""
105+
106+ def lookahead (self ) -> str :
107+ """Returns next token, or "" as EOL"""
108+ self .source = self .source .strip ()
109+ if self .has_finished ():
110+ return ""
111+
112+ qq = RE_IFPARSE .match (self .source )
96113 if not qq :
97- error_ = True
98- break
114+ raise PeepholeParserSyntaxError (f"Syntax error in line { self .lineno } : { self .source } " )
99115
100116 tok = qq .group ()
101117 if not RE_ID .match (tok ):
@@ -104,7 +120,25 @@ def parse_ifline(if_line: str, lineno: int) -> TreeType | None:
104120 tok = tok [: len (oper )]
105121 break
106122
107- if_line = if_line [len (tok ) :]
123+ return tok
124+
125+
126+ def parse_ifline (if_line : str , lineno : int ) -> TreeType | None :
127+ """Given a line from within a IF region (i.e. $1 == "af'")
128+ returns it as a list of tokens ['$1', '==', "af'"]
129+ """
130+ stack : list [TreeType ] = []
131+ expr : TreeType = []
132+ paren = 0
133+ error_ = False
134+ tokenizer = Tokenizer (if_line , lineno )
135+
136+ while not tokenizer .has_finished ():
137+ try :
138+ tok = tokenizer .get_token ()
139+ except PeepholeParserSyntaxError as e :
140+ errmsg .warning (lineno , str (e ))
141+ return None
108142
109143 if tok == "(" :
110144 paren += 1
@@ -139,8 +173,8 @@ def parse_ifline(if_line: str, lineno: int) -> TreeType | None:
139173 errmsg .warning (lineno , f"Unexpected { tok } in list" )
140174 return None
141175
142- while len (expr ) == 2 and isinstance (expr [- 2 ], str ):
143- op : str | TreeType = expr [- 2 ]
176+ while len (expr ) == 2 and isinstance (expr [0 ], str ):
177+ op : str = expr [0 ]
144178 if op in UNARY :
145179 stack [- 1 ].append (expr )
146180 expr = stack .pop ()
@@ -162,7 +196,11 @@ def parse_ifline(if_line: str, lineno: int) -> TreeType | None:
162196
163197 expr = [expr ]
164198
165- if not error_ and paren :
199+ if error_ :
200+ errmsg .warning (lineno , "syntax error in IF section" )
201+ return None
202+
203+ if paren :
166204 errmsg .warning (lineno , "unclosed parenthesis in IF section" )
167205 return None
168206
@@ -181,11 +219,20 @@ def parse_ifline(if_line: str, lineno: int) -> TreeType | None:
181219 errmsg .warning (lineno , f"unexpected binary operator '{ op } '" )
182220 return None
183221
184- if error_ :
185- errmsg .warning (lineno , "syntax error in IF section" )
222+ expr = simplify_expr (expr )
223+ if len (expr ) == 2 and isinstance (expr [- 1 ], str ) and expr [- 1 ] in BINARY :
224+ errmsg .warning (lineno , f"Unexpected binary operator '{ expr [- 1 ]} '" )
225+ return None
226+
227+ if len (expr ) == 3 and (expr [1 ] not in BINARY or expr [1 ] == FN .OP_COMMA ):
228+ errmsg .warning (lineno , f"Unexpected binary operator '{ expr [1 ]} '" )
229+ return None
230+
231+ if len (expr ) > 3 :
232+ errmsg .warning (lineno , "Lists not allowed in IF section condition. Missing operator" )
186233 return None
187234
188- return simplify_expr ( expr )
235+ return expr
189236
190237
191238def parse_define_line (sourceline : SourceLine ) -> tuple [str | None , TreeType | None ]:
@@ -329,12 +376,12 @@ def check_entry(key: str) -> bool:
329376 return result
330377
331378
332- def parse_file (fname : str ):
379+ def parse_file (fname : str ) -> dict [ str , str | int | list [ str | list ]] | None :
333380 """Opens and parse a file given by filename"""
334381 tmp = global_ .FILENAME
335382 global_ .FILENAME = fname # set filename so it shows up in error/warning msgs
336383
337- with open (fname , "rt" ) as f :
384+ with open (fname , "rt" , encoding = "utf-8" ) as f :
338385 result = parse_str (f .read ())
339386
340387 global_ .FILENAME = tmp # restores original filename
0 commit comments