tinyvm codegen started

This commit is contained in:
Irmen de Jong 2018-03-10 20:32:34 +01:00
parent 4d929e00f5
commit ef9176df45
11 changed files with 252 additions and 109 deletions

View File

@ -7,7 +7,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
from typing import Callable
from . import preserving_registers, Context
from ..shared import CodeError, to_hex
from ..shared import CodeGenerationError, to_hex
from ...plyparse import Scope, Assignment, AugAssignment, Register, LiteralValue, SymbolName, VarDef, Dereference
from ...datatypes import REGISTER_BYTES, VarType, DataType
from ...compile import Zeropage
@ -37,7 +37,7 @@ def generate_aug_assignment(ctx: Context) -> None:
assert rvalue.value >= 0, "augassign value can't be < 0"
_generate_aug_reg_int(out, lvalue, stmt.operator, rvalue.value, "", ctx.scope)
else:
raise CodeError("constant integer literal or variable required for now", rvalue) # XXX
raise CodeGenerationError("constant integer literal or variable required for now", rvalue) # XXX
elif isinstance(rvalue, SymbolName):
symdef = ctx.scope.lookup(rvalue.name)
if isinstance(symdef, VarDef):
@ -46,21 +46,21 @@ def generate_aug_assignment(ctx: Context) -> None:
assert symdef.value.const_value() >= 0, "augassign value can't be <0" # type: ignore
_generate_aug_reg_int(out, lvalue, stmt.operator, symdef.value.const_value(), "", ctx.scope) # type: ignore
else:
raise CodeError("aug. assignment value must be integer", rvalue)
raise CodeGenerationError("aug. assignment value must be integer", rvalue)
elif symdef.datatype == DataType.BYTE:
_generate_aug_reg_int(out, lvalue, stmt.operator, 0, symdef.name, ctx.scope)
else:
raise CodeError("variable must be of type byte for now", rvalue) # XXX
raise CodeGenerationError("variable must be of type byte for now", rvalue) # XXX
else:
raise CodeError("can only use variable name as symbol for aug assign rvalue", rvalue)
raise CodeGenerationError("can only use variable name as symbol for aug assign rvalue", rvalue)
elif isinstance(rvalue, Register):
if lvalue.datatype == DataType.BYTE and rvalue.datatype == DataType.WORD:
raise CodeError("cannot assign a combined 16-bit register to a single 8-bit register", rvalue)
raise CodeGenerationError("cannot assign a combined 16-bit register to a single 8-bit register", rvalue)
_generate_aug_reg_reg(out, lvalue, stmt.operator, rvalue, ctx.scope)
elif isinstance(rvalue, Dereference):
print("warning: {}: using indirect/dereferece is very costly".format(rvalue.sourceref))
if rvalue.datatype != DataType.BYTE:
raise CodeError("aug. assignment value must be a byte for now", rvalue)
raise CodeGenerationError("aug. assignment value must be a byte for now", rvalue)
if isinstance(rvalue.operand, (LiteralValue, SymbolName)):
if isinstance(rvalue.operand, LiteralValue):
what = to_hex(rvalue.operand.value)
@ -79,7 +79,7 @@ def generate_aug_assignment(ctx: Context) -> None:
out("\vlda (il65_lib.SCRATCH_ZPWORD1), y")
a_reg = Register(name="A", sourceref=stmt.sourceref) # type: ignore
if 'A' in lvalue.name:
raise CodeError("can't yet use register A in this aug assign lhs", lvalue.sourceref) # @todo
raise CodeGenerationError("can't yet use register A in this aug assign lhs", lvalue.sourceref) # @todo
_generate_aug_reg_reg(out, lvalue, stmt.operator, a_reg, ctx.scope)
if lvalue.name in REGISTER_BYTES:
out("\vst{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name.lower()))
@ -102,7 +102,7 @@ def generate_aug_assignment(ctx: Context) -> None:
out("\vlda (il65_lib.SCRATCH_ZPWORD1), y")
a_reg = Register(name="A", sourceref=stmt.sourceref) # type: ignore
if 'A' in lvalue.name:
raise CodeError("can't yet use register A in this aug assign lhs", lvalue.sourceref) # @todo
raise CodeGenerationError("can't yet use register A in this aug assign lhs", lvalue.sourceref) # @todo
_generate_aug_reg_reg(out, lvalue, stmt.operator, a_reg, ctx.scope)
if lvalue.name != 'X':
if lvalue.name in REGISTER_BYTES:
@ -118,13 +118,13 @@ def generate_aug_assignment(ctx: Context) -> None:
out("\vld{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name[0].lower()))
out("\vld{:s} il65_lib.SCRATCH_ZP2".format(lvalue.name[1].lower()))
else:
raise CodeError("invalid dereference operand type", rvalue)
raise CodeGenerationError("invalid dereference operand type", rvalue)
else:
raise CodeError("invalid rvalue type", rvalue)
raise CodeGenerationError("invalid rvalue type", rvalue)
elif isinstance(lvalue, SymbolName):
raise NotImplementedError("symbolname augassign", lvalue) # XXX
else:
raise CodeError("aug. assignment only implemented for registers and symbols for now", lvalue, stmt.sourceref) # XXX
raise CodeGenerationError("aug. assignment only implemented for registers and symbols for now", lvalue, stmt.sourceref) # XXX
def _generate_aug_reg_int(out: Callable, lvalue: Register, operator: str, rvalue: int, rname: str, scope: Scope) -> None:
@ -227,7 +227,7 @@ def _gen_aug_shiftleft_reg_int(lvalue: Register, out: Callable, rname: str, rval
shifts_A(rvalue)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word
raise CodeGenerationError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word
def _gen_aug_shiftright_reg_int(lvalue: Register, out: Callable, rname: str, rvalue: int, scope: Scope) -> None:
@ -270,7 +270,7 @@ def _gen_aug_shiftright_reg_int(lvalue: Register, out: Callable, rname: str, rva
shifts_A(rvalue)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word
raise CodeGenerationError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word
def _gen_aug_xor_reg_int(lvalue: Register, out: Callable, right_str: str, scope: Scope) -> None:
@ -287,7 +287,7 @@ def _gen_aug_xor_reg_int(lvalue: Register, out: Callable, right_str: str, scope:
out("\veor " + right_str)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
raise CodeGenerationError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
def _gen_aug_or_reg_int(lvalue: Register, out: Callable, right_str: str, scope: Scope) -> None:
@ -304,7 +304,7 @@ def _gen_aug_or_reg_int(lvalue: Register, out: Callable, right_str: str, scope:
out("\vora " + right_str)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
raise CodeGenerationError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
def _gen_aug_and_reg_int(lvalue: Register, out: Callable, right_str: str, scope: Scope) -> None:
@ -321,7 +321,7 @@ def _gen_aug_and_reg_int(lvalue: Register, out: Callable, right_str: str, scope:
out("\vand " + right_str)
out("\vtay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
raise CodeGenerationError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
def _gen_aug_minus_reg_int(lvalue: Register, out: Callable, right_str: str, scope: Scope) -> None:
@ -408,7 +408,7 @@ def _gen_aug_plus_reg_int(lvalue: Register, out: Callable, right_str: str, scope
def _gen_aug_shiftleft_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
if rvalue.name not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo <<=.word
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo <<=.word
if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
@ -425,12 +425,12 @@ def _gen_aug_shiftleft_reg_reg(lvalue: Register, out: Callable, rvalue: Register
out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word
def _gen_aug_shiftright_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
if rvalue.name not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo >>=.word
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo >>=.word
if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
@ -447,12 +447,12 @@ def _gen_aug_shiftright_reg_reg(lvalue: Register, out: Callable, rvalue: Registe
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word
def _gen_aug_xor_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
if rvalue.name not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo ^=.word
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo ^=.word
if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\veor " + to_hex(Zeropage.SCRATCH_B1))
@ -469,12 +469,12 @@ def _gen_aug_xor_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scop
out("\veor " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word
def _gen_aug_or_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
if rvalue.name not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo |=.word
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo |=.word
if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vora " + to_hex(Zeropage.SCRATCH_B1))
@ -491,12 +491,12 @@ def _gen_aug_or_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope
out("\vora " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word
def _gen_aug_and_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
if rvalue.name not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo &=.word
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo &=.word
if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vand " + to_hex(Zeropage.SCRATCH_B1))
@ -513,12 +513,12 @@ def _gen_aug_and_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scop
out("\vand " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word
def _gen_aug_minus_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
if rvalue.name not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo -=.word
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo -=.word
if lvalue.name == "A":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
out("\vsec")
@ -538,12 +538,12 @@ def _gen_aug_minus_reg_reg(lvalue: Register, out: Callable, rvalue: Register, sc
out("\vsbc " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word
def _gen_aug_plus_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
if rvalue.name not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo +=.word
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo +=.word
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
if lvalue.name == "A":
out("\vclc")

View File

@ -6,7 +6,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
from . import Context
from ..shared import CodeError, to_hex
from ..shared import CodeGenerationError, to_hex
from ...plyparse import Goto, SubCall, LiteralValue, SymbolName, Dereference
@ -81,14 +81,14 @@ def _gen_goto_special_if(ctx: Context, stmt: Goto) -> None:
ctx.out("+\t\tjmp ({:s})".format(targetstr))
ctx.out("+")
else:
raise CodeError("invalid if status " + stmt.if_cond)
raise CodeGenerationError("invalid if status " + stmt.if_cond)
else:
if isinstance(stmt.target, LiteralValue) and type(stmt.target.value) is int:
targetstr = to_hex(stmt.target.value)
elif isinstance(stmt.target, SymbolName):
targetstr = stmt.target.name
else:
raise CodeError("invalid goto target type", stmt)
raise CodeGenerationError("invalid goto target type", stmt)
if stmt.if_cond == "true":
ctx.out("\vbne " + targetstr)
elif stmt.if_cond in ("not", "zero"):
@ -111,7 +111,7 @@ def _gen_goto_special_if(ctx: Context, stmt: Goto) -> None:
ctx.out("\vbcc " + targetstr)
ctx.out("\vbeq " + targetstr)
else:
raise CodeError("invalid if status " + stmt.if_cond)
raise CodeGenerationError("invalid if status " + stmt.if_cond)
def _gen_goto_unconditional(ctx: Context, stmt: Goto) -> None:
@ -132,14 +132,14 @@ def _gen_goto_unconditional(ctx: Context, stmt: Goto) -> None:
ctx.out("\vst{:s} il65_lib.SCRATCH_ZPWORD1+1".format(stmt.target.operand.name[1]))
ctx.out("\vjmp (il65_lib.SCRATCH_ZPWORD1)")
else:
raise CodeError("invalid goto target type", stmt)
raise CodeGenerationError("invalid goto target type", stmt)
def _gen_goto_cond(ctx: Context, stmt: Goto, if_cond: str) -> None:
if isinstance(stmt.condition, LiteralValue):
pass # @todo if WITH conditional expression
else:
raise CodeError("no support for evaluating conditional expression yet", stmt) # @todo
raise CodeGenerationError("no support for evaluating conditional expression yet", stmt) # @todo
def generate_subcall(ctx: Context) -> None:

View File

@ -13,8 +13,7 @@ from .variables import generate_block_init, generate_block_vars
from .assignment import generate_assignment, generate_aug_assignment
from .calls import generate_goto, generate_subcall
from .incrdecr import generate_incrdecr
from ..shared import CodeError, to_hex, to_mflpt5
from ...plylex import print_bold
from ..shared import CodeGenerationError, to_hex, to_mflpt5, sanitycheck
from ...plyparse import (Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, ZpOptions,
InlineAssembly, Return, Register, Goto, SubCall, Assignment, AugAssignment, IncrDecr)
@ -45,33 +44,13 @@ class AssemblyGenerator:
raise
def _generate(self, out: Callable) -> None:
self.sanitycheck()
sanitycheck(self.module)
self.header(out)
self.blocks(out)
out("\t.end")
def sanitycheck(self) -> None:
for label in self.module.all_nodes(Label):
if label.name == "start" and label.my_scope().name == "main": # type: ignore
break
else:
print_bold("ERROR: program entry point is missing ('start' label in 'main' block)\n")
raise SystemExit(1)
all_blocknames = [b.name for b in self.module.all_nodes(Block)] # type: ignore
unique_blocknames = set(all_blocknames)
if len(all_blocknames) != len(unique_blocknames):
for name in unique_blocknames:
all_blocknames.remove(name)
raise CodeError("there are duplicate block names", all_blocknames)
zpblock = self.module.zeropage()
if zpblock:
# ZP block contains no code?
for stmt in zpblock.scope.nodes:
if not isinstance(stmt, (Directive, VarDef)):
raise CodeError("ZP block can only contain directive and var")
def header(self, out: Callable) -> None:
out("; code generated by il65.py - codename 'Sick'")
out("; 6502 code generated by il65.py - codename 'Sick'")
out("; source file:", self.module.sourceref.file)
out("; compiled on:", datetime.datetime.now())
out("; output options:", self.module.format, self.module.zp_options)
@ -81,7 +60,7 @@ class AssemblyGenerator:
if self.module.format in (ProgramFormat.PRG, ProgramFormat.BASIC):
if self.module.format == ProgramFormat.BASIC:
if self.module.address != 0x0801:
raise CodeError("BASIC output mode must have load address $0801")
raise CodeGenerationError("BASIC output mode must have load address $0801")
out("; ---- basic program with sys call ----")
out("* = " + to_hex(self.module.address))
year = datetime.datetime.now().year
@ -180,7 +159,7 @@ class AssemblyGenerator:
out("; -- end block subroutines")
if block.scope.float_const_values:
if not self.floats_enabled:
raise CodeError("floating point numbers not enabled via option")
raise CodeGenerationError("floating point numbers not enabled via option")
# generate additional float constants that are used in floating point expressions
out("\n; -- float constants")
for name, value in block.scope.float_const_values.items():

View File

@ -8,7 +8,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
from . import Context, preserving_registers
from ..shared import CodeError, to_hex
from ..shared import CodeGenerationError, to_hex
from ...plyparse import VarDef, Register, IncrDecr, SymbolName, Dereference, LiteralValue, scoped_name
from ...datatypes import VarType, DataType, REGISTER_BYTES
@ -21,14 +21,14 @@ def generate_incrdecr(ctx: Context) -> None:
if stmt.howmuch == 0:
return
if not 0 <= stmt.howmuch <= 255:
raise CodeError("incr/decr value must be 0..255 - other values should have been converted into an AugAssignment")
raise CodeGenerationError("incr/decr value must be 0..255 - other values should have been converted into an AugAssignment")
target = stmt.target # one of Register/SymbolName/Dereference, or a VarDef
if isinstance(target, SymbolName):
symdef = scope.lookup(target.name)
if isinstance(symdef, VarDef):
target = symdef # type: ignore
else:
raise CodeError("cannot incr/decr this", symdef)
raise CodeGenerationError("cannot incr/decr this", symdef)
howmuch_str = str(stmt.howmuch)
if isinstance(target, Register):
@ -82,7 +82,7 @@ def generate_incrdecr(ctx: Context) -> None:
out("\viny")
out("+")
else:
raise CodeError("invalid incr register: " + reg)
raise CodeGenerationError("invalid incr register: " + reg)
else:
if reg == 'A':
# a -= 1..255
@ -131,11 +131,11 @@ def generate_incrdecr(ctx: Context) -> None:
out("\vdey")
out("+")
else:
raise CodeError("invalid decr register: " + reg)
raise CodeGenerationError("invalid decr register: " + reg)
elif isinstance(target, VarDef):
if target.vartype == VarType.CONST:
raise CodeError("cannot modify a constant", target)
raise CodeGenerationError("cannot modify a constant", target)
what_str = scoped_name(target, scope)
if target.datatype == DataType.BYTE:
if stmt.howmuch == 1:
@ -186,7 +186,7 @@ def generate_incrdecr(ctx: Context) -> None:
out("+")
elif target.datatype == DataType.FLOAT:
if not ctx.floats_enabled:
raise CodeError("floating point numbers not enabled via option")
raise CodeGenerationError("floating point numbers not enabled via option")
if stmt.howmuch == 1.0:
# special case for +/-1
with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True):
@ -210,7 +210,7 @@ def generate_incrdecr(ctx: Context) -> None:
else:
out("\vjsr c64flt.float_sub_SW1_from_XY")
else:
raise CodeError("cannot in/decrement memory of type " + str(target.datatype), stmt.howmuch)
raise CodeGenerationError("cannot in/decrement memory of type " + str(target.datatype), stmt.howmuch)
elif isinstance(target, Dereference):
if isinstance(target.operand, (LiteralValue, SymbolName)):
@ -225,7 +225,7 @@ def generate_incrdecr(ctx: Context) -> None:
if stmt.howmuch == 1:
if target.datatype == DataType.FLOAT:
if not ctx.floats_enabled:
raise CodeError("floating point numbers not enabled via option")
raise CodeGenerationError("floating point numbers not enabled via option")
with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True):
out("\vldx " + what)
out("\vldy {:s}+1".format(what))
@ -244,9 +244,9 @@ def generate_incrdecr(ctx: Context) -> None:
out("\vclc" if stmt.operator == "++" else "\vsec")
out("\vjsr il65_lib.incrdecr_deref_word_reg_AY")
else:
raise CodeError("cannot inc/decrement dereferenced literal of type " + str(target.datatype), stmt)
raise CodeGenerationError("cannot inc/decrement dereferenced literal of type " + str(target.datatype), stmt)
else:
raise CodeError("can't inc/dec this by something else as 1 right now", stmt) # XXX
raise CodeGenerationError("can't inc/dec this by something else as 1 right now", stmt) # XXX
elif isinstance(target.operand, Register):
assert target.operand.datatype == DataType.WORD
reg = target.operand.name
@ -259,9 +259,9 @@ def generate_incrdecr(ctx: Context) -> None:
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
out("\vjsr il65_lib.incrdecr_deref_word_reg_" + reg)
else:
raise CodeError("can't inc/dec this by something else as 1 right now", stmt) # XXX
raise CodeGenerationError("can't inc/dec this by something else as 1 right now", stmt) # XXX
else:
raise TypeError("invalid dereference operand type", target)
else:
raise CodeError("cannot inc/decrement", target) # @todo support more
raise CodeGenerationError("cannot inc/decrement", target) # @todo support more

View File

@ -7,7 +7,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
from collections import defaultdict
from typing import Dict, List, Callable, Any, no_type_check
from ..shared import to_hex, to_mflpt5, CodeError
from ..shared import to_hex, to_mflpt5, CodeGenerationError
from ...plyparse import Block, VarDef, LiteralValue, AddressOf
from ...datatypes import DataType, VarType, STRING_DATATYPES
@ -67,7 +67,7 @@ def generate_block_init(out: Callable, block: Block) -> None:
out("\vsta {:s}".format(bytevar.name))
for wordvar in sorted(vars_by_datatype[DataType.WORD], key=lambda vd: vd.value):
if isinstance(wordvar.value, AddressOf):
raise CodeError("addressof is not a compile-time constant value", wordvar.sourceref)
raise CodeGenerationError("addressof is not a compile-time constant value", wordvar.sourceref)
assert isinstance(wordvar.value, LiteralValue) and type(wordvar.value.value) is int
v_hi, v_lo = divmod(wordvar.value.value, 256)
if v_hi != prev_value_a:
@ -130,7 +130,7 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
# a const string is just a string variable in the generated assembly
_generate_string_var(out, vardef)
else:
raise CodeError("invalid const type", vardef)
raise CodeGenerationError("invalid const type", vardef)
out("; memory mapped variables")
for vardef in vars_by_vartype.get(VarType.MEMORY, []):
# create a definition for variables at a specific place in memory (memory-mapped)
@ -151,17 +151,17 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
elif len(vardef.size) == 3:
comment = "matrix of {:d} by {:d}, interleave {:d}".format(vardef.size[0], vardef.size[1], vardef.size[2])
else:
raise CodeError("matrix size must be 2 or 3 numbers")
raise CodeGenerationError("matrix size must be 2 or 3 numbers")
out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), comment)) # type: ignore
else:
raise CodeError("invalid var type")
raise CodeGenerationError("invalid var type")
out("; normal variables - initial values will be set by init code")
if zeropage:
# zeropage uses the zp_address we've allocated, instead of allocating memory here
for vardef in vars_by_vartype.get(VarType.VAR, []):
assert vardef.zp_address is not None
if vardef.datatype.isstring():
raise CodeError("cannot put strings in the zeropage", vardef.sourceref)
raise CodeGenerationError("cannot put strings in the zeropage", vardef.sourceref)
if vardef.datatype.isarray():
size_str = "size " + str(vardef.size)
else:
@ -181,7 +181,7 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
elif vardef.datatype == DataType.FLOAT:
out("{:s}\v.fill 5\t\t; float {}".format(vardef.name, vardef.value.value))
else:
raise CodeError("weird datatype")
raise CodeGenerationError("weird datatype")
elif vardef.datatype in (DataType.BYTEARRAY, DataType.WORDARRAY):
assert len(vardef.size) == 1
if vardef.datatype == DataType.BYTEARRAY:
@ -189,7 +189,7 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
elif vardef.datatype == DataType.WORDARRAY:
out("{:s}\v.fill {:d}*2\t\t; wordarray".format(vardef.name, vardef.size[0]))
else:
raise CodeError("invalid datatype", vardef.datatype)
raise CodeGenerationError("invalid datatype", vardef.datatype)
elif vardef.datatype == DataType.MATRIX:
assert len(vardef.size) == 2
out("{:s}\v.fill {:d}\t\t; matrix {:d}*{:d} bytes"
@ -197,7 +197,7 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
elif vardef.datatype.isstring():
string_vars.append(vardef)
else:
raise CodeError("unknown variable type " + str(vardef.datatype))
raise CodeGenerationError("unknown variable type " + str(vardef.datatype))
# string vars are considered to be a constant, and are not re-initialized.
out("")

View File

@ -8,12 +8,35 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
import math
from ..datatypes import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE
from ..plyparse import Module, Label, Block, Directive, VarDef
from ..plylex import print_bold
class CodeError(Exception):
class CodeGenerationError(Exception):
pass
def sanitycheck(module: Module) -> None:
for label in module.all_nodes(Label):
if label.name == "start" and label.my_scope().name == "main": # type: ignore
break
else:
print_bold("ERROR: program entry point is missing ('start' label in 'main' block)\n")
raise SystemExit(1)
all_blocknames = [b.name for b in module.all_nodes(Block)] # type: ignore
unique_blocknames = set(all_blocknames)
if len(all_blocknames) != len(unique_blocknames):
for name in unique_blocknames:
all_blocknames.remove(name)
raise CodeGenerationError("there are duplicate block names", all_blocknames)
zpblock = module.zeropage()
if zpblock:
# ZP block contains no code?
for stmt in zpblock.scope.nodes:
if not isinstance(stmt, (Directive, VarDef)):
raise CodeGenerationError("ZP block can only contain directive and var")
def to_hex(number: int) -> str:
# 0..15 -> "0".."15"
# 16..255 -> "$10".."$ff"

View File

@ -5,4 +5,4 @@ This is the tinyvm stack based program generator
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
# @todo

View File

@ -0,0 +1,91 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the tinyvm stack based program generator.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
# @todo
import os
import datetime
import pickle
from typing import BinaryIO, List, Tuple, Dict, Optional
from ..shared import CodeGenerationError, sanitycheck
from ...plyparse import Module, Block, Scope, VarDef, Expression, LiteralValue
from ...datatypes import VarType, DataType
import tinyvm.core
import tinyvm.program
class AssemblyGenerator:
def __init__(self, module: Module, enable_floats: bool) -> None:
self.module = module
self.floats_enabled = enable_floats
def generate(self, filename: str) -> None:
with open(filename+".pickle", "wb") as stream:
self._generate(stream)
def _generate(self, out: BinaryIO) -> None:
sanitycheck(self.module)
program = self.header()
program.blocks = self.blocks(self.module.nodes[0], None) # type: ignore
pickle.dump(program, out, pickle.HIGHEST_PROTOCOL)
def header(self) -> tinyvm.program.Program:
return tinyvm.program.Program([])
def blocks(self, scope: Scope, parentblock_vm: Optional[tinyvm.program.Block]) -> List[tinyvm.program.Block]:
blocks = []
for node in scope.nodes:
if isinstance(node, Block):
variables = self.make_vars(node)
labels, instructions = self.make_instructions(node)
vmblock = tinyvm.program.Block(node.name, parentblock_vm, variables, instructions, labels)
print(vmblock)
blocks.append(vmblock)
vmblock.blocks = self.blocks(node.nodes[0], vmblock) # type: ignore
return blocks
def make_vars(self, block: Block) -> List[tinyvm.program.Variable]:
variables = []
for vardef in block.all_nodes(VarDef):
assert isinstance(vardef, VarDef)
dtype = self.translate_datatype(vardef.datatype)
value = self.translate_value(vardef.value, dtype)
if vardef.vartype == VarType.CONST:
const = True
elif vardef.vartype == VarType.VAR:
const = False
else:
raise CodeGenerationError("unsupported vartype", vardef.vartype)
variables.append(tinyvm.program.Variable(vardef.name, dtype, value, const))
return variables
def make_instructions(self, block: Block) -> Tuple[Dict[str, tinyvm.program.Instruction], List[tinyvm.program.Instruction]]:
# returns a dict with the labels (named instruction pointers),
# and a list of the program instructions.
return {}, []
def translate_datatype(self, datatype: DataType) -> tinyvm.core.DataType:
table = {
DataType.BYTE: tinyvm.core.DataType.BYTE,
DataType.WORD: tinyvm.core.DataType.WORD,
DataType.FLOAT: tinyvm.core.DataType.FLOAT,
DataType.BYTEARRAY: tinyvm.core.DataType.ARRAY_BYTE,
DataType.WORDARRAY: tinyvm.core.DataType.ARRAY_WORD,
DataType.MATRIX: tinyvm.core.DataType.MATRIX_BYTE
}
dt = table.get(datatype, None)
if dt:
return dt
raise CodeGenerationError("unsupported datatype", datatype)
def translate_value(self, expr: Expression, dtypehint: Optional[tinyvm.core.DataType]) -> tinyvm.program.Value:
if isinstance(expr, LiteralValue):
dtype = dtypehint or tinyvm.core.DataType.guess_datatype_for(expr.value)
return tinyvm.program.Value(dtype, expr.value)
else:
raise CodeGenerationError("cannot yet generate value for expression node", expr)

View File

@ -12,9 +12,8 @@ import argparse
import subprocess
from .compile import PlyParser
from .optimize import optimize
from .codegen.mos6502.generate import AssemblyGenerator
from .plylex import print_bold
from .plyparse import ProgramFormat
from .plyparse import ProgramFormat, Module
class Assembler64Tass:
@ -64,6 +63,7 @@ def main() -> None:
description = "Compiler for IL65 language, code name 'Sick'"
ap = argparse.ArgumentParser(description=description)
ap.add_argument("-o", "--output", help="output directory")
ap.add_argument("-c", "--codegenerator", choices=["6502", "tinyvm"], default="tinyvm", help="what code generator to use")
ap.add_argument("-f", "--enablefloat", action="store_true", help="enable C64 (mflpt5) floating point operations")
ap.add_argument("-no", "--nooptimize", action="store_true", help="do not optimize the parse tree")
ap.add_argument("-sv", "--startvice", action="store_true", help="autostart vice x64 emulator after compilation")
@ -88,22 +88,41 @@ def main() -> None:
else:
print("\nOptimizing code.")
optimize(parsed_module)
print("\nGenerating assembly code.")
cg = AssemblyGenerator(parsed_module, args.enablefloat)
cg.generate(assembly_filename)
assembler = Assembler64Tass(parsed_module.format)
assembler.assemble(assembly_filename, program_filename)
mon_command_file = assembler.generate_breakpoint_list(program_filename)
duration_total = time.perf_counter() - start
print("Compile duration: {:.2f} seconds".format(duration_total))
size = os.path.getsize(program_filename)
print("Output size: {:d} bytes".format(size))
print_bold("Output file: " + program_filename)
print()
if args.startvice:
print("Autostart vice emulator...")
# "-remotemonitor"
cmdline = ["x64", "-moncommands", mon_command_file,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", program_filename]
with open(os.devnull, "wb") as shutup:
subprocess.call(cmdline, stdout=shutup)
if args.codegenerator == "tinyvm":
generate_tinyvm_code(parsed_module, args.enablefloat, assembly_filename, start)
else:
generate_6502_code(parsed_module, args.enablefloat, args.startvice, program_filename, assembly_filename, start)
def generate_tinyvm_code(module: Module, float_enabled: bool, assembly_filename: str, compilationstart: float) -> None:
print("\nGenerating tinyvm code.")
from il65.codegen.tinyvm.generate import AssemblyGenerator
cg = AssemblyGenerator(module, float_enabled)
cg.generate(assembly_filename)
duration_total = time.perf_counter() - compilationstart
print("Compile duration: {:.2f} seconds".format(duration_total))
print()
def generate_6502_code(module: Module, float_enabled: bool, startvice: bool,
program_filename: str, assembly_filename: str, compilationstart: float) -> None:
print("\nGenerating 6502 assembly code.")
from il65.codegen.mos6502.generate import AssemblyGenerator
cg = AssemblyGenerator(module, float_enabled)
cg.generate(assembly_filename)
assembler = Assembler64Tass(module.format)
assembler.assemble(assembly_filename, program_filename)
mon_command_file = assembler.generate_breakpoint_list(program_filename)
duration_total = time.perf_counter() - compilationstart
print("Compile duration: {:.2f} seconds".format(duration_total))
size = os.path.getsize(program_filename)
print("Output size: {:d} bytes".format(size))
print_bold("Output file: " + program_filename)
print()
if startvice:
print("Autostart vice emulator...")
# "-remotemonitor"
cmdline = ["x64", "-moncommands", mon_command_file,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", program_filename]
with open(os.devnull, "wb") as shutup:
subprocess.call(cmdline, stdout=shutup)

View File

@ -7,7 +7,8 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
import enum
import struct
from typing import Callable
import array
from typing import Callable, Union
from il65.codegen.shared import mflpt5_to_float, to_mflpt5
@ -25,6 +26,36 @@ class DataType(enum.IntEnum):
MATRIX_BYTE = 11
MATRIX_SBYTE = 12
@staticmethod
def guess_datatype_for(value: Union[bool, int, float, bytearray, array.array]) -> 'DataType':
if isinstance(value, int):
if 0 <= value <= 255:
return DataType.BYTE
if -128 <= value <= 127:
return DataType.SBYTE
if 0 <= value <= 65535:
return DataType.WORD
if -32768 <= value <= 32767:
return DataType.SWORD
raise OverflowError("integer value too large for byte or word", value)
if isinstance(value, bool):
return DataType.BOOL
if isinstance(value, float):
return DataType.FLOAT
if isinstance(value, bytearray):
return DataType.ARRAY_BYTE
if isinstance(value, array.array):
if value.typecode == "B":
return DataType.ARRAY_BYTE
if value.typecode == "H":
return DataType.ARRAY_WORD
if value.typecode == "b":
return DataType.ARRAY_SBYTE
if value.typecode == "h":
return DataType.ARRAY_SWORD
raise ValueError("invalid array typecode", value.typecode)
raise TypeError("invalid value type", value)
class ExecutionError(Exception):
pass

View File

@ -167,7 +167,7 @@ class Instruction:
class Block:
def __init__(self, name: str, parent: 'Block',
def __init__(self, name: str, parent: Optional['Block'],
variables: List[Variable] = None,
instructions: List[Instruction] = None,
labels: Dict[str, Instruction] = None, # named entry points