mirror of
https://github.com/irmen/prog8.git
synced 2025-07-18 11:24:18 +00:00
moved
This commit is contained in:
218
python-prototype/tinyvm/parse.py
Normal file
218
python-prototype/tinyvm/parse.py
Normal file
@@ -0,0 +1,218 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
import array
|
||||
from typing import Optional, List, Tuple, Dict, Any
|
||||
from .program import Opcode, Program, Block, Variable, Instruction, Value
|
||||
from .vm import StackValueType
|
||||
from .core import DataType
|
||||
|
||||
|
||||
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 = Value(dtype, int(argstr))
|
||||
elif dtype == DataType.FLOAT:
|
||||
value = Value(dtype, float(argstr))
|
||||
elif dtype == DataType.BOOL:
|
||||
value = Value(dtype, 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 = Value(dtype, array.array(typecode, [int(v) for v in valuestr[1:-1].split()]))
|
||||
else:
|
||||
value = Value(dtype, 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 = Value(dtype, array.array(typecode, [int(v) for v in valuestr[1:-1].split()]))
|
||||
else:
|
||||
value = Value(dtype, array.array(typecode, [int(valuestr)] * length * height))
|
||||
else:
|
||||
raise TypeError("weird dtype", dtype)
|
||||
variables.append(Variable(name, dtype, value, 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()
|
Reference in New Issue
Block a user