prog8/tinyvm/parse.py
Irmen de Jong ed8267b1ec tinyvm
2018-02-25 16:43:00 +01:00

194 lines
6.5 KiB
Python

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()