work to validate funcs
This commit is contained in:
parent
c4f9bf4fa0
commit
3174225f55
12 changed files with 75 additions and 33 deletions
|
|
@ -1,4 +1,5 @@
|
|||
from ytdl_sub.script.types.resolvable import Boolean, AnyType
|
||||
from ytdl_sub.script.types.resolvable import AnyType
|
||||
from ytdl_sub.script.types.resolvable import Boolean
|
||||
|
||||
|
||||
class BooleanFunctions:
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ from typing import Optional
|
|||
|
||||
from ytdl_sub.script.types.array import Array
|
||||
from ytdl_sub.script.types.map import Map
|
||||
from ytdl_sub.script.types.resolvable import AnyType
|
||||
from ytdl_sub.script.types.resolvable import Hashable
|
||||
from ytdl_sub.script.types.resolvable import Resolvable
|
||||
from ytdl_sub.script.types.resolvable import AnyType
|
||||
from ytdl_sub.script.types.resolvable import String
|
||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ class MapFunctions:
|
|||
return Map(output)
|
||||
|
||||
@staticmethod
|
||||
def get(mapping: Map, key: Hashable, default: Optional[AnyType] = None) -> AnyType:
|
||||
def map_get(mapping: Map, key: Hashable, default: Optional[AnyType] = None) -> AnyType:
|
||||
if key not in mapping.value:
|
||||
if default is not None:
|
||||
return default
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from ytdl_sub.script.types.resolvable import AnyType
|
||||
from ytdl_sub.script.types.resolvable import Float
|
||||
from ytdl_sub.script.types.resolvable import Integer
|
||||
from ytdl_sub.script.types.resolvable import Numeric
|
||||
from ytdl_sub.script.types.resolvable import AnyType
|
||||
|
||||
|
||||
def _to_numeric(value: int | float) -> Numeric:
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
from typing import Union
|
||||
|
||||
from ytdl_sub.script.types.resolvable import Boolean
|
||||
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 Boolean
|
||||
|
||||
|
||||
class SpecialFunctions:
|
||||
@staticmethod
|
||||
def if_(
|
||||
condition: Boolean, true: AnyType_1, false: AnyType_2
|
||||
) -> Union[AnyType_1, AnyType_2]:
|
||||
def if_(condition: Boolean, true: AnyType_1, false: AnyType_2) -> Union[AnyType_1, AnyType_2]:
|
||||
if condition.value:
|
||||
return true
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Optional
|
||||
|
||||
from ytdl_sub.script.types.resolvable import Integer
|
||||
from ytdl_sub.script.types.resolvable import AnyType
|
||||
from ytdl_sub.script.types.resolvable import Integer
|
||||
from ytdl_sub.script.types.resolvable import String
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from ytdl_sub.script.types.resolvable import String
|
|||
from ytdl_sub.script.types.syntax_tree import SyntaxTree
|
||||
from ytdl_sub.script.types.variable import FunctionArgument
|
||||
from ytdl_sub.script.types.variable import Variable
|
||||
from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments
|
||||
from ytdl_sub.script.utils.exceptions import InvalidSyntaxException
|
||||
from ytdl_sub.script.utils.exceptions import UnreachableSyntaxException
|
||||
from ytdl_sub.script.utils.parser_exception_formatter import ParserExceptionFormatter
|
||||
|
|
@ -125,8 +126,8 @@ class _Parser:
|
|||
"""
|
||||
return self._syntax_tree
|
||||
|
||||
def _set_highlight_position(self) -> None:
|
||||
self._error_highlight_pos = self._pos
|
||||
def _set_highlight_position(self, pos: Optional[int] = None) -> None:
|
||||
self._error_highlight_pos = pos if pos is not None else self._pos
|
||||
|
||||
def _read(self, increment_pos: bool = True, length: int = 1) -> Optional[str]:
|
||||
if self._pos >= len(self._text):
|
||||
|
|
@ -322,10 +323,15 @@ class _Parser:
|
|||
"""
|
||||
function_name: str = ""
|
||||
function_args: List[ArgumentType] = []
|
||||
function_start_pos = self._pos
|
||||
|
||||
while ch := self._read():
|
||||
if ch == ")":
|
||||
return Function.from_name_and_args(name=function_name, args=function_args)
|
||||
try:
|
||||
return Function.from_name_and_args(name=function_name, args=function_args)
|
||||
except IncompatibleFunctionArguments as exc:
|
||||
self._set_highlight_position(function_start_pos)
|
||||
raise InvalidSyntaxException(exc) from exc
|
||||
|
||||
if ch != "(":
|
||||
function_name += ch
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ from typing import Dict
|
|||
from typing import List
|
||||
from typing import Set
|
||||
|
||||
from ytdl_sub.script.types.resolvable import ArgumentType, FutureResolvable
|
||||
from ytdl_sub.script.types.resolvable import ArgumentType
|
||||
from ytdl_sub.script.types.resolvable import FutureResolvable
|
||||
from ytdl_sub.script.types.resolvable import NonHashable
|
||||
from ytdl_sub.script.types.resolvable import Resolvable
|
||||
from ytdl_sub.script.types.resolvable import ResolvableToJson
|
||||
|
|
|
|||
|
|
@ -14,14 +14,15 @@ from typing import Union
|
|||
from typing import get_origin
|
||||
|
||||
from ytdl_sub.script.functions import Functions
|
||||
from ytdl_sub.script.types.resolvable import ArgumentType
|
||||
from ytdl_sub.script.types.resolvable import Resolvable
|
||||
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 Resolvable
|
||||
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.script.utils.exceptions import IncompatibleFunctionArguments
|
||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||
|
||||
|
||||
|
|
@ -216,7 +217,7 @@ class BuiltInFunction(Function):
|
|||
|
||||
def validate_args(self) -> "BuiltInFunction":
|
||||
if not self.input_spec.is_compatible(input_args=self.args):
|
||||
raise StringFormattingException(
|
||||
raise IncompatibleFunctionArguments(
|
||||
f"Invalid arguments passed to function {self.name}.\n"
|
||||
f"{self._expected_received_error_msg()}"
|
||||
)
|
||||
|
|
@ -239,6 +240,12 @@ class BuiltInFunction(Function):
|
|||
def input_spec(self) -> FunctionInputSpec:
|
||||
return FunctionInputSpec.from_function(self)
|
||||
|
||||
@classmethod
|
||||
def _arg_output_type(cls, arg: ArgumentType) -> Type[ArgumentType]:
|
||||
if isinstance(arg, BuiltInFunction):
|
||||
return arg.output_type
|
||||
return type(arg)
|
||||
|
||||
@property
|
||||
def output_type(self) -> Type[Resolvable]:
|
||||
output_type = self.arg_spec.annotations["return"]
|
||||
|
|
@ -246,11 +253,11 @@ class BuiltInFunction(Function):
|
|||
union_types_list = []
|
||||
for union_type in output_type.__args__:
|
||||
if union_type == AnyType_0:
|
||||
union_types_list.append(type(self.args[0]))
|
||||
union_types_list.append(self._arg_output_type(self.args[0]))
|
||||
elif union_type == AnyType_1:
|
||||
union_types_list.append(type(self.args[1]))
|
||||
union_types_list.append(self._arg_output_type(self.args[1]))
|
||||
elif union_type == AnyType_2:
|
||||
union_types_list.append(type(self.args[2]))
|
||||
union_types_list.append(self._arg_output_type(self.args[2]))
|
||||
else:
|
||||
union_types_list.append(union_type)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ from typing import Dict
|
|||
from typing import List
|
||||
from typing import Set
|
||||
|
||||
from ytdl_sub.script.types.resolvable import ArgumentType, FutureResolvable
|
||||
from ytdl_sub.script.types.resolvable import ArgumentType
|
||||
from ytdl_sub.script.types.resolvable import FutureResolvable
|
||||
from ytdl_sub.script.types.resolvable import Hashable
|
||||
from ytdl_sub.script.types.resolvable import NonHashable
|
||||
from ytdl_sub.script.types.resolvable import Resolvable
|
||||
|
|
|
|||
|
|
@ -14,33 +14,38 @@ class ArgumentType(ABC):
|
|||
"""
|
||||
Any possible argument type that has not been resolved yet
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class AnyType(ArgumentType, ABC):
|
||||
|
||||
class AnyType_0(ABC):
|
||||
pass
|
||||
|
||||
|
||||
class AnyType_1(ABC):
|
||||
pass
|
||||
|
||||
|
||||
class AnyType_2(ABC):
|
||||
pass
|
||||
|
||||
|
||||
class AnyType(ArgumentType, AnyType_0, AnyType_1, AnyType_2, ABC):
|
||||
"""
|
||||
Human-readable name for FutureResolvable
|
||||
"""
|
||||
|
||||
value: Any
|
||||
|
||||
|
||||
class FutureResolvable(AnyType, ABC):
|
||||
"""
|
||||
Type that will be resolved in the future
|
||||
"""
|
||||
|
||||
class AnyType_0(FutureResolvable, ABC):
|
||||
pass
|
||||
|
||||
|
||||
class AnyType_1(FutureResolvable, ABC):
|
||||
pass
|
||||
|
||||
|
||||
class AnyType_2(FutureResolvable, ABC):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Resolvable(AnyType_0, AnyType_1, AnyType_2, ABC):
|
||||
class Resolvable(AnyType, ABC):
|
||||
def __str__(self) -> str:
|
||||
return str(self.value)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,5 +5,9 @@ class InvalidSyntaxException(ValidationException):
|
|||
"""Syntax is incorrect"""
|
||||
|
||||
|
||||
class IncompatibleFunctionArguments(ValidationException):
|
||||
"""Function has invalid arguments"""
|
||||
|
||||
|
||||
class UnreachableSyntaxException(InvalidSyntaxException):
|
||||
"""For use in places where code _should_ never reach, but might from bugs"""
|
||||
|
|
|
|||
|
|
@ -51,6 +51,25 @@ class TestFunction:
|
|||
"func": String("winner"),
|
||||
}
|
||||
|
||||
def test_nested_if_function_incompatible(self):
|
||||
function_str = """{
|
||||
%map_get(
|
||||
%if(
|
||||
True,
|
||||
%if(
|
||||
True,
|
||||
{},
|
||||
[]
|
||||
),
|
||||
{}
|
||||
),
|
||||
"key"
|
||||
)
|
||||
}"""
|
||||
assert Script({"func": function_str}).resolve() == {
|
||||
"func": String("winner"),
|
||||
}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function_str", ["{%array_at({'a': 'dict?'}, 1)}" "{%array_extend('not', 'array')}"]
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue