1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.cst;
7   
8   import java.util.List;
9   
10  import dev.metaschema.core.metapath.DynamicContext;
11  import dev.metaschema.core.metapath.IExpression;
12  import dev.metaschema.core.metapath.item.ISequence;
13  import dev.metaschema.core.qname.IEnhancedQName;
14  import dev.metaschema.core.util.ObjectUtils;
15  import edu.umd.cs.findbugs.annotations.NonNull;
16  
17  /**
18   * An implementation of
19   * <a href="https://www.w3.org/TR/xpath-31/#id-let-expressions">Let
20   * expression</a> supporting variable value binding.
21   */
22  public class Let
23      extends AbstractExpression {
24    @NonNull
25    private final VariableDeclaration variable;
26    @NonNull
27    private final IExpression returnExpression;
28  
29    /**
30     * Construct a new Let CST expression.
31     *
32     * @param text
33     *          the parsed text of the expression
34     * @param name
35     *          the variable name
36     * @param boundExpression
37     *          the expression bound to the variable
38     * @param returnExpression
39     *          the inner expression to evaluate with the variable in-scope
40     */
41    public Let(
42        @NonNull String text,
43        @NonNull IEnhancedQName name,
44        @NonNull IExpression boundExpression,
45        @NonNull IExpression returnExpression) {
46      super(text);
47      this.variable = new VariableDeclaration(name, boundExpression);
48      this.returnExpression = returnExpression;
49    }
50  
51    /**
52     * Get the variable to evaluate with the variable in-scope.
53     *
54     * @return the inner expression
55     */
56    @NonNull
57    public VariableDeclaration getVariable() {
58      return variable;
59    }
60  
61    /**
62     * Get the inner expression to evaluate with the variable in-scope.
63     *
64     * @return the inner expression
65     */
66    @NonNull
67    public IExpression getReturnExpression() {
68      return returnExpression;
69    }
70  
71    @Override
72    public List<? extends IExpression> getChildren() {
73      return ObjectUtils.notNull(
74          List.of(returnExpression));
75    }
76  
77    @Override
78    public <RESULT, CONTEXT> RESULT accept(IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
79      return visitor.visitLet(this, context);
80    }
81  
82    @Override
83    protected ISequence<?> evaluate(DynamicContext dynamicContext, ISequence<?> focus) {
84      DynamicContext subDynamicContext = dynamicContext.subContext();
85  
86      getVariable().bind(dynamicContext, focus, subDynamicContext);
87  
88      return getReturnExpression().accept(subDynamicContext, focus);
89    }
90  
91    /**
92     * A Metapath expression that binds a variable name to an expresssion.
93     */
94    public static class VariableDeclaration {
95      @NonNull
96      private final IEnhancedQName name;
97      @NonNull
98      private final IExpression boundExpression;
99  
100     /**
101      * Construct a new variable declaration, binding the provided variable name to
102      * the bound expression.
103      *
104      * @param name
105      *          trhe variable name
106      * @param boundExpression
107      *          the bound expression
108      */
109     public VariableDeclaration(@NonNull IEnhancedQName name, @NonNull IExpression boundExpression) {
110       this.name = name;
111       this.boundExpression = boundExpression;
112     }
113 
114     /**
115      * Get the variable name.
116      *
117      * @return the variable name
118      */
119     @NonNull
120     public IEnhancedQName getName() {
121       return name;
122     }
123 
124     /**
125      * Get the expression bound to the variable.
126      *
127      * @return the bound expression
128      */
129     @NonNull
130     public IExpression getBoundExpression() {
131       return boundExpression;
132     }
133 
134     /**
135      * Bind the variable name to the evaluation result of the bound expression.
136      *
137      * @param evaluationDynamicContext
138      *          the {@link DynamicContext} used to evaluate the bound expression
139      * @param focus
140      *          the evaluation focus to use to evaluate the bound expression
141      * @param boundDynamicContext
142      *          the {@link DynamicContext} the variable is bound to
143      */
144     public void bind(
145         @NonNull DynamicContext evaluationDynamicContext,
146         @NonNull ISequence<?> focus,
147         @NonNull DynamicContext boundDynamicContext) {
148 
149       ISequence<?> result = getBoundExpression().accept(evaluationDynamicContext, focus)
150           // ensure this sequence is list backed
151           .reusable();
152       boundDynamicContext.bindVariableValue(getName(), result);
153     }
154   }
155 }