prog8/il65/emit/variables.py

270 lines
13 KiB
Python

"""
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 ..plyparse import Block, VarDef, LiteralValue, AddressOf
from ..datatypes import DataType, VarType, STRING_DATATYPES
from . import to_hex, to_mflpt5, CodeError
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 CodeError("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 CodeError("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 CodeError("matrix size must be 2 or 3 numbers")
out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), comment)) # type: ignore
else:
raise CodeError("invalid var type")
out("; normal variables - initial values will be set by init code")
if zeropage:
# zeropage uses the zp_address we've allocated, instead of allocating memory here
for vardef in vars_by_vartype.get(VarType.VAR, []):
assert vardef.zp_address is not None
if vardef.datatype.isstring():
raise CodeError("cannot put strings in the zeropage", vardef.sourceref)
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 CodeError("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 CodeError("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 CodeError("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.ptext {: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.ptext {: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)