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