From ef9176df452f3183f0afb7728090760445d8e1b8 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 10 Mar 2018 20:32:34 +0100 Subject: [PATCH] tinyvm codegen started --- il65/codegen/mos6502/assignment.py | 60 ++++++++++---------- il65/codegen/mos6502/calls.py | 12 ++-- il65/codegen/mos6502/generate.py | 31 ++-------- il65/codegen/mos6502/incrdecr.py | 26 ++++----- il65/codegen/mos6502/variables.py | 18 +++--- il65/codegen/shared.py | 25 +++++++- il65/codegen/tinyvm/__init__.py | 2 +- il65/codegen/tinyvm/generate.py | 91 ++++++++++++++++++++++++++++++ il65/main.py | 61 +++++++++++++------- tinyvm/core.py | 33 ++++++++++- tinyvm/program.py | 2 +- 11 files changed, 252 insertions(+), 109 deletions(-) create mode 100644 il65/codegen/tinyvm/generate.py diff --git a/il65/codegen/mos6502/assignment.py b/il65/codegen/mos6502/assignment.py index f8cb8cb11..4ba93160e 100644 --- a/il65/codegen/mos6502/assignment.py +++ b/il65/codegen/mos6502/assignment.py @@ -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") diff --git a/il65/codegen/mos6502/calls.py b/il65/codegen/mos6502/calls.py index 609bac89c..220f90e28 100644 --- a/il65/codegen/mos6502/calls.py +++ b/il65/codegen/mos6502/calls.py @@ -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: diff --git a/il65/codegen/mos6502/generate.py b/il65/codegen/mos6502/generate.py index 94c4c3775..7eb89996c 100644 --- a/il65/codegen/mos6502/generate.py +++ b/il65/codegen/mos6502/generate.py @@ -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(): diff --git a/il65/codegen/mos6502/incrdecr.py b/il65/codegen/mos6502/incrdecr.py index 711197c39..6b721e506 100644 --- a/il65/codegen/mos6502/incrdecr.py +++ b/il65/codegen/mos6502/incrdecr.py @@ -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 diff --git a/il65/codegen/mos6502/variables.py b/il65/codegen/mos6502/variables.py index 44b6d67d3..5d68b0a59 100644 --- a/il65/codegen/mos6502/variables.py +++ b/il65/codegen/mos6502/variables.py @@ -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("") diff --git a/il65/codegen/shared.py b/il65/codegen/shared.py index 3f9df418a..28989c267 100644 --- a/il65/codegen/shared.py +++ b/il65/codegen/shared.py @@ -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" diff --git a/il65/codegen/tinyvm/__init__.py b/il65/codegen/tinyvm/__init__.py index fa23816c1..5f58cd296 100644 --- a/il65/codegen/tinyvm/__init__.py +++ b/il65/codegen/tinyvm/__init__.py @@ -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 + diff --git a/il65/codegen/tinyvm/generate.py b/il65/codegen/tinyvm/generate.py new file mode 100644 index 000000000..824531c73 --- /dev/null +++ b/il65/codegen/tinyvm/generate.py @@ -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) + diff --git a/il65/main.py b/il65/main.py index d8a4553bc..dc5ee8157 100644 --- a/il65/main.py +++ b/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) diff --git a/tinyvm/core.py b/tinyvm/core.py index 19b42f600..4d7ec040f 100644 --- a/tinyvm/core.py +++ b/tinyvm/core.py @@ -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 diff --git a/tinyvm/program.py b/tinyvm/program.py index f7968ec42..0d32b31a6 100644 --- a/tinyvm/program.py +++ b/tinyvm/program.py @@ -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