Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refact/improve typing #905

Merged
merged 2 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 32 additions & 22 deletions src/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

import enum
import os
from typing import Optional, Union
from enum import StrEnum
from typing import Final, Optional, Union

from .decorator import classproperty

Expand All @@ -29,7 +30,7 @@


@enum.unique
class CLASS(str, enum.Enum):
class CLASS(StrEnum):
"""Enums class constants"""

unknown = "unknown" # 0
Expand Down Expand Up @@ -80,10 +81,12 @@ class TYPE(enum.IntEnum):
fixed = 7
float = 8
string = 9
boolean = 10

@classmethod
def type_size(cls, type_: "TYPE"):
def type_size(cls, type_: "TYPE") -> int:
type_sizes = {
cls.boolean: 1,
cls.byte: 1,
cls.ubyte: 1,
cls.integer: 2,
Expand All @@ -98,59 +101,66 @@ def type_size(cls, type_: "TYPE"):
return type_sizes[type_]

@classproperty
def types(cls):
def types(cls) -> set["TYPE"]:
return set(TYPE)

@classmethod
def size(cls, type_: "TYPE"):
def size(cls, type_: "TYPE") -> int:
return cls.type_size(type_)

@classproperty
def integral(cls):
return {cls.byte, cls.ubyte, cls.integer, cls.uinteger, cls.long, cls.ulong}
def integral(cls) -> set["TYPE"]:
return {cls.boolean, cls.byte, cls.ubyte, cls.integer, cls.uinteger, cls.long, cls.ulong}

@classproperty
def signed(cls):
def signed(cls) -> set["TYPE"]:
return {cls.byte, cls.integer, cls.long, cls.fixed, cls.float}

@classproperty
def unsigned(cls):
return {cls.ubyte, cls.uinteger, cls.ulong}
def unsigned(cls) -> set["TYPE"]:
return {cls.boolean, cls.ubyte, cls.uinteger, cls.ulong}

@classproperty
def decimals(cls):
def decimals(cls) -> set["TYPE"]:
return {cls.fixed, cls.float}

@classproperty
def numbers(cls):
return set(cls.integral) | set(cls.decimals)
def numbers(cls) -> set["TYPE"]:
return cls.integral | cls.decimals

@classmethod
def is_valid(cls, type_: "TYPE"):
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"):
def is_signed(cls, type_: "TYPE") -> bool:
return type_ in cls.signed

@classmethod
def is_unsigned(cls, type_: "TYPE"):
def is_unsigned(cls, type_: "TYPE") -> bool:
return type_ in cls.unsigned

@classmethod
def to_signed(cls, type_: "TYPE"):
def to_signed(cls, type_: "TYPE") -> "TYPE":
"""Return signed type or equivalent"""
if type_ in cls.unsigned:
return {TYPE.ubyte: TYPE.byte, TYPE.uinteger: TYPE.integer, TYPE.ulong: TYPE.long}[type_]
return {
TYPE.boolean: TYPE.boolean,
TYPE.ubyte: TYPE.byte,
TYPE.uinteger: TYPE.integer,
TYPE.ulong: TYPE.long,
}[type_]

if type_ in cls.decimals or type_ in cls.signed:
return type_

return cls.unknown

@staticmethod
def to_string(type_: "TYPE"):
def to_string(type_: "TYPE") -> str:
"""Return ID representation (string) of a type"""
return type_.name

Expand All @@ -173,11 +183,11 @@ class SCOPE(str, enum.Enum):
parameter = "parameter"

@staticmethod
def is_valid(scope: Union[str, "SCOPE"]):
def is_valid(scope: Union[str, "SCOPE"]) -> bool:
return scope in set(SCOPE)

@staticmethod
def to_string(scope: "SCOPE"):
def to_string(scope: "SCOPE") -> str:
assert SCOPE.is_valid(scope)
return scope.value

Expand Down Expand Up @@ -208,7 +218,7 @@ class LoopType(str, enum.Enum):
# ----------------------------------------------------------------------
# Deprecated suffixes for variable names, such as "a$"
# ----------------------------------------------------------------------
DEPRECATED_SUFFIXES = ("$", "%", "&")
DEPRECATED_SUFFIXES: Final[frozenset[str]] = frozenset(("$", "%", "&"))

# ----------------------------------------------------------------------
# Identifier type
Expand Down
7 changes: 6 additions & 1 deletion src/api/decorator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections.abc import Callable


class classproperty:
class ClassProperty:
"""Decorator for class properties.
Use @classproperty instead of @property to add properties
to the class object.
Expand All @@ -12,3 +12,8 @@ def __init__(self, fget: Callable[[type], Callable]) -> None:

def __get__(self, owner_self, owner_cls: type):
return self.fget(owner_cls)


def classproperty(fget: Callable[[type], Callable]) -> ClassProperty:
"""Use this function as the decorator in lowercase to follow Python conventions."""
return ClassProperty(fget)
45 changes: 23 additions & 22 deletions src/symbols/type_.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ def __bool__(self):


class SymbolBASICTYPE(SymbolTYPE):
"""Defines a basic type (Ubyte, Byte, etc..)
Basic (default) types are defined upon start and are case insensitive.
"""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.
"""

Expand Down Expand Up @@ -237,73 +237,74 @@ class Type:
fixed = SymbolBASICTYPE(TYPE.fixed)
float_ = SymbolBASICTYPE(TYPE.float)
string = SymbolBASICTYPE(TYPE.string)
boolean = SymbolBASICTYPE(TYPE.boolean)

types = [unknown, ubyte, byte_, uinteger, integer, ulong, long_, fixed, float_, string]
types = unknown, ubyte, byte_, uinteger, integer, ulong, long_, fixed, float_, string, boolean

_by_name = {x.name: x for x in types}

@staticmethod
def size(t: SymbolTYPE):
def size(t: SymbolTYPE) -> int:
assert isinstance(t, SymbolTYPE)
return t.size

@staticmethod
def to_string(t: SymbolTYPE):
def to_string(t: SymbolTYPE) -> str:
assert isinstance(t, SymbolTYPE)
return t.name

@classmethod
def by_name(cls, typename):
def by_name(cls, typename) -> SymbolBASICTYPE | None:
"""Converts a given typename to Type"""
return cls._by_name.get(typename, None)

@classproperty
def integrals(cls):
return (cls.byte_, cls.ubyte, cls.integer, cls.uinteger, cls.long_, cls.ulong)
def integrals(cls) -> set[SymbolBASICTYPE]:
return {cls.boolean, cls.byte_, cls.ubyte, cls.integer, cls.uinteger, cls.long_, cls.ulong}

@classproperty
def signed(cls):
return cls.byte_, cls.integer, cls.long_, cls.fixed, cls.float_
def signed(cls) -> set[SymbolBASICTYPE]:
return {cls.byte_, cls.integer, cls.long_, cls.fixed, cls.float_}

@classproperty
def unsigned(cls):
return cls.ubyte, cls.uinteger, cls.ulong
def unsigned(cls) -> set[SymbolBASICTYPE]:
return {cls.boolean, cls.ubyte, cls.uinteger, cls.ulong}

@classproperty
def decimals(cls):
return cls.fixed, cls.float_
def decimals(cls) -> set[SymbolBASICTYPE]:
return {cls.fixed, cls.float_}

@classproperty
def numbers(cls):
return tuple(list(cls.integrals) + list(cls.decimals))
def numbers(cls) -> set[SymbolBASICTYPE]:
return cls.integrals | cls.decimals

@classmethod
def is_numeric(cls, t: SymbolTYPE):
def is_numeric(cls, t: SymbolTYPE) -> bool:
assert isinstance(t, SymbolTYPE)
return t.final in cls.numbers

@classmethod
def is_signed(cls, t: SymbolTYPE):
def is_signed(cls, t: SymbolTYPE) -> bool:
assert isinstance(t, SymbolTYPE)
return t.final in cls.signed

@classmethod
def is_unsigned(cls, t: SymbolTYPE):
def is_unsigned(cls, t: SymbolTYPE) -> bool:
assert isinstance(t, SymbolTYPE)
return t.final in cls.unsigned

@classmethod
def is_integral(cls, t: SymbolTYPE):
def is_integral(cls, t: SymbolTYPE) -> bool:
assert isinstance(t, SymbolTYPE)
return t.final in cls.integrals

@classmethod
def is_decimal(cls, t: SymbolTYPE):
def is_decimal(cls, t: SymbolTYPE) -> bool:
assert isinstance(t, SymbolTYPE)
return t.final in cls.decimals

@classmethod
def is_string(cls, t: SymbolTYPE):
def is_string(cls, t: SymbolTYPE) -> bool:
assert isinstance(t, SymbolTYPE)
return t.final == cls.string

Expand Down
2 changes: 1 addition & 1 deletion tests/runtime/check_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import signal
import sys

import zx
import zx # type: ignore[import-untyped]


def signal_handler(sig, frame):
Expand Down
3 changes: 2 additions & 1 deletion tests/runtime/update_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import sys

import zx
import zx # type: ignore[import-untyped]


class Stop(Exception):
Expand Down Expand Up @@ -46,6 +46,7 @@ def run_test(self, filename):
def main():
with TakeSnapshot() as t:
t.run_test(sys.argv[1])

print("OK")


Expand Down
3 changes: 2 additions & 1 deletion tests/symbols/test_symbolBASICTYPE.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ def test__ne__(self):

def test_to_signed(self):
for type_ in TYPE.types:
if type_ is TYPE.unknown or type_ == TYPE.string:
if type_ in {TYPE.unknown, TYPE.string, TYPE.boolean}:
continue

t = SymbolBASICTYPE(type_)
q = t.to_signed()
self.assertTrue(q.is_signed)
Expand Down