diff --git a/src/ytdl_sub/script/syntax_tree.py b/src/ytdl_sub/script/syntax_tree.py index f1ef3298..a24621c7 100644 --- a/src/ytdl_sub/script/syntax_tree.py +++ b/src/ytdl_sub/script/syntax_tree.py @@ -73,6 +73,8 @@ class SyntaxTree(VariableDependency): unresolved_variables.remove(variable) if len(unresolved_variables) == unresolved_count: - raise StringFormattingException("did not resolve any variables, cycle detected") + raise StringFormattingException( + f"Cycle detected within these variables: {', '.join(sorted([var.name for var in unresolved_variables]))}" + ) return {variable.name: resolvable for variable, resolvable in resolved_variables.items()} diff --git a/src/ytdl_sub/script/types/function.py b/src/ytdl_sub/script/types/function.py index c1ccdece..6f274d19 100644 --- a/src/ytdl_sub/script/types/function.py +++ b/src/ytdl_sub/script/types/function.py @@ -15,7 +15,6 @@ from typing import final from typing import get_origin from ytdl_sub.script.functions import Functions -from ytdl_sub.script.functions.special_functions import SpecialFunctions from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import Integer @@ -227,4 +226,13 @@ class Function(VariableDependency): return variables def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable: - raise NotImplemented() + resolved_args: List[Resolvable] = [] + for arg in self.args: + if arg in resolved_variables: + resolved_args.append(resolved_variables[arg]) + elif isinstance(arg, Function): + resolved_args.append(arg.resolve(resolved_variables)) + else: + resolved_args.append(arg) + + return self.callable(*resolved_args) diff --git a/tests/unit/script/test_syntax_tree.py b/tests/unit/script/test_syntax_tree.py index 6e65bc21..beb67114 100644 --- a/tests/unit/script/test_syntax_tree.py +++ b/tests/unit/script/test_syntax_tree.py @@ -3,6 +3,7 @@ from typing import Dict import pytest from ytdl_sub.script.syntax_tree import SyntaxTree +from ytdl_sub.script.types.function import Function from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.types.variable import Variable from ytdl_sub.utils.exceptions import StringFormattingException @@ -23,6 +24,20 @@ class TestSyntaxTree: "b_": String(value="b"), } + def test_simple_with_function(self): + overrides: Dict[str, SyntaxTree] = { + "a": SyntaxTree(ast=[String("a")]), + "b": SyntaxTree(ast=[Function(name="capitalize", args=[Variable("b_")])]), + "b_": SyntaxTree(ast=[String("b")]), + } + + resolved = SyntaxTree.resolve_overrides(parsed_overrides=overrides) + assert resolved == { + "a": String(value="a"), + "b": String(value="B"), + "b_": String(value="b"), + } + def test_simple_cycle(self): overrides: Dict[str, SyntaxTree] = { "a": SyntaxTree(ast=[Variable("b")]), @@ -31,3 +46,13 @@ class TestSyntaxTree: with pytest.raises(StringFormattingException): _ = SyntaxTree.resolve_overrides(parsed_overrides=overrides) + + def test_simple_cycle_with_function(self): + overrides: Dict[str, SyntaxTree] = { + "a": SyntaxTree(ast=[String("a")]), + "b": SyntaxTree(ast=[Function(name="capitalize", args=[Variable("b_")])]), + "b_": SyntaxTree(ast=[Variable("b")]), + } + _ = SyntaxTree.resolve_overrides(parsed_overrides=overrides) + with pytest.raises(StringFormattingException): + _ = SyntaxTree.resolve_overrides(parsed_overrides=overrides)