Skip to content

Commit 056f92f

Browse files
committed
Add typing to opt parser
Partially done, because recursive typing is not supported.
1 parent fbe4c74 commit 056f92f

1 file changed

Lines changed: 66 additions & 40 deletions

File tree

src/arch/zx48k/peephole/parser.py

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,28 @@
44
import sys
55
import re
66
from 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+
716
import 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

1223
COMMENT = ';;'
1324
RE_REGION = re.compile(r'([_a-zA-Z][a-zA-Z0-9]*)[ \t]*{{')
1425
RE_DEF = re.compile(r'([_a-zA-Z][a-zA-Z0-9]*)[ \t]*:[ \t]*(.*)')
1526
RE_IFPARSE = re.compile(r'"(""|[^"])*"|[(),]|\b[_a-zA-Z]+\b|[^," \t()]+')
1627
RE_ID = re.compile(r'\b[_a-zA-Z]+\b')
28+
RE_INT = re.compile(r'^\d+$')
1729

1830
# Names of the different params
1931
REG_IF = 'IF'
@@ -50,29 +62,37 @@
5062
REQUIRED = (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

Comments
 (0)