From 8fc6a5ada99ca82527dd7f179cf0517de19febc4 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 15 Jan 2018 02:22:14 +0100 Subject: [PATCH] convert bool to 1/0 and single char to byte --- il65/compile.py | 12 +------ il65/emit/__init__.py | 1 + il65/emit/incrdecr.py | 2 +- il65/emit/variables.py | 13 +++---- il65/optimize.py | 1 + il65/plyparse.py | 28 +++++++++------ tests/test_core.py | 50 ++++++++++++++------------ tests/test_parser.py | 57 +++++++++++++++++++++++++++-- testsource/numbergame.ill | 13 ++++++- todo.ill | 76 ++++++++++++++++++++++----------------- 10 files changed, 164 insertions(+), 89 deletions(-) diff --git a/il65/compile.py b/il65/compile.py index c7a97ada0..9f1b6cfcb 100644 --- a/il65/compile.py +++ b/il65/compile.py @@ -15,7 +15,6 @@ from .plyparse import parse_file, ParseError, Module, Directive, Block, Subrouti SubCall, Goto, Return, Assignment, InlineAssembly, Register, Expression, ProgramFormat, ZpOptions,\ 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 @@ -212,7 +211,7 @@ class PlyParser: else: raise ParseError("invalid directive args", directive.sourceref) elif directive.name == "address": - if len(directive.args) != 1 or not isinstance(directive.args[0], int): + if len(directive.args) != 1 or type(directive.args[0]) is not int: raise ParseError("expected one integer directive argument", directive.sourceref) if block.format == ProgramFormat.BASIC: raise ParseError("basic cannot have a custom load address", directive.sourceref) @@ -533,12 +532,3 @@ class Zeropage: def available(self) -> int: return len(self.free) - - -if __name__ == "__main__": - description = "Compiler for IL65 language, code name 'Sick'" - print("\n" + description + "\n") - plyparser = PlyParser() - m = plyparser.parse_file(sys.argv[1]) - optimize(m) - print() diff --git a/il65/emit/__init__.py b/il65/emit/__init__.py index d86de2282..0922faf6a 100644 --- a/il65/emit/__init__.py +++ b/il65/emit/__init__.py @@ -22,6 +22,7 @@ def to_hex(number: int) -> str: # 0..15 -> "0".."15" # 16..255 -> "$10".."$ff" # 256..65536 -> "$0100".."$ffff" + assert type(number) is int if number is None: raise ValueError("number") if 0 <= number < 16: diff --git a/il65/emit/incrdecr.py b/il65/emit/incrdecr.py index 38683f249..34f5ccba7 100644 --- a/il65/emit/incrdecr.py +++ b/il65/emit/incrdecr.py @@ -209,4 +209,4 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope) -> None: raise CodeError("cannot in/decrement memory of type " + str(target.datatype), stmt.howmuch) else: - raise CodeError("cannot in/decrement", target) + raise CodeError("cannot in/decrement", target) # @todo support more such as [dereference]++ diff --git a/il65/emit/variables.py b/il65/emit/variables.py index 9b4e51adb..a3d3a5cc3 100644 --- a/il65/emit/variables.py +++ b/il65/emit/variables.py @@ -15,6 +15,7 @@ from . import to_hex, to_mflpt5, CodeError def generate_block_init(out: Callable, block: Block) -> None: # generate the block initializer # @todo add a block initializer subroutine that can contain custom reset/init code? (static initializer) + # @todo will be called at program start automatically, so there's no risk of forgetting to call it manually def _memset(varname: str, value: int, size: int) -> None: if size > 6: @@ -59,13 +60,13 @@ def generate_block_init(out: Callable, block: Block) -> None: if vardef.vartype == VarType.VAR: vars_by_datatype[vardef.datatype].append(vardef) for bytevar in sorted(vars_by_datatype[DataType.BYTE], key=lambda vd: vd.value): - assert isinstance(bytevar.value, int) + assert type(bytevar.value) is int if bytevar.value != prev_value_a: out("\vlda #${:02x}".format(bytevar.value)) prev_value_a = bytevar.value out("\vsta {:s}".format(bytevar.name)) for wordvar in sorted(vars_by_datatype[DataType.WORD], key=lambda vd: vd.value): - assert isinstance(wordvar.value, int) + assert type(wordvar.value) is int v_hi, v_lo = divmod(wordvar.value, 256) if v_hi != prev_value_a: out("\vlda #${:02x}".format(v_hi)) @@ -80,13 +81,13 @@ def generate_block_init(out: Callable, block: Block) -> None: fpbytes = to_mflpt5(floatvar.value) # type: ignore float_inits[floatvar.name] = (floatvar.name, fpbytes, floatvar.value) for arrayvar in vars_by_datatype[DataType.BYTEARRAY]: - assert isinstance(arrayvar.value, int) + assert type(arrayvar.value) is int _memset(arrayvar.name, arrayvar.value, arrayvar.size[0]) for arrayvar in vars_by_datatype[DataType.WORDARRAY]: - assert isinstance(arrayvar.value, int) + assert type(arrayvar.value) is int _memsetw(arrayvar.name, arrayvar.value, arrayvar.size[0]) for arrayvar in vars_by_datatype[DataType.MATRIX]: - assert isinstance(arrayvar.value, int) + assert type(arrayvar.value) is int _memset(arrayvar.name, arrayvar.value, arrayvar.size[0] * arrayvar.size[1]) if float_inits: out("\vldx #4") @@ -248,7 +249,7 @@ def _format_string(value: str, screencodes: bool = False) -> str: def _numeric_value_str(value: Any, as_hex: bool=False) -> str: if isinstance(value, bool): return "1" if value else "0" - if isinstance(value, int): + if type(value) is int: if as_hex: return to_hex(value) return str(value) diff --git a/il65/optimize.py b/il65/optimize.py index dc619c276..7fc17637a 100644 --- a/il65/optimize.py +++ b/il65/optimize.py @@ -23,6 +23,7 @@ class Optimizer: self.remove_unused_subroutines() self.optimize_compare_with_zero() # @todo join multiple incr/decr of same var into one (if value stays < 256) + # @todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) self.remove_empty_blocks() def optimize_assignments(self): diff --git a/il65/plyparse.py b/il65/plyparse.py index bc0dc33a8..1bc30aa1d 100644 --- a/il65/plyparse.py +++ b/il65/plyparse.py @@ -530,9 +530,6 @@ class Dereference(AstNode): class LiteralValue(AstNode): value = attr.ib() - def __repr__(self) -> str: - return repr(self.value) - @attr.s(cmp=False, repr=False) class AddressOf(AstNode): @@ -645,12 +642,8 @@ def coerce_constant_value(datatype: DataType, value: Union[int, float, str], 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 isinstance(value, str) and len(value) == 1 and (datatype.isnumeric() or datatype.isarray()): + 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) @@ -661,6 +654,14 @@ def coerce_constant_value(datatype: DataType, value: Union[int, float, str], return True, value if isinstance(value, (int, float)): verify_bounds(value) + if isinstance(value, (Expression, SubCall)): + return False, value + elif datatype == DataType.WORD: + if not isinstance(value, (int, float, str, Dereference, Register, SymbolName, AddressOf)): + raise TypeError("cannot assign '{:s}' to {:s}".format(type(value).__name__, datatype.name.lower()), sourceref) + elif datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT): + if not isinstance(value, (int, float, Dereference, Register, SymbolName)): + raise TypeError("cannot assign '{:s}' to {:s}".format(type(value).__name__, datatype.name.lower()), sourceref) return False, value @@ -737,7 +738,7 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref) elif isinstance(target, Dereference): # '[...](1,2,3)' raise ExpressionEvaluationError("dereferenced value call is not a constant value", expr.sourceref) - elif isinstance(target, int): # '64738()' + elif type(target) is int: # '64738()' raise ExpressionEvaluationError("immediate address call is not a constant value", expr.sourceref) else: raise NotImplementedError("weird call target", target) @@ -1028,6 +1029,11 @@ def p_literal_value(p): | STRING | CHARACTER | BOOLEAN""" + tok = p.slice[-1] + if tok.type == "CHARACTER": + p[1] = char_to_bytevalue(p[1]) # character literals are converted to byte value. + elif tok.type == "BOOLEAN": + p[1] = int(p[1]) # boolean literals are converted to integer form (true=1, false=0). p[0] = LiteralValue(value=p[1], sourceref=_token_sref(p, 1)) @@ -1038,7 +1044,7 @@ def p_subroutine(p): body = p[10] if isinstance(body, Scope): p[0] = Subroutine(name=p[2], param_spec=p[4] or [], result_spec=p[8] or [], scope=body, sourceref=_token_sref(p, 1)) - elif isinstance(body, int): + elif type(body) is int: p[0] = Subroutine(name=p[2], param_spec=p[4] or [], result_spec=p[8] or [], address=body, sourceref=_token_sref(p, 1)) else: raise TypeError("subroutine_body", p.slice) diff --git a/tests/test_core.py b/tests/test_core.py index cd095cb49..e45dc11ed 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,6 @@ import pytest from il65 import datatypes +from il65.plyparse import coerce_constant_value from il65.compile import ParseError from il65.plylex import SourceRef from il65.emit import to_hex, to_mflpt5 @@ -100,37 +101,40 @@ def test_char_to_bytevalue(): def test_coerce_value(): - assert datatypes.coerce_value(datatypes.DataType.BYTE, 0) == (False, 0) - assert datatypes.coerce_value(datatypes.DataType.BYTE, 255) == (False, 255) - assert datatypes.coerce_value(datatypes.DataType.WORD, 0) == (False, 0) - assert datatypes.coerce_value(datatypes.DataType.WORD, 65535) == (False, 65535) - assert datatypes.coerce_value(datatypes.DataType.FLOAT, -999.22) == (False, -999.22) - 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") + assert coerce_constant_value(datatypes.DataType.BYTE, 0) == (False, 0) + assert coerce_constant_value(datatypes.DataType.BYTE, 255) == (False, 255) + assert coerce_constant_value(datatypes.DataType.BYTE, '@') == (True, 64) + assert coerce_constant_value(datatypes.DataType.WORD, 0) == (False, 0) + assert coerce_constant_value(datatypes.DataType.WORD, 65535) == (False, 65535) + assert coerce_constant_value(datatypes.DataType.WORD, '@') == (True, 64) + assert coerce_constant_value(datatypes.DataType.FLOAT, -999.22) == (False, -999.22) + assert coerce_constant_value(datatypes.DataType.FLOAT, 123.45) == (False, 123.45) + assert coerce_constant_value(datatypes.DataType.FLOAT, '@') == (True, 64) + assert coerce_constant_value(datatypes.DataType.BYTE, 5.678) == (True, 5) + assert coerce_constant_value(datatypes.DataType.WORD, 5.678) == (True, 5) + assert coerce_constant_value(datatypes.DataType.STRING, "string") == (False, "string") + assert coerce_constant_value(datatypes.DataType.STRING_P, "string") == (False, "string") + assert coerce_constant_value(datatypes.DataType.STRING_S, "string") == (False, "string") + assert coerce_constant_value(datatypes.DataType.STRING_PS, "string") == (False, "string") with pytest.raises(OverflowError): - datatypes.coerce_value(datatypes.DataType.BYTE, -1) + coerce_constant_value(datatypes.DataType.BYTE, -1) with pytest.raises(OverflowError): - datatypes.coerce_value(datatypes.DataType.BYTE, 256) + coerce_constant_value(datatypes.DataType.BYTE, 256) with pytest.raises(OverflowError): - datatypes.coerce_value(datatypes.DataType.BYTE, 256.12345) + coerce_constant_value(datatypes.DataType.BYTE, 256.12345) with pytest.raises(OverflowError): - datatypes.coerce_value(datatypes.DataType.WORD, -1) + coerce_constant_value(datatypes.DataType.WORD, -1) with pytest.raises(OverflowError): - datatypes.coerce_value(datatypes.DataType.WORD, 65536) + coerce_constant_value(datatypes.DataType.WORD, 65536) with pytest.raises(OverflowError): - datatypes.coerce_value(datatypes.DataType.WORD, 65536.12345) + coerce_constant_value(datatypes.DataType.WORD, 65536.12345) with pytest.raises(OverflowError): - datatypes.coerce_value(datatypes.DataType.FLOAT, -1.7014118346e+38) + coerce_constant_value(datatypes.DataType.FLOAT, -1.7014118346e+38) with pytest.raises(OverflowError): - datatypes.coerce_value(datatypes.DataType.FLOAT, 1.7014118347e+38) + coerce_constant_value(datatypes.DataType.FLOAT, 1.7014118347e+38) with pytest.raises(TypeError): - datatypes.coerce_value(datatypes.DataType.BYTE, "string") + coerce_constant_value(datatypes.DataType.BYTE, "string") with pytest.raises(TypeError): - datatypes.coerce_value(datatypes.DataType.WORD, "string") + coerce_constant_value(datatypes.DataType.WORD, "string") with pytest.raises(TypeError): - datatypes.coerce_value(datatypes.DataType.FLOAT, "string") + coerce_constant_value(datatypes.DataType.FLOAT, "string") diff --git a/tests/test_parser.py b/tests/test_parser.py index eec78df54..98cb69b6e 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,7 +1,7 @@ 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, Dereference -from il65.datatypes import DataType +from il65.datatypes import DataType, char_to_bytevalue def lexer_error(sourceref: SourceRef, fmtstring: str, *args: str) -> None: @@ -128,8 +128,8 @@ def test_parser(): assert isinstance(bool_vdef, VarDef) assert isinstance(bool_vdef.value, Expression) assert isinstance(bool_vdef.value.right, LiteralValue) - assert isinstance(bool_vdef.value.right.value, bool) - assert bool_vdef.value.right.value == True + assert isinstance(bool_vdef.value.right.value, int) + assert bool_vdef.value.right.value == 1 assert block.address == 49152 sub2 = block.scope["calculate"] assert sub2 is sub @@ -233,3 +233,54 @@ def test_typespec(): assert t2.size is None assert t3.size is None assert t4.size is None + + +test_source_4 = """ +~ { + var x1 = '@' + var x2 = 'π' + var x3 = 'abc' + A = '@' + A = 'π' + A = 'abc' +} +""" + + +def test_char_string(): + lexer.lineno = 1 + lexer.source_filename = "sourcefile" + filter = TokenFilter(lexer) + result = parser.parse(input=test_source_4, tokenfunc=filter.token) + nodes = result.nodes[0].nodes + var1, var2, var3, assgn1, assgn2, assgn3, = nodes + assert var1.value.value == 64 + assert var2.value.value == 126 + assert var3.value.value == "abc" + assert assgn1.right.value == 64 + assert assgn2.right.value == 126 + assert assgn3.right.value == "abc" + + + +test_source_5 = """ +~ { + var x1 = true + var x2 = false + A = true + A = false +} +""" + + +def test_boolean_int(): + lexer.lineno = 1 + lexer.source_filename = "sourcefile" + filter = TokenFilter(lexer) + result = parser.parse(input=test_source_5, tokenfunc=filter.token) + nodes = result.nodes[0].nodes + var1, var2, assgn1, assgn2, = nodes + assert type(var1.value.value) is int and var1.value.value == 1 + assert type(var2.value.value) is int and var2.value.value == 0 + assert type(assgn1.right.value) is int and assgn1.right.value == 1 + assert type(assgn2.right.value) is int and assgn2.right.value == 0 diff --git a/testsource/numbergame.ill b/testsource/numbergame.ill index b482486ac..2f7df13c7 100644 --- a/testsource/numbergame.ill +++ b/testsource/numbergame.ill @@ -85,7 +85,16 @@ game_over: c64scr.print_string("\nToo bad! It was: ") c64scr.print_byte_decimal(secretnumber) c64.CHROUT('\n') - goodbye() + goodbye() ; @todo fix subroutine usage tracking, it doesn't register this one + return + return + return + return + return + return + return + return + return return sub goodbye ()->() { @@ -93,6 +102,8 @@ sub goodbye ()->() { memory y = $c000 ; @todo vars in sub const q = 22 ; @todo const in sub + xxxxxx++ + y++ xxxxxx = q *4 xxxxxx = qqqqq *44 ;@todo symbol error diff --git a/todo.ill b/todo.ill index 405b19bb8..cc65464ce 100644 --- a/todo.ill +++ b/todo.ill @@ -4,30 +4,58 @@ ~ main { - var zp1_1 = 200 - var zp1_2 = 200 - var .float zpf1 - var .text zp_s1 = "hello\n" - var .ptext zp_s2 = "goodbye\n" - var .stext zp_s3 = "welcome\n" - var .pstext zp_s4 = "go away\n" - const .text ctext = "constant\n" + var .byte v1t = true + var .byte v1f = false + var .word v2t = true + var .word v2f = false + var .float v3t = true + var .float v3f = false + var .text v4t = true + var .text v4f = false + var .array(3) v5t = true + var .array(3) v5f = false + var .array(10) v6t = true + var .array(10) v6f = false + var .wordarray(3) v7t = true + var .wordarray(3) v7f = false + var .wordarray(10) v8t = true + var .wordarray(10) v8f = false + var .matrix(2,2) v9t = true + var .matrix(2,2) v9f = false + var .matrix(5,5) v10t = true + var .matrix(5,5) v10f = false + + const .byte c1t=true + const .byte c1f=false + const .word c2t=true + const .word c2f=false + const .float c3t=true + const .float c3f=false - var .array(20) arr1 = $ea - var .wordarray(20) arr2 = $ea - memory border = $d020 - const .word cword = 5/3 start: %breakpoint abc,def + ;c3f=444 ; @todo constant error + ;c3f=c3f ; @todo constant error + ;c3f=c2t ; @todo constant error + ;c3f+=2.23424 ; @todo constant error + v3t++ + v3t+=1 + v3t+=0 + ;v3t+=2.23424 ; @todo store as constant float with generated name, replace value node + ;v3t+=2.23424 ; @todo store as constant float with generated name, replace value node + ;v3t+=2.23425 ; @todo store as constant float with generated name, replace value node +; v4t++ ; @todo parser error +; v4t+=20 ; @todo parser error +; v4t+=2000 ; @todo parser error +; v5t++ ; @todo parser error +; v5t+=20 ; @todo parser error +; v5t+=2000 ; @todo parser error A++ X-- A+=1 X-=2 - border++ - zp1_1++ - zpf1++ ;[AX]++ ;[AX .byte]++ ;[AX .word]++ @@ -42,25 +70,7 @@ start: 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() return 44 - -sub foobar () -> () { - - return - %breakpoint yep - return -} - -label2: - A++ - %noreturn - }