From afc4ba8ff02773e40bf45a21df53a6449a41a21d Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 15 Jan 2018 00:20:36 +0100 Subject: [PATCH] fixed a bunch of issues --- il65/compile.py | 75 +++++++++++++++++++++++++++++++---------- il65/datatypes.py | 29 ---------------- il65/emit/assignment.py | 15 --------- il65/emit/generate.py | 2 +- il65/emit/incrdecr.py | 9 ++--- il65/lib/c64lib.ill | 5 ++- il65/lib/il65lib.ill | 2 ++ il65/optimize.py | 9 +++-- il65/plylex.py | 4 +-- il65/plyparse.py | 62 ++++++++++++++++++++++++++++++---- reference.md | 10 ++++++ tests/test_parser.py | 43 ++++++++++++++++++----- testsource/calls.ill | 2 ++ testsource/dtypes.ill | 5 ++- testsource/large.ill | 60 ++++++++++++++++++++++----------- testsource/source1.ill | 1 + testsource/source2.ill | 2 ++ todo.ill | 36 +++++++++++++------- 18 files changed, 251 insertions(+), 120 deletions(-) diff --git a/il65/compile.py b/il65/compile.py index 534cf1ab9..c7a97ada0 100644 --- a/il65/compile.py +++ b/il65/compile.py @@ -13,7 +13,7 @@ from typing import Optional, Tuple, Set, Dict, List, Any, no_type_check import attr from .plyparse import parse_file, ParseError, Module, Directive, Block, Subroutine, Scope, VarDef, LiteralValue, \ SubCall, Goto, Return, Assignment, InlineAssembly, Register, Expression, ProgramFormat, ZpOptions,\ - SymbolName, Dereference, AddressOf, IncrDecr, TargetRegisters + SymbolName, Dereference, AddressOf, IncrDecr, Label, AstNode, datatype_of, coerce_constant_value from .plylex import SourceRef, print_bold from .optimize import optimize from .datatypes import DataType, VarType @@ -57,15 +57,43 @@ class PlyParser: def semantic_check(self, module: Module) -> None: # perform semantic analysis / checks on the syntactic parse tree we have so far + def check_last_statement_is_return(last_stmt: AstNode) -> None: + if isinstance(last_stmt, Subroutine): + return + if isinstance(last_stmt, Directive) and last_stmt.name == "noreturn": + return + if isinstance(last_stmt, InlineAssembly): + for line in reversed(last_stmt.assembly.splitlines()): + line = line.strip() + if line.startswith(";"): + continue + if "jmp " in line or "jmp\t" in line or "rts" in line or "rti" in line: + return + raise ParseError("last statement in a block/subroutine must be a return or goto, " + "(or %noreturn directive to silence this error)", last_stmt.sourceref) + for block, parent in module.all_scopes(): assert isinstance(block, (Module, Block, Subroutine)) assert parent is None or isinstance(parent, (Module, Block, Subroutine)) + previous_stmt = None for stmt in block.nodes: + if isinstance(stmt, Subroutine): + # the previous statement (if any) must be a Goto or Return + if previous_stmt and not isinstance(previous_stmt, (Goto, Return, VarDef, Subroutine)): + raise ParseError("statement preceding subroutine must be a goto or return or another subroutine", stmt.sourceref) + if isinstance(previous_stmt, Subroutine): + # the statement after a subroutine can not be some random executable instruction because it could not be reached + if not isinstance(stmt, (Subroutine, Label, Directive, InlineAssembly, VarDef)): + raise ParseError("statement following a subroutine can't be runnable code, " + "at least use a label first", stmt.sourceref) + previous_stmt = stmt if isinstance(stmt, IncrDecr): if isinstance(stmt.target, SymbolName): symdef = block.scope[stmt.target.name] if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST: raise ParseError("cannot modify a constant", stmt.sourceref) + if parent and block.name != "ZP" and not isinstance(stmt, (Return, Goto)): + check_last_statement_is_return(stmt) def check_and_merge_zeropages(self, module: Module) -> None: # merge all ZP blocks into one @@ -122,6 +150,15 @@ class PlyParser: raise except Exception as x: self.handle_internal_error(x, "process_expressions of node {} in block {}".format(node, block.name)) + if isinstance(node, IncrDecr) and node.howmuch not in (0, 1): + _, node.howmuch = coerce_constant_value(datatype_of(node.target, block.scope), node.howmuch, node.sourceref) + elif isinstance(node, Assignment): + lvalue_types = set(datatype_of(lv, block.scope) for lv in node.left) + if len(lvalue_types) == 1: + _, node.right = coerce_constant_value(lvalue_types.pop(), node.right, node.sourceref) + else: + for lv_dt in lvalue_types: + coerce_constant_value(lv_dt, node.right, node.sourceref) def create_multiassigns(self, module: Module) -> None: # create multi-assign statements from nested assignments (A=B=C=5), @@ -201,7 +238,7 @@ class PlyParser: for directive in block.scope.filter_nodes(Directive): if directive.name == "saveregisters": set_save_registers(block.scope, directive) - elif directive.name in ("breakpoint", "asmbinary", "asminclude"): + elif directive.name in ("breakpoint", "asmbinary", "asminclude", "noreturn"): continue else: raise NotImplementedError(directive.name) @@ -211,7 +248,7 @@ class PlyParser: for directive in block.scope.filter_nodes(Directive): if directive.name == "saveregisters": set_save_registers(block.scope, directive) - elif directive.name in ("breakpoint", "asmbinary", "asminclude"): + elif directive.name in ("breakpoint", "asmbinary", "asminclude", "noreturn"): continue else: raise NotImplementedError(directive.name) @@ -326,7 +363,7 @@ class PlyParser: # check module-level directives imports = set() # type: Set[str] for directive in node.scope.filter_nodes(Directive): - if directive.name not in {"output", "zp", "address", "import", "saveregisters"}: + if directive.name not in {"output", "zp", "address", "import", "saveregisters", "noreturn"}: raise ParseError("invalid directive in module", directive.sourceref) if directive.name == "import": if imports & set(directive.args): @@ -339,7 +376,7 @@ class PlyParser: continue for sub_node in node.scope.nodes: if isinstance(sub_node, Directive): - if sub_node.name not in {"asmbinary", "asminclude", "breakpoint", "saveregisters"}: + if sub_node.name not in {"asmbinary", "asminclude", "breakpoint", "saveregisters", "noreturn"}: raise ParseError("invalid directive in " + node.__class__.__name__.lower(), sub_node.sourceref) if sub_node.name == "saveregisters" and not first_node: raise ParseError("saveregisters directive must be the first", sub_node.sourceref) @@ -396,28 +433,30 @@ class PlyParser: def handle_parse_error(self, exc: ParseError) -> None: self.parse_errors += 1 - if sys.stderr.isatty(): - print("\x1b[1m", file=sys.stderr) + out = sys.stdout + if out.isatty(): + print("\x1b[1m", file=out) if self.parsing_import: - print("Error (in imported file):", str(exc), file=sys.stderr) + print("Error (in imported file):", str(exc), file=out) else: - print("Error:", str(exc), file=sys.stderr) + print("Error:", str(exc), file=out) sourcetext = linecache.getline(exc.sourceref.file, exc.sourceref.line).rstrip() if sourcetext: - print(" " + sourcetext.expandtabs(8), file=sys.stderr) + print(" " + sourcetext.expandtabs(8), file=out) if exc.sourceref.column: - print(' ' * (1+exc.sourceref.column) + '^', file=sys.stderr) - if sys.stderr.isatty(): - print("\x1b[0m", file=sys.stderr, end="", flush=True) + print(' ' * (1+exc.sourceref.column) + '^', file=out) + if out.isatty(): + print("\x1b[0m", file=out, end="", flush=True) def handle_internal_error(self, exc: Exception, msg: str="") -> None: - if sys.stderr.isatty(): - print("\x1b[1m", file=sys.stderr) - print("\nERROR: internal parser error: ", exc, file=sys.stderr) + out = sys.stdout + if out.isatty(): + print("\x1b[1m", file=out) + print("\nERROR: internal parser error: ", exc, file=out) if msg: print(" Message:", msg, end="\n\n") - if sys.stderr.isatty(): - print("\x1b[0m", file=sys.stderr, end="", flush=True) + if out.isatty(): + print("\x1b[0m", file=out, end="", flush=True) raise exc diff --git a/il65/datatypes.py b/il65/datatypes.py index 1620fb74c..9b8457557 100644 --- a/il65/datatypes.py +++ b/il65/datatypes.py @@ -67,35 +67,6 @@ FLOAT_MAX_POSITIVE = 1.7014118345e+38 FLOAT_MAX_NEGATIVE = -1.7014118345e+38 -def coerce_value(datatype: DataType, value: Union[int, float, str], sourceref: SourceRef=None) -> Tuple[bool, Union[int, float, str]]: - # if we're a BYTE type, and the value is a single character, convert it to the numeric value - def verify_bounds(value: Union[int, float, str]) -> None: - # 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: " + str(value)) - if datatype == DataType.WORD and not (0 <= value <= 0xffff): # type: ignore - 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: " + 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) - # if we're an integer value and the passed value is float, truncate it (and give a warning) - if datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX) and isinstance(value, float): - frac = math.modf(value) - if frac != 0: - print_warning("float value truncated ({} to datatype {})".format(value, datatype.name), sourceref=sourceref) - value = int(value) - verify_bounds(value) - return True, value - verify_bounds(value) - return False, value - - def char_to_bytevalue(character: str, petscii: bool=True) -> int: assert len(character) == 1 if petscii: diff --git a/il65/emit/assignment.py b/il65/emit/assignment.py index 5dcbe9e95..021885b0f 100644 --- a/il65/emit/assignment.py +++ b/il65/emit/assignment.py @@ -8,21 +8,6 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 from typing import Callable from ..plyparse import AstNode, Scope, VarDef, Dereference, Register, TargetRegisters,\ LiteralValue, Assignment, AugAssignment -from ..datatypes import DataType -from ..plyparse import SymbolName - - -def datatype_of(assignmenttarget: AstNode, scope: Scope) -> DataType: - if isinstance(assignmenttarget, (VarDef, Dereference, Register)): - return assignmenttarget.datatype - elif isinstance(assignmenttarget, SymbolName): - symdef = scope[assignmenttarget.name] - if isinstance(symdef, VarDef): - return symdef.datatype - elif isinstance(assignmenttarget, TargetRegisters): - if len(assignmenttarget.registers) == 1: - return datatype_of(assignmenttarget.registers[0], scope) - raise TypeError("cannot determine datatype", assignmenttarget) def generate_assignment(out: Callable, stmt: Assignment) -> None: diff --git a/il65/emit/generate.py b/il65/emit/generate.py index cd854595d..aa2609e7e 100644 --- a/il65/emit/generate.py +++ b/il65/emit/generate.py @@ -117,7 +117,7 @@ class AssemblyGenerator: out("\vcld") out("\vjmp _il65_restore_zeropage\n") # include the assembly code for the save/restore zeropage routines - zprestorefile = os.path.join(os.path.split(__file__)[0], "lib", "restorezp.asm") + zprestorefile = os.path.join(os.path.split(__file__)[0], "../lib", "restorezp.asm") with open(zprestorefile, "rU") as f: for line in f.readlines(): out(line.rstrip("\n")) diff --git a/il65/emit/incrdecr.py b/il65/emit/incrdecr.py index 036753beb..38683f249 100644 --- a/il65/emit/incrdecr.py +++ b/il65/emit/incrdecr.py @@ -1,15 +1,16 @@ """ Programming Language for 6502/6510 microprocessors, codename 'Sick' This is the code generator for the in-place incr and decr instructions. +Incrementing or decrementing variables by 1 (or another constant amount) +is quite frequent and this generates assembly code tweaked for this case. Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 """ from typing import Callable -from ..plyparse import Scope, VarType, VarDef, Register, TargetRegisters, IncrDecr, SymbolName, Dereference +from ..plyparse import Scope, VarType, VarDef, Register, IncrDecr, SymbolName, Dereference, datatype_of from ..datatypes import DataType, REGISTER_BYTES -from . import CodeError, to_hex, preserving_registers -from .assignment import datatype_of +from . import CodeError, preserving_registers def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope) -> None: @@ -190,7 +191,7 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope) -> None: out("\vjsr c64flt.float_add_one") else: out("\vjsr c64flt.float_sub_one") - elif stmt.value.name: # XXX + elif NOTYETIMPLEMENTED: # XXX for the float += otherfloat cases with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True): out("\vlda #<" + stmt.value.name) out("\vsta c64.SCRATCH_ZPWORD1") diff --git a/il65/lib/c64lib.ill b/il65/lib/c64lib.ill index 6ade9999b..502d63510 100644 --- a/il65/lib/c64lib.ill +++ b/il65/lib/c64lib.ill @@ -259,7 +259,7 @@ sub init_system () -> (?) { } } - +%noreturn } ; ------ end of block c64 @@ -442,6 +442,8 @@ sub float_sub_SW1_from_XY (mflt: XY) -> (?) { } } +%noreturn + } ; ------ end of block c64flt @@ -1029,6 +1031,7 @@ sub input_chars (buffer: AX) -> (A?, Y) { } } +%noreturn } ; ---- end block c64scr diff --git a/il65/lib/il65lib.ill b/il65/lib/il65lib.ill index 186c7f934..5b3eef1a7 100644 --- a/il65/lib/il65lib.ill +++ b/il65/lib/il65lib.ill @@ -168,4 +168,6 @@ _done rts } + + %noreturn } diff --git a/il65/optimize.py b/il65/optimize.py index 34cbeca4e..dc619c276 100644 --- a/il65/optimize.py +++ b/il65/optimize.py @@ -5,7 +5,8 @@ This is the optimizer that applies various optimizations to the parse tree. Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 """ -from .plyparse import Module, Subroutine, Block, Directive, Assignment, AugAssignment, Goto, Expression, IncrDecr +from .plyparse import Module, Subroutine, Block, Directive, Assignment, AugAssignment, Goto, Expression, IncrDecr,\ + datatype_of, coerce_constant_value from .plylex import print_warning, print_bold @@ -21,6 +22,7 @@ class Optimizer: self.optimize_multiassigns() self.remove_unused_subroutines() self.optimize_compare_with_zero() + # @todo join multiple incr/decr of same var into one (if value stays < 256) self.remove_empty_blocks() def optimize_assignments(self): @@ -47,8 +49,11 @@ class Optimizer: new_stmt = Assignment(left=[assignment.left], right=0, sourceref=assignment.sourceref) block.scope.replace_node(assignment, new_stmt) if assignment.operator in ("+=", "-=") and 0 < assignment.right < 256: + howmuch = assignment.right + if howmuch not in (0, 1): + _, howmuch = coerce_constant_value(datatype_of(assignment.left, block.scope), howmuch, assignment.sourceref) new_stmt = IncrDecr(target=assignment.left, operator="++" if assignment.operator == "+=" else "--", - howmuch=assignment.right, sourceref=assignment.sourceref) + howmuch=howmuch, sourceref=assignment.sourceref) block.scope.replace_node(assignment, new_stmt) def combine_assignments_into_multi(self): diff --git a/il65/plylex.py b/il65/plylex.py index 10d5a23d8..5045116b4 100644 --- a/il65/plylex.py +++ b/il65/plylex.py @@ -321,7 +321,7 @@ def t_error(t): if hasattr(t.lexer, "error_function"): t.lexer.error_function(sref, "illegal character '{:s}'", t.value[0]) else: - print("{}: illegal character '{:s}'".format(sref, t.value[0]), file=sys.stderr) + print("{}: illegal character '{:s}'".format(sref, t.value[0])) t.lexer.skip(1) @@ -332,7 +332,7 @@ def custom_error(t, message): if hasattr(t.lexer, "error_function"): t.lexer.error_function(sref, message) else: - print(sref, message, file=sys.stderr) + print(sref, message) t.lexer.skip(1) diff --git a/il65/plyparse.py b/il65/plyparse.py index 244afbe4d..bc0dc33a8 100644 --- a/il65/plyparse.py +++ b/il65/plyparse.py @@ -13,8 +13,9 @@ from collections import defaultdict 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, REGISTER_SYMBOLS, REGISTER_BYTES, REGISTER_WORDS +from .plylex import SourceRef, tokens, lexer, find_tok_column, print_warning +from .datatypes import DataType, VarType, REGISTER_SYMBOLS, REGISTER_BYTES, REGISTER_WORDS, \ + char_to_bytevalue, FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE class ProgramFormat(enum.Enum): @@ -293,6 +294,8 @@ class Register(AstNode): self.datatype = DataType.BYTE elif self.name in REGISTER_WORDS: self.datatype = DataType.WORD + else: + self.datatype = None # register 'SC' etc. def __hash__(self) -> int: return hash(self.name) @@ -359,21 +362,21 @@ class Return(AstNode): 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) + _, self.value_A = coerce_constant_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.sourceref) if isinstance(self.value_X, (int, float, str, bool)): try: - _, self.value_X = coerce_value(DataType.BYTE, self.value_X, self.sourceref) + _, self.value_X = coerce_constant_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.sourceref) if isinstance(self.value_Y, (int, float, str, bool)): try: - _, self.value_Y = coerce_value(DataType.BYTE, self.value_Y, self.sourceref) + _, self.value_Y = coerce_constant_value(DataType.BYTE, self.value_Y, self.sourceref) except (OverflowError, TypeError) as x: raise ParseError("third value (Y): " + str(x), self.sourceref) from None @@ -444,7 +447,7 @@ class VarDef(AstNode): assert not isinstance(self.value, Expression), "processed expression for vardef should reduce to a constant value" if self.vartype in (VarType.CONST, VarType.VAR): try: - _, self.value = coerce_value(self.datatype, self.value, self.sourceref) + _, self.value = coerce_constant_value(self.datatype, self.value, self.sourceref) except OverflowError as x: raise ParseError(str(x), self.sourceref) from None except TypeError as x: @@ -538,7 +541,7 @@ class AddressOf(AstNode): @attr.s(cmp=False, repr=False) class IncrDecr(AstNode): - # increment or decrement something by a constant value (1 or more) + # increment or decrement something by a CONSTANT value (1 or more) target = attr.ib() operator = attr.ib(type=str, validator=attr.validators.in_(["++", "--"])) howmuch = attr.ib(default=1) @@ -616,6 +619,51 @@ class Expression(AstNode): print(tree(self, 0)) +def datatype_of(assignmenttarget: AstNode, scope: Scope) -> DataType: + # tries to determine the DataType of an assignment target node + if isinstance(assignmenttarget, (VarDef, Dereference, Register)): + return assignmenttarget.datatype + elif isinstance(assignmenttarget, SymbolName): + symdef = scope[assignmenttarget.name] + if isinstance(symdef, VarDef): + return symdef.datatype + elif isinstance(assignmenttarget, TargetRegisters): + if len(assignmenttarget.registers) == 1: + return datatype_of(assignmenttarget.registers[0], scope) + raise TypeError("cannot determine datatype", assignmenttarget) + + +def coerce_constant_value(datatype: DataType, value: Union[int, float, str], + sourceref: SourceRef=None) -> Tuple[bool, Union[int, float, str]]: + # if we're a BYTE type, and the value is a single character, convert it to the numeric value + def verify_bounds(value: Union[int, float, str]) -> None: + # 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: " + str(value)) + if datatype == DataType.WORD and not (0 <= value <= 0xffff): # type: ignore + 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: " + 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) + # if we're an integer value and the passed value is float, truncate it (and give a warning) + if datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX) and isinstance(value, float): + frac = math.modf(value) + if frac != 0: + print_warning("float value truncated ({} to datatype {})".format(value, datatype.name), sourceref=sourceref) + value = int(value) + verify_bounds(value) + return True, value + if isinstance(value, (int, float)): + verify_bounds(value) + return False, value + + def process_expression(value: Any, scope: Scope, sourceref: SourceRef) -> Any: # process/simplify all expressions (constant folding etc) if isinstance(value, AstNode): diff --git a/reference.md b/reference.md index ae90d0fee..994b70e6a 100644 --- a/reference.md +++ b/reference.md @@ -627,3 +627,13 @@ and SPRITES (24x21 monochrome or 12x21 multicolor = 63 bytes) A = X * Y A /= Y A = Y / Y + + +Troubleshooting +--------------- + +Getting assembler error about undefined symbols such as ``not defined 'c64flt'``? +This happens when your program uses floating point values, and you forgot to import the ``c64lib``. +If you use floating points, the program will need routines from that library. +Fix it by adding an ``%import c64lib``. + diff --git a/tests/test_parser.py b/tests/test_parser.py index ac73cb50d..eec78df54 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,6 +1,13 @@ from il65.plylex import lexer, tokens, find_tok_column, literals, reserved, SourceRef from il65.plyparse import parser, TokenFilter, Module, Subroutine, Block, Return, Scope, \ - VarDef, Expression, LiteralValue, Label, SubCall, CallTarget, SymbolName + VarDef, Expression, LiteralValue, Label, SubCall, CallTarget, SymbolName, Dereference +from il65.datatypes import DataType + + +def lexer_error(sourceref: SourceRef, fmtstring: str, *args: str) -> None: + print("ERROR: {}: {}".format(sourceref, fmtstring.format(*args))) + +lexer.error_function = lexer_error def test_lexer_definitions(): @@ -184,10 +191,10 @@ def test_parser_2(): test_source_3 = """ ~ { - goto.XY = 5 - AX.text = 5 [$c000.word] = 5 - [AX.word] = 5 + [$c000 .byte] = 5 + [AX .word] = 5 + [AX .float] = 5 } """ @@ -202,7 +209,27 @@ def test_typespec(): assert assignment2.right.value == 5 assert assignment3.right.value == 5 assert assignment4.right.value == 5 - print("A1", assignment1.left) - print("A2", assignment2.left) - print("A3", assignment3.left) - print("A4", assignment4.left) + assert len(assignment1.left) == 1 + assert len(assignment2.left) == 1 + assert len(assignment3.left) == 1 + assert len(assignment4.left) == 1 + t1 = assignment1.left[0] + t2 = assignment2.left[0] + t3 = assignment3.left[0] + t4 = assignment4.left[0] + assert isinstance(t1, Dereference) + assert isinstance(t2, Dereference) + assert isinstance(t3, Dereference) + assert isinstance(t4, Dereference) + assert t1.location == 0xc000 + assert t2.location == 0xc000 + assert t3.location == "AX" + assert t4.location == "AX" + assert t1.datatype == DataType.WORD + assert t2.datatype == DataType.BYTE + assert t3.datatype == DataType.WORD + assert t4.datatype == DataType.FLOAT + assert t1.size is None + assert t2.size is None + assert t3.size is None + assert t4.size is None diff --git a/testsource/calls.ill b/testsource/calls.ill index ab6cb811d..6cddb5072 100644 --- a/testsource/calls.ill +++ b/testsource/calls.ill @@ -141,6 +141,8 @@ bar: goto $c000 constw() sub1() main.start() + return + } diff --git a/testsource/dtypes.ill b/testsource/dtypes.ill index 5eca7fadf..5d40bf6cc 100644 --- a/testsource/dtypes.ill +++ b/testsource/dtypes.ill @@ -36,6 +36,7 @@ var foovar1 memory foomem1 = $f0f0 const fooconst = 1.234 + return } @@ -152,7 +153,8 @@ start: A = 0 A = '@' A = 1.2345 - A = true + A=X=Y= true + A=XY= true A = false A = 255 A = X @@ -308,4 +310,5 @@ start: [$c000.word] = "another" ; must reuse string [$c100.word] = "text-immediate" ; must reuse string [$c200.word] = "" ; must reuse string + return } diff --git a/testsource/large.ill b/testsource/large.ill index d5a44058b..8da55ad23 100644 --- a/testsource/large.ill +++ b/testsource/large.ill @@ -295,7 +295,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -404,6 +404,7 @@ bar: constw() sub1() main.start() + %noreturn } ~ blockdtypes2 { @@ -648,7 +649,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -757,6 +758,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes3 { @@ -1001,7 +1003,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -1110,6 +1112,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes4 { @@ -1354,7 +1357,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -1463,6 +1466,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes5 { @@ -1707,7 +1711,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -1816,6 +1820,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes6 { @@ -2060,7 +2065,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -2169,6 +2174,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes7 { @@ -2413,7 +2419,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -2522,6 +2528,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes8 { @@ -2766,7 +2773,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -2875,6 +2882,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes9 { @@ -3119,7 +3127,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -3228,6 +3236,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes10 { @@ -3472,7 +3481,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -3581,6 +3590,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes11 { @@ -3825,7 +3835,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -3934,6 +3944,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes12 { @@ -4178,7 +4189,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -4287,6 +4298,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes13 { @@ -4531,7 +4543,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -4640,6 +4652,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes14 { @@ -4884,7 +4897,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -4993,6 +5006,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes15 { @@ -5237,7 +5251,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -5346,6 +5360,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes16 { @@ -5590,7 +5605,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -5699,6 +5714,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes17 { @@ -5943,7 +5959,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -6052,6 +6068,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes18 { @@ -6296,7 +6313,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -6405,6 +6422,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes19 { @@ -6649,7 +6667,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -6758,6 +6776,7 @@ bar: constw() sub1() main.start() +%noreturn } ~ blockdtypes20 { @@ -7002,7 +7021,7 @@ max: initfloat2 = initword2 initfloat1 = uninitfloat initfloat1 = initfloat2 - +%noreturn } @@ -7111,5 +7130,6 @@ bar: constw() sub1() main.start() +%noreturn } diff --git a/testsource/source1.ill b/testsource/source1.ill index 829e2fe89..4f3f91b3d 100644 --- a/testsource/source1.ill +++ b/testsource/source1.ill @@ -117,6 +117,7 @@ somelabel1222: const red = 2 const .word border2 = $d020 const .word screen2 = $d021 + return } ~ block3 { diff --git a/testsource/source2.ill b/testsource/source2.ill index 9a418130d..267f8ffbc 100644 --- a/testsource/source2.ill +++ b/testsource/source2.ill @@ -22,4 +22,6 @@ start: ;%asmbinary " included.binary 234 " ;%asmbinary " included.binary", $40 %asmbinary "included.binary", $40, $200 + + return } diff --git a/todo.ill b/todo.ill index 4630fe919..405b19bb8 100644 --- a/todo.ill +++ b/todo.ill @@ -1,4 +1,5 @@ %output basic +%import c64lib ~ main { @@ -15,7 +16,7 @@ var .array(20) arr1 = $ea var .wordarray(20) arr2 = $ea memory border = $d020 - const .word cword = 2 + const .word cword = 5/3 start: %breakpoint abc,def @@ -27,16 +28,23 @@ start: border++ zp1_1++ zpf1++ - [AX]++ - [AX .byte]++ - [AX .word]++ - [AX .float]++ - [$ccc0]++ - [$ccc0 .byte]++ - [$ccc0 .word]++ - [$ccc0 .float]++ + ;[AX]++ + ;[AX .byte]++ + ;[AX .word]++ + ;[AX .float]++ + ;[$ccc0]++ + ;[$ccc0 .byte]++ + ;[$ccc0 .word]++ + ;[$ccc0 .float]++ A+=2 + A+=3 + XY+=6 + XY+=222 + A=222/13 ; @todo warn truncate (in assignment stmt) XY+=666 + zpf1+=1 + zpf1+=2 + zpf1+=2.123425425 ; @todo store as constant float with generated name, replace value node foobar() @@ -47,8 +55,12 @@ sub foobar () -> () { return %breakpoint yep - - ; @todo check that subs/asm blocks end with return/rts -} + return +} + +label2: + A++ + %noreturn + }