Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ shell = "pytest tests --no-cov"
[[tool.poe.tasks.format]]
help = "Formats code"
shell = """
ruff check --fix
ruff check --select I --fix .
ruff format .
"""
Expand Down
84 changes: 54 additions & 30 deletions src/api/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# See the file CONTRIBUTORS.md for copyright details.
# See https://www.gnu.org/licenses/agpl-3.0.html for details.
# --------------------------------------------------------------------
from typing import Any

from src.api import config, errmsg, global_
from src.api.constants import CLASS, SCOPE
Expand Down Expand Up @@ -82,13 +83,13 @@ def check_is_callable(lineno: int, id_: str) -> bool:


def check_type_is_explicit(lineno: int, id_: str, type_):
assert isinstance(type_, symbols.TYPE)
assert isinstance(type_, symbols.TYPEREF)
if type_.implicit:
if config.OPTIONS.strict:
errmsg.syntax_error_undeclared_type(lineno, id_)


def check_call_arguments(lineno: int, id_: str, args, filename: str):
def check_call_arguments(lineno: int, id_: str, args: symbols.ARGLIST, filename: str) -> bool:
"""Check arguments against function signature.

Checks every argument in a function call against a function.
Expand Down Expand Up @@ -128,8 +129,10 @@ def check_call_arguments(lineno: int, id_: str, args, filename: str):
for param in entry.ref.params:
if param.name in named_args:
continue

if param.default_value is None:
break

arg = symbols.ARGUMENT(param.default_value, lineno=lineno, byref=False, name=param.name)
symbols.ARGLIST.make_node(args, arg)
named_args[arg.name] = arg
Expand All @@ -151,7 +154,7 @@ def check_call_arguments(lineno: int, id_: str, args, filename: str):

if arg.class_ in (CLASS.var, CLASS.array) and param.class_ != arg.class_:
errmsg.error(lineno, f"Invalid argument '{arg.value}'", fname=arg.filename)
return None
return False

if not arg.typecast(param.type_):
return False
Expand Down Expand Up @@ -252,57 +255,62 @@ def check_and_make_label(lbl: str | int | float, lineno):
# ----------------------------------------------------------------------
# Function for checking some arguments
# ----------------------------------------------------------------------
def is_null(*symbols_):
def is_null(*symbols_: Any) -> bool:
"""True if no nodes or all the given nodes are either
None, NOP or empty blocks. For blocks this applies recursively
"""
for sym in symbols_:
if sym is None:
continue

if not isinstance(sym, symbols.SYMBOL):
return False

if sym.token == "NOP":
continue

if sym.token == "BLOCK":
if not is_null(*sym.children):
return False
continue

return False

return True


def is_SYMBOL(token: str, *symbols_: symbols.SYMBOL):
def is_SYMBOL(token: str, *symbols_: symbols.SYMBOL) -> bool:
"""Returns True if ALL the given argument are AST nodes
of the given token (e.g. 'BINARY')
"""
assert all(isinstance(x, symbols.SYMBOL) for x in symbols_)
return all(sym.token == token for sym in symbols_)


def is_LABEL(*p):
def is_LABEL(*p: symbols.SYMBOL) -> bool:
return is_SYMBOL("LABEL", *p)


def is_string(*p):
def is_string(*p: symbols.SYMBOL) -> bool:
"""Returns True if ALL the arguments are AST nodes
containing STRING or string CONSTANTS
"""
return all(is_SYMBOL("STRING", x) or is_const(x) and is_type(Type.string, x) for x in p)


def is_const(*p):
def is_const(*p: symbols.SYMBOL) -> bool:
"""A constant in the program, like CONST a = 5"""
return is_SYMBOL("CONST", *p)


def is_CONST(*p):
def is_CONST(*p: symbols.SYMBOL) -> bool:
"""Not to be confused with the above.
Check it's a CONSTant EXPRession
"""
return is_SYMBOL("CONSTEXPR", *p)


def is_static(*p):
def is_static(*p: symbols.SYMBOL) -> bool:
"""A static value (does not change at runtime)
which is known at compile time
"""
Expand All @@ -313,7 +321,12 @@ def is_number(*p):
"""Returns True if ALL the arguments are AST nodes
containing NUMBER or numeric CONSTANTS
"""
return all(isinstance(i, Symbol) and i.token in ("NUMBER", "CONST") and Type.is_numeric(i.type_) for i in p)
return all(
isinstance(i, Symbol)
and i.token in ("NUMBER", "CONST")
and Type.is_numeric(i.type_.type_ if isinstance(i.type_, symbols.TYPEREF) else i.type_)
for i in p
)


def is_static_str(*p):
Expand All @@ -330,8 +343,8 @@ def is_var(*p):
def is_unsigned(*p):
"""Returns false unless all types in p are unsigned"""
try:
return all(i.type_.is_basic and Type.is_unsigned(i.type_) for i in p)
except Exception:
return all(i.type_.final.is_basic and Type.is_unsigned(i.type_.final) for i in p)
except (Exception,):
pass

return False
Expand All @@ -341,7 +354,7 @@ def is_signed(*p):
"""Returns false unless all types in p are signed"""
try:
return all(i.type_.is_basic and Type.is_signed(i.type_) for i in p)
except Exception:
except (Exception,):
pass

return False
Expand All @@ -350,8 +363,8 @@ def is_signed(*p):
def is_numeric(*p):
"""Returns false unless all elements in p are of numerical type"""
try:
return all(i.type_.is_basic and Type.is_numeric(i.type_) for i in p)
except Exception:
return all(i.type_.final.is_basic and Type.is_numeric(i.type_.final) for i in p)
except (Exception,):
pass

return False
Expand All @@ -361,7 +374,7 @@ def is_type(type_, *p):
"""True if all args have the same type"""
try:
return all(i.type_ == type_ for i in p)
except Exception:
except (Exception,):
pass

return False
Expand All @@ -373,7 +386,7 @@ def is_dynamic(*p): # TODO: Explain this better
"""
try:
return not any(i.scope == SCOPE.global_ and i.is_basic and i.type_ != Type.string for i in p)
except Exception:
except (Exception,):
pass

return False
Expand All @@ -384,11 +397,22 @@ def is_callable(*p):
return all(x.token == "FUNCTION" for x in p)


def is_block_accessed(block):
"""Returns True if a block is "accessed". A block of code is accessed if
it has a LABEL and it is used in a GOTO, GO SUB or @address access
:param block: A block of code (AST node)
:return: True / False depending if it has labels accessed or not
def is_block_accessed(block: Symbol):
"""
Checks if a code block or any of its nested children has been accessed.

This function evaluates whether a specific block has been accessed. If the
block is not directly accessed, it recursively checks its child blocks.

Args:
block: The code block to be evaluated. The block is assumed to have
attributes `accessed` (a boolean indicating if the block has
been accessed) and `children` (an iterable of nested child
blocks).

Returns:
bool: True if the block or at least one of its nested child blocks has
been accessed; False otherwise.
"""
if is_LABEL(block) and block.accessed:
return True
Expand All @@ -401,17 +425,17 @@ def is_temporary_value(node) -> bool:
return node.token not in ("STRING", "VAR") and node.t[0] not in ("_", "#")


def common_type(a: symbols.TYPE | Type | None, b: symbols.TYPE | Type | None) -> symbols.TYPE | Type | None:
def common_type(a: symbols.TYPING, b: symbols.TYPING) -> symbols.TYPE | None:
"""Returns a type which is common for both a and b types.
Returns None if no common types allowed.
"""
if a is None or b is None:
return None
assert isinstance(a, symbols.TYPING)
assert isinstance(b, symbols.TYPING)

if not isinstance(a, symbols.TYPE):
if isinstance(a, symbols.TYPEREF):
a = a.type_

if not isinstance(b, symbols.TYPE):
if isinstance(b, symbols.TYPEREF):
b = b.type_

if a == b: # Both types are the same?
Expand Down Expand Up @@ -449,7 +473,7 @@ def common_type(a: symbols.TYPE | Type | None, b: symbols.TYPE | Type | None) ->
return result


def is_ender(node) -> bool:
def is_ender(node: symbols.SYMBOL) -> bool:
"""Returns whether this node ends a block, that is, the following instruction won't be
executed after this one
"""
Expand All @@ -468,7 +492,7 @@ def is_ender(node) -> bool:
}


def check_class(node, class_: CLASS, lineno: int) -> bool:
def check_class(node: symbols.ID, class_: CLASS, lineno: int) -> bool:
"""Returns whether the given node has CLASS.unknown or the given class_.
It False, it will emit a syntax error
"""
Expand Down
Loading