mirror of
https://github.com/irmen/prog8.git
synced 2024-11-26 11:49:22 +00:00
codegen vars
This commit is contained in:
parent
29060f3373
commit
534bf2f252
@ -55,7 +55,7 @@ class PlyParser:
|
||||
if block.name == "ZP":
|
||||
if zeropage:
|
||||
# merge other ZP block into first ZP block
|
||||
for node in block.scope.nodes:
|
||||
for node in block.nodes:
|
||||
if isinstance(node, Directive):
|
||||
zeropage.scope.add_node(node, 0)
|
||||
elif isinstance(node, VarDef):
|
||||
|
@ -49,6 +49,12 @@ class DataType(enum.Enum):
|
||||
|
||||
STRING_DATATYPES = {DataType.STRING, DataType.STRING_P, DataType.STRING_S, DataType.STRING_PS}
|
||||
|
||||
REGISTER_SYMBOLS = {"A", "X", "Y", "AX", "AY", "XY", "SC", "SI"}
|
||||
REGISTER_SYMBOLS_RETURNVALUES = REGISTER_SYMBOLS | {"SZ"}
|
||||
REGISTER_BYTES = {"A", "X", "Y"}
|
||||
REGISTER_SBITS = {"SC", "SI", "SZ"}
|
||||
REGISTER_WORDS = {"AX", "AY", "XY"}
|
||||
|
||||
# 5-byte cbm MFLPT format limitations:
|
||||
FLOAT_MAX_POSITIVE = 1.7014118345e+38
|
||||
FLOAT_MAX_NEGATIVE = -1.7014118345e+38
|
||||
@ -106,11 +112,14 @@ def coerce_value(datatype: DataType, value: PrimitiveType, sourceref: SourceRef=
|
||||
# if the value is out of bounds, raise an overflow exception
|
||||
if isinstance(value, (int, float)):
|
||||
if datatype == DataType.BYTE and not (0 <= value <= 0xff): # type: ignore
|
||||
raise OverflowError("value out of range for byte")
|
||||
raise OverflowError("value out of range for byte: " + str(value))
|
||||
if datatype == DataType.WORD and not (0 <= value <= 0xffff): # type: ignore
|
||||
raise OverflowError("value out of range for word")
|
||||
raise OverflowError("value out of range for word: " + str(value))
|
||||
if datatype == DataType.FLOAT and not (FLOAT_MAX_NEGATIVE <= value <= FLOAT_MAX_POSITIVE): # type: ignore
|
||||
raise OverflowError("value out of range for float")
|
||||
raise OverflowError("value out of range for float: " + str(value))
|
||||
if datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
|
||||
if not isinstance(value, (int, float)):
|
||||
raise TypeError("cannot assign '{:s}' to {:s}".format(type(value).__name__, datatype.name.lower()))
|
||||
if datatype in (DataType.BYTE, DataType.BYTEARRAY, DataType.MATRIX) and isinstance(value, str):
|
||||
if len(value) == 1:
|
||||
return True, char_to_bytevalue(value)
|
||||
|
@ -8,10 +8,11 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
import re
|
||||
import subprocess
|
||||
import datetime
|
||||
import itertools
|
||||
from typing import Union, TextIO, List, Tuple, Iterator
|
||||
from collections import defaultdict
|
||||
from typing import Dict, TextIO, List, Any
|
||||
from .plylex import print_bold
|
||||
from .plyparse import Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions
|
||||
from .plyparse import Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions, \
|
||||
InlineAssembly, Return, Register, LiteralValue
|
||||
from .datatypes import VarType, DataType, to_hex, mflpt5_to_float, to_mflpt5, STRING_DATATYPES
|
||||
|
||||
|
||||
@ -43,7 +44,7 @@ class AssemblyGenerator:
|
||||
def _generate(self) -> None:
|
||||
self.sanitycheck()
|
||||
self.header()
|
||||
self.initialize_variables()
|
||||
self.init_vars_and_start()
|
||||
self.blocks()
|
||||
self.footer()
|
||||
|
||||
@ -98,83 +99,46 @@ class AssemblyGenerator:
|
||||
self.p("; ---- raw assembler program ----")
|
||||
self.p("* = " + to_hex(self.module.address) + "\n")
|
||||
|
||||
def initialize_variables(self) -> None:
|
||||
def init_vars_and_start(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.vartype == VarType.VAR and v.vartype 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")
|
||||
# @todo all block vars should be (re)initialized here as well!
|
||||
self.p("\v; initialize all blocks (reset vars)")
|
||||
if self.module.zeropage():
|
||||
self.p("\vjsr ZP._il65_init_block")
|
||||
for block in self.module.nodes:
|
||||
if isinstance(block, Block) and block.name != "ZP":
|
||||
self.p("\vjsr {}._il65_init_block".format(block.name))
|
||||
self.p("\v; call user code")
|
||||
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
|
||||
self.p("\vjsr {:s}.start\v; call user code".format(self.module.main().label))
|
||||
self.p("\vjsr {:s}.start".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("\vjmp {:s}.start".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) -> None:
|
||||
zpblock = self.module.zeropage()
|
||||
if zpblock:
|
||||
# if there's a Zeropage block, it always goes first
|
||||
self.cur_block = zpblock # type: ignore
|
||||
self.p("\n; ---- zero page block: '{:s}' ----\t\t; src l. {:d}\n".format(zpblock.sourceref.file, zpblock.sourceref.line))
|
||||
self.p("\n; ---- zero page block: '{:s}' ----".format(zpblock.name))
|
||||
self.p("; file: '{:s}' src l. {:d}\n".format(zpblock.sourceref.file, zpblock.sourceref.line))
|
||||
self.p("{:s}\t.proc\n".format(zpblock.label))
|
||||
self.generate_block_init(zpblock)
|
||||
self.generate_block_vars(zpblock)
|
||||
self.p("\v.pend\n")
|
||||
for block in sorted(self.module.scope.filter_nodes(Block), key=lambda b: b.address or 0):
|
||||
if block.name == "ZP":
|
||||
continue # already processed
|
||||
self.cur_block = block
|
||||
self.p("\n; ---- next block: '{:s}' ----\t\t; src l. {:d}\n".format(block.sourceref.file, block.sourceref.line))
|
||||
self.p("\n; ---- block: '{:s}' ----".format(block.name))
|
||||
self.p("; file: '{:s}' src l. {:d}\n".format(block.sourceref.file, block.sourceref.line))
|
||||
if block.address:
|
||||
self.p(".cerror * > ${0:04x}, 'block address overlaps by ', *-${0:04x},' bytes'".format(block.address))
|
||||
self.p("* = ${:04x}".format(block.address))
|
||||
self.p("{:s}\t.proc\n".format(block.label))
|
||||
self.generate_block_init(block)
|
||||
self.generate_block_vars(block)
|
||||
subroutines = list(sub for sub in block.scope.filter_nodes(Subroutine) if sub.address is not None)
|
||||
if subroutines:
|
||||
@ -185,14 +149,12 @@ class AssemblyGenerator:
|
||||
self.p("\v{:s} = {:s}".format(subdef.name, to_hex(subdef.address)))
|
||||
self.p("; end external subroutines\n")
|
||||
for stmt in block.scope.nodes:
|
||||
if isinstance(stmt, Directive):
|
||||
if isinstance(stmt, (VarDef, Directive)):
|
||||
continue # should have been handled already
|
||||
self.generate_statement(stmt)
|
||||
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
|
||||
self.p("\vcld\t\t\t; clear decimal flag")
|
||||
self.p("\vclc\t\t\t; clear carry flag")
|
||||
self.p("\vclv\t\t\t; clear overflow flag")
|
||||
self.p("\vcld\n\vclc\n\vclv")
|
||||
subroutines = list(sub for sub in block.scope.filter_nodes(Subroutine) if sub.address is None)
|
||||
if subroutines:
|
||||
# these are subroutines that are defined by a scope/code
|
||||
@ -208,7 +170,7 @@ class AssemblyGenerator:
|
||||
cur_block = self.cur_block
|
||||
self.cur_block = subdef.scope
|
||||
for stmt in subdef.scope.nodes:
|
||||
if isinstance(stmt, Directive):
|
||||
if isinstance(stmt, (VarDef, Directive)):
|
||||
continue # should have been handled already
|
||||
self.generate_statement(stmt)
|
||||
self.cur_block = cur_block
|
||||
@ -249,99 +211,172 @@ class AssemblyGenerator:
|
||||
result += '", {:d}, "'.format(ord(char))
|
||||
return result + '"'
|
||||
|
||||
def generate_block_vars(self, block: Block) -> None:
|
||||
# @todo block vars should be re-initialized when the program is run again, and not depend on statically prefilled data!
|
||||
vars_by_vartype = itertools.groupby(block.scope.filter_nodes(VarDef), lambda c: c.vartype)
|
||||
variable_definitions = sorted(vars_by_vartype, key=lambda gt: gt[0]) # type: List[Tuple[VarType, Iterator[VarDef]]]
|
||||
for vartype, varnodes in variable_definitions:
|
||||
if vartype == VarType.CONST:
|
||||
self.p("; constants")
|
||||
for vardef in varnodes:
|
||||
if vardef.datatype == DataType.FLOAT:
|
||||
self.p("\t\t{:s} = {}".format(vardef.name, vardef.value))
|
||||
elif vardef.datatype in (DataType.BYTE, DataType.WORD):
|
||||
self.p("\t\t{:s} = {:s}".format(vardef.name, to_hex(vardef.value)))
|
||||
elif vardef.datatype in STRING_DATATYPES:
|
||||
# a const string is just a string variable in the generated assembly
|
||||
self._generate_string_var(vardef)
|
||||
else:
|
||||
raise CodeError("invalid const type", vardef)
|
||||
elif vartype == VarType.MEMORY:
|
||||
self.p("; memory mapped variables")
|
||||
for vardef in varnodes:
|
||||
# create a definition for variables at a specific place in memory (memory-mapped)
|
||||
if vardef.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
|
||||
assert vardef.size == [1]
|
||||
self.p("\t\t{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value), vardef.datatype.name.lower()))
|
||||
elif vardef.datatype == DataType.BYTEARRAY:
|
||||
assert len(vardef.size) == 1
|
||||
self.p("\t\t{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, to_hex(vardef.value), vardef.size[0]))
|
||||
elif vardef.datatype == DataType.WORDARRAY:
|
||||
assert len(vardef.size) == 1
|
||||
self.p("\t\t{:s} = {:s}\t; array of {:d} words".format(vardef.name, to_hex(vardef.value), vardef.size[0]))
|
||||
elif vardef.datatype == DataType.MATRIX:
|
||||
assert len(vardef.size) == 2
|
||||
self.p("\t\t{:s} = {:s}\t; matrix {:d} by {:d} = {:d} bytes"
|
||||
.format(vardef.name, to_hex(vardef.value), vardef.size[0], vardef.size[1], vardef.size[0]*vardef.size[1]))
|
||||
else:
|
||||
raise CodeError("invalid var type")
|
||||
elif vartype == VarType.VAR:
|
||||
self.p("; normal variables")
|
||||
for vardef in varnodes:
|
||||
# create a definition for a variable that takes up space and will be initialized at startup
|
||||
if vardef.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
|
||||
assert vardef.size == [1]
|
||||
if vardef.datatype == DataType.BYTE:
|
||||
self.p("{:s}\t\t.byte {:s}".format(vardef.name, to_hex(int(vardef.value or 0))))
|
||||
elif vardef.datatype == DataType.WORD:
|
||||
self.p("{:s}\t\t.word {:s}".format(vardef.name, to_hex(int(vardef.value or 0))))
|
||||
elif vardef.datatype == DataType.FLOAT:
|
||||
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}{:s}"
|
||||
.format(vardef.name, *to_mflpt5(float(vardef.value or 0.0))))
|
||||
else:
|
||||
raise CodeError("weird datatype")
|
||||
elif vardef.datatype in (DataType.BYTEARRAY, DataType.WORDARRAY):
|
||||
assert len(vardef.size) == 1
|
||||
if vardef.datatype == DataType.BYTEARRAY:
|
||||
self.p("{:s}\t\t.fill {:d}, ${:02x}".format(vardef.name, vardef.size[0], vardef.value or 0))
|
||||
elif vardef.datatype == DataType.WORDARRAY:
|
||||
f_hi, f_lo = divmod(vardef.value or 0, 256) # type: ignore
|
||||
self.p("{:s}\t\t.fill {:d}, [${:02x}, ${:02x}]\t; {:d} words of ${:04x}"
|
||||
.format(vardef.name, vardef.size[0] * 2, f_lo, f_hi, vardef.size[0], vardef.value or 0))
|
||||
else:
|
||||
raise CodeError("invalid datatype", vardef.datatype)
|
||||
elif vardef.datatype == DataType.MATRIX:
|
||||
assert len(vardef.size) == 2
|
||||
self.p("{:s}\t\t.fill {:d}, ${:02x}\t\t; matrix {:d}*{:d} bytes"
|
||||
.format(vardef.name, vardef.size[0] * vardef.size[1], vardef.value or 0, vardef.size[0], vardef.size[1]))
|
||||
elif vardef.datatype in STRING_DATATYPES:
|
||||
self._generate_string_var(vardef)
|
||||
else:
|
||||
raise CodeError("unknown variable type " + str(vardef.datatype))
|
||||
def generate_block_init(self, block: Block) -> None:
|
||||
# generate the block initializer
|
||||
# @todo add a block initializer subroutine that can contain custom reset/init code? (static initializers)
|
||||
self.p("_il65_init_block\v; (re)set vars to initial values")
|
||||
# @todo optimize init order (sort on value first to avoid needless register loads, etc)
|
||||
self.p("\vlda #0\n\vldx #0")
|
||||
float_inits = {}
|
||||
string_inits = []
|
||||
prev_value = 0
|
||||
for variable in [vd for vd in block.scope.filter_nodes(VarDef) if vd.vartype == VarType.VAR]:
|
||||
vname = variable.name
|
||||
vvalue = variable.value
|
||||
if variable.datatype == DataType.BYTE:
|
||||
if vvalue != prev_value:
|
||||
self.p("\vlda #${:02x}".format(vvalue))
|
||||
prev_value = vvalue
|
||||
self.p("\vsta {:s}".format(vname))
|
||||
elif variable.datatype == 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.datatype == DataType.FLOAT:
|
||||
fpbytes = to_mflpt5(vvalue) # type: ignore
|
||||
float_inits[variable.name] = (vname, fpbytes, vvalue)
|
||||
elif variable.datatype in STRING_DATATYPES:
|
||||
string_inits.append(variable)
|
||||
else:
|
||||
raise CodeError("weird var datatype", variable.datatype)
|
||||
if float_inits:
|
||||
self.p("\vldx #4")
|
||||
self.p("-")
|
||||
for varname, (vname, b, fv) in sorted(float_inits.items()):
|
||||
self.p("\vlda _init_float_{:s},x".format(varname))
|
||||
self.p("\vsta {:s},x".format(vname))
|
||||
self.p("\vdex")
|
||||
self.p("\vbpl -")
|
||||
if string_inits:
|
||||
pass # @todo init string block (1 memcopy)
|
||||
self.p("\vrts\n")
|
||||
for varname, (vname, fpbytes, fpvalue) in sorted(float_inits.items()):
|
||||
self.p("_init_float_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *fpbytes, fpvalue))
|
||||
if string_inits:
|
||||
self.p("_init_strings_start")
|
||||
for svar in sorted(string_inits, key=lambda v: v.name):
|
||||
self._generate_string_var(svar, init=True)
|
||||
self.p("_init_strings_size = * - _init_strings_start")
|
||||
self.p("")
|
||||
|
||||
def _generate_string_var(self, vardef: VarDef) -> None:
|
||||
def generate_block_vars(self, block: Block) -> 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.scope.filter_nodes(VarDef):
|
||||
vars_by_vartype[vardef.vartype].append(vardef)
|
||||
self.p("; constants")
|
||||
for vardef in vars_by_vartype.get(VarType.CONST, []):
|
||||
if vardef.datatype == DataType.FLOAT:
|
||||
self.p("\v{:s} = {}".format(vardef.name, vardef.value))
|
||||
elif vardef.datatype in (DataType.BYTE, DataType.WORD):
|
||||
self.p("\v{:s} = {:s}".format(vardef.name, to_hex(vardef.value)))
|
||||
elif vardef.datatype in STRING_DATATYPES:
|
||||
# a const string is just a string variable in the generated assembly
|
||||
self._generate_string_var(vardef)
|
||||
else:
|
||||
raise CodeError("invalid const type", vardef)
|
||||
self.p("; 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)
|
||||
if vardef.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
|
||||
assert vardef.size == [1]
|
||||
self.p("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value), vardef.datatype.name.lower()))
|
||||
elif vardef.datatype == DataType.BYTEARRAY:
|
||||
assert len(vardef.size) == 1
|
||||
self.p("\v{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, to_hex(vardef.value), vardef.size[0]))
|
||||
elif vardef.datatype == DataType.WORDARRAY:
|
||||
assert len(vardef.size) == 1
|
||||
self.p("\v{:s} = {:s}\t; array of {:d} words".format(vardef.name, to_hex(vardef.value), vardef.size[0]))
|
||||
elif vardef.datatype == DataType.MATRIX:
|
||||
assert len(vardef.size) == 2
|
||||
self.p("\v{:s} = {:s}\t; matrix of {:d} by {:d} = {:d} bytes"
|
||||
.format(vardef.name, to_hex(vardef.value), vardef.size[0], vardef.size[1], vardef.size[0]*vardef.size[1]))
|
||||
else:
|
||||
raise CodeError("invalid var type")
|
||||
self.p("; normal variables - initial values will be set by init code")
|
||||
string_vars = []
|
||||
for vardef in vars_by_vartype.get(VarType.VAR, []):
|
||||
# create a definition for a variable that takes up empty space and will be initialized at startup
|
||||
if vardef.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
|
||||
assert vardef.size == [1]
|
||||
if vardef.datatype == DataType.BYTE:
|
||||
self.p("{:s}\v.byte ?".format(vardef.name))
|
||||
elif vardef.datatype == DataType.WORD:
|
||||
self.p("{:s}\v.word ?".format(vardef.name))
|
||||
elif vardef.datatype == DataType.FLOAT:
|
||||
self.p("{:s}\v.fill 5\t\t; float".format(vardef.name))
|
||||
else:
|
||||
raise CodeError("weird datatype")
|
||||
elif vardef.datatype in (DataType.BYTEARRAY, DataType.WORDARRAY):
|
||||
assert len(vardef.size) == 1
|
||||
if vardef.datatype == DataType.BYTEARRAY:
|
||||
self.p("{:s}\v.fill {:d}\t\t; bytearray".format(vardef.name, vardef.size[0]))
|
||||
elif vardef.datatype == DataType.WORDARRAY:
|
||||
self.p("{: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
|
||||
self.p("{: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 in STRING_DATATYPES:
|
||||
string_vars.append(vardef)
|
||||
else:
|
||||
raise CodeError("unknown variable type " + str(vardef.datatype))
|
||||
if string_vars:
|
||||
self.p("il65_string_vars_start")
|
||||
for sv in sorted(string_vars): # must be the same order as in the init routine!!!
|
||||
self.p("{:s}\v.fill {:d}+1\t\t; {}".format(sv.name, len(sv.value), sv.datatype.name.lower()))
|
||||
self.p("")
|
||||
|
||||
def _generate_string_var(self, vardef: VarDef, init: bool=False) -> None:
|
||||
prefix = "_init_str_" if init else ""
|
||||
if vardef.datatype == DataType.STRING:
|
||||
# 0-terminated string
|
||||
self.p("{:s}\n\v.null {:s}".format(vardef.name, self.output_string(str(vardef.value))))
|
||||
self.p("{:s}{:s}\n\v.null {:s}".format(prefix, vardef.name, self.output_string(str(vardef.value))))
|
||||
elif vardef.datatype == DataType.STRING_P:
|
||||
# pascal string
|
||||
self.p("{:s}\n\v.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value))))
|
||||
self.p("{:s}{:s}\n\v.ptext {:s}".format(prefix, vardef.name, self.output_string(str(vardef.value))))
|
||||
elif vardef.datatype == DataType.STRING_S:
|
||||
# 0-terminated string in screencode encoding
|
||||
self.p(".enc 'screen'")
|
||||
self.p("{:s}\n\v.null {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
|
||||
self.p("{:s}{:s}\n\v.null {:s}".format(prefix, vardef.name, self.output_string(str(vardef.value), True)))
|
||||
self.p(".enc 'none'")
|
||||
elif vardef.datatype == DataType.STRING_PS:
|
||||
# 0-terminated pascal string in screencode encoding
|
||||
self.p(".enc 'screen'")
|
||||
self.p("{:s}\n\v.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
|
||||
self.p("{:s}{:s}n\v.ptext {:s}".format(prefix, vardef.name, self.output_string(str(vardef.value), True)))
|
||||
self.p(".enc 'none'")
|
||||
|
||||
def generate_statement(self, stmt: AstNode) -> None:
|
||||
if isinstance(stmt, Label):
|
||||
self.p("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref))
|
||||
self.p("\vrts")
|
||||
# @todo rest of the statement nodes
|
||||
elif isinstance(stmt, Return):
|
||||
if stmt.value_A:
|
||||
self.generate_assignment(Register(name="A", sourceref=stmt.sourceref), '=', stmt.value_A) # type: ignore
|
||||
if stmt.value_X:
|
||||
self.generate_assignment(Register(name="X", sourceref=stmt.sourceref), '=', stmt.value_X) # type: ignore
|
||||
if stmt.value_Y:
|
||||
self.generate_assignment(Register(name="Y", sourceref=stmt.sourceref), '=', stmt.value_Y) # type: ignore
|
||||
self.p("\vrts")
|
||||
elif isinstance(stmt, InlineAssembly):
|
||||
self.p("\n\v; inline asm, " + stmt.lineref)
|
||||
self.p(stmt.assembly)
|
||||
self.p("\v; end inline asm, " + stmt.lineref + "\n")
|
||||
else:
|
||||
self.p("\vrts; " + str(stmt)) # @todo rest of the statement nodes
|
||||
|
||||
def generate_assignment(self, lvalue: AstNode, operator: str, rvalue: Any) -> None:
|
||||
assert rvalue is not None
|
||||
if isinstance(rvalue, LiteralValue):
|
||||
rvalue = rvalue.value
|
||||
print("ASSIGN", lvalue, lvalue.datatype, operator, rvalue)
|
||||
# @todo
|
||||
|
||||
|
||||
class Assembler64Tass:
|
||||
|
@ -62,7 +62,6 @@ zp_backup .fill 256, 0
|
||||
|
||||
|
||||
%asm {
|
||||
|
||||
; ---- jmp (indirect) routines for register pairs containing the indirect address
|
||||
jsr_indirect_nozpuse_AX
|
||||
sta jsr_indirect_tmp
|
||||
@ -93,5 +92,48 @@ jsr_indirect_XY
|
||||
sty SCRATCH_ZP2
|
||||
jmp (SCRATCH_ZP1)
|
||||
|
||||
|
||||
; copy memory UP from (SCRATCH_ZPWORD1) to (SCRATCH_ZPWORD2) of length X/Y (16-bit, X=lo, Y=hi)
|
||||
; clobbers register A,X,Y
|
||||
memcopy16_up
|
||||
source = SCRATCH_ZPWORD1
|
||||
dest = SCRATCH_ZPWORD2
|
||||
length = SCRATCH_ZP1 ; (and SCRATCH_ZP2)
|
||||
|
||||
stx length
|
||||
sty length+1
|
||||
|
||||
ldx length ; move low byte of length into X
|
||||
bne + ; jump to start if X > 0
|
||||
dec length ; subtract 1 from length
|
||||
+ ldy #0 ; set Y to 0
|
||||
- lda (source),y ; set A to whatever (source) points to offset by Y
|
||||
sta (dest),y ; move A to location pointed to by (dest) offset by Y
|
||||
iny ; increment Y
|
||||
bne + ; if Y<>0 then (rolled over) then still moving bytes
|
||||
inc source+1 ; increment hi byte of source
|
||||
inc dest+1 ; increment hi byte of dest
|
||||
+ dex ; decrement X (lo byte counter)
|
||||
bne - ; if X<>0 then move another byte
|
||||
dec length ; weve moved 255 bytes, dec length
|
||||
bpl - ; if length is still positive go back and move more
|
||||
rts ; done
|
||||
|
||||
|
||||
; copy memory UP from (SCRATCH_ZPWORD1) to (AY) with length X (0 = 256)
|
||||
; destination must not overlap, or be before start, then overlap is possible.
|
||||
; clobbers A, X, Y
|
||||
|
||||
memcopy
|
||||
sta SCRATCH_ZPWORD2
|
||||
sty SCRATCH_ZPWORD2+1
|
||||
ldy #0
|
||||
- lda (SCRATCH_ZPWORD1),y
|
||||
sta (SCRATCH_ZPWORD2),y
|
||||
iny
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,8 @@ class Optimizer:
|
||||
if not usages and parent.name + '.' + sub.name not in never_remove:
|
||||
parent.scope.remove_node(sub)
|
||||
num_discarded += 1
|
||||
print("discarded {:d} unused subroutines".format(num_discarded))
|
||||
if num_discarded:
|
||||
print("discarded {:d} unused subroutines".format(num_discarded))
|
||||
|
||||
def optimize_compare_with_zero(self):
|
||||
# a conditional goto that compares a value with zero will be simplified
|
||||
|
@ -14,7 +14,7 @@ from typing import Union, Generator, Tuple, List, Optional, Dict, Any, Iterable
|
||||
import attr
|
||||
from ply.yacc import yacc
|
||||
from .plylex import SourceRef, tokens, lexer, find_tok_column
|
||||
from .datatypes import DataType, VarType, coerce_value
|
||||
from .datatypes import DataType, VarType, coerce_value, REGISTER_SYMBOLS, REGISTER_BYTES, REGISTER_WORDS
|
||||
|
||||
|
||||
class ProgramFormat(enum.Enum):
|
||||
@ -261,7 +261,14 @@ class Label(AstNode):
|
||||
|
||||
@attr.s(cmp=False, repr=False)
|
||||
class Register(AstNode):
|
||||
name = attr.ib(type=str)
|
||||
name = attr.ib(type=str, validator=attr.validators.in_(REGISTER_SYMBOLS))
|
||||
datatype = attr.ib(type=DataType, init=False)
|
||||
|
||||
def __attrs_post_init__(self):
|
||||
if self.name in REGISTER_BYTES:
|
||||
self.datatype = DataType.BYTE
|
||||
elif self.name in REGISTER_WORDS:
|
||||
self.datatype = DataType.WORD
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.name)
|
||||
@ -337,11 +344,26 @@ class Return(AstNode):
|
||||
|
||||
def process_expressions(self, scope: Scope) -> None:
|
||||
if self.value_A is not None:
|
||||
self.value_A = process_expression(self.value_A, scope, self.value_A.sourceref)
|
||||
self.value_A = process_expression(self.value_A, scope, self.sourceref)
|
||||
if isinstance(self.value_A, (int, float, str, bool)):
|
||||
try:
|
||||
_, self.value_A = coerce_value(DataType.BYTE, self.value_A, self.sourceref)
|
||||
except (OverflowError, TypeError) as x:
|
||||
raise ParseError("first value (A): " + str(x), self.sourceref) from None
|
||||
if self.value_X is not None:
|
||||
self.value_X = process_expression(self.value_X, scope, self.value_X.sourceref)
|
||||
self.value_X = process_expression(self.value_X, scope, self.sourceref)
|
||||
if isinstance(self.value_X, (int, float, str, bool)):
|
||||
try:
|
||||
_, self.value_X = coerce_value(DataType.BYTE, self.value_X, self.sourceref)
|
||||
except (OverflowError, TypeError) as x:
|
||||
raise ParseError("second value (X): " + str(x), self.sourceref) from None
|
||||
if self.value_Y is not None:
|
||||
self.value_Y = process_expression(self.value_Y, scope, self.value_Y.sourceref)
|
||||
self.value_Y = process_expression(self.value_Y, scope, self.sourceref)
|
||||
if isinstance(self.value_Y, (int, float, str, bool)):
|
||||
try:
|
||||
_, self.value_Y = coerce_value(DataType.BYTE, self.value_Y, self.sourceref)
|
||||
except (OverflowError, TypeError) as x:
|
||||
raise ParseError("third value (Y): " + str(x), self.sourceref) from None
|
||||
|
||||
|
||||
@attr.s(cmp=False, repr=False)
|
||||
@ -384,6 +406,9 @@ class VarDef(AstNode):
|
||||
assert self.size is None
|
||||
self.size = self.datatype.dimensions or [1]
|
||||
self.datatype = self.datatype.to_enum()
|
||||
if self.vartype == VarType.CONST and self.value is None:
|
||||
raise ParseError("constant value assignment is missing",
|
||||
attr.evolve(self.sourceref, column=self.sourceref.column+len(self.name)))
|
||||
# if the value is an expression, mark it as a *constant* expression here
|
||||
if isinstance(self.value, Expression):
|
||||
self.value.processed_must_be_constant = True
|
||||
@ -397,7 +422,7 @@ class VarDef(AstNode):
|
||||
if self.vartype in (VarType.CONST, VarType.VAR):
|
||||
try:
|
||||
_, self.value = coerce_value(self.datatype, self.value, self.sourceref)
|
||||
except OverflowError as x:
|
||||
except (TypeError, OverflowError) as x:
|
||||
raise ParseError(str(x), self.sourceref) from None
|
||||
|
||||
|
||||
|
@ -96,6 +96,10 @@ def test_coerce_value():
|
||||
assert datatypes.coerce_value(datatypes.DataType.FLOAT, 123.45) == (False, 123.45)
|
||||
assert datatypes.coerce_value(datatypes.DataType.BYTE, 5.678) == (True, 5)
|
||||
assert datatypes.coerce_value(datatypes.DataType.WORD, 5.678) == (True, 5)
|
||||
assert datatypes.coerce_value(datatypes.DataType.STRING, "string") == (False, "string")
|
||||
assert datatypes.coerce_value(datatypes.DataType.STRING_P, "string") == (False, "string")
|
||||
assert datatypes.coerce_value(datatypes.DataType.STRING_S, "string") == (False, "string")
|
||||
assert datatypes.coerce_value(datatypes.DataType.STRING_PS, "string") == (False, "string")
|
||||
with pytest.raises(OverflowError):
|
||||
datatypes.coerce_value(datatypes.DataType.BYTE, -1)
|
||||
with pytest.raises(OverflowError):
|
||||
@ -112,3 +116,9 @@ def test_coerce_value():
|
||||
datatypes.coerce_value(datatypes.DataType.FLOAT, -1.7014118346e+38)
|
||||
with pytest.raises(OverflowError):
|
||||
datatypes.coerce_value(datatypes.DataType.FLOAT, 1.7014118347e+38)
|
||||
with pytest.raises(TypeError):
|
||||
datatypes.coerce_value(datatypes.DataType.BYTE, "string")
|
||||
with pytest.raises(TypeError):
|
||||
datatypes.coerce_value(datatypes.DataType.WORD, "string")
|
||||
with pytest.raises(TypeError):
|
||||
datatypes.coerce_value(datatypes.DataType.FLOAT, "string")
|
||||
|
231
todo.ill
231
todo.ill
@ -1,226 +1,29 @@
|
||||
%output basic
|
||||
%saveregisters
|
||||
%import c64lib
|
||||
%import mathlib
|
||||
|
||||
~ ZP {
|
||||
var zp1_1
|
||||
var zp1_2
|
||||
var zp1_3
|
||||
var zp1_4
|
||||
const zpc1_1
|
||||
const zpc1_2
|
||||
}
|
||||
|
||||
~ ZP {
|
||||
var zp2_1
|
||||
var zp2_2
|
||||
var zp2_3
|
||||
var zp2_4
|
||||
const zpc2_1
|
||||
const zpc2_2
|
||||
}
|
||||
|
||||
|
||||
~ main {
|
||||
|
||||
%saveregisters true
|
||||
|
||||
|
||||
const num = 2 + max(2, 8, 3.44//3)
|
||||
const pi_val = 22/7 - 2.23423
|
||||
var var1 =2 + 9/4
|
||||
var .word wvar2 = 2 + cos(23.2)
|
||||
memory memvar = $d020
|
||||
var .word test2b = &memvar
|
||||
var test3 = var1
|
||||
|
||||
|
||||
start:
|
||||
|
||||
wvar1 = 2+foo()+emptysub2
|
||||
|
||||
A=math.randbyte()
|
||||
A += c64.RASTER
|
||||
A-=c64.TIME_LO
|
||||
X,A=math.divmod_bytes(A, 99)
|
||||
A=B=C=foo()
|
||||
c64scr.print_byte_decimal(A)
|
||||
c64.CHROUT('\n')
|
||||
return
|
||||
|
||||
screen = border = cursor = X = Y = A = X = Y = A = border = cursor = border = cursor = 66 ; multi-assign!
|
||||
X = Y = A = X = Y = A = X = Y = A = X = Y = AX = Y = A = X = AY = XY =A = 123 ; multi-assign!
|
||||
XY = XY
|
||||
A= A
|
||||
A=X=Y=A
|
||||
|
||||
|
||||
rndloop:
|
||||
XY = math.randword()
|
||||
%asm {
|
||||
txa
|
||||
sta $0400,y
|
||||
tya
|
||||
sta $0500,x
|
||||
}
|
||||
[wvar1] = 81 ; @todo implement pointers like this
|
||||
goto rndloop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A = math.randbyte()
|
||||
Y=A
|
||||
%asm {
|
||||
txa
|
||||
sta $0400,y
|
||||
}
|
||||
X--
|
||||
if_ne goto rndloop
|
||||
%asm {
|
||||
iny
|
||||
sty math.randbyte._seed
|
||||
}
|
||||
goto rndloop
|
||||
return
|
||||
|
||||
goto start
|
||||
|
||||
X <<= var1
|
||||
X >>= var1
|
||||
|
||||
var1 ++
|
||||
var1 += num
|
||||
X++
|
||||
X+=num
|
||||
X+=0
|
||||
X-=0
|
||||
X <<= Y
|
||||
A <<= X
|
||||
Y <<= A
|
||||
X <<= 0
|
||||
X <<= 33333
|
||||
X >>= 33333
|
||||
X <<= 2
|
||||
X <<= 7
|
||||
X <<= 8
|
||||
X <<= 22
|
||||
X >>= 0
|
||||
X >>= 1
|
||||
X >>= 2
|
||||
X >>= 7
|
||||
X >>= 8
|
||||
X >>= 22
|
||||
XY = 1
|
||||
AX = 2
|
||||
SC =1
|
||||
|
||||
var QW
|
||||
QW =3
|
||||
;XY <<= 0
|
||||
;XY <<= 1
|
||||
;XY <<= 2
|
||||
|
||||
|
||||
%asm {
|
||||
ldy #200
|
||||
- lda #81
|
||||
sta c64.Screen+39-40,y
|
||||
sta c64.Screen+39+4*40,y
|
||||
sta c64.Screen+39+9*40,y
|
||||
sta c64.Screen+39+14*40,y
|
||||
sta c64.Screen+39+19*40,y
|
||||
lda #83
|
||||
sta c64.Screen-40,y
|
||||
sta c64.Screen+4*40,y
|
||||
sta c64.Screen+9*40,y
|
||||
sta c64.Screen+14*40,y
|
||||
sta c64.Screen+19*40,y
|
||||
lda #1
|
||||
sta c64.Colors+39-40,y
|
||||
sta c64.Colors+39+4*40,y
|
||||
sta c64.Colors+39+9*40,y
|
||||
sta c64.Colors+39+14*40,y
|
||||
sta c64.Colors+39+19*40,y
|
||||
lda #5
|
||||
sta c64.Colors-40,y
|
||||
sta c64.Colors+4*40,y
|
||||
sta c64.Colors+9*40,y
|
||||
sta c64.Colors+14*40,y
|
||||
sta c64.Colors+19*40,y
|
||||
tya
|
||||
sec
|
||||
sbc #40
|
||||
tay
|
||||
bne -
|
||||
}
|
||||
|
||||
|
||||
loop :
|
||||
A=c64.GETIN()
|
||||
if_not goto loop
|
||||
c64scr.scroll_right_full(1)
|
||||
goto loop
|
||||
c64.CHROUT(A)
|
||||
goto loop
|
||||
|
||||
;c64scr.print_byte_hex(0, A)
|
||||
;c64.CHROUT(' ')
|
||||
;goto loop
|
||||
|
||||
;return
|
||||
|
||||
A = $11
|
||||
A = $11
|
||||
A = $11
|
||||
X = $11
|
||||
Y = $11
|
||||
X = $11
|
||||
Y = $11
|
||||
X = $22
|
||||
Y = $33
|
||||
|
||||
c64scr.clear_screen (81, 5)
|
||||
c64scr.clear_screen (81, 5)
|
||||
c64scr.clear_screen (81, 5)
|
||||
c64scr.clear_screen (81, 5)
|
||||
c64scr.clear_screen ! (81, 5)
|
||||
c64scr.clear_screen ! (81, 5)
|
||||
c64scr.clear_screen !(81, 5)
|
||||
c64scr.clear_screen !(81, 5)
|
||||
c64scr.clear_screen !A (81, 5)
|
||||
c64scr.clear_screen !X (81, 5)
|
||||
c64scr.clear_screen !Y (81, 5)
|
||||
c64scr.clear_screen !XY (81, 5)
|
||||
c64scr.clear_screen !AXY (81, 5)
|
||||
|
||||
c64scr.print_byte_hex(1,A)
|
||||
c64.CHROUT(' ')
|
||||
c64scr.print_byte_hex(1,X)
|
||||
c64.CHROUT(' ')
|
||||
c64scr.print_byte_hex(1,Y)
|
||||
c64scr.print_word_decimal(1222)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
%breakpoint
|
||||
|
||||
return
|
||||
|
||||
sub sub1 () -> () {
|
||||
|
||||
%saveregisters no
|
||||
%breakpoint
|
||||
%breakpoint
|
||||
%breakpoint
|
||||
|
||||
label:
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
sub emptysub () -> () {
|
||||
|
||||
%saveregisters yes
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
~ zzzz {
|
||||
|
||||
%saveregisters
|
||||
%breakpoint
|
||||
|
||||
return 999990 + (2*sin(1.0)) + foo(), 999990 -1, 999999
|
||||
|
||||
}
|
||||
~ {
|
||||
;sdfsdf
|
||||
return 999, -1, 3.445
|
||||
;sdfsdf
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user