Skip to content

Commit e8a58bc

Browse files
authored
Merge pull request #577 from boriel/feature/allow_named_parameters
feat: add keyword parameters
2 parents 1a6aacc + b35561d commit e8a58bc

19 files changed

Lines changed: 344 additions & 19 deletions

src/api/check.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# the GNU General License
1010
# ----------------------------------------------------------------------
1111

12-
from typing import Union
12+
from typing import Dict, Union
1313

1414
from src.api import config
1515
from src.api import global_
@@ -111,21 +111,54 @@ def check_call_arguments(lineno: int, id_: str, args):
111111
return False
112112

113113
entry = global_.SYMBOL_TABLE.get_entry(id_)
114+
named_args: Dict[str, symbols.ARGUMENT] = {}
114115

115-
if len(args) < len(entry.params): # try filling default params
116-
for param in entry.params[len(args) :]:
116+
param_names = set(x.name for x in entry.params)
117+
for arg in args:
118+
if arg.name is not None and arg.name not in param_names:
119+
errmsg.error(lineno, f"Unexpected argument '{arg.name}'", fname=entry.filename)
120+
return False
121+
122+
last_arg_name = None
123+
for arg, param in zip(args, entry.params):
124+
if last_arg_name is not None and arg.name is None:
125+
errmsg.error(
126+
lineno, f"Positional argument cannot go after keyword argument '{last_arg_name}'", fname=entry.filename
127+
)
128+
return False
129+
130+
if arg.name is not None:
131+
last_arg_name = arg.name
132+
else:
133+
arg.name = param.name
134+
135+
named_args[arg.name] = arg
136+
137+
if len(named_args) < len(entry.params): # try filling default params
138+
for param in entry.params:
139+
if param.name in named_args:
140+
continue
117141
if param.default_value is None:
118142
break
119-
symbols.ARGLIST.make_node(args, symbols.ARGUMENT(param.default_value, lineno=lineno, byref=False))
143+
arg = symbols.ARGUMENT(param.default_value, lineno=lineno, byref=False, name=param.name)
144+
symbols.ARGLIST.make_node(args, arg)
145+
named_args[arg.name] = arg
146+
147+
for arg in args:
148+
if arg.name is None:
149+
errmsg.error(lineno, f"Too many arguments for Function '{id_}'", fname=entry.filename)
150+
return False
120151

121-
if len(args) != len(entry.params):
152+
if len(named_args) != len(entry.params):
122153
c = "s" if len(entry.params) != 1 else ""
123154
errmsg.error(
124155
lineno, f"Function '{id_}' takes {len(entry.params)} parameter{c}, not {len(args)}", fname=entry.filename
125156
)
126157
return False
127158

128-
for arg, param in zip(args, entry.params):
159+
for param in entry.params:
160+
arg = named_args[param.name]
161+
129162
if arg.class_ in (CLASS.var, CLASS.array) and param.class_ != arg.class_:
130163
errmsg.error(lineno, "Invalid argument '{}'".format(arg.value), fname=arg.filename)
131164
return None

src/api/global_.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class LoopInfo(NamedTuple):
4949
# scope exit, the previous LOOPS is restored and popped out of the
5050
# META_LOOPS stack.
5151
# ----------------------------------------------------------------------
52-
META_LOOPS = []
52+
META_LOOPS: List[List[LoopInfo]] = []
5353

5454
# ----------------------------------------------------------------------
5555
# Number of parser (both syntactic & semantic) errors found. If not 0

src/parsetab/tabs.dbm.bak

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
'zxbpp', (0, 76970)
22
'asmparse', (77312, 268610)
33
'zxnext_asmparse', (346112, 298625)
4-
'zxbparser', (645120, 704752)
4+
'zxbparser', (645120, 708977)

src/parsetab/tabs.dbm.dat

4.13 KB
Binary file not shown.

src/parsetab/tabs.dbm.dir

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
'zxbpp', (0, 76970)
22
'asmparse', (77312, 268610)
33
'zxnext_asmparse', (346112, 298625)
4-
'zxbparser', (645120, 704752)
4+
'zxbparser', (645120, 708977)

src/symbols/argument.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@
2424
class SymbolARGUMENT(Symbol):
2525
"""Defines an argument in a function call"""
2626

27-
def __init__(self, value, lineno: int, byref=None, filename: str = None):
27+
def __init__(self, value, lineno: int, byref=None, filename: str = None, name: str = None):
2828
"""Initializes the argument data. Byref must be set
2929
to True if this Argument is passed by reference.
3030
"""
3131
super().__init__(value)
3232
self.lineno = lineno
3333
self.filename = filename or gl.FILENAME
3434
self.byref = byref if byref is not None else OPTIONS.default_byref
35+
self.name = name
3536

3637
@property
3738
def t(self):

src/zxbc/zxblex.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"NE",
5757
"ID",
5858
"NEWLINE",
59+
"WEQ",
5960
"CO",
6061
"SC",
6162
"COMMA",
@@ -141,6 +142,12 @@ def t_RP(t):
141142
return t
142143

143144

145+
def t_WEQ(t):
146+
r":="
147+
148+
return t
149+
150+
144151
def t_CO(t):
145152
r":"
146153

src/zxbc/zxbparser.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -289,32 +289,33 @@ def make_func_declaration(func_name: str, lineno: int, class_: CLASS, type_=None
289289

290290
def make_arg_list(node, *args):
291291
"""Wrapper: returns a node with an argument_list."""
292-
return symbols.ARGLIST.make_node(node, *args)
292+
result = symbols.ARGLIST.make_node(node, *args)
293+
return result
293294

294295

295-
def make_argument(expr, lineno, byref=None):
296+
def make_argument(expr, lineno: int, byref=None, name: str = None):
296297
"""Wrapper: Creates a node containing an ARGUMENT"""
297298
if expr is None:
298299
return # There were a syntax / semantic error
299300

300301
if byref is None:
301302
byref = OPTIONS.default_byref
302-
return symbols.ARGUMENT(expr, lineno=lineno, byref=byref)
303+
return symbols.ARGUMENT(expr, lineno=lineno, byref=byref, name=name)
303304

304305

305306
def make_param_list(node, *args):
306307
"""Wrapper: Returns a param declaration list (function header)"""
307308
return symbols.PARAMLIST.make_node(node, *args)
308309

309310

310-
def make_sub_call(id_, lineno, params):
311+
def make_sub_call(id_, lineno, arg_list):
311312
"""This will return an AST node for a sub/procedure call."""
312-
return symbols.CALL.make_node(id_, params, lineno, gl.FILENAME)
313+
return symbols.CALL.make_node(id_, arg_list, lineno, gl.FILENAME)
313314

314315

315-
def make_func_call(id_, lineno, params):
316+
def make_func_call(id_, lineno, arg_list):
316317
"""This will return an AST node for a function call."""
317-
return symbols.FUNCCALL.make_node(id_, params, lineno, gl.FILENAME)
318+
return symbols.FUNCCALL.make_node(id_, arg_list, lineno, gl.FILENAME)
318319

319320

320321
def make_array_access(id_, lineno, arglist):
@@ -2766,6 +2767,11 @@ def p_argument(p):
27662767
p[0] = make_argument(p[1], p.lineno(1))
27672768

27682769

2770+
def p_named_argument(p):
2771+
"""argument : ID WEQ expr %prec ID"""
2772+
p[0] = make_argument(p[3], p.lineno(1), name=p[1])
2773+
2774+
27692775
def p_argument_array(p):
27702776
"""argument : ARRAY_ID"""
27712777
entry = SYMBOL_TABLE.access_array(p[1], p.lineno(1))

tests/functional/test_errmsg.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ line_asm.bi:26: warning: this should be line 26
184184
line_err.bas:5: error: Variable 'q' already declared at line_err.bas:1
185185
>>> process_file('zx48k/let_expr_type_crash.bas')
186186
let_expr_type_crash.bas:3: error: Syntax Error. Unexpected token 's' <ID>
187-
let_expr_type_crash.bas:8: error: Function 'editStringFN' takes 0 parameters, not 3
187+
let_expr_type_crash.bas:8: error: Too many arguments for Function 'editStringFN'
188188
>>> process_file('db256.asm')
189189
db256.asm:3: warning: [W200] Value will be truncated
190190
db256.asm:4: warning: [W200] Value will be truncated
@@ -242,7 +242,7 @@ tap_errline1.bas:15: error: Syntax error. Unexpected token 'HL' [HL]
242242
>>> process_file('zx48k/bad_fname_err0.bas', ['-S', '-q'])
243243
ND.Controls.bas:4: error: Expected a variable name, not an expression (parameter By Reference)
244244
>>> process_file('zx48k/bad_fname_err1.bas', ['-S', '-q'])
245-
ND.Controls.bas:4: error: Function 'Controls_LABEL' takes 1 parameter, not 2
245+
ND.Controls.bas:4: error: Too many arguments for Function 'Controls_LABEL'
246246
>>> process_file('zx48k/bad_fname_err2.bas', ['-S', '-q'])
247247
ND.Controls.bas:4: error: Invalid argument 'dirData'
248248
>>> process_file('zx48k/bad_fname_err3.bas', ['-S', '-q'])
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
org 32768
2+
.core.__START_PROGRAM:
3+
di
4+
push ix
5+
push iy
6+
exx
7+
push hl
8+
exx
9+
ld hl, 0
10+
add hl, sp
11+
ld (.core.__CALL_BACK__), hl
12+
ei
13+
jp .core.__MAIN_PROGRAM__
14+
.core.__CALL_BACK__:
15+
DEFW 0
16+
.core.ZXBASIC_USER_DATA:
17+
; Defines USER DATA Length in bytes
18+
.core.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_END - .core.ZXBASIC_USER_DATA
19+
.core.__LABEL__.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_LEN
20+
.core.__LABEL__.ZXBASIC_USER_DATA EQU .core.ZXBASIC_USER_DATA
21+
.core.ZXBASIC_USER_DATA_END:
22+
.core.__MAIN_PROGRAM__:
23+
ld a, 2
24+
push af
25+
ld a, 213
26+
push af
27+
call _test
28+
ld hl, 0
29+
ld b, h
30+
ld c, l
31+
.core.__END_PROGRAM:
32+
di
33+
ld hl, (.core.__CALL_BACK__)
34+
ld sp, hl
35+
exx
36+
pop hl
37+
exx
38+
pop iy
39+
pop ix
40+
ei
41+
ret
42+
_test:
43+
push ix
44+
ld ix, 0
45+
add ix, sp
46+
_test__leave:
47+
ld sp, ix
48+
pop ix
49+
exx
50+
pop hl
51+
pop bc
52+
ex (sp), hl
53+
exx
54+
ret
55+
;; --- end of user code ---
56+
END

0 commit comments

Comments
 (0)