Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request boriel-basic#577 from boriel/feature/allow_named_p…
Browse files Browse the repository at this point in the history
…arameters

feat: add keyword parameters
  • Loading branch information
boriel committed Oct 17, 2021
2 parents 1a6aacc + b35561d commit e8a58bc
Show file tree
Hide file tree
Showing 19 changed files with 344 additions and 19 deletions.
45 changes: 39 additions & 6 deletions src/api/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# the GNU General License
# ----------------------------------------------------------------------

from typing import Union
from typing import Dict, Union

from src.api import config
from src.api import global_
Expand Down Expand Up @@ -111,21 +111,54 @@ def check_call_arguments(lineno: int, id_: str, args):
return False

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

if len(args) < len(entry.params): # try filling default params
for param in entry.params[len(args) :]:
param_names = set(x.name for x in entry.params)
for arg in args:
if arg.name is not None and arg.name not in param_names:
errmsg.error(lineno, f"Unexpected argument '{arg.name}'", fname=entry.filename)
return False

last_arg_name = None
for arg, param in zip(args, entry.params):
if last_arg_name is not None and arg.name is None:
errmsg.error(
lineno, f"Positional argument cannot go after keyword argument '{last_arg_name}'", fname=entry.filename
)
return False

if arg.name is not None:
last_arg_name = arg.name
else:
arg.name = param.name

named_args[arg.name] = arg

if len(named_args) < len(entry.params): # try filling default params
for param in entry.params:
if param.name in named_args:
continue
if param.default_value is None:
break
symbols.ARGLIST.make_node(args, symbols.ARGUMENT(param.default_value, lineno=lineno, byref=False))
arg = symbols.ARGUMENT(param.default_value, lineno=lineno, byref=False, name=param.name)
symbols.ARGLIST.make_node(args, arg)
named_args[arg.name] = arg

for arg in args:
if arg.name is None:
errmsg.error(lineno, f"Too many arguments for Function '{id_}'", fname=entry.filename)
return False

if len(args) != len(entry.params):
if len(named_args) != len(entry.params):
c = "s" if len(entry.params) != 1 else ""
errmsg.error(
lineno, f"Function '{id_}' takes {len(entry.params)} parameter{c}, not {len(args)}", fname=entry.filename
)
return False

for arg, param in zip(args, entry.params):
for param in entry.params:
arg = named_args[param.name]

if arg.class_ in (CLASS.var, CLASS.array) and param.class_ != arg.class_:
errmsg.error(lineno, "Invalid argument '{}'".format(arg.value), fname=arg.filename)
return None
Expand Down
2 changes: 1 addition & 1 deletion src/api/global_.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class LoopInfo(NamedTuple):
# scope exit, the previous LOOPS is restored and popped out of the
# META_LOOPS stack.
# ----------------------------------------------------------------------
META_LOOPS = []
META_LOOPS: List[List[LoopInfo]] = []

# ----------------------------------------------------------------------
# Number of parser (both syntactic & semantic) errors found. If not 0
Expand Down
2 changes: 1 addition & 1 deletion src/parsetab/tabs.dbm.bak
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'zxbpp', (0, 76970)
'asmparse', (77312, 268610)
'zxnext_asmparse', (346112, 298625)
'zxbparser', (645120, 704752)
'zxbparser', (645120, 708977)
Binary file modified src/parsetab/tabs.dbm.dat
Binary file not shown.
2 changes: 1 addition & 1 deletion src/parsetab/tabs.dbm.dir
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'zxbpp', (0, 76970)
'asmparse', (77312, 268610)
'zxnext_asmparse', (346112, 298625)
'zxbparser', (645120, 704752)
'zxbparser', (645120, 708977)
3 changes: 2 additions & 1 deletion src/symbols/argument.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@
class SymbolARGUMENT(Symbol):
"""Defines an argument in a function call"""

def __init__(self, value, lineno: int, byref=None, filename: str = None):
def __init__(self, value, lineno: int, byref=None, filename: str = None, name: str = None):
"""Initializes the argument data. Byref must be set
to True if this Argument is passed by reference.
"""
super().__init__(value)
self.lineno = lineno
self.filename = filename or gl.FILENAME
self.byref = byref if byref is not None else OPTIONS.default_byref
self.name = name

@property
def t(self):
Expand Down
7 changes: 7 additions & 0 deletions src/zxbc/zxblex.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"NE",
"ID",
"NEWLINE",
"WEQ",
"CO",
"SC",
"COMMA",
Expand Down Expand Up @@ -141,6 +142,12 @@ def t_RP(t):
return t


def t_WEQ(t):
r":="

return t


def t_CO(t):
r":"

Expand Down
20 changes: 13 additions & 7 deletions src/zxbc/zxbparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,32 +289,33 @@ def make_func_declaration(func_name: str, lineno: int, class_: CLASS, type_=None

def make_arg_list(node, *args):
"""Wrapper: returns a node with an argument_list."""
return symbols.ARGLIST.make_node(node, *args)
result = symbols.ARGLIST.make_node(node, *args)
return result


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

if byref is None:
byref = OPTIONS.default_byref
return symbols.ARGUMENT(expr, lineno=lineno, byref=byref)
return symbols.ARGUMENT(expr, lineno=lineno, byref=byref, name=name)


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


def make_sub_call(id_, lineno, params):
def make_sub_call(id_, lineno, arg_list):
"""This will return an AST node for a sub/procedure call."""
return symbols.CALL.make_node(id_, params, lineno, gl.FILENAME)
return symbols.CALL.make_node(id_, arg_list, lineno, gl.FILENAME)


def make_func_call(id_, lineno, params):
def make_func_call(id_, lineno, arg_list):
"""This will return an AST node for a function call."""
return symbols.FUNCCALL.make_node(id_, params, lineno, gl.FILENAME)
return symbols.FUNCCALL.make_node(id_, arg_list, lineno, gl.FILENAME)


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


def p_named_argument(p):
"""argument : ID WEQ expr %prec ID"""
p[0] = make_argument(p[3], p.lineno(1), name=p[1])


def p_argument_array(p):
"""argument : ARRAY_ID"""
entry = SYMBOL_TABLE.access_array(p[1], p.lineno(1))
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/test_errmsg.txt
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ line_asm.bi:26: warning: this should be line 26
line_err.bas:5: error: Variable 'q' already declared at line_err.bas:1
>>> process_file('zx48k/let_expr_type_crash.bas')
let_expr_type_crash.bas:3: error: Syntax Error. Unexpected token 's' <ID>
let_expr_type_crash.bas:8: error: Function 'editStringFN' takes 0 parameters, not 3
let_expr_type_crash.bas:8: error: Too many arguments for Function 'editStringFN'
>>> process_file('db256.asm')
db256.asm:3: warning: [W200] Value will be truncated
db256.asm:4: warning: [W200] Value will be truncated
Expand Down Expand Up @@ -242,7 +242,7 @@ tap_errline1.bas:15: error: Syntax error. Unexpected token 'HL' [HL]
>>> process_file('zx48k/bad_fname_err0.bas', ['-S', '-q'])
ND.Controls.bas:4: error: Expected a variable name, not an expression (parameter By Reference)
>>> process_file('zx48k/bad_fname_err1.bas', ['-S', '-q'])
ND.Controls.bas:4: error: Function 'Controls_LABEL' takes 1 parameter, not 2
ND.Controls.bas:4: error: Too many arguments for Function 'Controls_LABEL'
>>> process_file('zx48k/bad_fname_err2.bas', ['-S', '-q'])
ND.Controls.bas:4: error: Invalid argument 'dirData'
>>> process_file('zx48k/bad_fname_err3.bas', ['-S', '-q'])
Expand Down
56 changes: 56 additions & 0 deletions tests/functional/zx48k/keyword_arg0.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
org 32768
.core.__START_PROGRAM:
di
push ix
push iy
exx
push hl
exx
ld hl, 0
add hl, sp
ld (.core.__CALL_BACK__), hl
ei
jp .core.__MAIN_PROGRAM__
.core.__CALL_BACK__:
DEFW 0
.core.ZXBASIC_USER_DATA:
; Defines USER DATA Length in bytes
.core.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_END - .core.ZXBASIC_USER_DATA
.core.__LABEL__.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_LEN
.core.__LABEL__.ZXBASIC_USER_DATA EQU .core.ZXBASIC_USER_DATA
.core.ZXBASIC_USER_DATA_END:
.core.__MAIN_PROGRAM__:
ld a, 2
push af
ld a, 213
push af
call _test
ld hl, 0
ld b, h
ld c, l
.core.__END_PROGRAM:
di
ld hl, (.core.__CALL_BACK__)
ld sp, hl
exx
pop hl
exx
pop iy
pop ix
ei
ret
_test:
push ix
ld ix, 0
add ix, sp
_test__leave:
ld sp, ix
pop ix
exx
pop hl
pop bc
ex (sp), hl
exx
ret
;; --- end of user code ---
END
5 changes: 5 additions & 0 deletions tests/functional/zx48k/keyword_arg0.bas
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

sub test(x as Ubyte = 1, y as Ubyte = 2)
end sub

test(213)
5 changes: 5 additions & 0 deletions tests/functional/zx48k/keyword_arg1.bas
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

sub test(x as Ubyte = 1, y as Ubyte = 2)
end sub

test(y:=213, 4)
56 changes: 56 additions & 0 deletions tests/functional/zx48k/keyword_arg2.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
org 32768
.core.__START_PROGRAM:
di
push ix
push iy
exx
push hl
exx
ld hl, 0
add hl, sp
ld (.core.__CALL_BACK__), hl
ei
jp .core.__MAIN_PROGRAM__
.core.__CALL_BACK__:
DEFW 0
.core.ZXBASIC_USER_DATA:
; Defines USER DATA Length in bytes
.core.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_END - .core.ZXBASIC_USER_DATA
.core.__LABEL__.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_LEN
.core.__LABEL__.ZXBASIC_USER_DATA EQU .core.ZXBASIC_USER_DATA
.core.ZXBASIC_USER_DATA_END:
.core.__MAIN_PROGRAM__:
ld a, 1
push af
ld a, 213
push af
call _test
ld hl, 0
ld b, h
ld c, l
.core.__END_PROGRAM:
di
ld hl, (.core.__CALL_BACK__)
ld sp, hl
exx
pop hl
exx
pop iy
pop ix
ei
ret
_test:
push ix
ld ix, 0
add ix, sp
_test__leave:
ld sp, ix
pop ix
exx
pop hl
pop bc
ex (sp), hl
exx
ret
;; --- end of user code ---
END
5 changes: 5 additions & 0 deletions tests/functional/zx48k/keyword_arg2.bas
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

sub test(x as Ubyte = 1, y as Ubyte = 2)
end sub

test(y:=213)
5 changes: 5 additions & 0 deletions tests/functional/zx48k/keyword_arg3.bas
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

Function test as uByte
end Function

let a = test(z:=1)

0 comments on commit e8a58bc

Please sign in to comment.