Skip to content

Commit 9aaf376

Browse files
committed
refact: code clean up in the peephole parser
1 parent 9afbb6d commit 9aaf376

3 files changed

Lines changed: 71 additions & 69 deletions

File tree

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,6 @@ norecursedirs = ["test_*tmp", "runtime"]
7878

7979
[tool.isort]
8080
profile = "black"
81+
82+
[tool.mypy]
83+
enable_recursive_aliases = true

src/arch/z80/peephole/parser.py

Lines changed: 64 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
43
import re
54
import sys
65
from collections import defaultdict
7-
from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union
6+
from types import MappingProxyType
7+
from typing import Any, Final, NamedTuple
8+
9+
from src.api import errmsg, global_
810

9-
import src.api.global_
10-
from src.arch.z80.peephole import evaluator, pattern
11+
from . import pattern
12+
from .evaluator import BINARY, FN, OPERS, UNARY, Evaluator
1113

12-
TreeType = List[Union[str, List[Any]]]
14+
TreeType = list[str | list["TreeType"]]
1315

14-
COMMENT = ";;"
16+
COMMENT: Final[str] = ";;"
1517
RE_REGION = re.compile(r"([_a-zA-Z][a-zA-Z0-9]*)[ \t]*{{")
1618
RE_DEF = re.compile(r"([_a-zA-Z][a-zA-Z0-9]*)[ \t]*:[ \t]*(.*)")
1719
RE_IFPARSE = re.compile(r'"(""|[^"])*"|[(),]|\b[_a-zA-Z]+\b|[^," \t()]+')
@@ -27,19 +29,21 @@
2729
O_FLAG = "OFLAG"
2830

2931
# Operators : priority (lower number -> highest priority)
30-
IF_OPERATORS = {
31-
evaluator.FN.OP_NMUL: 3,
32-
evaluator.FN.OP_NDIV: 3,
33-
evaluator.FN.OP_PLUS: 5,
34-
evaluator.FN.OP_NPLUS: 5,
35-
evaluator.FN.OP_NSUB: 5,
36-
evaluator.FN.OP_NE: 10,
37-
evaluator.FN.OP_EQ: 10,
38-
evaluator.FN.OP_AND: 15,
39-
evaluator.FN.OP_OR: 20,
40-
evaluator.FN.OP_IN: 25,
41-
evaluator.FN.OP_COMMA: 30,
42-
}
32+
IF_OPERATORS: Final[MappingProxyType[FN, int]] = MappingProxyType(
33+
{
34+
FN.OP_NMUL: 3,
35+
FN.OP_NDIV: 3,
36+
FN.OP_PLUS: 5,
37+
FN.OP_NPLUS: 5,
38+
FN.OP_NSUB: 5,
39+
FN.OP_NE: 10,
40+
FN.OP_EQ: 10,
41+
FN.OP_AND: 15,
42+
FN.OP_OR: 20,
43+
FN.OP_IN: 25,
44+
FN.OP_COMMA: 30,
45+
}
46+
)
4347

4448

4549
# Which one of the above are REGIONS (that is, {{ ... }} blocks)
@@ -53,7 +57,7 @@
5357
REQUIRED = (REG_REPLACE, REG_WITH, O_LEVEL, O_FLAG)
5458

5559

56-
def simplify_expr(expr: List[Any]) -> List[Any]:
60+
def simplify_expr(expr: list[Any]) -> list[Any]:
5761
"""Simplifies ("unnest") a list, removing redundant brackets.
5862
i.e. [[x, [[y]]] becomes [x, [y]]
5963
"""
@@ -75,14 +79,14 @@ class SourceLine(NamedTuple):
7579
# Defines a define expr with its linenum
7680
class DefineLine(NamedTuple):
7781
lineno: int
78-
expr: evaluator.Evaluator
82+
expr: Evaluator
7983

8084

81-
def parse_ifline(if_line: str, lineno: int) -> Optional[TreeType]:
85+
def parse_ifline(if_line: str, lineno: int) -> TreeType | None:
8286
"""Given a line from within a IF region (i.e. $1 == "af'")
8387
returns it as a list of tokens ['$1', '==', "af'"]
8488
"""
85-
stack: List[TreeType] = []
89+
stack: list[TreeType] = []
8690
expr: TreeType = []
8791
paren = 0
8892
error_ = False
@@ -98,7 +102,7 @@ def parse_ifline(if_line: str, lineno: int) -> Optional[TreeType]:
98102

99103
tok = qq.group()
100104
if not RE_ID.match(tok):
101-
for oper in evaluator.OPERS:
105+
for oper in OPERS:
102106
if tok.startswith(oper):
103107
tok = tok[: len(oper)]
104108
break
@@ -111,19 +115,19 @@ def parse_ifline(if_line: str, lineno: int) -> Optional[TreeType]:
111115
expr = []
112116
continue
113117

114-
if tok in evaluator.UNARY:
118+
if tok in UNARY:
115119
stack.append(expr)
116120
expr = [tok]
117121
continue
118122

119123
if tok == ")":
120124
paren -= 1
121125
if paren < 0:
122-
src.api.errmsg.warning(lineno, "Too much closed parenthesis")
126+
errmsg.warning(lineno, "Too much closed parenthesis")
123127
return None
124128

125-
if expr and expr[-1] == evaluator.FN.OP_COMMA:
126-
src.api.errmsg.warning(lineno, "missing element in list")
129+
if expr and expr[-1] == FN.OP_COMMA:
130+
errmsg.warning(lineno, "missing element in list")
127131
return None
128132

129133
stack[-1].append(expr)
@@ -133,31 +137,31 @@ def parse_ifline(if_line: str, lineno: int) -> Optional[TreeType]:
133137
tok = tok[1:-1]
134138
expr.append(tok)
135139

136-
if tok == evaluator.FN.OP_COMMA:
140+
if tok == FN.OP_COMMA:
137141
if len(expr) < 2 or expr[-2] == tok:
138-
src.api.errmsg.warning(lineno, "Unexpected {} in list".format(tok))
142+
errmsg.warning(lineno, "Unexpected {} in list".format(tok))
139143
return None
140144

141145
while len(expr) == 2 and isinstance(expr[-2], str):
142-
op: Union[str, TreeType] = expr[-2]
143-
if op in evaluator.UNARY:
146+
op: str | TreeType = expr[-2]
147+
if op in UNARY:
144148
stack[-1].append(expr)
145149
expr = stack.pop()
146150
else:
147151
break
148152

149-
if len(expr) == 3 and expr[1] != evaluator.FN.OP_COMMA:
150-
left_, op, right_ = expr # type: ignore
153+
if len(expr) == 3 and expr[1] != FN.OP_COMMA:
154+
left_, op, right_ = expr
151155
if not isinstance(op, str) or op not in IF_OPERATORS:
152-
src.api.errmsg.warning(lineno, "Unexpected binary operator '{0}'".format(op))
156+
errmsg.warning(lineno, "Unexpected binary operator '{0}'".format(op))
153157
return None
154158
if isinstance(left_, list) and len(left_) == 3 and IF_OPERATORS[left_[-2]] > IF_OPERATORS[op]:
155159
expr = [[left_[:-2], left_[-2], [left_[-1], op, right_]]] # Rebalance tree
156160
else:
157161
expr = [expr]
158162

159163
if not error_ and paren:
160-
src.api.errmsg.warning(lineno, "unclosed parenthesis in IF section")
164+
errmsg.warning(lineno, "unclosed parenthesis in IF section")
161165
return None
162166

163167
while stack and not error_:
@@ -166,32 +170,32 @@ def parse_ifline(if_line: str, lineno: int) -> Optional[TreeType]:
166170

167171
if len(expr) == 2:
168172
op = expr[0]
169-
if not isinstance(op, str) or op not in evaluator.UNARY:
170-
src.api.errmsg.warning(lineno, "unexpected unary operator '{0}'".format(op))
173+
if not isinstance(op, str) or op not in UNARY:
174+
errmsg.warning(lineno, "unexpected unary operator '{0}'".format(op))
171175
return None
172176
elif len(expr) == 3:
173177
op = expr[1]
174-
if not isinstance(op, str) or op not in evaluator.BINARY:
175-
src.api.errmsg.warning(lineno, "unexpected binary operator '{0}'".format(op))
178+
if not isinstance(op, str) or op not in BINARY:
179+
errmsg.warning(lineno, "unexpected binary operator '{0}'".format(op))
176180
return None
177181

178182
if error_:
179-
src.api.errmsg.warning(lineno, "syntax error in IF section")
183+
errmsg.warning(lineno, "syntax error in IF section")
180184
return None
181185

182186
return simplify_expr(expr)
183187

184188

185-
def parse_define_line(sourceline: SourceLine) -> Tuple[Optional[str], Optional[TreeType]]:
189+
def parse_define_line(sourceline: SourceLine) -> tuple[str | None, TreeType | None]:
186190
"""Given a line $nnn = <expression>, returns a tuple the parsed
187191
("$var", [expression]) or None, None if error."""
188192
if "=" not in sourceline.line:
189-
src.api.errmsg.warning(sourceline.lineno, "assignation '=' not found")
193+
errmsg.warning(sourceline.lineno, "assignation '=' not found")
190194
return None, None
191195

192-
result: List[str] = [x.strip() for x in sourceline.line.split("=", 1)]
196+
result: list[str] = [x.strip() for x in sourceline.line.split("=", 1)]
193197
if not pattern.RE_SVAR.match(result[0]): # Define vars
194-
src.api.errmsg.warning(sourceline.lineno, "'{0}' not a variable name".format(result[0]))
198+
errmsg.warning(sourceline.lineno, "'{0}' not a variable name".format(result[0]))
195199
return None, None
196200

197201
right_part = parse_ifline(result[1], sourceline.lineno)
@@ -201,7 +205,7 @@ def parse_define_line(sourceline: SourceLine) -> Tuple[Optional[str], Optional[T
201205
return result[0], right_part
202206

203207

204-
def parse_str(spec: str) -> Optional[Dict[str, Union[str, int, TreeType]]]:
208+
def parse_str(spec: str) -> dict[str, str | int | TreeType] | None:
205209
"""Given a string with an optimizer template definition,
206210
parses it and return a python object as a result.
207211
If any error is detected, fname will be used as filename.
@@ -210,26 +214,26 @@ def parse_str(spec: str) -> Optional[Dict[str, Union[str, int, TreeType]]]:
210214
ST_INITIAL = 0
211215
ST_REGION = 1
212216

213-
result: Dict[str, Any] = defaultdict(list)
217+
result: dict[str, Any] = defaultdict(list)
214218
state = ST_INITIAL
215219
line_num = 0
216220
region_name = None
217221
is_ok = True
218222

219-
def add_entry(key: str, val: Union[str, int, TreeType]) -> bool:
223+
def add_entry(key: str, val: str | int | TreeType) -> bool:
220224
key = key.upper()
221225
if key in result:
222-
src.api.errmsg.warning(line_num, "duplicated definition {0}".format(key))
226+
errmsg.warning(line_num, "duplicated definition {0}".format(key))
223227
return False
224228

225229
if key not in REGIONS and key not in SCALARS:
226-
src.api.errmsg.warning(line_num, "unknown definition parameter '{0}'".format(key))
230+
errmsg.warning(line_num, "unknown definition parameter '{0}'".format(key))
227231
return False
228232

229233
if key in NUMERIC:
230234
assert isinstance(val, str)
231235
if not RE_INT.match(val):
232-
src.api.errmsg.warning(line_num, "field '{0} must be integer".format(key))
236+
errmsg.warning(line_num, "field '{0} must be integer".format(key))
233237
return False
234238
val = int(val)
235239

@@ -238,7 +242,7 @@ def add_entry(key: str, val: Union[str, int, TreeType]) -> bool:
238242

239243
def check_entry(key: str) -> bool:
240244
if key not in result:
241-
src.api.errmsg.warning(line_num, "undefined section {0}".format(key))
245+
errmsg.warning(line_num, "undefined section {0}".format(key))
242246
return False
243247

244248
return True
@@ -279,7 +283,7 @@ def check_entry(key: str) -> bool:
279283
region_name = None
280284
continue
281285

282-
src.api.errmsg.warning(line_num, "syntax error. Cannot parse file")
286+
errmsg.warning(line_num, "syntax error. Cannot parse file")
283287
is_ok = False
284288
break
285289

@@ -291,10 +295,10 @@ def check_entry(key: str) -> bool:
291295
is_ok = False
292296
break
293297
if var_ in defined_vars:
294-
src.api.errmsg.warning(source_line.lineno, "duplicated variable '{0}'".format(var_))
298+
errmsg.warning(source_line.lineno, "duplicated variable '{0}'".format(var_))
295299
is_ok = False
296300
break
297-
defines.append([var_, DefineLine(expr=evaluator.Evaluator(expr), lineno=source_line.lineno)])
301+
defines.append([var_, DefineLine(expr=Evaluator(expr), lineno=source_line.lineno)])
298302
defined_vars.add(var_)
299303
result[REG_DEFINE] = defines
300304

@@ -313,25 +317,25 @@ def check_entry(key: str) -> bool:
313317

314318
if is_ok:
315319
if not result[REG_REPLACE]: # Empty REPLACE region??
316-
src.api.errmsg.warning(line_num, "empty region {0}".format(REG_REPLACE))
320+
errmsg.warning(line_num, "empty region {0}".format(REG_REPLACE))
317321
is_ok = False
318322

319323
if not is_ok:
320-
src.api.errmsg.warning(line_num, "this optimizer template will be ignored due to errors")
324+
errmsg.warning(line_num, "this optimizer template will be ignored due to errors")
321325
return None
322326

323327
return result
324328

325329

326330
def parse_file(fname: str):
327331
"""Opens and parse a file given by filename"""
328-
tmp = src.api.global_.FILENAME
329-
src.api.global_.FILENAME = fname # set filename so it shows up in error/warning msgs
332+
tmp = global_.FILENAME
333+
global_.FILENAME = fname # set filename so it shows up in error/warning msgs
330334

331335
with open(fname, "rt") as f:
332336
result = parse_str(f.read())
333337

334-
src.api.global_.FILENAME = tmp # restores original filename
338+
global_.FILENAME = tmp # restores original filename
335339
return result
336340

337341

tests/arch/zx48k/peephole/test_parser.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import unittest
44

55
from src.arch.z80.peephole import parser
6+
from src.arch.z80.peephole.evaluator import Evaluator
67

78

89
class TestParser(unittest.TestCase):
@@ -98,9 +99,7 @@ def test_parse_chain_plus(self):
9899
self.assertDictEqual(
99100
result,
100101
{
101-
"DEFINE": [
102-
["$3", parser.DefineLine(lineno=8, expr=parser.evaluator.Evaluator([["$1", "+", "$1"], "+", "$1"]))]
103-
],
102+
"DEFINE": [["$3", parser.DefineLine(lineno=8, expr=Evaluator([["$1", "+", "$1"], "+", "$1"]))]],
104103
"IF": [],
105104
"OFLAG": 27,
106105
"OLEVEL": 1,
@@ -127,9 +126,7 @@ def test_parse_chain_parent(self):
127126
self.assertDictEqual(
128127
result,
129128
{
130-
"DEFINE": [
131-
["$3", parser.DefineLine(lineno=8, expr=parser.evaluator.Evaluator([["(", "+", "$1"], "+", ")"]))]
132-
],
129+
"DEFINE": [["$3", parser.DefineLine(lineno=8, expr=Evaluator([["(", "+", "$1"], "+", ")"]))]],
133130
"IF": [],
134131
"OFLAG": 27,
135132
"OLEVEL": 1,
@@ -225,9 +222,7 @@ def test_define_concat(self):
225222
"DEFINE": [
226223
[
227224
"$3",
228-
parser.DefineLine(
229-
lineno=11, expr=parser.evaluator.Evaluator([[["ld ", "+", "$2"], "+", ", "], "+", "$1"])
230-
),
225+
parser.DefineLine(lineno=11, expr=Evaluator([[["ld ", "+", "$2"], "+", ", "], "+", "$1"])),
231226
]
232227
],
233228
"IF": [],

0 commit comments

Comments
 (0)