work to validate funcs

This commit is contained in:
Jesse Bannon 2023-11-15 00:09:51 -08:00
parent c4f9bf4fa0
commit 3174225f55
12 changed files with 75 additions and 33 deletions

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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"""

View file

@ -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')}"]
)