Skip to content

Commit

Permalink
Merge pull request #905 from boriel-basic/refact/improve_typing
Browse files Browse the repository at this point in the history
Refact/improve typing
  • Loading branch information
boriel authored Nov 23, 2024
2 parents 298c591 + a76a88f commit 518068b
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 48 deletions.
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

0 comments on commit 518068b

Please sign in to comment.