mirror of
https://github.com/irmen/prog8.git
synced 2025-03-02 07:30:52 +00:00
195 lines
8.8 KiB
Python
195 lines
8.8 KiB
Python
|
"""
|
||
|
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 io
|
||
|
import re
|
||
|
import subprocess
|
||
|
import datetime
|
||
|
from typing import Union
|
||
|
from .plyparse import Module, ProgramFormat, Block, Directive, VarDef, Label, ZpOptions, DataType
|
||
|
from .symbols import to_hex
|
||
|
|
||
|
|
||
|
class CodeError(Exception):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class AssemblyGenerator:
|
||
|
BREAKPOINT_COMMENT_SIGNATURE = "~~~BREAKPOINT~~~"
|
||
|
BREAKPOINT_COMMENT_DETECTOR = r".(?P<address>\w+)\s+ea\s+nop\s+;\s+{:s}.*".format(BREAKPOINT_COMMENT_SIGNATURE)
|
||
|
|
||
|
def __init__(self, module: Module) -> None:
|
||
|
self.module = module
|
||
|
self.generated_code = io.StringIO()
|
||
|
|
||
|
def p(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.generated_code, **vargs)
|
||
|
|
||
|
def generate(self, filename: str) -> None:
|
||
|
self._generate()
|
||
|
with open(filename, "wt") as out:
|
||
|
out.write(self.generated_code.getvalue())
|
||
|
self.generated_code.close()
|
||
|
|
||
|
def _generate(self) -> None:
|
||
|
self.sanitycheck()
|
||
|
self.header()
|
||
|
self.initialize_variables()
|
||
|
self.blocks()
|
||
|
self.footer()
|
||
|
|
||
|
def sanitycheck(self):
|
||
|
# duplicate block names?
|
||
|
all_blocknames = [b.name for b in self.module.scope.filter_nodes(Block)]
|
||
|
unique_blocknames = set(all_blocknames)
|
||
|
if len(all_blocknames) != len(unique_blocknames):
|
||
|
for name in unique_blocknames:
|
||
|
all_blocknames.remove(name)
|
||
|
raise CodeError("there are duplicate block names", all_blocknames)
|
||
|
zpblock = self.module.zeropage()
|
||
|
if zpblock:
|
||
|
# ZP block contains no code?
|
||
|
for stmt in zpblock.scope.nodes:
|
||
|
if not isinstance(stmt, (Directive, VarDef)):
|
||
|
raise CodeError("ZP block can only contain directive and var")
|
||
|
|
||
|
def header(self):
|
||
|
self.p("; code generated by il65.py - codename 'Sick'")
|
||
|
self.p("; source file:", self.module.sourceref.file)
|
||
|
self.p("; compiled on:", datetime.datetime.now())
|
||
|
self.p("; output options:", self.module.format, self.module.zp_options)
|
||
|
self.p("; assembler syntax is for the 64tasm cross-assembler")
|
||
|
self.p("\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 CodeError("BASIC output mode must have load address $0801")
|
||
|
self.p("; ---- basic program with sys call ----")
|
||
|
self.p("* = " + to_hex(self.module.address))
|
||
|
year = datetime.datetime.now().year
|
||
|
self.p("\v.word (+), {:d}".format(year))
|
||
|
self.p("\v.null $9e, format(' %d ', _il65_sysaddr), $3a, $8f, ' il65 by idj'")
|
||
|
self.p("+\v.word 0")
|
||
|
self.p("_il65_sysaddr\v; assembly code starts here\n")
|
||
|
else:
|
||
|
self.p("; ---- program without sys call ----")
|
||
|
self.p("* = " + to_hex(self.module.address) + "\n")
|
||
|
elif self.module.format == ProgramFormat.RAW:
|
||
|
self.p("; ---- raw assembler program ----")
|
||
|
self.p("* = " + to_hex(self.module.address) + "\n")
|
||
|
|
||
|
def initialize_variables(self) -> None:
|
||
|
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
|
||
|
self.p("\vjsr il65_lib_zp.save_zeropage")
|
||
|
zp_float_bytes = {}
|
||
|
# Only the vars from the ZeroPage need to be initialized here,
|
||
|
# the vars in all other blocks are just defined and pre-filled there.
|
||
|
zpblock = self.module.zeropage()
|
||
|
if zpblock:
|
||
|
vars_to_init = [v for v in zpblock.scope.filter_nodes(VarDef)
|
||
|
if v.allocate and v.type in (DataType.BYTE, DataType.WORD, DataType.FLOAT)]
|
||
|
# @todo optimize sort order (sort on value first, then type, then blockname, then address/name)
|
||
|
# (str(self.value) or "", self.blockname, self.name or "", self.address or 0, self.seq_nr)
|
||
|
prev_value = 0 # type: Union[str, int, float]
|
||
|
if vars_to_init:
|
||
|
self.p("; init zp vars")
|
||
|
self.p("\vlda #0\n\vldx #0")
|
||
|
for variable in vars_to_init:
|
||
|
vname = zpblock.label + '.' + variable.name
|
||
|
vvalue = variable.value
|
||
|
if variable.type == DataType.BYTE:
|
||
|
if vvalue != prev_value:
|
||
|
self.p("\vlda #${:02x}".format(vvalue))
|
||
|
prev_value = vvalue
|
||
|
self.p("\vsta {:s}".format(vname))
|
||
|
elif variable.type == DataType.WORD:
|
||
|
if vvalue != prev_value:
|
||
|
self.p("\vlda #<${:04x}".format(vvalue))
|
||
|
self.p("\vldx #>${:04x}".format(vvalue))
|
||
|
prev_value = vvalue
|
||
|
self.p("\vsta {:s}".format(vname))
|
||
|
self.p("\vstx {:s}+1".format(vname))
|
||
|
elif variable.type == DataType.FLOAT:
|
||
|
bytes = self.to_mflpt5(vvalue) # type: ignore
|
||
|
zp_float_bytes[variable.name] = (vname, bytes, vvalue)
|
||
|
if zp_float_bytes:
|
||
|
self.p("\vldx #4")
|
||
|
self.p("-")
|
||
|
for varname, (vname, b, fv) in zp_float_bytes.items():
|
||
|
self.p("\vlda _float_bytes_{:s},x".format(varname))
|
||
|
self.p("\vsta {:s},x".format(vname))
|
||
|
self.p("\vdex")
|
||
|
self.p("\vbpl -")
|
||
|
self.p("; end init zp vars")
|
||
|
else:
|
||
|
self.p("\v; there are no zp vars to initialize")
|
||
|
else:
|
||
|
self.p("\v; there is no zp block to initialize")
|
||
|
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
|
||
|
self.p("\vjsr {:s}.start\v; call user code".format(self.module.main().label))
|
||
|
self.p("\vcld")
|
||
|
self.p("\vjmp il65_lib_zp.restore_zeropage")
|
||
|
else:
|
||
|
self.p("\vjmp {:s}.start\v; call user code".format(self.module.main().label))
|
||
|
self.p("")
|
||
|
for varname, (vname, bytes, fpvalue) in zp_float_bytes.items():
|
||
|
self.p("_float_bytes_{:s}\v.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *bytes, fpvalue))
|
||
|
self.p("\n")
|
||
|
|
||
|
def blocks(self):
|
||
|
self.p("; @todo") # @todo
|
||
|
pass
|
||
|
|
||
|
def footer(self):
|
||
|
self.p("; @todo") # @todo
|
||
|
pass
|
||
|
|
||
|
|
||
|
class Assembler64Tass:
|
||
|
def __init__(self, format: ProgramFormat) -> None:
|
||
|
self.format = format
|
||
|
|
||
|
def assemble(self, inputfilename: str, outputfilename: str) -> None:
|
||
|
args = ["64tass", "--ascii", "--case-sensitive", "-Wall", "-Wno-strict-bool",
|
||
|
"--dump-labels", "--vice-labels", "-l", outputfilename+".vice-mon-list",
|
||
|
"-L", outputfilename+".final-asm", "--no-monitor", "--output", outputfilename, inputfilename]
|
||
|
if self.format in (ProgramFormat.PRG, ProgramFormat.BASIC):
|
||
|
args.append("--cbm-prg")
|
||
|
elif self.format == ProgramFormat.RAW:
|
||
|
args.append("--nostart")
|
||
|
else:
|
||
|
raise ValueError("don't know how to create format "+str(self.format))
|
||
|
try:
|
||
|
if self.format == ProgramFormat.PRG:
|
||
|
print("\nCreating C-64 prg.")
|
||
|
elif self.format == ProgramFormat.RAW:
|
||
|
print("\nCreating raw binary.")
|
||
|
try:
|
||
|
subprocess.check_call(args)
|
||
|
except FileNotFoundError as x:
|
||
|
raise SystemExit("ERROR: cannot run assembler program: "+str(x))
|
||
|
except subprocess.CalledProcessError as x:
|
||
|
raise SystemExit("assembler failed with returncode " + str(x.returncode))
|
||
|
|
||
|
def generate_breakpoint_list(self, program_filename: str) -> str:
|
||
|
breakpoints = []
|
||
|
with open(program_filename + ".final-asm", "rU") as f:
|
||
|
for line in f:
|
||
|
match = re.fullmatch(AssemblyGenerator.BREAKPOINT_COMMENT_DETECTOR, line, re.DOTALL)
|
||
|
if match:
|
||
|
breakpoints.append("$" + match.group("address"))
|
||
|
cmdfile = program_filename + ".vice-mon-list"
|
||
|
with open(cmdfile, "at") as f:
|
||
|
print("; vice monitor breakpoint list now follows", file=f)
|
||
|
print("; {:d} breakpoints have been defined here".format(len(breakpoints)), file=f)
|
||
|
print("del", file=f)
|
||
|
for b in breakpoints:
|
||
|
print("break", b, file=f)
|
||
|
return cmdfile
|