diff --git a/src/arch/z80/peephole/evaluator.py b/src/arch/z80/peephole/evaluator.py index dae65ee11..b7e90fa09 100644 --- a/src/arch/z80/peephole/evaluator.py +++ b/src/arch/z80/peephole/evaluator.py @@ -2,7 +2,7 @@ import re from collections.abc import Callable -from enum import Enum, unique +from enum import StrEnum, unique from typing import Any from src.api import utils @@ -13,7 +13,7 @@ @unique -class FN(str, Enum): +class FN(StrEnum): OP_NOT = "!" OP_PLUS = "+" OP_EQ = "==" @@ -215,28 +215,31 @@ def eval(self, vars_: dict[str, Any] | None = None) -> str | Evaluator | list[An val = self.expression[0] if not isinstance(val, str): return val + if val == "$": return val + if not RE_SVAR.match(val): return val + if val not in vars_: raise UnboundVarError(f"Unbound variable '{val}'") + return vars_[val] if len(self.expression) == 2: - oper = self.expression[0] + oper = FN(self.expression[0]) assert oper in UNARY operand = self.expression[1].eval(vars_) - # FIXME - return self.normalize(UNARY[oper](operand)) # type: ignore[index] + return self.normalize(UNARY[oper](operand)) if len(self.expression) == 3 and self.expression[1] != FN.OP_COMMA: - assert self.expression[1] in BINARY + oper = FN(self.expression[1]) + assert oper in BINARY # Do lazy evaluation left_ = lambda: self.expression[0].eval(vars_) right_ = lambda: self.expression[2].eval(vars_) - # FIXME - return self.normalize(BINARY[self.expression[1]](left_, right_)) # type: ignore[index] + return self.normalize(BINARY[oper](left_, right_)) # It's a list return [x.eval(vars_) for i, x in enumerate(self.expression) if not i % 2] diff --git a/src/arch/z80/peephole/parser.py b/src/arch/z80/peephole/parser.py index f17959420..edf12ceb6 100644 --- a/src/arch/z80/peephole/parser.py +++ b/src/arch/z80/peephole/parser.py @@ -11,7 +11,7 @@ TreeType = list[str | list["TreeType"]] COMMENT: Final[str] = ";;" -RE_REGION = re.compile(r"([_a-zA-Z][a-zA-Z0-9]*)[ \t]*{{") +RE_REGION = re.compile(r"([_a-zA-Z][a-zA-Z0-9]*)[ \t]*\{\{$") RE_DEF = re.compile(r"([_a-zA-Z][a-zA-Z0-9]*)[ \t]*:[ \t]*(.*)") RE_IFPARSE = re.compile(r'"(""|[^"])*"|[(),]|\b[_a-zA-Z]+\b|[^," \t()]+') RE_ID = re.compile(r"\b[_a-zA-Z]+\b") @@ -152,11 +152,15 @@ def parse_ifline(if_line: str, lineno: int) -> TreeType | None: if not isinstance(op, str) or op not in IF_OPERATORS: errmsg.warning(lineno, f"Unexpected binary operator '{op}'") return None - # FIXME - if isinstance(left_, list) and len(left_) == 3 and IF_OPERATORS[left_[-2]] > IF_OPERATORS[op]: # type: ignore[index] - expr = [[left_[:-2], left_[-2], [left_[-1], op, right_]]] # Rebalance tree - else: - expr = [expr] + + oper = FN(op) + if isinstance(left_, list) and len(left_) == 3: + oper2 = FN(left_[-2]) + if IF_OPERATORS[oper2] > IF_OPERATORS[oper]: + expr = [[left_[:-2], left_[-2], [left_[-1], op, right_]]] # Rebalance tree + continue + + expr = [expr] if not error_ and paren: errmsg.warning(lineno, "unclosed parenthesis in IF section") diff --git a/tests/arch/zx48k/peephole/test_parser.py b/tests/arch/zx48k/peephole/test_parser.py index f70062fdb..314409deb 100644 --- a/tests/arch/zx48k/peephole/test_parser.py +++ b/tests/arch/zx48k/peephole/test_parser.py @@ -355,3 +355,46 @@ def test_in_list(self): "WITH": ["pop $1", "$2"], "DEFINE": [], } + + def test_parse_cond(self): + result = parser.parse_str( + """ + OLEVEL: 1 + OFLAG: 14 + REPLACE {{ + $1 + }} + + WITH {{ + }} + + IF {{ + $1 == "nop" + }} + """ + ) + assert result == { + "OLEVEL": 1, + "OFLAG": 14, + "REPLACE": ["$1"], + "WITH": [], + "DEFINE": [], + "IF": ["$1", "==", "nop"], + } + + def test_parse_if_must_start_in_a_new_line(self): + result = parser.parse_str( + """ + OLEVEL: 1 + OFLAG: 14 + REPLACE {{ + $1 + }} + + WITH {{ + }} + ;; this is not valid + IF {{ $1 == "nop" }} + """ + ) + assert result is None