diff --git a/Expressif.Cli.Tests/BindCommandTests.cs b/Expressif.Cli.Tests/BindCommandTests.cs index 4353b6c5..e83ad80b 100644 --- a/Expressif.Cli.Tests/BindCommandTests.cs +++ b/Expressif.Cli.Tests/BindCommandTests.cs @@ -52,6 +52,20 @@ public async Task Bind_BinaryPredicate_PreservesBothOperands() }); } + [Test] + public async Task Bind_ClosedFunctionPredicateArgument_ResolvesNestedFunction() + { + var result = await InvokeAsync("bind", "even |or greater-than(45 | add(17))"); + + Assert.Multiple(() => + { + Assert.That(result.ExitCode, Is.EqualTo(ExitCodes.Success)); + Assert.That(result.StdOut, Does.Contain("Arg[0]: InputExpression")); + Assert.That(result.StdOut, Does.Contain("Function: add")); + Assert.That(result.StdErr, Is.Empty); + }); + } + [Test] public async Task Bind_FieldShorthand_ShowsBinderGeneratedFunction() { diff --git a/Expressif.Cli.Tests/CliCommandTests.cs b/Expressif.Cli.Tests/CliCommandTests.cs index 4bfae74d..c96b6dee 100644 --- a/Expressif.Cli.Tests/CliCommandTests.cs +++ b/Expressif.Cli.Tests/CliCommandTests.cs @@ -124,6 +124,23 @@ public async Task Evaluate_TwoNestedPredicateBranches_ReturnsBoolean(string inpu }); } + [Test] + public async Task Evaluate_PredicateClosedInputExpression_ResolvesNestedFunction() + { + var result = await InvokeAsync( + "evaluate", + "filter(greater-than(17 | add(17)))", + "--input", + "{10,12,13}"); + + Assert.Multiple(() => + { + Assert.That(result.ExitCode, Is.EqualTo(ExitCodes.Success)); + Assert.That(result.StdOut.Trim(), Is.EqualTo("{}")); + Assert.That(result.StdErr, Is.Empty); + }); + } + [Test] public async Task Evaluate_MultipleInputOptions_ReturnsClearError() { diff --git a/Expressif.Testing/Functions/FunctionFactoryTest.cs b/Expressif.Testing/Functions/FunctionFactoryTest.cs index 4870187a..e17cda98 100644 --- a/Expressif.Testing/Functions/FunctionFactoryTest.cs +++ b/Expressif.Testing/Functions/FunctionFactoryTest.cs @@ -250,6 +250,24 @@ public void Instantiate_FilterWithPredicateExpression_Valid() Assert.That(predicate, Is.TypeOf()); } + [Test] + public void Instantiate_FilterWithClosedPredicateParameter_ResolvesNestedFunction() + { + var reference = new InputExpressionParameter( + new Expressif.Bindings.ClosedExpression( + new LiteralParameter(17m), + [new Function("add", [new LiteralParameter(17m)])])); + var predicate = new Function("greater-than", [reference]); + var filter = new Function( + "filter", + [new OpenExpressionParameter(new OpenExpression([predicate]))]); + var root = new OpenRootExpression(new OpenExpression([filter])); + + var function = new FunctionFactory().Instantiate(root, new Context()); + + Assert.That(function.Evaluate(new object[] { 10, 12, 13 }), Is.Empty); + } + [Test] public void Instantiate_SliceAlias_Valid() { diff --git a/Expressif.Testing/Predicates/PredicationFactoryTest.cs b/Expressif.Testing/Predicates/PredicationFactoryTest.cs index b06dd7a6..4014da3c 100644 --- a/Expressif.Testing/Predicates/PredicationFactoryTest.cs +++ b/Expressif.Testing/Predicates/PredicationFactoryTest.cs @@ -90,4 +90,20 @@ public void Instantiate_NumericEqualToObjectIndexParameter_Valid() Assert.That((predicate as EqualTo)!.Reference.Invoke(), Is.EqualTo(4)); }); } + + [TestCase(62, false)] + [TestCase(63, true)] + public void Instantiate_ClosedFunctionParameter_ResolvesThroughFunctionRegistry(object value, bool expected) + { + var reference = new InputExpressionParameter( + new Expressif.Bindings.ClosedExpression( + new LiteralParameter(45m), + [new Function("add", [new LiteralParameter(17m)])])); + var predication = new SinglePredication( + new Function("greater-than", [reference])); + + var predicate = new PredicationFactory().Instantiate(predication, new Context()); + + Assert.That(predicate.Evaluate(value), Is.EqualTo(expected)); + } } diff --git a/Expressif/Predicates/PredicationFactory.cs b/Expressif/Predicates/PredicationFactory.cs index 0bc09fef..db144f0b 100644 --- a/Expressif/Predicates/PredicationFactory.cs +++ b/Expressif/Predicates/PredicationFactory.cs @@ -63,6 +63,12 @@ protected override Delegate CreateParameter(IParameter parameter, Type scalarTyp scalarType) : base.CreateParameter(parameter, scalarType, context); + protected override Delegate CreateInputExpression(InputExpressionParameter input, Type type, IContext context) + { + var expression = new FunctionFactory().Instantiate(new ClosedRootExpression(input.Expression), context); + return CreateFunctionCast(() => expression.Evaluate(null), type); + } + private sealed class ContextualPredicate(IFunction expression, IContext context) : IPredicate { public bool Evaluate(object? value)