codegen vars

This commit is contained in:
Irmen de Jong 2018-01-11 00:29:46 +01:00
parent 29060f3373
commit 534bf2f252
8 changed files with 289 additions and 364 deletions

View File

@ -55,7 +55,7 @@ class PlyParser:
if block.name == "ZP": if block.name == "ZP":
if zeropage: if zeropage:
# merge other ZP block into first ZP block # merge other ZP block into first ZP block
for node in block.scope.nodes: for node in block.nodes:
if isinstance(node, Directive): if isinstance(node, Directive):
zeropage.scope.add_node(node, 0) zeropage.scope.add_node(node, 0)
elif isinstance(node, VarDef): elif isinstance(node, VarDef):

View File

@ -49,6 +49,12 @@ class DataType(enum.Enum):
STRING_DATATYPES = {DataType.STRING, DataType.STRING_P, DataType.STRING_S, DataType.STRING_PS} 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: # 5-byte cbm MFLPT format limitations:
FLOAT_MAX_POSITIVE = 1.7014118345e+38 FLOAT_MAX_POSITIVE = 1.7014118345e+38
FLOAT_MAX_NEGATIVE = -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 the value is out of bounds, raise an overflow exception
if isinstance(value, (int, float)): if isinstance(value, (int, float)):
if datatype == DataType.BYTE and not (0 <= value <= 0xff): # type: ignore 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 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 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 datatype in (DataType.BYTE, DataType.BYTEARRAY, DataType.MATRIX) and isinstance(value, str):
if len(value) == 1: if len(value) == 1:
return True, char_to_bytevalue(value) return True, char_to_bytevalue(value)

View File

@ -8,10 +8,11 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
import re import re
import subprocess import subprocess
import datetime import datetime
import itertools from collections import defaultdict
from typing import Union, TextIO, List, Tuple, Iterator from typing import Dict, TextIO, List, Any
from .plylex import print_bold 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 from .datatypes import VarType, DataType, to_hex, mflpt5_to_float, to_mflpt5, STRING_DATATYPES
@ -43,7 +44,7 @@ class AssemblyGenerator:
def _generate(self) -> None: def _generate(self) -> None:
self.sanitycheck() self.sanitycheck()
self.header() self.header()
self.initialize_variables() self.init_vars_and_start()
self.blocks() self.blocks()
self.footer() self.footer()
@ -98,83 +99,46 @@ class AssemblyGenerator:
self.p("; ---- raw assembler program ----") self.p("; ---- raw assembler program ----")
self.p("* = " + to_hex(self.module.address) + "\n") 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: if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
self.p("\vjsr il65_lib_zp.save_zeropage") self.p("\vjsr il65_lib_zp.save_zeropage")
zp_float_bytes = {} self.p("\v; initialize all blocks (reset vars)")
# Only the vars from the ZeroPage need to be initialized here, if self.module.zeropage():
# the vars in all other blocks are just defined and pre-filled there. self.p("\vjsr ZP._il65_init_block")
zpblock = self.module.zeropage() for block in self.module.nodes:
if zpblock: if isinstance(block, Block) and block.name != "ZP":
vars_to_init = [v for v in zpblock.scope.filter_nodes(VarDef) self.p("\vjsr {}._il65_init_block".format(block.name))
if v.vartype == VarType.VAR and v.vartype in (DataType.BYTE, DataType.WORD, DataType.FLOAT)] self.p("\v; call user code")
# @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!
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE: 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("\vcld")
self.p("\vjmp il65_lib_zp.restore_zeropage") self.p("\vjmp il65_lib_zp.restore_zeropage")
else: 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("") 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: def blocks(self) -> None:
zpblock = self.module.zeropage() zpblock = self.module.zeropage()
if zpblock: if zpblock:
# if there's a Zeropage block, it always goes first # if there's a Zeropage block, it always goes first
self.cur_block = zpblock # type: ignore 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.p("{:s}\t.proc\n".format(zpblock.label))
self.generate_block_init(zpblock)
self.generate_block_vars(zpblock) self.generate_block_vars(zpblock)
self.p("\v.pend\n") self.p("\v.pend\n")
for block in sorted(self.module.scope.filter_nodes(Block), key=lambda b: b.address or 0): for block in sorted(self.module.scope.filter_nodes(Block), key=lambda b: b.address or 0):
if block.name == "ZP": if block.name == "ZP":
continue # already processed continue # already processed
self.cur_block = block 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: if block.address:
self.p(".cerror * > ${0:04x}, 'block address overlaps by ', *-${0:04x},' bytes'".format(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("* = ${:04x}".format(block.address))
self.p("{:s}\t.proc\n".format(block.label)) self.p("{:s}\t.proc\n".format(block.label))
self.generate_block_init(block)
self.generate_block_vars(block) self.generate_block_vars(block)
subroutines = list(sub for sub in block.scope.filter_nodes(Subroutine) if sub.address is not None) subroutines = list(sub for sub in block.scope.filter_nodes(Subroutine) if sub.address is not None)
if subroutines: if subroutines:
@ -185,14 +149,12 @@ class AssemblyGenerator:
self.p("\v{:s} = {:s}".format(subdef.name, to_hex(subdef.address))) self.p("\v{:s} = {:s}".format(subdef.name, to_hex(subdef.address)))
self.p("; end external subroutines\n") self.p("; end external subroutines\n")
for stmt in block.scope.nodes: for stmt in block.scope.nodes:
if isinstance(stmt, Directive): if isinstance(stmt, (VarDef, Directive)):
continue # should have been handled already continue # should have been handled already
self.generate_statement(stmt) self.generate_statement(stmt)
if block.name == "main" and isinstance(stmt, Label) and stmt.name == "start": 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 # 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("\vcld\n\vclc\n\vclv")
self.p("\vclc\t\t\t; clear carry flag")
self.p("\vclv\t\t\t; clear overflow flag")
subroutines = list(sub for sub in block.scope.filter_nodes(Subroutine) if sub.address is None) subroutines = list(sub for sub in block.scope.filter_nodes(Subroutine) if sub.address is None)
if subroutines: if subroutines:
# these are subroutines that are defined by a scope/code # these are subroutines that are defined by a scope/code
@ -208,7 +170,7 @@ class AssemblyGenerator:
cur_block = self.cur_block cur_block = self.cur_block
self.cur_block = subdef.scope self.cur_block = subdef.scope
for stmt in subdef.scope.nodes: for stmt in subdef.scope.nodes:
if isinstance(stmt, Directive): if isinstance(stmt, (VarDef, Directive)):
continue # should have been handled already continue # should have been handled already
self.generate_statement(stmt) self.generate_statement(stmt)
self.cur_block = cur_block self.cur_block = cur_block
@ -249,99 +211,172 @@ class AssemblyGenerator:
result += '", {:d}, "'.format(ord(char)) result += '", {:d}, "'.format(ord(char))
return result + '"' return result + '"'
def generate_block_vars(self, block: Block) -> None: def generate_block_init(self, block: Block) -> None:
# @todo block vars should be re-initialized when the program is run again, and not depend on statically prefilled data! # generate the block initializer
vars_by_vartype = itertools.groupby(block.scope.filter_nodes(VarDef), lambda c: c.vartype) # @todo add a block initializer subroutine that can contain custom reset/init code? (static initializers)
variable_definitions = sorted(vars_by_vartype, key=lambda gt: gt[0]) # type: List[Tuple[VarType, Iterator[VarDef]]] self.p("_il65_init_block\v; (re)set vars to initial values")
for vartype, varnodes in variable_definitions: # @todo optimize init order (sort on value first to avoid needless register loads, etc)
if vartype == VarType.CONST: self.p("\vlda #0\n\vldx #0")
self.p("; constants") float_inits = {}
for vardef in varnodes: string_inits = []
if vardef.datatype == DataType.FLOAT: prev_value = 0
self.p("\t\t{:s} = {}".format(vardef.name, vardef.value)) for variable in [vd for vd in block.scope.filter_nodes(VarDef) if vd.vartype == VarType.VAR]:
elif vardef.datatype in (DataType.BYTE, DataType.WORD): vname = variable.name
self.p("\t\t{:s} = {:s}".format(vardef.name, to_hex(vardef.value))) vvalue = variable.value
elif vardef.datatype in STRING_DATATYPES: if variable.datatype == DataType.BYTE:
# a const string is just a string variable in the generated assembly if vvalue != prev_value:
self._generate_string_var(vardef) self.p("\vlda #${:02x}".format(vvalue))
else: prev_value = vvalue
raise CodeError("invalid const type", vardef) self.p("\vsta {:s}".format(vname))
elif vartype == VarType.MEMORY: elif variable.datatype == DataType.WORD:
self.p("; memory mapped variables") if vvalue != prev_value:
for vardef in varnodes: self.p("\vlda #<${:04x}".format(vvalue))
# create a definition for variables at a specific place in memory (memory-mapped) self.p("\vldx #>${:04x}".format(vvalue))
if vardef.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT): prev_value = vvalue
assert vardef.size == [1] self.p("\vsta {:s}".format(vname))
self.p("\t\t{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value), vardef.datatype.name.lower())) self.p("\vstx {:s}+1".format(vname))
elif vardef.datatype == DataType.BYTEARRAY: elif variable.datatype == DataType.FLOAT:
assert len(vardef.size) == 1 fpbytes = to_mflpt5(vvalue) # type: ignore
self.p("\t\t{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, to_hex(vardef.value), vardef.size[0])) float_inits[variable.name] = (vname, fpbytes, vvalue)
elif vardef.datatype == DataType.WORDARRAY: elif variable.datatype in STRING_DATATYPES:
assert len(vardef.size) == 1 string_inits.append(variable)
self.p("\t\t{:s} = {:s}\t; array of {:d} words".format(vardef.name, to_hex(vardef.value), vardef.size[0])) else:
elif vardef.datatype == DataType.MATRIX: raise CodeError("weird var datatype", variable.datatype)
assert len(vardef.size) == 2 if float_inits:
self.p("\t\t{:s} = {:s}\t; matrix {:d} by {:d} = {:d} bytes" self.p("\vldx #4")
.format(vardef.name, to_hex(vardef.value), vardef.size[0], vardef.size[1], vardef.size[0]*vardef.size[1])) self.p("-")
else: for varname, (vname, b, fv) in sorted(float_inits.items()):
raise CodeError("invalid var type") self.p("\vlda _init_float_{:s},x".format(varname))
elif vartype == VarType.VAR: self.p("\vsta {:s},x".format(vname))
self.p("; normal variables") self.p("\vdex")
for vardef in varnodes: self.p("\vbpl -")
# create a definition for a variable that takes up space and will be initialized at startup if string_inits:
if vardef.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT): pass # @todo init string block (1 memcopy)
assert vardef.size == [1] self.p("\vrts\n")
if vardef.datatype == DataType.BYTE: for varname, (vname, fpbytes, fpvalue) in sorted(float_inits.items()):
self.p("{:s}\t\t.byte {:s}".format(vardef.name, to_hex(int(vardef.value or 0)))) self.p("_init_float_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *fpbytes, fpvalue))
elif vardef.datatype == DataType.WORD: if string_inits:
self.p("{:s}\t\t.word {:s}".format(vardef.name, to_hex(int(vardef.value or 0)))) self.p("_init_strings_start")
elif vardef.datatype == DataType.FLOAT: for svar in sorted(string_inits, key=lambda v: v.name):
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}{:s}" self._generate_string_var(svar, init=True)
.format(vardef.name, *to_mflpt5(float(vardef.value or 0.0)))) self.p("_init_strings_size = * - _init_strings_start")
else: self.p("")
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_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: if vardef.datatype == DataType.STRING:
# 0-terminated 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: elif vardef.datatype == DataType.STRING_P:
# pascal string # 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: elif vardef.datatype == DataType.STRING_S:
# 0-terminated string in screencode encoding # 0-terminated string in screencode encoding
self.p(".enc 'screen'") 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'") self.p(".enc 'none'")
elif vardef.datatype == DataType.STRING_PS: elif vardef.datatype == DataType.STRING_PS:
# 0-terminated pascal string in screencode encoding # 0-terminated pascal string in screencode encoding
self.p(".enc 'screen'") 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'") self.p(".enc 'none'")
def generate_statement(self, stmt: AstNode) -> None: def generate_statement(self, stmt: AstNode) -> None:
if isinstance(stmt, Label): if isinstance(stmt, Label):
self.p("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref)) self.p("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref))
self.p("\vrts") elif isinstance(stmt, Return):
# @todo rest of the statement nodes 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: class Assembler64Tass:

View File

@ -62,7 +62,6 @@ zp_backup .fill 256, 0
%asm { %asm {
; ---- jmp (indirect) routines for register pairs containing the indirect address ; ---- jmp (indirect) routines for register pairs containing the indirect address
jsr_indirect_nozpuse_AX jsr_indirect_nozpuse_AX
sta jsr_indirect_tmp sta jsr_indirect_tmp
@ -93,5 +92,48 @@ jsr_indirect_XY
sty SCRATCH_ZP2 sty SCRATCH_ZP2
jmp (SCRATCH_ZP1) 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
} }
} }

View File

@ -97,7 +97,8 @@ class Optimizer:
if not usages and parent.name + '.' + sub.name not in never_remove: if not usages and parent.name + '.' + sub.name not in never_remove:
parent.scope.remove_node(sub) parent.scope.remove_node(sub)
num_discarded += 1 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): def optimize_compare_with_zero(self):
# a conditional goto that compares a value with zero will be simplified # a conditional goto that compares a value with zero will be simplified

View File

@ -14,7 +14,7 @@ from typing import Union, Generator, Tuple, List, Optional, Dict, Any, Iterable
import attr import attr
from ply.yacc import yacc from ply.yacc import yacc
from .plylex import SourceRef, tokens, lexer, find_tok_column 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): class ProgramFormat(enum.Enum):
@ -261,7 +261,14 @@ class Label(AstNode):
@attr.s(cmp=False, repr=False) @attr.s(cmp=False, repr=False)
class Register(AstNode): 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: def __hash__(self) -> int:
return hash(self.name) return hash(self.name)
@ -337,11 +344,26 @@ class Return(AstNode):
def process_expressions(self, scope: Scope) -> None: def process_expressions(self, scope: Scope) -> None:
if self.value_A is not 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: 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: 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) @attr.s(cmp=False, repr=False)
@ -384,6 +406,9 @@ class VarDef(AstNode):
assert self.size is None assert self.size is None
self.size = self.datatype.dimensions or [1] self.size = self.datatype.dimensions or [1]
self.datatype = self.datatype.to_enum() 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 the value is an expression, mark it as a *constant* expression here
if isinstance(self.value, Expression): if isinstance(self.value, Expression):
self.value.processed_must_be_constant = True self.value.processed_must_be_constant = True
@ -397,7 +422,7 @@ class VarDef(AstNode):
if self.vartype in (VarType.CONST, VarType.VAR): if self.vartype in (VarType.CONST, VarType.VAR):
try: try:
_, self.value = coerce_value(self.datatype, self.value, self.sourceref) _, 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 raise ParseError(str(x), self.sourceref) from None

View File

@ -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.FLOAT, 123.45) == (False, 123.45)
assert datatypes.coerce_value(datatypes.DataType.BYTE, 5.678) == (True, 5) 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.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): with pytest.raises(OverflowError):
datatypes.coerce_value(datatypes.DataType.BYTE, -1) datatypes.coerce_value(datatypes.DataType.BYTE, -1)
with pytest.raises(OverflowError): with pytest.raises(OverflowError):
@ -112,3 +116,9 @@ def test_coerce_value():
datatypes.coerce_value(datatypes.DataType.FLOAT, -1.7014118346e+38) datatypes.coerce_value(datatypes.DataType.FLOAT, -1.7014118346e+38)
with pytest.raises(OverflowError): with pytest.raises(OverflowError):
datatypes.coerce_value(datatypes.DataType.FLOAT, 1.7014118347e+38) 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
View File

@ -1,226 +1,29 @@
%output basic %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 { ~ 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: 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 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
}