From 4a9d3200cdef6a32a41cbdf25e802b6509c5e163 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 25 Dec 2017 22:22:19 +0100 Subject: [PATCH] fixed arg parsing --- il65/astparse.py | 41 +++++++++++++++++++++- il65/parse.py | 83 +++++++++++++++++++++----------------------- il65/symbols.py | 11 ++++-- lib/c64lib.ill | 4 +-- reference.md | 6 +++- testsource/calls.ill | 23 ++++++++++++ testsource/input.ill | 10 ++++-- 7 files changed, 125 insertions(+), 53 deletions(-) diff --git a/il65/astparse.py b/il65/astparse.py index 8928d6b77..8a811647d 100644 --- a/il65/astparse.py +++ b/il65/astparse.py @@ -7,7 +7,7 @@ License: GNU GPL 3.0, see LICENSE """ import ast -from typing import Union, Optional +from typing import Union, Optional, List, Tuple, Any from .symbols import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE, SourceRef, SymbolTable, SymbolError, PrimitiveType @@ -61,6 +61,45 @@ class SourceLine: return text +def parse_arguments(text: str, sourceref: SourceRef) -> List[Tuple[str, PrimitiveType]]: + src = SourceLine(text, sourceref) + text = src.preprocess() + try: + nodes = ast.parse("__func({:s})".format(text), sourceref.file, "eval") + except SyntaxError as x: + raise src.to_error(str(x)) + + def astnode_to_repr(node: ast.AST) -> str: + if isinstance(node, ast.Name): + return node.id + if isinstance(node, ast.Num): + return repr(node.n) + if isinstance(node, ast.Str): + return repr(node.s) + if isinstance(node, ast.BinOp): + if node.left.id == "__ptr" and isinstance(node.op, ast.MatMult): + return '#' + astnode_to_repr(node.right) + else: + print("error", ast.dump(node)) + raise TypeError("invalid arg ast node type", node) + if isinstance(node, ast.Attribute): + return astnode_to_repr(node.value) + "." + node.attr + print("error", ast.dump(node)) + raise TypeError("invalid arg ast node type", node) + + args = [] # type: List[Tuple[str, Any]] + if isinstance(nodes, ast.Expression): + for arg in nodes.body.args: + reprvalue = astnode_to_repr(arg) + args.append((None, reprvalue)) + for kwarg in nodes.body.keywords: + reprvalue = astnode_to_repr(kwarg.value) + args.append((kwarg.arg, reprvalue)) + return args + else: + raise TypeError("ast.Expression expected") + + def parse_expr_as_int(text: str, context: Optional[SymbolTable], ppcontext: Optional[SymbolTable], sourceref: SourceRef, *, minimum: int=0, maximum: int=0xffff) -> int: result = parse_expr_as_primitive(text, context, ppcontext, sourceref, minimum=minimum, maximum=maximum) diff --git a/il65/parse.py b/il65/parse.py index f7e506b87..5e48003e5 100644 --- a/il65/parse.py +++ b/il65/parse.py @@ -12,9 +12,9 @@ import os import shutil import enum from collections import defaultdict -from typing import Set, List, Tuple, Optional, Any, Dict, Union, Set +from typing import Set, List, Tuple, Optional, Any, Dict, Union from .astparse import ParseError, parse_expr_as_int, parse_expr_as_number, parse_expr_as_primitive,\ - parse_expr_as_string + parse_expr_as_string, parse_arguments from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \ zeropage, check_value_in_range, char_to_bytevalue, \ PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \ @@ -62,6 +62,8 @@ class ParseResult: return label def lookup(self, dottedname: str) -> Tuple[Optional['ParseResult.Block'], Optional[Union[SymbolDefinition, SymbolTable]]]: + # Searches a name in the current block or globally, if the name is scoped (=contains a '.'). + # Does NOT utilize a symbol table from a preprocessing parse phase, only looks in the current. try: scope, result = self.symbols.lookup(dottedname) return scope.owning_block, result @@ -1044,15 +1046,7 @@ class Parser: argumentstr = argumentstr.strip() if argumentstr else "" arguments = None if argumentstr: - arguments = [] - for part in argumentstr.split(','): - pname, sep, pvalue = part.partition('=') - pname = pname.strip() - pvalue = pvalue.strip() - if sep: - arguments.append((pname, pvalue)) - else: - arguments.append((None, pname)) + arguments = parse_arguments(argumentstr, self.sourceref) target = None # type: ParseResult.Value if targetstr[0] == '[' and targetstr[-1] == ']': # indirect call to address in register pair or memory location @@ -1069,7 +1063,7 @@ class Parser: raise self.PError("cannot call that type of indirect symbol") address = target.address if isinstance(target, ParseResult.MemMappedValue) else None try: - _, symbol = self.lookup(targetstr) + _, symbol = self.lookup_with_ppsymbols(targetstr) except ParseError: symbol = None # it's probably a number or a register then if isinstance(symbol, SubroutineDef): @@ -1080,11 +1074,10 @@ class Parser: args_with_pnames = [] for i, (argname, value) in enumerate(arguments or []): pname, preg = symbol.parameters[i] - if argname: - if argname != preg: - raise self.PError("parameter mismatch ({:s}, expected {:s})".format(argname, preg)) - else: - argname = preg + required_name = pname or preg + if argname and argname != required_name: + raise self.PError("parameter mismatch ('{:s}', expected '{:s}')".format(argname, required_name)) + argname = preg args_with_pnames.append((argname, value)) arguments = args_with_pnames self.result.sub_used_by(symbol, self.sourceref) @@ -1111,14 +1104,17 @@ class Parser: return int(text[1:], 2) return int(text) - def parse_assignment(self, line: str) -> ParseResult.AssignmentStmt: + def parse_assignment(self, line: str, parsed_rvalue: ParseResult.Value = None) -> ParseResult.AssignmentStmt: # parses assigning a value to one or more targets parts = line.split("=") - rhs = parts.pop() + if parsed_rvalue: + r_value = parsed_rvalue + else: + rhs = parts.pop() + r_value = self.parse_expression(rhs) l_values = [self.parse_expression(part) for part in parts] if any(isinstance(lv, ParseResult.IntegerValue) for lv in l_values): raise self.PError("can't have a constant as assignment target, perhaps you wanted indirection [...] instead?") - r_value = self.parse_expression(rhs) for lv in l_values: assignable, reason = lv.assignable_from(r_value) if not assignable: @@ -1162,22 +1158,21 @@ class Parser: if line.strip() == "}": return ParseResult.InlineAsm(lineno, asmlines) # asm can refer to other symbols as well, track subroutine usage - if line.startswith((" ", "\t")): - splits = line.split(maxsplit=2) - if len(splits) == 2: - for match in re.finditer(r"(?P[a-zA-Z_$][a-zA-Z0-9_\.]+)", splits[1]): - name = match.group("symbol") - if name[0] == '$': - continue - try: - if '.' not in name: - name = self.cur_block.symbols.parent.name + '.' + name - _, symbol = self.lookup(name) - except ParseError: - pass - else: - if isinstance(symbol, SubroutineDef): - self.result.sub_used_by(symbol, self.sourceref) + splits = line.split(maxsplit=1) + if len(splits) == 2: + for match in re.finditer(r"(?P[a-zA-Z_$][a-zA-Z0-9_\.]+)", splits[1]): + name = match.group("symbol") + if name[0] == '$': + continue + try: + if '.' not in name: + name = self.cur_block.symbols.parent.name + '.' + name + _, symbol = self.lookup_with_ppsymbols(name) + except ParseError: + pass + else: + if isinstance(symbol, SubroutineDef): + self.result.sub_used_by(symbol, self.sourceref) asmlines.append(line) def parse_asminclude(self, line: str) -> ParseResult.InlineAsm: @@ -1262,7 +1257,7 @@ class Parser: elif text == "false": return ParseResult.IntegerValue(0) elif self.is_identifier(text): - symblock, sym = self.lookup(text) + symblock, sym = self.lookup_with_ppsymbols(text) if isinstance(sym, (VariableDef, ConstantDef)): constant = isinstance(sym, ConstantDef) if self.cur_block is symblock: @@ -1340,17 +1335,17 @@ class Parser: return blockname.isidentifier() and name.isidentifier() return False - def lookup(self, dottedname: str) -> Tuple[ParseResult.Block, Union[SymbolDefinition, SymbolTable]]: + def lookup_with_ppsymbols(self, dottedname: str) -> Tuple[ParseResult.Block, Union[SymbolDefinition, SymbolTable]]: + # Tries to find a symbol, if it cannot be located, the symbol table from the preprocess parse phase is consulted as well symblock, sym = self.cur_block.lookup(dottedname) - if sym is None: - # symbol is not (yet) known in current block, see if the ppsymbols know about it + if sym is None and self.ppsymbols: + # symbol is not (yet) known, see if the symbols from the preprocess parse phase know about it if '.' not in dottedname: dottedname = self.cur_block.name + '.' + dottedname try: - if self.ppsymbols: - symtable, sym = self.ppsymbols.lookup(dottedname) - assert dottedname.startswith(symtable.name) - symblock = None # the block might not have been parsed yet, so just return this instead + symtable, sym = self.ppsymbols.lookup(dottedname) + assert dottedname.startswith(symtable.name) + symblock = None # the block might not have been parsed yet, so just return this instead except (LookupError, SymbolError) as x: raise self.PError(str(x)) return symblock, sym diff --git a/il65/symbols.py b/il65/symbols.py index 83bd97380..754169f02 100644 --- a/il65/symbols.py +++ b/il65/symbols.py @@ -179,9 +179,12 @@ class SubroutineDef(SymbolDefinition): for _, param in parameters: if param in REGISTER_BYTES: self.input_registers.add(param) + self.clobbered_registers.add(param) elif param in REGISTER_WORDS: self.input_registers.add(param[0]) self.input_registers.add(param[1]) + self.clobbered_registers.add(param[0]) + self.clobbered_registers.add(param[1]) else: raise SymbolError("invalid parameter spec: " + param) for register in returnvalues: @@ -254,6 +257,10 @@ class SymbolTable: return symbolname in self.symbols def lookup(self, dottedname: str, include_builtin_names: bool=False) -> Tuple['SymbolTable', Union[SymbolDefinition, 'SymbolTable']]: + # Tries to find the dottedname in the current symbol table (if it is not scoped), + # or globally if it is scoped (=contains a '.'). If required, math and builtin symbols + # such as 'sin' or 'max' are also resolved. + # Does NOT utilize a symbol table from a preprocessing parse phase, only looks in the current. nameparts = dottedname.split('.') if len(nameparts) == 1: try: @@ -265,7 +272,7 @@ class SymbolTable: elif nameparts[0] in BUILTIN_SYMBOLS: return self, getattr(builtins, nameparts[0]) raise SymbolError("undefined symbol '{:s}'".format(nameparts[0])) from None - # start from toplevel namespace: + # restart from global namespace: scope = self while scope.parent: scope = scope.parent @@ -278,7 +285,7 @@ class SymbolTable: if isinstance(scope, SymbolTable): return scope.lookup(nameparts[-1]) elif isinstance(scope, SubroutineDef): - return scope.sub_block.symbols.lookup(nameparts[-1]) + return scope.sub_block.symbols.lookup_with_ppsymbols(nameparts[-1]) else: raise SymbolError("invalid block name '{:s}' in dotted name".format(namepart)) diff --git a/lib/c64lib.ill b/lib/c64lib.ill index 4a596305c..03b6c1618 100644 --- a/lib/c64lib.ill +++ b/lib/c64lib.ill @@ -10,8 +10,8 @@ output raw memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP - memory COLOR = $0286 ; cursor color - memory CINV = $0314 ; IRQ vector + memory .byte COLOR = $0286 ; cursor color + memory .word CINV = $0314 ; IRQ vector ; ---- VIC-II registers ---- diff --git a/reference.md b/reference.md index 38f9fbb20..a4b1ae63c 100644 --- a/reference.md +++ b/reference.md @@ -251,7 +251,11 @@ The syntax is: ... statements ... } - proc_parameters = comma separated list of ":" pairs specifying the input parameters + proc_parameters = comma separated list of ":" pairs specifying the input parameters. + You can omit the parameter names as long as the arguments "line up". + (actually, the Python parameter passing rules apply, so you can also mix positional + and keyword arguments, as long as the keyword arguments come last) + proc_results = comma separated list of names specifying in which register(s) the output is returned. If the register name ends with a '?', that means the register doesn't contain a real return value but is clobbered in the process so the original value it had before calling the sub is no longer valid. diff --git a/testsource/calls.ill b/testsource/calls.ill index 39daa68df..d90e0e53f 100644 --- a/testsource/calls.ill +++ b/testsource/calls.ill @@ -17,11 +17,20 @@ sub sub1 () -> (X?) = $ffdd sub sub2 (A) -> (Y?) = $eecc + sub sub3 (XY) -> (Y?) = $ddaa + sub sub4 (string: XY, other : A) -> (Y?) = $dd22 bar goto sub1 goto sub2 (1 ) + goto sub3 (3) + goto sub3 (XY="hello") + goto sub3 ("hello, there") + goto sub4 (string="hello, there", other = 42) + goto sub4 ("hello", 42) + goto sub4 ("hello", other= 42) + goto sub4 (string="hello", other = 42) goto bar () goto [AX] goto [AX] () @@ -47,6 +56,13 @@ bar sub1!() sub2!(11) + sub3 !(3) + sub3! (XY="hello") + sub3! ("hello, there") + sub4! ("hello", 42) + sub4! ("hello", other=42) + sub4! (string="hello", other = 42) + sub4! (string="hello, there", other = 42) bar!() [XY] ! () [var1] !() @@ -65,6 +81,13 @@ bar sub1() sub2(11) + sub3 (3) + sub3 (XY="hello") + sub3 ("hello, there") + sub4 ("hello", 42) + sub4 ("hello", other= 42) + sub4 (string="hello", other = 42) + sub4 (string="hello, there", other = 42) bar () [AX]() [var1] ( ) diff --git a/testsource/input.ill b/testsource/input.ill index aa1374668..e302dad79 100644 --- a/testsource/input.ill +++ b/testsource/input.ill @@ -7,18 +7,22 @@ import "c64lib" var .text name = "?"*80 start + XY = c64.CINV SI = 1 - [$0314.word] = #irq_handler + c64.CINV = #irq_handler SI = 0 c64util.print_string("enter your name: ") c64util.input_chars(name) c64.CHROUT('\n') - ; c64util.print_string("thank you, ") ; @todo fix param parsing /splitting - c64util.print_string("thank you ") + c64util.print_string("thank you, mr or mrs: ") c64util.print_string(name) c64.CHROUT('\n') + SI = 1 + c64.CINV = XY + SI = 0 + return irq_handler