function spec do more
This commit is contained in:
parent
fb02479108
commit
4278534c21
5 changed files with 57 additions and 30 deletions
|
|
@ -4,6 +4,7 @@ from typing import List
|
|||
from typing import Optional
|
||||
from typing import Set
|
||||
|
||||
from ytdl_sub.script.functions import Functions
|
||||
from ytdl_sub.script.parser import parse
|
||||
from ytdl_sub.script.types.resolvable import Resolvable
|
||||
from ytdl_sub.script.types.syntax_tree import SyntaxTree
|
||||
|
|
@ -144,6 +145,12 @@ class Script:
|
|||
f"{nested_custom_function.num_input_args}"
|
||||
)
|
||||
|
||||
def _ensure_lambda_usage_num_input_arguments_valid(self) -> None:
|
||||
for variable_name, variable_definition in self._variables.items():
|
||||
for lambda_argument in variable_definition.lambda_arguments:
|
||||
if Functions.is_built_in(name=lambda_argument.value):
|
||||
pass
|
||||
|
||||
def _validate(self) -> None:
|
||||
self._ensure_no_custom_function_cycles()
|
||||
self._ensure_custom_function_arguments_valid()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import copy
|
||||
import functools
|
||||
import inspect
|
||||
from abc import ABC
|
||||
from dataclasses import dataclass
|
||||
from inspect import FullArgSpec
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
|
@ -29,7 +27,7 @@ from ytdl_sub.script.utils.exception_formatters import FunctionArgumentsExceptio
|
|||
from ytdl_sub.script.utils.exceptions import UNREACHABLE
|
||||
from ytdl_sub.script.utils.exceptions import FunctionRuntimeException
|
||||
from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError
|
||||
from ytdl_sub.script.utils.type_checking import FunctionInputSpec
|
||||
from ytdl_sub.script.utils.type_checking import FunctionSpec
|
||||
from ytdl_sub.script.utils.type_checking import is_union
|
||||
|
||||
|
||||
|
|
@ -82,9 +80,9 @@ class CustomFunction(Function, NamedCustomFunction):
|
|||
|
||||
class BuiltInFunction(Function, TypeHintedFunctionType):
|
||||
def validate_args(self) -> "BuiltInFunction":
|
||||
if not self.input_spec.is_compatible(input_args=self.args):
|
||||
if not self.function_spec.is_compatible(input_args=self.args):
|
||||
raise FunctionArgumentsExceptionFormatter(
|
||||
input_spec=self.input_spec,
|
||||
input_spec=self.function_spec,
|
||||
function_instance=self,
|
||||
).highlight()
|
||||
|
||||
|
|
@ -101,21 +99,8 @@ class BuiltInFunction(Function, TypeHintedFunctionType):
|
|||
raise UNREACHABLE
|
||||
|
||||
@functools.cached_property
|
||||
def arg_spec(self) -> FullArgSpec:
|
||||
return inspect.getfullargspec(self.callable)
|
||||
|
||||
@property
|
||||
def input_spec(self) -> FunctionInputSpec:
|
||||
if self.arg_spec.varargs:
|
||||
return FunctionInputSpec(varargs=self.arg_spec.annotations[self.arg_spec.varargs])
|
||||
|
||||
return FunctionInputSpec(
|
||||
args=[self.arg_spec.annotations[arg_name] for arg_name in self.arg_spec.args]
|
||||
)
|
||||
|
||||
@property
|
||||
def is_lambda_function(self) -> bool:
|
||||
return Lambda in (self.input_spec.args or [])
|
||||
def function_spec(self) -> FunctionSpec:
|
||||
return FunctionSpec.from_callable(self.callable)
|
||||
|
||||
@classmethod
|
||||
def _arg_output_type(cls, arg: Argument) -> Type[Argument]:
|
||||
|
|
@ -124,19 +109,18 @@ class BuiltInFunction(Function, TypeHintedFunctionType):
|
|||
return type(arg)
|
||||
|
||||
def output_type(self) -> Type[Resolvable]:
|
||||
output_type = self.arg_spec.annotations["return"]
|
||||
if is_union(output_type):
|
||||
if is_union(self.function_spec.return_type):
|
||||
union_types_list = []
|
||||
for union_type in output_type.__args__:
|
||||
for union_type in self.function_spec.return_type.__args__:
|
||||
if union_type in (ReturnableArgument, ReturnableArgumentA, ReturnableArgumentB):
|
||||
generic_arg_index = self.input_spec.args.index(union_type)
|
||||
generic_arg_index = self.function_spec.args.index(union_type)
|
||||
union_types_list.append(self._arg_output_type(self.args[generic_arg_index]))
|
||||
else:
|
||||
union_types_list.append(union_type)
|
||||
|
||||
return Union[tuple(union_types_list)]
|
||||
|
||||
return output_type
|
||||
return self.function_spec.return_type
|
||||
|
||||
def _resolve_lambda_function(
|
||||
self,
|
||||
|
|
@ -153,7 +137,7 @@ class BuiltInFunction(Function, TypeHintedFunctionType):
|
|||
3. Resolve it like any other syntax
|
||||
"""
|
||||
function_input_lambda_args = [arg for arg in resolved_arguments if isinstance(arg, Lambda)]
|
||||
if not self.is_lambda_function or len(function_input_lambda_args) != 1:
|
||||
if not self.function_spec.is_lambda_function or len(function_input_lambda_args) != 1:
|
||||
raise UNREACHABLE
|
||||
|
||||
lambda_function_name = function_input_lambda_args[0].value
|
||||
|
|
@ -196,7 +180,7 @@ class BuiltInFunction(Function, TypeHintedFunctionType):
|
|||
]
|
||||
|
||||
# If a lambda is in a function's arg, resolve it differently
|
||||
if self.is_lambda_function:
|
||||
if self.function_spec.is_lambda_function:
|
||||
return self._resolve_lambda_function(
|
||||
resolved_arguments=resolved_arguments,
|
||||
resolved_variables=resolved_variables,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from typing import final
|
|||
|
||||
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 ParsedCustomFunction
|
||||
from ytdl_sub.script.types.resolvable import Resolvable
|
||||
|
|
@ -48,6 +49,18 @@ class VariableDependency(ABC):
|
|||
|
||||
return output
|
||||
|
||||
@final
|
||||
@property
|
||||
def lambda_arguments(self) -> Set[Lambda]:
|
||||
output: Set[Lambda] = set()
|
||||
for arg in self._iterable_arguments:
|
||||
if isinstance(arg, Lambda):
|
||||
output.add(arg)
|
||||
if isinstance(arg, VariableDependency):
|
||||
output.update(arg.lambda_arguments)
|
||||
|
||||
return output
|
||||
|
||||
@final
|
||||
@property
|
||||
def custom_functions(self) -> Set[ParsedCustomFunction]:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from ytdl_sub.script.types.resolvable import NamedType
|
|||
from ytdl_sub.script.types.resolvable import TypeHintedFunctionType
|
||||
from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments
|
||||
from ytdl_sub.script.utils.exceptions import UserException
|
||||
from ytdl_sub.script.utils.type_checking import FunctionInputSpec
|
||||
from ytdl_sub.script.utils.type_checking import FunctionSpec
|
||||
from ytdl_sub.script.utils.type_checking import get_optional_type
|
||||
from ytdl_sub.script.utils.type_checking import is_optional
|
||||
from ytdl_sub.script.utils.type_checking import is_union
|
||||
|
|
@ -100,7 +100,7 @@ class ParserExceptionFormatter:
|
|||
class FunctionArgumentsExceptionFormatter:
|
||||
def __init__(
|
||||
self,
|
||||
input_spec: FunctionInputSpec,
|
||||
input_spec: FunctionSpec,
|
||||
function_instance: TypeHintedFunctionType,
|
||||
):
|
||||
self._args = input_spec.args
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import inspect
|
||||
from dataclasses import dataclass
|
||||
from inspect import FullArgSpec
|
||||
from typing import Callable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Type
|
||||
|
|
@ -7,6 +10,7 @@ from typing import get_origin
|
|||
|
||||
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 NamedType
|
||||
from ytdl_sub.script.types.resolvable import Resolvable
|
||||
from ytdl_sub.script.types.resolvable import TypeHintedFunctionType
|
||||
|
|
@ -90,7 +94,8 @@ def is_type_compatible(
|
|||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FunctionInputSpec:
|
||||
class FunctionSpec:
|
||||
return_type: Type[Resolvable]
|
||||
args: Optional[List[Type[Resolvable | Optional[Resolvable]]]] = None
|
||||
varargs: Optional[Type[Resolvable]] = None
|
||||
|
||||
|
|
@ -131,3 +136,21 @@ class FunctionInputSpec:
|
|||
return self._is_varargs_compatible(input_args=input_args)
|
||||
|
||||
raise UNREACHABLE # TODO: functions with no args
|
||||
|
||||
@property
|
||||
def is_lambda_function(self) -> bool:
|
||||
return Lambda in (self.args or [])
|
||||
|
||||
@classmethod
|
||||
def from_callable(cls, callable_ref: Callable[..., Resolvable]) -> "FunctionSpec":
|
||||
arg_spec: FullArgSpec = inspect.getfullargspec(callable_ref)
|
||||
if arg_spec.varargs:
|
||||
return FunctionSpec(
|
||||
return_type=arg_spec.annotations["return"],
|
||||
varargs=arg_spec.annotations[arg_spec.varargs],
|
||||
)
|
||||
|
||||
return FunctionSpec(
|
||||
return_type=arg_spec.annotations["return"],
|
||||
args=[arg_spec.annotations[arg_name] for arg_name in arg_spec.args],
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue