diff options
Diffstat (limited to 'atk16_asm/parser.py')
| -rw-r--r-- | atk16_asm/parser.py | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/atk16_asm/parser.py b/atk16_asm/parser.py new file mode 100644 index 0000000..4823450 --- /dev/null +++ b/atk16_asm/parser.py @@ -0,0 +1,138 @@ +from typing import Callable, Literal, cast +from dataclasses import dataclass +import re + +RegisterValue = Literal["RA", "RB", "RC", "RD", "RE", "RF", "RG", "RH"] +REGISTERS: list[RegisterValue] = ["RA", "RB", "RC", "RD", "RE", "RF", "RG", "RH"] +@dataclass +class RegisterRef: + """Reference to a register, e.g. RA""" + reg: RegisterValue + + def __str__(self) -> str: + return self.reg + +@dataclass +class Immediate: + """Immediate value, e.g. 0x1234""" + value: int + base: Literal["dec", "hex", "bin"] + + def __str__(self) -> str: + if self.base == "dec": + return str(self.value) + elif self.base == "hex": + return f"0x{self.value:>04x}" + elif self.base == "bin": + return f"0b{self.value:>016b}" + + raise Exception("Invalid base: {self.base}") + +@dataclass +class Label: + """Label declaration, e.g. @loop""" + label: str + + def __str__(self) -> str: + return f"@{self.label}" + +@dataclass +class LabelRef: + """Label reference, e.g. &loop""" + label: str + + def __str__(self) -> str: + return f"&{self.label}" + +FlagValue = Literal[0, 1, 2, 3] +FLAGS = ["carry", "overflow", "zero", "sign"] +@dataclass +class Flag: + """ALU Flag, e.g. zero""" + num: FlagValue + + @staticmethod + def from_str(flag: str) -> "Flag": + return Flag(cast(FlagValue, FLAGS.index(flag))) + + def __str__(self) -> str: + return FLAGS[self.num] + +ALUCodeValue = Literal[0, 1, 2, 3, 4, 5, 6, 7] +ALU_CODES = ["al_plus", "al_minus", "al_and", "al_or", "al_xor", "al_sll", "al_slr", "al_sar"] +@dataclass +class ALUCode: + """ALU operation code, e.g. al_plus""" + code: ALUCodeValue + + @staticmethod + def from_str(code: str) -> "ALUCode": + return ALUCode(cast(ALUCodeValue, ALU_CODES.index(code))) + + def __str__(self) -> str: + return ALU_CODES[self.code] + +@dataclass +class Expr: + """Python expression that can be evaluated to an immediate value""" + expr: str + + def __str__(self) -> str: + return f"${{{self.expr}}}" + +@dataclass +class Directive: + """Directive, e.g. #address""" + directive: str + + def __str__(self) -> str: + return f"#${self.directive}" + +@dataclass +class StringLiteral: + """String literal, e.g. "hello world" """ + string: str + + def __str__(self) -> str: + return f'"{self.string}"' + +@dataclass +class Symbol: + """Symbol, e.g. loop""" + symbol: str + + def __str__(self) -> str: + return self.symbol + +Term = RegisterRef | Immediate | LabelRef | Label | Flag | ALUCode | Expr | Directive | StringLiteral | Symbol + +symbol_pattern = "[a-zA-Z_][a-zA-Z0-9_]*" + +def parse(tokens: list[str]) -> list[Term]: + def parse_token(term: str, idx: int) -> Term: + if term in REGISTERS: + return RegisterRef(term) + elif term in FLAGS: + return Flag.from_str(term) + elif term.startswith("${") and term.endswith("}"): + return Expr(term[2:-1]) + elif term.startswith("\"") and term.endswith("\""): + return StringLiteral(term[1:-1]) + elif term.startswith("&"): + return LabelRef(term[1:]) + elif term.startswith("@"): + return Label(term[1:]) + elif term.startswith("#"): + return Directive(term[1:]) + elif term.startswith("0x"): + return Immediate(int(term.replace("_", ""), 16), "hex") + elif term.startswith("0b"): + return Immediate(int(term.replace("_", ""), 2), "bin") + elif term.isdigit(): + return Immediate(int(term.replace("_", ""), 10), "dec") + elif re.match(symbol_pattern, term) is not None: + return Symbol(term) + else: + raise Exception(f"Invalid term \"{term}\" at index {idx} in {tokens}") + + return [parse_token(token, idx) for idx, token in enumerate(tokens)] |
