diff --git a/src/ytdl_sub/script/types/array.py b/src/ytdl_sub/script/types/array.py index 8adcfe50..26d99d40 100644 --- a/src/ytdl_sub/script/types/array.py +++ b/src/ytdl_sub/script/types/array.py @@ -11,6 +11,7 @@ from ytdl_sub.script.types.resolvable import NonHashable from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.resolvable import ResolvableToJson from ytdl_sub.script.types.variable import Variable +from ytdl_sub.script.types.variable_dependency import TypeT from ytdl_sub.script.types.variable_dependency import VariableDependency @@ -47,6 +48,25 @@ class UnresolvedArray(_Array, VariableDependency, FutureResolvable): ] ) + def partial_resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + custom_functions: Dict[str, "VariableDependency"], + ) -> "UnresolvedArray" | Resolvable: + maybe_resolvable_values, is_resolvable = VariableDependency.try_partial_resolve( + args=self.value, + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + if is_resolvable: + return self.resolve( + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + return UnresolvedArray(value=maybe_resolvable_values) + def future_resolvable_type(self) -> Type[Resolvable]: return Array diff --git a/src/ytdl_sub/script/types/function.py b/src/ytdl_sub/script/types/function.py index 5f33006e..5ff6111a 100644 --- a/src/ytdl_sub/script/types/function.py +++ b/src/ytdl_sub/script/types/function.py @@ -22,6 +22,7 @@ from ytdl_sub.script.types.resolvable import ReturnableArgumentA from ytdl_sub.script.types.resolvable import ReturnableArgumentB from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable +from ytdl_sub.script.types.variable_dependency import TypeT from ytdl_sub.script.types.variable_dependency import VariableDependency from ytdl_sub.script.utils.exception_formatters import FunctionArgumentsExceptionFormatter from ytdl_sub.script.utils.exceptions import UNREACHABLE @@ -38,6 +39,25 @@ class Function(FunctionType, VariableDependency, ABC): def _iterable_arguments(self) -> List[Argument]: return self.args + def partial_resolve( + self: TypeT, + resolved_variables: Dict[Variable, Resolvable], + custom_functions: Dict[str, "VariableDependency"], + ) -> TypeT | Resolvable: + maybe_resolvable_values, is_resolvable = VariableDependency.try_partial_resolve( + args=self.value, + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + if is_resolvable: + return self.resolve( + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + return BuiltInFunction(name=self.name, args=maybe_resolvable_values) + class CustomFunction(Function, NamedCustomFunction): def resolve( diff --git a/src/ytdl_sub/script/types/map.py b/src/ytdl_sub/script/types/map.py index 0fca1cbb..4344e45c 100644 --- a/src/ytdl_sub/script/types/map.py +++ b/src/ytdl_sub/script/types/map.py @@ -55,6 +55,31 @@ class UnresolvedMap(_Map, VariableDependency, FutureResolvable): return Map(output) + def partial_resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + custom_functions: Dict[str, "VariableDependency"], + ) -> "UnresolvedMap" | Resolvable: + maybe_resolvable_keys, is_keys_resolvable = VariableDependency.try_partial_resolve( + args=self.value.keys(), + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + maybe_resolvable_values, is_values_resolvable = VariableDependency.try_partial_resolve( + args=self.value.values(), + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + if is_keys_resolvable and is_values_resolvable: + return self.resolve( + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + return UnresolvedMap(value=dict(zip(maybe_resolvable_keys, maybe_resolvable_values))) + def future_resolvable_type(self) -> Type[Resolvable]: return Map diff --git a/src/ytdl_sub/script/types/variable_dependency.py b/src/ytdl_sub/script/types/variable_dependency.py index 37130b7a..4ec4b905 100644 --- a/src/ytdl_sub/script/types/variable_dependency.py +++ b/src/ytdl_sub/script/types/variable_dependency.py @@ -5,6 +5,7 @@ from typing import Dict from typing import Iterable from typing import List from typing import Set +from typing import Tuple from typing import Type from typing import TypeVar from typing import final @@ -138,6 +139,25 @@ class VariableDependency(ABC): Resolved value """ + @abstractmethod + def partial_resolve( + self: TypeT, + resolved_variables: Dict[Variable, Resolvable], + custom_functions: Dict[str, "VariableDependency"], + ) -> TypeT | Resolvable: + """ + Parameters + ---------- + resolved_variables + Lookup of variables that have been resolved + custom_functions + Lookup of any custom functions that have been parsed + + Returns + ------- + Either a fully resolved value or partially resolved value of the same type. + """ + @classmethod def _resolve_argument_type( cls, @@ -222,3 +242,36 @@ class VariableDependency(ABC): ): return True return len(self.variables.intersection(variables)) > 0 + + @classmethod + def try_partial_resolve( + cls, + args: Iterable[Argument], + resolved_variables: Dict[Variable, Resolvable], + custom_functions: Dict[str, "VariableDependency"], + ) -> Tuple[List[Argument], bool]: + maybe_resolvable_args: List[Resolvable | Argument] = [] + is_resolvable = True + for arg in args: + if isinstance(arg, Lambda) and arg.value in custom_functions: + maybe_resolvable_args.append(arg) + + if not custom_functions[arg.value].is_subset_of( + variables=resolved_variables, + custom_function_definitions=custom_functions, + ): + is_resolvable = False + + elif isinstance(arg, VariableDependency): + maybe_resolvable_args.append( + arg.partial_resolve( + resolved_variables=resolved_variables, custom_functions=custom_functions + ) + ) + + if not isinstance(maybe_resolvable_args[-1], Resolvable): + is_resolvable = False + else: + maybe_resolvable_args.append(arg) + + return maybe_resolvable_args, is_resolvable