[BACKEND] Optimize script interpreter (#1390)
Makes scripting runtime much faster.
This commit is contained in:
parent
24e71ce733
commit
19f47cd914
8 changed files with 60 additions and 17 deletions
|
|
@ -158,10 +158,15 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
|
||||||
script = entry.script
|
script = entry.script
|
||||||
unresolvable = entry.unresolvable
|
unresolvable = entry.unresolvable
|
||||||
|
|
||||||
|
# Update the script internally so long as we are not supplying overrides
|
||||||
|
# that could alter the script with one-off state
|
||||||
|
update = function_overrides is None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return script.resolve_once(
|
return script.resolve_once(
|
||||||
dict({"tmp_var": formatter.format_string}, **(function_overrides or {})),
|
dict({"tmp_var": formatter.format_string}, **(function_overrides or {})),
|
||||||
unresolvable=unresolvable,
|
unresolvable=unresolvable,
|
||||||
|
update=update,
|
||||||
)["tmp_var"]
|
)["tmp_var"]
|
||||||
except ScriptVariableNotResolved as exc:
|
except ScriptVariableNotResolved as exc:
|
||||||
raise StringFormattingException(
|
raise StringFormattingException(
|
||||||
|
|
|
||||||
|
|
@ -605,7 +605,7 @@ def parse(
|
||||||
name=name,
|
name=name,
|
||||||
custom_function_names=custom_function_names,
|
custom_function_names=custom_function_names,
|
||||||
variable_names=variable_names,
|
variable_names=variable_names,
|
||||||
).ast
|
).ast.maybe_resolvable_casted()
|
||||||
|
|
||||||
|
|
||||||
# pylint: enable=invalid-name
|
# pylint: enable=invalid-name
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from ytdl_sub.script.script_output import ScriptOutput
|
||||||
from ytdl_sub.script.types.resolvable import BuiltInFunctionType
|
from ytdl_sub.script.types.resolvable import BuiltInFunctionType
|
||||||
from ytdl_sub.script.types.resolvable import Lambda
|
from ytdl_sub.script.types.resolvable import Lambda
|
||||||
from ytdl_sub.script.types.resolvable import Resolvable
|
from ytdl_sub.script.types.resolvable import Resolvable
|
||||||
|
from ytdl_sub.script.types.syntax_tree import ResolvedSyntaxTree
|
||||||
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
|
||||||
|
|
@ -273,7 +274,7 @@ class Script:
|
||||||
|
|
||||||
def _update_internally(self, resolved_variables: Dict[str, Resolvable]) -> None:
|
def _update_internally(self, resolved_variables: Dict[str, Resolvable]) -> None:
|
||||||
for variable_name, resolved in resolved_variables.items():
|
for variable_name, resolved in resolved_variables.items():
|
||||||
self._variables[variable_name] = SyntaxTree(ast=[resolved])
|
self._variables[variable_name] = ResolvedSyntaxTree(ast=[resolved])
|
||||||
|
|
||||||
def _recursive_get_unresolved_output_filter_variables(
|
def _recursive_get_unresolved_output_filter_variables(
|
||||||
self, current_var: SyntaxTree, subset_to_resolve: Set[str], unresolvable: Set[Variable]
|
self, current_var: SyntaxTree, subset_to_resolve: Set[str], unresolvable: Set[Variable]
|
||||||
|
|
@ -398,8 +399,8 @@ class Script:
|
||||||
|
|
||||||
# Otherwise, if it has dependencies that are all resolved, then
|
# Otherwise, if it has dependencies that are all resolved, then
|
||||||
# resolve the definition
|
# resolve the definition
|
||||||
elif not definition.is_subset_of(
|
elif definition.is_subset_of(
|
||||||
variables=resolved.keys(), custom_function_definitions=self._functions
|
variables=resolved, custom_function_definitions=self._functions
|
||||||
):
|
):
|
||||||
resolved[variable] = unresolved[variable].resolve(
|
resolved[variable] = unresolved[variable].resolve(
|
||||||
resolved_variables=resolved,
|
resolved_variables=resolved,
|
||||||
|
|
@ -522,6 +523,7 @@ class Script:
|
||||||
variable_definitions: Dict[str, str],
|
variable_definitions: Dict[str, str],
|
||||||
resolved: Optional[Dict[str, Resolvable]] = None,
|
resolved: Optional[Dict[str, Resolvable]] = None,
|
||||||
unresolvable: Optional[Set[str]] = None,
|
unresolvable: Optional[Set[str]] = None,
|
||||||
|
update: bool = False,
|
||||||
) -> Dict[str, Resolvable]:
|
) -> Dict[str, Resolvable]:
|
||||||
"""
|
"""
|
||||||
Given a new set of variable definitions, resolve them using the Script, but do not
|
Given a new set of variable definitions, resolve them using the Script, but do not
|
||||||
|
|
@ -536,6 +538,8 @@ class Script:
|
||||||
unresolvable
|
unresolvable
|
||||||
Optional. Unresolvable variables that will be ignored in resolution, including all
|
Optional. Unresolvable variables that will be ignored in resolution, including all
|
||||||
variables with a dependency to them.
|
variables with a dependency to them.
|
||||||
|
update
|
||||||
|
Whether to update the script's state with resolved variables. Defaults to False.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
@ -548,6 +552,7 @@ class Script:
|
||||||
pre_resolved=resolved,
|
pre_resolved=resolved,
|
||||||
unresolvable=unresolvable,
|
unresolvable=unresolvable,
|
||||||
output_filter=set(list(variable_definitions.keys())),
|
output_filter=set(list(variable_definitions.keys())),
|
||||||
|
update=update,
|
||||||
).output
|
).output
|
||||||
finally:
|
finally:
|
||||||
for name in variable_definitions.keys():
|
for name in variable_definitions.keys():
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import copy
|
|
||||||
import functools
|
import functools
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
@ -58,23 +57,29 @@ class CustomFunction(Function, NamedCustomFunction):
|
||||||
# Should be validated in the Script
|
# Should be validated in the Script
|
||||||
raise UNREACHABLE
|
raise UNREACHABLE
|
||||||
|
|
||||||
resolved_variables_with_args = copy.deepcopy(resolved_variables)
|
function_args: List[FunctionArgument] = []
|
||||||
for i, arg in enumerate(resolved_args):
|
for i, arg in enumerate(resolved_args):
|
||||||
function_arg = FunctionArgument.from_idx(idx=i, custom_function_name=self.name)
|
function_arg = FunctionArgument.from_idx(idx=i, custom_function_name=self.name)
|
||||||
|
|
||||||
if function_arg in resolved_variables_with_args:
|
if function_arg in resolved_variables:
|
||||||
# function args should always be unique since they are only defined once
|
# function args should always be unique since they are only defined once
|
||||||
# in the custom function as %custom_function_name___idx
|
# in the custom function as %custom_function_name___idx
|
||||||
# and returned as a set from each custom function.
|
# and returned as a set from each custom function.
|
||||||
raise UNREACHABLE
|
raise UNREACHABLE
|
||||||
|
|
||||||
resolved_variables_with_args[function_arg] = arg
|
resolved_variables[function_arg] = arg
|
||||||
|
function_args.append(function_arg)
|
||||||
|
|
||||||
return custom_functions[self.name].resolve(
|
out = custom_functions[self.name].resolve(
|
||||||
resolved_variables=resolved_variables_with_args,
|
resolved_variables=resolved_variables,
|
||||||
custom_functions=custom_functions,
|
custom_functions=custom_functions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for function_arg in function_args:
|
||||||
|
del resolved_variables[function_arg]
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
# Implies the custom function does not exist. This should have
|
# Implies the custom function does not exist. This should have
|
||||||
# been checked in the parser with
|
# been checked in the parser with
|
||||||
raise UNREACHABLE
|
raise UNREACHABLE
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,32 @@ class SyntaxTree(VariableDependency):
|
||||||
-------
|
-------
|
||||||
A resolvable if the AST contains a single type that is resolvable. None otherwise.
|
A resolvable if the AST contains a single type that is resolvable. None otherwise.
|
||||||
"""
|
"""
|
||||||
if len(self.ast) == 1 and isinstance(self.ast[0], Resolvable):
|
|
||||||
return self.ast[0]
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def maybe_resolvable_casted(self) -> "SyntaxTree":
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Optimized SyntaxTree if its deemed resolvable
|
||||||
|
"""
|
||||||
|
if len(self.ast) == 1 and isinstance(self.ast[0], Resolvable):
|
||||||
|
return ResolvedSyntaxTree(self.ast)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ResolvedSyntaxTree(SyntaxTree):
|
||||||
|
"""
|
||||||
|
SyntaxTree with optimized helper functions if it's known to be resolved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def resolve(
|
||||||
|
self,
|
||||||
|
resolved_variables: Dict[Variable, Resolvable],
|
||||||
|
custom_functions: Dict[str, VariableDependency],
|
||||||
|
) -> Resolvable:
|
||||||
|
return self.ast[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def maybe_resolvable(self) -> Optional[Resolvable]:
|
||||||
|
return self.ast[0]
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ class VariableDependency(ABC):
|
||||||
@final
|
@final
|
||||||
def is_subset_of(
|
def is_subset_of(
|
||||||
self,
|
self,
|
||||||
variables: Iterable[Variable],
|
variables: Dict[Variable, Resolvable],
|
||||||
custom_function_definitions: Dict[str, "VariableDependency"],
|
custom_function_definitions: Dict[str, "VariableDependency"],
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
@ -171,12 +171,12 @@ class VariableDependency(ABC):
|
||||||
True if it contains all input variables as a dependency. False otherwise.
|
True if it contains all input variables as a dependency. False otherwise.
|
||||||
"""
|
"""
|
||||||
for custom_function in self.custom_functions:
|
for custom_function in self.custom_functions:
|
||||||
if custom_function_definitions[custom_function.name].is_subset_of(
|
if not custom_function_definitions[custom_function.name].is_subset_of(
|
||||||
variables=variables, custom_function_definitions=custom_function_definitions
|
variables=variables, custom_function_definitions=custom_function_definitions
|
||||||
):
|
):
|
||||||
return True
|
return False
|
||||||
|
|
||||||
return not self.variables.issubset(variables)
|
return all(var in variables for var in self.variables)
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def contains(
|
def contains(
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,7 @@ def _validate_formatter(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
unresolvable=unresolvable,
|
unresolvable=unresolvable,
|
||||||
|
update=True,
|
||||||
)
|
)
|
||||||
except RuntimeException as exc:
|
except RuntimeException as exc:
|
||||||
if isinstance(exc, ScriptVariableNotResolved) and is_static_formatter:
|
if isinstance(exc, ScriptVariableNotResolved) and is_static_formatter:
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ 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 Lambda
|
from ytdl_sub.script.types.resolvable import Lambda
|
||||||
from ytdl_sub.script.types.resolvable import String
|
from ytdl_sub.script.types.resolvable import String
|
||||||
|
from ytdl_sub.script.types.syntax_tree import ResolvedSyntaxTree
|
||||||
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 Variable
|
from ytdl_sub.script.types.variable import Variable
|
||||||
from ytdl_sub.script.utils.exceptions import InvalidSyntaxException
|
from ytdl_sub.script.utils.exceptions import InvalidSyntaxException
|
||||||
|
|
@ -24,7 +25,7 @@ from ytdl_sub.script.utils.exceptions import InvalidSyntaxException
|
||||||
class TestParser:
|
class TestParser:
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
parsed = parse("hello world")
|
parsed = parse("hello world")
|
||||||
assert parsed == SyntaxTree([String(value="hello world")])
|
assert parsed == ResolvedSyntaxTree([String(value="hello world")])
|
||||||
assert parsed.variables == set()
|
assert parsed.variables == set()
|
||||||
|
|
||||||
def test_single_function_one_arg(self):
|
def test_single_function_one_arg(self):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue