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
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)

View File

@ -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<symbol>[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<symbol>[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

View File

@ -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))

View File

@ -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 ----

View File

@ -251,7 +251,11 @@ The syntax is:
... 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.
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.

View File

@ -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] ( )

View File

@ -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