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#573 from boriel/feature/optional_para…
Browse files Browse the repository at this point in the history
…meters

feat: allow optional parameters!
  • Loading branch information
boriel committed Oct 11, 2021
2 parents 69b6ed8 + 707aafc commit be0b538
Show file tree
Hide file tree
Showing 66 changed files with 1,703 additions and 881 deletions.
61 changes: 29 additions & 32 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,56 @@
import pathlib
from setuptools import setup

packages = [
'src'
]
packages = ["src"]

# The directory containing this file
HERE = pathlib.Path(__file__).parent

# The text of the README file
README = (HERE / "README.md").read_text()

package_data = {'': ['*'], 'arch.zx48k.peephole': ['opts/*']}
package_data = {"": ["*"], "arch.zx48k.peephole": ["opts/*"]}

entry_points = {
'console_scripts': ['zxb = src.libzxbc.zxb:main',
'zxbasm = src.libzxbasm.zxbasm:main',
'zxbc = src.libzxbc.zxb:main',
'zxbpp = src.libzxbpp.zxbpp:entry_point']
"console_scripts": [
"zxb = src.libzxbc.zxb:main",
"zxbasm = src.libzxbasm.zxbasm:main",
"zxbc = src.libzxbc.zxb:main",
"zxbpp = src.libzxbpp.zxbpp:entry_point",
]
}

setup_kwargs = {
'name': 'zxbasic',
'version': '1.15.2',
'description': "Boriel's ZX BASIC Compiler",
'classifiers': [
"name": "zxbasic",
"version": "1.15.2",
"description": "Boriel's ZX BASIC Compiler",
"classifiers": [
# How mature is this project? Common values are
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
'Development Status :: 5 - Production/Stable',

"Development Status :: 5 - Production/Stable",
# Indicate who your project is intended for
'Intended Audience :: Developers',
'Topic :: Software Development :: Build Tools',

"Intended Audience :: Developers",
"Topic :: Software Development :: Build Tools",
# Pick your license as you wish (should match "license" above)
'License :: OSI Approved :: GNU Affero General Public License v3',

"License :: OSI Approved :: GNU Affero General Public License v3",
# Specify the Python versions you support here. In particular, ensure
# that you indicate whether you support Python 2, Python 3 or both.
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.8',
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.8",
],
'long_description_content_type': "text/markdown",
'long_description': README,
'author': 'Jose Rodriguez',
'author_email': 'boriel@gmail.com',
'maintainer': None,
'maintainer_email': None,
'url': 'http://zxbasic.net',
'packages': packages,
'package_data': package_data,
'entry_points': entry_points,
'python_requires': '>=3.6,<4.0',
"long_description_content_type": "text/markdown",
"long_description": README,
"author": "Jose Rodriguez",
"author_email": "boriel@gmail.com",
"maintainer": None,
"maintainer_email": None,
"url": "http://zxbasic.net",
"packages": packages,
"package_data": package_data,
"entry_points": entry_points,
"python_requires": ">=3.6,<4.0",
}

setup(**setup_kwargs)
6 changes: 6 additions & 0 deletions src/api/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ def check_call_arguments(lineno: int, id_: str, args):

entry = global_.SYMBOL_TABLE.get_entry(id_)

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

if len(args) != len(entry.params):
c = "s" if len(entry.params) != 1 else ""
errmsg.error(
Expand Down
14 changes: 14 additions & 0 deletions src/api/errmsg.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,13 @@ def syntax_error_cannot_initialize_array_of_type(lineno: int, type_name: str):
error(lineno, f"Cannot initialize array of type {type_name}")


# ----------------------------------------
# Cannot define a default array argument
# ----------------------------------------
def syntax_error_cannot_define_default_array_argument(lineno: int):
error(lineno, "Cannot define default array argument")


# ----------------------------------------
# Error, ID is a ... not a ...
# ----------------------------------------
Expand All @@ -319,4 +326,11 @@ def syntax_error_already_declared(lineno: int, id_name: str, as_class: CLASS, at
error(lineno, f"'{id_name}' already declared as {as_class} at {at_lineno}")


# ----------------------------------------
# Can't declare a mandatory parameter after an optional one
# ----------------------------------------
def syntax_error_mandatory_param_after_optional(lineno: int, param1: str, param2: str):
error(lineno, f"Can't declare mandatory param '{param2}' after optional param '{param1}'")


# endregion
12 changes: 10 additions & 2 deletions src/api/symboltable.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from .errmsg import warning_not_used
from .errmsg import syntax_error_func_type_mismatch
from .errmsg import syntax_error_not_array_nor_func
from .errmsg import syntax_error_cannot_define_default_array_argument

from .constants import DEPRECATED_SUFFIXES
from .constants import SUFFIX_TYPE
Expand Down Expand Up @@ -695,26 +696,33 @@ def declare_label(self, id_: str, lineno: int) -> Optional[SymbolLABEL]:
entry.type_ = self.basic_types[global_.PTR_TYPE]
return entry

def declare_param(self, id_: str, lineno: int, type_=None, is_array=False) -> Optional[SymbolVAR]:
def declare_param(
self, id_: str, lineno: int, type_=None, is_array=False, default_value: Optional[Symbol] = None
) -> Optional[SymbolVAR]:
"""Declares a parameter
Check if entry.declared is False. Otherwise raises an error.
"""
if not self.check_is_undeclared(id_, lineno, classname="parameter", scope=self.current_scope, show_error=True):
return None

if is_array:
if default_value is not None:
syntax_error_cannot_define_default_array_argument(lineno)
return None

entry = self.declare(id_, lineno, symbols.VARARRAY(id_, symbols.BOUNDLIST(), lineno, None, type_))
entry.callable = True
entry.scope = SCOPE.parameter
else:
entry = self.declare(id_, lineno, symbols.PARAMDECL(id_, lineno, type_))
entry = self.declare(id_, lineno, symbols.PARAMDECL(id_, lineno, type_, default_value))

if entry is None:
return None

entry.declared = True
if entry.type_.implicit:
warning_implicit_type(lineno, id_, type_)

return entry

def declare_array(self, id_: str, lineno: int, type_, bounds, default_value=None, addr=None):
Expand Down
2 changes: 1 addition & 1 deletion src/arch/z80/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1447,7 +1447,7 @@ def visit_FUNCTION(self, node):
if local_var.default_value is not None:
r.extend(self.array_default_value(local_var.type_, local_var.default_value))
self.ic_larrd(local_var.offset, q, local_var.size, r, bound_ptrs) # Initializes array bounds
elif local_var.class_ == CLASS.const:
elif local_var.class_ == CLASS.const or local_var.scope == SCOPE.parameter:
continue
else: # Local vars always defaults to 0, so if 0 we do nothing
if local_var.default_value is not None and local_var.default_value != 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, 268394)
'zxnext_asmparse', (346112, 298411)
'zxbparser', (644608, 703160)
'zxbparser', (644608, 704752)
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, 268394)
'zxnext_asmparse', (346112, 298411)
'zxbparser', (644608, 703160)
'zxbparser', (644608, 704752)
14 changes: 9 additions & 5 deletions src/symbols/paramdecl.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,27 @@
# This program is Free Software and is released under the terms of
# the GNU General License
# ----------------------------------------------------------------------
from typing import Optional

import src.api.global_ as gl

from src.api.constants import CLASS
from src.api.constants import SCOPE
from src.api.config import OPTIONS
import src.api.global_ as gl
from .type_ import SymbolBASICTYPE as BasicType
from .var import SymbolVAR
from src.symbols.type_ import SymbolBASICTYPE as BasicType
from src.symbols.var import SymbolVAR
from src.symbols.symbol_ import Symbol


class SymbolPARAMDECL(SymbolVAR):
"""Defines a parameter declaration"""

def __init__(self, varname, lineno, type_=None):
super(SymbolPARAMDECL, self).__init__(varname, lineno, type_=type_, class_=CLASS.var)
def __init__(self, varname: str, lineno: int, type_=None, default_value: Optional[Symbol] = None):
super().__init__(varname, lineno, type_=type_, class_=CLASS.var)
self.byref = OPTIONS.default_byref # By default all params By value (false)
self.offset = None # Set by PARAMLIST, contains positive offset from top of the stack
self.scope = SCOPE.parameter
self.default_value = default_value

@property
def size(self):
Expand Down
33 changes: 28 additions & 5 deletions src/zxbc/zxbparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,9 +411,9 @@ def make_call(id_: str, lineno: int, args: symbols.ARGLIST):
return make_func_call(id_, lineno, args)


def make_param_decl(id_: str, lineno: int, typedef, is_array=False):
def make_param_decl(id_: str, lineno: int, typedef, is_array: bool, default_value: Optional[symbols.SYMBOL] = None):
"""Wrapper that creates a param declaration"""
return SYMBOL_TABLE.declare_param(id_, lineno, typedef, is_array)
return SYMBOL_TABLE.declare_param(id_, lineno, typedef, is_array, default_value)


def make_type(typename, lineno, implicit=False):
Expand Down Expand Up @@ -2931,6 +2931,10 @@ def p_param_decl_list(p):

def p_param_decl_list2(p):
"""param_decl_list : param_decl_list COMMA param_definition"""
if p[1] is not None and p[3] is not None: # No errors in parsing
if p[3].default_value is None and p[1][-1].default_value is not None:
src.api.errmsg.syntax_error_mandatory_param_after_optional(p[3].lineno, p[1][-1].name, p[3].name)

p[0] = make_param_list(p[1], p[3])


Expand Down Expand Up @@ -2981,13 +2985,32 @@ def p_param_def_array(p):


def p_param_def_type(p):
"""param_def : singleid typedef"""
"""param_def : singleid typedef default_arg_value"""
id_: Id = p[1]
typedef = p[2]
if typedef is not None:
src.api.check.check_type_is_explicit(id_.lineno, id_.name, typedef)

p[0] = make_param_decl(id_.name, id_.lineno, typedef)
default_value = make_typecast(typedef, p[3], id_.lineno)
p[0] = make_param_decl(
id_.name,
id_.lineno,
typedef,
is_array=False,
default_value=default_value,
)


def p_param_def_default_arg_value(p):
"""default_arg_value :
| EQ expr
"""
if len(p) == 1:
p[0] = None
return

p[0] = p[2]
return


def p_function_body(p):
Expand Down Expand Up @@ -3307,7 +3330,7 @@ def p_sgn(p):


# ----------------------------------------
# Trigonometrics and LN, EXP, SQR
# Trigonometric and LN, EXP, SQR
# ----------------------------------------
def p_expr_trig(p):
"""bexpr : math_fn bexpr %prec UMINUS"""
Expand Down
8 changes: 4 additions & 4 deletions tests/api/test_arg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@


class TestArgParser(unittest.TestCase):
""" Test argument options from the cmdline
"""
"""Test argument options from the cmdline"""

def setUp(self):
self.parser = parser()

def test_autorun_defaults_to_none(self):
""" Some boolean options, when not specified from the command line
"""Some boolean options, when not specified from the command line
must return None (null) instead of False to preserve .INI saved
value.
"""
options = self.parser.parse_args(["test.bas"])
self.assertIsNone(options.autorun)

def test_loader_defaults_to_none(self):
""" Some boolean options, when not specified from the command line
"""Some boolean options, when not specified from the command line
must return None (null) instead of False to preserve .INI saved
value.
"""
Expand Down
6 changes: 3 additions & 3 deletions tests/api/test_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@


class TestCheck(unittest.TestCase):
""" Tests api.check
"""
"""Tests api.check"""

def test_is_temporary_value_const_string(self):
node = symbols.STRING("Hello world", 1)
self.assertFalse(check.is_temporary_value(node))
Expand All @@ -24,5 +24,5 @@ def test_is_temporary_value_param(self):

def test_is_temporary_value_expr(self):
child = symbols.VAR("a", 1)
node = symbols.BINARY('PLUS', child, child, 1)
node = symbols.BINARY("PLUS", child, child, 1)
self.assertTrue(check.is_temporary_value(node))

0 comments on commit be0b538

Please sign in to comment.