This commit is contained in:
Irmen de Jong
2018-09-30 23:13:35 +02:00
parent 623b42cc14
commit 6d343bd75d
59 changed files with 4 additions and 3 deletions

View File

@@ -0,0 +1 @@
# package

View File

@@ -0,0 +1,75 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the 6502 assembly code generator (directly from the parse tree)
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import contextlib
import attr
from typing import Set, Callable
from ...plyparse import Scope, AstNode
from ...compile import Zeropage
@attr.s(repr=False, cmp=False)
class Context:
out = attr.ib(type=Callable)
stmt = attr.ib(type=AstNode)
scope = attr.ib(type=Scope)
floats_enabled = attr.ib(type=bool)
@contextlib.contextmanager
def preserving_registers(registers: Set[str], scope: Scope, out: Callable, loads_a_within: bool=False, force_preserve: bool=False):
# this sometimes clobbers a ZP scratch register and is therefore NOT safe to use in interrupts
# see http://6502.org/tutorials/register_preservation.html
if not scope.save_registers and not force_preserve:
yield
return
if registers == {'A'}:
out("\t\tpha")
yield
out("\t\tpla")
elif registers:
if not loads_a_within:
out("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
if 'A' in registers:
out("\t\tpha")
if 'X' in registers:
out("\t\ttxa\n\t\tpha")
if 'Y' in registers:
out("\t\ttya\n\t\tpha")
if not loads_a_within:
out("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
yield
if 'X' in registers and 'Y' in registers:
if 'A' not in registers:
out("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
out("\t\tpla\n\t\ttay")
out("\t\tpla\n\t\ttax")
out("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
out("\t\tpla\n\t\ttay")
out("\t\tpla\n\t\ttax")
else:
if 'Y' in registers:
if 'A' not in registers:
out("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
out("\t\tpla\n\t\ttay")
out("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
out("\t\tpla\n\t\ttay")
if 'X' in registers:
if 'A' not in registers:
out("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
out("\t\tpla\n\t\ttax")
out("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
out("\t\tpla\n\t\ttax")
if 'A' in registers:
out("\t\tpla")
else:
yield

View File

@@ -0,0 +1,585 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the code generator for assignment statements.
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 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
def generate_assignment(ctx: Context) -> None:
assert isinstance(ctx.stmt, Assignment)
assert not isinstance(ctx.stmt.right, Assignment), "assignment should have been flattened"
ctx.out("\v\t\t\t; " + ctx.stmt.lineref)
ctx.out("\v; @todo assignment: {} = {}".format(ctx.stmt.left, ctx.stmt.right))
# @todo assignment
def generate_aug_assignment(ctx: Context) -> None:
# for instance: value += 33
# (note that with += and -=, values 0..255 usually occur as the more efficient incrdecr statements instead)
# left: Register, SymbolName, or Dereference. right: Expression/LiteralValue
out = ctx.out
stmt = ctx.stmt
assert isinstance(stmt, AugAssignment)
out("\v\t\t\t; " + stmt.lineref)
lvalue = stmt.left
rvalue = stmt.right
if isinstance(lvalue, Register):
if isinstance(rvalue, LiteralValue):
if type(rvalue.value) is int:
assert rvalue.value >= 0, "augassign value can't be < 0"
_generate_aug_reg_int(out, lvalue, stmt.operator, rvalue.value, "", ctx.scope)
else:
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):
if symdef.vartype == VarType.CONST:
if symdef.datatype.isinteger():
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 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 CodeGenerationError("variable must be of type byte for now", rvalue) # XXX
else:
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 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 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)
else:
symdef = rvalue.my_scope().lookup(rvalue.operand.name)
if isinstance(symdef, VarDef) and symdef.vartype == VarType.MEMORY:
what = to_hex(symdef.value.value) # type: ignore
else:
what = rvalue.operand.name
out("\vpha\n\vtya\n\vpha") # save A, Y on stack
out("\vlda " + what)
out("\vsta il65_lib.SCRATCH_ZPWORD1")
out("\vlda {:s}+1".format(what))
out("\vsta il65_lib.SCRATCH_ZPWORD1+1")
out("\vldy #0")
out("\vlda (il65_lib.SCRATCH_ZPWORD1), y")
a_reg = Register(name="A", sourceref=stmt.sourceref) # type: ignore
if 'A' in lvalue.name:
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()))
else:
out("\vst{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name[0].lower()))
out("\vst{:s} il65_lib.SCRATCH_ZP2".format(lvalue.name[1].lower()))
out("\vpla\n\vtay\n\vpla") # restore A, Y from stack
if lvalue.name in REGISTER_BYTES:
out("\vld{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name.lower()))
else:
out("\vld{:s} il65_lib.SCRATCH_ZP2".format(lvalue.name[0].lower()))
out("\vld{:s} il65_lib.SCRATCH_ZP2".format(lvalue.name[1].lower()))
elif isinstance(rvalue.operand, Register):
assert rvalue.operand.datatype == DataType.WORD
reg = rvalue.operand.name
out("\vst{:s} il65_lib.SCRATCH_ZPWORD1".format(reg[0].lower()))
out("\vst{:s} il65_lib.SCRATCH_ZPWORD1+1".format(reg[1].lower()))
out("\vpha\n\vtya\n\vpha") # save A, Y on stack
out("\vldy #0")
out("\vlda (il65_lib.SCRATCH_ZPWORD1), y")
a_reg = Register(name="A", sourceref=stmt.sourceref) # type: ignore
if 'A' in lvalue.name:
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:
out("\vst{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name.lower()))
else:
out("\vst{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name[0].lower()))
out("\vst{:s} il65_lib.SCRATCH_ZP2".format(lvalue.name[1].lower()))
out("\vpla\n\vtay\n\vpla") # restore A, Y from stack
if lvalue.name != 'X':
if lvalue.name in REGISTER_BYTES:
out("\vld{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name.lower()))
else:
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 CodeGenerationError("invalid dereference operand type", rvalue)
else:
raise CodeGenerationError("invalid rvalue type", rvalue)
elif isinstance(lvalue, SymbolName):
raise NotImplementedError("symbolname augassign", lvalue) # XXX
else:
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:
if rname:
right_str = rname
else:
# an immediate value is provided in rvalue
right_str = "#" + str(rvalue)
if operator == "+=":
assert 0 <= rvalue <= 255, "+= value should be 0..255 for now at " + str(lvalue.sourceref) # @todo
if rvalue > 0:
_gen_aug_plus_reg_int(lvalue, out, right_str, scope)
elif operator == "-=":
assert 0 <= rvalue <= 255, "-= value should be 0..255 for now at " + str(lvalue.sourceref) # @todo
if rvalue > 0:
_gen_aug_minus_reg_int(lvalue, out, right_str, scope)
elif operator == "&=":
assert 0 <= rvalue <= 255, "&= value should be 0..255 for now at " + str(lvalue.sourceref) # @todo
if rvalue == 0:
# output '=0'
assignment = Assignment(sourceref=lvalue.sourceref) # type: ignore
assignment.nodes.append(lvalue)
assignment.nodes.append(LiteralValue(value=0, sourceref=lvalue.sourceref)) # type: ignore
ctx = Context(out=out, stmt=assignment, scope=scope, floats_enabled=False) # type: ignore
generate_assignment(ctx)
else:
_gen_aug_and_reg_int(lvalue, out, right_str, scope)
elif operator == "|=":
assert 0 <= rvalue <= 255, "|= value should be 0..255 for now at " + str(lvalue.sourceref) # @todo
if rvalue > 0:
_gen_aug_or_reg_int(lvalue, out, right_str, scope)
elif operator == "^=":
assert 0 <= rvalue <= 255, "^= value should be 0..255 for now at " + str(lvalue.sourceref) # @todo
if rvalue > 0:
_gen_aug_xor_reg_int(lvalue, out, right_str, scope)
elif operator == ">>=":
_gen_aug_shiftright_reg_int(lvalue, out, rname, rvalue, scope)
elif operator == "<<=":
_gen_aug_shiftleft_reg_int(lvalue, out, rname, rvalue, scope)
else:
raise ValueError("invalid operator: " + operator, str(lvalue.sourceref)) # @todo implement more operators such as *=, /=
def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue: Register, scope: Scope) -> None:
if operator == "+=":
_gen_aug_plus_reg_reg(lvalue, out, rvalue, scope)
elif operator == "-=":
_gen_aug_minus_reg_reg(lvalue, out, rvalue, scope)
elif operator == "&=":
_gen_aug_and_reg_reg(lvalue, out, rvalue, scope)
elif operator == "|=":
_gen_aug_or_reg_reg(lvalue, out, rvalue, scope)
elif operator == "^=":
_gen_aug_xor_reg_reg(lvalue, out, rvalue, scope)
elif operator == ">>=":
_gen_aug_shiftright_reg_reg(lvalue, out, rvalue, scope)
elif operator == "<<=":
_gen_aug_shiftleft_reg_reg(lvalue, out, rvalue, scope)
else:
raise ValueError("invalid operator: " + operator, str(lvalue.sourceref)) # @todo implement more operators such as *=, /=
def _gen_aug_shiftleft_reg_int(lvalue: Register, out: Callable, rname: str, rvalue: int, scope: Scope) -> None:
if rname:
assert lvalue.name in REGISTER_BYTES, "only single registers for now" # @todo <<=.word
if lvalue.name == "A":
preserve_regs = {'X'}
elif lvalue.name == "X":
preserve_regs = {'A'}
out("\vtxa")
elif lvalue.name == "Y":
preserve_regs = {'A', 'X'}
out("\vtya")
with preserving_registers(preserve_regs, scope, out):
out("\vldx " + rname)
out("\vjsr il65_lib.asl_A_by_X")
# put A back into target register
if lvalue.name == "X":
out("\vtax")
elif lvalue.name == "Y":
out("\vtay")
else:
def shifts_A(times: int) -> None:
if times >= 8:
out("\vlda #0")
else:
for _ in range(min(8, times)):
out("\vasl a")
if lvalue.name == "A":
shifts_A(rvalue)
elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
shifts_A(rvalue)
out("\vtax")
elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
shifts_A(rvalue)
out("\vtay")
else:
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:
if rname:
assert lvalue.name in REGISTER_BYTES, "only single registers for now" # @todo >>=.word
if lvalue.name == "A":
preserve_regs = {'X'}
elif lvalue.name == "X":
preserve_regs = {'A'}
out("\vtxa")
elif lvalue.name == "Y":
preserve_regs = {'A', 'X'}
out("\vtya")
with preserving_registers(preserve_regs, scope, out):
out("\vldx " + rname)
out("\vjsr il65_lib.lsr_A_by_X")
# put A back into target register
if lvalue.name == "X":
out("\vtax")
if lvalue.name == "Y":
out("\vtay")
else:
def shifts_A(times: int) -> None:
if times >= 8:
out("\vlda #0")
else:
for _ in range(min(8, times)):
out("\vlsr a")
if lvalue.name == "A":
shifts_A(rvalue)
elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
shifts_A(rvalue)
out("\vtax")
elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
shifts_A(rvalue)
out("\vtay")
else:
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:
if lvalue.name == "A":
out("\veor " + right_str)
elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\veor " + right_str)
out("\vtax")
elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\veor " + right_str)
out("\vtay")
else:
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:
if lvalue.name == "A":
out("\vora " + right_str)
elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vora " + right_str)
out("\vtax")
elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vora " + right_str)
out("\vtay")
else:
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:
if lvalue.name == "A":
out("\vand " + right_str)
elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vand " + right_str)
out("\vtax")
elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vand " + right_str)
out("\vtay")
else:
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:
if lvalue.name == "A":
out("\vsec")
out("\vsbc " + right_str)
elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vsec")
out("\vsbc " + right_str)
out("\vtax")
elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vsec")
out("\vsbc " + right_str)
out("\vtay")
elif lvalue.name == "AX":
out("\vsec")
out("\vsbc " + right_str)
out("\vbcs +")
out("\vdex")
out("+")
elif lvalue.name == "AY":
out("\vsec")
out("\vsbc " + right_str)
out("\vbcs +")
out("\vdey")
out("+")
elif lvalue.name == "XY":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vsec")
out("\vsbc " + right_str)
out("\vtax")
out("\vbcs +")
out("\vdey")
out("+")
else:
raise ValueError("invalid register", str(lvalue))
def _gen_aug_plus_reg_int(lvalue: Register, out: Callable, right_str: str, scope: Scope) -> None:
if lvalue.name == "A":
out("\vclc")
out("\vadc " + right_str)
elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vclc")
out("\vadc " + right_str)
out("\vtax")
elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vclc")
out("\vadc " + right_str)
out("\vtay")
elif lvalue.name == "AX":
out("\vclc")
out("\vadc " + right_str)
out("\vbcc +")
out("\vinx")
out("+")
elif lvalue.name == "AY":
out("\vclc")
out("\vadc " + right_str)
out("\vbcc +")
out("\viny")
out("+")
elif lvalue.name == "XY":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vclc")
out("\vadc " + right_str)
out("\vtax")
out("\vbcc +")
out("\viny")
out("+")
else:
raise ValueError("invalid register", str(lvalue))
def _gen_aug_shiftleft_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
if rvalue.name not in REGISTER_BYTES:
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))
elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax")
elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
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 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))
elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax")
elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
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 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))
elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\veor " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax")
elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\veor " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
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 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))
elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vora " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax")
elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vora " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
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 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))
elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vand " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax")
elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vand " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
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 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")
out("\vsbc " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.name == "X":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vsec")
out("\vsbc " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax")
elif lvalue.name == "Y":
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vsec")
out("\vsbc " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
else:
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 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")
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
elif lvalue.name == "X":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vclc")
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax")
elif lvalue.name == "Y":
with preserving_registers({'A'}, scope, out):
out("\vtya")
out("\vclc")
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
out("\vtay")
elif lvalue.name == "AX":
out("\vclc")
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
out("\vbcc +")
out("\vinx")
out("+")
elif lvalue.name == "AY":
out("\vclc")
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
out("\vbcc +")
out("\viny")
out("+")
elif lvalue.name == "XY":
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vclc")
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
out("\vtax")
out("\vbcc +")
out("\viny")
out("+")
else:
raise ValueError("invalid register", str(lvalue))

View File

@@ -0,0 +1,150 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the code generator for gotos and subroutine calls.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
from . import Context
from ..shared import CodeGenerationError, to_hex
from ...plyparse import Goto, SubCall, LiteralValue, SymbolName, Dereference
def generate_goto(ctx: Context) -> None:
stmt = ctx.stmt
assert isinstance(stmt, Goto)
ctx.out("\v\t\t\t; " + ctx.stmt.lineref)
if stmt.condition:
if stmt.if_stmt:
_gen_goto_cond(ctx, stmt, "true")
else:
_gen_goto_cond(ctx, stmt, stmt.if_cond)
else:
if stmt.if_stmt:
_gen_goto_special_if(ctx, stmt)
else:
_gen_goto_unconditional(ctx, stmt)
def _gen_goto_special_if(ctx: Context, stmt: Goto) -> None:
# a special if, but no conditional expression
if isinstance(stmt.target, Dereference):
# dereference is symbol, literal, or register (pair)
if isinstance(stmt.target.operand, LiteralValue):
targetstr = to_hex(stmt.target.operand.value)
elif isinstance(stmt.target.operand, SymbolName):
targetstr = stmt.target.operand.name
else:
# register pair
ctx.out("\vst{:s} il65_lib.SCRATCH_ZPWORD1".format(stmt.target.operand.name[0]))
ctx.out("\vst{:s} il65_lib.SCRATCH_ZPWORD1+1".format(stmt.target.operand.name[1]))
targetstr = "il65_lib.SCRATCH_ZPWORD1"
if stmt.if_cond == "true":
ctx.out("\vbeq +")
ctx.out("\vjmp ({:s})".format(targetstr))
ctx.out("+")
elif stmt.if_cond in ("not", "zero"):
ctx.out("\vbne +")
ctx.out("\vjmp ({:s})".format(targetstr))
ctx.out("+")
elif stmt.if_cond in ("cc", "cs", "vc", "vs", "eq", "ne"):
if stmt.if_cond == "cc":
ctx.out("\vbcs +")
elif stmt.if_cond == "cs":
ctx.out("\vbcc +")
elif stmt.if_cond == "vc":
ctx.out("\vbvs +")
elif stmt.if_cond == "vs":
ctx.out("\vbvc +")
elif stmt.if_cond == "eq":
ctx.out("\vbne +")
elif stmt.if_cond == "ne":
ctx.out("\vbeq +")
ctx.out("\vjmp ({:s})".format(targetstr))
ctx.out("+")
elif stmt.if_cond == "lt":
ctx.out("\vbcs +")
ctx.out("\vjmp ({:s})".format(targetstr))
ctx.out("+")
elif stmt.if_cond == "gt":
ctx.out("\vbcc +")
ctx.out("\vbeq +")
ctx.out("\vjmp ({:s})".format(targetstr))
ctx.out("+")
elif stmt.if_cond == "ge":
ctx.out("\vbcc +")
ctx.out("\vjmp ({:s})".format(targetstr))
ctx.out("+")
elif stmt.if_cond == "le":
ctx.out("\vbeq +")
ctx.out("\vbcs ++")
ctx.out("+\t\tjmp ({:s})".format(targetstr))
ctx.out("+")
else:
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 CodeGenerationError("invalid goto target type", stmt)
if stmt.if_cond == "true":
ctx.out("\vbne " + targetstr)
elif stmt.if_cond in ("not", "zero"):
ctx.out("\vbeq " + targetstr)
elif stmt.if_cond in ("cc", "cs", "vc", "vs", "eq", "ne"):
ctx.out("\vb{:s} {:s}".format(stmt.if_cond, targetstr))
elif stmt.if_cond == "pos":
ctx.out("\vbpl " + targetstr)
elif stmt.if_cond == "neg":
ctx.out("\vbmi " + targetstr)
elif stmt.if_cond == "lt":
ctx.out("\vbcc " + targetstr)
elif stmt.if_cond == "gt":
ctx.out("\vbeq +")
ctx.out("\vbcs " + targetstr)
ctx.out("+")
elif stmt.if_cond == "ge":
ctx.out("\vbcs " + targetstr)
elif stmt.if_cond == "le":
ctx.out("\vbcc " + targetstr)
ctx.out("\vbeq " + targetstr)
else:
raise CodeGenerationError("invalid if status " + stmt.if_cond)
def _gen_goto_unconditional(ctx: Context, stmt: Goto) -> None:
# unconditional jump to <target>
if isinstance(stmt.target, LiteralValue) and type(stmt.target.value) is int:
ctx.out("\vjmp " + to_hex(stmt.target.value))
elif isinstance(stmt.target, SymbolName):
ctx.out("\vjmp " + stmt.target.name)
elif isinstance(stmt.target, Dereference):
# dereference is symbol, literal, or register (pair)
if isinstance(stmt.target.operand, LiteralValue):
ctx.out("\vjmp ({:s})".format(to_hex(stmt.target.operand.value)))
elif isinstance(stmt.target.operand, SymbolName):
ctx.out("\vjmp ({:s})".format(stmt.target.operand.name))
else:
# register pair
ctx.out("\vst{:s} il65_lib.SCRATCH_ZPWORD1".format(stmt.target.operand.name[0]))
ctx.out("\vst{:s} il65_lib.SCRATCH_ZPWORD1+1".format(stmt.target.operand.name[1]))
ctx.out("\vjmp (il65_lib.SCRATCH_ZPWORD1)")
else:
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 CodeGenerationError("no support for evaluating conditional expression yet", stmt) # @todo
def generate_subcall(ctx: Context) -> None:
stmt = ctx.stmt
assert isinstance(stmt, SubCall)
ctx.out("\v\t\t\t; " + ctx.stmt.lineref)
ctx.out("\v; @todo sub call: {}".format(ctx.stmt.target))
# @todo subcall

View File

@@ -0,0 +1,221 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the assembly code generator (from the parse tree)
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import os
import datetime
from typing import TextIO, Callable, no_type_check
from . import Context
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 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)
class Output:
def __init__(self, stream: TextIO) -> None:
self.stream = stream
def __call__(self, text, *args, **vargs):
# replace '\v' (vertical tab) char by the actual line indent (2 tabs) and write to the stringIo
print(text.replace("\v", "\t\t"), *args, file=self.stream, **vargs)
class AssemblyGenerator:
def __init__(self, module: Module, enable_floats: bool) -> None:
self.module = module
self.floats_enabled = enable_floats
self.cur_block = None
self.output = None # type: Output
def generate(self, filename: str) -> None:
with open(filename, "wt") as stream:
output = Output(stream)
try:
self._generate(output)
except Exception as x:
output(".error \"****** ABORTED DUE TO ERROR:", x, "\"\n")
raise
def _generate(self, out: Callable) -> None:
sanitycheck(self.module)
self.header(out)
self.blocks(out)
out("\t.end")
def header(self, out: Callable) -> None:
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)
out("; assembler syntax is for the 64tasm cross-assembler")
out("\n.cpu '6502'\n.enc 'none'\n")
assert self.module.address is not None
if self.module.format in (ProgramFormat.PRG, ProgramFormat.BASIC):
if self.module.format == ProgramFormat.BASIC:
if self.module.address != 0x0801:
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
out("\v.word (+), {:d}".format(year))
out("\v.null $9e, format(' %d ', _il65_entrypoint), $3a, $8f, ' il65 by idj'")
out("+\v.word 0")
out("_il65_entrypoint\v; assembly code starts here\n")
else:
out("; ---- program without sys call ----")
out("* = " + to_hex(self.module.address) + "\n")
elif self.module.format == ProgramFormat.RAW:
out("; ---- raw assembler program ----")
out("* = " + to_hex(self.module.address) + "\n")
# call the block init methods and jump to the user's main.start entrypoint
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
out("\vjsr _il65_save_zeropage")
out("\v; initialize all blocks (reset vars)")
if self.module.zeropage():
out("\vjsr ZP._il65_init_block")
for block in self.module.nodes:
if isinstance(block, Block) and block.name != "ZP":
out("\vjsr {}._il65_init_block".format(block.name))
out("\v; call user code")
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
out("\vjsr {:s}.start".format(self.module.main().label))
out("\vcld")
out("\vjmp _il65_restore_zeropage\n")
# include the assembly code for the save/restore zeropage routines
zprestorefile = os.path.join(os.path.split(__file__)[0], "../lib", "restorezp.asm")
with open(zprestorefile, "rU") as f:
for line in f.readlines():
out(line.rstrip("\n"))
else:
out("\vjmp {:s}.start".format(self.module.main().label))
out("")
@no_type_check
def blocks(self, out: Callable) -> None:
zpblock = self.module.zeropage()
if zpblock:
# if there's a Zeropage block, it always goes first
self.cur_block = zpblock # type: ignore
out("\n; ---- ZeroPage block: '{:s}' ----".format(zpblock.name))
out("; file: '{:s}' src l. {:d}\n".format(zpblock.sourceref.file, zpblock.sourceref.line))
out("{:s}\t.proc\n".format(zpblock.label))
generate_block_init(out, zpblock)
generate_block_vars(out, zpblock, True)
# there's no code in the ZeroPage block.
out("\v.pend\n")
for block in sorted(self.module.all_nodes(Block), key=lambda b: b.address or 0):
ctx = Context(out=out, stmt=None, scope=block.scope, floats_enabled=self.floats_enabled)
if block.name == "ZP":
continue # already processed
self.cur_block = block
out("\n; ---- block: '{:s}' ----".format(block.name))
out("; file: '{:s}' src l. {:d}\n".format(block.sourceref.file, block.sourceref.line))
if block.address:
out(".cerror * > ${0:04x}, 'block address overlaps by ', *-${0:04x},' bytes'".format(block.address))
out("* = ${:04x}".format(block.address))
out("{:s}\t.proc\n".format(block.label))
generate_block_init(out, block)
generate_block_vars(out, block)
subroutines = list(sub for sub in block.all_nodes(Subroutine) if sub.address is not None)
if subroutines:
# these are (external) subroutines that are defined by address instead of a scope/code
out("; external subroutines")
for subdef in subroutines:
assert subdef.scope is None
out("\v{:s} = {:s}".format(subdef.name, to_hex(subdef.address)))
out("; end external subroutines\n")
for stmt in block.scope.nodes:
if isinstance(stmt, (VarDef, Subroutine)):
continue # should have been handled already or will be later
ctx.stmt = stmt
self.generate_statement(ctx)
if block.name == "main" and isinstance(stmt, Label) and stmt.name == "start":
# make sure the main.start routine clears the decimal and carry flags as first steps
out("\vcld\n\vclc\n\vclv")
subroutines = list(sub for sub in block.all_nodes(Subroutine) if sub.address is None)
if subroutines:
# these are subroutines that are defined by a scope/code
out("; -- block subroutines")
for subdef in subroutines:
assert subdef.scope is not None
out("{:s}\v; src l. {:d}".format(subdef.name, subdef.sourceref.line))
params = ", ".join("{:s} -> {:s}".format(name or "<unnamed>", registers) for name, registers in subdef.param_spec)
returns = ",".join(sorted(register for register in subdef.result_spec if register[-1] != '?'))
clobbers = ",".join(sorted(register for register in subdef.result_spec if register[-1] == '?'))
out("\v; params: {}\n\v; returns: {} clobbers: {}".format(params or "-", returns or "-", clobbers or "-"))
cur_block = self.cur_block
self.cur_block = subdef.scope
for ctx.stmt in subdef.scope.nodes:
self.generate_statement(ctx)
self.cur_block = cur_block
out("")
out("; -- end block subroutines")
if block.scope.float_const_values:
if not self.floats_enabled:
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():
out("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(name, *to_mflpt5(value), value))
out("\n\v.pend\n")
@no_type_check
def generate_statement(self, ctx: Context) -> None:
stmt = ctx.stmt
if isinstance(stmt, Label):
ctx.out("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref))
elif isinstance(stmt, Return):
if stmt.value_A:
reg = Register(name="A", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(reg)
assignment.nodes.append(stmt.value_A)
assignment.mark_lhs()
ctx.stmt = assignment
generate_assignment(ctx)
if stmt.value_X:
reg = Register(name="X", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(reg)
assignment.nodes.append(stmt.value_X)
assignment.mark_lhs()
ctx.stmt = assignment
generate_assignment(ctx)
if stmt.value_Y:
reg = Register(name="Y", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(reg)
assignment.nodes.append(stmt.value_Y)
assignment.mark_lhs()
ctx.stmt = assignment
generate_assignment(ctx)
ctx.out("\vrts")
elif isinstance(stmt, InlineAssembly):
ctx.out("\n\v; inline asm, " + stmt.lineref)
ctx.out(stmt.assembly)
ctx.out("\v; end inline asm, " + stmt.lineref + "\n")
elif isinstance(stmt, IncrDecr):
generate_incrdecr(ctx)
elif isinstance(stmt, Goto):
generate_goto(ctx)
elif isinstance(stmt, SubCall):
generate_subcall(ctx)
elif isinstance(stmt, Assignment):
generate_assignment(ctx)
elif isinstance(stmt, AugAssignment):
generate_aug_assignment(ctx)
elif isinstance(stmt, Directive):
if stmt.name == "breakpoint":
# put a marker in the source so that we can generate a list of breakpoints later
# this label is later extracted from the label dump file to turn it into a breakpoint instruction
ctx.out("_il65_breakpoint_{:d}".format(id(stmt)))
# other directives are ignored here
else:
raise NotImplementedError("statement", stmt)

View File

@@ -0,0 +1,267 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the code generator for the in-place incr and decr instructions.
Incrementing or decrementing variables by a small byte value 0..255
is quite frequent and this generates assembly code tweaked for this case.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
from . import Context, preserving_registers
from ..shared import CodeGenerationError, to_hex
from ...plyparse import VarDef, Register, IncrDecr, SymbolName, Dereference, LiteralValue, scoped_name
from ...datatypes import VarType, DataType, REGISTER_BYTES
def generate_incrdecr(ctx: Context) -> None:
out = ctx.out
stmt = ctx.stmt
scope = ctx.scope
assert isinstance(stmt, IncrDecr)
if stmt.howmuch == 0:
return
if not 0 <= stmt.howmuch <= 255:
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 CodeGenerationError("cannot incr/decr this", symdef)
howmuch_str = str(stmt.howmuch)
if isinstance(target, Register):
reg = target.name
# note: these operations below are all checked to be ok
if stmt.operator == "++":
if reg == 'A':
# a += 1..255
out("\vclc")
out("\vadc #" + howmuch_str)
elif reg in REGISTER_BYTES:
if stmt.howmuch == 1:
# x/y += 1
out("\vin{:s}".format(reg.lower()))
else:
# x/y += 2..255
with preserving_registers({'A'}, scope, out):
out("\vt{:s}a".format(reg.lower()))
out("\vclc")
out("\vadc #" + howmuch_str)
out("\vta{:s}".format(reg.lower()))
elif reg == "AX":
# AX += 1..255
out("\vclc")
out("\vadc #" + howmuch_str)
out("\vbcc +")
out("\vinx")
out("+")
elif reg == "AY":
# AY += 1..255
out("\vclc")
out("\vadc # " + howmuch_str)
out("\vbcc +")
out("\viny")
out("+")
elif reg == "XY":
if stmt.howmuch == 1:
# XY += 1
out("\vinx")
out("\vbne +")
out("\viny")
out("+")
else:
# XY += 2..255
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vclc")
out("\vadc #" + howmuch_str)
out("\vtax")
out("\vbcc +")
out("\viny")
out("+")
else:
raise CodeGenerationError("invalid incr register: " + reg)
else:
if reg == 'A':
# a -= 1..255
out("\vsec")
out("\vsbc #" + howmuch_str)
elif reg in REGISTER_BYTES:
if stmt.howmuch == 1:
# x/y -= 1
out("\vde{:s}".format(reg.lower()))
else:
# x/y -= 2..255
with preserving_registers({'A'}, scope, out):
out("\vt{:s}a".format(reg.lower()))
out("\vsec")
out("\vsbc #" + howmuch_str)
out("\vta{:s}".format(reg.lower()))
elif reg == "AX":
# AX -= 1..255
out("\vsec")
out("\vsbc #" + howmuch_str)
out("\vbcs +")
out("\vdex")
out("+")
elif reg == "AY":
# AY -= 1..255
out("\vsec")
out("\vsbc #" + howmuch_str)
out("\vbcs +")
out("\vdey")
out("+")
elif reg == "XY":
if stmt.howmuch == 1:
# XY -= 1
out("\vcpx #0")
out("\vbne +")
out("\vdey")
out("+\t\tdex")
else:
# XY -= 2..255
with preserving_registers({'A'}, scope, out):
out("\vtxa")
out("\vsec")
out("\vsbc #" + howmuch_str)
out("\vtax")
out("\vbcs +")
out("\vdey")
out("+")
else:
raise CodeGenerationError("invalid decr register: " + reg)
elif isinstance(target, VarDef):
if target.vartype == VarType.CONST:
raise CodeGenerationError("cannot modify a constant", target)
what_str = scoped_name(target, scope)
if target.datatype == DataType.BYTE:
if stmt.howmuch == 1:
out("\v{:s} {:s}".format("inc" if stmt.operator == "++" else "dec", what_str))
else:
with preserving_registers({'A'}, scope, out):
out("\vlda " + what_str)
if stmt.operator == "++":
out("\vclc")
out("\vadc #" + howmuch_str)
else:
out("\vsec")
out("\vsbc #" + howmuch_str)
out("\vsta " + what_str)
elif target.datatype == DataType.WORD:
if stmt.howmuch == 1:
# mem.word +=/-= 1
if stmt.operator == "++":
out("\vinc " + what_str)
out("\vbne +")
out("\vinc {:s}+1".format(what_str))
out("+")
else:
with preserving_registers({'A'}, scope, out):
out("\vlda " + what_str)
out("\vbne +")
out("\vdec {:s}+1".format(what_str))
out("+\t\tdec " + what_str)
else:
# mem.word +=/-= 2..255
if stmt.operator == "++":
with preserving_registers({'A'}, scope, out):
out("\vclc")
out("\vlda " + what_str)
out("\vadc #" + howmuch_str)
out("\vsta " + what_str)
out("\vbcc +")
out("\vinc {:s}+1".format(what_str))
out("+")
else:
with preserving_registers({'A'}, scope, out):
out("\vsec")
out("\vlda " + what_str)
out("\vsbc #" + howmuch_str)
out("\vsta " + what_str)
out("\vbcs +")
out("\vdec {:s}+1".format(what_str))
out("+")
elif target.datatype == DataType.FLOAT:
if not ctx.floats_enabled:
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):
out("\vldx #<" + what_str)
out("\vldy #>" + what_str)
if stmt.operator == "++":
out("\vjsr c64flt.float_add_one")
else:
out("\vjsr c64flt.float_sub_one")
elif stmt.howmuch != 0:
float_name = scope.define_float_constant(stmt.howmuch)
with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True):
out("\vlda #<" + float_name)
out("\vsta c64.SCRATCH_ZPWORD1")
out("\vlda #>" + float_name)
out("\vsta c64.SCRATCH_ZPWORD1+1")
out("\vldx #<" + what_str)
out("\vldy #>" + what_str)
if stmt.operator == "++":
out("\vjsr c64flt.float_add_SW1_to_XY")
else:
out("\vjsr c64flt.float_sub_SW1_from_XY")
else:
raise CodeGenerationError("cannot in/decrement memory of type " + str(target.datatype), stmt.howmuch)
elif isinstance(target, Dereference):
if isinstance(target.operand, (LiteralValue, SymbolName)):
if isinstance(target.operand, LiteralValue):
what = to_hex(target.operand.value)
else:
symdef = target.my_scope().lookup(target.operand.name)
if isinstance(symdef, VarDef) and symdef.vartype == VarType.MEMORY:
what = to_hex(symdef.value.value) # type: ignore
else:
what = target.operand.name
if stmt.howmuch == 1:
if target.datatype == DataType.FLOAT:
if not ctx.floats_enabled:
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))
if stmt.operator == "++":
out("\vjsr c64flt.float_add_one")
else:
out("\vjsr c64flt.float_sub_one")
else:
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
out("\vlda " + what)
out("\vldy {:s}+1".format(what))
if target.datatype == DataType.BYTE:
out("\vclc" if stmt.operator == "++" else "\vsec")
out("\vjsr il65_lib.incrdecr_deref_byte_reg_AY")
elif target.datatype == DataType.WORD:
out("\vclc" if stmt.operator == "++" else "\vsec")
out("\vjsr il65_lib.incrdecr_deref_word_reg_AY")
else:
raise CodeGenerationError("cannot inc/decrement dereferenced literal of type " + str(target.datatype), stmt)
else:
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
if stmt.howmuch == 1:
out("\vclc" if stmt.operator == "++" else "\vsec")
if target.datatype == DataType.BYTE:
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
out("\vjsr il65_lib.incrdecr_deref_byte_reg_" + reg)
else:
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
out("\vjsr il65_lib.incrdecr_deref_word_reg_" + reg)
else:
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 CodeGenerationError("cannot inc/decrement", target) # @todo support more

View File

@@ -0,0 +1,269 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the code generator for variable declarations and initialization.
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, CodeGenerationError
from ...plyparse import Block, VarDef, LiteralValue, AddressOf
from ...datatypes import DataType, VarType, STRING_DATATYPES
def generate_block_init(out: Callable, block: Block) -> None:
# generate the block initializer
# @todo add a block initializer subroutine that can contain custom reset/init code? (static initializer)
# @todo will be called at program start automatically, so there's no risk of forgetting to call it manually
def _memset(varname: str, value: int, size: int) -> None:
if size > 6:
out("\vlda #<" + varname)
out("\vsta il65_lib.SCRATCH_ZPWORD1")
out("\vlda #>" + varname)
out("\vsta il65_lib.SCRATCH_ZPWORD1+1")
out("\vlda #" + to_hex(value))
out("\vldx #<" + to_hex(size))
out("\vldy #>" + to_hex(size))
out("\vjsr il65_lib.memset")
else:
out("\vlda #" + to_hex(value))
for i in range(size):
out("\vsta {:s}+{:d}".format(varname, i))
def _memsetw(varname: str, value: int, size: int) -> None:
if size > 4:
out("\vlda #<" + varname)
out("\vsta il65_lib.SCRATCH_ZPWORD1")
out("\vlda #>" + varname)
out("\vsta il65_lib.SCRATCH_ZPWORD1+1")
out("\vlda #<" + to_hex(size))
out("\vsta il65_lib.SCRATCH_ZPWORD2")
out("\vlda #>" + to_hex(size))
out("\vsta il65_lib.SCRATCH_ZPWORD2+1")
out("\vlda #<" + to_hex(value))
out("\vldx #>" + to_hex(value))
out("\vjsr il65_lib.memsetw")
else:
out("\vlda #<" + to_hex(value))
out("\vldy #>" + to_hex(value))
for i in range(size):
out("\vsta {:s}+{:d}".format(varname, i * 2))
out("\vsty {:s}+{:d}".format(varname, i * 2 + 1))
out("_il65_init_block\v; (re)set vars to initial values")
float_inits = {}
prev_value_a, prev_value_x = None, None
vars_by_datatype = defaultdict(list) # type: Dict[DataType, List[VarDef]]
for vardef in block.all_nodes(VarDef):
if vardef.vartype == VarType.VAR: # type: ignore
vars_by_datatype[vardef.datatype].append(vardef) # type: ignore
for bytevar in sorted(vars_by_datatype[DataType.BYTE], key=lambda vd: vd.value):
assert isinstance(bytevar.value, LiteralValue) and type(bytevar.value.value) is int
if bytevar.value.value != prev_value_a:
out("\vlda #${:02x}".format(bytevar.value.value))
prev_value_a = bytevar.value.value
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 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:
out("\vlda #${:02x}".format(v_hi))
prev_value_a = v_hi
if v_lo != prev_value_x:
out("\vldx #${:02x}".format(v_lo))
prev_value_x = v_lo
out("\vsta {:s}".format(wordvar.name))
out("\vstx {:s}+1".format(wordvar.name))
for floatvar in vars_by_datatype[DataType.FLOAT]:
assert isinstance(floatvar.value, LiteralValue) and type(floatvar.value.value) in (int, float)
fpbytes = to_mflpt5(floatvar.value.value) # type: ignore
float_inits[floatvar.name] = (floatvar.name, fpbytes, floatvar.value)
for arrayvar in vars_by_datatype[DataType.BYTEARRAY]:
assert isinstance(arrayvar.value, LiteralValue) and type(arrayvar.value.value) is int
_memset(arrayvar.name, arrayvar.value.value, arrayvar.size[0])
for arrayvar in vars_by_datatype[DataType.WORDARRAY]:
assert isinstance(arrayvar.value, LiteralValue) and type(arrayvar.value.value) is int
_memsetw(arrayvar.name, arrayvar.value.value, arrayvar.size[0])
for arrayvar in vars_by_datatype[DataType.MATRIX]:
assert isinstance(arrayvar.value, LiteralValue) and type(arrayvar.value.value) is int
_memset(arrayvar.name, arrayvar.value.value, arrayvar.size[0] * arrayvar.size[1])
if float_inits:
out("\vldx #4")
out("-")
for varname, (vname, b, fv) in sorted(float_inits.items()):
out("\vlda _init_float_{:s},x".format(varname))
out("\vsta {:s},x".format(vname))
out("\vdex")
out("\vbpl -")
out("\vrts\n")
for varname, (vname, fpbytes, fpvalue) in sorted(float_inits.items()):
assert isinstance(fpvalue, LiteralValue)
out("_init_float_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *fpbytes, fpvalue.value))
all_string_vars = []
for svtype in STRING_DATATYPES:
all_string_vars.extend(vars_by_datatype[svtype])
for strvar in all_string_vars:
# string vars are considered to be a constant, and are statically initialized.
_generate_string_var(out, strvar)
out("")
def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> None:
# Generate the block variable storage.
# The memory bytes of the allocated variables is set to zero (so it compresses very well),
# their actual starting values are set by the block init code.
vars_by_vartype = defaultdict(list) # type: Dict[VarType, List[VarDef]]
for vardef in block.all_nodes(VarDef):
vars_by_vartype[vardef.vartype].append(vardef) # type: ignore
out("; constants")
for vardef in vars_by_vartype.get(VarType.CONST, []):
if vardef.datatype == DataType.FLOAT:
out("\v{:s} = {}".format(vardef.name, _numeric_value_str(vardef.value)))
elif vardef.datatype in (DataType.BYTE, DataType.WORD):
assert isinstance(vardef.value.value, int) # type: ignore
out("\v{:s} = {:s}".format(vardef.name, _numeric_value_str(vardef.value, True)))
elif vardef.datatype.isstring():
# a const string is just a string variable in the generated assembly
_generate_string_var(out, vardef)
else:
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)
assert isinstance(vardef.value.value, int) # type: ignore
if vardef.datatype.isnumeric():
assert vardef.size == [1]
out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), vardef.datatype.name.lower())) # type: ignore
elif vardef.datatype == DataType.BYTEARRAY:
assert len(vardef.size) == 1
out("\v{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, to_hex(vardef.value.value), vardef.size[0])) # type: ignore
elif vardef.datatype == DataType.WORDARRAY:
assert len(vardef.size) == 1
out("\v{:s} = {:s}\t; array of {:d} words".format(vardef.name, to_hex(vardef.value.value), vardef.size[0])) # type: ignore
elif vardef.datatype == DataType.MATRIX:
assert len(vardef.size) in (2, 3)
if len(vardef.size) == 2:
comment = "matrix of {:d} by {:d} = {:d} bytes".format(vardef.size[0], vardef.size[1], vardef.size[0]*vardef.size[1])
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 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 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 CodeGenerationError("cannot put strings in the zeropage", vardef.sourceref)
if vardef.datatype.isarray():
size_str = "size " + str(vardef.size)
else:
size_str = ""
out("\v{:s} = {:s}\t; {:s} {:s}".format(vardef.name, to_hex(vardef.zp_address), vardef.datatype.name.lower(), size_str))
else:
# create definitions for the variables that takes up empty space and will be initialized at startup
string_vars = []
for vardef in vars_by_vartype.get(VarType.VAR, []):
if vardef.datatype.isnumeric():
assert vardef.size == [1]
assert isinstance(vardef.value, LiteralValue)
if vardef.datatype == DataType.BYTE:
out("{:s}\v.byte ?\t; {:s}".format(vardef.name, to_hex(vardef.value.value)))
elif vardef.datatype == DataType.WORD:
out("{:s}\v.word ?\t; {:s}".format(vardef.name, to_hex(vardef.value.value)))
elif vardef.datatype == DataType.FLOAT:
out("{:s}\v.fill 5\t\t; float {}".format(vardef.name, vardef.value.value))
else:
raise CodeGenerationError("weird datatype")
elif vardef.datatype in (DataType.BYTEARRAY, DataType.WORDARRAY):
assert len(vardef.size) == 1
if vardef.datatype == DataType.BYTEARRAY:
out("{:s}\v.fill {:d}\t\t; bytearray".format(vardef.name, vardef.size[0]))
elif vardef.datatype == DataType.WORDARRAY:
out("{:s}\v.fill {:d}*2\t\t; wordarray".format(vardef.name, vardef.size[0]))
else:
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"
.format(vardef.name, vardef.size[0] * vardef.size[1], vardef.size[0], vardef.size[1]))
elif vardef.datatype.isstring():
string_vars.append(vardef)
else:
raise CodeGenerationError("unknown variable type " + str(vardef.datatype))
# string vars are considered to be a constant, and are not re-initialized.
out("")
@no_type_check
def _generate_string_var(out: Callable, vardef: VarDef) -> None:
if vardef.datatype == DataType.STRING:
# 0-terminated string
out("{:s}\n\v.null {:s}".format(vardef.name, _format_string(str(vardef.value.value))))
elif vardef.datatype == DataType.STRING_P:
# pascal string
out("{:s}\n\v.strp {:s}".format(vardef.name, _format_string(str(vardef.value.value))))
elif vardef.datatype == DataType.STRING_S:
# 0-terminated string in screencode encoding
out(".enc 'screen'")
out("{:s}\n\v.null {:s}".format(vardef.name, _format_string(str(vardef.value.value), True)))
out(".enc 'none'")
elif vardef.datatype == DataType.STRING_PS:
# 0-terminated pascal string in screencode encoding
out(".enc 'screen'")
out("{:s}n\v.strp {:s}".format(vardef.name, _format_string(str(vardef.value.value), True)))
out(".enc 'none'")
def _format_string(value: str, screencodes: bool = False) -> str:
if len(value) == 1 and screencodes:
if value[0].isprintable() and ord(value[0]) < 128:
return "'{:s}'".format(value[0])
else:
return str(ord(value[0]))
result = '"'
for char in value:
if char in "{}":
result += '", {:d}, "'.format(ord(char))
elif char.isprintable() and ord(char) < 128:
result += char
else:
if screencodes:
result += '", {:d}, "'.format(ord(char))
else:
if char == '\f':
result += "{clear}"
elif char == '\b':
result += "{delete}"
elif char == '\n':
result += "{cr}"
elif char == '\r':
result += "{down}"
elif char == '\t':
result += "{tab}"
else:
result += '", {:d}, "'.format(ord(char))
return result + '"'
def _numeric_value_str(value: Any, as_hex: bool=False) -> str:
if isinstance(value, LiteralValue):
value = value.value
if isinstance(value, bool):
return "1" if value else "0"
if type(value) is int:
if as_hex:
return to_hex(value)
return str(value)
if isinstance(value, (int, float)):
if as_hex:
raise TypeError("cannot output float as hex")
return str(value)
raise TypeError("no numeric representation possible", value)

View File

@@ -0,0 +1,87 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
Shared logic for the code generators.
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 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"
# 256..65536 -> "$0100".."$ffff"
assert type(number) is int
if number is None:
raise ValueError("number")
if 0 <= number < 16:
return str(number)
if 0 <= number < 0x100:
return "${:02x}".format(number)
if 0 <= number < 0x10000:
return "${:04x}".format(number)
raise OverflowError(number)
def to_mflpt5(number: float) -> bytearray:
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
number = float(number)
if number < FLOAT_MAX_NEGATIVE or number > FLOAT_MAX_POSITIVE:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
if number == 0.0:
return bytearray([0, 0, 0, 0, 0])
if number < 0.0:
sign = 0x80000000
number = -number
else:
sign = 0x00000000
mant, exp = math.frexp(number)
exp += 128
if exp < 1:
# underflow, use zero instead
return bytearray([0, 0, 0, 0, 0])
if exp > 255:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
mant = sign | int(mant * 0x100000000) & 0x7fffffff
return bytearray([exp]) + int.to_bytes(mant, 4, "big")
def mflpt5_to_float(mflpt: bytearray) -> float:
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
if mflpt == bytearray([0, 0, 0, 0, 0]):
return 0.0
exp = mflpt[0] - 128
sign = mflpt[1] & 0x80
number = 0x80000000 | int.from_bytes(mflpt[1:], "big")
number = float(number) * 2**exp / 0x100000000
return -number if sign else number

View File

@@ -0,0 +1,8 @@
"""
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
"""

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)