prog8/python-prototype/tinyvm/parse.py

219 lines
7.3 KiB
Python
Raw Normal View History

2018-03-04 16:03:49 +00:00
"""
Simplistic 8/16 bit Virtual Machine to execute a stack based instruction language.
Parser for the simplistic text based program representation
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
2018-02-27 19:39:46 +00:00
import array
2018-03-02 23:07:44 +00:00
from typing import Optional, List, Tuple, Dict, Any
2018-03-05 22:13:19 +00:00
from .program import Opcode, Program, Block, Variable, Instruction, Value
2018-02-25 15:43:00 +00:00
from .vm import StackValueType
2018-03-05 22:13:19 +00:00
from .core import DataType
2018-02-25 15:43:00 +00:00
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
2018-02-27 19:39:46 +00:00
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]
2018-02-25 15:43:00 +00:00
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):
2018-03-04 14:11:45 +00:00
value = Value(dtype, int(argstr))
2018-02-25 15:43:00 +00:00
elif dtype == DataType.FLOAT:
2018-03-04 14:11:45 +00:00
value = Value(dtype, float(argstr))
2018-02-25 15:43:00 +00:00
elif dtype == DataType.BOOL:
2018-03-04 14:11:45 +00:00
value = Value(dtype, argstr.lower() not in ("0", "false"))
2018-02-25 15:43:00 +00:00
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]
2018-02-27 19:39:46 +00:00
typecode = self.get_array_type(dtype)
2018-02-25 15:43:00 +00:00
if valuestr[0] == '[' and valuestr[-1] == ']':
2018-03-04 14:11:45 +00:00
value = Value(dtype, array.array(typecode, [int(v) for v in valuestr[1:-1].split()]))
2018-02-25 15:43:00 +00:00
else:
2018-03-04 14:11:45 +00:00
value = Value(dtype, array.array(typecode, [int(valuestr)]) * length)
2018-02-25 15:43:00 +00:00
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]
2018-02-27 19:39:46 +00:00
typecode = self.get_array_type(dtype)
2018-02-25 15:43:00 +00:00
if valuestr[0] == '[' and valuestr[-1] == ']':
2018-03-04 14:11:45 +00:00
value = Value(dtype, array.array(typecode, [int(v) for v in valuestr[1:-1].split()]))
2018-02-25 15:43:00 +00:00
else:
2018-03-04 14:11:45 +00:00
value = Value(dtype, array.array(typecode, [int(valuestr)] * length * height))
2018-02-25 15:43:00 +00:00
else:
raise TypeError("weird dtype", dtype)
2018-03-04 14:11:45 +00:00
variables.append(Variable(name, dtype, value, vartype == "const"))
2018-02-25 15:43:00 +00:00
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()]
2018-03-02 23:07:44 +00:00
args = [] # type: List[Any]
2018-02-25 15:43:00 +00:00
if len(parts) == 2:
args = parts[1].split()
else:
args = []
2018-03-02 01:49:43 +00:00
if opcode in (Opcode.CALL, Opcode.RETURN):
args[0] = int(args[0]) # the number of arguments/parameters
2018-02-25 15:43:00 +00:00
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]
2018-02-27 21:16:45 +00:00
next_instruction = parse_instruction(line)
labels[label] = next_instruction
instructions.append(next_instruction)
self.lineno += 1
2018-02-25 15:43:00 +00:00
else:
instructions.append(parse_instruction(line))
2018-02-27 21:16:45 +00:00
self.lineno += 1
2018-02-25 15:43:00 +00:00
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
2018-03-02 01:49:43 +00:00
call 3 l1
return 2
2018-02-25 15:43:00 +00:00
%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
2018-03-02 01:49:43 +00:00
return 99
2018-02-25 15:43:00 +00:00
%end_instructions
%end_block ; b3
"""
parser = Parser(src)
program = parser.parse()