From 534bf2f2521331fe47d0eefd431b283b85828f79 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 11 Jan 2018 00:29:46 +0100 Subject: [PATCH] codegen vars --- il65/compile.py | 2 +- il65/datatypes.py | 15 ++- il65/generateasm.py | 311 ++++++++++++++++++++++++------------------- il65/lib/il65lib.ill | 44 +++++- il65/optimize.py | 3 +- il65/plyparse.py | 37 ++++- tests/test_core.py | 10 ++ todo.ill | 231 +++----------------------------- 8 files changed, 289 insertions(+), 364 deletions(-) diff --git a/il65/compile.py b/il65/compile.py index 996ea3b35..aa952d0b4 100644 --- a/il65/compile.py +++ b/il65/compile.py @@ -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): diff --git a/il65/datatypes.py b/il65/datatypes.py index 2c08b9d42..3ef2e2970 100644 --- a/il65/datatypes.py +++ b/il65/datatypes.py @@ -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) diff --git a/il65/generateasm.py b/il65/generateasm.py index 02bb74c5e..e71e0ea9f 100644 --- a/il65/generateasm.py +++ b/il65/generateasm.py @@ -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: diff --git a/il65/lib/il65lib.ill b/il65/lib/il65lib.ill index dce7b038a..ba37b2b95 100644 --- a/il65/lib/il65lib.ill +++ b/il65/lib/il65lib.ill @@ -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 + } } diff --git a/il65/optimize.py b/il65/optimize.py index b4ce9546e..f1f9bf980 100644 --- a/il65/optimize.py +++ b/il65/optimize.py @@ -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 diff --git a/il65/plyparse.py b/il65/plyparse.py index c3c8ad910..7b0421783 100644 --- a/il65/plyparse.py +++ b/il65/plyparse.py @@ -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 diff --git a/tests/test_core.py b/tests/test_core.py index 59f6f9734..df55df1c3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -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") diff --git a/todo.ill b/todo.ill index 62bc5c082..ece0f0877 100644 --- a/todo.ill +++ b/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 -}