diff --git a/pyproject.toml b/pyproject.toml index 6e8cc9baa..fa47935b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 . """ diff --git a/src/api/check.py b/src/api/check.py index b728d0511..c9425fcb1 100644 --- a/src/api/check.py +++ b/src/api/check.py @@ -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 @@ -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. @@ -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 @@ -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 @@ -252,26 +255,31 @@ 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') """ @@ -279,30 +287,30 @@ def is_SYMBOL(token: str, *symbols_: symbols.SYMBOL): 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 """ @@ -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): @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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? @@ -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 """ @@ -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 """ diff --git a/src/api/constants.py b/src/api/constants.py index 888a89b95..6f1981b11 100644 --- a/src/api/constants.py +++ b/src/api/constants.py @@ -4,22 +4,21 @@ # See the file CONTRIBUTORS.md for copyright details. # See https://www.gnu.org/licenses/agpl-3.0.html for details. # -------------------------------------------------------------------- +from __future__ import annotations import enum import os from enum import StrEnum -from typing import Final, Optional, Union +from typing import Final -from .decorator import classproperty +from .decorator import cached_function, classproperty # ------------------------------------------------- # Global constants # ------------------------------------------------- -# Path to main ZX Basic compiler executable -ZXBASIC_ROOT = os.path.abspath( - os.path.join(os.path.abspath(os.path.dirname(os.path.abspath(__file__))), os.path.pardir) -) +# Path to main Boriel Basic compiler executable +ZXBASIC_ROOT = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.path.pardir)) # ---------------------------------------------------------------------- @@ -41,18 +40,18 @@ class CLASS(StrEnum): type = "type" # 7 # type @classproperty - def classes(cls): + def classes(cls) -> tuple[CLASS, ...]: return cls.unknown, cls.var, cls.array, cls.function, cls.sub, cls.const, cls.label @classmethod - def is_valid(cls, class_: Union[str, "CLASS"]): + def is_valid(cls, class_: str | CLASS) -> bool: """Whether the given class is valid or not. """ return class_ in set(CLASS) @classmethod - def to_string(cls, class_: "CLASS"): + def to_string(cls, class_: CLASS): assert cls.is_valid(class_) return class_.value @@ -69,7 +68,7 @@ class ARRAY: class TYPE(enum.IntEnum): """Enums primary type constants""" - unknown = 0 + unknown = 0 # Denotes a type that is not yet known byte = 1 ubyte = 2 integer = 3 @@ -80,9 +79,10 @@ class TYPE(enum.IntEnum): float = 8 string = 9 boolean = 10 + nil = 11 @classmethod - def type_size(cls, type_: "TYPE") -> int: + def type_size(cls, type_: TYPE) -> int: type_sizes = { cls.boolean: 1, cls.byte: 1, @@ -95,54 +95,55 @@ def type_size(cls, type_: "TYPE") -> int: cls.float: 5, cls.string: 2, cls.unknown: 0, + cls.nil: 0, } return type_sizes[type_] @classproperty - def types(cls) -> set["TYPE"]: + def types(cls) -> set[TYPE]: return set(TYPE) @classmethod - def size(cls, type_: "TYPE") -> int: + def size(cls, type_: TYPE) -> int: return cls.type_size(type_) @classproperty - def integral(cls) -> set["TYPE"]: + def integral(cls) -> set[TYPE]: return {cls.boolean, cls.byte, cls.ubyte, cls.integer, cls.uinteger, cls.long, cls.ulong} @classproperty - def signed(cls) -> set["TYPE"]: + def signed(cls) -> set[TYPE]: return {cls.byte, cls.integer, cls.long, cls.fixed, cls.float} @classproperty - def unsigned(cls) -> set["TYPE"]: + def unsigned(cls) -> set[TYPE]: return {cls.boolean, cls.ubyte, cls.uinteger, cls.ulong} @classproperty - def decimals(cls) -> set["TYPE"]: + def decimals(cls) -> set[TYPE]: return {cls.fixed, cls.float} @classproperty - def numbers(cls) -> set["TYPE"]: + def numbers(cls) -> set[TYPE]: return cls.integral | cls.decimals @classmethod - def is_valid(cls, type_: "TYPE") -> bool: + def is_valid(cls, type_: TYPE) -> bool: """Whether the given type is valid or not. """ return type_ in cls.types @classmethod - def is_signed(cls, type_: "TYPE") -> bool: + def is_signed(cls, type_: TYPE) -> bool: return type_ in cls.signed @classmethod - def is_unsigned(cls, type_: "TYPE") -> bool: + def is_unsigned(cls, type_: TYPE) -> bool: return type_ in cls.unsigned @classmethod - def to_signed(cls, type_: "TYPE") -> "TYPE": + def to_signed(cls, type_: TYPE) -> TYPE: """Return signed type or equivalent""" if type_ in cls.unsigned: return { @@ -158,12 +159,13 @@ def to_signed(cls, type_: "TYPE") -> "TYPE": return cls.unknown @staticmethod - def to_string(type_: "TYPE") -> str: + def to_string(type_: TYPE) -> str: """Return ID representation (string) of a type""" return type_.name + @cached_function @staticmethod - def to_type(typename: str) -> Optional["TYPE"]: + def to_type(typename: str) -> TYPE | None: """Converts a type ID to name. On error returns None""" for t in TYPE: if t.name == typename: @@ -181,11 +183,11 @@ class SCOPE(str, enum.Enum): parameter = "parameter" @staticmethod - def is_valid(scope: Union[str, "SCOPE"]) -> bool: + def is_valid(scope: str | SCOPE) -> bool: return scope in set(SCOPE) @staticmethod - def to_string(scope: "SCOPE") -> str: + def to_string(scope: SCOPE) -> str: assert SCOPE.is_valid(scope) return scope.value @@ -197,11 +199,11 @@ class CONVENTION(str, enum.Enum): stdcall = "__stdcall__" @staticmethod - def is_valid(convention: Union[str, "CONVENTION"]): + def is_valid(convention: str | CONVENTION): return convention in set(CONVENTION) @staticmethod - def to_string(convention: "CONVENTION"): + def to_string(convention: CONVENTION): assert CONVENTION.is_valid(convention) return convention.value diff --git a/src/api/decorator.py b/src/api/decorator.py index 8c1d57057..e2573df65 100644 --- a/src/api/decorator.py +++ b/src/api/decorator.py @@ -6,6 +6,7 @@ # -------------------------------------------------------------------- from collections.abc import Callable +from functools import wraps class ClassProperty: @@ -24,3 +25,14 @@ def __get__(self, owner_self, owner_cls: type): def classproperty(fget: Callable[[type], Callable]) -> ClassProperty: """Use this function as the decorator in lowercase to follow Python conventions.""" return ClassProperty(fget) + + +def cached_function(f: Callable) -> Callable: + """Decorator to cache the return value of a function.""" + cache = {} + + @wraps(f) + def wrapper(*args, **kwargs): + return cache.setdefault(args, f(*args, **kwargs)) + + return wrapper diff --git a/src/api/exception.py b/src/api/exception.py index a1c2d849b..ca353394a 100644 --- a/src/api/exception.py +++ b/src/api/exception.py @@ -5,6 +5,8 @@ # See https://www.gnu.org/licenses/agpl-3.0.html for details. # -------------------------------------------------------------------- +from __future__ import annotations + __all__ = [ "Error", "InternalError", @@ -57,3 +59,10 @@ class TempAlreadyFreedError(InternalError): def __init__(self, label): super().__init__(f"Label '{label}' already freed") self.label = label + + +class InvalidTypeError(InternalError): + """Raised when a type is invalid.""" + + def __init__(self, msg: str): + super().__init__(msg) diff --git a/src/api/symboltable/symboltable.py b/src/api/symboltable/symboltable.py index 1deaf810a..70e66ba54 100644 --- a/src/api/symboltable/symboltable.py +++ b/src/api/symboltable/symboltable.py @@ -58,7 +58,7 @@ def __init__(self): # Initialize canonical types for type_ in TYPE.types: - self.basic_types[type_] = self.declare_type(symbols.BASICTYPE(type_)) + self.basic_types[type_] = self.declare_type(name=type_.name, lineno=0, type_=symbols.BASICTYPE(type_)) @property def current_scope(self) -> Scope: @@ -91,16 +91,18 @@ def get_existing_entry(self, id_: str, scope: Scope | None = None) -> symbols.ID return result - def declare(self, id_: str, lineno: int, entry: symbols.ID) -> None | symbols.ID | symbols.TYPE: + def declare(self, id_: str, lineno: int, entry: symbols.ID | symbols.TYPE) -> None | symbols.ID | symbols.TYPE: """Check there is no 'id' already declared in the current scope, and creates and returns it. Otherwise, returns None, and the caller function raises the syntax/semantic error. Parameter entry is the SymbolVAR, SymbolVARARRAY, etc. instance - The entry 'declared' field is leave untouched. Setting it if on + The entry 'declared' field is left untouched. Setting it if on behalf of the caller. """ + assert isinstance(entry, symbols.ID | symbols.TYPE) + id2 = id_ - type_ = entry.type_ + type_ = entry.type_ if isinstance(entry, symbols.ID) else entry if id2[-1] in DEPRECATED_SUFFIXES: id2 = id2[:-1] # Remove it @@ -174,7 +176,15 @@ def check_is_declared( return False return True - def check_is_undeclared(self, id_: str, lineno: int, classname="identifier", scope=None, show_error=False) -> bool: + def check_is_undeclared( + self, + id_: str, + lineno: int, + classname: str = "identifier", + scope: Scope | None = None, + *, + show_error=False, + ) -> bool: """The reverse of the above. Check the given identifier is not already declared. Returns True @@ -328,8 +338,9 @@ def access_id( id_: str, lineno: int, scope=None, - default_type=None, - default_class=CLASS.unknown, + default_type: symbols.TYPING | None = None, + default_class: CLASS = CLASS.unknown, + *, ignore_explicit_flag=False, ): """Access a symbol by its identifier and checks if it exists. @@ -350,7 +361,7 @@ def access_id( default_type = symbols.TYPEREF(self.basic_types[global_.DEFAULT_IMPLICIT_TYPE], lineno, implicit=True) result = self.declare_safe( - id_, lineno, entry=symbols.ID(id_, lineno, type_=default_type, class_=default_class) + id_, lineno, entry=symbols.ID(id_, lineno, type_ref=default_type, class_=default_class) ) return result @@ -358,7 +369,7 @@ def access_id( # update its type. if default_type is not None and result.type_ == self.basic_types[TYPE.unknown]: if default_type == self.basic_types[TYPE.boolean]: - default_type = self.basic_types[TYPE.ubyte] + default_type = symbols.TYPEREF(self.basic_types[TYPE.ubyte], 0) result.type_ = default_type warning_implicit_type(lineno, id_, default_type.name) @@ -475,7 +486,9 @@ def access_label(self, id_: str, lineno: int, scope: Scope | None = None): return result - def declare_variable(self, id_: str, lineno: int, type_, default_value=None, class_: CLASS = CLASS.var): + def declare_variable( + self, id_: str, lineno: int, type_: symbols.TYPEREF, default_value=None, class_: CLASS = CLASS.var + ): """Like the above, but checks that entry.declared is False. Otherwise, raises an error. @@ -500,7 +513,7 @@ def declare_variable(self, id_: str, lineno: int, type_, default_value=None, cla entry = self.get_entry(id_, scope=self.current_scope) if entry is None: - entry = self.declare(id_, lineno, symbols.ID(name=id_, lineno=lineno, type_=type_)) + entry = self.declare(id_, lineno, symbols.ID(name=id_, lineno=lineno, type_ref=type_)) assert entry is not None if entry.class_ == CLASS.unknown: @@ -522,7 +535,8 @@ def declare_variable(self, id_: str, lineno: int, type_, default_value=None, cla if entry.type_ != type_: if not type_.implicit and entry.type_ is not None: syntax_error( - lineno, "'%s' suffix is for type '%s' but it was declared as '%s'" % (id_, entry.type_, type_) + lineno, + f"'{id_!s}' suffix is for type '{entry.type_.name}' but it was declared as '{type_.name}'", ) return None @@ -533,7 +547,7 @@ def declare_variable(self, id_: str, lineno: int, type_, default_value=None, cla if default_value is not None and entry.type_ != default_value.type_: if check.is_number(default_value): - default_value = symbols.TYPECAST.make_node(entry.type_, default_value, lineno) + default_value = symbols.TYPECAST.make_node(entry.type_.type_, default_value, lineno) if default_value is None: return None else: @@ -546,23 +560,27 @@ def declare_variable(self, id_: str, lineno: int, type_, default_value=None, cla return entry - def declare_type(self, type_): - """Declares a type. + def declare_type(self, name: str, lineno: int, type_: symbols.TYPE) -> symbols.TYPE | None: + """Declares a type. The Type object must be already instantiated, as it's not + a normal ID Symbol. + Checks its name is not already used in the current scope, and that it's not a basic type. Returns the given type_ Symbol, or None on error. """ assert isinstance(type_, symbols.TYPE) + # Checks it's not a basic type if not type_.is_basic and type_.name.lower() in TYPE.TYPE_NAMES.values(): syntax_error(type_.lineno, "'%s' is a basic type and cannot be redefined" % type_.name) return None - if not self.check_is_undeclared(type_.name, type_.lineno, scope=self.current_scope, show_error=True): + if not self.check_is_undeclared(name, lineno, scope=self.current_scope, show_error=True): return None entry = self.declare(type_.name, type_.lineno, type_) + assert isinstance(entry, symbols.TYPE) return entry def declare_const(self, id_: str, lineno: int, type_, default_value): @@ -624,11 +642,17 @@ def declare_label(self, id_: str, lineno: int) -> symbols.ID | None: self.move_to_global_scope(id_) # Labels are always global # TODO: not in the future entry.declared = True - entry.type_ = self.basic_types[global_.PTR_TYPE] + entry.type_ = symbols.TYPEREF(self.basic_types[global_.PTR_TYPE], lineno, implicit=False) return entry def declare_param( - self, id_: str, lineno: int, type_=None, is_array=False, default_value: Symbol | None = None + self, + id_: str, + lineno: int, + type_: symbols.TYPEREF | None = None, + default_value: Symbol | None = None, + *, + is_array: bool = False, ) -> symbols.ID | None: """Declares a parameter Check if entry.declared is False. Otherwise, raises an error. @@ -641,23 +665,35 @@ def declare_param( syntax_error_cannot_define_default_array_argument(lineno) return None - entry = self.declare_safe(id_, lineno, symbols.ID(name=id_, lineno=lineno, type_=type_)).to_vararray( - bounds=symbols.BOUNDLIST() - ) + entry = self.declare_safe( + id_, + lineno, + symbols.ID(name=id_, lineno=lineno, type_ref=type_), + ).to_vararray(bounds=symbols.BOUNDLIST()) else: - entry = self.declare_safe(id_, lineno, symbols.ID(name=id_, lineno=lineno, type_=type_)).to_var( - default_value=default_value - ) + entry = self.declare_safe( + id_, + lineno, + symbols.ID(name=id_, lineno=lineno, type_ref=type_), + ).to_var(default_value=default_value) entry.scope = SCOPE.parameter entry.declared = True if entry.type_.implicit: - warning_implicit_type(lineno, id_, type_) + warning_implicit_type(lineno, id_, type_.name) return entry - def declare_array(self, id_: str, lineno: int, type_, bounds, default_value=None, addr=None): + def declare_array( + self, + id_: str, + lineno: int, + type_: symbols.TYPEREF, + bounds: symbols.BOUNDLIST, + default_value=None, + addr: str | None = None, + ): """Declares an array in the symbol table (VARARRAY). Error if already exists. The optional parameter addr specifies if the array elements must be placed at an specific @@ -671,7 +707,7 @@ def declare_array(self, id_: str, lineno: int, type_, bounds, default_value=None entry = self.get_entry(id_, self.current_scope) if entry is None: - entry = self.declare(id_, lineno, symbols.ID(name=id_, lineno=lineno, type_=type_)) + entry = self.declare(id_, lineno, symbols.ID(name=id_, lineno=lineno, type_ref=type_)) assert entry is not None if not entry.declared: @@ -699,7 +735,7 @@ def declare_array(self, id_: str, lineno: int, type_, bounds, default_value=None type_ = entry.type_ if type_.implicit: - warning_implicit_type(lineno, id_, type_) + warning_implicit_type(lineno, id_, type_.name) if entry.class_ != CLASS.array: entry = symbols.ID.to_vararray(entry, bounds) @@ -740,7 +776,7 @@ def declare_func(self, id_: str, lineno: int, type_=None, class_=CLASS.function) entry.mangled = f"{self.current_namespace}_{entry.name}" # HINT: mangle for nexted scopes else: - entry = self.declare(id_, lineno, symbols.ID(id_, lineno, type_=type_).to_function(class_=class_)) + entry = self.declare(id_, lineno, symbols.ID(id_, lineno, type_ref=type_).to_function(class_=class_)) assert entry.token == "FUNCTION" if entry.forwarded: @@ -818,7 +854,7 @@ def __getitem__(self, level: int): return self.table[level] def __iter__(self): - """Iterates through scopes, from current one (innermost) to global + """Iterates through scopes, from the current one (innermost) to global (outermost) """ for scope in self.table[::-1]: diff --git a/src/api/typing/__init__.py b/src/api/typing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/api/typing/system.py b/src/api/typing/system.py new file mode 100644 index 000000000..5883c66a5 --- /dev/null +++ b/src/api/typing/system.py @@ -0,0 +1,63 @@ +# -------------------------------------------------------------------- +# SPDX-License-Identifier: AGPL-3.0-or-later +# © Copyright 2008-2024 José Manuel Rodríguez de la Rosa and contributors. +# See the file CONTRIBUTORS.md for copyright details. +# See https://www.gnu.org/licenses/agpl-3.0.html for details. +# -------------------------------------------------------------------- + +from src.api import global_ as gl +from src.api.constants import TYPE + + +class Type: + """Represents a type in the system.""" + + def __init__(self, name: str, size: int): + self._name = name + self._size = size + + @property + def name(self): + return self._name + + @property + def size(self) -> int: + return self._size + + @property + def is_primitive(self) -> bool: + return True + + +BASIC_TYPES: dict[str, Type] = {x.name: Type(x.name, TYPE.size(x)) for x in TYPE.types} + + +class CompositeType(Type): + def __init__(self, name: str, size: int): + super().__init__(name, size) + + @property + def is_primitive(self) -> bool: + return False + + +class AliasType(CompositeType): + def __init__(self, name: str, base: Type): + super().__init__(name, base.size) + self._base = base + + @property + def base(self) -> Type: + return self._base + + +class PointerType(CompositeType): + def __init__(self, name: str, base: Type): + super().__init__(name, TYPE.size(gl.PTR_TYPE)) + self._base = base + + +class ArrayType(CompositeType): + def __init__(self, name: str, base: Type): + super().__init__(name, base.size) + self._base = base diff --git a/src/arch/z80/visitor/translator.py b/src/arch/z80/visitor/translator.py index b01bd9da0..d6eeddc5c 100644 --- a/src/arch/z80/visitor/translator.py +++ b/src/arch/z80/visitor/translator.py @@ -1017,12 +1017,16 @@ def loop_cont_label(self, loop_type): raise InvalidLoopError(loop_type) @classmethod - def default_value(cls, type_: symbols.TYPE, expr) -> list[str]: # TODO: This function must be moved to api.xx + def default_value(cls, type_: symbols.TYPING, expr: symbols.EXPR) -> list[str]: + # TODO: This function must be moved to api.xx """Returns a list of bytes (as hexadecimal 2 char string)""" - assert isinstance(type_, symbols.TYPE) + assert isinstance(type_, symbols.TYPING) assert type_.is_basic assert check.is_static(expr) + if isinstance(type_, symbols.TYPEREF): + type_ = type_.final + if expr.token in ("CONSTEXPR", "CONST"): # a constant expression like @label + 1 if type_ in (cls.TYPE(TYPE.float), cls.TYPE(TYPE.string)): error(expr.lineno, f"Can't convert non-numeric value to {type_.name} at compile time") diff --git a/src/arch/z80/visitor/translator_inst_visitor.py b/src/arch/z80/visitor/translator_inst_visitor.py index 306c66512..393139c27 100644 --- a/src/arch/z80/visitor/translator_inst_visitor.py +++ b/src/arch/z80/visitor/translator_inst_visitor.py @@ -26,8 +26,8 @@ def emit(self, *args: str) -> None: self.backend.MEMORY.append(quad) @staticmethod - def TSUFFIX(type_: TYPE | sym.TYPEREF | sym.BASICTYPE) -> str: - assert isinstance(type_, sym.TYPE) or TYPE.is_valid(type_) + def TSUFFIX(type_: TYPE | sym.TYPING) -> str: + assert isinstance(type_, sym.TYPING) or TYPE.is_valid(type_) _TSUFFIX = { TYPE.byte: I8_t, @@ -42,17 +42,16 @@ def TSUFFIX(type_: TYPE | sym.TYPEREF | sym.BASICTYPE) -> str: TYPE.boolean: BOOL_t, } - if isinstance(type_, sym.TYPEREF): + if isinstance(type_, sym.TYPEREF | sym.BASICTYPE): type_ = type_.final assert isinstance(type_, sym.BASICTYPE) + type_ = TYPE.to_type(type_.name) - if isinstance(type_, sym.BASICTYPE): - return _TSUFFIX[type_.type_] - + assert isinstance(type_, TYPE) return _TSUFFIX[type_] @classmethod - def _no_bool(cls, type_: TYPE | sym.TYPEREF | sym.BASICTYPE) -> str: + def _no_bool(cls, type_: TYPE | sym.TYPING | sym.BASICTYPE) -> str: """Returns the corresponding type suffix except for bool which maps to U8_t""" return cls.TSUFFIX(type_) if cls.TSUFFIX(type_) != BOOL_t else U8_t diff --git a/src/symbols/argument.py b/src/symbols/argument.py index 7e3fb037e..28e0ca47b 100644 --- a/src/symbols/argument.py +++ b/src/symbols/argument.py @@ -9,6 +9,7 @@ from src.api.config import OPTIONS from src.api.constants import CLASS, SCOPE from src.symbols.symbol_ import Symbol +from src.symbols.type_ import SymbolTYPE, SymbolTYPEREF from src.symbols.typecast import SymbolTYPECAST @@ -80,10 +81,13 @@ def __eq__(self, other): return self.value == other.value return self.value == other - def typecast(self, type_): + def typecast(self, type_: SymbolTYPE | SymbolTYPEREF): """Test type casting to the argument expression. On success changes the node value to the new typecast, and returns True. On failure, returns False, and the node value is set to None. """ + if isinstance(type_, SymbolTYPEREF): + type_ = type_.type_ + self.value = SymbolTYPECAST.make_node(type_, self.value, self.lineno) return self.value is not None diff --git a/src/symbols/arrayaccess.py b/src/symbols/arrayaccess.py index d6c79bc6f..d8c12e9e7 100644 --- a/src/symbols/arrayaccess.py +++ b/src/symbols/arrayaccess.py @@ -14,6 +14,7 @@ from src.symbols.arglist import SymbolARGLIST from src.symbols.call import SymbolCALL from src.symbols.id_ import SymbolID +from src.symbols.type_ import Type from src.symbols.typecast import SymbolTYPECAST as TYPECAST @@ -31,9 +32,10 @@ class SymbolARRAYACCESS(SymbolCALL): Arglist a SymbolARGLIST instance. """ - def __init__(self, entry, arglist: SymbolARGLIST, lineno: int, filename: str): + def __init__(self, entry: SymbolID, arglist: SymbolARGLIST, lineno: int, filename: str): super().__init__(entry, arglist, lineno, filename) - assert all(gl.BOUND_TYPE == x.type_.type_ for x in arglist), "Invalid type for array index" + bound_type = Type.by_name(gl.BOUND_TYPE.name) + assert all(bound_type == x.type_.final for x in arglist), "Invalid type for array index" self.entry.ref.is_dynamically_accessed = True @property diff --git a/src/symbols/arraydecl.py b/src/symbols/arraydecl.py index a409dbd50..5ea367244 100644 --- a/src/symbols/arraydecl.py +++ b/src/symbols/arraydecl.py @@ -5,13 +5,14 @@ # See https://www.gnu.org/licenses/agpl-3.0.html for details. # -------------------------------------------------------------------- +from .id_.interface import SymbolIdABC as SymbolID from .symbol_ import Symbol class SymbolARRAYDECL(Symbol): """Defines an Array declaration""" - def __init__(self, entry): + def __init__(self, entry: SymbolID): super().__init__(entry) @property diff --git a/src/symbols/binary.py b/src/symbols/binary.py index 1d3c256fc..624b5f83f 100644 --- a/src/symbols/binary.py +++ b/src/symbols/binary.py @@ -23,7 +23,7 @@ def __init__(self, operator: str, left: Symbol, right: Symbol, lineno: int, type super().__init__(left, right) self.lineno = lineno self.operator = operator - self.type_ = type_ if type_ is not None else check.common_type(left, right) + self.type_ = type_ if type_ is not None else check.common_type(left.type_, right.type_) self.func = func # Will be used for constant folding at later stages if not None @property @@ -98,7 +98,7 @@ def make_node(cls, operator, left, right, lineno, func=None, type_=None): b = SymbolTYPECAST.make_node(TYPE.ubyte, b, lineno) # Check for constant non-numeric operations - c_type = check.common_type(a, b) # Resulting operation type or None + c_type = check.common_type(a.type_, b.type_) # Resulting operation type or None if TYPE.is_numeric(c_type): # there must be a common type for a and b if ( check.is_numeric(a, b) @@ -129,8 +129,8 @@ def make_node(cls, operator, left, right, lineno, func=None, type_=None): c_type = a.type_ # Will give an error based on the fist operand if operator not in ("SHR", "SHL"): - a = SymbolTYPECAST.make_node(c_type, a, lineno) - b = SymbolTYPECAST.make_node(c_type, b, lineno) + a = SymbolTYPECAST.make_node(c_type.final, a, lineno) + b = SymbolTYPECAST.make_node(c_type.final, b, lineno) if a is None or b is None: return None diff --git a/src/symbols/builtin.py b/src/symbols/builtin.py index f23e24dc1..592cf16d7 100644 --- a/src/symbols/builtin.py +++ b/src/symbols/builtin.py @@ -4,6 +4,8 @@ # See the file CONTRIBUTORS.md for copyright details. # See https://www.gnu.org/licenses/agpl-3.0.html for details. # -------------------------------------------------------------------- +from collections.abc import Callable +from typing import Self from src.api import check @@ -62,7 +64,14 @@ def size(self): return self.type_.size @classmethod - def make_node(cls, lineno, fname, func=None, type_=None, *operands): + def make_node( + cls, + lineno: int, + fname: str, + func: Callable | None = None, + type_: SymbolTYPE | None = None, + *operands, + ) -> Self | SymbolNUMBER: """Creates a node for a unary operation. E.g. -x or LEN(a$) Parameters: diff --git a/src/symbols/funcdecl.py b/src/symbols/funcdecl.py index 3757abd92..4a7d763e1 100644 --- a/src/symbols/funcdecl.py +++ b/src/symbols/funcdecl.py @@ -4,12 +4,14 @@ # See the file CONTRIBUTORS.md for copyright details. # See https://www.gnu.org/licenses/agpl-3.0.html for details. # -------------------------------------------------------------------- +from typing import Self import src.api.symboltable.scope from src.api import global_ from src.api.constants import CLASS from src.symbols.id_ import SymbolID from src.symbols.symbol_ import Symbol +from src.symbols.type_ import SymbolTYPEREF class SymbolFUNCDECL(Symbol): @@ -68,7 +70,7 @@ def mangled(self): return self.entry.mangled @classmethod - def make_node(cls, func_name: str, lineno: int, class_: CLASS, type_=None): + def make_node(cls, func_name: str, lineno: int, class_: CLASS, type_: SymbolTYPEREF | None = None) -> Self | None: """This will return a node with the symbol as a function.""" assert class_ in (CLASS.sub, CLASS.function) entry = global_.SYMBOL_TABLE.declare_func(func_name, lineno, type_=type_, class_=class_) diff --git a/src/symbols/id_/_id.py b/src/symbols/id_/_id.py index ca9fc8836..e233a9851 100644 --- a/src/symbols/id_/_id.py +++ b/src/symbols/id_/_id.py @@ -16,7 +16,7 @@ from src.symbols.id_ import ref from src.symbols.id_.interface import SymbolIdABC from src.symbols.symbol_ import Symbol -from src.symbols.type_ import SymbolTYPE +from src.symbols.type_ import SymbolTYPEREF, Type # ---------------------------------------------------------------------- # Identifier Symbol object @@ -47,23 +47,23 @@ def __init__( name: str, lineno: int, filename: str = None, - type_: SymbolTYPE | None = None, + type_ref: SymbolTYPEREF | None = None, class_: CLASS = CLASS.unknown, ): - super().__init__(name=name, lineno=lineno, filename=filename, type_=type_, class_=class_) + super().__init__(name=name, lineno=lineno, filename=filename, type_=type_ref, class_=class_) assert class_ in (CLASS.const, CLASS.label, CLASS.var, CLASS.unknown) - self.name = name # This value will be modified later removing the trailing sigil ($) if used. + self.name = name # This value will be modified later, removing the trailing sigil ($) if used. self.original_name = name # This value will always contain the original name, preserving the sigil if used self.filename = global_.FILENAME if filename is None else filename # In which file was first used self.lineno = lineno # In which line was first used self.mangled = f"{global_.MANGLE_CHR}{name}" # This value will be overridden later self.declared = False # if explicitly declared (DIM var AS ) - self.type_ = type_ # if None => unknown type (yet) + self.type_ = type_ref # Typing annotation. If None => unknown type (yet) self.caseins = OPTIONS.case_insensitive # Whether this ID is case-insensitive or not self.scope = SCOPE.global_ # One of 'global', 'parameter', 'local' self.scope_ref: Any | None = None # TODO: type Scope | None # Scope object this ID lives in - self.addr = None # If not None, the address of this symbol in memory (string, cam be an expr like "_addr1 + 2") + self.addr: str | None = None # If set, the address of this symbol in memory (can be an expr like "_addr1 + 2") self._ref: ref.SymbolRef = ref.SymbolRef(self) self.has_address: bool | None = None # Whether this ID exist in memory or not @@ -95,12 +95,12 @@ def t(self) -> str: return self._ref.t @property - def type_(self): + def type_(self) -> SymbolTYPEREF | None: return self._type @type_.setter - def type_(self, value: SymbolTYPE | None): - assert value is None or isinstance(value, SymbolTYPE) + def type_(self, value: SymbolTYPEREF | None): + assert isinstance(value, SymbolTYPEREF | None) self._type = value @property @@ -122,6 +122,11 @@ def to_var(self, default_value: Symbol | None = None): assert self.has_address or self.has_address is None self.has_address = True + if self.type_ is None: + if default_value is not None and default_value.type_ is not None: + self.type_ = SymbolTYPEREF(default_value.type_.final, lineno=self.lineno) + else: + self.type_ = SymbolTYPEREF(Type.unknown, lineno=self.lineno) if self.class_ == CLASS.var: self._ref.default_value = default_value diff --git a/src/symbols/id_/interface.py b/src/symbols/id_/interface.py index dc99fda37..fbe9ae467 100644 --- a/src/symbols/id_/interface.py +++ b/src/symbols/id_/interface.py @@ -9,7 +9,7 @@ from src.api.constants import CLASS, SCOPE from src.symbols.symbol_ import Symbol -from src.symbols.type_ import SymbolTYPE +from src.symbols.type_ import SymbolTYPEREF class SymbolIdABC(Symbol, ABC): @@ -26,14 +26,14 @@ def __init__( name: str, lineno: int, filename: str = None, - type_: SymbolTYPE | None = None, + type_: SymbolTYPEREF | None = None, class_: CLASS = CLASS.unknown, ): super().__init__() @property @abstractmethod - def type_(self) -> SymbolTYPE | None: + def type_(self) -> SymbolTYPEREF | None: pass @property diff --git a/src/symbols/id_/ref/varref.py b/src/symbols/id_/ref/varref.py index 36538833d..482916945 100644 --- a/src/symbols/id_/ref/varref.py +++ b/src/symbols/id_/ref/varref.py @@ -18,7 +18,7 @@ class VarRef(SymbolRef): def __init__(self, parent: SymbolID, default_value: Symbol | None = None): super().__init__(parent) - self.default_value = default_value # If defined, it be initialized with this value (Arrays = List of Bytes) + self.default_value = default_value # If defined, it is initialized with this value (Arrays = List of Bytes) self.offset: str | None = None # If local variable or parameter, +/- offset from top of the stack self.byref = False self.alias = None # If not None, this var is an alias of another diff --git a/src/symbols/sym.py b/src/symbols/sym.py index 8f93a0239..faf9d5776 100644 --- a/src/symbols/sym.py +++ b/src/symbols/sym.py @@ -35,6 +35,9 @@ from src.symbols.unary import SymbolUNARY as UNARY from src.symbols.vardecl import SymbolVARDECL as VARDECL +EXPR = CONSTEXPR | FUNCCALL | BINARY | BUILTIN | UNARY | ID | NUMBER | STRING | STRSLICE | ARRAYLOAD +TYPING = TYPE | TYPEREF + __all__ = [ "ARGLIST", "ARGUMENT", @@ -50,6 +53,7 @@ "BUILTIN", "CALL", "CONSTEXPR", + "EXPR", "FUNCCALL", "FUNCDECL", "ID", @@ -63,6 +67,7 @@ "TYPE", "TYPECAST", "TYPEREF", + "TYPING", "UNARY", "VARDECL", ] diff --git a/src/symbols/type_.py b/src/symbols/type_.py index be13c7f8e..d463d102a 100644 --- a/src/symbols/type_.py +++ b/src/symbols/type_.py @@ -4,29 +4,36 @@ # See the file CONTRIBUTORS.md for copyright details. # See https://www.gnu.org/licenses/agpl-3.0.html for details. # -------------------------------------------------------------------- +from __future__ import annotations +from typing import Any, Final + +from src.api import global_ as gl from src.api.config import OPTIONS from src.api.constants import CLASS, TYPE from src.api.decorator import classproperty from .symbol_ import Symbol +__all__: Final[tuple[str]] = "SymbolTYPE", "SymbolBASICTYPE", "SymbolTYPEALIAS", "SymbolTYPEREF", "Type" + class SymbolTYPE(Symbol): """A Type definition. Defines a type, - both user defined or basic ones. + both user-defined or basic ones. """ - def __init__(self, name: str, lineno: int, *children): + def __init__(self, name: str, namespace: str, size: int, lineno: int, filename: str = ""): # All children (if any) must be SymbolTYPE - assert all(isinstance(x, SymbolTYPE) for x in children) - super().__init__(*children) + super().__init__() self.name = name # typename + self.namespace = namespace # Namespace where this type was defined + self._size = size self.lineno = lineno # The line the type was defined. Line 0 = basic type - self.final = self # self.final always return the original aliased type (if this type is an alias) self.caseins = OPTIONS.case_insensitive # Whether this ID is case-insensitive or not self.class_ = CLASS.type self.accessed = False # Whether this type has been used or not + self.filename = filename or gl.FILENAME # Filename where this type was defined def __repr__(self): return "%s(%s)" % (self.token, str(self)) @@ -34,190 +41,181 @@ def __repr__(self): def __str__(self): return self.name - @property - def size(self): - return sum(x.size for x in self.children) + def __hash__(self): + return id(self) @property - def is_basic(self): - """Whether this is a basic (canonical) type or not.""" - if len(self.children) == 1: - return self.children[0].is_basic - - return False + def final(self) -> SymbolTYPE: + """For any aliased type, return the final type.""" + return self @property - def is_signed(self): - if self is not self.final: - return self.final.is_signed - - if len(self.children) != 1: - return False + def canonical_name(self) -> str: + canonical_name = f"{self.namespace}{gl.NAMESPACE_SEPARATOR}{self.name}" + return canonical_name.lower() if self.caseins else canonical_name - return self.children[0].is_signed + @property + def is_primitive(self) -> bool: + """Whether this is a primitive type or not.""" + return True # Subclasses must override this @property - def is_dynamic(self): - """True if this type uses dynamic (Heap) memory. - e.g. strings or dynamic arrays - """ - if self is not self.final: - return self.final.is_dynamic + def size(self) -> int: + return self._size - return any(x.is_dynamic for x in self.children) + @property + def is_basic(self) -> bool: + """Whether this is a basic (canonical) type or not.""" + return False @property - def is_alias(self): + def is_alias(self) -> bool: """Whether this is an alias of another type or not.""" return False - def __eq__(self, other): - assert isinstance(other, SymbolTYPE), f"Invalid operand '{other}':{type(other)}" - - if self is not self.final: - return self.final == other - - other = other.final # remove alias - - if other.is_basic: - return other == self - - if len(self.children) != len(other.children): - if len(self.children) == 1 and not other.children: - return self.children[0] == other - if len(other.children) == 1 and not self.children: - return other.children[0] == self - return False - - return all(i == j for i, j in zip(self.children, other.children)) - - def __ne__(self, other): - assert isinstance(other, SymbolTYPE) - return not (self == other) - - def __nonzero__(self): - return self.__bool__() + @property + def is_dynamic(self) -> bool: + """Whether this type is dynamic in memory (allocated in the HEAP).""" + return False - def __bool__(self): - if self is not self.final: - return bool(self.final) + def __eq__(self, other: Any) -> bool: + """Nominal equality""" + if not isinstance(other, SymbolTYPE): + return NotImplemented - return any(x for x in self.children) + return self.canonical_name == other.canonical_name class SymbolBASICTYPE(SymbolTYPE): """Defines a basic type (Ubyte, Byte, etc.) Basic (default) types are defined upon start and are case-insensitive. - If name is None or '', default typename from TYPES.to_string will be used. """ - def __init__(self, type_: TYPE, name: str = None): + TYPES: Final[dict[TYPE, SymbolBASICTYPE]] = {} + + def __init__(self, type_: TYPE): """type_ = Internal representation (e.g. TYPE.ubyte)""" assert TYPE.is_valid(type_) - if not name: - name = TYPE.to_string(type_) + name = TYPE.to_string(type_) - super().__init__(name, 0) - self.type_ = type_ + super().__init__(name=name, size=TYPE.size(type_), namespace="", lineno=0, filename="") + self._type = type_ - @property - def size(self): - return TYPE.size(self.type_) + def __new__(cls, *args, **kwargs) -> SymbolBASICTYPE: + """Ensures the same basic type is returned for the same TYPE + if it's already been created.""" + return cls.TYPES.setdefault(args[0], super().__new__(cls)) @property - def is_basic(self): + def is_basic(self) -> bool: """Whether this is a basic (canonical) type or not.""" return True @property - def is_signed(self): - return TYPE.is_signed(self.type_) + def is_signed(self) -> bool: + return TYPE.is_signed(self._type) def to_signed(self): """Returns another instance with the signed equivalent of this type. """ - return SymbolBASICTYPE(TYPE.to_signed(self.type_)) + return SymbolBASICTYPE(TYPE.to_signed(self._type)) @property - def is_dynamic(self): - return self.type_ == TYPE.string - - def __hash__(self): - return hash(self.type_) - - def __eq__(self, other): - if self is not self.final: - return self.final == other - - other = other.final # remove alias - if other.is_basic: # for both basic types, just compare - return self.type_ == other.type_ - - assert other.children # must be not empty - if len(other.children) > 1: # Different size - return False + def is_dynamic(self) -> bool: + return self._type == TYPE.string - return self == other.children[0] - - def __bool__(self): - return self.type_ != TYPE.unknown + def __bool__(self) -> bool: + return self._type != TYPE.unknown class SymbolTYPEALIAS(SymbolTYPE): - """Defines a type which is alias of another""" + """Defines a type which is an alias of another""" - def __init__(self, name, lineno: int, alias: SymbolTYPE): + def __init__(self, name: str, namespace: str, alias: SymbolTYPE, lineno: int, filename: str = ""): assert isinstance(alias, SymbolTYPE) - super().__init__(name, lineno, alias) - self.final = alias.final + filename = filename or gl.FILENAME + super().__init__(name=name, namespace=namespace, lineno=lineno, size=alias.size, filename=filename) + self._base = alias @property - def is_alias(self): + def is_alias(self) -> bool: """Whether this is an alias of another type or not.""" return True @property - def size(self): - return self.final.size + def final(self) -> SymbolTYPE: + return self._base.final @property - def is_basic(self): - return self.final.is_basic + def is_basic(self) -> bool: + return self._base.is_basic @property - def alias(self): - return self.children[0] + def base(self) -> SymbolTYPE: + return self._base @property def to_signed(self): assert self.is_basic - return self.final.to_signed() + return self._base.to_signed() + + @property + def is_dynamic(self) -> bool: + return self._base.is_dynamic -class SymbolTYPEREF(SymbolTYPEALIAS): - """Creates a Type reference or usage. - Eg. DIM a As Integer - In this case, the Integer type is accessed. - It's an alias type, containing just the - original Type definition (SymbolTYPE), the - the lineno it is currently being accessed, - and if it was implicitly inferred or explicitly declared. +class SymbolTYPEREF(Symbol): + """ + Describes a Type annotation (file, line number). + Eg. DIM a As Integer + In this case, the variable a is annotated with the type Integer. """ - def __init__(self, type_: SymbolTYPE, lineno: int, implicit: bool = False): + def __init__(self, type_: SymbolTYPE, lineno: int, filename: str = "", *, implicit: bool = False): assert isinstance(type_, SymbolTYPE) - super().__init__(type_.name, lineno, type_) - self.implicit = implicit + super().__init__(type_) + self.implicit = implicit # Whether this annotation was implicit or not + self.lineno = lineno # Line number where this annotation was defined + self.filename = filename or gl.FILENAME # Filename where this annotation was defined + + @property + def type_(self) -> SymbolTYPE: + return self.children[0] + + @property + def name(self) -> str: + return self.type_.name + + @property + def size(self) -> int: + return self.type_.size def to_signed(self): - assert self.is_basic - return self.final.to_signed() + type_ = self.type_ + assert isinstance(type_, SymbolBASICTYPE) + return type_.to_signed() @property - def type_(self): - assert self.is_basic - return self.final.type_ + def is_basic(self) -> bool: + return self.type_.is_basic + + @property + def is_dynamic(self) -> bool: + return self.type_.is_dynamic + + @property + def final(self) -> SymbolTYPE: + return self.type_.final + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, SymbolTYPEREF | SymbolTYPE): + raise NotImplementedError(f"Invalid operand '{other}':{type(other)}") + + if isinstance(other, SymbolTYPEREF): + return self.type_ == other.type_ + + return self.type_ == other class Type: @@ -241,14 +239,12 @@ class Type: _by_name = {x.name: x for x in types} - @staticmethod - def size(t: SymbolTYPE) -> int: - assert isinstance(t, SymbolTYPE) + @classmethod + def size(cls, t: SymbolTYPE) -> int: return t.size - @staticmethod - def to_string(t: SymbolTYPE) -> str: - assert isinstance(t, SymbolTYPE) + @classmethod + def to_string(cls, t: SymbolTYPE) -> str: return t.name @classmethod @@ -279,27 +275,27 @@ def numbers(cls) -> set[SymbolBASICTYPE]: @classmethod def is_numeric(cls, t: SymbolTYPE) -> bool: assert isinstance(t, SymbolTYPE) - return t.final in cls.numbers + return t.is_basic and t.final in cls.numbers @classmethod def is_signed(cls, t: SymbolTYPE) -> bool: assert isinstance(t, SymbolTYPE) - return t.final in cls.signed + return t.is_basic and t.final in cls.signed @classmethod def is_unsigned(cls, t: SymbolTYPE) -> bool: assert isinstance(t, SymbolTYPE) - return t.final in cls.unsigned + return t.is_basic and t.final in cls.unsigned @classmethod def is_integral(cls, t: SymbolTYPE) -> bool: assert isinstance(t, SymbolTYPE) - return t.final in cls.integrals + return t.is_basic and t.final in cls.integrals @classmethod def is_decimal(cls, t: SymbolTYPE) -> bool: assert isinstance(t, SymbolTYPE) - return t.final in cls.decimals + return t.is_basic and t.final in cls.decimals @classmethod def is_string(cls, t: SymbolTYPE) -> bool: @@ -310,11 +306,13 @@ def is_string(cls, t: SymbolTYPE) -> bool: def to_signed(cls, t: SymbolTYPE): """Return signed type or equivalent""" assert isinstance(t, SymbolTYPE) - t = t.final - assert t.is_basic + if not isinstance(t, SymbolBASICTYPE): + return cls.unknown + if cls.is_unsigned(t): - # FIXME - return {cls.boolean: cls.byte_, cls.ubyte: cls.byte_, cls.uinteger: cls.integer, cls.ulong: cls.long_}[t] # type:ignore[index] + return {cls.boolean: cls.byte_, cls.ubyte: cls.byte_, cls.uinteger: cls.integer, cls.ulong: cls.long_}[t] + if cls.is_signed(t) or cls.is_decimal(t): return t + return cls.unknown diff --git a/src/symbols/typecast.py b/src/symbols/typecast.py index 31a2d30d2..74047c553 100644 --- a/src/symbols/typecast.py +++ b/src/symbols/typecast.py @@ -35,7 +35,7 @@ def operand(self, operand_): self.children[0] = operand_ @classmethod - def make_node(cls, new_type: SymbolTYPE, node: Symbol, lineno: int): + def make_node(cls, new_type: SymbolTYPE, node: Symbol | None, lineno: int) -> Symbol | None: """Creates a node containing the type cast of the given one. If new_type == node.type, then nothing is done, and the same node is @@ -43,7 +43,7 @@ def make_node(cls, new_type: SymbolTYPE, node: Symbol, lineno: int): Returns None on failure (and calls syntax_error) """ - assert isinstance(new_type, SymbolTYPE) + assert isinstance(new_type, SymbolTYPE), f"{new_type} is not a SymbolTYPE" # None (null) means the given AST node is empty (usually an error) if node is None: @@ -84,7 +84,7 @@ def make_node(cls, new_type: SymbolTYPE, node: Symbol, lineno: int): # It's a number. So let's convert it directly if check.is_const(node): - node = SymbolNUMBER(node.value, node.lineno, node.type_) + node = SymbolNUMBER(node.value, node.lineno, node.type_.final) if new_type == TYPE.boolean: node.value = int(bool(node.value)) diff --git a/src/symbols/unary.py b/src/symbols/unary.py index 963232f38..a4651cdd0 100644 --- a/src/symbols/unary.py +++ b/src/symbols/unary.py @@ -4,6 +4,8 @@ # See the file CONTRIBUTORS.md for copyright details. # See https://www.gnu.org/licenses/agpl-3.0.html for details. # -------------------------------------------------------------------- +from collections.abc import Callable +from typing import Self from src.api import check from src.symbols.number import SymbolNUMBER @@ -53,7 +55,14 @@ def __repr__(self): return "(%s: %s)" % (self.operator, self.operand) @classmethod - def make_node(cls, lineno, operator, operand, func=None, type_=None): + def make_node( + cls, + lineno: int, + operator: str, + operand: Symbol | None, + func: Callable | None = None, + type_: SymbolTYPE | None = None, + ) -> Self | SymbolNUMBER | SymbolSTRING: """Creates a node for a unary operation. E.g. -x or LEN(a$) Parameters: @@ -73,6 +82,8 @@ def make_node(cls, lineno, operator, operand, func=None, type_=None): if type_ is None: type_ = operand.type_ + type_ = type_.final + if operator == "MINUS": if not type_.is_signed: type_ = type_.to_signed() diff --git a/src/zxbc/zxbparser.py b/src/zxbc/zxbparser.py index 41071ba0e..d8c9195c7 100755 --- a/src/zxbc/zxbparser.py +++ b/src/zxbc/zxbparser.py @@ -9,10 +9,11 @@ import math import sys +from collections.abc import Callable from math import pi as PI # typings -from typing import NamedTuple +from typing import NamedTuple, cast import src.api.config import src.api.dataref @@ -40,7 +41,7 @@ is_unsigned, ) from src.api.config import OPTIONS -from src.api.constants import CLASS, CONVENTION, SCOPE, LoopType +from src.api.constants import CLASS, CONVENTION, SCOPE, TYPE, LoopType from src.api.debug import __DEBUG__ from src.api.errmsg import error, warning from src.api.global_ import LoopInfo @@ -53,7 +54,7 @@ from src.symbols import sym from src.symbols.id_ import SymbolID from src.symbols.symbol_ import Symbol -from src.symbols.type_ import Type as TYPE +from src.symbols.type_ import Type from src.zxbc import zxblex from src.zxbc.zxblex import tokens # noqa @@ -154,13 +155,17 @@ def init(): # ---------------------------------------------------------------------- # "Macro" functions. Just return more complex expressions # ---------------------------------------------------------------------- -def _TYPE(type_): - """returns an internal type converted to a SYMBOL_TABLE - type. - """ +def _TYPE(type_: TYPE) -> sym.TYPE | None: + """returns an internal type converted to a SYMBOL_TABLE type.""" + assert isinstance(type_, TYPE) return SYMBOL_TABLE.basic_types[type_] +def _TYPEREF(type_: TYPE, *, implicit: bool = True) -> sym.TYPEREF: + """Returns a typing annotation""" + return sym.TYPEREF(_TYPE(type_), 0, implicit=implicit) + + # ---------------------------------------------------------------------- # Utils # ---------------------------------------------------------------------- @@ -198,13 +203,20 @@ def make_number(value, lineno: int, type_=None): return sym.NUMBER(value, type_=type_, lineno=lineno) -def make_typecast(type_: sym.TYPE, node: sym.SYMBOL | None, lineno: int) -> sym.TYPECAST | None: +def make_typecast(type_: sym.TYPING, node: sym.EXPR | None, lineno: int) -> sym.TYPECAST | sym.EXPR | None: """Wrapper: returns a Typecast node""" if node is None or node.type_ is None: return None # syntax / semantic error + if isinstance(type_, sym.TYPEREF): + type_ = type_.type_ + assert isinstance(type_, sym.TYPE) - return sym.TYPECAST.make_node(type_, node, lineno) + + result = sym.TYPECAST.make_node(type_, node, lineno) + assert isinstance(result, None | sym.TYPECAST | sym.EXPR), f"{result.__class__.__name__} != TYPECAST | EXPR" + + return result def make_binary(lineno: int, operator, left, right, func=None, type_=None): @@ -212,7 +224,13 @@ def make_binary(lineno: int, operator, left, right, func=None, type_=None): return sym.BINARY.make_node(operator, left, right, lineno, func, type_) -def make_unary(lineno: int, operator, operand, func=None, type_=None): +def make_unary( + lineno: int, + operator: str, + operand: sym.EXPR | None, + func=Callable, + type_: sym.TYPE | None = None, +) -> sym.UNARY | sym.NUMBER | sym.STRING | None: """Wrapper: returns a Unary node""" if operand is None: # syntax / semantic error return None @@ -220,17 +238,26 @@ def make_unary(lineno: int, operator, operand, func=None, type_=None): return sym.UNARY.make_node(lineno, operator, operand, func, type_) -def make_builtin(lineno: int, fname: str, operands: Symbol | tuple | list | None, func=None, type_=None): +def make_builtin( + lineno: int, + fname: str, + operands: Symbol | tuple | list | None, + func: Callable | None = None, + type_: sym.TYPE | None = None, +) -> sym.BUILTIN | sym.NUMBER: """Wrapper: returns a Builtin function node. Can be a Symbol, tuple or list of Symbols If operand is an iterable, they will be expanded. """ if operands is None: operands = [] + assert isinstance(operands, Symbol | tuple | list) + assert isinstance(type_, sym.TYPE | None) + # TODO: In the future, builtin functions will be implemented in an external stdlib, like POINT or ATTR __DEBUG__(f'Creating BUILTIN "{fname}"', 1) - if not isinstance(operands, (list, tuple)): + if not isinstance(operands, list | tuple): operands = [operands] return sym.BUILTIN.make_node(lineno, fname, func, type_, *operands) @@ -267,12 +294,17 @@ def make_var_declaration(entry): return sym.VARDECL(entry) -def make_array_declaration(entry): +def make_array_declaration(entry: sym.ID) -> sym.ARRAYDECL: """This will return a node with the symbol as an array.""" return sym.ARRAYDECL(entry) -def make_func_declaration(func_name: str, lineno: int, class_: CLASS, type_=None): +def make_func_declaration( + func_name: str, + lineno: int, + class_: CLASS, + type_: sym.TYPEREF | None = None, +) -> sym.FUNCDECL | None: """This will return a node with the symbol as a function or sub.""" return sym.FUNCDECL.make_node(func_name, lineno, class_, type_=type_) @@ -314,7 +346,7 @@ def make_array_access(id_, lineno, arglist): """ for i, arg in enumerate(arglist): value = make_typecast( - TYPE.by_name(src.api.constants.TYPE.to_string(gl.BOUND_TYPE)), + Type.by_name(src.api.constants.TYPE.to_string(gl.BOUND_TYPE)), arg.value, arg.lineno, ) @@ -333,7 +365,7 @@ def make_array_substr_assign(lineno: int, id_: str, arg_list, substr, expr_) -> if entry is None: return None # There were errors - if entry.type_ != TYPE.string: + if entry.type_ != Type.string: error(lineno, "Array '%s' is not of type String" % id_) return None # There were errors @@ -383,7 +415,7 @@ def make_call(id_: str, lineno: int, args: sym.ARGLIST): if entry is None: return None - if entry.class_ is CLASS.unknown and entry.type_ == TYPE.string and len(args) == 1 and is_numeric(args[0]): + if entry.class_ is CLASS.unknown and entry.type_ == Type.string and len(args) == 1 and is_numeric(args[0]): entry = entry.to_var() # A scalar variable. e.g a$(expr) if entry.class_ == CLASS.array: # An already declared array @@ -392,7 +424,7 @@ def make_call(id_: str, lineno: int, args: sym.ARGLIST): return None if arr.offset is not None: - offset = make_typecast(TYPE.uinteger, make_number(arr.offset, lineno=lineno), lineno) + offset = make_typecast(Type.uinteger, make_number(arr.offset, lineno=lineno), lineno) arr.append_child(offset) return arr @@ -418,9 +450,16 @@ def make_call(id_: str, lineno: int, args: sym.ARGLIST): return make_func_call(id_, lineno, args) -def make_param_decl(id_: str, lineno: int, typedef, is_array: bool, default_value: sym.SYMBOL | None = None): +def make_param_decl( + id_: str, + lineno: int, + typedef: sym.TYPEREF, + *, + is_array: bool, + default_value: sym.SYMBOL | None = None, +): """Wrapper that creates a param declaration""" - return SYMBOL_TABLE.declare_param(id_, lineno, typedef, is_array, default_value) + return SYMBOL_TABLE.declare_param(id_, lineno, typedef, default_value, is_array=is_array) def make_type(typename, lineno, implicit=False): @@ -435,7 +474,7 @@ def make_type(typename, lineno, implicit=False): if not SYMBOL_TABLE.check_is_declared(typename, lineno, "type"): return None - type_ = sym.TYPEREF(SYMBOL_TABLE.get_entry(typename), lineno, implicit) + type_ = sym.TYPEREF(SYMBOL_TABLE.get_entry(typename), lineno, implicit=implicit) return type_ @@ -468,7 +507,7 @@ def make_break(lineno: int, p): return None last_brk_linenum = lineno - return make_sentence(lineno, "CHKBREAK", make_number(lineno, lineno, TYPE.uinteger)) + return make_sentence(lineno, "CHKBREAK", make_number(lineno, lineno, Type.uinteger)) # ---------------------------------------------------------------------- @@ -704,8 +743,8 @@ def p_var_decl_ini(p): if typedef.implicit: typedef = sym.TYPEREF(expr.type_, p.lexer.lineno, implicit=True) - value = make_typecast(typedef, expr, p.lineno(4)) - defval = value if is_static(expr) and value.type_ != TYPE.string else None + value = make_typecast(typedef.type_, expr, p.lineno(4)) + defval = value if is_static(expr) and value.type_ != Type.string else None if keyword == "DIM": SYMBOL_TABLE.declare_variable(idlist[0].name, idlist[0].lineno, typedef, default_value=defval) @@ -851,8 +890,8 @@ def check_bound(boundlist, remaining): if entry is None: return - if p[6] == TYPE.string or entry.type_ == TYPE.string: - errmsg.syntax_error_cannot_initialize_array_of_type(p.lineno(1), TYPE.string) + if p[6] == Type.string or entry.type_ == Type.string: + errmsg.syntax_error_cannot_initialize_array_of_type(p.lineno(1), Type.string) return @@ -943,7 +982,7 @@ def p_staement_func_decl(p): def p_statement_border(p): """statement : BORDER expr""" - p[0] = make_sentence(p.lineno(1), "BORDER", make_typecast(TYPE.ubyte, p[2], p.lineno(1))) + p[0] = make_sentence(p.lineno(1), "BORDER", make_typecast(Type.ubyte, p[2], p.lineno(1))) def p_statement_plot(p): @@ -951,8 +990,8 @@ def p_statement_plot(p): p[0] = make_sentence( p.lineno(1), "PLOT", - make_typecast(TYPE.ubyte, p[2], p.lineno(3)), - make_typecast(TYPE.ubyte, p[4], p.lineno(3)), + make_typecast(Type.ubyte, p[2], p.lineno(3)), + make_typecast(Type.ubyte, p[4], p.lineno(3)), ) @@ -961,8 +1000,8 @@ def p_statement_plot_attr(p): p[0] = make_sentence( p.lineno(1), "PLOT", - make_typecast(TYPE.ubyte, p[3], p.lineno(4)), - make_typecast(TYPE.ubyte, p[5], p.lineno(4)), + make_typecast(Type.ubyte, p[3], p.lineno(4)), + make_typecast(Type.ubyte, p[5], p.lineno(4)), p[2], ) @@ -972,9 +1011,9 @@ def p_statement_draw3(p): p[0] = make_sentence( p.lineno(1), "DRAW3", - make_typecast(TYPE.integer, p[2], p.lineno(3)), - make_typecast(TYPE.integer, p[4], p.lineno(5)), - make_typecast(TYPE.float_, p[6], p.lineno(5)), + make_typecast(Type.integer, p[2], p.lineno(3)), + make_typecast(Type.integer, p[4], p.lineno(5)), + make_typecast(Type.float_, p[6], p.lineno(5)), ) @@ -983,9 +1022,9 @@ def p_statement_draw3_attr(p): p[0] = make_sentence( p.lineno(1), "DRAW3", - make_typecast(TYPE.integer, p[3], p.lineno(4)), - make_typecast(TYPE.integer, p[5], p.lineno(6)), - make_typecast(TYPE.float_, p[7], p.lineno(6)), + make_typecast(Type.integer, p[3], p.lineno(4)), + make_typecast(Type.integer, p[5], p.lineno(6)), + make_typecast(Type.float_, p[7], p.lineno(6)), p[2], ) @@ -995,8 +1034,8 @@ def p_statement_draw(p): p[0] = make_sentence( p.lineno(1), "DRAW", - make_typecast(TYPE.integer, p[2], p.lineno(3)), - make_typecast(TYPE.integer, p[4], p.lineno(3)), + make_typecast(Type.integer, p[2], p.lineno(3)), + make_typecast(Type.integer, p[4], p.lineno(3)), ) @@ -1005,8 +1044,8 @@ def p_statement_draw_attr(p): p[0] = make_sentence( p.lineno(1), "DRAW", - make_typecast(TYPE.integer, p[3], p.lineno(4)), - make_typecast(TYPE.integer, p[5], p.lineno(4)), + make_typecast(Type.integer, p[3], p.lineno(4)), + make_typecast(Type.integer, p[5], p.lineno(4)), p[2], ) @@ -1016,9 +1055,9 @@ def p_statement_circle(p): p[0] = make_sentence( p.lineno(1), "CIRCLE", - make_typecast(TYPE.byte_, p[2], p.lineno(3)), - make_typecast(TYPE.byte_, p[4], p.lineno(5)), - make_typecast(TYPE.byte_, p[6], p.lineno(5)), + make_typecast(Type.byte_, p[2], p.lineno(3)), + make_typecast(Type.byte_, p[4], p.lineno(5)), + make_typecast(Type.byte_, p[6], p.lineno(5)), ) @@ -1027,9 +1066,9 @@ def p_statement_circle_attr(p): p[0] = make_sentence( p.lineno(1), "CIRCLE", - make_typecast(TYPE.byte_, p[3], p.lineno(4)), - make_typecast(TYPE.byte_, p[5], p.lineno(6)), - make_typecast(TYPE.byte_, p[7], p.lineno(6)), + make_typecast(Type.byte_, p[3], p.lineno(4)), + make_typecast(Type.byte_, p[5], p.lineno(6)), + make_typecast(Type.byte_, p[7], p.lineno(6)), p[2], ) @@ -1046,12 +1085,12 @@ def p_statement_asm(p): def p_statement_randomize(p): """statement : RANDOMIZE""" - p[0] = make_sentence(p.lineno(1), "RANDOMIZE", make_number(0, lineno=p.lineno(1), type_=TYPE.ulong)) + p[0] = make_sentence(p.lineno(1), "RANDOMIZE", make_number(0, lineno=p.lineno(1), type_=Type.ulong)) def p_statement_randomize_expr(p): """statement : RANDOMIZE expr""" - p[0] = make_sentence(p.lineno(1), "RANDOMIZE", make_typecast(TYPE.ulong, p[2], p.lineno(1))) + p[0] = make_sentence(p.lineno(1), "RANDOMIZE", make_typecast(Type.ulong, p[2], p.lineno(1))) def p_statement_beep(p): @@ -1059,8 +1098,8 @@ def p_statement_beep(p): p[0] = make_sentence( p.lineno(1), "BEEP", - make_typecast(TYPE.float_, p[2], p.lineno(1)), - make_typecast(TYPE.float_, p[4], p.lineno(3)), + make_typecast(Type.float_, p[2], p.lineno(1)), + make_typecast(Type.float_, p[4], p.lineno(3)), ) @@ -1196,7 +1235,7 @@ def p_arr_assignment(p): if entry is None: return - if entry.type_ == TYPE.string: + if entry.type_ == Type.string: variable = gl.SYMBOL_TABLE.access_array(id_, p.lineno(i)) # variable is an array. If it has 0 bounds means they are undefined (param byref) if len(variable.ref.bounds) and len(variable.ref.bounds) + 1 == len(arg_list): @@ -1229,7 +1268,7 @@ def p_substr_assignment_no_let(p): if entry.class_ == CLASS.unknown: entry.class_ = CLASS.var - if p[6].type_ != TYPE.string: + if p[6].type_ != Type.string: errmsg.syntax_error_expected_string(p.lineno(5), p[6].type_) lineno = p.lineno(2) @@ -1262,11 +1301,11 @@ def p_substr_assignment(p): errmsg.syntax_error_cannot_assign_not_a_var(p.lineno(2), p[2]) return - if entry.type_ != TYPE.string: + if entry.type_ != Type.string: errmsg.syntax_error_expected_string(p.lineno(2), entry.type_) return - if p[5].type_ != TYPE.string: + if p[5].type_ != Type.string: errmsg.syntax_error_expected_string(p.lineno(4), p[5].type_) return @@ -1324,10 +1363,10 @@ def p_str_assign(p): p[0] = None return - if r.type_ != TYPE.string: + if r.type_ != Type.string: errmsg.syntax_error_expected_string(lineno, r.type_) - entry = SYMBOL_TABLE.access_var(q, lineno, default_type=TYPE.string) + entry = SYMBOL_TABLE.access_var(q, lineno, default_type=Type.string) if entry is None: p[0] = None return @@ -1611,7 +1650,7 @@ def p_for_sentence_start(p): "FOR start value is lower than end. This FOR loop is useless", ) - id_type = common_type(common_type(p[4], p[6]), p[7]) + id_type = common_type(common_type(p[4].type_, p[6].type_), p[7].type_) variable = SYMBOL_TABLE.access_var(p[2], p.lineno(2), default_type=id_type) if variable is None: return @@ -1651,7 +1690,7 @@ def p_error_raise(p): r = make_binary( p.lineno(1), "MINUS", - make_typecast(TYPE.ubyte, p[2], p.lineno(1)), + make_typecast(Type.ubyte, p[2], p.lineno(1)), q, lambda x, y: x - y, ) @@ -1667,7 +1706,7 @@ def p_stop_raise(p): r = make_binary( p.lineno(1), "MINUS", - make_typecast(TYPE.ubyte, q, p.lineno(1)), + make_typecast(Type.ubyte, q, p.lineno(1)), z, lambda x, y: x - y, ) @@ -1751,7 +1790,12 @@ def p_data(p): continue new_lbl = f"__DATA__FUNCPTR__{len(gl.DATA_FUNCTIONS)}" - entry = make_func_declaration(new_lbl, p.lineno(1), type_=value.type_, class_=CLASS.function) + type_ = value.type_ + assert isinstance(type_, sym.TYPING) + if isinstance(type_, sym.TYPE): + type_ = sym.TYPEREF(value.type_, 0) + + entry = make_func_declaration(new_lbl, p.lineno(1), type_=type_, class_=CLASS.function) if not entry: continue @@ -1811,9 +1855,9 @@ def p_read(p): return mark_entry_as_accessed(entry) - if entry.type_ == TYPE.auto: + if entry.type_ == Type.auto: entry.type_ = _TYPE(gl.DEFAULT_TYPE) - errmsg.warning_implicit_type(p.lineno(2), p[2], entry.type_) + errmsg.warning_implicit_type(p.lineno(2), p[2], entry.type_.name) reads.append(make_sentence(p.lineno(1), "READ", entry)) continue @@ -1986,8 +2030,8 @@ def p_print_sentence(p): def p_print_elem_expr(p): """print_elem : expr""" p[0] = p[1] - if p[1] is not None and p[1].type_ == TYPE.boolean: - p[0] = make_typecast(TYPE.ubyte, p[1], p.lineno(1)) + if p[1] is not None and p[1].type_ == Type.boolean: + p[0] = make_typecast(Type.ubyte, p[1], p.lineno(1)) def p_print_list_expr(p): @@ -1998,7 +2042,7 @@ def p_print_list_expr(p): | ITALIC expr """ if p[1] in ("BOLD", "ITALIC"): - p[0] = make_sentence(p.lineno(1), p[1] + "_TMP", make_typecast(TYPE.ubyte, p[2], p.lineno(1))) + p[0] = make_sentence(p.lineno(1), p[1] + "_TMP", make_typecast(Type.ubyte, p[2], p.lineno(1))) else: p[0] = p[1] @@ -2025,7 +2069,7 @@ def p_attr(p): # BOLD and ITALIC are ignored by them, so we put them out of the # attr definition so something like DRAW BOLD 1; .... will raise # a syntax error - p[0] = make_sentence(p.lineno(1), p[1] + "_TMP", make_typecast(TYPE.ubyte, p[2], p.lineno(1))) + p[0] = make_sentence(p.lineno(1), p[1] + "_TMP", make_typecast(Type.ubyte, p[2], p.lineno(1))) def p_print_list_epsilon(p): @@ -2063,19 +2107,19 @@ def p_print_list_at(p): p[0] = make_sentence( p.lineno(1), "PRINT_AT", - make_typecast(TYPE.ubyte, p[2], p.lineno(1)), - make_typecast(TYPE.ubyte, p[4], p.lineno(3)), + make_typecast(Type.ubyte, p[2], p.lineno(1)), + make_typecast(Type.ubyte, p[4], p.lineno(3)), ) def p_print_list_tab(p): """print_tab : TAB expr""" - p[0] = make_sentence(p.lineno(1), "PRINT_TAB", make_typecast(TYPE.ubyte, p[2], p.lineno(1))) + p[0] = make_sentence(p.lineno(1), "PRINT_TAB", make_typecast(Type.ubyte, p[2], p.lineno(1))) def p_on_goto(p): """statement : ON expr goto label_list""" - expr = make_typecast(TYPE.ubyte, p[2], p.lineno(1)) + expr = make_typecast(Type.ubyte, p[2], p.lineno(1)) p[0] = make_sentence(p.lineno(1), "ON_" + p[3], expr, *p[4]) @@ -2130,7 +2174,7 @@ def p_return_expr(p): p[0] = None return - if is_numeric(p[2]) and FUNCTION_LEVEL[-1].type_ == TYPE.string: + if is_numeric(p[2]) and FUNCTION_LEVEL[-1].type_.final == Type.string: error( p.lineno(2), "Type Error: Function must return a string, not a numeric value", @@ -2138,7 +2182,7 @@ def p_return_expr(p): p[0] = None return - if not is_numeric(p[2]) and FUNCTION_LEVEL[-1].type_ != TYPE.string: + if not is_numeric(p[2]) and FUNCTION_LEVEL[-1].type_.final != Type.string: error( p.lineno(2), "Type Error: Function must return a numeric value, not a string", @@ -2156,7 +2200,7 @@ def p_return_expr(p): def p_pause(p): """statement : PAUSE expr""" - p[0] = make_sentence(p.lineno(1), "PAUSE", make_typecast(TYPE.uinteger, p[2], p.lineno(1))) + p[0] = make_sentence(p.lineno(1), "PAUSE", make_typecast(Type.uinteger, p[2], p.lineno(1))) def p_poke(p): @@ -2170,8 +2214,8 @@ def p_poke(p): p[0] = make_sentence( p.lineno(1), "POKE", - make_typecast(TYPE.uinteger, p[i], p.lineno(i + 1)), - make_typecast(TYPE.ubyte, p[i + 2], p.lineno(i + 1)), + make_typecast(Type.uinteger, p[i], p.lineno(i + 1)), + make_typecast(Type.ubyte, p[i + 2], p.lineno(i + 1)), ) @@ -2186,7 +2230,7 @@ def p_poke2(p): p[0] = make_sentence( p.lineno(1), "POKE", - make_typecast(TYPE.uinteger, p[i + 1], p.lineno(i + 2)), + make_typecast(Type.uinteger, p[i + 1], p.lineno(i + 2)), make_typecast(p[i], p[i + 3], p.lineno(i + 3)), ) @@ -2202,7 +2246,7 @@ def p_poke3(p): p[0] = make_sentence( p.lineno(1), "POKE", - make_typecast(TYPE.uinteger, p[i + 2], p.lineno(i + 3)), + make_typecast(Type.uinteger, p[i + 2], p.lineno(i + 3)), make_typecast(p[i], p[i + 4], p.lineno(i + 5)), ) @@ -2212,8 +2256,8 @@ def p_out(p): p[0] = make_sentence( p.lineno(1), "OUT", - make_typecast(TYPE.uinteger, p[2], p.lineno(3)), - make_typecast(TYPE.ubyte, p[4], p.lineno(4)), + make_typecast(Type.uinteger, p[2], p.lineno(3)), + make_typecast(Type.ubyte, p[4], p.lineno(4)), ) @@ -2227,7 +2271,7 @@ def p_simple_instruction(p): | OVER expr | INVERSE expr """ - p[0] = make_sentence(p.lineno(1), p[1], make_typecast(TYPE.ubyte, p[2], p.lineno(1))) + p[0] = make_sentence(p.lineno(1), p[1], make_typecast(Type.ubyte, p[2], p.lineno(1))) def p_save_code(p): @@ -2236,7 +2280,7 @@ def p_save_code(p): | SAVE expr ARRAY_ID """ expr = p[2] - if expr.type_ != TYPE.string: + if expr.type_ != Type.string: errmsg.syntax_error_expected_string(p.lineno(1), expr.type_) if len(p) == 4: @@ -2248,8 +2292,8 @@ def p_save_code(p): start = make_number(16384, lineno=p.lineno(1)) length = make_number(6912, lineno=p.lineno(1)) else: - start = make_typecast(TYPE.uinteger, p[4], p.lineno(4)) - length = make_typecast(TYPE.uinteger, p[6], p.lineno(6)) + start = make_typecast(Type.uinteger, p[4], p.lineno(4)) + length = make_typecast(Type.uinteger, p[6], p.lineno(6)) p[0] = make_sentence(p.lineno(1), p[1], expr, start, length) @@ -2259,7 +2303,7 @@ def p_save_data(p): | SAVE expr DATA ID | SAVE expr DATA ID LP RP """ - if p[2].type_ != TYPE.string: + if p[2].type_ != Type.string: errmsg.syntax_error_expected_string(p.lineno(1), p[2].type_) if len(p) != 4: @@ -2270,7 +2314,7 @@ def p_save_data(p): mark_entry_as_accessed(entry) access = entry - start = make_unary(p.lineno(4), "ADDRESS", access, type_=TYPE.uinteger) + start = make_unary(p.lineno(4), "ADDRESS", access, type_=Type.uinteger) if entry.class_ == CLASS.array: length = make_number(entry.memsize, lineno=p.lineno(4)) @@ -2278,10 +2322,10 @@ def p_save_data(p): length = make_number(entry.type_.size, lineno=p.lineno(4)) else: access = SYMBOL_TABLE.access_label(gl.ZXBASIC_USER_DATA, p.lineno(3), SYMBOL_TABLE.global_scope) - start = make_unary(p.lineno(3), "ADDRESS", access, type_=TYPE.uinteger) + start = make_unary(p.lineno(3), "ADDRESS", access, type_=Type.uinteger) access = SYMBOL_TABLE.access_label(gl.ZXBASIC_USER_DATA_LEN, p.lineno(3), SYMBOL_TABLE.global_scope) - length = make_unary(p.lineno(3), "ADDRESS", access, type_=TYPE.uinteger) + length = make_unary(p.lineno(3), "ADDRESS", access, type_=Type.uinteger) p[0] = make_sentence(p.lineno(1), p[1], p[2], start, length) @@ -2299,7 +2343,7 @@ def p_load_code(p): | load_or_verify expr CODE expr | load_or_verify expr CODE expr COMMA expr """ - if p[2].type_ != TYPE.string: + if p[2].type_ != Type.string: errmsg.syntax_error_expected_string(p.lineno(3), p[2].type_) if len(p) == 4: @@ -2313,12 +2357,12 @@ def p_load_code(p): start = make_number(16384, lineno=p.lineno(3)) length = make_number(6912, lineno=p.lineno(3)) else: - start = make_typecast(TYPE.uinteger, p[4], p.lineno(3)) + start = make_typecast(Type.uinteger, p[4], p.lineno(3)) if len(p) == 5: length = make_number(0, lineno=p.lineno(3)) else: - length = make_typecast(TYPE.uinteger, p[6], p.lineno(5)) + length = make_typecast(Type.uinteger, p[6], p.lineno(5)) p[0] = make_sentence(p.lineno(3), p[1], p[2], start, length) @@ -2328,7 +2372,7 @@ def p_load_data(p): | load_or_verify expr DATA ID | load_or_verify expr DATA ID LP RP """ - if p[2].type_ != TYPE.string: + if p[2].type_ != Type.string: errmsg.syntax_error_expected_string(p.lineno(1), p[2].type_) if len(p) != 4: @@ -2338,7 +2382,7 @@ def p_load_data(p): return mark_entry_as_accessed(entry) - start = make_unary(p.lineno(4), "ADDRESS", entry, type_=TYPE.uinteger) + start = make_unary(p.lineno(4), "ADDRESS", entry, type_=Type.uinteger) if entry.class_ == CLASS.array: length = make_number(entry.memsize, lineno=p.lineno(4)) @@ -2346,10 +2390,10 @@ def p_load_data(p): length = make_number(entry.type_.size, lineno=p.lineno(4)) else: entry = SYMBOL_TABLE.access_label(gl.ZXBASIC_USER_DATA, p.lineno(3), SYMBOL_TABLE.global_scope) - start = make_unary(p.lineno(3), "ADDRESS", entry, type_=TYPE.uinteger) + start = make_unary(p.lineno(3), "ADDRESS", entry, type_=Type.uinteger) entry = SYMBOL_TABLE.access_label(gl.ZXBASIC_USER_DATA_LEN, p.lineno(3), SYMBOL_TABLE.global_scope) - length = make_unary(p.lineno(3), "ADDRESS", entry, type_=TYPE.uinteger) + length = make_unary(p.lineno(3), "ADDRESS", entry, type_=Type.uinteger) p[0] = make_sentence(p.lineno(3), p[1], p[2], start, length) @@ -2397,8 +2441,8 @@ def p_expr_pow_expr(p): p[0] = make_binary( p.lineno(2), "POW", - make_typecast(TYPE.float_, p[1], p.lineno(2)), - make_typecast(TYPE.float_, p[3], p.lexer.lineno), + make_typecast(Type.float_, p[1], p.lineno(2)), + make_typecast(Type.float_, p[3], p.lexer.lineno), lambda x, y: x**y, ) @@ -2409,14 +2453,14 @@ def p_expr_shl_expr(p): p[0] = None return - if p[1].type_ in (TYPE.float_, TYPE.fixed): - p[1] = make_typecast(TYPE.ulong, p[1], p.lineno(2)) + if p[1].type_ in (Type.float_, Type.fixed): + p[1] = make_typecast(Type.ulong, p[1], p.lineno(2)) p[0] = make_binary( p.lineno(2), "SHL", p[1], - make_typecast(TYPE.ubyte, p[3], p.lineno(2)), + make_typecast(Type.ubyte, p[3], p.lineno(2)), lambda x, y: x << y, ) @@ -2427,14 +2471,14 @@ def p_expr_shr_expr(p): p[0] = None return - if p[1].type_ in (TYPE.float_, TYPE.fixed): - p[1] = make_typecast(TYPE.ulong, p[1], p.lineno(2)) + if p[1].type_ in (Type.float_, Type.fixed): + p[1] = make_typecast(Type.ulong, p[1], p.lineno(2)) p[0] = make_binary( p.lineno(2), "SHR", p[1], - make_typecast(TYPE.ubyte, p[3], p.lineno(2)), + make_typecast(Type.ubyte, p[3], p.lineno(2)), lambda x, y: x >> y, ) @@ -2531,7 +2575,7 @@ def p_number_expr(p): def p_expr_PI(p): """bexpr : PI""" - p[0] = make_number(PI, lineno=p.lineno(1), type_=TYPE.float_) + p[0] = make_number(PI, lineno=p.lineno(1), type_=Type.float_) def p_expr_string(p): @@ -2567,11 +2611,11 @@ def p_string_lp_expr_rp(p): def p_expr_id_substr(p): """string : ID substr""" entry = SYMBOL_TABLE.get_entry(p[1]) - if entry is not None and entry.type_ == TYPE.string and entry.token == "CONST": + if entry is not None and entry.type_ == Type.string and entry.token == "CONST": p[0] = make_strslice(p.lineno(1), entry, p[2][0], p[2][1]) return - entry = SYMBOL_TABLE.access_var(p[1], p.lineno(1), default_type=TYPE.string) + entry = SYMBOL_TABLE.access_var(p[1], p.lineno(1), default_type=Type.string) p[0] = None if entry is None: return @@ -2587,10 +2631,10 @@ def p_string_substr(p): def p_string_expr_lp(p): """string : LP expr RP substr""" - if p[2].type_ != TYPE.string: + if p[2].type_ != Type.string: error( p.lexer.lineno, - "Expected a string type expression. Got %s type instead" % TYPE.to_string(p[2].type_), + "Expected a string type expression. Got %s type instead" % Type.to_string(p[2].type_), ) p[0] = None else: @@ -2600,25 +2644,25 @@ def p_string_expr_lp(p): def p_subind_str(p): """substr : LP expr TO expr RP""" p[0] = ( - make_typecast(TYPE.uinteger, p[2], p.lineno(1)), - make_typecast(TYPE.uinteger, p[4], p.lineno(3)), + make_typecast(Type.uinteger, p[2], p.lineno(1)), + make_typecast(Type.uinteger, p[4], p.lineno(3)), ) def p_subind_strTO(p): """substr : LP TO expr RP""" p[0] = ( - make_typecast(TYPE.uinteger, make_number(0, lineno=p.lineno(2)), p.lineno(1)), - make_typecast(TYPE.uinteger, p[3], p.lineno(2)), + make_typecast(Type.uinteger, make_number(0, lineno=p.lineno(2)), p.lineno(1)), + make_typecast(Type.uinteger, p[3], p.lineno(2)), ) def p_subind_TOstr(p): """substr : LP expr TO RP""" p[0] = ( - make_typecast(TYPE.uinteger, p[2], p.lineno(1)), + make_typecast(Type.uinteger, p[2], p.lineno(1)), make_typecast( - TYPE.uinteger, + Type.uinteger, make_number(gl.MAX_STRSLICE_IDX, lineno=p.lineno(4)), lineno=p.lineno(4), ), @@ -2629,9 +2673,9 @@ def p_subind_TOstr(p): def p_subind_TO(p): """substr : LP TO RP""" p[0] = ( - make_typecast(TYPE.uinteger, make_number(0, lineno=p.lineno(2)), p.lineno(1)), + make_typecast(Type.uinteger, make_number(0, lineno=p.lineno(2)), p.lineno(1)), make_typecast( - TYPE.uinteger, + Type.uinteger, make_number(gl.MAX_STRSLICE_IDX, lineno=p.lineno(3)), p.lineno(2), ), @@ -2646,9 +2690,9 @@ def p_id_expr(p): return mark_entry_as_accessed(entry) - if entry.type_ == TYPE.auto: - entry.type_ = _TYPE(gl.DEFAULT_TYPE) - errmsg.warning_implicit_type(p.lineno(1), p[1], entry.type_) + if entry.type_ == Type.auto: + entry.type_ = _TYPEREF(gl.DEFAULT_TYPE) + errmsg.warning_implicit_type(p.lineno(1), p[1], entry.type_.name) p[0] = entry @@ -2705,7 +2749,7 @@ def p_idcall_expr(p): if p[0] is None: return - if p[0].token in ("STRSLICE", "ID", "STRING") or p[0].token == "CONST" and p[0].type_ == TYPE.string: + if p[0].token in ("STRSLICE", "ID", "STRING") or p[0].token == "CONST" and p[0].type_ == Type.string: entry = SYMBOL_TABLE.access_call(p[1], p.lineno(1)) mark_entry_as_accessed(entry) return @@ -2958,10 +3002,10 @@ def p_function_header_pre(p): lineno = p.lineno(3) previoustype_ = p[0].type_ - if not p[3].implicit or p[0].entry.type_ is None or p[0].entry.type_ == TYPE.unknown: + if not p[3].implicit or p[0].entry.type_ is None or p[0].entry.type_ == Type.unknown: p[0].type_ = p[3] if p[3].implicit and p[0].entry.class_ == CLASS.function: - errmsg.warning_implicit_type(p[3].lineno, p[0].entry.name, p[0].type_) + errmsg.warning_implicit_type(p[3].lineno, p[0].entry.name, p[0].type_.name) if forwarded and previoustype_ != p[0].type_: errmsg.syntax_error_func_type_mismatch(lineno, p[0].entry) @@ -3271,14 +3315,14 @@ def p_preproc_pragma_pop(p): def p_expr_usr(p): """bexpr : USR bexpr %prec UMINUS""" - if p[2].type_ == TYPE.string: - p[0] = make_builtin(p.lineno(1), "USR_STR", p[2], type_=TYPE.uinteger) + if p[2].type_ == Type.string: + p[0] = make_builtin(p.lineno(1), "USR_STR", p[2], type_=Type.uinteger) else: p[0] = make_builtin( p.lineno(1), "USR", - make_typecast(TYPE.uinteger, p[2], p.lineno(1)), - type_=TYPE.uinteger, + make_typecast(Type.uinteger, p[2], p.lineno(1)), + type_=Type.uinteger, ) @@ -3286,7 +3330,7 @@ def p_expr_rnd(p): """bexpr : RND %prec ID | RND LP RP """ - p[0] = make_builtin(p.lineno(1), "RND", None, type_=TYPE.float_) + p[0] = make_builtin(p.lineno(1), "RND", None, type_=Type.float_) def p_expr_peek(p): @@ -3294,14 +3338,23 @@ def p_expr_peek(p): p[0] = make_builtin( p.lineno(1), "PEEK", - make_typecast(TYPE.uinteger, p[2], p.lineno(1)), - type_=TYPE.ubyte, + make_typecast(Type.uinteger, p[2], p.lineno(1)), + type_=Type.ubyte, ) def p_expr_peektype_(p): """bexpr : PEEK LP numbertype COMMA expr RP""" - p[0] = make_builtin(p.lineno(1), "PEEK", make_typecast(TYPE.uinteger, p[5], p.lineno(4)), type_=p[3]) + if p[3] is None or p[5] is None: + p[0] = None + return + + p[0] = make_builtin( + p.lineno(1), + "PEEK", + make_typecast(Type.uinteger, p[5], p.lineno(4)), + type_=cast(sym.TYPEREF, p[3]).type_, + ) def p_expr_in(p): @@ -3309,8 +3362,8 @@ def p_expr_in(p): p[0] = make_builtin( p.lineno(1), "IN", - make_typecast(TYPE.uinteger, p[2], p.lineno(1)), - type_=TYPE.ubyte, + make_typecast(Type.uinteger, p[2], p.lineno(1)), + type_=Type.ubyte, ) @@ -3326,10 +3379,10 @@ def p_expr_lbound(p): mark_entry_as_accessed(entry) if entry.scope == SCOPE.parameter: - num = make_number(0, p.lineno(3), TYPE.uinteger) - p[0] = make_builtin(p.lineno(1), p[1], [entry, num], type_=TYPE.uinteger) + num = make_number(0, p.lineno(3), Type.uinteger) + p[0] = make_builtin(p.lineno(1), p[1], [entry, num], type_=Type.uinteger) else: - p[0] = make_number(len(entry.bounds), p.lineno(3), TYPE.uinteger) + p[0] = make_number(len(entry.bounds), p.lineno(3), Type.uinteger) def p_expr_lbound_expr(p): @@ -3347,7 +3400,7 @@ def p_expr_lbound_expr(p): return mark_entry_as_accessed(entry) - num = make_typecast(TYPE.uinteger, expr, p.lineno(6)) + num = make_typecast(Type.uinteger, expr, p.lineno(6)) if num is None: p[0] = None return @@ -3363,11 +3416,11 @@ def p_expr_lbound_expr(p): return if not val: # 0 => number of dims - p[0] = make_number(len(entry.bounds), p.lineno(3), TYPE.uinteger) + p[0] = make_number(len(entry.bounds), p.lineno(3), Type.uinteger) elif p[1] == "LBOUND": - p[0] = make_number(entry.bounds[val - 1].lower, p.lineno(3), TYPE.uinteger) + p[0] = make_number(entry.bounds[val - 1].lower, p.lineno(3), Type.uinteger) else: - p[0] = make_number(entry.bounds[val - 1].upper, p.lineno(3), TYPE.uinteger) + p[0] = make_number(entry.bounds[val - 1].upper, p.lineno(3), Type.uinteger) return if p[1] == "LBOUND": @@ -3375,7 +3428,7 @@ def p_expr_lbound_expr(p): else: entry.ref.ubound_used = True - p[0] = make_builtin(p.lineno(1), p[1], [entry, num], type_=TYPE.uinteger) + p[0] = make_builtin(p.lineno(1), p[1], [entry, num], type_=Type.uinteger) def p_len(p): @@ -3385,13 +3438,13 @@ def p_len(p): p[0] = None elif arg.token == "VAR" and arg.class_ == CLASS.array: p[0] = make_number(len(arg.bounds), lineno=p.lineno(1)) # Do constant folding - elif arg.type_ != TYPE.string: - errmsg.syntax_error_expected_string(p.lineno(1), TYPE.to_string(arg.type_)) + elif arg.type_ != Type.string: + errmsg.syntax_error_expected_string(p.lineno(1), Type.to_string(arg.type_)) p[0] = None elif is_string(arg): # Constant string? p[0] = make_number(len(arg.value), lineno=p.lineno(1)) # Do constant folding else: - p[0] = make_builtin(p.lineno(1), "LEN", arg, type_=TYPE.uinteger) + p[0] = make_builtin(p.lineno(1), "LEN", arg, type_=Type.uinteger) def p_sizeof(p): @@ -3399,11 +3452,11 @@ def p_sizeof(p): | SIZEOF LP ID RP | SIZEOF LP ARRAY_ID RP """ - if TYPE.to_type(p[3].lower()) is not None: - p[0] = make_number(TYPE.size(TYPE.to_type(p[3].lower())), lineno=p.lineno(3)) + if Type.to_type(p[3].lower()) is not None: + p[0] = make_number(Type.size(Type.to_type(p[3].lower())), lineno=p.lineno(3)) else: entry = SYMBOL_TABLE.get_id_or_make_var(p[3], p.lineno(1)) - p[0] = make_number(TYPE.size(entry.type_), lineno=p.lineno(3)) + p[0] = make_number(Type.size(entry.type_), lineno=p.lineno(3)) def p_str(p): @@ -3414,21 +3467,21 @@ def p_str(p): p[0] = make_builtin( p.lineno(1), "STR", - make_typecast(TYPE.float_, p[2], p.lineno(1)), - type_=TYPE.string, + make_typecast(Type.float_, p[2], p.lineno(1)), + type_=Type.string, ) def p_inkey(p): """string : INKEY""" - p[0] = make_builtin(p.lineno(1), "INKEY", None, type_=TYPE.string) + p[0] = make_builtin(p.lineno(1), "INKEY", None, type_=Type.string) def p_chr_one(p): """string : CHR bexpr %prec UMINUS""" arg_list = make_arg_list(make_argument(p[2], p.lineno(1))) - arg_list[0].value = make_typecast(TYPE.ubyte, arg_list[0].value, p.lineno(1)) - p[0] = make_builtin(p.lineno(1), "CHR", arg_list, type_=TYPE.string) + arg_list[0].value = make_typecast(Type.ubyte, arg_list[0].value, p.lineno(1)) + p[0] = make_builtin(p.lineno(1), "CHR", arg_list, type_=Type.string) def p_chr(p): @@ -3439,9 +3492,9 @@ def p_chr(p): return for i in range(len(p[2])): # Convert every argument to 8bit unsigned - p[2][i].value = make_typecast(TYPE.ubyte, p[2][i].value, p.lineno(1)) + p[2][i].value = make_typecast(Type.ubyte, p[2][i].value, p.lineno(1)) - p[0] = make_builtin(p.lineno(1), "CHR", p[2], type_=TYPE.string) + p[0] = make_builtin(p.lineno(1), "CHR", p[2], type_=Type.string) def p_val(p): @@ -3455,11 +3508,11 @@ def val(s): warning(p.lineno(1), f"Invalid string numeric constant '{s}' evaluated as 0") return x - if p[2].type_ != TYPE.string: - errmsg.syntax_error_expected_string(p.lineno(1), TYPE.to_string(p[2].type_)) + if p[2].type_ != Type.string: + errmsg.syntax_error_expected_string(p.lineno(1), Type.to_string(p[2].type_)) p[0] = None else: - p[0] = make_builtin(p.lineno(1), "VAL", p[2], lambda x: val(x), type_=TYPE.float_) + p[0] = make_builtin(p.lineno(1), "VAL", p[2], lambda x: val(x), type_=Type.float_) def p_code(p): @@ -3475,25 +3528,25 @@ def asc(x): p[0] = None return - if p[2].type_ != TYPE.string: - errmsg.syntax_error_expected_string(p.lineno(1), TYPE.to_string(p[2].type_)) + if p[2].type_ != Type.string: + errmsg.syntax_error_expected_string(p.lineno(1), Type.to_string(p[2].type_)) p[0] = None else: - p[0] = make_builtin(p.lineno(1), "CODE", p[2], lambda x: asc(x), type_=TYPE.ubyte) + p[0] = make_builtin(p.lineno(1), "CODE", p[2], lambda x: asc(x), type_=Type.ubyte) def p_sgn(p): """bexpr : SGN bexpr %prec UMINUS""" sgn = lambda x: x < 0 and -1 or x > 0 and 1 or 0 - if p[2].type_ == TYPE.string: + if p[2].type_ == Type.string: error(p.lineno(1), "Expected a numeric expression, got TYPE.string instead") p[0] = None else: if is_unsigned(p[2]) and not is_number(p[2]): warning(p.lineno(1), "Sign of unsigned value is always 0 or 1") - p[0] = make_builtin(p.lineno(1), "SGN", p[2], lambda x: sgn(x), type_=TYPE.byte_) + p[0] = make_builtin(p.lineno(1), "SGN", p[2], lambda x: sgn(x), type_=Type.byte_) # ---------------------------------------- @@ -3504,7 +3557,7 @@ def p_expr_trig(p): p[0] = make_builtin( p.lineno(1), p[1], - make_typecast(TYPE.float_, p[2], p.lineno(1)), + make_typecast(Type.float_, p[2], p.lineno(1)), { "SIN": math.sin, "COS": math.cos, @@ -3516,7 +3569,7 @@ def p_expr_trig(p): "EXP": math.exp, "SQR": math.sqrt, }[p[1]], - type_=TYPE.float_, + type_=Type.float_, ) @@ -3539,7 +3592,7 @@ def p_math_fn(p): # ---------------------------------------- def p_expr_int(p): """bexpr : INT bexpr %prec UMINUS""" - p[0] = make_typecast(TYPE.long_, p[2], p.lineno(1)) + p[0] = make_typecast(Type.long_, p[2], p.lineno(1)) def p_abs(p): diff --git a/tests/__init__.py b/tests/__init__.py index ba3d20693..0b98abd16 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,7 +6,6 @@ # -------------------------------------------------------------------- import os -import os.path import sys path = os.path.realpath(os.path.join(os.path.dirname(__file__), os.pardir)) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..bdc012d15 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,3 @@ +# Initialize imports + +from src.api import check # noqa: F401 diff --git a/tests/symbols/test_symbolARRAYACCESS.py b/tests/symbols/test_symbolARRAYACCESS.py index 9ebd64ed7..c876e87e3 100644 --- a/tests/symbols/test_symbolARRAYACCESS.py +++ b/tests/symbols/test_symbolARRAYACCESS.py @@ -17,6 +17,10 @@ class TestSymbolARRAYACCESS(TestCase): + @staticmethod + def _type_ref(type_: sym.TYPE) -> sym.TYPEREF: + return sym.TYPEREF(type_, 1) + def setUp(self): zxbpp.init() l1 = 1 @@ -26,7 +30,7 @@ def setUp(self): b = sym.BOUND(l1, l2) c = sym.BOUND(l3, l4) self.bounds = sym.BOUNDLIST.make_node(None, b, c) - self.arr = sym.ID("test", 1, type_=Type.ubyte).to_vararray(self.bounds) + self.arr = sym.ID("test", 1, type_ref=self._type_ref(Type.ubyte)).to_vararray(self.bounds) self.arg = sym.ARGLIST( sym.ARGUMENT(sym.NUMBER(2, 1, type_=Type.uinteger), 1), sym.ARGUMENT(sym.NUMBER(3, 1, type_=Type.uinteger), 1), @@ -54,7 +58,7 @@ def test_entry__getter(self): self.assertIs(self.aa1.entry, self.arr) def test_entry__setter(self): - ar2 = sym.ID("test2", 1, type_=Type.ubyte).to_vararray(self.bounds) + ar2 = sym.ID("test2", 1, type_ref=self._type_ref(Type.ubyte)).to_vararray(self.bounds) self.aa1.entry = ar2 self.assertIs(self.aa1.entry, ar2) @@ -66,26 +70,26 @@ def test_scope(self): self.assertEqual(self.aa1.scope, self.arr.scope) def test_make_node(self): - gl.SYMBOL_TABLE.declare_array("test", 1, sym.TYPEREF(self.arr.type_, 1), bounds=self.bounds) + gl.SYMBOL_TABLE.declare_array("test", 1, self.arr.type_, bounds=self.bounds) self.aa2 = sym.ARRAYACCESS.make_node("test", self.arg, lineno=2, filename="fake-filename") self.assertIsInstance(self.aa2, sym.ARRAYACCESS) def test_make_node_fail(self): - gl.SYMBOL_TABLE.declare_array("test", 1, sym.TYPEREF(self.arr.type_, 1), bounds=self.bounds) + gl.SYMBOL_TABLE.declare_array("test", 1, self.arr.type_, bounds=self.bounds) self.arg = sym.ARGLIST(sym.ARGUMENT(sym.NUMBER(2, 1), 1)) self.aa2 = sym.ARRAYACCESS.make_node("test", self.arg, lineno=2, filename="fake-filename") self.assertIsNone(self.aa2) self.assertEqual(self.OUTPUT, "(stdin):2: error: Array 'test' has 2 dimensions, not 1\n") def test_make_node_warn(self): - gl.SYMBOL_TABLE.declare_array("test", 1, sym.TYPEREF(self.arr.type_, 1), bounds=self.bounds) + gl.SYMBOL_TABLE.declare_array("test", 1, self.arr.type_, bounds=self.bounds) self.arg[1] = sym.ARGUMENT(sym.NUMBER(9, 1), 1) self.aa2 = sym.ARRAYACCESS.make_node("test", self.arg, lineno=2, filename="fake-filename") self.assertIsNotNone(self.aa2) self.assertEqual(self.OUTPUT, "(stdin):2: warning: Array 'test' subscript out of range\n") def test_offset(self): - gl.SYMBOL_TABLE.declare_array("test", 1, sym.TYPEREF(self.arr.type_, 1), bounds=self.bounds) + gl.SYMBOL_TABLE.declare_array("test", 1, self.arr.type_, bounds=self.bounds) self.aa2 = sym.ARRAYACCESS.make_node("test", self.arg, lineno=2, filename="fake-filename") self.assertIsInstance(self.aa2, sym.ARRAYACCESS) self.assertIsNotNone(self.aa2.offset) diff --git a/tests/symbols/test_symbolBASICTYPE.py b/tests/symbols/test_symbolBASICTYPE.py index 9eab67d54..b206750d2 100644 --- a/tests/symbols/test_symbolBASICTYPE.py +++ b/tests/symbols/test_symbolBASICTYPE.py @@ -52,7 +52,7 @@ def test__ne__(self): def test_to_signed(self): for type_ in TYPE.types: - if type_ in {TYPE.unknown, TYPE.string, TYPE.boolean}: + if type_ in {TYPE.unknown, TYPE.string, TYPE.boolean, TYPE.nil}: continue t = SymbolBASICTYPE(type_) @@ -62,7 +62,7 @@ def test_to_signed(self): def test_bool(self): for type_ in TYPE.types: t = SymbolBASICTYPE(type_) - if t.type_ == TYPE.unknown: + if t == Type.unknown: self.assertFalse(t) else: self.assertTrue(t) diff --git a/tests/symbols/test_symbolBINARY.py b/tests/symbols/test_symbolBINARY.py index 369395f8f..22c3cdead 100644 --- a/tests/symbols/test_symbolBINARY.py +++ b/tests/symbols/test_symbolBINARY.py @@ -10,14 +10,14 @@ from src.api.config import OPTIONS, Action from src.symbols import sym -from src.symbols.type_ import Type +from src.symbols.type_ import SymbolTYPEREF, Type from src.zxbpp import zxbpp class TestSymbolBINARY(TestCase): def setUp(self): zxbpp.init() - self.l = sym.ID("a", lineno=1, type_=Type.ubyte).to_var() + self.l = sym.ID("a", lineno=1, type_ref=SymbolTYPEREF(Type.ubyte, 0)).to_var() self.r = sym.NUMBER(3, lineno=2) self.b = sym.BINARY("PLUS", self.l, self.r, lineno=3) self.st = sym.STRING("ZXBASIC", lineno=1) diff --git a/tests/symbols/test_symbolFUNCDECL.py b/tests/symbols/test_symbolFUNCDECL.py index b477a3f25..f1ee0f21d 100644 --- a/tests/symbols/test_symbolFUNCDECL.py +++ b/tests/symbols/test_symbolFUNCDECL.py @@ -12,13 +12,13 @@ import src.api.symboltable.symboltable from src.api.constants import CLASS from src.symbols.sym import FUNCDECL -from src.symbols.type_ import Type +from src.symbols.type_ import SymbolTYPEREF, Type class TestSymbolFUNCDECL(TestCase): def setUp(self): src.api.global_.SYMBOL_TABLE = src.api.symboltable.symboltable.SymbolTable() - self.f = gl.SYMBOL_TABLE.declare_func("f", 1, type_=Type.ubyte) + self.f = gl.SYMBOL_TABLE.declare_func("f", 1, type_=SymbolTYPEREF(Type.ubyte, 0)) self.s = FUNCDECL(self.f, 1) def test__init__fail(self): diff --git a/tests/symbols/test_symbolSTRSLICE.py b/tests/symbols/test_symbolSTRSLICE.py index 151efceed..882e001af 100644 --- a/tests/symbols/test_symbolSTRSLICE.py +++ b/tests/symbols/test_symbolSTRSLICE.py @@ -59,6 +59,6 @@ def test_make_node(self): self.assertEqual(s.value, "XB") def test_make_node_wrong(self): - bad_index = sym.ID("a", 0, type_=gl.SYMBOL_TABLE.basic_types[gl.TYPE.string]).to_var() + bad_index = sym.ID("a", 0, type_ref=sym.TYPEREF(gl.SYMBOL_TABLE.basic_types[gl.TYPE.string], 0)).to_var() s = sym.STRSLICE.make_node(1, self.str_, bad_index, bad_index) self.assertIsNone(s) diff --git a/tests/symbols/test_symbolTYPE.py b/tests/symbols/test_symbolTYPE.py index 3c691c82f..33d40e4ec 100644 --- a/tests/symbols/test_symbolTYPE.py +++ b/tests/symbols/test_symbolTYPE.py @@ -14,11 +14,12 @@ class TestSymbolTYPE(TestCase): def test__eq__(self): for t1_ in TYPE.types: - t1 = SymbolBASICTYPE(t1_) + t = SymbolBASICTYPE(t1_) for t2_ in TYPE.types: - t2 = SymbolBASICTYPE(t2_) - t = SymbolTYPE("test_type", 0, t1, t2) - tt = SymbolTYPE("other_type", 0, t) + if t1_ == t2_: + continue + + tt = SymbolBASICTYPE(t2_) self.assertTrue(t == t) self.assertFalse(t != t) self.assertFalse(tt == t) @@ -46,14 +47,13 @@ def test_is_alias(self): def test_size(self): for t1_ in TYPE.types: - t1 = SymbolBASICTYPE(t1_) + t = SymbolBASICTYPE(t1_) for t2_ in TYPE.types: - t2 = SymbolBASICTYPE(t2_) - t = SymbolTYPE("test_type", 0, t1, t2) - self.assertEqual(t.size, t1.size + t2.size) + tt = SymbolBASICTYPE(t2_) + self.assertEqual(TYPE.size(t1_) + TYPE.size(t2_), t.size + tt.size) def test_cmp_types(self): """Test == operator for different types""" tr = SymbolTYPEREF(Type.unknown, 0) self.assertTrue(tr == Type.unknown) - self.assertRaises(AssertionError, tr.__eq__, TYPE.unknown) + self.assertRaises(NotImplementedError, tr.__eq__, "dummy") diff --git a/tests/symbols/test_symbolTYPEALIAS.py b/tests/symbols/test_symbolTYPEALIAS.py index 53715188b..0c3ea2dcf 100644 --- a/tests/symbols/test_symbolTYPEALIAS.py +++ b/tests/symbols/test_symbolTYPEALIAS.py @@ -15,16 +15,16 @@ class TestSymbolTYPEALIAS(TestCase): def test__eq__(self): for type_ in TYPE.types: t = SymbolBASICTYPE(type_) - ta = SymbolTYPEALIAS("alias", 0, t) + ta = SymbolTYPEALIAS("alias", "", alias=t, lineno=0) self.assertEqual(t.size, ta.size) self.assertTrue(ta == ta) - self.assertTrue(t == ta) - self.assertTrue(ta == t) + self.assertTrue(t.final == ta.final) + self.assertTrue(ta.final == t.final) def test_is_alias(self): for type_ in TYPE.types: t = SymbolBASICTYPE(type_) - ta = SymbolTYPEALIAS("alias", 0, t) + ta = SymbolTYPEALIAS("alias", "", alias=t, lineno=0) self.assertTrue(ta.is_alias) self.assertTrue(ta.is_basic) self.assertFalse(t.is_alias) diff --git a/tests/symbols/test_symbolTYPECAST.py b/tests/symbols/test_symbolTYPECAST.py index f5213aa74..b5c5f8a25 100644 --- a/tests/symbols/test_symbolTYPECAST.py +++ b/tests/symbols/test_symbolTYPECAST.py @@ -10,7 +10,7 @@ from src.api.config import OPTIONS, Action from src.symbols.sym import ID, NUMBER, TYPECAST -from src.symbols.type_ import Type +from src.symbols.type_ import SymbolTYPEREF, Type from src.zxbpp import zxbpp @@ -48,7 +48,7 @@ def test_make_node(self): def test_make_const(self): """Must return a number""" - v = ID("a", lineno=1, type_=Type.byte_).to_const(NUMBER(3, lineno=1)) + v = ID("a", lineno=1, type_ref=SymbolTYPEREF(Type.byte_, 0)).to_const(NUMBER(3, lineno=1)) t = TYPECAST.make_node(Type.float_, v, lineno=2) self.assertIsInstance(t, NUMBER) self.assertEqual(t, 3) diff --git a/tests/symbols/test_symbolVAR.py b/tests/symbols/test_symbolVAR.py index 74f1d1235..24abfdeab 100644 --- a/tests/symbols/test_symbolVAR.py +++ b/tests/symbols/test_symbolVAR.py @@ -9,7 +9,7 @@ from src.api.constants import SCOPE from src.symbols import sym -from src.symbols.type_ import Type +from src.symbols.type_ import SymbolTYPEREF, Type class TestSymbolVAR(TestCase): @@ -21,7 +21,7 @@ def test__init__fail(self): def test_size(self): self.assertIsNone(self.v.type_) - self.v.type_ = Type.byte_ + self.v.type_ = SymbolTYPEREF(Type.byte_, 0) self.assertEqual(self.v.type_, Type.byte_) def test_set_value(self): @@ -43,7 +43,7 @@ def test_t_const(self): self.assertEqual(self.v.t, "54321") def test_type_(self): - self.v.type_ = Type.byte_ + self.v.type_ = SymbolTYPEREF(Type.byte_, 0) self.assertEqual(self.v.type_, Type.byte_) def test_type_fail(self): diff --git a/tests/symbols/test_symbolVARARRAY.py b/tests/symbols/test_symbolVARARRAY.py index 440d2e173..1dfbd2d4c 100644 --- a/tests/symbols/test_symbolVARARRAY.py +++ b/tests/symbols/test_symbolVARARRAY.py @@ -23,7 +23,7 @@ def setUp(self): b = sym.BOUND(l1, l2) c = sym.BOUND(l3, l4) self.bounds = sym.BOUNDLIST.make_node(None, b, c) - self.arr = sym.ID("test", 1, type_=Type.ubyte).to_vararray(self.bounds) + self.arr = sym.ID("test", 1, type_ref=sym.TYPEREF(Type.ubyte, 0)).to_vararray(self.bounds) def test__init__fail(self): self.assertRaises(AssertionError, sym.ID("test", 2).to_vararray, "blahblah")