diff --git a/il65/astdefs.py b/il65/astdefs.py index d10b2cd28..c2f9e0583 100644 --- a/il65/astdefs.py +++ b/il65/astdefs.py @@ -379,7 +379,7 @@ class AssignmentStmt(_AstNode): else: stringvar_name = "il65_str_{:d}".format(id(self)) value = self.right.value - containing_block.symbols.define_variable(stringvar_name, self.sourceref, DataType.STRING, value=value) + containing_block.symbols.define_constant(stringvar_name, self.sourceref, DataType.STRING, value=value) self.right.name = stringvar_name self._immediate_string_vars[self.right.value] = (containing_block.name, stringvar_name) @@ -419,19 +419,21 @@ class ReturnStmt(_AstNode): class InplaceIncrStmt(_AstNode): - def __init__(self, what: Value, howmuch: Union[int, float], sourceref: SourceRef) -> None: + def __init__(self, what: Value, howmuch: Union[None, int, float], byname: Optional[str], sourceref: SourceRef) -> None: super().__init__(sourceref) - assert howmuch > 0 + assert howmuch is None or howmuch > 0 self.what = what self.howmuch = howmuch + self.float_var_name = byname class InplaceDecrStmt(_AstNode): - def __init__(self, what: Value, howmuch: Union[int, float], sourceref: SourceRef) -> None: + def __init__(self, what: Value, howmuch: Union[None, int, float], byname: Optional[str], sourceref: SourceRef) -> None: super().__init__(sourceref) - assert howmuch > 0 + assert howmuch is None or howmuch > 0 self.what = what self.howmuch = howmuch + self.float_var_name = byname class IfCondition(_AstNode): diff --git a/il65/codegen.py b/il65/codegen.py index b1a5010c8..6593aebfe 100644 --- a/il65/codegen.py +++ b/il65/codegen.py @@ -296,25 +296,26 @@ class CodeGenerator: self.p("; normal variables") for vardef in non_mem_vars: # create a definition for a variable that takes up space and will be initialized at startup + sourcecomment = "\t; " + vardef.sourcecomment if vardef.sourcecomment else "" if vardef.type in (DataType.BYTE, DataType.WORD, DataType.FLOAT): if vardef.address: assert block.name == "ZP", "only ZP-variables can be put on an address" self.p("\t\t{:s} = {:s}".format(vardef.name, Parser.to_hex(vardef.address))) else: if vardef.type == DataType.BYTE: - self.p("{:s}\t\t.byte {:s}".format(vardef.name, Parser.to_hex(int(vardef.value)))) + self.p("{:s}\t\t.byte {:s}{:s}".format(vardef.name, Parser.to_hex(int(vardef.value)), sourcecomment)) elif vardef.type == DataType.WORD: - self.p("{:s}\t\t.word {:s}".format(vardef.name, Parser.to_hex(int(vardef.value)))) + self.p("{:s}\t\t.word {:s}{:s}".format(vardef.name, Parser.to_hex(int(vardef.value)), sourcecomment)) elif vardef.type == DataType.FLOAT: - self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}" - .format(vardef.name, *self.to_mflpt5(float(vardef.value)))) + self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}{:s}" + .format(vardef.name, *self.to_mflpt5(float(vardef.value)), sourcecomment)) else: raise CodeError("weird datatype") elif vardef.type in (DataType.BYTEARRAY, DataType.WORDARRAY): if vardef.address: raise CodeError("array or wordarray vars must not have address; will be allocated by assembler") if vardef.type == DataType.BYTEARRAY: - self.p("{:s}\t\t.fill {:d}, ${:02x}".format(vardef.name, vardef.length, vardef.value or 0)) + self.p("{:s}\t\t.fill {:d}, ${:02x}{:s}".format(vardef.name, vardef.length, vardef.value or 0, sourcecomment)) elif vardef.type == 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}" @@ -395,7 +396,7 @@ class CodeGenerator: self.previous_stmt_was_assignment = isinstance(stmt, AssignmentStmt) def generate_incr_or_decr(self, stmt: Union[InplaceIncrStmt, InplaceDecrStmt]) -> None: - assert stmt.howmuch > 0 + assert (stmt.howmuch is None and stmt.float_var_name) or (stmt.howmuch > 0 and not stmt.float_var_name) if stmt.what.datatype != DataType.FLOAT and stmt.howmuch > 0xff: raise CodeError("only supports integer incr/decr by up to 255 for now") # XXX is_incr = isinstance(stmt, InplaceIncrStmt) @@ -562,16 +563,28 @@ class CodeGenerator: self.p("+\t\tpla") elif what.datatype == DataType.FLOAT: if stmt.howmuch == 1.0: - t_str = stmt.what.name or Parser.to_hex(stmt.what.address) + # special case for +/-1 with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True): - self.p("\t\t ldx #<" + t_str) - self.p("\t\t ldy #>" + t_str) + self.p("\t\tldx #<" + r_str) + self.p("\t\tldy #>" + r_str) if is_incr: - self.p("\t\t jsr il65_lib.float_add_one") + self.p("\t\tjsr il65_lib.float_add_one") else: - self.p("\t\t jsr il65_lib.float_sub_one") + self.p("\t\tjsr il65_lib.float_sub_one") + elif stmt.float_var_name: + with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True): + self.p("\t\tlda #<" + stmt.float_var_name) + self.p("\t\tsta c64.SCRATCH_ZPWORD1") + self.p("\t\tlda #>" + stmt.float_var_name) + self.p("\t\tsta c64.SCRATCH_ZPWORD1+1") + self.p("\t\tldx #<" + r_str) + self.p("\t\tldy #>" + r_str) + if is_incr: + self.p("\t\tjsr il65_lib.float_add_SW1_to_XY") + else: + self.p("\t\tjsr il65_lib.float_sub_SW1_from_XY") else: - raise CodeError("cannot incr/decr float by other than 1 at this time", stmt.howmuch) # XXX + raise CodeError("incr/decr missing float constant definition") else: raise CodeError("cannot in/decrement memory of type " + str(what.datatype), stmt.howmuch) else: @@ -1015,7 +1028,7 @@ class CodeGenerator: else: raise CodeError("invalid rvalue for augmented assignment on register", str(rvalue)) else: - raise CodeError("augmented assignment only implemented for registers for now") # XXX + raise CodeError("augmented assignment only implemented for registers for now", str(rvalue)) # XXX def _generate_aug_reg_mem(self, lvalue: RegisterValue, operator: str, rvalue: MemMappedValue) -> None: r_str = rvalue.name or Parser.to_hex(rvalue.address) diff --git a/il65/parse.py b/il65/parse.py index 53aa2c4e2..63ff81afe 100644 --- a/il65/parse.py +++ b/il65/parse.py @@ -225,6 +225,8 @@ class Parser: break self.print_warning("{:s} doesn't end with a return statement".format(message), block.sourceref) + _immediate_floats = {} # type: Dict[float, Tuple[str, str]] + def _parse_2(self) -> None: # parsing pass 2 (not done during preprocessing!) self.cur_block = None @@ -243,6 +245,49 @@ class Parser: self.sourceref = stmt.sourceref.copy() stmt.desugar_immediate_string(containing_block) + def desugar_immediate_floats(stmt: _AstNode, containing_block: Block) -> None: + if isinstance(stmt, (InplaceIncrStmt, InplaceDecrStmt)): + if stmt.howmuch is None: + assert stmt.float_var_name + return + if stmt.howmuch in (0, 1): + return # 1 is special cased in the code generator + rom_floats = { + 1: "c64.FL_FONE", + .25: "c64.FL_FR4", + .5: "c64.FL_FHALF", + -.5: "c64.FL_NEGHLF", + 10: "c64.FL_TENC", + -32768: "c64.FL_N32768", + 1e9: "c64.FL_NZMIL", + math.pi: "c64.FL_PIVAL", + math.pi / 2: "c64.FL_PIHALF", + math.pi * 2: "c64.FL_TWOPI", + math.sqrt(2)/2.0: "c64.FL_SQRHLF", + math.sqrt(2): "c64.FL_SQRTWO", + math.log(2): "c64.FL_LOG2", + 1.0 / math.log(2): "c64.FL_LOGEB2", + } + for fv, name in rom_floats.items(): + if math.isclose(stmt.howmuch, fv, rel_tol=0, abs_tol=1e-9): + # use one of the constants available in ROM + stmt.float_var_name = name + return + if stmt.howmuch in self._immediate_floats: + # reuse previously defined float constant + blockname, floatvar_name = self._immediate_floats[stmt.howmuch] + if blockname: + stmt.float_var_name = blockname + '.' + floatvar_name + else: + stmt.float_var_name = floatvar_name + else: + # define new float variable to hold the incr/decr value + # note: not a constant, because we need the MFLT bytes + floatvar_name = "il65_float_{:d}".format(id(stmt)) + containing_block.symbols.define_variable(floatvar_name, stmt.sourceref, DataType.FLOAT, value=stmt.howmuch) + self._immediate_floats[stmt.howmuch] = (containing_block.name, floatvar_name) + stmt.float_var_name = floatvar_name + for block in self.result.blocks: self.cur_block = block self.sourceref = block.sourceref.copy() @@ -252,6 +297,7 @@ class Parser: self.sourceref = stmt.sourceref.copy() self.desugar_call_arguments_and_outputs(stmt) desugar_immediate_strings(stmt, self.cur_block) + desugar_immediate_floats(stmt, self.cur_block) def desugar_call_arguments_and_outputs(self, stmt: CallStmt) -> None: stmt.desugared_call_arguments.clear() @@ -764,8 +810,8 @@ class Parser: if isinstance(what, IntegerValue): raise self.PError("cannot in/decrement a constant value") if incr: - return InplaceIncrStmt(what, 1, self.sourceref) - return InplaceDecrStmt(what, 1, self.sourceref) + return InplaceIncrStmt(what, 1, None, self.sourceref) + return InplaceDecrStmt(what, 1, None, self.sourceref) else: # perhaps it is an augmented assignment statement match = re.fullmatch(r"(?P\S+)\s*(?P\+=|-=|\*=|/=|%=|//=|\*\*=|&=|\|=|\^=|>>=|<<=)\s*(?P\S.*)", line) @@ -903,21 +949,28 @@ class Parser: truncated, value = self.coerce_value(self.sourceref, l_value.datatype, r_value.value) if truncated: r_value = IntegerValue(int(value), self.sourceref, datatype=l_value.datatype, name=r_value.name) - if r_value.constant and operator in ("+=", "-="): - if operator == "+=": - if r_value.value > 0: # type: ignore - return InplaceIncrStmt(l_value, r_value.value, self.sourceref) # type: ignore - elif r_value.value < 0: # type: ignore - return InplaceDecrStmt(l_value, -r_value.value, self.sourceref) # type: ignore + if operator in ("+=", "-="): + # see if we can simplify this to inplace incr/decr statement + if r_value.constant: + if operator == "+=": + if r_value.value > 0: # type: ignore + return InplaceIncrStmt(l_value, r_value.value, self.sourceref) # type: ignore + elif r_value.value < 0: # type: ignore + return InplaceDecrStmt(l_value, -r_value.value, self.sourceref) # type: ignore + else: + self.print_warning("incr with zero, ignored") else: - self.print_warning("incr with zero, ignored") + if r_value.value > 0: # type: ignore + return InplaceDecrStmt(l_value, r_value.value, self.sourceref) # type: ignore + elif r_value.value < 0: # type: ignore + return InplaceIncrStmt(l_value, -r_value.value, self.sourceref) # type: ignore + else: + self.print_warning("decr with zero, ignored") else: - if r_value.value > 0: # type: ignore - return InplaceDecrStmt(l_value, r_value.value, self.sourceref) # type: ignore - elif r_value.value < 0: # type: ignore - return InplaceIncrStmt(l_value, -r_value.value, self.sourceref) # type: ignore - else: - self.print_warning("decr with zero, ignored") + if r_value.name: + if operator == "+=": + return InplaceIncrStmt(l_value, None, r_value.name, self.sourceref) + return InplaceDecrStmt(l_value, None, r_value.name, self.sourceref) return AugmentedAssignmentStmt(l_value, operator, r_value, self.sourceref) def parse_return(self, line: str) -> ReturnStmt: @@ -1022,7 +1075,7 @@ class Parser: elif isinstance(expression, MemMappedValue): return IntegerValue(expression.address, self.sourceref, datatype=DataType.WORD, name=expression.name) else: - raise self.PError("cannot take the address of this type") + raise self.PError("cannot take the address of type " + expression.__class__.__name__) elif text[0] in "-.0123456789$%~": number = parse_expr_as_number(text, self.cur_block.symbols, self.ppsymbols, self.sourceref) try: diff --git a/il65/symbols.py b/il65/symbols.py index d09773a4d..d32b29b8c 100644 --- a/il65/symbols.py +++ b/il65/symbols.py @@ -121,7 +121,7 @@ class VariableDef(SymbolDefinition): def __init__(self, blockname: str, name: str, sourceref: SourceRef, datatype: DataType, allocate: bool, *, value: PrimitiveType, length: int, address: Optional[int]=None, - register: str=None, matrixsize: Tuple[int, int]=None) -> None: + register: str=None, matrixsize: Tuple[int, int]=None, sourcecomment: str="") -> None: super().__init__(blockname, name, sourceref, allocate) self.type = datatype self.address = address @@ -129,6 +129,7 @@ class VariableDef(SymbolDefinition): self.value = value self.register = register self.matrixsize = matrixsize + self.sourcecomment = sourcecomment @property def is_memmap(self): @@ -406,8 +407,9 @@ class SymbolTable: address = self._zeropage.allocate(name, datatype) except LookupError: raise SymbolError("no space in ZP left for global 5-byte MFLT float variable (try zp clobber)") + sourcecomment = "float " + str(value) self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.FLOAT, allocate, - value=value, length=1, address=address) + value=value, length=1, address=address, sourcecomment=sourcecomment) elif datatype == DataType.BYTEARRAY: self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.BYTEARRAY, allocate, value=value, length=length, address=address) diff --git a/lib/il65lib.ill b/lib/il65lib.ill index cc4b594f3..ab86b3838 100644 --- a/lib/il65lib.ill +++ b/lib/il65lib.ill @@ -151,5 +151,33 @@ float_sub_one ldy SCRATCH_ZP2 jmp c64.FTOMEMXY ; float XY = fac1 + +; ---- add MFLT pointed to by SCRATCH_ZPWORD1 to the MFLT pointed to by X/Y. Clobbers A, X, Y +float_add_SW1_to_XY + stx SCRATCH_ZP1 + sty SCRATCH_ZP2 + txa + jsr c64.MOVFM ; fac1 = float XY + lda SCRATCH_ZPWORD1 + ldy SCRATCH_ZPWORD1+1 + jsr c64.FADD ; fac1 += SCRATCH_ZPWORD1 + ldx SCRATCH_ZP1 + ldy SCRATCH_ZP2 + jmp c64.FTOMEMXY ; float XY = fac1 + + +; ---- subtract MFLT pointed to by SCRATCH_ZPWORD1 from the MFLT pointed to by X/Y. Clobbers A, X, Y +float_sub_SW1_from_XY + stx SCRATCH_ZP1 + sty SCRATCH_ZP2 + lda SCRATCH_ZPWORD1 + ldy SCRATCH_ZPWORD1+1 + jsr c64.MOVFM ; fac1 = SCRATCH_ZPWORD1 + txa + ldy SCRATCH_ZP2 + jsr c64.FSUB ; fac1 = float XY - SCRATCH_ZPWORD1 + ldx SCRATCH_ZP1 + ldy SCRATCH_ZP2 + jmp c64.FTOMEMXY ; float XY = fac1 } } diff --git a/todo.ill b/todo.ill index 994143b2e..2731d9242 100644 --- a/todo.ill +++ b/todo.ill @@ -7,32 +7,12 @@ import "c64lib" ~ main { var .float float1 = 123.456 -start - c64.MOVFM(#float1) - c64.FPRINTLN() - float1++ - c64.MOVFM(#float1) - c64.FPRINTLN() - float1-=1 - c64.MOVFM(#float1) - c64.FPRINTLN() - float1-- - c64.MOVFM(#float1) - c64.FPRINTLN() - ;float1+=0.5 - ;c64.MOVFM(#float1) - ;c64.FPRINTLN() - ;float1+=2235.55 - ;c64.MOVFM(#float1) - ;c64.FPRINTLN() - ;float1-=999.55 - ;c64.MOVFM(#float1) - ;c64.FPRINTLN() - ;float1-=33333.456 - ;c64.MOVFM(#float1) - ;c64.FPRINTLN() + var .float float2 = 99.99 + const .float flc = 1234.55 + const .text foostr = "derp" + var .text foostr2 = "derp2" - return +start A = $11 X = $22