mirror of
https://github.com/irmen/prog8.git
synced 2024-11-26 11:49:22 +00:00
tinyvm codegen started
This commit is contained in:
parent
4d929e00f5
commit
ef9176df45
@ -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")
|
||||
|
@ -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:
|
||||
|
@ -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():
|
||||
|
@ -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
|
||||
|
@ -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("")
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
91
il65/codegen/tinyvm/generate.py
Normal file
91
il65/codegen/tinyvm/generate.py
Normal 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)
|
||||
|
61
il65/main.py
61
il65/main.py
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user