from typing import Optional, List, Tuple, Dict from .core import DataType, Opcode, Program, Block, Variable, Instruction from .vm import StackValueType class ParseError(Exception): pass class Parser: def __init__(self, source: str) -> None: self.source = source.splitlines() self.lineno = 0 def parse(self) -> Program: blocklist = [] while self.lineno < len(self.source): self.skip_empty() blocklist.append(self.parse_block(None)) return Program(blocklist) def skip_empty(self) -> None: while self.lineno < len(self.source): line = self.source[self.lineno].strip() if not line or line.startswith(";"): self.lineno += 1 else: break def parse_block(self, parent: Optional[Block]) -> Block: assert self.source[self.lineno].startswith("%block") blockname = self.source[self.lineno].split()[1] variables = [] # type: List[Variable] instructions = [] # type: List[Instruction] labels = {} # type: Dict[str, Instruction] self.lineno += 1 self.skip_empty() if self.source[self.lineno].startswith("%vardefs"): variables = self.parse_vardefs() self.skip_empty() if self.source[self.lineno].startswith("%instructions"): instructions, labels = self.parse_instructions() block = Block(blockname, parent, variables, instructions, labels, []) self.skip_empty() if self.source[self.lineno].startswith("%subblocks"): block.blocks = self.parse_subblocks(block) self.skip_empty() assert self.source[self.lineno].startswith("%end_block") self.lineno += 1 return block def parse_vardefs(self) -> List[Variable]: assert self.source[self.lineno].startswith("%vardefs") self.lineno += 1 variables = [] while not self.source[self.lineno].startswith("%"): vartype, datatype, name, argstr = self.source[self.lineno].split(maxsplit=3) dtype = DataType[datatype.upper()] length = height = 0 value = None # type: StackValueType if dtype in (DataType.BYTE, DataType.WORD, DataType.SBYTE, DataType.SWORD): value = int(argstr) elif dtype == DataType.FLOAT: value = float(argstr) elif dtype == DataType.BOOL: value = argstr.lower() not in ("0", "false") elif dtype in (DataType.ARRAY_BYTE, DataType.ARRAY_SBYTE, DataType.ARRAY_WORD, DataType.ARRAY_SWORD): args = argstr.split(maxsplit=1) length = int(args[0]) valuestr = args[1] if valuestr[0] == '[' and valuestr[-1] == ']': value = bytearray([int(v) for v in valuestr[1:-1].split()]) else: value = bytearray([int(valuestr)]) * length elif dtype in (DataType.MATRIX_BYTE, DataType.MATRIX_SBYTE): args = argstr.split(maxsplit=2) length = int(args[0]) height = int(args[1]) valuestr = args[2] if valuestr[0] == '[' and valuestr[-1] == ']': value = bytearray([int(v) for v in valuestr[1:-1].split()]) else: value = bytearray([int(valuestr)]) * length * height else: raise TypeError("weird dtype", dtype) if vartype == "const" and dtype not in (DataType.BYTE, DataType.WORD, DataType.SBYTE, DataType.SWORD, DataType.FLOAT, DataType.BOOL): raise TypeError("invalid const datatype", dtype) variables.append(Variable(name, dtype, value, length, height, vartype == "const")) self.lineno += 1 self.skip_empty() assert self.source[self.lineno].startswith("%end_vardefs") self.lineno += 1 return variables def parse_instructions(self) -> Tuple[List[Instruction], Dict[str, Instruction]]: assert self.source[self.lineno].startswith("%instructions") self.lineno += 1 instructions = [] labels = {} # type: Dict[str, Instruction] def parse_instruction(ln: str) -> Instruction: parts = ln.split(maxsplit=1) opcode = Opcode[parts[0].upper()] if len(parts) == 2: args = parts[1].split() else: args = [] return Instruction(opcode, args, None, None) while not self.source[self.lineno].startswith("%"): line = self.source[self.lineno].strip() if line.endswith(":"): # a label that points to an instruction label = line[:-1].rstrip() self.lineno += 1 line = self.source[self.lineno] labels[label] = parse_instruction(line) else: instructions.append(parse_instruction(line)) self.lineno += 1 self.skip_empty() assert self.source[self.lineno].startswith("%end_instructions") self.lineno += 1 return instructions, labels def parse_subblocks(self, parent: Block) -> List[Block]: assert self.source[self.lineno].startswith("%subblocks") self.lineno += 1 blocks = [] while not self.source[self.lineno].startswith("%end_subblocks"): self.lineno += 1 while True: if self.source[self.lineno].startswith("%block"): blocks.append(self.parse_block(parent)) else: break self.skip_empty() self.lineno += 1 return blocks if __name__ == "__main__": src = """ %block b1 %vardefs var byte v1 0 var word w1 2222 var sword ws -3333 const byte c1 99 const sword cws -5444 var array_byte ba 10 33 var array_byte ba2 10 [1 2 3 4 5 6 7 8 9 10] var matrix_byte mxb 4 5 33 var matrix_byte mxb2 3 2 [1 2 3 4 5 6] %end_vardefs %instructions nop nop l1: nop push c1 push2 c1 cws return %end_instructions %subblocks %block b2 %vardefs %end_vardefs %end_block ; b2 %end_subblocks %end_block ;b1 %block b3 %vardefs %end_vardefs %instructions nop nop l1: nop return %end_instructions %end_block ; b3 """ parser = Parser(src) program = parser.parse()