mirror of
https://github.com/irmen/prog8.git
synced 2026-01-22 14:17:34 +00:00
moved
This commit is contained in:
1
python-prototype/il65/codegen/__init__.py
Normal file
1
python-prototype/il65/codegen/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# package
|
||||
75
python-prototype/il65/codegen/mos6502/__init__.py
Normal file
75
python-prototype/il65/codegen/mos6502/__init__.py
Normal 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
|
||||
|
||||
|
||||
585
python-prototype/il65/codegen/mos6502/assignment.py
Normal file
585
python-prototype/il65/codegen/mos6502/assignment.py
Normal 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))
|
||||
150
python-prototype/il65/codegen/mos6502/calls.py
Normal file
150
python-prototype/il65/codegen/mos6502/calls.py
Normal 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
|
||||
221
python-prototype/il65/codegen/mos6502/generate.py
Normal file
221
python-prototype/il65/codegen/mos6502/generate.py
Normal 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)
|
||||
267
python-prototype/il65/codegen/mos6502/incrdecr.py
Normal file
267
python-prototype/il65/codegen/mos6502/incrdecr.py
Normal 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
|
||||
269
python-prototype/il65/codegen/mos6502/variables.py
Normal file
269
python-prototype/il65/codegen/mos6502/variables.py
Normal 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)
|
||||
87
python-prototype/il65/codegen/shared.py
Normal file
87
python-prototype/il65/codegen/shared.py
Normal 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
|
||||
8
python-prototype/il65/codegen/tinyvm/__init__.py
Normal file
8
python-prototype/il65/codegen/tinyvm/__init__.py
Normal 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
|
||||
"""
|
||||
|
||||
|
||||
91
python-prototype/il65/codegen/tinyvm/generate.py
Normal file
91
python-prototype/il65/codegen/tinyvm/generate.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the tinyvm stack based program generator.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
# @todo
|
||||
|
||||
import os
|
||||
import datetime
|
||||
import pickle
|
||||
from typing import BinaryIO, List, Tuple, Dict, Optional
|
||||
from ..shared import CodeGenerationError, sanitycheck
|
||||
from ...plyparse import Module, Block, Scope, VarDef, Expression, LiteralValue
|
||||
from ...datatypes import VarType, DataType
|
||||
import tinyvm.core
|
||||
import tinyvm.program
|
||||
|
||||
|
||||
class AssemblyGenerator:
|
||||
def __init__(self, module: Module, enable_floats: bool) -> None:
|
||||
self.module = module
|
||||
self.floats_enabled = enable_floats
|
||||
|
||||
def generate(self, filename: str) -> None:
|
||||
with open(filename+".pickle", "wb") as stream:
|
||||
self._generate(stream)
|
||||
|
||||
def _generate(self, out: BinaryIO) -> None:
|
||||
sanitycheck(self.module)
|
||||
program = self.header()
|
||||
program.blocks = self.blocks(self.module.nodes[0], None) # type: ignore
|
||||
pickle.dump(program, out, pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def header(self) -> tinyvm.program.Program:
|
||||
return tinyvm.program.Program([])
|
||||
|
||||
def blocks(self, scope: Scope, parentblock_vm: Optional[tinyvm.program.Block]) -> List[tinyvm.program.Block]:
|
||||
blocks = []
|
||||
for node in scope.nodes:
|
||||
if isinstance(node, Block):
|
||||
variables = self.make_vars(node)
|
||||
labels, instructions = self.make_instructions(node)
|
||||
vmblock = tinyvm.program.Block(node.name, parentblock_vm, variables, instructions, labels)
|
||||
print(vmblock)
|
||||
blocks.append(vmblock)
|
||||
vmblock.blocks = self.blocks(node.nodes[0], vmblock) # type: ignore
|
||||
return blocks
|
||||
|
||||
def make_vars(self, block: Block) -> List[tinyvm.program.Variable]:
|
||||
variables = []
|
||||
for vardef in block.all_nodes(VarDef):
|
||||
assert isinstance(vardef, VarDef)
|
||||
dtype = self.translate_datatype(vardef.datatype)
|
||||
value = self.translate_value(vardef.value, dtype)
|
||||
if vardef.vartype == VarType.CONST:
|
||||
const = True
|
||||
elif vardef.vartype == VarType.VAR:
|
||||
const = False
|
||||
else:
|
||||
raise CodeGenerationError("unsupported vartype", vardef.vartype)
|
||||
variables.append(tinyvm.program.Variable(vardef.name, dtype, value, const))
|
||||
return variables
|
||||
|
||||
def make_instructions(self, block: Block) -> Tuple[Dict[str, tinyvm.program.Instruction], List[tinyvm.program.Instruction]]:
|
||||
# returns a dict with the labels (named instruction pointers),
|
||||
# and a list of the program instructions.
|
||||
return {}, []
|
||||
|
||||
def translate_datatype(self, datatype: DataType) -> tinyvm.core.DataType:
|
||||
table = {
|
||||
DataType.BYTE: tinyvm.core.DataType.BYTE,
|
||||
DataType.WORD: tinyvm.core.DataType.WORD,
|
||||
DataType.FLOAT: tinyvm.core.DataType.FLOAT,
|
||||
DataType.BYTEARRAY: tinyvm.core.DataType.ARRAY_BYTE,
|
||||
DataType.WORDARRAY: tinyvm.core.DataType.ARRAY_WORD,
|
||||
DataType.MATRIX: tinyvm.core.DataType.MATRIX_BYTE
|
||||
}
|
||||
dt = table.get(datatype, None)
|
||||
if dt:
|
||||
return dt
|
||||
raise CodeGenerationError("unsupported datatype", datatype)
|
||||
|
||||
def translate_value(self, expr: Expression, dtypehint: Optional[tinyvm.core.DataType]) -> tinyvm.program.Value:
|
||||
if isinstance(expr, LiteralValue):
|
||||
dtype = dtypehint or tinyvm.core.DataType.guess_datatype_for(expr.value)
|
||||
return tinyvm.program.Value(dtype, expr.value)
|
||||
else:
|
||||
raise CodeGenerationError("cannot yet generate value for expression node", expr)
|
||||
|
||||
Reference in New Issue
Block a user