mirror of
https://github.com/irmen/prog8.git
synced 2025-02-26 11:29:24 +00:00
tinyvm
This commit is contained in:
parent
ac2ed0528f
commit
ed8267b1ec
@ -77,6 +77,8 @@ def char_to_bytevalue(character: str, petscii: bool=True) -> int:
|
||||
|
||||
# ASCII/UNICODE-to-PETSCII translation table
|
||||
# Unicode symbols supported that map to a PETSCII character: £ ↑ ← ♠ ♥ ♦ ♣ π ● ○ and various others
|
||||
|
||||
# @todo cbmcodecs pypi package? extend that with screencode codec?
|
||||
ascii_to_petscii_trans = str.maketrans({
|
||||
'\f': 147, # form feed becomes ClearScreen "{clear}"
|
||||
'\n': 13, # line feed becomes a RETURN "{cr}" (not a line feed)
|
||||
|
@ -150,7 +150,7 @@ reserved = {
|
||||
"if_gt": "IF",
|
||||
"if_lt": "IF",
|
||||
"if_pos": "IF",
|
||||
"if_get": "IF",
|
||||
"if_neg": "IF",
|
||||
}
|
||||
|
||||
|
||||
|
56
testvm.txt
Normal file
56
testvm.txt
Normal file
@ -0,0 +1,56 @@
|
||||
; source code for a tinyvm program
|
||||
%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 linefeed 12 11
|
||||
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 cws
|
||||
jump_if_false l3
|
||||
push c1
|
||||
push2 c1 cws
|
||||
syscall hexstr_signed
|
||||
push w1
|
||||
syscall decimalstr_signed
|
||||
syscall printstr
|
||||
push linefeed
|
||||
syscall printstr
|
||||
l3:
|
||||
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
|
||||
l2:
|
||||
return
|
||||
%end_instructions
|
||||
%end_block ; b3
|
0
tinyvm/__init__.py
Normal file
0
tinyvm/__init__.py
Normal file
98
tinyvm/core.py
Normal file
98
tinyvm/core.py
Normal file
@ -0,0 +1,98 @@
|
||||
import enum
|
||||
from typing import Any, List, Dict, Optional
|
||||
|
||||
|
||||
class Opcode(enum.IntEnum):
|
||||
NOP = 0
|
||||
TERMINATE = 1
|
||||
PUSH = 10
|
||||
PUSH2 = 11
|
||||
PUSH3 = 12
|
||||
POP = 20
|
||||
POP2 = 21
|
||||
POP3 = 22
|
||||
ADD = 100
|
||||
SUB = 101
|
||||
MUL = 102
|
||||
DIV = 103
|
||||
AND = 200
|
||||
OR = 201
|
||||
XOR = 202
|
||||
NOT = 203
|
||||
CMP_EQ = 300
|
||||
CMP_LT = 301
|
||||
CMP_GT = 302
|
||||
CMP_LTE = 303
|
||||
CMP_GTE = 304
|
||||
RETURN = 500
|
||||
JUMP = 501
|
||||
JUMP_IF_TRUE = 502
|
||||
JUMP_IF_FALSE = 503
|
||||
SYSCALL = 504
|
||||
|
||||
|
||||
CONDITIONAL_OPCODES = {Opcode.JUMP_IF_FALSE, Opcode.JUMP_IF_TRUE}
|
||||
|
||||
|
||||
class DataType(enum.IntEnum):
|
||||
BOOL = 1
|
||||
BYTE = 2
|
||||
SBYTE = 3
|
||||
WORD = 4
|
||||
SWORD = 5
|
||||
FLOAT = 6
|
||||
ARRAY_BYTE = 7
|
||||
ARRAY_SBYTE = 8
|
||||
ARRAY_WORD = 9
|
||||
ARRAY_SWORD = 10
|
||||
MATRIX_BYTE = 11
|
||||
MATRIX_SBYTE = 12
|
||||
|
||||
|
||||
class Variable:
|
||||
__slots__ = ["name", "value", "dtype", "length", "height", "const"]
|
||||
|
||||
def __init__(self, name: str, dtype: DataType, value: Any, length: int=0, height: int=0, const: bool=False) -> None:
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.dtype = dtype
|
||||
self.const = const
|
||||
self.length = length
|
||||
self.height = height
|
||||
|
||||
|
||||
class Instruction:
|
||||
__slots__ = ["opcode", "args", "next", "condnext"]
|
||||
|
||||
def __init__(self, opcode: Opcode, args: List[Any], nxt: Optional['Instruction'], condnxt: Optional['Instruction']) -> None:
|
||||
self.opcode = opcode
|
||||
self.args = args
|
||||
self.next = nxt # regular next statement, None=end
|
||||
self.condnext = condnxt # alternate next statement (for condition nodes)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "<Instruction {} args: {}>".format(self.opcode.name, self.args)
|
||||
|
||||
|
||||
class Block:
|
||||
def __init__(self, name: str, parent: 'Block',
|
||||
variables: List[Variable],
|
||||
instructions: List[Instruction],
|
||||
labels: Dict[str, Instruction], # named entry points
|
||||
blocks: List['Block']) -> None:
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
self.variables = variables
|
||||
self.blocks = blocks
|
||||
self.instructions = instructions
|
||||
self.labels = labels
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.parent:
|
||||
return "<Block '{}' in '{}'>".format(self.name, self.parent.name)
|
||||
return "<Block '{}'>".format(self.name)
|
||||
|
||||
|
||||
class Program:
|
||||
def __init__(self, blocks: List[Block]) -> None:
|
||||
self.blocks = blocks
|
13
tinyvm/main.py
Normal file
13
tinyvm/main.py
Normal file
@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from .parse import Parser
|
||||
from .core import Program
|
||||
from .vm import VM
|
||||
|
||||
|
||||
source = open(sys.argv[1]).read()
|
||||
|
||||
parser = Parser(source)
|
||||
program = parser.parse()
|
||||
timerprogram = Program([])
|
||||
vm = VM(program, timerprogram)
|
||||
vm.run()
|
193
tinyvm/parse.py
Normal file
193
tinyvm/parse.py
Normal file
@ -0,0 +1,193 @@
|
||||
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()
|
486
tinyvm/vm.py
Normal file
486
tinyvm/vm.py
Normal file
@ -0,0 +1,486 @@
|
||||
# 8/16 bit virtual machine
|
||||
|
||||
# machine specs:
|
||||
|
||||
# MEMORY: 64K bytes, treated as one single array, indexed per byte, ONLY DATA - NO CODE
|
||||
# elements addressable as one of three elementary data types:
|
||||
# 8-bit byte (singed and unsigned),
|
||||
# 16-bit words (two 8-bit bytes, signed and unsigned) (stored in LSB order),
|
||||
# 5-byte MFLPT floating point
|
||||
# addressing is possible via byte index (for the $0000-$00ff range) or via an unsigned word.
|
||||
#
|
||||
# MEMORY ACCESS: via explicit load and store instructions,
|
||||
# to put a value onto the stack or store the value on the top of the stack,
|
||||
# or in one of the dynamic variables.
|
||||
#
|
||||
# I/O: either via programmed I/O routines:
|
||||
# write [byte to text output/screen],
|
||||
# read [byte from keyboard],
|
||||
# wait [till any input comes available],
|
||||
# check [if input is available)
|
||||
# or via memory-mapped I/O (text screen matrix, keyboard scan register)
|
||||
#
|
||||
# CPU: stack based execution, no registers.
|
||||
# unlimited dynamic variables (v0, v1, ...) that have a value and a type.
|
||||
# types:
|
||||
# 1-bit boolean (can be CONST),
|
||||
# 8-bit byte (singed and unsigned) (can be CONST),
|
||||
# 16-bit words (two 8-bit bytes, signed and unsigned) (can be CONST),
|
||||
# floating point (can be CONST),
|
||||
# array of bytes (signed and unsigned),
|
||||
# array of words (signed and unsigned),
|
||||
# matrix (2-dimensional array) of bytes (signed and unsigned).
|
||||
#
|
||||
# push (constant,
|
||||
# mark, unwind to previous mark.
|
||||
#
|
||||
# CPU INSTRUCTIONS:
|
||||
# stack manipulation mainly:
|
||||
# nop
|
||||
# push var / push2 var1, var2
|
||||
# pop var / pop2 var1, var2
|
||||
# various arithmetic operations, logical operations, boolean comparison operations
|
||||
# jump label
|
||||
# jump_if_true label, jump_if_false label
|
||||
# jump_if_status_XX label special system dependent status register conditional check such as carry bit or overflow bit)
|
||||
# return (return values on stack)
|
||||
# syscall function (special system dependent implementation)
|
||||
# call function (arguments are on stack)
|
||||
# enter / exit (block for function, loop)
|
||||
#
|
||||
# TIMER INTERRUPT: triggered every 1/60th of a second.
|
||||
# executes on a DIFFERENT stack and with a different PROGRAM LIST,
|
||||
# but with the ALL THE SAME DYNAMIC VARIABLES.
|
||||
#
|
||||
|
||||
import time
|
||||
import itertools
|
||||
from .core import Instruction, Variable, Block, Program, Opcode, CONDITIONAL_OPCODES
|
||||
from typing import Dict, List, Tuple, Union
|
||||
from il65.emit import mflpt5_to_float, to_mflpt5
|
||||
|
||||
|
||||
class ExecutionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Memory:
|
||||
def __init__(self):
|
||||
self.mem = bytearray(65536)
|
||||
|
||||
def get_byte(self, index: int) -> int:
|
||||
return self.mem[index]
|
||||
|
||||
def get_sbyte(self, index: int) -> int:
|
||||
return 256 - self.mem[index]
|
||||
|
||||
def get_word(self, index: int) -> int:
|
||||
return self.mem[index] + 256 * self.mem[index+1]
|
||||
|
||||
def get_sword(self, index: int) -> int:
|
||||
return 65536 - (self.mem[index] + 256 * self.mem[index+1])
|
||||
|
||||
def get_float(self, index: int) -> float:
|
||||
return mflpt5_to_float(self.mem[index: index+5])
|
||||
|
||||
def set_byte(self, index: int, value: int) -> None:
|
||||
self.mem[index] = value
|
||||
|
||||
def set_sbyte(self, index: int, value: int) -> None:
|
||||
self.mem[index] = value + 256
|
||||
|
||||
def set_word(self, index: int, value: int) -> None:
|
||||
hi, lo = divmod(value, 256)
|
||||
self.mem[index] = lo
|
||||
self.mem[index+1] = hi
|
||||
|
||||
def set_sword(self, index: int, value: int) -> None:
|
||||
hi, lo = divmod(value + 65536, 256)
|
||||
self.mem[index] = lo
|
||||
self.mem[index+1] = hi
|
||||
|
||||
def set_float(self, index: int, value: float) -> None:
|
||||
self.mem[index: index+5] = to_mflpt5(value)
|
||||
|
||||
|
||||
StackValueType = Union[bool, int, float, bytearray]
|
||||
|
||||
|
||||
class Stack:
|
||||
def __init__(self):
|
||||
self.stack = []
|
||||
|
||||
def debug_peek(self, size: int) -> List[StackValueType]:
|
||||
return self.stack[-size:]
|
||||
|
||||
def size(self) -> int:
|
||||
return len(self.stack)
|
||||
|
||||
def pop(self) -> StackValueType:
|
||||
return self.stack.pop()
|
||||
|
||||
def pop2(self) -> Tuple[StackValueType, StackValueType]:
|
||||
return self.stack.pop(), self.stack.pop()
|
||||
|
||||
def pop3(self) -> Tuple[StackValueType, StackValueType, StackValueType]:
|
||||
return self.stack.pop(), self.stack.pop(), self.stack.pop()
|
||||
|
||||
def push(self, item: StackValueType) -> None:
|
||||
self._typecheck(item)
|
||||
self.stack.append(item)
|
||||
|
||||
def push2(self, first: StackValueType, second: StackValueType) -> None:
|
||||
self._typecheck(first)
|
||||
self._typecheck(second)
|
||||
self.stack.append(first)
|
||||
self.stack.append(second)
|
||||
|
||||
def push3(self, first: StackValueType, second: StackValueType, third: StackValueType) -> None:
|
||||
self._typecheck(first)
|
||||
self._typecheck(second)
|
||||
self._typecheck(third)
|
||||
self.stack.extend([first, second, third])
|
||||
|
||||
def peek(self) -> StackValueType:
|
||||
return self.stack[-1] if self.stack else None
|
||||
|
||||
def swap(self) -> None:
|
||||
x = self.stack[-1]
|
||||
self.stack[-1] = self.stack[-2]
|
||||
self.stack[-2] = x
|
||||
|
||||
def _typecheck(self, value: StackValueType):
|
||||
if type(value) not in (bool, int, float, bytearray):
|
||||
raise TypeError("stack can only contain bool, int, float, bytearray")
|
||||
|
||||
|
||||
# noinspection PyPep8Naming,PyUnusedLocal,PyMethodMayBeStatic
|
||||
class VM:
|
||||
str_encoding = "iso-8859-15" # @todo machine encoding via cbmcodecs or something?
|
||||
str_alt_encoding = "iso-8859-15" # @todo machine encoding via cbmcodecs or something?
|
||||
|
||||
def __init__(self, program: Program, timerprogram: Program) -> None:
|
||||
opcode_names = [oc.name for oc in Opcode]
|
||||
for ocname in opcode_names:
|
||||
if not hasattr(self, "opcode_" + ocname):
|
||||
raise NotImplementedError("missing opcode method for " + ocname)
|
||||
for method in dir(self):
|
||||
if method.startswith("opcode_"):
|
||||
if not method[7:] in opcode_names:
|
||||
raise RuntimeError("opcode method for undefined opcode " + method)
|
||||
self.memory = Memory()
|
||||
self.main_stack = Stack()
|
||||
self.timer_stack = Stack()
|
||||
(self.main_program, self.timer_program), self.variables, self.labels = self.flatten_programs(program, timerprogram)
|
||||
self.connect_instruction_pointers(self.main_program)
|
||||
self.connect_instruction_pointers(self.timer_program)
|
||||
self.program = self.main_program
|
||||
self.stack = self.main_stack
|
||||
self.pc = None # type: Instruction
|
||||
self.previous_pc = None # type: Instruction
|
||||
assert all(i.next for i in self.main_program if i.opcode != Opcode.TERMINATE), "main: all instrs next must be set"
|
||||
assert all(i.next for i in self.timer_program if i.opcode != Opcode.TERMINATE), "main: all instrs next must be set"
|
||||
assert all(i.condnext for i in self.main_program if i.opcode in CONDITIONAL_OPCODES), "timer: all conditional instrs condnext must be set"
|
||||
assert all(i.condnext for i in self.timer_program if i.opcode in CONDITIONAL_OPCODES), "timer: all conditional instrs condnext must be set"
|
||||
|
||||
def flatten_programs(self, *programs: Program) -> Tuple[List[List[Instruction]], Dict[str, Variable], Dict[str, Instruction]]:
|
||||
variables = {} # type: Dict[str, Variable]
|
||||
labels = {} # type: Dict[str, Instruction]
|
||||
flat_programs = [] # type: List[List[Instruction]]
|
||||
for program in programs:
|
||||
for block in program.blocks:
|
||||
flat = self.flatten(block, variables, labels)
|
||||
flat_programs.append(flat)
|
||||
return flat_programs, variables, labels
|
||||
|
||||
def flatten(self, block: Block, variables: Dict[str, Variable], labels: Dict[str, Instruction]) -> List[Instruction]:
|
||||
def block_prefix(b: Block) -> str:
|
||||
if b.parent:
|
||||
return block_prefix(b.parent) + "." + b.name
|
||||
else:
|
||||
return b.name
|
||||
prefix = block_prefix(block)
|
||||
instructions = block.instructions
|
||||
for ins in instructions:
|
||||
if ins.opcode == Opcode.SYSCALL:
|
||||
continue
|
||||
if ins.args:
|
||||
newargs = []
|
||||
for a in ins.args:
|
||||
if type(a) is str:
|
||||
newargs.append(prefix + "." + a)
|
||||
else:
|
||||
newargs.append(a)
|
||||
ins.args = newargs
|
||||
for vardef in block.variables:
|
||||
vname = prefix + "." + vardef.name
|
||||
assert vname not in variables
|
||||
variables[vname] = vardef
|
||||
for name, instr in block.labels.items():
|
||||
name = prefix + "." + name
|
||||
assert name not in labels
|
||||
labels[name] = instr
|
||||
for subblock in block.blocks:
|
||||
instructions.extend(self.flatten(subblock, variables, labels))
|
||||
instructions.append(Instruction(Opcode.TERMINATE, [], None, None))
|
||||
del block.instructions
|
||||
del block.variables
|
||||
del block.labels
|
||||
return instructions
|
||||
|
||||
def connect_instruction_pointers(self, instructions: List[Instruction]) -> None:
|
||||
i1, i2 = itertools.tee(instructions)
|
||||
next(i2, None)
|
||||
for i, nexti in itertools.zip_longest(i1, i2):
|
||||
i.next = nexti
|
||||
if i.opcode in CONDITIONAL_OPCODES:
|
||||
i.condnext = self.labels[i.args[0]]
|
||||
|
||||
def run(self) -> None:
|
||||
last_timer = time.time()
|
||||
self.pc = self.program[0] # first instruction of the main program
|
||||
steps = 1
|
||||
try:
|
||||
while True:
|
||||
now = time.time()
|
||||
# if now - last_timer >= 1/60:
|
||||
# last_timer = now
|
||||
# # start running the timer interrupt program instead
|
||||
# self.previous_pc = self.pc
|
||||
# self.program = self.timer_program
|
||||
# self.stack = self.timer_stack
|
||||
# self.pc = 0
|
||||
# while True:
|
||||
# self.dispatch(self.program[self.pc])
|
||||
# return True
|
||||
# self.pc = self.previous_pc
|
||||
# self.program = self.mainprogram
|
||||
# self.stack = self.mainstack
|
||||
print("Step", steps)
|
||||
self.debug_stack()
|
||||
next_pc = getattr(self, "opcode_" + self.pc.opcode.name)(self.pc)
|
||||
if next_pc:
|
||||
self.pc = self.pc.next
|
||||
steps += 1
|
||||
except Exception as x:
|
||||
print("EXECUTION ERROR")
|
||||
self.debug_stack(5)
|
||||
raise
|
||||
|
||||
def debug_stack(self, size: int=5) -> None:
|
||||
stack = self.stack.debug_peek(size)
|
||||
if len(stack) > 0:
|
||||
print(" stack (top {:d}):".format(size))
|
||||
for i, value in enumerate(reversed(stack), start=1):
|
||||
print(" {:d}. {:s} {!r}".format(i, type(value).__name__, value))
|
||||
else:
|
||||
print(" stack is empty.")
|
||||
if self.pc is not None:
|
||||
print(" instruction:", self.pc)
|
||||
|
||||
def _encodestr(self, string: str, alt: bool=False) -> bytearray:
|
||||
return bytearray(string, self.str_alt_encoding if alt else self.str_encoding)
|
||||
|
||||
def _decodestr(self, bb: bytearray, alt: bool=False) -> str:
|
||||
return str(bb, self.str_alt_encoding if alt else self.str_encoding)
|
||||
|
||||
def opcode_NOP(self, instruction: Instruction) -> bool:
|
||||
# do nothing
|
||||
return True
|
||||
|
||||
def opcode_TERMINATE(self, instruction: Instruction) -> bool:
|
||||
# immediately terminate the VM
|
||||
raise ExecutionError("virtual machine terminated")
|
||||
|
||||
def opcode_PUSH(self, instruction: Instruction) -> bool:
|
||||
# push a value onto the stack
|
||||
value = self.variables[instruction.args[0]].value
|
||||
self.stack.push(value)
|
||||
return True
|
||||
|
||||
def opcode_PUSH2(self, instruction: Instruction) -> bool:
|
||||
# push two values onto the stack
|
||||
value1 = self.variables[instruction.args[0]].value
|
||||
value2 = self.variables[instruction.args[1]].value
|
||||
self.stack.push2(value1, value2)
|
||||
return True
|
||||
|
||||
def opcode_PUSH3(self, instruction: Instruction) -> bool:
|
||||
# push three values onto the stack
|
||||
value1 = self.variables[instruction.args[0]].value
|
||||
value2 = self.variables[instruction.args[1]].value
|
||||
value3 = self.variables[instruction.args[2]].value
|
||||
self.stack.push3(value1, value2, value3)
|
||||
return True
|
||||
|
||||
def opcode_POP(self, instruction: Instruction) -> bool:
|
||||
# pop value from stack and store it in a variable
|
||||
value = self.stack.pop()
|
||||
variable = self.variables[instruction.args[0]]
|
||||
assert not variable.const
|
||||
variable.value = value
|
||||
return True
|
||||
|
||||
def opcode_POP2(self, instruction: Instruction) -> bool:
|
||||
# pop two values from tack and store it in two variables
|
||||
value1, value2 = self.stack.pop2()
|
||||
variable = self.variables[instruction.args[0]]
|
||||
assert not variable.const
|
||||
variable.value = value1
|
||||
variable = self.variables[instruction.args[1]]
|
||||
assert not variable.const
|
||||
variable.value = value2
|
||||
return True
|
||||
|
||||
def opcode_POP3(self, instruction: Instruction) -> bool:
|
||||
# pop three values from tack and store it in two variables
|
||||
value1, value2, value3 = self.stack.pop3()
|
||||
variable = self.variables[instruction.args[0]]
|
||||
assert not variable.const
|
||||
variable.value = value1
|
||||
variable = self.variables[instruction.args[1]]
|
||||
assert not variable.const
|
||||
variable.value = value2
|
||||
variable = self.variables[instruction.args[2]]
|
||||
assert not variable.const
|
||||
variable.value = value3
|
||||
return True
|
||||
|
||||
def opcode_ADD(self, instruction: Instruction) -> bool:
|
||||
# add top to second value on stack and replace them with the result
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 + v1) # type: ignore
|
||||
return True
|
||||
|
||||
def opcode_SUB(self, instruction: Instruction) -> bool:
|
||||
# subtract top from second value on stack and replace them with the result
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 - v1) # type: ignore
|
||||
return True
|
||||
|
||||
def opcode_MUL(self, instruction: Instruction) -> bool:
|
||||
# multiply top with second value on stack and replace them with the result
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 * v1) # type: ignore
|
||||
return True
|
||||
|
||||
def opcode_DIV(self, instruction: Instruction) -> bool:
|
||||
# divide second value by top value on stack and replace them with the result
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 / v1) # type: ignore
|
||||
return True
|
||||
|
||||
def opcode_AND(self, instruction: Instruction) -> bool:
|
||||
# second value LOGICAL_AND top value on stack and replace them with the result
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 and v1)
|
||||
return True
|
||||
|
||||
def opcode_OR(self, instruction: Instruction) -> bool:
|
||||
# second value LOGICAL_OR top value on stack and replace them with the result
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 or v1)
|
||||
return True
|
||||
|
||||
def opcode_XOR(self, instruction: Instruction) -> bool:
|
||||
# second value LOGICAL_XOR top value on stack and replace them with the result
|
||||
v1, v2 = self.stack.pop2()
|
||||
i1 = 1 if v1 else 0
|
||||
i2 = 1 if v2 else 0
|
||||
self.stack.push(bool(i1 ^ i2))
|
||||
return True
|
||||
|
||||
def opcode_NOT(self, instruction: Instruction) -> bool:
|
||||
# replace top value on stack with its LOGICAL_NOT
|
||||
self.stack.push(not self.stack.pop())
|
||||
return True
|
||||
|
||||
def opcode_CMP_EQ(self, instruction: Instruction) -> bool:
|
||||
# replace second and top value on stack with their == comparison
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 == v1)
|
||||
return True
|
||||
|
||||
def opcode_CMP_LT(self, instruction: Instruction) -> bool:
|
||||
# replace second and top value on stack with their < comparison
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 < v1) # type: ignore
|
||||
return True
|
||||
|
||||
def opcode_CMP_GT(self, instruction: Instruction) -> bool:
|
||||
# replace second and top value on stack with their > comparison
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 > v1) # type: ignore
|
||||
return True
|
||||
|
||||
def opcode_CMP_LTE(self, instruction: Instruction) -> bool:
|
||||
# replace second and top value on stack with their <= comparison
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 <= v1) # type: ignore
|
||||
return True
|
||||
|
||||
def opcode_CMP_GTE(self, instruction: Instruction) -> bool:
|
||||
# replace second and top value on stack with their >= comparison
|
||||
v1, v2 = self.stack.pop2()
|
||||
self.stack.push(v2 >= v1) # type: ignore
|
||||
return True
|
||||
|
||||
def opcode_RETURN(self, instruction: Instruction) -> bool:
|
||||
# returns from the current function call
|
||||
# any return values have already been pushed on the stack
|
||||
raise NotImplementedError("return")
|
||||
|
||||
def opcode_JUMP(self, instruction: Instruction) -> bool:
|
||||
# jumps unconditionally by resetting the PC to the given instruction index value
|
||||
return True
|
||||
|
||||
def opcode_JUMP_IF_TRUE(self, instruction: Instruction) -> bool:
|
||||
# pops stack and jumps if that value is true, by resetting the PC to the given instruction index value
|
||||
result = self.stack.pop()
|
||||
if result:
|
||||
self.pc = self.pc.condnext
|
||||
return False
|
||||
return True
|
||||
|
||||
def opcode_JUMP_IF_FALSE(self, instruction: Instruction) -> bool:
|
||||
# pops stack and jumps if that value is false, by resetting the PC to the given instruction index value
|
||||
result = self.stack.pop()
|
||||
if result:
|
||||
return True
|
||||
self.pc = self.pc.condnext
|
||||
return False
|
||||
|
||||
def opcode_SYSCALL(self, instruction: Instruction) -> bool:
|
||||
call = getattr(self, "syscall_" + instruction.args[0], None)
|
||||
if call:
|
||||
return call()
|
||||
else:
|
||||
raise RuntimeError("no syscall method for " + instruction.args[0])
|
||||
|
||||
def syscall_printstr(self) -> bool:
|
||||
value = self.stack.pop()
|
||||
if isinstance(value, bytearray):
|
||||
print(self._decodestr(value), end="")
|
||||
return True
|
||||
else:
|
||||
raise TypeError("printstr expects bytearray value", value)
|
||||
|
||||
def syscall_decimalstr_signed(self) -> bool:
|
||||
value = self.stack.pop()
|
||||
if type(value) is int:
|
||||
self.stack.push(self._encodestr(str(value)))
|
||||
return True
|
||||
else:
|
||||
raise TypeError("decimalstr expects int value", value)
|
||||
|
||||
def syscall_hexstr_signed(self) -> bool:
|
||||
value = self.stack.pop()
|
||||
if type(value) is int:
|
||||
if value >= 0: # type: ignore
|
||||
strvalue = "${:x}".format(value)
|
||||
else:
|
||||
strvalue = "-${:x}".format(-value) # type: ignore
|
||||
self.stack.push(self._encodestr(strvalue))
|
||||
return True
|
||||
else:
|
||||
raise TypeError("hexstr expects int value", value)
|
4
todo.ill
4
todo.ill
@ -7,7 +7,9 @@
|
||||
start:
|
||||
counter ++
|
||||
main.counter ++
|
||||
; @todo float augassign
|
||||
; @
|
||||
|
||||
todo float augassign
|
||||
flt += 1000.1
|
||||
flt *= 2.34
|
||||
flt *= flt
|
||||
|
87
todo2.ill
Normal file
87
todo2.ill
Normal file
@ -0,0 +1,87 @@
|
||||
~ main {
|
||||
|
||||
var b1
|
||||
var .word w1
|
||||
var .float f1
|
||||
memory .byte border = $d020
|
||||
|
||||
start:
|
||||
X++
|
||||
X++
|
||||
return ; dummy
|
||||
X+=2
|
||||
return ; dummy
|
||||
X+=255
|
||||
return ; dummy
|
||||
X--
|
||||
X--
|
||||
return ; dummy
|
||||
X-=2
|
||||
return ; dummy
|
||||
X-=255
|
||||
return ; dummy
|
||||
XY++
|
||||
XY++
|
||||
return ; dummy
|
||||
XY+=2
|
||||
return ; dummy
|
||||
XY+=255
|
||||
return ; dummy
|
||||
XY--
|
||||
XY--
|
||||
return ; dummy
|
||||
XY-=2
|
||||
return ; dummy
|
||||
XY-=255
|
||||
return ; dummy
|
||||
b1++
|
||||
b1++
|
||||
w1++
|
||||
w1++
|
||||
f1++
|
||||
f1++
|
||||
b1--
|
||||
b1--
|
||||
w1--
|
||||
w1--
|
||||
f1--
|
||||
f1--
|
||||
b1+=255
|
||||
w1+=255
|
||||
f1+=255
|
||||
b1-=255
|
||||
w1-=255
|
||||
f1-=255
|
||||
|
||||
[$c000]++
|
||||
[$c000]++
|
||||
[$c000]--
|
||||
[$c000]--
|
||||
[border]++
|
||||
[border]++
|
||||
[border]--
|
||||
[border]--
|
||||
|
||||
X ++
|
||||
X ++
|
||||
X += 255
|
||||
Y--
|
||||
Y--
|
||||
Y-=255
|
||||
XY ++
|
||||
XY ++
|
||||
XY += 255
|
||||
|
||||
;[$c000]+=2
|
||||
;[$c000]+=255
|
||||
;[$c000]+=255
|
||||
;[$c000 .word]+=255
|
||||
;[$c000]-=2
|
||||
;[$c000]-=255
|
||||
;[$c000 .word]-=255
|
||||
;[border]+=2
|
||||
;[border]+=255
|
||||
;[border]-=2
|
||||
;[border]-=255
|
||||
%noreturn
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user