44import sys
55import re
66from collections import defaultdict , namedtuple
7+
8+ from typing import Any
9+ from typing import Dict
10+ from typing import List
11+ from typing import NamedTuple
12+ from typing import Optional
13+ from typing import Tuple
14+ from typing import Union
15+
716import src .api .global_
817
9- from . import evaluator
10- from . import pattern
18+ from src .arch .zx48k .peephole import evaluator
19+ from src .arch .zx48k .peephole import pattern
20+
21+ TreeType = List [Union [str , List [Any ]]]
1122
1223COMMENT = ';;'
1324RE_REGION = re .compile (r'([_a-zA-Z][a-zA-Z0-9]*)[ \t]*{{' )
1425RE_DEF = re .compile (r'([_a-zA-Z][a-zA-Z0-9]*)[ \t]*:[ \t]*(.*)' )
1526RE_IFPARSE = re .compile (r'"(""|[^"])*"|[(),]|\b[_a-zA-Z]+\b|[^," \t()]+' )
1627RE_ID = re .compile (r'\b[_a-zA-Z]+\b' )
28+ RE_INT = re .compile (r'^\d+$' )
1729
1830# Names of the different params
1931REG_IF = 'IF'
5062REQUIRED = (REG_REPLACE , REG_WITH , O_LEVEL , O_FLAG )
5163
5264
53- def flatten (expr ):
65+ def simplify_expr (expr : List [Any ]) -> List [Any ]:
66+ """ Simplifies ("unnest") a list, removing redundant brackets.
67+ i.e. [[x, [[y]]] becomes [x, [y]]
68+ """
5469 if not isinstance (expr , list ):
5570 return expr
5671
5772 if len (expr ) == 1 and isinstance (expr [0 ], list ):
58- return flatten (expr [0 ])
73+ return simplify_expr (expr [0 ])
5974
60- return [flatten (x ) for x in expr ]
75+ return [simplify_expr (x ) for x in expr ]
6176
6277
6378# Stores a source opt line
64- SourceLine = namedtuple ('SourceLine' , ('lineno' , 'line' ))
79+ class SourceLine (NamedTuple ):
80+ lineno : int
81+ line : str
82+
6583
6684# Defines a define expr with its linenum
67- DefineLine = namedtuple ('DefineLine' , ('lineno' , 'expr' ))
85+ class DefineLine (NamedTuple ):
86+ lineno : int
87+ expr : evaluator .Evaluator
6888
6989
70- def parse_ifline (if_line , lineno ) :
90+ def parse_ifline (if_line : str , lineno : int ) -> Optional [ TreeType ] :
7191 """ Given a line from within a IF region (i.e. $1 == "af'")
7292 returns it as a list of tokens ['$1', '==', "af'"]
7393 """
74- stack = []
75- expr = []
94+ stack : List [ TreeType ] = []
95+ expr : TreeType = []
7696 paren = 0
7797 error_ = False
7898
@@ -87,9 +107,9 @@ def parse_ifline(if_line, lineno):
87107
88108 tok = qq .group ()
89109 if not RE_ID .match (tok ):
90- for op in evaluator .OPERS :
91- if tok .startswith (op ):
92- tok = tok [:len (op )]
110+ for oper in evaluator .OPERS :
111+ if tok .startswith (oper ):
112+ tok = tok [:len (oper )]
93113 break
94114
95115 if_line = if_line [len (tok ):]
@@ -109,11 +129,11 @@ def parse_ifline(if_line, lineno):
109129 paren -= 1
110130 if paren < 0 :
111131 src .api .errmsg .warning (lineno , "Too much closed parenthesis" )
112- return
132+ return None
113133
114134 if expr and expr [- 1 ] == evaluator .OP_COMMA :
115135 src .api .errmsg .warning (lineno , "missing element in list" )
116- return
136+ return None
117137
118138 stack [- 1 ].append (expr )
119139 expr = stack .pop ()
@@ -125,29 +145,29 @@ def parse_ifline(if_line, lineno):
125145 if tok == evaluator .OP_COMMA :
126146 if len (expr ) < 2 or expr [- 2 ] == tok :
127147 src .api .errmsg .warning (lineno , "Unexpected {} in list" .format (tok ))
128- return
148+ return None
129149
130150 while len (expr ) == 2 and isinstance (expr [- 2 ], str ):
131- op = expr [- 2 ]
151+ op : Union [ str , TreeType ] = expr [- 2 ]
132152 if op in evaluator .UNARY :
133153 stack [- 1 ].append (expr )
134154 expr = stack .pop ()
135155 else :
136156 break
137157
138158 if len (expr ) == 3 and expr [1 ] != evaluator .OP_COMMA :
139- left_ , op , right_ = expr
159+ left_ , op , right_ = expr # type: ignore
140160 if not isinstance (op , str ) or op not in IF_OPERATORS :
141161 src .api .errmsg .warning (lineno , "Unexpected binary operator '{0}'" .format (op ))
142- return
162+ return None
143163 if isinstance (left_ , list ) and len (left_ ) == 3 and IF_OPERATORS [left_ [- 2 ]] > IF_OPERATORS [op ]:
144164 expr = [[left_ [:- 2 ], left_ [- 2 ], [left_ [- 1 ], op , right_ ]]] # Rebalance tree
145165 else :
146166 expr = [expr ]
147167
148168 if not error_ and paren :
149169 src .api .errmsg .warning (lineno , "unclosed parenthesis in IF section" )
150- return
170+ return None
151171
152172 while stack and not error_ :
153173 stack [- 1 ].append (expr )
@@ -157,40 +177,40 @@ def parse_ifline(if_line, lineno):
157177 op = expr [0 ]
158178 if not isinstance (op , str ) or op not in evaluator .UNARY :
159179 src .api .errmsg .warning (lineno , "unexpected unary operator '{0}'" .format (op ))
160- return
180+ return None
161181 elif len (expr ) == 3 :
162182 op = expr [1 ]
163183 if not isinstance (op , str ) or op not in evaluator .BINARY :
164184 src .api .errmsg .warning (lineno , "unexpected binary operator '{0}'" .format (op ))
165- return
185+ return None
166186
167187 if error_ :
168188 src .api .errmsg .warning (lineno , "syntax error in IF section" )
169- return
189+ return None
170190
171- return flatten (expr )
191+ return simplify_expr (expr )
172192
173193
174- def parse_define_line (sourceline ) :
194+ def parse_define_line (sourceline : SourceLine ) -> Tuple [ Optional [ str ], Optional [ TreeType ]] :
175195 """ Given a line $nnn = <expression>, returns a tuple the parsed
176196 ("$var", [expression]) or None, None if error. """
177197 if '=' not in sourceline .line :
178198 src .api .errmsg .warning (sourceline .lineno , "assignation '=' not found" )
179199 return None , None
180200
181- result = [x .strip () for x in sourceline .line .split ('=' , 1 )]
201+ result : List [ str ] = [x .strip () for x in sourceline .line .split ('=' , 1 )]
182202 if not pattern .RE_SVAR .match (result [0 ]): # Define vars
183203 src .api .errmsg .warning (sourceline .lineno , "'{0}' not a variable name" .format (result [0 ]))
184204 return None , None
185205
186- result [ 1 ] = parse_ifline (result [1 ], sourceline .lineno )
187- if result [ 1 ] is None :
206+ right_part = parse_ifline (result [1 ], sourceline .lineno )
207+ if right_part is None :
188208 return None , None
189209
190- return result
210+ return result [ 0 ], right_part
191211
192212
193- def parse_str (spec ) :
213+ def parse_str (spec : str ) -> Optional [ Dict [ str , Union [ str , int , TreeType ]]] :
194214 """ Given a string with an optimizer template definition,
195215 parses it and return a python object as a result.
196216 If any error is detected, fname will be used as filename.
@@ -199,14 +219,13 @@ def parse_str(spec):
199219 ST_INITIAL = 0
200220 ST_REGION = 1
201221
202- result = defaultdict (list )
222+ result : Dict [ str , Any ] = defaultdict (list )
203223 state = ST_INITIAL
204224 line_num = 0
205225 region_name = None
206226 is_ok = True
207- re_int = re .compile (r'^\d+$' )
208227
209- def add_entry (key , val ) :
228+ def add_entry (key : str , val : Union [ str , int , TreeType ]) -> bool :
210229 key = key .upper ()
211230 if key in result :
212231 src .api .errmsg .warning (line_num , "duplicated definition {0}" .format (key ))
@@ -217,15 +236,16 @@ def add_entry(key, val):
217236 return False
218237
219238 if key in NUMERIC :
220- if not re_int .match (val ):
239+ assert isinstance (val , str )
240+ if not RE_INT .match (val ):
221241 src .api .errmsg .warning (line_num , "field '{0} must be integer" .format (key ))
222242 return False
223243 val = int (val )
224244
225245 result [key ] = val
226246 return True
227247
228- def check_entry (key ) :
248+ def check_entry (key : str ) -> bool :
229249 if key not in result :
230250 src .api .errmsg .warning (line_num , "undefined section {0}" .format (key ))
231251 return False
@@ -242,12 +262,14 @@ def check_entry(key):
242262 if state == ST_INITIAL :
243263 if line .startswith (COMMENT ):
244264 continue
265+
245266 x = RE_REGION .match (line )
246267 if x :
247268 region_name = x .groups ()[0 ]
248269 state = ST_REGION
249270 add_entry (region_name , [])
250271 continue
272+
251273 x = RE_DEF .match (line )
252274 if x :
253275 if not add_entry (* x .groups ()):
@@ -258,8 +280,10 @@ def check_entry(key):
258280 if line .endswith ('}}' ):
259281 line = line [:- 2 ].strip ()
260282 state = ST_INITIAL
283+
261284 if line :
262- result [region_name ].append (SourceLine (line_num , line ))
285+ result [region_name ].append (SourceLine (line_num , line )) # type: ignore
286+
263287 if state == ST_INITIAL :
264288 region_name = None
265289 continue
@@ -288,9 +312,11 @@ def check_entry(key):
288312 result [reg ] = [x .line for x in result [reg ]]
289313
290314 if is_ok :
291- result [ REG_IF ] = parse_ifline (' ' .join (x for x in result [REG_IF ]), line_num )
292- if result [ REG_IF ] is None :
315+ reg_if = parse_ifline (' ' .join (x for x in result [REG_IF ]), line_num )
316+ if reg_if is None :
293317 is_ok = False
318+ else :
319+ result [REG_IF ] = reg_if
294320
295321 is_ok = is_ok and all (check_entry (x ) for x in REQUIRED )
296322
@@ -301,12 +327,12 @@ def check_entry(key):
301327
302328 if not is_ok :
303329 src .api .errmsg .warning (line_num , "this optimizer template will be ignored due to errors" )
304- return
330+ return None
305331
306332 return result
307333
308334
309- def parse_file (fname ):
335+ def parse_file (fname : str ):
310336 """ Opens and parse a file given by filename
311337 """
312338 tmp = src .api .global_ .FILENAME
0 commit comments