diff --git a/src/ytdl_sub/script/functions/boolean_functions.py b/src/ytdl_sub/script/functions/boolean_functions.py index ac466883..b29f4d2e 100644 --- a/src/ytdl_sub/script/functions/boolean_functions.py +++ b/src/ytdl_sub/script/functions/boolean_functions.py @@ -1,4 +1,4 @@ -from ytdl_sub.script.types.resolvable import AnyType +from ytdl_sub.script.types.resolvable import AnyArgument from ytdl_sub.script.types.resolvable import Boolean # pylint: disable=invalid-name @@ -10,49 +10,49 @@ class BooleanFunctions: """ @staticmethod - def bool(value: AnyType) -> Boolean: + def bool(value: AnyArgument) -> Boolean: """ Cast any type to a Boolean. """ return Boolean(bool(value.value)) @staticmethod - def eq(left: AnyType, right: AnyType) -> Boolean: + def eq(left: AnyArgument, right: AnyArgument) -> Boolean: """ ``==`` operator. Returns True if left == right. False otherwise. """ return Boolean(left.value == right.value) @staticmethod - def ne(left: AnyType, right: AnyType) -> Boolean: + def ne(left: AnyArgument, right: AnyArgument) -> Boolean: """ ``!=`` operator. Returns True if left != right. False otherwise. """ return Boolean(left.value != right.value) @staticmethod - def lt(left: AnyType, right: AnyType) -> Boolean: + def lt(left: AnyArgument, right: AnyArgument) -> Boolean: """ ``<`` operator. Returns True if left < right. False otherwise. """ return Boolean(left.value < right.value) @staticmethod - def lte(left: AnyType, right: AnyType) -> Boolean: + def lte(left: AnyArgument, right: AnyArgument) -> Boolean: """ ``<=`` operator. Returns True if left <= right. False otherwise. """ return Boolean(left.value <= right.value) @staticmethod - def gt(left: AnyType, right: AnyType) -> Boolean: + def gt(left: AnyArgument, right: AnyArgument) -> Boolean: """ ``>`` operator. Returns True if left > right. False otherwise. """ return Boolean(left.value > right.value) @staticmethod - def gte(left: AnyType, right: AnyType) -> Boolean: + def gte(left: AnyArgument, right: AnyArgument) -> Boolean: """ ``>=`` operator. Returns True if left >= right. False otherwise. """ diff --git a/src/ytdl_sub/script/functions/conditional_functions.py b/src/ytdl_sub/script/functions/conditional_functions.py index 4faaa2b4..291455a3 100644 --- a/src/ytdl_sub/script/functions/conditional_functions.py +++ b/src/ytdl_sub/script/functions/conditional_functions.py @@ -1,15 +1,15 @@ from typing import Union -from ytdl_sub.script.types.resolvable import AnyTypeReturnableA -from ytdl_sub.script.types.resolvable import AnyTypeReturnableB from ytdl_sub.script.types.resolvable import Boolean +from ytdl_sub.script.types.resolvable import ReturnableArgumentA +from ytdl_sub.script.types.resolvable import ReturnableArgumentB class ConditionalFunctions: @staticmethod def if_( - condition: Boolean, true: AnyTypeReturnableA, false: AnyTypeReturnableB - ) -> Union[AnyTypeReturnableA, AnyTypeReturnableB]: + condition: Boolean, true: ReturnableArgumentA, false: ReturnableArgumentB + ) -> Union[ReturnableArgumentA, ReturnableArgumentB]: """ Conditional ``if`` statement that returns the ``true`` or ``false`` parameter depending on the ``condition`` value. diff --git a/src/ytdl_sub/script/functions/error_functions.py b/src/ytdl_sub/script/functions/error_functions.py index 133c40c0..352e80a9 100644 --- a/src/ytdl_sub/script/functions/error_functions.py +++ b/src/ytdl_sub/script/functions/error_functions.py @@ -1,4 +1,4 @@ -from ytdl_sub.script.types.resolvable import AnyType +from ytdl_sub.script.types.resolvable import AnyArgument from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError @@ -6,7 +6,7 @@ from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError class ErrorFunctions: @staticmethod - def throw(error_message: String) -> AnyType: + def throw(error_message: String) -> AnyArgument: """ Explicitly throw an error with the provided error message. """ diff --git a/src/ytdl_sub/script/functions/map_functions.py b/src/ytdl_sub/script/functions/map_functions.py index 1b2067b6..0559336d 100644 --- a/src/ytdl_sub/script/functions/map_functions.py +++ b/src/ytdl_sub/script/functions/map_functions.py @@ -1,13 +1,13 @@ from typing import Optional from ytdl_sub.script.types.map import Map -from ytdl_sub.script.types.resolvable import AnyType +from ytdl_sub.script.types.resolvable import AnyArgument from ytdl_sub.script.types.resolvable import Hashable class MapFunctions: @staticmethod - def map_get(mapping: Map, key: Hashable, default: Optional[AnyType] = None) -> AnyType: + def map_get(mapping: Map, key: Hashable, default: Optional[AnyArgument] = None) -> AnyArgument: """ Return ``key``'s value within the Map. If ``key`` does not exist, and ``default`` is provided, it will return ``default``. Otherwise, will error. diff --git a/src/ytdl_sub/script/functions/string_functions.py b/src/ytdl_sub/script/functions/string_functions.py index 37751a6c..61e42542 100644 --- a/src/ytdl_sub/script/functions/string_functions.py +++ b/src/ytdl_sub/script/functions/string_functions.py @@ -1,13 +1,13 @@ from typing import Optional -from ytdl_sub.script.types.resolvable import AnyType +from ytdl_sub.script.types.resolvable import AnyArgument from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.types.resolvable import String class StringFunctions: @staticmethod - def string(value: AnyType) -> String: + def string(value: AnyArgument) -> String: """ Cast to String. """ diff --git a/src/ytdl_sub/script/parser.py b/src/ytdl_sub/script/parser.py index cb6a15f2..db1a040b 100644 --- a/src/ytdl_sub/script/parser.py +++ b/src/ytdl_sub/script/parser.py @@ -4,7 +4,7 @@ from typing import List from typing import Optional from ytdl_sub.script.types.array import UnresolvedArray -from ytdl_sub.script.types.function import ArgumentType +from ytdl_sub.script.types.function import Argument from ytdl_sub.script.types.function import Function from ytdl_sub.script.types.map import UnresolvedMap from ytdl_sub.script.types.resolvable import Boolean @@ -114,7 +114,7 @@ class _Parser: self._custom_function_name = custom_function_name self._pos = 0 self._error_highlight_pos = 0 - self._ast: List[ArgumentType] = [] + self._ast: List[Argument] = [] try: self._syntax_tree = self._parse() @@ -267,7 +267,7 @@ class _Parser: raise STRINGS_NOT_CLOSED - def _parse_function_arg(self, argument_parser: ParsedArgType) -> ArgumentType: + def _parse_function_arg(self, argument_parser: ParsedArgType) -> Argument: if self._read(increment_pos=False) == "%": self._pos += 1 return self._parse_function() @@ -298,12 +298,12 @@ class _Parser: def _parse_args( self, argument_parser: ParsedArgType, breaking_chars: str = ")" - ) -> List[ArgumentType]: + ) -> List[Argument]: """ Begin parsing function args after the first ``(``, i.e. ``function_name(`` """ comma_count = 0 - arguments: List[ArgumentType] = [] + arguments: List[Argument] = [] while ch := self._read(increment_pos=False): if ch in breaking_chars: # i.e. ["arg", ] which is invalid @@ -330,7 +330,7 @@ class _Parser: Begin parsing a function after reading the first ``%`` """ function_name: str = "" - function_args: Optional[List[ArgumentType]] = None + function_args: Optional[List[Argument]] = None function_start_pos = self._pos while ch := self._read(): @@ -351,7 +351,7 @@ class _Parser: # Go back one so the parent function can close using the ')' self._pos -= 1 - return Lambda(function_name=function_name) + return Lambda(value=function_name) if _is_function_name_char(ch): function_name += ch @@ -359,7 +359,7 @@ class _Parser: function_args = self._parse_args(argument_parser=ParsedArgType.FUNCTION) elif ch.isspace() or ch == ",": # function with no args, it's a lambda - return Lambda(function_name=function_name) + return Lambda(value=function_name) else: break @@ -370,7 +370,7 @@ class _Parser: """ Begin parsing an array after reading the first ``[`` """ - function_args: List[ArgumentType] = [] + function_args: List[Argument] = [] while ch := self._read(increment_pos=False): if ch == "]": @@ -387,8 +387,8 @@ class _Parser: """ Begin parsing a map after reading the first ``{`` """ - output: Dict[ArgumentType, ArgumentType] = {} - key: Optional[ArgumentType] = None + output: Dict[Argument, Argument] = {} + key: Optional[Argument] = None in_comma = False self._set_highlight_position() diff --git a/src/ytdl_sub/script/types/array.py b/src/ytdl_sub/script/types/array.py index cc57f95a..13c4f99d 100644 --- a/src/ytdl_sub/script/types/array.py +++ b/src/ytdl_sub/script/types/array.py @@ -1,14 +1,12 @@ from dataclasses import dataclass from typing import Dict from typing import List -from typing import Set -from ytdl_sub.script.types.resolvable import ArgumentType -from ytdl_sub.script.types.resolvable import FutureResolvable +from ytdl_sub.script.types.resolvable import AnyArgument +from ytdl_sub.script.types.resolvable import Argument from ytdl_sub.script.types.resolvable import NonHashable from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.resolvable import ResolvableToJson -from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.types.variable_dependency import VariableDependency @@ -23,11 +21,11 @@ class Array(NonHashable): @dataclass(frozen=True) -class UnresolvedArray(Array, VariableDependency, FutureResolvable): - value: List[ArgumentType] +class UnresolvedArray(Array, VariableDependency, AnyArgument): + value: List[Argument] @property - def _iterable_arguments(self) -> List[ArgumentType]: + def _iterable_arguments(self) -> List[Argument]: return self.value def resolve( diff --git a/src/ytdl_sub/script/types/function.py b/src/ytdl_sub/script/types/function.py index 705ca3a8..dd63fa36 100644 --- a/src/ytdl_sub/script/types/function.py +++ b/src/ytdl_sub/script/types/function.py @@ -16,14 +16,14 @@ from ytdl_sub.script.functions import Functions from ytdl_sub.script.types.array import Array from ytdl_sub.script.types.array import ResolvedArray from ytdl_sub.script.types.array import UnresolvedArray -from ytdl_sub.script.types.resolvable import AnyTypeReturnable -from ytdl_sub.script.types.resolvable import AnyTypeReturnableA -from ytdl_sub.script.types.resolvable import AnyTypeReturnableB -from ytdl_sub.script.types.resolvable import ArgumentType +from ytdl_sub.script.types.resolvable import Argument from ytdl_sub.script.types.resolvable import FunctionType from ytdl_sub.script.types.resolvable import Lambda from ytdl_sub.script.types.resolvable import NamedCustomFunction from ytdl_sub.script.types.resolvable import Resolvable +from ytdl_sub.script.types.resolvable import ReturnableArgument +from ytdl_sub.script.types.resolvable import ReturnableArgumentA +from ytdl_sub.script.types.resolvable import ReturnableArgumentB from ytdl_sub.script.types.resolvable import TypeHintedFunctionType from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable @@ -42,11 +42,11 @@ from ytdl_sub.utils.exceptions import StringFormattingException @dataclass(frozen=True) class Function(FunctionType, VariableDependency, ABC): @property - def _iterable_arguments(self) -> List[ArgumentType]: + def _iterable_arguments(self) -> List[Argument]: return self.args @classmethod - def from_name_and_args(cls, name: str, args: List[ArgumentType]) -> "Function": + def from_name_and_args(cls, name: str, args: List[Argument]) -> "Function": if Functions.is_built_in(name): return BuiltInFunction(name=name, args=args).validate_args() return CustomFunction(name=name, args=args) @@ -128,7 +128,7 @@ class BuiltInFunction(Function, TypeHintedFunctionType): return Lambda in (self.input_spec.args or []) @classmethod - def _arg_output_type(cls, arg: ArgumentType) -> Type[ArgumentType]: + def _arg_output_type(cls, arg: Argument) -> Type[Argument]: if isinstance(arg, BuiltInFunction): return arg.output_type() return type(arg) @@ -138,7 +138,7 @@ class BuiltInFunction(Function, TypeHintedFunctionType): if is_union(output_type): union_types_list = [] for union_type in output_type.__args__: - if union_type in (AnyTypeReturnable, AnyTypeReturnableA, AnyTypeReturnableB): + if union_type in (ReturnableArgument, ReturnableArgumentA, ReturnableArgumentB): generic_arg_index = self.input_spec.args.index(union_type) union_types_list.append(self._arg_output_type(self.args[generic_arg_index])) else: @@ -166,7 +166,7 @@ class BuiltInFunction(Function, TypeHintedFunctionType): if not self.is_lambda_function or len(function_input_lambda_args) != 1: raise UNREACHABLE - lambda_function_name = function_input_lambda_args[0].function_name + lambda_function_name = function_input_lambda_args[0].value try: lambda_args = self.callable(*resolved_arguments) diff --git a/src/ytdl_sub/script/types/map.py b/src/ytdl_sub/script/types/map.py index df165893..12e722f5 100644 --- a/src/ytdl_sub/script/types/map.py +++ b/src/ytdl_sub/script/types/map.py @@ -4,13 +4,12 @@ from typing import Dict from typing import List from typing import Set -from ytdl_sub.script.types.resolvable import ArgumentType -from ytdl_sub.script.types.resolvable import FutureResolvable +from ytdl_sub.script.types.resolvable import AnyArgument +from ytdl_sub.script.types.resolvable import Argument from ytdl_sub.script.types.resolvable import Hashable from ytdl_sub.script.types.resolvable import NonHashable from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.resolvable import ResolvableToJson -from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.types.variable_dependency import VariableDependency from ytdl_sub.utils.exceptions import StringFormattingException @@ -26,11 +25,11 @@ class Map(NonHashable): @dataclass(frozen=True) -class UnresolvedMap(Map, VariableDependency, FutureResolvable): - value: Dict[ArgumentType, ArgumentType] +class UnresolvedMap(Map, VariableDependency, AnyArgument): + value: Dict[Argument, Argument] @property - def _iterable_arguments(self) -> List[ArgumentType]: + def _iterable_arguments(self) -> List[Argument]: return list(itertools.chain(*self.value.items())) def resolve( diff --git a/src/ytdl_sub/script/types/resolvable.py b/src/ytdl_sub/script/types/resolvable.py index 3bad00e8..d45ff2e2 100644 --- a/src/ytdl_sub/script/types/resolvable.py +++ b/src/ytdl_sub/script/types/resolvable.py @@ -12,6 +12,7 @@ T = TypeVar("T") NumericT = TypeVar("NumericT", bound=int | float) +@dataclass(frozen=True) class NamedType(ABC): @classmethod def type_name(cls) -> str: @@ -23,31 +24,45 @@ class NamedType(ABC): return cls.__name__ -class ArgumentType(NamedType, ABC): +@dataclass(frozen=True) +class Argument(NamedType, ABC): """ Any possible argument type that has not been resolved yet """ -class AnyTypeReturnable(NamedType, ABC): +@dataclass(frozen=True) +class NamedArgument(Argument, ABC): + """ + Argument that has an explicit name (i.e. custom function or variable) + """ + + name: str + + +@dataclass(frozen=True) +class ReturnableArgument(NamedType, ABC): """ AnyType to express generics in functions that are part of the return type """ -class AnyTypeReturnableA(NamedType, ABC): +@dataclass(frozen=True) +class ReturnableArgumentA(NamedType, ABC): """ AnyType to express generics in functions when more than one are present (i.e. `if`) """ -class AnyTypeReturnableB(NamedType, ABC): +@dataclass(frozen=True) +class ReturnableArgumentB(NamedType, ABC): """ AnyType to express generics in functions when more than one are present (i.e. `if`) """ -class AnyType(ArgumentType, AnyTypeReturnable, AnyTypeReturnableA, AnyTypeReturnableB, ABC): +@dataclass(frozen=True) +class AnyArgument(Argument, ReturnableArgument, ReturnableArgumentA, ReturnableArgumentB, ABC): """ Human-readable name for FutureResolvable """ @@ -55,26 +70,23 @@ class AnyType(ArgumentType, AnyTypeReturnable, AnyTypeReturnableA, AnyTypeReturn value: Any -class FutureResolvable(AnyType, ABC): - """ - Type that will be resolved in the future (Map, Array) - """ - - @dataclass(frozen=True) -class Resolvable(AnyType, ABC): +class Resolvable(AnyArgument, ABC): def __str__(self) -> str: return str(self.value) +@dataclass(frozen=True) class Hashable(Resolvable, ABC): pass +@dataclass(frozen=True) class NonHashable(ABC): pass +@dataclass(frozen=True) class ResolvableToJson(Resolvable, ABC): @classmethod def _to_native(cls, to_convert: Resolvable) -> Any: @@ -102,34 +114,33 @@ class Numeric(ResolvableT[NumericT], Hashable, ABC, Generic[NumericT]): @dataclass(frozen=True) -class Integer(Numeric[int], ArgumentType): +class Integer(Numeric[int], Argument): pass @dataclass(frozen=True) -class Float(Numeric[float], ArgumentType): +class Float(Numeric[float], Argument): pass @dataclass(frozen=True) -class Boolean(ResolvableT[bool], Hashable, ArgumentType): +class Boolean(ResolvableT[bool], Hashable, Argument): pass @dataclass(frozen=True) -class String(ResolvableT[str], Hashable, ArgumentType): +class String(ResolvableT[str], Hashable, Argument): pass @dataclass(frozen=True) -class NamedCustomFunction(ArgumentType, ABC): +class NamedCustomFunction(Argument, ABC): name: str @dataclass(frozen=True) -class FunctionType(ArgumentType, ABC): - name: str - args: List[ArgumentType] +class FunctionType(NamedArgument, ABC): + args: List[Argument] @dataclass(frozen=True) @@ -141,4 +152,4 @@ class TypeHintedFunctionType(FunctionType, ABC): @dataclass(frozen=True) class Lambda(Resolvable): - function_name: str + value: str diff --git a/src/ytdl_sub/script/types/syntax_tree.py b/src/ytdl_sub/script/types/syntax_tree.py index 614df80c..530eb4c3 100644 --- a/src/ytdl_sub/script/types/syntax_tree.py +++ b/src/ytdl_sub/script/types/syntax_tree.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from typing import Dict from typing import List -from ytdl_sub.script.types.resolvable import ArgumentType +from ytdl_sub.script.types.resolvable import Argument from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.types.variable import Variable @@ -11,10 +11,10 @@ from ytdl_sub.script.types.variable_dependency import VariableDependency @dataclass(frozen=True) class SyntaxTree(VariableDependency): - ast: List[ArgumentType] + ast: List[Argument] @property - def _iterable_arguments(self) -> List[ArgumentType]: + def _iterable_arguments(self) -> List[Argument]: return self.ast def resolve( diff --git a/src/ytdl_sub/script/types/variable.py b/src/ytdl_sub/script/types/variable.py index 98626d84..62c68798 100644 --- a/src/ytdl_sub/script/types/variable.py +++ b/src/ytdl_sub/script/types/variable.py @@ -1,12 +1,12 @@ from dataclasses import dataclass from typing import Optional -from ytdl_sub.script.types.resolvable import ArgumentType +from ytdl_sub.script.types.resolvable import NamedArgument @dataclass(frozen=True) -class Variable(ArgumentType): - name: str +class Variable(NamedArgument): + pass @dataclass(frozen=True) diff --git a/src/ytdl_sub/script/types/variable_dependency.py b/src/ytdl_sub/script/types/variable_dependency.py index 7c0fbea2..c0182bb7 100644 --- a/src/ytdl_sub/script/types/variable_dependency.py +++ b/src/ytdl_sub/script/types/variable_dependency.py @@ -6,7 +6,7 @@ from typing import List from typing import Set from typing import final -from ytdl_sub.script.types.resolvable import ArgumentType +from ytdl_sub.script.types.resolvable import Argument from ytdl_sub.script.types.resolvable import NamedCustomFunction from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.variable import FunctionArgument @@ -19,7 +19,7 @@ from ytdl_sub.utils.exceptions import StringFormattingException class VariableDependency(ABC): @property @abstractmethod - def _iterable_arguments(self) -> List[ArgumentType]: + def _iterable_arguments(self) -> List[Argument]: pass @final @@ -70,7 +70,7 @@ class VariableDependency(ABC): @classmethod def _resolve_argument_type( cls, - arg: ArgumentType, + arg: Argument, resolved_variables: Dict[Variable, Resolvable], custom_functions: Dict[str, "VariableDependency"], ) -> Resolvable: diff --git a/src/ytdl_sub/script/utils/type_checking.py b/src/ytdl_sub/script/utils/type_checking.py index 138fb96a..b7334517 100644 --- a/src/ytdl_sub/script/utils/type_checking.py +++ b/src/ytdl_sub/script/utils/type_checking.py @@ -5,7 +5,7 @@ from typing import Type from typing import Union from typing import get_origin -from ytdl_sub.script.types.resolvable import ArgumentType +from ytdl_sub.script.types.resolvable import Argument from ytdl_sub.script.types.resolvable import FunctionType from ytdl_sub.script.types.resolvable import NamedType from ytdl_sub.script.types.resolvable import Resolvable @@ -97,7 +97,7 @@ class FunctionInputSpec: def __post_init__(self): assert (self.args is None) ^ (self.varargs is None) - def _is_args_compatible(self, input_args: List[ArgumentType]) -> bool: + def _is_args_compatible(self, input_args: List[Argument]) -> bool: assert self.args is not None if len(input_args) > len(self.args): @@ -110,7 +110,7 @@ class FunctionInputSpec: return True - def _is_varargs_compatible(self, input_args: List[ArgumentType]) -> bool: + def _is_varargs_compatible(self, input_args: List[Argument]) -> bool: assert self.varargs is not None for input_arg in input_args: @@ -119,7 +119,7 @@ class FunctionInputSpec: return True - def is_compatible(self, input_args: List[ArgumentType]) -> bool: + def is_compatible(self, input_args: List[Argument]) -> bool: """ Returns ------- diff --git a/tests/unit/script/test_parser.py b/tests/unit/script/test_parser.py index ee4abea7..c248d85e 100644 --- a/tests/unit/script/test_parser.py +++ b/tests/unit/script/test_parser.py @@ -165,7 +165,7 @@ class TestParser: name="array_apply", args=[ UnresolvedArray(value=[Integer(1)]), - Lambda(function_name="times_two"), + Lambda(value="times_two"), ], ) ] diff --git a/tests/unit/script/types/test_function.py b/tests/unit/script/types/test_function.py index 0f3e6997..9e79da0b 100644 --- a/tests/unit/script/types/test_function.py +++ b/tests/unit/script/types/test_function.py @@ -68,7 +68,7 @@ class TestFunction: with pytest.raises( IncompatibleFunctionArguments, match=_incompatible_arguments_match( - expected="Map, Hashable, Optional[AnyType]", + expected="Map, Hashable, Optional[AnyArgument]", recieved="%if(...)->Union[Map, Array], String", ), ):