mirror of
https://github.com/irmen/prog8.git
synced 2024-08-16 10:29:01 +00:00
194 lines
6.5 KiB
Python
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()
|