diff --git a/il65/exprparse.py b/il65/exprparse.py index 4f6201a41..ef78f7f9a 100644 --- a/il65/exprparse.py +++ b/il65/exprparse.py @@ -7,6 +7,7 @@ License: GNU GPL 3.0, see LICENSE """ import ast +import attr from typing import Union, Optional, List, Tuple, Any from .symbols import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE, SourceRef, SymbolTable, SymbolError, PrimitiveType @@ -52,7 +53,7 @@ class SourceLine: if c == '$' and self.text[i + 1] in "0123456789abcdefABCDEF": text += "0x" continue - if c == '#': + if c == '&': if i > 0: text += " " text += "__ptr@" @@ -172,11 +173,7 @@ class EvaluatingTransformer(ast.NodeTransformer): self.ppcontext = ppcontext def error(self, message: str, column: int=0) -> ParseError: - if column: - ref = self.src.sourceref.copy() - ref.column = column - else: - ref = self.src.sourceref + ref = attr.evolve(self.src.sourceref, column=column) return ParseError(message, self.src.text, ref) def evaluate(self, node: ast.Expression) -> PrimitiveType: @@ -267,7 +264,7 @@ def astnode_to_repr(node: ast.AST) -> str: return repr(node.s) if isinstance(node, ast.BinOp): if node.left.id == "__ptr" and isinstance(node.op, ast.MatMult): # type: ignore - return '#' + astnode_to_repr(node.right) + return '&' + astnode_to_repr(node.right) else: print("error", ast.dump(node)) raise TypeError("invalid arg ast node type", node) diff --git a/il65/lexer.py b/il65/lexer.py index 40683ccd3..23b4a5bf6 100644 --- a/il65/lexer.py +++ b/il65/lexer.py @@ -1,6 +1,14 @@ +""" +Programming Language for 6502/6510 microprocessors +This is the lexer of the IL65 code, that generates a stream of tokens for the parser. + +Written by Irmen de Jong (irmen@razorvine.net) +License: GNU GPL 3.0, see LICENSE +""" + import sys -from .symbols import SourceRef import ply.lex +from .symbols import SourceRef # token names diff --git a/il65/parse.py b/il65/parse.py index cbb4157db..cabbad0a6 100644 --- a/il65/parse.py +++ b/il65/parse.py @@ -10,6 +10,7 @@ import re import os import sys import shutil +import attr from collections import defaultdict from typing import Set, List, Tuple, Optional, Dict, Union, Generator from .exprparse import ParseError, parse_expr_as_int, parse_expr_as_number, parse_expr_as_primitive,\ @@ -235,8 +236,7 @@ class Parser: def _parse_2(self) -> None: # parsing pass 2 (not done during preprocessing!) self.cur_block = None - self.sourceref.line = -1 - self.sourceref.column = 0 + self.sourceref = SourceRef(self.sourceref.file, -1) def imm_string_to_var(stmt: AssignmentStmt, containing_block: Block) -> None: if stmt.right.name or not isinstance(stmt.right, StringValue): @@ -257,13 +257,13 @@ class Parser: def desugar_immediate_strings(stmt: AstNode, containing_block: Block) -> None: if isinstance(stmt, CallStmt): for s in stmt.desugared_call_arguments: - self.sourceref = s.sourceref.copy() + self.sourceref = s.sourceref imm_string_to_var(s, containing_block) for s in stmt.desugared_output_assignments: - self.sourceref = s.sourceref.copy() + self.sourceref = s.sourceref imm_string_to_var(s, containing_block) if isinstance(stmt, AssignmentStmt): - self.sourceref = stmt.sourceref.copy() + self.sourceref = stmt.sourceref imm_string_to_var(stmt, containing_block) def desugar_immediate_floats(stmt: AstNode, containing_block: Block) -> None: @@ -312,11 +312,10 @@ class Parser: for block in self.result.blocks: self.cur_block = block - self.sourceref = block.sourceref.copy() - self.sourceref.column = 0 + self.sourceref = attr.evolve(block.sourceref, column=0) for _, sub, stmt in block.all_statements(): if isinstance(stmt, CallStmt): - self.sourceref = stmt.sourceref.copy() + self.sourceref = stmt.sourceref self.desugar_call_arguments_and_outputs(stmt) desugar_immediate_strings(stmt, self.cur_block) desugar_immediate_floats(stmt, self.cur_block) @@ -327,7 +326,7 @@ class Parser: for name, value in stmt.arguments or []: assert name is not None, "all call arguments should have a name or be matched on a named parameter" assignment = self.parse_assignment(name, value) - assignment.sourceref = stmt.sourceref.copy() + assignment.sourceref = stmt.sourceref if assignment.leftvalues[0].datatype != DataType.BYTE: if isinstance(assignment.right, IntegerValue) and assignment.right.constant: # a call that doesn't expect a BYTE argument but gets one, converted from a 1-byte string most likely @@ -360,15 +359,16 @@ class Parser: def next_line(self) -> str: self._cur_lineidx += 1 try: - self.sourceref.line, line = self.lines[self._cur_lineidx] - self.sourceref.column = 0 + lineno, line = self.lines[self._cur_lineidx] + self.sourceref = SourceRef(file=self.sourceref.file, line=lineno) return line except IndexError: return "" def prev_line(self) -> str: self._cur_lineidx -= 1 - self.sourceref.line, line = self.lines[self._cur_lineidx] + lineno, line = self.lines[self._cur_lineidx] + self.sourceref = SourceRef(file=self.sourceref.file, line=lineno) return line def peek_next_line(self) -> str: @@ -506,6 +506,8 @@ class Parser: _, filename = line.split(maxsplit=1) except ValueError: raise self.PError("invalid import statement") + if filename[0] in "'\"" and filename[-1] in "'\"": + filename = filename[1:-1] if not filename: raise self.PError("invalid filename") self._parse_import_file(filename) @@ -554,7 +556,7 @@ class Parser: raise self.PError("expected '~' (block)") block_args = line[1:].split() arg = "" - self.cur_block = Block("", self.sourceref.copy(), self.root_scope, self.result.preserve_registers) + self.cur_block = Block("", self.sourceref, self.root_scope, self.result.preserve_registers) is_zp_block = False while block_args: arg = block_args.pop(0) @@ -568,7 +570,7 @@ class Parser: raise self.PError("duplicate block name '{:s}', original definition at {}".format(arg, orig.sourceref)) self.cur_block = orig # zero page block occurrences are merged else: - self.cur_block = Block(arg, self.sourceref.copy(), self.root_scope, self.result.preserve_registers) + self.cur_block = Block(arg, self.sourceref, self.root_scope, self.result.preserve_registers) try: self.root_scope.define_scope(self.cur_block.symbols, self.cur_block.sourceref) except SymbolError as x: @@ -1040,10 +1042,15 @@ class Parser: return InplaceIncrStmt(l_value, r_value.negative(), self.sourceref) return AugmentedAssignmentStmt(l_value, operator, r_value, self.sourceref) - def parse_return(self, line: str) -> ReturnStmt: + def parse_return(self, line: str) -> Union[ReturnStmt, CallStmt]: parts = line.split(maxsplit=1) if parts[0] != "return": raise self.PError("invalid statement, return expected") + if '(' in line and line[-1] == ')': + # assume it's a function call that follows the 'return'. Turn it into a goto. + parts[0] = "goto" + line = " ".join(parts) + return self.parse_statement(line) # type: ignore a = x = y = None values = [] # type: List[str] if len(parts) > 1: @@ -1057,7 +1064,7 @@ class Parser: if len(values) > 2: y = self.parse_expression(values[2]) if values[2] else None if len(values) > 3: - raise self.PError("too many returnvalues") + raise self.PError("too many returnvalues (>3)") return ReturnStmt(self.sourceref, a, x, y) def parse_asm(self) -> InlineAsm: @@ -1089,14 +1096,16 @@ class Parser: asmlines.append(line) def parse_asminclude(self, line: str) -> InlineAsm: + line = line.replace(",", " ") aline = line.split() if len(aline) < 2: raise self.PError("invalid asminclude or asmbinary directive") filename = aline[1] + if filename[0] not in "'\"" or filename[-1] not in "'\"": + raise self.PError("invalid filename, should use quotes") + filename = filename[1:-1] if not filename: raise self.PError("invalid filename") - if filename[0] in "'\"" or filename[-1] in "'\"": - raise self.PError("invalid filename, should not use quotes") filename_in_sourcedir = os.path.join(os.path.split(self.sourceref.file)[0], filename) filename_in_output_location = os.path.join(self.outputdir, filename) if not os.path.isfile(filename_in_sourcedir): @@ -1131,7 +1140,7 @@ class Parser: text = text.strip() if not text: raise self.PError("value expected") - if text[0] == '#': + if text[0] == '&': if is_indirect: raise self.PError("using the address-of something in an indirect value makes no sense") # take the pointer (memory address) from the thing that follows this diff --git a/il65/plyacc.py b/il65/plyparser.py similarity index 50% rename from il65/plyacc.py rename to il65/plyparser.py index bc6baae3b..378c7980a 100644 --- a/il65/plyacc.py +++ b/il65/plyparser.py @@ -1,26 +1,34 @@ +""" +Programming Language for 6502/6510 microprocessors +This is the parser of the IL65 code, that generates a parse tree. + +Written by Irmen de Jong (irmen@razorvine.net) +License: GNU GPL 3.0, see LICENSE +""" + +from typing import List, Any from ply.yacc import yacc from .symbols import SourceRef, AstNode from .lexer import tokens, lexer, find_tok_column # get the lexer tokens. required. - start = "start" class Module(AstNode): - def __init__(self, nodes, sourceref): + def __init__(self, nodes: List[AstNode], sourceref: SourceRef) -> None: super().__init__(sourceref) self.nodes = nodes or [] class Directive(AstNode): - def __init__(self, name, args, sourceref): + def __init__(self, name: str, args, sourceref: SourceRef) -> None: super().__init__(sourceref) self.name = name self.args = args or [] class Block(AstNode): - def __init__(self, name, address, scope, sourceref): + def __init__(self, name: str, address, scope, sourceref: SourceRef) -> None: super().__init__(sourceref) self.name = name self.address = address @@ -28,58 +36,72 @@ class Block(AstNode): class Scope(AstNode): - def __init__(self, nodes, sourceref): + def __init__(self, nodes: List[AstNode], sourceref: SourceRef) -> None: super().__init__(sourceref) self.nodes = nodes class Label(AstNode): - def __init__(self, name, sourceref): + def __init__(self, name: str, sourceref: SourceRef) -> None: super().__init__(sourceref) self.name = name class Register(AstNode): - def __init__(self, name, sourceref): + def __init__(self, name: str, sourceref: SourceRef) -> None: super().__init__(sourceref) self.name = name class PreserveRegs(AstNode): - def __init__(self, registers, sourceref): + def __init__(self, registers, sourceref: SourceRef) -> None: super().__init__(sourceref) self.registers = registers class Assignment(AstNode): - def __init__(self, lhs, operator, rhs, sourceref): + def __init__(self, lhs, rhs, sourceref: SourceRef) -> None: super().__init__(sourceref) self.lhs = lhs - self.operator = operator self.rhs = rhs +class AugAssignment(Assignment): + def __init__(self, lhs, operator: str, rhs, sourceref: SourceRef) -> None: + super().__init__(lhs, rhs, sourceref) + self.operator = operator + + class SubCall(AstNode): - def __init__(self, target, arguments, sourceref): + def __init__(self, target, preserveregs, arguments, sourceref: SourceRef) -> None: super().__init__(sourceref) self.target = target + self.preserveregs = preserveregs self.arguments = arguments class Return(AstNode): - def __init__(self, value, sourceref): + def __init__(self, valueA, valueX, valueY, sourceref: SourceRef) -> None: super().__init__(sourceref) - self.value = value + self.valueA = valueA + self.valueX = valueX + self.valueY = valueY + + +class TargetRegisters(AstNode): + def __init__(self, registers, sourceref: SourceRef) -> None: + super().__init__(sourceref) + self.registers = registers class InlineAssembly(AstNode): - def __init__(self, assembly, sourceref): + def __init__(self, assembly: str, sourceref: SourceRef) -> None: super().__init__(sourceref) self.assembly = assembly class VarDef(AstNode): - def __init__(self, name, vartype, datatype, value, sourceref): + def __init__(self, name: str, vartype, datatype, value, sourceref: SourceRef) -> None: super().__init__(sourceref) self.name = name self.vartype = vartype @@ -88,14 +110,14 @@ class VarDef(AstNode): class Datatype(AstNode): - def __init__(self, name, dimension, sourceref): + def __init__(self, name: str, dimension, sourceref: SourceRef) -> None: super().__init__(sourceref) self.name = name self.dimension = dimension class Subroutine(AstNode): - def __init__(self, name, paramspec, resultspec, code, sourceref): + def __init__(self, name: str, paramspec, resultspec, code, sourceref: SourceRef) -> None: super().__init__(sourceref) self.name = name self.paramspec = paramspec @@ -104,7 +126,7 @@ class Subroutine(AstNode): class Goto(AstNode): - def __init__(self, target, ifstmt, condition, sourceref): + def __init__(self, target, ifstmt, condition, sourceref: SourceRef) -> None: super().__init__(sourceref) self.target = target self.ifstmt = ifstmt @@ -112,7 +134,7 @@ class Goto(AstNode): class Dereference(AstNode): - def __init__(self, location, datatype, sourceref): + def __init__(self, location, datatype, sourceref: SourceRef) -> None: super().__init__(sourceref) self.location = location self.datatype = datatype @@ -126,43 +148,41 @@ class CallTarget(AstNode): class CallArgument(AstNode): - def __init__(self, name, value, sourceref): + def __init__(self, name: str, value, sourceref: SourceRef) -> None: super().__init__(sourceref) self.name = name self.value = value class UnaryOp(AstNode): - def __init__(self, operator, operand, sourceref): + def __init__(self, operator, operand, sourceref: SourceRef) -> None: super().__init__(sourceref) self.operator = operator self.operand = operand -class BinaryOp(AstNode): - def __init__(self, operator, left, right, sourceref): +class Expression(AstNode): + def __init__(self, lhs, operator, rhs, sourceref: SourceRef) -> None: super().__init__(sourceref) + self.lhs = lhs self.operator = operator - self.left = left - self.right = right - - -class Integer(AstNode): - def __init__(self, value, sourceref): - super().__init__(sourceref) - self.value = value + self.rhs = rhs def p_start(p): - """start : empty - | module_elements""" + """ + start : empty + | module_elements + """ if p[1]: p[0] = Module(p[1], _token_sref(p, 1)) def p_module(p): - """module_elements : module_elt - | module_elements module_elt""" + """ + module_elements : module_elt + | module_elements module_elt + """ if len(p) == 2: p[0] = [p[1]] else: @@ -170,15 +190,18 @@ def p_module(p): def p_module_elt(p): - """module_elt : ENDL - | directive - | block """ + """ + module_elt : ENDL + | directive + | block + """ p[0] = p[1] def p_directive(p): - """directive : DIRECTIVE ENDL - | DIRECTIVE directive_args ENDL + """ + directive : DIRECTIVE ENDL + | DIRECTIVE directive_args ENDL """ if len(p) == 2: p[0] = Directive(p[1], None, _token_sref(p, 1)) @@ -187,8 +210,9 @@ def p_directive(p): def p_directive_args(p): - """directive_args : directive_arg - | directive_args ',' directive_arg + """ + directive_args : directive_arg + | directive_args ',' directive_arg """ if len(p) == 2: p[0] = [p[1]] @@ -197,48 +221,63 @@ def p_directive_args(p): def p_directive_arg(p): - """directive_arg : NAME - | INTEGER - | STRING + """ + directive_arg : NAME + | INTEGER + | STRING """ p[0] = p[1] def p_block_name_addr(p): - """block : BITINVERT NAME INTEGER endl_opt scope""" + """ + block : BITINVERT NAME INTEGER endl_opt scope + """ p[0] = Block(p[2], p[3], p[5], _token_sref(p, 1)) def p_block_name(p): - """block : BITINVERT NAME endl_opt scope""" + """ + block : BITINVERT NAME endl_opt scope + """ p[0] = Block(p[2], None, p[4], _token_sref(p, 1)) def p_block(p): - """block : BITINVERT endl_opt scope""" + """ + block : BITINVERT endl_opt scope + """ p[0] = Block(None, None, p[3], _token_sref(p, 1)) def p_endl_opt(p): - """endl_opt : empty - | ENDL""" - p[0] = p[1] + """ + endl_opt : empty + | ENDL + """ + pass def p_scope(p): - """scope : '{' scope_elements_opt '}'""" + """ + scope : '{' scope_elements_opt '}' + """ p[0] = Scope(p[2], _token_sref(p, 1)) def p_scope_elements_opt(p): - """scope_elements_opt : empty - | scope_elements""" + """ + scope_elements_opt : empty + | scope_elements + """ p[0] = p[1] def p_scope_elements(p): - """scope_elements : scope_element - | scope_elements scope_element""" + """ + scope_elements : scope_element + | scope_elements scope_element + """ if len(p) == 2: p[0] = [p[1]] else: @@ -246,49 +285,63 @@ def p_scope_elements(p): def p_scope_element(p): - """scope_element : ENDL - | label - | directive - | vardef - | subroutine - | inlineasm - | statement""" + """ + scope_element : ENDL + | label + | directive + | vardef + | subroutine + | inlineasm + | statement + """ p[0] = p[1] def p_label(p): - """label : LABEL""" + """ + label : LABEL + """ p[0] = Label(p[1], _token_sref(p, 1)) def p_inlineasm(p): - """inlineasm : INLINEASM ENDL""" + """ + inlineasm : INLINEASM ENDL + """ p[0] = InlineAssembly(p[1], _token_sref(p, 1)) def p_vardef(p): - """vardef : VARTYPE type_opt NAME ENDL""" + """ + vardef : VARTYPE type_opt NAME ENDL + """ p[0] = VarDef(p[3], p[1], p[2], None, _token_sref(p, 1)) def p_vardef_value(p): - """vardef : VARTYPE type_opt NAME IS expression""" + """ + vardef : VARTYPE type_opt NAME IS expression + """ p[0] = VarDef(p[3], p[1], p[2], p[5], _token_sref(p, 1)) def p_type_opt(p): - """type_opt : DATATYPE '(' dimensions ')' - | DATATYPE - | empty""" - if len(p) == 4: + """ + type_opt : DATATYPE '(' dimensions ')' + | DATATYPE + | empty + """ + if len(p) == 5: p[0] = Datatype(p[1], p[3], _token_sref(p, 1)) elif len(p) == 2: p[0] = Datatype(p[1], None, _token_sref(p, 1)) def p_dimensions(p): - """dimensions : INTEGER - | dimensions ',' INTEGER""" + """ + dimensions : INTEGER + | dimensions ',' INTEGER + """ if len(p) == 2: p[0] = [p[1]] else: @@ -305,28 +358,36 @@ def p_literal_value(p): def p_subroutine(p): - """subroutine : SUB NAME '(' sub_param_spec ')' RARROW '(' sub_result_spec ')' subroutine_body ENDL""" - p[0] = Subroutine(p[2], p[4], p[8], p[10], _token_sref(p, 1)) + """ + subroutine : SUB NAME '(' sub_param_spec ')' RARROW '(' sub_result_spec ')' subroutine_body ENDL + """ + p[0] = Subroutine(p[1], p[3], p[7], p[9], _token_sref(p, 1)) def p_sub_param_spec(p): - """sub_param_spec : empty - | sub_param_list""" + """ + sub_param_spec : empty + | sub_param_list + """ p[0] = p[1] def p_sub_param_list(p): - """sub_param_list : sub_param - | sub_param_list ',' sub_param""" + """ + sub_param_list : sub_param + | sub_param_list ',' sub_param + """ if len(p) == 2: p[0] = [p[1]] else: - p[0] = p[1] + [p[2]] + p[0] = p[1] + [p[3]] def p_sub_param(p): - """sub_param : LABEL REGISTER - | REGISTER""" + """ + sub_param : LABEL REGISTER + | REGISTER + """ if len(p) == 3: p[0] = (p[1], p[2]) elif len(p) == 2: @@ -334,17 +395,22 @@ def p_sub_param(p): def p_sub_result_spec(p): - """sub_result_spec : empty - | '?' - | sub_result_list""" + """ + sub_result_spec : empty + | '?' + | sub_result_list + """ if p[1] == '?': p[0] = ['A', 'X', 'Y'] # '?' means: all registers clobbered - p[0] = p[1] + else: + p[0] = p[1] def p_sub_result_list(p): - """sub_result_list : sub_result_reg - | sub_result_list ',' sub_result_reg""" + """ + sub_result_list : sub_result_reg + | sub_result_list ',' sub_result_reg + """ if len(p) == 2: p[0] = [p[1]] else: @@ -352,14 +418,18 @@ def p_sub_result_list(p): def p_sub_result_reg(p): - """sub_result_reg : REGISTER - | CLOBBEREDREGISTER""" + """ + sub_result_reg : REGISTER + | CLOBBEREDREGISTER + """ p[0] = p[1] def p_subroutine_body(p): - """subroutine_body : scope - | IS INTEGER""" + """ + subroutine_body : scope + | IS INTEGER + """ if len(p) == 2: p[0] = p[1] else: @@ -367,47 +437,61 @@ def p_subroutine_body(p): def p_statement(p): - """statement : assignment ENDL - | subroutine_call ENDL - | goto ENDL - | conditional_goto ENDL - | incrdecr ENDL - | return ENDL + """ + statement : assignment ENDL + | aug_assignment ENDL + | subroutine_call ENDL + | goto ENDL + | conditional_goto ENDL + | incrdecr ENDL + | return ENDL """ p[0] = p[1] def p_incrdecr(p): - """incrdecr : assignment_target INCR - | assignment_target DECR""" + """ + incrdecr : assignment_target INCR + | assignment_target DECR + """ p[0] = UnaryOp(p[2], p[1], _token_sref(p, 1)) def p_call_subroutine(p): - """subroutine_call : calltarget preserveregs_opt '(' call_arguments_opt ')'""" - p[0] = SubCall(p[1], p[3], _token_sref(p, 1)) + """ + subroutine_call : calltarget preserveregs_opt '(' call_arguments_opt ')' + """ + p[0] = SubCall(p[1], p[2], p[4], _token_sref(p, 1)) def p_preserveregs_opt(p): - """preserveregs_opt : empty - | preserveregs""" + """ + preserveregs_opt : empty + | preserveregs + """ p[0] = p[1] def p_preserveregs(p): - """preserveregs : PRESERVEREGS""" + """ + preserveregs : PRESERVEREGS + """ p[0] = PreserveRegs(p[1], _token_sref(p, 1)) def p_call_arguments_opt(p): - """call_arguments_opt : empty - | call_arguments""" + """ + call_arguments_opt : empty + | call_arguments + """ p[0] = p[1] def p_call_arguments(p): - """call_arguments : call_argument - | call_arguments ',' call_argument""" + """ + call_arguments : call_argument + | call_arguments ',' call_argument + """ if len(p) == 2: p[0] = [p[1]] else: @@ -415,49 +499,69 @@ def p_call_arguments(p): def p_call_argument(p): - """call_argument : expression - | register IS expression - | NAME IS expression""" + """ + call_argument : expression + | register IS expression + | NAME IS expression + """ if len(p) == 2: p[0] = CallArgument(None, p[1], _token_sref(p, 1)) - elif len(p) == 3: + elif len(p) == 4: p[0] = CallArgument(p[1], p[3], _token_sref(p, 1)) def p_return(p): - """return : RETURN - | RETURN expression""" + """ + return : RETURN + | RETURN expression + | RETURN expression ',' expression + | RETURN expression ',' expression ',' expression + """ if len(p) == 2: - p[0] = Return(None, _token_sref(p, 1)) - elif len(p) == 4: - p[0] = Return(p[3], _token_sref(p, 1)) + p[0] = Return(None, None, None, _token_sref(p, 1)) + elif len(p) == 3: + p[0] = Return(p[2], None, None, _token_sref(p, 1)) + elif len(p) == 5: + p[0] = Return(p[2], p[4], None, _token_sref(p, 1)) + elif len(p) == 7: + p[0] = Return(p[2], p[4], p[6], _token_sref(p, 1)) def p_register(p): - """register : REGISTER""" + """ + register : REGISTER + """ p[0] = Register(p[1], _token_sref(p, 1)) def p_goto(p): - """goto : GOTO calltarget""" + """ + goto : GOTO calltarget + """ p[0] = Goto(p[2], None, None, _token_sref(p, 1)) def p_conditional_goto_plain(p): - """conditional_goto : IF GOTO calltarget""" + """ + conditional_goto : IF GOTO calltarget + """ p[0] = Goto(p[3], p[1], None, _token_sref(p, 1)) def p_conditional_goto_expr(p): - """conditional_goto : IF expression GOTO calltarget""" + """ + conditional_goto : IF expression GOTO calltarget + """ p[0] = Goto(p[4], p[1], p[2], _token_sref(p, 1)) def p_calltarget(p): - """calltarget : symbolname - | INTEGER - | BITAND symbolname - | dereference""" + """ + calltarget : symbolname + | INTEGER + | BITAND symbolname + | dereference + """ if len(p) == 2: p[0] = CallTarget(p[1], False, _token_sref(p, 1)) elif len(p) == 3: @@ -465,32 +569,42 @@ def p_calltarget(p): def p_dereference(p): - """dereference : '[' dereference_operand ']' """ + """ + dereference : '[' dereference_operand ']' + """ p[0] = Dereference(p[2][0], p[2][1], _token_sref(p, 1)) def p_dereference_operand(p): - """dereference_operand : symbolname type_opt - | REGISTER type_opt - | INTEGER type_opt""" + """ + dereference_operand : symbolname type_opt + | REGISTER type_opt + | INTEGER type_opt + """ p[0] = (p[1], p[2]) def p_symbolname(p): - """symbolname : NAME - | DOTTEDNAME""" + """ + symbolname : NAME + | DOTTEDNAME + """ p[0] = p[1] def p_assignment(p): - """assignment : assignment_lhs assignment_operator expression""" - p[0] = Assignment(p[1], p[2], p[3], _token_sref(p, 1)) + """ + assignment : assignment_target IS expression + | assignment_target IS assignment + """ + p[0] = Assignment(p[1], p[3], _token_sref(p, 1)) -def p_assignment_operator(p): - """assignment_operator : IS - | AUGASSIGN""" - p[0] = p[1] +def p_aug_assignment(p): + """ + aug_assignment : assignment_target AUGASSIGN expression + """ + p[0] = AugAssignment(p[1], p[2], p[3], _token_sref(p, 1)) precedence = ( @@ -503,69 +617,87 @@ precedence = ( def p_expression(p): - """expression : expression '+' expression - | expression '-' expression - | expression '*' expression - | expression '/' expression - | expression LT expression - | expression GT expression - | expression LE expression - | expression GE expression - | expression EQUALS expression - | expression NOTEQUALS expression""" - pass + """ + expression : expression '+' expression + | expression '-' expression + | expression '*' expression + | expression '/' expression + | expression LT expression + | expression GT expression + | expression LE expression + | expression GE expression + | expression EQUALS expression + | expression NOTEQUALS expression + """ + p[0] = Expression(p[1], p[2], p[3], _token_sref(p, 1)) def p_expression_uminus(p): - """expression : '-' expression %prec UNARY_MINUS""" - pass + """ + expression : '-' expression %prec UNARY_MINUS + """ + p[0] = UnaryOp(p[1], p[2], _token_sref(p, 1)) def p_expression_addressof(p): - """expression : BITAND symbolname %prec UNARY_ADDRESSOF""" - pass + """ + expression : BITAND symbolname %prec UNARY_ADDRESSOF + """ + p[0] = UnaryOp(p[1], p[2], _token_sref(p, 1)) def p_unary_expression_bitinvert(p): - """expression : BITINVERT expression""" - pass + """ + expression : BITINVERT expression + """ + p[0] = UnaryOp(p[1], p[2], _token_sref(p, 1)) def p_expression_group(p): - """expression : '(' expression ')'""" + """ + expression : '(' expression ')' + """ p[0] = p[2] def p_expression_ass_rhs(p): - """expression : expression_value""" + """expression : expression_value""" p[0] = p[1] def p_expression_value(p): - """expression_value : literal_value - | symbolname - | register - | subroutine_call - | dereference""" + """ + expression_value : literal_value + | symbolname + | register + | subroutine_call + | dereference + """ p[0] = p[1] -def p_assignment_lhs(p): - """assignment_lhs : assignment_target - | assignment_lhs ',' assignment_target""" - if len(p) == 2: - p[0] = [p[1]] - else: - p[0] = p[1] + [p[2]] - - def p_assignment_target(p): - """assignment_target : register - | symbolname - | dereference""" + """ + assignment_target : target_registers + | symbolname + | dereference + """ p[0] = p[1] +def p_target_registers(p): + """ + target_registers : register + | target_registers ',' register + """ + if len(p) == 2: + p[0] = TargetRegisters([p[1]], _token_sref(p, 1)) + else: + p[1].add_register(p[3]) + p[0] = p[1] + + + def p_empty(p): """empty :""" pass diff --git a/il65/symbols.py b/il65/symbols.py index a1fc5e1b5..16fa146f9 100644 --- a/il65/symbols.py +++ b/il65/symbols.py @@ -12,6 +12,7 @@ import enum import builtins from functools import total_ordering from typing import Optional, Set, Union, Tuple, Dict, Iterable, Sequence, Any, List, Generator +import attr PrimitiveType = Union[int, float, str] @@ -74,13 +75,11 @@ class SymbolError(Exception): _identifier_seq_nr = 0 +@attr.s(slots=True, frozen=True) class SourceRef: - __slots__ = ("file", "line", "column") - - def __init__(self, file: str, line: int, column: int=0) -> None: - self.file = file - self.line = line - self.column = column + file = attr.ib(type=str) + line = attr.ib(type=int) + column = attr.ib(type=int, default=0) def __str__(self) -> str: if self.column: @@ -89,15 +88,12 @@ class SourceRef: return "{:s}:{:d}".format(self.file, self.line) return self.file - def copy(self) -> 'SourceRef': - return SourceRef(self.file, self.line, self.column) - class SymbolDefinition: def __init__(self, blockname: str, name: str, sourceref: SourceRef, allocate: bool) -> None: self.blockname = blockname self.name = name - self.sourceref = sourceref.copy() + self.sourceref = sourceref self.allocate = allocate # set to false if the variable is memory mapped (or a constant) instead of allocated global _identifier_seq_nr self.seq_nr = _identifier_seq_nr @@ -695,9 +691,10 @@ ascii_to_petscii_trans = str.maketrans({ class AstNode: - def __init__(self, sourceref: SourceRef, children: List['AstNode']=None) -> None: - self.sourceref = sourceref.copy() - self.children = children or [] + __slots__ = ["sourceref"] + + def __init__(self, sourceref: SourceRef) -> None: + self.sourceref = sourceref @property def lineref(self) -> str: diff --git a/testsource/calls.ill b/testsource/calls.ill index 070b88dd1..89ed6bf8a 100644 --- a/testsource/calls.ill +++ b/testsource/calls.ill @@ -1,8 +1,5 @@ ; call tests -%output foobar - - ~ foo { diff --git a/testsource/dtypes.ill b/testsource/dtypes.ill index e34e37e46..9f26f52e1 100644 --- a/testsource/dtypes.ill +++ b/testsource/dtypes.ill @@ -64,9 +64,8 @@ var .float initfloat4 = 1.70141183e+38 var .float initfloat5 = -1.70141183e+38 var .float initfloat6 = 1.234 - var .float (44) zzzz = 333 - var .wordarray ( 10 ) uninit_wordarray + var .wordarray( 10 ) uninit_wordarray var .wordarray(10) init_wordarray = $1234 var .wordarray(10) init_wordarrayb = true var .array( 10) uninit_bytearray @@ -113,12 +112,12 @@ const .pstext ctext6 = "constant-pstext" ; taking the address of various things: - var .word vmemaddr1 = & membyte1 - var .word vmemaddr2 = & memword1 - var .word vmemaddr3 = & memfloat - var .word vmemaddr4 = & membytes - var .word vmemaddr5 = & memwords - var .word vmemaddr6 = & memmatrix + var .word vmemaddr1 = &membyte1 + var .word vmemaddr2 = &memword1 + var .word vmemaddr3 = &memfloat + var .word vmemaddr4 = &membytes + var .word vmemaddr5 = &memwords + var .word vmemaddr6 = &memmatrix var .word vmemaddr8 = 100*sin(cbyte1) var .word vmemaddr9 = cword2+$5432 var .word vmemaddr10 = cfloat2b @@ -126,10 +125,10 @@ ; taking the address of things from the ZP will work even when it is a var ; because zp-vars get assigned a specific address (from a pool). Also, it's a byte. - var .word initword0a = & ZP.zpmem1 - var .word initword0 = & ZP.zpvar1 - var initbytea0 = & ZP.zpmem1 - var .word initworda1 = & ZP.zpvar1 + var .word initword0a = &ZP.zpmem1 + var .word initword0 = &ZP.zpvar1 + var initbytea0 = &ZP.zpmem1 + var .word initworda1 = &ZP.zpvar1 ; (constant) expressions @@ -181,7 +180,6 @@ start: XY = uninitbyte1 XY = "text-immediate" AY = "text-immediate" - AX = &derp ; AX = &"text-immediate" ; equivalent to simply assigning the string directly ; AX = & "text-immediate" ; equivalent to simply assigning the string directly AX = ctext3 diff --git a/testsource/source1.ill b/testsource/source1.ill index 272897315..c24b4bf5c 100644 --- a/testsource/source1.ill +++ b/testsource/source1.ill @@ -33,7 +33,7 @@ start: ;foo Y = X X = 66 screen = 0 - screen = border = cursor = X = Y = A = X = Y = A = border = cursor = border = cursor = 66 ; multi-assign! @todo ply parse + screen = border = cursor = X = Y = A = X = Y = A = border = cursor = border = cursor = 66 ; multi-assign! border = false border = true border = 0 @@ -104,6 +104,7 @@ sub customsub (Y)->() { somelabel1222: + customsub(2) return diff --git a/testsource/source2.ill b/testsource/source2.ill index 483917dae..9a418130d 100644 --- a/testsource/source2.ill +++ b/testsource/source2.ill @@ -15,11 +15,11 @@ start: ;return ;included_assembly - %asminclude " included.sourcelynx walla derp ", test_include - %asminclude " included.sourcelynx walla derp ", "test_include" + ;%asminclude " included.sourcelynx walla derp ", test_include + ;%asminclude " included.sourcelynx walla derp ", "test_include" ;included_binary - %asmbinary " included.binary 234 " - %asmbinary " included.binary", $40 + ;%asmbinary " included.binary 234 " + ;%asmbinary " included.binary", $40 %asmbinary "included.binary", $40, $200 } diff --git a/testsource/source3.ill b/testsource/source3.ill index 65dc41317..3bb552ef3 100644 --- a/testsource/source3.ill +++ b/testsource/source3.ill @@ -67,7 +67,7 @@ start: ~ global2 { make_screen_black: - c64.EXTCOL = c64.BGCOL0 = 0 ; @todo ply parse multiassign + c64.EXTCOL = c64.BGCOL0 = 0 c64.COLOR = 3 Y = true return diff --git a/testsource/source4.ill b/testsource/source4.ill index 6d8caf8d6..571ad6ae1 100644 --- a/testsource/source4.ill +++ b/testsource/source4.ill @@ -53,7 +53,7 @@ start2: ~ global2 { make_screen_black: - c64.EXTCOL = c64.BGCOL0 = 0 ; @todo ply parse multi assign + c64.EXTCOL = c64.BGCOL0 = 0 c64.COLOR = 3 return diff --git a/todo.ill b/todo.ill index 76fa63ed6..c6da268b1 100644 --- a/todo.ill +++ b/todo.ill @@ -14,6 +14,7 @@ start: 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 @@ -27,7 +28,7 @@ rndloop: tya sta $0500,x } - ;[wvar1] = 81 ; @todo implement pointers like this + [wvar1] = 81 ; @todo implement pointers like this goto rndloop