fixed arg parsing

This commit is contained in:
Irmen de Jong 2017-12-25 22:22:19 +01:00
parent c78cbc4a33
commit 4a9d3200cd
7 changed files with 125 additions and 53 deletions

View File

@ -7,7 +7,7 @@ License: GNU GPL 3.0, see LICENSE
""" """
import ast 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 from .symbols import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE, SourceRef, SymbolTable, SymbolError, PrimitiveType
@ -61,6 +61,45 @@ class SourceLine:
return text 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, *, def parse_expr_as_int(text: str, context: Optional[SymbolTable], ppcontext: Optional[SymbolTable], sourceref: SourceRef, *,
minimum: int=0, maximum: int=0xffff) -> int: minimum: int=0, maximum: int=0xffff) -> int:
result = parse_expr_as_primitive(text, context, ppcontext, sourceref, minimum=minimum, maximum=maximum) result = parse_expr_as_primitive(text, context, ppcontext, sourceref, minimum=minimum, maximum=maximum)

View File

@ -12,9 +12,9 @@ import os
import shutil import shutil
import enum import enum
from collections import defaultdict 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,\ 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, \ from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \
zeropage, check_value_in_range, char_to_bytevalue, \ zeropage, check_value_in_range, char_to_bytevalue, \
PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \ PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \
@ -62,6 +62,8 @@ class ParseResult:
return label return label
def lookup(self, dottedname: str) -> Tuple[Optional['ParseResult.Block'], Optional[Union[SymbolDefinition, SymbolTable]]]: 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: try:
scope, result = self.symbols.lookup(dottedname) scope, result = self.symbols.lookup(dottedname)
return scope.owning_block, result return scope.owning_block, result
@ -1044,15 +1046,7 @@ class Parser:
argumentstr = argumentstr.strip() if argumentstr else "" argumentstr = argumentstr.strip() if argumentstr else ""
arguments = None arguments = None
if argumentstr: if argumentstr:
arguments = [] arguments = parse_arguments(argumentstr, self.sourceref)
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))
target = None # type: ParseResult.Value target = None # type: ParseResult.Value
if targetstr[0] == '[' and targetstr[-1] == ']': if targetstr[0] == '[' and targetstr[-1] == ']':
# indirect call to address in register pair or memory location # 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") raise self.PError("cannot call that type of indirect symbol")
address = target.address if isinstance(target, ParseResult.MemMappedValue) else None address = target.address if isinstance(target, ParseResult.MemMappedValue) else None
try: try:
_, symbol = self.lookup(targetstr) _, symbol = self.lookup_with_ppsymbols(targetstr)
except ParseError: except ParseError:
symbol = None # it's probably a number or a register then symbol = None # it's probably a number or a register then
if isinstance(symbol, SubroutineDef): if isinstance(symbol, SubroutineDef):
@ -1080,10 +1074,9 @@ class Parser:
args_with_pnames = [] args_with_pnames = []
for i, (argname, value) in enumerate(arguments or []): for i, (argname, value) in enumerate(arguments or []):
pname, preg = symbol.parameters[i] pname, preg = symbol.parameters[i]
if argname: required_name = pname or preg
if argname != preg: if argname and argname != required_name:
raise self.PError("parameter mismatch ({:s}, expected {:s})".format(argname, preg)) raise self.PError("parameter mismatch ('{:s}', expected '{:s}')".format(argname, required_name))
else:
argname = preg argname = preg
args_with_pnames.append((argname, value)) args_with_pnames.append((argname, value))
arguments = args_with_pnames arguments = args_with_pnames
@ -1111,14 +1104,17 @@ class Parser:
return int(text[1:], 2) return int(text[1:], 2)
return int(text) 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 # parses assigning a value to one or more targets
parts = line.split("=") parts = line.split("=")
if parsed_rvalue:
r_value = parsed_rvalue
else:
rhs = parts.pop() rhs = parts.pop()
r_value = self.parse_expression(rhs)
l_values = [self.parse_expression(part) for part in parts] l_values = [self.parse_expression(part) for part in parts]
if any(isinstance(lv, ParseResult.IntegerValue) for lv in l_values): 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?") 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: for lv in l_values:
assignable, reason = lv.assignable_from(r_value) assignable, reason = lv.assignable_from(r_value)
if not assignable: if not assignable:
@ -1162,8 +1158,7 @@ class Parser:
if line.strip() == "}": if line.strip() == "}":
return ParseResult.InlineAsm(lineno, asmlines) return ParseResult.InlineAsm(lineno, asmlines)
# asm can refer to other symbols as well, track subroutine usage # asm can refer to other symbols as well, track subroutine usage
if line.startswith((" ", "\t")): splits = line.split(maxsplit=1)
splits = line.split(maxsplit=2)
if len(splits) == 2: if len(splits) == 2:
for match in re.finditer(r"(?P<symbol>[a-zA-Z_$][a-zA-Z0-9_\.]+)", splits[1]): for match in re.finditer(r"(?P<symbol>[a-zA-Z_$][a-zA-Z0-9_\.]+)", splits[1]):
name = match.group("symbol") name = match.group("symbol")
@ -1172,7 +1167,7 @@ class Parser:
try: try:
if '.' not in name: if '.' not in name:
name = self.cur_block.symbols.parent.name + '.' + name name = self.cur_block.symbols.parent.name + '.' + name
_, symbol = self.lookup(name) _, symbol = self.lookup_with_ppsymbols(name)
except ParseError: except ParseError:
pass pass
else: else:
@ -1262,7 +1257,7 @@ class Parser:
elif text == "false": elif text == "false":
return ParseResult.IntegerValue(0) return ParseResult.IntegerValue(0)
elif self.is_identifier(text): elif self.is_identifier(text):
symblock, sym = self.lookup(text) symblock, sym = self.lookup_with_ppsymbols(text)
if isinstance(sym, (VariableDef, ConstantDef)): if isinstance(sym, (VariableDef, ConstantDef)):
constant = isinstance(sym, ConstantDef) constant = isinstance(sym, ConstantDef)
if self.cur_block is symblock: if self.cur_block is symblock:
@ -1340,14 +1335,14 @@ class Parser:
return blockname.isidentifier() and name.isidentifier() return blockname.isidentifier() and name.isidentifier()
return False 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) symblock, sym = self.cur_block.lookup(dottedname)
if sym is None: if sym is None and self.ppsymbols:
# symbol is not (yet) known in current block, see if the ppsymbols know about it # symbol is not (yet) known, see if the symbols from the preprocess parse phase know about it
if '.' not in dottedname: if '.' not in dottedname:
dottedname = self.cur_block.name + '.' + dottedname dottedname = self.cur_block.name + '.' + dottedname
try: try:
if self.ppsymbols:
symtable, sym = self.ppsymbols.lookup(dottedname) symtable, sym = self.ppsymbols.lookup(dottedname)
assert dottedname.startswith(symtable.name) assert dottedname.startswith(symtable.name)
symblock = None # the block might not have been parsed yet, so just return this instead symblock = None # the block might not have been parsed yet, so just return this instead

View File

@ -179,9 +179,12 @@ class SubroutineDef(SymbolDefinition):
for _, param in parameters: for _, param in parameters:
if param in REGISTER_BYTES: if param in REGISTER_BYTES:
self.input_registers.add(param) self.input_registers.add(param)
self.clobbered_registers.add(param)
elif param in REGISTER_WORDS: elif param in REGISTER_WORDS:
self.input_registers.add(param[0]) self.input_registers.add(param[0])
self.input_registers.add(param[1]) self.input_registers.add(param[1])
self.clobbered_registers.add(param[0])
self.clobbered_registers.add(param[1])
else: else:
raise SymbolError("invalid parameter spec: " + param) raise SymbolError("invalid parameter spec: " + param)
for register in returnvalues: for register in returnvalues:
@ -254,6 +257,10 @@ class SymbolTable:
return symbolname in self.symbols return symbolname in self.symbols
def lookup(self, dottedname: str, include_builtin_names: bool=False) -> Tuple['SymbolTable', Union[SymbolDefinition, 'SymbolTable']]: 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('.') nameparts = dottedname.split('.')
if len(nameparts) == 1: if len(nameparts) == 1:
try: try:
@ -265,7 +272,7 @@ class SymbolTable:
elif nameparts[0] in BUILTIN_SYMBOLS: elif nameparts[0] in BUILTIN_SYMBOLS:
return self, getattr(builtins, nameparts[0]) return self, getattr(builtins, nameparts[0])
raise SymbolError("undefined symbol '{:s}'".format(nameparts[0])) from None raise SymbolError("undefined symbol '{:s}'".format(nameparts[0])) from None
# start from toplevel namespace: # restart from global namespace:
scope = self scope = self
while scope.parent: while scope.parent:
scope = scope.parent scope = scope.parent
@ -278,7 +285,7 @@ class SymbolTable:
if isinstance(scope, SymbolTable): if isinstance(scope, SymbolTable):
return scope.lookup(nameparts[-1]) return scope.lookup(nameparts[-1])
elif isinstance(scope, SubroutineDef): elif isinstance(scope, SubroutineDef):
return scope.sub_block.symbols.lookup(nameparts[-1]) return scope.sub_block.symbols.lookup_with_ppsymbols(nameparts[-1])
else: else:
raise SymbolError("invalid block name '{:s}' in dotted name".format(namepart)) raise SymbolError("invalid block name '{:s}' in dotted name".format(namepart))

View File

@ -10,8 +10,8 @@ output raw
memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
memory COLOR = $0286 ; cursor color memory .byte COLOR = $0286 ; cursor color
memory CINV = $0314 ; IRQ vector memory .word CINV = $0314 ; IRQ vector
; ---- VIC-II registers ---- ; ---- VIC-II registers ----

View File

@ -251,7 +251,11 @@ The syntax is:
... statements ... ... statements ...
} }
proc_parameters = comma separated list of "<parametername>:<register>" pairs specifying the input parameters proc_parameters = comma separated list of "<parametername>:<register>" 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 <register> names specifying in which register(s) the output is returned. proc_results = comma separated list of <register> 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 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. is clobbered in the process so the original value it had before calling the sub is no longer valid.

View File

@ -17,11 +17,20 @@
sub sub1 () -> (X?) = $ffdd sub sub1 () -> (X?) = $ffdd
sub sub2 (A) -> (Y?) = $eecc sub sub2 (A) -> (Y?) = $eecc
sub sub3 (XY) -> (Y?) = $ddaa
sub sub4 (string: XY, other : A) -> (Y?) = $dd22
bar bar
goto sub1 goto sub1
goto sub2 (1 ) 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 bar ()
goto [AX] goto [AX]
goto [AX] () goto [AX] ()
@ -47,6 +56,13 @@ bar
sub1!() sub1!()
sub2!(11) 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!() bar!()
[XY] ! () [XY] ! ()
[var1] !() [var1] !()
@ -65,6 +81,13 @@ bar
sub1() sub1()
sub2(11) 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 () bar ()
[AX]() [AX]()
[var1] ( ) [var1] ( )

View File

@ -7,18 +7,22 @@ import "c64lib"
var .text name = "?"*80 var .text name = "?"*80
start start
XY = c64.CINV
SI = 1 SI = 1
[$0314.word] = #irq_handler c64.CINV = #irq_handler
SI = 0 SI = 0
c64util.print_string("enter your name: ") c64util.print_string("enter your name: ")
c64util.input_chars(name) c64util.input_chars(name)
c64.CHROUT('\n') c64.CHROUT('\n')
; c64util.print_string("thank you, ") ; @todo fix param parsing /splitting c64util.print_string("thank you, mr or mrs: ")
c64util.print_string("thank you ")
c64util.print_string(name) c64util.print_string(name)
c64.CHROUT('\n') c64.CHROUT('\n')
SI = 1
c64.CINV = XY
SI = 0
return return
irq_handler irq_handler