diff --git a/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs b/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs index 36a1af6241..35884e22f5 100644 --- a/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs +++ b/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs @@ -29,6 +29,11 @@ public EvaluationContext(ITypedElement resource, ITypedElement rootResource) Resource = resource; RootResource = rootResource ?? resource; } + + public EvaluationContext(ITypedElement resource, ITypedElement rootResource, IDictionary> environment) : this(resource, rootResource) + { + Environment = environment; + } /// /// The data represented by %rootResource. @@ -39,6 +44,11 @@ public EvaluationContext(ITypedElement resource, ITypedElement rootResource) /// The data represented by %resource. /// public ITypedElement Resource { get; set; } + + /// + /// The environment variables that are available to the FHIRPath expressions. + /// + public IDictionary> Environment { get; set; } /// /// A delegate that handles the output for the trace() function. diff --git a/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs b/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs index 5c0ed26cd5..19a675ca85 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs @@ -80,6 +80,11 @@ public override Invokee VisitVariableRef(FP.VariableRefExpression expression) if (expression.Name == "rootResource") return InvokeeFactory.GetRootResource; + if (expression is ContextVariableRefExpression Cvre) + { + return Cvre.Resolve; + } + // Variables are still functions without arguments. For now variables are treated separately here, //Functions are handled elsewhere. return resolve(Symbols, expression.Name, Enumerable.Empty()); diff --git a/src/Hl7.Fhir.Base/FhirPath/Expressions/ExpressionNode.cs b/src/Hl7.Fhir.Base/FhirPath/Expressions/ExpressionNode.cs index fd84567cdc..98b9718dc9 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Expressions/ExpressionNode.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Expressions/ExpressionNode.cs @@ -329,6 +329,14 @@ public override int GetHashCode() return base.GetHashCode() ^ Name.GetHashCode(); } } + + public class ContextVariableRefExpression(string name) : VariableRefExpression(name) + { + internal IEnumerable Resolve(Closure context, IEnumerable _) + { + return context.EvaluationContext.Environment[Name] ?? throw Error.InvalidOperation($"Variable {Name} not found in environment"); + } + } public class AxisExpression : VariableRefExpression { diff --git a/src/Hl7.Fhir.Base/FhirPath/FhirEvaluationContext.cs b/src/Hl7.Fhir.Base/FhirPath/FhirEvaluationContext.cs index cb1c552171..f26d955f18 100644 --- a/src/Hl7.Fhir.Base/FhirPath/FhirEvaluationContext.cs +++ b/src/Hl7.Fhir.Base/FhirPath/FhirEvaluationContext.cs @@ -10,6 +10,7 @@ using Hl7.Fhir.Specification.Terminology; using Hl7.FhirPath; using System; +using System.Collections.Generic; namespace Hl7.Fhir.FhirPath { @@ -33,6 +34,15 @@ public FhirEvaluationContext(ITypedElement resource, ITypedElement rootResource) { } + /// + /// Create a FhirEvaluationContext with a resource and an environment. + /// + /// + /// + public FhirEvaluationContext(ITypedElement resource, IDictionary> environment) : base(resource, null, environment) + { + } + /// /// Create a FhirEvaluationContext and also set the variables %resource and %rootResource to their correct values. /// diff --git a/src/Hl7.Fhir.Base/FhirPath/Parser/Grammar.cs b/src/Hl7.Fhir.Base/FhirPath/Parser/Grammar.cs index 439b0228a2..74b96e72cd 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Parser/Grammar.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Parser/Grammar.cs @@ -60,6 +60,7 @@ internal class Grammar // : invocation #invocationTerm // | literal #literalTerm // | externalConstant #externalConstantTerm + // | externalVariable #externalVariableTerm // | '(' expression ')' #parenthesizedTerm // | '{' '}' #nullLiteral // ; @@ -101,7 +102,8 @@ public static Parser FunctionInvocation(Expression focus) public static readonly Parser Term = Literal .Or(FunctionInvocation(AxisExpression.That)) - .XOr(Lexer.ExternalConstant.Select(n => BuildVariableRefExpression(n))) //Was .XOr(Lexer.ExternalConstant.Select(v => Eval.ExternalConstant(v))) + .XOr(Lexer.ExternalVariable.Select(n => new ContextVariableRefExpression(n))) + .Or(Lexer.ExternalConstant.Select(n => BuildVariableRefExpression(n))) //Was .XOr(Lexer.ExternalConstant.Select(v => Eval.ExternalConstant(v))) .XOr(BracketExpr) .XOr(EmptyList) .XOr(Lexer.Axis.Select(a => new AxisExpression(a))) diff --git a/src/Hl7.Fhir.Base/FhirPath/Parser/Lexer.cs b/src/Hl7.Fhir.Base/FhirPath/Parser/Lexer.cs index 47c69bc53f..b412267b4b 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Parser/Lexer.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Parser/Lexer.cs @@ -78,12 +78,19 @@ from closeQ in Parse.Char(delimiter) // ; public static readonly Parser Identifier = Id.XOr(DelimitedIdentifier); + + // externalVariable + // : '%%' identifier + // ; + public static readonly Parser ExternalVariable = + Parse.String("%%").Then(_ => Identifier.XOr(String)) + .Named("external variable"); // externalConstant // : '%' identifier // ; public static readonly Parser ExternalConstant = - Parse.Char('%').Then(c => Identifier.XOr(String)) + Parse.Char('%').Then(_ => Identifier.XOr(String)) .Named("external constant"); // DATE diff --git a/src/Hl7.FhirPath.Tests/Tests/EnviromentTests.cs b/src/Hl7.FhirPath.Tests/Tests/EnviromentTests.cs new file mode 100644 index 0000000000..8901e1e387 --- /dev/null +++ b/src/Hl7.FhirPath.Tests/Tests/EnviromentTests.cs @@ -0,0 +1,20 @@ +using Hl7.Fhir.ElementModel; +using Hl7.FhirPath; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; + +namespace HL7.FhirPath.Tests.Tests; + +[TestClass] +public class EnviromentTests +{ + [TestMethod] + public void TestEnvironment() + { + var compiler = new FhirPathCompiler(); + var expr = compiler.Compile("%%var = 1"); + + expr.IsTrue(null, new EvaluationContext(null, null, new Dictionary> { { "var", new [] { ElementNode.ForPrimitive(1) } } })); + expr.IsBoolean(false, null, new EvaluationContext(null, null, new Dictionary> { { "var", new[] { ElementNode.ForPrimitive(2) } } })); + } +} \ No newline at end of file diff --git a/src/Hl7.FhirPath.Tests/Tests/FhirPathGrammarTest.cs b/src/Hl7.FhirPath.Tests/Tests/FhirPathGrammarTest.cs index 499fd98305..c76e11504c 100644 --- a/src/Hl7.FhirPath.Tests/Tests/FhirPathGrammarTest.cs +++ b/src/Hl7.FhirPath.Tests/Tests/FhirPathGrammarTest.cs @@ -75,6 +75,7 @@ public void FhirPath_Gramm_Term() AssertParser.SucceedsMatch(parser, "doSomething('hi', 3.14)", new FunctionCallExpression(AxisExpression.This, "doSomething", TypeSpecifier.Any, new ConstantExpression("hi"), new ConstantExpression(3.14m))); AssertParser.SucceedsMatch(parser, "%external", new VariableRefExpression("external")); + AssertParser.SucceedsMatch(parser, "%%contextvar", new ContextVariableRefExpression("contextvar")); AssertParser.SucceedsMatch(parser, "@2013-12", new ConstantExpression(P.Date.Parse("2013-12"))); AssertParser.SucceedsMatch(parser, "@2013-12T", new ConstantExpression(P.DateTime.Parse("2013-12"))); AssertParser.SucceedsMatch(parser, "3", new ConstantExpression(3));