This commit is contained in:
Jesse Bannon 2023-11-15 23:16:15 -08:00
parent 96182af2c1
commit acceb50377
6 changed files with 48 additions and 25 deletions

View file

@ -222,13 +222,13 @@ class _Parser:
self._set_highlight_position()
raise NUMERICS_INVALID_CHAR
if numeric_string == "." or numeric_string == "-":
if numeric_string in (".", "-"):
raise NUMERICS_INVALID_CHAR
try:
numeric_float = float(numeric_string)
except ValueError:
raise UNREACHABLE
except ValueError as exc:
raise UNREACHABLE from exc
if (numeric_int := int(numeric_float)) == numeric_float:
return Integer(value=numeric_int)
@ -351,10 +351,10 @@ class _Parser:
if ch == "]":
self._pos += 1
return UnresolvedArray(value=function_args)
else:
function_args = self._parse_args(
argument_parser=ArgumentParser.ARRAY, breaking_chars="]"
)
function_args = self._parse_args(
argument_parser=ArgumentParser.ARRAY, breaking_chars="]"
)
raise UNREACHABLE

View file

@ -19,7 +19,7 @@ class Array(NonHashable):
value: List[Resolvable]
@classmethod
def human_readable_name(cls) -> str:
def type_name(cls) -> str:
return "Array"

View file

@ -18,6 +18,7 @@ from ytdl_sub.script.types.resolvable import AnyType_0
from ytdl_sub.script.types.resolvable import AnyType_1
from ytdl_sub.script.types.resolvable import AnyType_2
from ytdl_sub.script.types.resolvable import ArgumentType
from ytdl_sub.script.types.resolvable import NamedType
from ytdl_sub.script.types.resolvable import Resolvable
from ytdl_sub.script.types.variable import FunctionArgument
from ytdl_sub.script.types.variable import Variable
@ -30,6 +31,14 @@ def is_union(arg_type: Type) -> bool:
return get_origin(arg_type) is Union
def is_optional(arg_type: Type) -> bool:
return is_union(arg_type) and type(None) in arg_type.__args__
def get_optional_type(optional_type: Type) -> Type[NamedType]:
return [arg for arg in optional_type.__args__ if arg != type(None)][0]
@dataclass(frozen=True)
class FunctionInputSpec:
args: Optional[List[Type[Resolvable | Optional[Resolvable]]]] = None
@ -110,10 +119,17 @@ class FunctionInputSpec:
assert False, "should never reach here"
def expected_args_str(self) -> str:
def to_human_readable_name(python_type: Type[NamedType] | Type[Union[NamedType]]) -> str:
if is_optional(python_type):
return f"Optional[{to_human_readable_name(get_optional_type(python_type))}]"
if is_union(python_type):
return ", ".join(to_human_readable_name(arg) for arg in python_type.__args__)
return python_type.type_name()
if self.args is not None:
return f"({', '.join([type_.__name__ for type_ in self.args])})"
return f"({', '.join([to_human_readable_name(type_) for type_ in self.args])})"
elif self.varargs is not None:
return f"({self.varargs.__name__}, ...)"
return f"({to_human_readable_name(self.varargs.__name__)}, ...)"
@classmethod
def from_function(cls, func: "BuiltInFunction") -> "FunctionInputSpec":
@ -210,12 +226,12 @@ class BuiltInFunction(Function):
if is_union(arg.output_type):
# TODO: Move naming to separate function, deal with Union input naming
received_type_names.append(
f"%{arg.name}(...)->Union[{', '.join(arg_type.human_readable_name() for arg_type in arg.output_type.__args__)}]"
f"%{arg.name}(...)->Union[{', '.join(arg_type.type_name() for arg_type in arg.output_type.__args__)}]"
)
else:
received_type_names.append(f"%{arg.name}(...)->{arg.output_type.__name__}")
received_type_names.append(f"%{arg.name}(...)->{arg.output_type.type_name()}")
else:
received_type_names.append(arg.__class__.__name__)
received_type_names.append(arg.type_name())
received_args_str = f"({', '.join([name for name in received_type_names])})"
@ -224,7 +240,7 @@ class BuiltInFunction(Function):
def validate_args(self) -> "BuiltInFunction":
if not self.input_spec.is_compatible(input_args=self.args):
raise IncompatibleFunctionArguments(
f"Invalid arguments passed to function {self.name}.\n"
f"Incompatible arguments passed to function {self.name}.\n"
f"{self._expected_received_error_msg()}"
)
return self

View file

@ -21,7 +21,7 @@ class Map(NonHashable):
value: Dict[Hashable, Resolvable]
@classmethod
def human_readable_name(cls) -> str:
def type_name(cls) -> str:
return "Map"

View file

@ -10,25 +10,27 @@ T = TypeVar("T")
NumericT = TypeVar("NumericT", bound=int | float)
class ArgumentType(ABC):
class NamedType(ABC):
@classmethod
def type_name(cls) -> str:
return cls.__name__
class ArgumentType(NamedType, ABC):
"""
Any possible argument type that has not been resolved yet
"""
@classmethod
def human_readable_name(cls) -> str:
return cls.__name__
class AnyType_0(ABC):
class AnyType_0(NamedType, ABC):
pass
class AnyType_1(ABC):
class AnyType_1(NamedType, ABC):
pass
class AnyType_2(ABC):
class AnyType_2(NamedType, ABC):
pass

View file

@ -74,7 +74,8 @@ class TestFunction:
with pytest.raises(
IncompatibleFunctionArguments,
match=_incompatible_arguments_match(
expected="Map, Hashable, Optional", recieved="%if(...)->Union[Map, Array], String"
expected="Map, Hashable, Optional[AnyType]",
recieved="%if(...)->Union[Map, Array], String",
),
):
Script({"func": function_str}).resolve()
@ -83,4 +84,8 @@ class TestFunction:
"function_str", ["{%array_at({'a': 'dict?'}, 1)}" "{%array_extend('not', 'array')}"]
)
def test_incompatible_types(self, function_str):
assert Script({"func": function_str}).resolve()
with pytest.raises(
IncompatibleFunctionArguments,
match=_incompatible_arguments_match(expected="Array, Integer", recieved="Map, Integer"),
):
Script({"func": function_str}).resolve()