import array from typing import Optional, List, Tuple, Dict, Any from .program 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 get_array_type(self, dtype: DataType) -> str: return { DataType.ARRAY_BYTE: 'B', DataType.ARRAY_SBYTE: 'b', DataType.ARRAY_WORD: 'H', DataType.ARRAY_SWORD: 'h', DataType.MATRIX_BYTE: 'B', DataType.MATRIX_SBYTE: 'b' }[dtype] 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] typecode = self.get_array_type(dtype) if valuestr[0] == '[' and valuestr[-1] == ']': value = array.array(typecode, [int(v) for v in valuestr[1:-1].split()]) else: value = array.array(typecode, [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] typecode = self.get_array_type(dtype) if valuestr[0] == '[' and valuestr[-1] == ']': value = array.array(typecode, [int(v) for v in valuestr[1:-1].split()]) else: value = array.array(typecode, [int(valuestr)] * length * height) else: raise TypeError("weird dtype", 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()] args = [] # type: List[Any] if len(parts) == 2: args = parts[1].split() else: args = [] if opcode in (Opcode.CALL, Opcode.RETURN): args[0] = int(args[0]) # the number of arguments/parameters 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] next_instruction = parse_instruction(line) labels[label] = next_instruction instructions.append(next_instruction) self.lineno += 1 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 call 3 l1 return 2 %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 99 %end_instructions %end_block ; b3 """ parser = Parser(src) program = parser.parse()