View Javadoc
1   /*
2   Copyright (c) 2016 James Ahlborn
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess.impl.expr;
18  
19  import java.math.BigDecimal;
20  import java.time.LocalDateTime;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Deque;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.LinkedList;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.regex.Pattern;
34  import java.util.regex.PatternSyntaxException;
35  
36  import com.healthmarketscience.jackcess.expr.EvalContext;
37  import com.healthmarketscience.jackcess.expr.EvalException;
38  import com.healthmarketscience.jackcess.expr.Expression;
39  import com.healthmarketscience.jackcess.expr.Function;
40  import com.healthmarketscience.jackcess.expr.FunctionLookup;
41  import com.healthmarketscience.jackcess.expr.Identifier;
42  import com.healthmarketscience.jackcess.expr.LocaleContext;
43  import com.healthmarketscience.jackcess.expr.ParseException;
44  import com.healthmarketscience.jackcess.expr.Value;
45  import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token;
46  import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType;
47  import org.apache.commons.lang3.StringUtils;
48  
49  
50  /**
51   *
52   * @author James Ahlborn
53   */
54  public class Expressionator
55  {
56  
57    // Useful links:
58    // - syntax: https://support.office.com/en-us/article/Guide-to-expression-syntax-ebc770bc-8486-4adc-a9ec-7427cce39a90
59    // - examples: https://support.office.com/en-us/article/Examples-of-expressions-d3901e11-c04e-4649-b40b-8b6ec5aed41f
60    // - validation rule usage: https://support.office.com/en-us/article/Restrict-data-input-by-using-a-validation-rule-6c0b2ce1-76fa-4be0-8ae9-038b52652320
61  
62  
63    public enum Type {
64      DEFAULT_VALUE, EXPRESSION, FIELD_VALIDATOR, RECORD_VALIDATOR;
65    }
66  
67    public interface ParseContext extends LocaleContext {
68      public FunctionLookup getFunctionLookup();
69    }
70  
71    private enum WordType {
72      OP, COMP, LOG_OP, CONST, SPEC_OP_PREFIX, DELIM;
73    }
74  
75    private static final String FUNC_START_DELIM = "(";
76    private static final String OPEN_PAREN = "(";
77    private static final String CLOSE_PAREN = ")";
78    private static final String FUNC_PARAM_SEP = ",";
79  
80    private static final Map<String,WordType> WORD_TYPES =
81      new HashMap<String,WordType>();
82  
83    static {
84      setWordType(WordType.OP, "+", "-", "*", "/", "\\", "^", "&", "mod");
85      setWordType(WordType.COMP, "<", "<=", ">", ">=", "=", "<>");
86      setWordType(WordType.LOG_OP, "and", "or", "eqv", "xor", "imp");
87      setWordType(WordType.CONST, "true", "false", "null", "on", "off",
88                  "yes", "no");
89      setWordType(WordType.SPEC_OP_PREFIX, "is", "like", "between", "in", "not");
90      // "X is null", "X is not null", "X like P", "X between A and B",
91      // "X not between A and B", "X in (A, B, C...)", "X not in (A, B, C...)",
92      // "not X"
93      setWordType(WordType.DELIM, ".", "!", ",", "(", ")");
94    }
95  
96    private static final Collection<String> TRUE_STRS =
97      Arrays.asList("true", "yes", "on");
98    private static final Collection<String> FALSE_STRS =
99      Arrays.asList("false", "no", "off");
100 
101   private interface OpType {}
102 
103   private enum UnaryOp implements OpType {
104     NEG("-", false) {
105       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1) {
106         return BuiltinOperators.negate(ctx, param1);
107       }
108       @Override public UnaryOp getUnaryNumOp() {
109         return UnaryOp.NEG_NUM;
110       }
111     },
112     POS("+", false) {
113       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1) {
114         // basically a no-op
115         return param1;
116       }
117       @Override public UnaryOp getUnaryNumOp() {
118         return UnaryOp.POS_NUM;
119       }
120     },
121     NOT("Not", true) {
122       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1) {
123         return BuiltinOperators.not(ctx, param1);
124       }
125     },
126     // when a '-' immediately precedes a number, it needs "highest" precedence
127     NEG_NUM("-", false) {
128       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1) {
129         return BuiltinOperators.negate(ctx, param1);
130       }
131     },
132     // when a '+' immediately precedes a number, it needs "highest" precedence
133     POS_NUM("+", false) {
134       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1) {
135         // basically a no-op
136         return param1;
137       }
138     };
139 
140     private final String _str;
141     private final boolean _needSpace;
142 
143     private UnaryOp(String str, boolean needSpace) {
144       _str = str;
145       _needSpace = needSpace;
146     }
147 
148     public boolean needsSpace() {
149       return _needSpace;
150     }
151 
152     @Override
153     public String toString() {
154       return _str;
155     }
156 
157     public UnaryOp getUnaryNumOp() {
158       return null;
159     }
160 
161     public abstract Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1);
162   }
163 
164   private enum BinaryOp implements OpType {
165     PLUS("+") {
166       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
167         return BuiltinOperators.add(ctx, param1, param2);
168       }
169     },
170     MINUS("-") {
171       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
172         return BuiltinOperators.subtract(ctx, param1, param2);
173       }
174     },
175     MULT("*") {
176       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
177         return BuiltinOperators.multiply(ctx, param1, param2);
178       }
179     },
180     DIV("/") {
181       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
182         return BuiltinOperators.divide(ctx, param1, param2);
183       }
184     },
185     INT_DIV("\\") {
186       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
187         return BuiltinOperators.intDivide(ctx, param1, param2);
188       }
189     },
190     EXP("^") {
191       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
192         return BuiltinOperators.exp(ctx, param1, param2);
193       }
194     },
195     CONCAT("&") {
196       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
197         return BuiltinOperators.concat(ctx, param1, param2);
198       }
199     },
200     MOD("Mod") {
201       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
202         return BuiltinOperators.mod(ctx, param1, param2);
203       }
204     };
205 
206     private final String _str;
207 
208     private BinaryOp(String str) {
209       _str = str;
210     }
211 
212     @Override
213     public String toString() {
214       return _str;
215     }
216 
217     public abstract Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2);
218   }
219 
220   private enum CompOp implements OpType {
221     LT("<") {
222       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
223         return BuiltinOperators.lessThan(ctx, param1, param2);
224       }
225     },
226     LTE("<=") {
227       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
228         return BuiltinOperators.lessThanEq(ctx, param1, param2);
229       }
230     },
231     GT(">") {
232       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
233         return BuiltinOperators.greaterThan(ctx, param1, param2);
234       }
235     },
236     GTE(">=") {
237       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
238         return BuiltinOperators.greaterThanEq(ctx, param1, param2);
239       }
240     },
241     EQ("=") {
242       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
243         return BuiltinOperators.equals(ctx, param1, param2);
244       }
245     },
246     NE("<>") {
247       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
248         return BuiltinOperators.notEquals(ctx, param1, param2);
249       }
250     };
251 
252     private final String _str;
253 
254     private CompOp(String str) {
255       _str = str;
256     }
257 
258     @Override
259     public String toString() {
260       return _str;
261     }
262 
263     public abstract Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2);
264   }
265 
266   private enum LogOp implements OpType {
267     AND("And") {
268       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
269         return BuiltinOperators.and(ctx, param1, param2);
270       }
271     },
272     OR("Or") {
273       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
274         return BuiltinOperators.or(ctx, param1, param2);
275       }
276     },
277     EQV("Eqv") {
278       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
279         return BuiltinOperators.eqv(ctx, param1, param2);
280       }
281     },
282     XOR("Xor") {
283       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
284         return BuiltinOperators.xor(ctx, param1, param2);
285       }
286     },
287     IMP("Imp") {
288       @Override public Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2) {
289         return BuiltinOperators.imp(ctx, param1, param2);
290       }
291     };
292 
293     private final String _str;
294 
295     private LogOp(String str) {
296       _str = str;
297     }
298 
299     @Override
300     public String toString() {
301       return _str;
302     }
303 
304     public abstract Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Value param2);
305   }
306 
307   private enum SpecOp implements OpType {
308     // note, "NOT" is not actually used as a special operation, always
309     // replaced with UnaryOp.NOT
310     NOT("Not") {
311       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
312         throw new UnsupportedOperationException();
313       }
314     },
315     IS_NULL("Is Null") {
316       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
317         return BuiltinOperators.isNull(param1);
318       }
319     },
320     IS_NOT_NULL("Is Not Null") {
321       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
322         return BuiltinOperators.isNotNull(param1);
323       }
324     },
325     LIKE("Like") {
326       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
327         return BuiltinOperators.like(ctx, param1, (Pattern)param2);
328       }
329     },
330     NOT_LIKE("Not Like") {
331       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
332         return BuiltinOperators.notLike(ctx, param1, (Pattern)param2);
333       }
334     },
335     BETWEEN("Between") {
336       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
337         return BuiltinOperators.between(ctx, param1, (Value="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value)param2, (Value)param3);
338       }
339     },
340     NOT_BETWEEN("Not Between") {
341       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
342         return BuiltinOperators.notBetween(ctx, param1, (Value="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value)param2, (Value)param3);
343       }
344     },
345     IN("In") {
346       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
347         return BuiltinOperators.in(ctx, param1, (Value[])param2);
348       }
349     },
350     NOT_IN("Not In") {
351       @Override public Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
352         return BuiltinOperators.notIn(ctx, param1, (Value[])param2);
353       }
354     };
355 
356     private final String _str;
357 
358     private SpecOp(String str) {
359       _str = str;
360     }
361 
362     @Override
363     public String toString() {
364       return _str;
365     }
366 
367     public abstract Value/../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval(EvalContext ctx, Value param1, Object param2, Object param3);
368   }
369 
370   private static final Map<OpType, Integer> PRECENDENCE =
371     buildPrecedenceMap(
372         new OpType[]{UnaryOp.NEG_NUM, UnaryOp.POS_NUM},
373         new OpType[]{BinaryOp.EXP},
374         new OpType[]{UnaryOp.NEG, UnaryOp.POS},
375         new OpType[]{BinaryOp.MULT, BinaryOp.DIV},
376         new OpType[]{BinaryOp.INT_DIV},
377         new OpType[]{BinaryOp.MOD},
378         new OpType[]{BinaryOp.PLUS, BinaryOp.MINUS},
379         new OpType[]{BinaryOp.CONCAT},
380         new OpType[]{CompOp.LT, CompOp.GT, CompOp.NE, CompOp.LTE, CompOp.GTE,
381                      CompOp.EQ, SpecOp.LIKE, SpecOp.NOT_LIKE,
382                      SpecOp.IS_NULL, SpecOp.IS_NOT_NULL},
383         new OpType[]{UnaryOp.NOT},
384         new OpType[]{LogOp.AND},
385         new OpType[]{LogOp.OR},
386         new OpType[]{LogOp.XOR},
387         new OpType[]{LogOp.EQV},
388         new OpType[]{LogOp.IMP},
389         new OpType[]{SpecOp.IN, SpecOp.NOT_IN, SpecOp.BETWEEN,
390                      SpecOp.NOT_BETWEEN});
391 
392   private static final Set<Character> REGEX_SPEC_CHARS = new HashSet<Character>(
393       Arrays.asList('\\','.','%','=','+', '$','^','|','(',')','{','}','&',
394                     '[',']','*','?'));
395   // this is a regular expression which will never match any string
396   private static final Pattern UNMATCHABLE_REGEX = Pattern.compile("(?!)");
397 
398   private static final Expr THIS_COL_VALUE = new EThisValue();
399 
400   private static final Expr NULL_VALUE = new EConstValue(
401       ValueSupport.NULL_VAL, "Null");
402   private static final Expr TRUE_VALUE = new EConstValue(
403       ValueSupport.TRUE_VAL, "True");
404   private static final Expr FALSE_VALUE = new EConstValue(
405       ValueSupport.FALSE_VAL, "False");
406 
407 
408   private Expressionator() {}
409 
410   public static Expression parse(Type exprType, String exprStr,
411                                  Value.Type resultType,
412                                  ParseContext context) {
413 
414     List<Token> tokens = trimSpaces(
415         ExpressionTokenizer.tokenize(exprType, exprStr, context));
416 
417     if(tokens == null) {
418       throw new ParseException("null/empty expression");
419     }
420 
421     TokBuf buf = new TokBuf(exprType, tokens, context);
422 
423     if(isLiteralDefaultValue(buf, resultType, exprStr)) {
424 
425       // this is handled as a literal string value, not an expression.  no
426       // need to memo-ize cause it's a simple literal value
427       return new ExprWrapper(exprStr,
428           new ELiteralValue(Value.Type.STRING, exprStr), resultType);
429     }
430 
431     // normal expression handling
432     Expr expr = parseExpression(buf, false);
433 
434     if((exprType == Type.FIELD_VALIDATOR) && !expr.isValidationExpr()) {
435       // a non-validation expression for a FIELD_VALIDATOR treats the result
436       // as an equality comparison with the field in question.  so, transform
437       // the expression accordingly
438       expr = new EImplicitCompOp(expr);
439     }
440 
441     switch(exprType) {
442     case DEFAULT_VALUE:
443     case EXPRESSION:
444       return (expr.isConstant() ?
445               // for now, just cache at top-level for speed (could in theory
446               // cache intermediate values?)
447               new MemoizedExprWrapper(exprStr, expr, resultType) :
448               new ExprWrapper(exprStr, expr, resultType));
449     case FIELD_VALIDATOR:
450     case RECORD_VALIDATOR:
451       return (expr.isConstant() ?
452               // for now, just cache at top-level for speed (could in theory
453               // cache intermediate values?)
454               new MemoizedCondExprWrapper(exprStr, expr) :
455               new CondExprWrapper(exprStr, expr));
456     default:
457       throw new ParseException("unexpected expression type " + exprType);
458     }
459   }
460 
461   private static List<Token> trimSpaces(List<Token> tokens) {
462     if(tokens == null) {
463       return null;
464     }
465 
466     // for the most part, spaces are superfluous except for one situation(?).
467     // when they appear between a string literal and '(' they help distinguish
468     // a function call from another expression form
469     for(int i = 1; i < (tokens.size() - 1); ++i) {
470       Token t = tokens.get(i);
471       if(t.getType() == TokenType.SPACE) {
472         if((tokens.get(i - 1).getType() == TokenType.STRING) &&
473            isDelim(tokens.get(i + 1), FUNC_START_DELIM)) {
474           // we want to keep this space
475         } else {
476           tokens.remove(i);
477           --i;
478         }
479       }
480     }
481     return tokens;
482   }
483 
484   private static Expr parseExpression(TokBuf buf, boolean singleExpr)
485   {
486     while(buf.hasNext()) {
487       Token t = buf.next();
488 
489       switch(t.getType()) {
490       case OBJ_NAME:
491 
492         parseObjectRefExpression(t, buf);
493         break;
494 
495       case LITERAL:
496 
497         buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue()));
498         break;
499 
500       case OP:
501 
502         WordType wordType = getWordType(t);
503         if(wordType == null) {
504           // shouldn't happen
505           throw new ParseException("Invalid operator " + t);
506         }
507 
508         // this can only be an OP or a COMP (those are the only words that the
509         // tokenizer would define as TokenType.OP)
510         switch(wordType) {
511         case OP:
512           parseOperatorExpression(t, buf);
513           break;
514 
515         case COMP:
516 
517           parseCompOpExpression(t, buf);
518           break;
519 
520         default:
521           throw new ParseException("Unexpected OP word type " + wordType);
522         }
523 
524         break;
525 
526       case DELIM:
527 
528         parseDelimExpression(t, buf);
529         break;
530 
531       case STRING:
532 
533         // see if it's a special word?
534         wordType = getWordType(t);
535         if(wordType == null) {
536 
537           // is it a function call?
538           if(!maybeParseFuncCallExpression(t, buf)) {
539 
540             // is it an object name?
541             Token next = buf.peekNext();
542             if((next != null) && isObjNameSep(next)) {
543 
544               parseObjectRefExpression(t, buf);
545 
546             } else {
547 
548               // FIXME maybe bare obj name, maybe string literal?
549               throw new UnsupportedOperationException("FIXME");
550             }
551           }
552 
553         } else {
554 
555           // this could be anything but COMP or DELIM (all COMPs would be
556           // returned as TokenType.OP and all DELIMs would be TokenType.DELIM)
557           switch(wordType) {
558           case OP:
559 
560             parseOperatorExpression(t, buf);
561             break;
562 
563           case LOG_OP:
564 
565             parseLogicalOpExpression(t, buf);
566             break;
567 
568           case CONST:
569 
570             parseConstExpression(t, buf);
571             break;
572 
573           case SPEC_OP_PREFIX:
574 
575             parseSpecOpExpression(t, buf);
576             break;
577 
578           default:
579             throw new ParseException("Unexpected STRING word type "
580                                        + wordType);
581           }
582         }
583 
584         break;
585 
586       case SPACE:
587         // top-level space is irrelevant (and we strip them anyway)
588         break;
589 
590       default:
591         throw new ParseException("unknown token type " + t);
592       }
593 
594       if(singleExpr && buf.hasPendingExpr()) {
595         break;
596       }
597     }
598 
599     Expr expr = buf.takePendingExpr();
600     if(expr == null) {
601       throw new ParseException("No expression found? " + buf);
602     }
603 
604     return expr;
605   }
606 
607   private static void parseObjectRefExpression(Token firstTok, TokBuf buf) {
608 
609     // object references may be joined by '.' or '!'. access syntac docs claim
610     // object identifiers can be formatted like:
611     //     "[Collection name]![Object name].[Property name]"
612     // However, in practice, they only ever seem to be (at most) two levels
613     // and only use '.'.  Apparently '!' is actually a special late-bind
614     // operator (not sure it makes a difference for this code?), see:
615     // http://bytecomb.com/the-bang-exclamation-operator-in-vba/
616     Deque<String> objNames = new LinkedList<String>();
617     objNames.add(firstTok.getValueStr());
618 
619     Token t = null;
620     boolean atSep = false;
621     while((t = buf.peekNext()) != null) {
622       if(!atSep) {
623         if(isObjNameSep(t)) {
624           buf.next();
625           atSep = true;
626           continue;
627         }
628       } else {
629         if((t.getType() == TokenType.OBJ_NAME) ||
630            (t.getType() == TokenType.STRING)) {
631           buf.next();
632           // always insert at beginning of list so names are in reverse order
633           objNames.addFirst(t.getValueStr());
634           atSep = false;
635           continue;
636         }
637       }
638       break;
639     }
640 
641     int numNames = objNames.size();
642     if(atSep || (numNames > 3)) {
643       throw new ParseException("Invalid object reference " + buf);
644     }
645 
646     // names are in reverse order
647     String propName = null;
648     if(numNames == 3) {
649       propName = objNames.poll();
650     }
651     String objName = objNames.poll();
652     String collectionName = objNames.poll();
653 
654     buf.setPendingExpr(
655         new EObjValue(new Identifier(collectionName, objName, propName)));
656   }
657 
658   private static void parseDelimExpression(Token firstTok, TokBuf buf) {
659     // the only "top-level" delim we expect to find is open paren, and
660     // there shouldn't be any pending expression
661     if(!isDelim(firstTok, OPEN_PAREN) || buf.hasPendingExpr()) {
662       throw new ParseException("Unexpected delimiter " +
663                                          firstTok.getValue() + " " + buf);
664     }
665 
666     Expr subExpr = findParenExprs(buf, false).get(0);
667     buf.setPendingExpr(new EParen(subExpr));
668   }
669 
670   private static boolean maybeParseFuncCallExpression(
671       Token firstTok, TokBuf buf) {
672 
673     int startPos = buf.curPos();
674     boolean foundFunc = false;
675 
676     try {
677       Token t = buf.peekNext();
678       if(!isDelim(t, FUNC_START_DELIM)) {
679         // not a function call
680         return false;
681       }
682 
683       buf.next();
684       List<Expr> params = findParenExprs(buf, true);
685       String funcName = firstTok.getValueStr();
686       Function func = buf.getFunction(funcName);
687       if(func == null) {
688         throw new ParseException("Could not find function '" +
689                                            funcName + "' " + buf);
690       }
691       buf.setPendingExpr(new EFunc(func, params));
692       foundFunc = true;
693       return true;
694 
695     } finally {
696       if(!foundFunc) {
697         buf.reset(startPos);
698       }
699     }
700   }
701 
702   private static List<Expr> findParenExprs(
703       TokBuf buf, boolean allowMulti) {
704 
705     if(allowMulti) {
706       // simple case, no nested expr
707       Token t = buf.peekNext();
708       if(isDelim(t, CLOSE_PAREN)) {
709         buf.next();
710         return Collections.emptyList();
711       }
712     }
713 
714     // find closing ")", handle nested parens
715     List<Expr> exprs = new ArrayList<Expr>(3);
716     int level = 1;
717     int startPos = buf.curPos();
718     while(buf.hasNext()) {
719 
720       Token t = buf.next();
721 
722       if(isDelim(t, OPEN_PAREN)) {
723 
724         ++level;
725 
726       } else if(isDelim(t, CLOSE_PAREN)) {
727 
728         --level;
729         if(level == 0) {
730           TokBuf subBuf = buf.subBuf(startPos, buf.prevPos());
731           exprs.add(parseExpression(subBuf, false));
732           return exprs;
733         }
734 
735       } else if(allowMulti && (level == 1) && isDelim(t, FUNC_PARAM_SEP)) {
736 
737         TokBuf subBuf = buf.subBuf(startPos, buf.prevPos());
738         exprs.add(parseExpression(subBuf, false));
739         startPos = buf.curPos();
740       }
741     }
742 
743     throw new ParseException("Missing closing '" + CLOSE_PAREN
744                                        + " " + buf);
745   }
746 
747   private static void parseOperatorExpression(Token t, TokBuf buf) {
748 
749     // most ops are two argument except that '-' could be negation, "+" could
750     // be pos-ation
751     if(buf.hasPendingExpr()) {
752       parseBinaryOpExpression(t, buf);
753     } else if(isEitherOp(t, "-", "+")) {
754       parseUnaryOpExpression(t, buf);
755     } else {
756       throw new ParseException(
757           "Missing left expression for binary operator " + t.getValue() +
758           " " + buf);
759     }
760   }
761 
762   private static void parseBinaryOpExpression(Token firstTok, TokBuf buf) {
763     BinaryOp op = getOpType(firstTok, BinaryOp.class);
764     Expr leftExpr = buf.takePendingExpr();
765     Expr rightExpr = parseExpression(buf, true);
766 
767     buf.setPendingExpr(new EBinaryOp(op, leftExpr, rightExpr));
768   }
769 
770   private static void parseUnaryOpExpression(Token firstTok, TokBuf buf) {
771     UnaryOp op = getOpType(firstTok, UnaryOp.class);
772 
773     UnaryOp numOp = op.getUnaryNumOp();
774     if(numOp != null) {
775       // if this operator is immediately preceding a number, it has a higher
776       // precedence
777       Token nextTok = buf.peekNext();
778       if((nextTok != null) && (nextTok.getType() == TokenType.LITERAL) &&
779          nextTok.getValueType().isNumeric()) {
780         op = numOp;
781       }
782     }
783 
784     Expr val = parseExpression(buf, true);
785 
786     buf.setPendingExpr(new EUnaryOp(op, val));
787   }
788 
789   private static void parseCompOpExpression(Token firstTok, TokBuf buf) {
790 
791     if(!buf.hasPendingExpr()) {
792       if(buf.getExprType() == Type.FIELD_VALIDATOR) {
793         // comparison operators for field validators can implicitly use
794         // the current field value for the left value
795         buf.setPendingExpr(THIS_COL_VALUE);
796       } else {
797         throw new ParseException(
798             "Missing left expression for comparison operator " +
799             firstTok.getValue() + " " + buf);
800       }
801     }
802 
803     CompOp op = getOpType(firstTok, CompOp.class);
804     Expr leftExpr = buf.takePendingExpr();
805     Expr rightExpr = parseExpression(buf, true);
806 
807     buf.setPendingExpr(new ECompOp(op, leftExpr, rightExpr));
808   }
809 
810   private static void parseLogicalOpExpression(Token firstTok, TokBuf buf) {
811 
812     if(!buf.hasPendingExpr()) {
813       throw new ParseException(
814           "Missing left expression for logical operator " +
815           firstTok.getValue() + " " + buf);
816     }
817 
818     LogOp op = getOpType(firstTok, LogOp.class);
819     Expr leftExpr = buf.takePendingExpr();
820     Expr rightExpr = parseExpression(buf, true);
821 
822     buf.setPendingExpr(new ELogicalOp(op, leftExpr, rightExpr));
823   }
824 
825   private static void parseSpecOpExpression(Token firstTok, TokBuf buf) {
826 
827     SpecOp specOp = getSpecialOperator(firstTok, buf);
828 
829     if(specOp == SpecOp.NOT) {
830       // this is the unary prefix operator
831       parseUnaryOpExpression(firstTok, buf);
832       return;
833     }
834 
835     if(!buf.hasPendingExpr()) {
836       if(buf.getExprType() == Type.FIELD_VALIDATOR) {
837         // comparison operators for field validators can implicitly use
838         // the current field value for the left value
839         buf.setPendingExpr(THIS_COL_VALUE);
840       } else {
841         throw new ParseException(
842             "Missing left expression for comparison operator " +
843             specOp + " " + buf);
844       }
845     }
846 
847     Expr expr = buf.takePendingExpr();
848 
849     Expr specOpExpr = null;
850     switch(specOp) {
851     case IS_NULL:
852     case IS_NOT_NULL:
853       specOpExpr = new ENullOp(specOp, expr);
854       break;
855 
856     case LIKE:
857     case NOT_LIKE:
858       Token t = buf.next();
859       if((t.getType() != TokenType.LITERAL) ||
860          (t.getValueType() != Value.Type.STRING)) {
861         throw new ParseException("Missing Like pattern " + buf);
862       }
863       String patternStr = t.getValueStr();
864       specOpExpr = new ELikeOp(specOp, expr, patternStr);
865       break;
866 
867     case BETWEEN:
868     case NOT_BETWEEN:
869 
870       // the "rest" of a between expression is of the form "X And Y".  we are
871       // going to speculatively parse forward until we find the "And"
872       // operator.
873       Expr startRangeExpr = null;
874       while(true) {
875 
876         Expr tmpExpr = parseExpression(buf, true);
877         Token tmpT = buf.peekNext();
878 
879         if(tmpT == null) {
880           // ran out of expression?
881           throw new ParseException(
882               "Missing 'And' for 'Between' expression " + buf);
883         }
884 
885         if(isString(tmpT, "and")) {
886           buf.next();
887           startRangeExpr = tmpExpr;
888           break;
889         }
890 
891         // put the pending expression back and try parsing some more
892         buf.restorePendingExpr(tmpExpr);
893       }
894 
895       Expr endRangeExpr = parseExpression(buf, true);
896 
897       specOpExpr = new EBetweenOp(specOp, expr, startRangeExpr, endRangeExpr);
898       break;
899 
900     case IN:
901     case NOT_IN:
902 
903       // there might be a space before open paren
904       t = buf.next();
905       if(t.getType() == TokenType.SPACE) {
906         t = buf.next();
907       }
908       if(!isDelim(t, OPEN_PAREN)) {
909         throw new ParseException("Malformed 'In' expression " + buf);
910       }
911 
912       List<Expr> exprs = findParenExprs(buf, true);
913       specOpExpr = new EInOp(specOp, expr, exprs);
914       break;
915 
916     default:
917       throw new ParseException("Unexpected special op " + specOp);
918     }
919 
920     buf.setPendingExpr(specOpExpr);
921   }
922 
923   private static SpecOp getSpecialOperator(Token firstTok, TokBuf buf) {
924     String opStr = firstTok.getValueStr().toLowerCase();
925 
926     if("is".equals(opStr)) {
927       Token t = buf.peekNext();
928       if(isString(t, "null")) {
929         buf.next();
930         return SpecOp.IS_NULL;
931       } else if(isString(t, "not")) {
932         buf.next();
933         t = buf.peekNext();
934         if(isString(t, "null")) {
935           buf.next();
936           return SpecOp.IS_NOT_NULL;
937         }
938       }
939     } else if("like".equals(opStr)) {
940       return SpecOp.LIKE;
941     } else if("between".equals(opStr)) {
942       return SpecOp.BETWEEN;
943     } else if("in".equals(opStr)) {
944       return SpecOp.IN;
945     } else if("not".equals(opStr)) {
946       Token t = buf.peekNext();
947       if(isString(t, "between")) {
948         buf.next();
949         return SpecOp.NOT_BETWEEN;
950       } else if(isString(t, "in")) {
951         buf.next();
952         return SpecOp.NOT_IN;
953       } else if(isString(t, "like")) {
954         buf.next();
955         return SpecOp.NOT_LIKE;
956       }
957       return SpecOp.NOT;
958     }
959 
960     throw new ParseException(
961         "Malformed special operator " + opStr + " " + buf);
962   }
963 
964   private static void parseConstExpression(Token firstTok, TokBuf buf) {
965     Expr constExpr = null;
966     String tokStr = firstTok.getValueStr().toLowerCase();
967     if(TRUE_STRS.contains(tokStr)) {
968       constExpr = TRUE_VALUE;
969     } else if(FALSE_STRS.contains(tokStr)) {
970       constExpr = FALSE_VALUE;
971     } else if("null".equals(tokStr)) {
972       constExpr = NULL_VALUE;
973     } else {
974       throw new ParseException("Unexpected CONST word "
975                                  + firstTok.getValue());
976     }
977     buf.setPendingExpr(constExpr);
978   }
979 
980   private static boolean isObjNameSep(Token t) {
981     return (isDelim(t, ".") || isDelim(t, "!"));
982   }
983 
984   private static boolean isOp(Token t, String opStr) {
985     return ((t != null) && (t.getType() == TokenType.OP) &&
986             opStr.equalsIgnoreCase(t.getValueStr()));
987   }
988 
989   private static boolean isEitherOp(Token t, String opStr1, String opStr2) {
990     return ((t != null) && (t.getType() == TokenType.OP) &&
991             (opStr1.equalsIgnoreCase(t.getValueStr()) ||
992              opStr2.equalsIgnoreCase(t.getValueStr())));
993   }
994 
995   private static boolean isDelim(Token t, String opStr) {
996     return ((t != null) && (t.getType() == TokenType.DELIM) &&
997             opStr.equalsIgnoreCase(t.getValueStr()));
998   }
999 
1000   private static boolean isString(Token t, String opStr) {
1001     return ((t != null) && (t.getType() == TokenType.STRING) &&
1002             opStr.equalsIgnoreCase(t.getValueStr()));
1003   }
1004 
1005   private static WordType getWordType(Token t) {
1006     return WORD_TYPES.get(t.getValueStr().toLowerCase());
1007   }
1008 
1009   private static void setWordType(WordType type, String... words) {
1010     for(String w : words) {
1011       WORD_TYPES.put(w, type);
1012     }
1013   }
1014 
1015   private static <T extends Enum<T>> T getOpType(Token t, Class<T> opClazz) {
1016     String str = t.getValueStr();
1017     for(T op : opClazz.getEnumConstants()) {
1018       if(str.equalsIgnoreCase(op.toString())) {
1019         return op;
1020       }
1021     }
1022     throw new ParseException("Unexpected op string " + t.getValueStr());
1023   }
1024 
1025   private static StringBuilder appendLeadingExpr(
1026       Expr expr, LocaleContext ctx, StringBuilder sb, boolean isDebug)
1027   {
1028     int len = sb.length();
1029     expr.toString(ctx, sb, isDebug);
1030     if(sb.length() > len) {
1031       // only add space if the leading expr added some text
1032       sb.append(" ");
1033     }
1034     return sb;
1035   }
1036 
1037   private static final class TokBuf
1038   {
1039     private final Type _exprType;
1040     private final List<Token> _tokens;
1041     private final TokBuf _parent;
1042     private final int _parentOff;
1043     private final ParseContext _ctx;
1044     private int _pos;
1045     private Expr _pendingExpr;
1046 
1047     private TokBuf(Type exprType, List<Token> tokens, ParseContext context) {
1048       this(exprType, tokens, null, 0, context);
1049     }
1050 
1051     private TokBuf(List<Token> tokens, TokBuf parent, int parentOff) {
1052       this(parent._exprType, tokens, parent, parentOff, parent._ctx);
1053     }
1054 
1055     private TokBuf(Type exprType, List<Token> tokens, TokBuf parent,
1056                    int parentOff, ParseContext context) {
1057       _exprType = exprType;
1058       _tokens = tokens;
1059       _parent = parent;
1060       _parentOff = parentOff;
1061       _ctx = context;
1062     }
1063 
1064     public Type getExprType() {
1065       return _exprType;
1066     }
1067 
1068     public int curPos() {
1069       return _pos;
1070     }
1071 
1072     public int prevPos() {
1073       return _pos - 1;
1074     }
1075 
1076     public boolean hasNext() {
1077       return (_pos < _tokens.size());
1078     }
1079 
1080     public Token peekNext() {
1081       if(!hasNext()) {
1082         return null;
1083       }
1084       return _tokens.get(_pos);
1085     }
1086 
1087     public Token next() {
1088       if(!hasNext()) {
1089         throw new ParseException(
1090             "Unexpected end of expression " + this);
1091       }
1092       return _tokens.get(_pos++);
1093     }
1094 
1095     public void reset(int pos) {
1096       _pos = pos;
1097     }
1098 
1099     public TokBuf subBuf(int start, int end) {
1100       return new TokBuf(_tokens.subList(start, end), this, start);
1101     }
1102 
1103     public void setPendingExpr(Expr expr) {
1104       if(_pendingExpr != null) {
1105         throw new ParseException(
1106             "Found multiple expressions with no operator " + this);
1107       }
1108       _pendingExpr = expr.resolveOrderOfOperations();
1109     }
1110 
1111     public void restorePendingExpr(Expr expr) {
1112       // this is an expression which was previously set, so no need to re-resolve
1113       _pendingExpr = expr;
1114     }
1115 
1116     public Expr takePendingExpr() {
1117       Expr expr = _pendingExpr;
1118       _pendingExpr = null;
1119       return expr;
1120     }
1121 
1122     public boolean hasPendingExpr() {
1123       return (_pendingExpr != null);
1124     }
1125 
1126     private Map.Entry<Integer,List<Token>> getTopPos() {
1127       int pos = _pos;
1128       List<Token> toks = _tokens;
1129       TokBuf cur = this;
1130       while(cur._parent != null) {
1131         pos += cur._parentOff;
1132         cur = cur._parent;
1133         toks = cur._tokens;
1134       }
1135       return ExpressionTokenizer.newEntry(pos, toks);
1136     }
1137 
1138     public Function getFunction(String funcName) {
1139       return _ctx.getFunctionLookup().getFunction(funcName);
1140     }
1141 
1142     @Override
1143     public String toString() {
1144 
1145       Map.Entry<Integer,List<Token>> e = getTopPos();
1146 
1147       // TODO actually format expression?
1148       StringBuilder sb = new StringBuilder()
1149         .append("[token ").append(e.getKey()).append("] (");
1150 
1151       for(Iterator<Token> iter = e.getValue().iterator(); iter.hasNext(); ) {
1152         Token t = iter.next();
1153         sb.append("'").append(t.getValueStr()).append("'");
1154         if(iter.hasNext()) {
1155           sb.append(",");
1156         }
1157       }
1158 
1159       sb.append(")");
1160 
1161       if(_pendingExpr != null) {
1162         sb.append(" [pending '").append(_pendingExpr.toDebugString(_ctx))
1163           .append("']");
1164       }
1165 
1166       return sb.toString();
1167     }
1168   }
1169 
1170   private static boolean isHigherPrecendence(OpType op1, OpType op2) {
1171     int prec1 = PRECENDENCE.get(op1);
1172     int prec2 = PRECENDENCE.get(op2);
1173 
1174     // higher preceendence ops have lower numbers
1175     return (prec1 < prec2);
1176   }
1177 
1178   private static final Map<OpType, Integer> buildPrecedenceMap(
1179       OpType[]... opArrs) {
1180     Map<OpType, Integer> prec = new HashMap<OpType, Integer>();
1181 
1182     int level = 0;
1183     for(OpType[] ops : opArrs) {
1184       for(OpType op : ops) {
1185         prec.put(op, level);
1186       }
1187       ++level;
1188     }
1189 
1190     return prec;
1191   }
1192 
1193   private static void exprListToString(
1194       List<Expr> exprs, String sep, LocaleContext ctx, StringBuilder sb,
1195       boolean isDebug) {
1196     Iterator<Expr> iter = exprs.iterator();
1197     iter.next().toString(ctx, sb, isDebug);
1198     while(iter.hasNext()) {
1199       sb.append(sep);
1200       iter.next().toString(ctx, sb, isDebug);
1201     }
1202   }
1203 
1204   private static Value[] exprListToValues(
1205       List<Expr> exprs, EvalContext ctx) {
1206     Valueess/expr/Value.html#Value">Value[] paramVals = new Value[exprs.size()];
1207     for(int i = 0; i < exprs.size(); ++i) {
1208       paramVals[i] = exprs.get(i).eval(ctx);
1209     }
1210     return paramVals;
1211   }
1212 
1213   private static Value[] exprListToDelayedValues(
1214       List<Expr> exprs, EvalContext ctx) {
1215     Valueess/expr/Value.html#Value">Value[] paramVals = new Value[exprs.size()];
1216     for(int i = 0; i < exprs.size(); ++i) {
1217       paramVals[i] = new DelayedValue(exprs.get(i), ctx);
1218     }
1219     return paramVals;
1220   }
1221 
1222   private static boolean areConstant(List<Expr> exprs) {
1223     for(Expr expr : exprs) {
1224       if(!expr.isConstant()) {
1225         return false;
1226       }
1227     }
1228     return true;
1229   }
1230 
1231   private static boolean areConstant(Expr... exprs) {
1232     for(Expr expr : exprs) {
1233       if(!expr.isConstant()) {
1234         return false;
1235       }
1236     }
1237     return true;
1238   }
1239 
1240   private static void literalStrToString(String str, StringBuilder sb) {
1241     sb.append("\"")
1242       .append(StringUtils.replace(str, "\"", "\"\""))
1243       .append("\"");
1244   }
1245 
1246   /**
1247    * Converts an ms access like pattern to a java regex, always matching case
1248    * insensitively.
1249    */
1250   public static Pattern likePatternToRegex(String pattern) {
1251 
1252     StringBuilder sb = new StringBuilder(pattern.length());
1253 
1254     // Access LIKE pattern supports (note, matching is case-insensitive):
1255     // - '*' -> 0 or more chars
1256     // - '?' -> single character
1257     // - '#' -> single digit
1258     // - '[...]' -> character class, '[!...]' -> not in char class
1259 
1260     for(int i = 0; i < pattern.length(); ++i) {
1261       char c = pattern.charAt(i);
1262 
1263       if(c == '*') {
1264         sb.append(".*");
1265       } else if(c == '?') {
1266         sb.append('.');
1267       } else if(c == '#') {
1268         sb.append("\\d");
1269       } else if(c == '[') {
1270 
1271         // find closing brace
1272         int startPos = i + 1;
1273         int endPos = -1;
1274         for(int j = startPos; j < pattern.length(); ++j) {
1275           if(pattern.charAt(j) == ']') {
1276             endPos = j;
1277             break;
1278           }
1279         }
1280 
1281         // access treats invalid expression like "unmatchable"
1282         if(endPos == -1) {
1283           return UNMATCHABLE_REGEX;
1284         }
1285 
1286         String charClass = pattern.substring(startPos, endPos);
1287 
1288         if((charClass.length() > 0) && (charClass.charAt(0) == '!')) {
1289           // this is a negated char class
1290           charClass = '^' + charClass.substring(1);
1291         }
1292 
1293         sb.append('[').append(charClass).append(']');
1294         i += (endPos - startPos) + 1;
1295 
1296       } else if(isRegexSpecialChar(c)) {
1297         // this char is special in regexes, so escape it
1298         sb.append('\\').append(c);
1299       } else {
1300         sb.append(c);
1301       }
1302     }
1303 
1304     try {
1305       return Pattern.compile(sb.toString(),
1306                              Pattern.CASE_INSENSITIVE | Pattern.DOTALL |
1307                              Pattern.UNICODE_CASE);
1308     } catch(PatternSyntaxException ignored) {
1309       return UNMATCHABLE_REGEX;
1310     }
1311   }
1312 
1313   public static boolean isRegexSpecialChar(char c) {
1314     return REGEX_SPEC_CHARS.contains(c);
1315   }
1316 
1317   private static Value toLiteralValue(Value.Type valType, Object value) {
1318     switch(valType) {
1319     case STRING:
1320       return ValueSupport.toValue((String)value);
1321     case DATE:
1322     case TIME:
1323     case DATE_TIME:
1324       return ValueSupport.toValue(valType, (LocalDateTime)value);
1325     case LONG:
1326       return ValueSupport.toValue((Integer)value);
1327     case DOUBLE:
1328       return ValueSupport.toValue((Double)value);
1329     case BIG_DEC:
1330       return ValueSupport.toValue((BigDecimal)value);
1331     default:
1332       throw new ParseException("unexpected literal type " + valType);
1333     }
1334   }
1335 
1336   private static boolean isLiteralDefaultValue(
1337       TokBuf buf, Value.Type resultType, String exprStr) {
1338 
1339     // if a default value expression does not start with an '=' and is used in
1340     // a string context, then it is taken as a literal value unless it starts
1341     // with a " char
1342 
1343     if(buf.getExprType() != Type.DEFAULT_VALUE) {
1344       return false;
1345     }
1346 
1347     // a leading "=" indicates "full" expression handling for a DEFAULT_VALUE
1348     // (consume this value once we detect it)
1349     if(isOp(buf.peekNext(), "=")) {
1350       buf.next();
1351       return false;
1352     }
1353 
1354     return((resultType == Value.Type.STRING) &&
1355            ((exprStr.length() == 0) ||
1356             (exprStr.charAt(0) != ExpressionTokenizer.QUOTED_STR_CHAR)));
1357   }
1358 
1359   private interface LeftAssocExpr {
1360     public OpType getOp();
1361     public Expr getLeft();
1362     public void setLeft(Expr left);
1363   }
1364 
1365   private interface RightAssocExpr {
1366     public OpType getOp();
1367     public Expr getRight();
1368     public void setRight(Expr right);
1369   }
1370 
1371   private static final class DelayedValue extends BaseDelayedValue
1372   {
1373     private final Expr _expr;
1374     private final EvalContext _ctx;
1375 
1376     private DelayedValue(Expr expr, EvalContext ctx) {
1377       _expr = expr;
1378       _ctx = ctx;
1379     }
1380 
1381     @Override
1382     public Value eval() {
1383       return _expr.eval(_ctx);
1384     }
1385   }
1386 
1387 
1388   private static abstract class Expr
1389   {
1390     public String toCleanString(LocaleContext ctx) {
1391       return toString(ctx, new StringBuilder(), false).toString();
1392     }
1393 
1394     public String toDebugString(LocaleContext ctx) {
1395       return toString(ctx, new StringBuilder(), true).toString();
1396     }
1397 
1398     protected boolean isValidationExpr() {
1399       return false;
1400     }
1401 
1402     protected StringBuilder toString(
1403         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1404       if(isDebug) {
1405         sb.append("<").append(getClass().getSimpleName()).append(">{");
1406       }
1407       toExprString(ctx, sb, isDebug);
1408       if(isDebug) {
1409         sb.append("}");
1410       }
1411       return sb;
1412     }
1413 
1414     protected Expr resolveOrderOfOperations() {
1415 
1416       if(!(this instanceof LeftAssocExpr)) {
1417         // nothing we can do
1418         return this;
1419       }
1420 
1421       // in order to get the precedence right, we need to first associate this
1422       // expression with the "rightmost" expression preceding it, then adjust
1423       // this expression "down" (lower precedence) as the precedence of the
1424       // operations dictates.  since we parse from left to right, the initial
1425       // "left" value isn't the immediate left expression, instead it's based
1426       // on how the preceding operator precedence worked out.  we need to
1427       // adjust "this" expression to the closest preceding expression before
1428       // we can correctly resolve precedence.
1429 
1430       Expr outerExpr = this;
1431       final LeftAssocExpr thisExpr = (LeftAssocExpr)this;
1432       final Expr thisLeft = thisExpr.getLeft();
1433 
1434       // current: <this>{<left>{A op1 B} op2 <right>{C}}
1435       if(thisLeft instanceof RightAssocExpr) {
1436 
1437         RightAssocExpr leftOp = (RightAssocExpr)thisLeft;
1438 
1439         // target: <left>{A op1 <this>{B op2 <right>{C}}}
1440 
1441         thisExpr.setLeft(leftOp.getRight());
1442 
1443         // give the new version of this expression an opportunity to further
1444         // swap (since the swapped expression may itself be a binary
1445         // expression)
1446         leftOp.setRight(resolveOrderOfOperations());
1447         outerExpr = thisLeft;
1448 
1449         // at this point, this expression has been pushed all the way to the
1450         // rightmost preceding expression (we artifically gave "this" the
1451         // highest precedence).  now, we want to adjust precedence as
1452         // necessary (shift it back down if the operator precedence is
1453         // incorrect).  note, we only need to check precedence against "this",
1454         // as all other precedence has been resolved in previous parsing
1455         // rounds.
1456         if((leftOp.getRight() == this) &&
1457            !isHigherPrecendence(thisExpr.getOp(), leftOp.getOp())) {
1458 
1459           // doh, "this" is lower (or the same) precedence, restore the
1460           // original order of things
1461           leftOp.setRight(thisExpr.getLeft());
1462           thisExpr.setLeft(thisLeft);
1463           outerExpr = this;
1464         }
1465       }
1466 
1467       return outerExpr;
1468     }
1469 
1470     public abstract boolean isConstant();
1471 
1472     public abstract Value eval(EvalContext ctx);
1473 
1474     public abstract void collectIdentifiers(Collection<Identifier> identifiers);
1475 
1476     protected abstract void toExprString(
1477         LocaleContext ctx, StringBuilder sb, boolean isDebug);
1478   }
1479 
1480   private static final class EConstValue extends Expr
1481   {
1482     private final Value _val;
1483     private final String _str;
1484 
1485     private EConstValue(Value val, String str) {
1486       _val = val;
1487       _str = str;
1488     }
1489 
1490     @Override
1491     public boolean isConstant() {
1492       return true;
1493     }
1494 
1495     @Override
1496     public Value eval(EvalContext ctx) {
1497       return _val;
1498     }
1499 
1500     @Override
1501     public void collectIdentifiers(Collection<Identifier> identifiers) {
1502       // none
1503     }
1504 
1505     @Override
1506     protected void toExprString(
1507         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1508       sb.append(_str);
1509     }
1510   }
1511 
1512   private static final class EThisValue extends Expr
1513   {
1514     @Override
1515     public boolean isConstant() {
1516       return false;
1517     }
1518     @Override
1519     public Value eval(EvalContext ctx) {
1520       return ctx.getThisColumnValue();
1521     }
1522     @Override
1523     public void collectIdentifiers(Collection<Identifier> identifiers) {
1524       // none
1525     }
1526     @Override
1527     protected void toExprString(
1528         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1529       if(isDebug) {
1530         sb.append("<THIS_COL>");
1531       }
1532     }
1533   }
1534 
1535   private static final class ELiteralValue extends Expr
1536   {
1537     private final Value _val;
1538 
1539     private ELiteralValue(Value.Type valType, Object value) {
1540       _val = toLiteralValue(valType, value);
1541     }
1542 
1543     @Override
1544     public boolean isConstant() {
1545       return true;
1546     }
1547 
1548     @Override
1549     public Value eval(EvalContext ctx) {
1550       return _val;
1551     }
1552 
1553     @Override
1554     public void collectIdentifiers(Collection<Identifier> identifiers) {
1555       // none
1556     }
1557 
1558     @Override
1559     protected void toExprString(
1560         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1561       if(_val.getType() == Value.Type.STRING) {
1562         literalStrToString((String)_val.get(), sb);
1563       } else if(_val.getType().isTemporal()) {
1564         sb.append("#").append(_val.getAsString(ctx)).append("#");
1565       } else {
1566         sb.append(_val.get());
1567       }
1568     }
1569   }
1570 
1571   private static final class EObjValue extends Expr
1572   {
1573     private final Identifier _identifier;
1574 
1575     private EObjValue(Identifier identifier) {
1576       _identifier = identifier;
1577     }
1578 
1579     @Override
1580     public boolean isConstant() {
1581       return false;
1582     }
1583 
1584     @Override
1585     public Value eval(EvalContext ctx) {
1586       return ctx.getIdentifierValue(_identifier);
1587     }
1588 
1589     @Override
1590     public void collectIdentifiers(Collection<Identifier> identifiers) {
1591       identifiers.add(_identifier);
1592     }
1593 
1594     @Override
1595     protected void toExprString(
1596         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1597       sb.append(_identifier);
1598     }
1599   }
1600 
1601   private static class EParen extends Expr
1602   {
1603     private final Expr _expr;
1604 
1605     private EParen(Expr expr) {
1606       _expr = expr;
1607     }
1608 
1609     @Override
1610     public boolean isConstant() {
1611       return _expr.isConstant();
1612     }
1613 
1614     @Override
1615     protected boolean isValidationExpr() {
1616       return _expr.isValidationExpr();
1617     }
1618 
1619     @Override
1620     public Value eval(EvalContext ctx) {
1621       return _expr.eval(ctx);
1622     }
1623 
1624     @Override
1625     public void collectIdentifiers(Collection<Identifier> identifiers) {
1626       _expr.collectIdentifiers(identifiers);
1627     }
1628 
1629     @Override
1630     protected void toExprString(
1631         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1632       sb.append("(");
1633       _expr.toString(ctx, sb, isDebug);
1634       sb.append(")");
1635     }
1636   }
1637 
1638   private static class EFunc extends Expr
1639   {
1640     private final Function _func;
1641     private final List<Expr> _params;
1642 
1643     private EFunc(Function func, List<Expr> params) {
1644       _func = func;
1645       _params = params;
1646     }
1647 
1648     @Override
1649     public boolean isConstant() {
1650       return _func.isPure() && areConstant(_params);
1651     }
1652 
1653     @Override
1654     public Value eval(EvalContext ctx) {
1655       return _func.eval(ctx, exprListToValues(_params, ctx));
1656     }
1657 
1658     @Override
1659     public void collectIdentifiers(Collection<Identifier> identifiers) {
1660       for(Expr param : _params) {
1661         param.collectIdentifiers(identifiers);
1662       }
1663     }
1664 
1665     @Override
1666     protected void toExprString(
1667         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1668       sb.append(_func.getName()).append("(");
1669 
1670       if(!_params.isEmpty()) {
1671         exprListToString(_params, ",", ctx, sb, isDebug);
1672       }
1673 
1674       sb.append(")");
1675     }
1676   }
1677 
1678   private static abstract class EBaseBinaryOp extends Expr
1679     implements LeftAssocExpr, RightAssocExpr
1680   {
1681     protected final OpType _op;
1682     protected Expr _left;
1683     protected Expr _right;
1684 
1685     private EBaseBinaryOp(OpType op, Expr left, Expr right) {
1686       _op = op;
1687       _left = left;
1688       _right = right;
1689     }
1690 
1691     @Override
1692     public boolean isConstant() {
1693       return areConstant(_left, _right);
1694     }
1695 
1696     @Override
1697     public OpType getOp() {
1698       return _op;
1699     }
1700 
1701     @Override
1702     public Expr getLeft() {
1703       return _left;
1704     }
1705 
1706     @Override
1707     public void setLeft(Expr left) {
1708       _left = left;
1709     }
1710 
1711     @Override
1712     public Expr getRight() {
1713       return _right;
1714     }
1715 
1716     @Override
1717     public void setRight(Expr right) {
1718       _right = right;
1719     }
1720 
1721     @Override
1722     public void collectIdentifiers(Collection<Identifier> identifiers) {
1723       _left.collectIdentifiers(identifiers);
1724       _right.collectIdentifiers(identifiers);
1725     }
1726 
1727     @Override
1728     protected void toExprString(
1729         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1730       appendLeadingExpr(_left, ctx, sb, isDebug)
1731         .append(_op).append(" ");
1732       _right.toString(ctx, sb, isDebug);
1733     }
1734   }
1735 
1736   private static class EBinaryOp extends EBaseBinaryOp
1737   {
1738     private EBinaryOp(BinaryOp op, Expr left, Expr right) {
1739       super(op, left, right);
1740     }
1741 
1742     @Override
1743     public Value eval(EvalContext ctx) {
1744       return ((BinaryOp)_op).eval(ctx, _left.eval(ctx), _right.eval(ctx));
1745     }
1746   }
1747 
1748   private static class EUnaryOp extends Expr
1749     implements RightAssocExpr
1750   {
1751     private final OpType _op;
1752     private Expr _expr;
1753 
1754     private EUnaryOp(UnaryOp op, Expr expr) {
1755       _op = op;
1756       _expr = expr;
1757     }
1758 
1759     @Override
1760     public boolean isConstant() {
1761       return _expr.isConstant();
1762     }
1763 
1764     @Override
1765     public OpType getOp() {
1766       return _op;
1767     }
1768 
1769     @Override
1770     public Expr getRight() {
1771       return _expr;
1772     }
1773 
1774     @Override
1775     public void setRight(Expr right) {
1776       _expr = right;
1777     }
1778 
1779     @Override
1780     public Value eval(EvalContext ctx) {
1781       return ((UnaryOp)_op).eval(ctx, _expr.eval(ctx));
1782     }
1783 
1784     @Override
1785     public void collectIdentifiers(Collection<Identifier> identifiers) {
1786       _expr.collectIdentifiers(identifiers);
1787     }
1788 
1789     @Override
1790     protected void toExprString(
1791         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1792       sb.append(_op);
1793       if(isDebug || ((UnaryOp)_op).needsSpace()) {
1794         sb.append(" ");
1795       }
1796       _expr.toString(ctx, sb, isDebug);
1797     }
1798   }
1799 
1800   private static class ECompOp extends EBaseBinaryOp
1801   {
1802     private ECompOp(CompOp op, Expr left, Expr right) {
1803       super(op, left, right);
1804     }
1805 
1806     @Override
1807     protected boolean isValidationExpr() {
1808       return true;
1809     }
1810 
1811     @Override
1812     public Value eval(EvalContext ctx) {
1813       return ((CompOp)_op).eval(ctx, _left.eval(ctx), _right.eval(ctx));
1814     }
1815   }
1816 
1817   private static class EImplicitCompOp extends ECompOp
1818   {
1819     private EImplicitCompOp(Expr right) {
1820       super(CompOp.EQ, THIS_COL_VALUE, right);
1821     }
1822 
1823     @Override
1824     protected void toExprString(
1825         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1826       // only output the full "implicit" comparison in debug mode
1827       if(isDebug) {
1828         super.toExprString(ctx, sb, isDebug);
1829       } else {
1830         // just output the explicit part of the expression
1831         _right.toString(ctx, sb, isDebug);
1832       }
1833     }
1834   }
1835 
1836   private static class ELogicalOp extends EBaseBinaryOp
1837   {
1838     private ELogicalOp(LogOp op, Expr left, Expr right) {
1839       super(op, left, right);
1840     }
1841 
1842     @Override
1843     protected boolean isValidationExpr() {
1844       return true;
1845     }
1846 
1847     @Override
1848     public Value eval(final EvalContext ctx) {
1849 
1850       // logical operations do short circuit evaluation, so we need to delay
1851       // computing results until necessary
1852       return ((LogOp)_op).eval(ctx, new DelayedValue(_left, ctx),
1853                                new DelayedValue(_right, ctx));
1854     }
1855   }
1856 
1857   private static abstract class ESpecOp extends Expr
1858     implements LeftAssocExpr
1859   {
1860     protected final SpecOp _op;
1861     protected Expr _expr;
1862 
1863     private ESpecOp(SpecOp op, Expr expr) {
1864       _op = op;
1865       _expr = expr;
1866     }
1867 
1868     @Override
1869     public boolean isConstant() {
1870       return _expr.isConstant();
1871     }
1872 
1873     @Override
1874     public OpType getOp() {
1875       return _op;
1876     }
1877 
1878     @Override
1879     public Expr getLeft() {
1880       return _expr;
1881     }
1882 
1883     @Override
1884     public void setLeft(Expr left) {
1885       _expr = left;
1886     }
1887 
1888     @Override
1889     public void collectIdentifiers(Collection<Identifier> identifiers) {
1890       _expr.collectIdentifiers(identifiers);
1891     }
1892 
1893     @Override
1894     protected boolean isValidationExpr() {
1895       return true;
1896     }
1897   }
1898 
1899   private static class ENullOp extends ESpecOp
1900   {
1901     private ENullOp(SpecOp op, Expr expr) {
1902       super(op, expr);
1903     }
1904 
1905     @Override
1906     public Value eval(EvalContext ctx) {
1907       return _op.eval(ctx, _expr.eval(ctx), null, null);
1908     }
1909 
1910     @Override
1911     protected void toExprString(
1912         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1913       appendLeadingExpr(_expr, ctx, sb, isDebug)
1914         .append(_op);
1915     }
1916   }
1917 
1918   private static class ELikeOp extends ESpecOp
1919   {
1920     private final String _patternStr;
1921     private Pattern _pattern;
1922 
1923     private ELikeOp(SpecOp op, Expr expr, String patternStr) {
1924       super(op, expr);
1925       _patternStr = patternStr;
1926     }
1927 
1928     private Pattern getPattern()
1929     {
1930       if(_pattern == null) {
1931         _pattern = likePatternToRegex(_patternStr);
1932       }
1933       return _pattern;
1934     }
1935 
1936     @Override
1937     public Value eval(EvalContext ctx) {
1938       return _op.eval(ctx, _expr.eval(ctx), getPattern(), null);
1939     }
1940 
1941     @Override
1942     protected void toExprString(
1943         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1944       appendLeadingExpr(_expr, ctx, sb, isDebug)
1945         .append(_op).append(" ");
1946       literalStrToString(_patternStr, sb);
1947       if(isDebug) {
1948         sb.append("(").append(getPattern()).append(")");
1949       }
1950     }
1951   }
1952 
1953   private static class EInOp extends ESpecOp
1954   {
1955     private final List<Expr> _exprs;
1956 
1957     private EInOp(SpecOp op, Expr expr, List<Expr> exprs) {
1958       super(op, expr);
1959       _exprs = exprs;
1960     }
1961 
1962     @Override
1963     public boolean isConstant() {
1964       return super.isConstant() && areConstant(_exprs);
1965     }
1966 
1967     @Override
1968     public Value eval(EvalContext ctx) {
1969       return _op.eval(ctx, _expr.eval(ctx),
1970                       exprListToDelayedValues(_exprs, ctx), null);
1971     }
1972 
1973     @Override
1974     public void collectIdentifiers(Collection<Identifier> identifiers) {
1975       for(Expr expr : _exprs) {
1976         expr.collectIdentifiers(identifiers);
1977       }
1978     }
1979 
1980     @Override
1981     protected void toExprString(
1982         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1983       appendLeadingExpr(_expr, ctx, sb, isDebug)
1984         .append(_op).append(" (");
1985       exprListToString(_exprs, ",", ctx, sb, isDebug);
1986       sb.append(")");
1987     }
1988   }
1989 
1990   private static class EBetweenOp extends ESpecOp
1991     implements RightAssocExpr
1992   {
1993     private final Expr _startRangeExpr;
1994     private Expr _endRangeExpr;
1995 
1996     private EBetweenOp(SpecOp op, Expr expr, Expr startRangeExpr,
1997                        Expr endRangeExpr) {
1998       super(op, expr);
1999       _startRangeExpr = startRangeExpr;
2000       _endRangeExpr = endRangeExpr;
2001     }
2002 
2003     @Override
2004     public boolean isConstant() {
2005       return _expr.isConstant() && areConstant(_startRangeExpr, _endRangeExpr);
2006     }
2007 
2008     @Override
2009     public Expr getRight() {
2010       return _endRangeExpr;
2011     }
2012 
2013     @Override
2014     public void setRight(Expr right) {
2015       _endRangeExpr = right;
2016     }
2017 
2018     @Override
2019     public Value eval(EvalContext ctx) {
2020       return _op.eval(ctx, _expr.eval(ctx),
2021                       new DelayedValue(_startRangeExpr, ctx),
2022                       new DelayedValue(_endRangeExpr, ctx));
2023     }
2024 
2025     @Override
2026     public void collectIdentifiers(Collection<Identifier> identifiers) {
2027       super.collectIdentifiers(identifiers);
2028       _startRangeExpr.collectIdentifiers(identifiers);
2029       _endRangeExpr.collectIdentifiers(identifiers);
2030     }
2031 
2032     @Override
2033     protected void toExprString(
2034         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
2035       appendLeadingExpr(_expr, ctx, sb, isDebug)
2036         .append(_op).append(" ");
2037       _startRangeExpr.toString(ctx, sb, isDebug);
2038       sb.append(" And ");
2039       _endRangeExpr.toString(ctx, sb, isDebug);
2040     }
2041   }
2042 
2043   /**
2044    * Base Expression wrapper for an Expr.
2045    */
2046   private static abstract class BaseExprWrapper implements Expression
2047   {
2048     private final String _rawExprStr;
2049     private final Expr _expr;
2050 
2051     private BaseExprWrapper(String rawExprStr, Expr expr) {
2052       _rawExprStr = rawExprStr;
2053       _expr = expr;
2054     }
2055 
2056     @Override
2057     public String toDebugString(LocaleContext ctx) {
2058       return _expr.toDebugString(ctx);
2059     }
2060 
2061     @Override
2062     public String toRawString() {
2063       return _rawExprStr;
2064     }
2065 
2066     @Override
2067     public String toCleanString(LocaleContext ctx) {
2068       return _expr.toCleanString(ctx);
2069     }
2070 
2071     @Override
2072     public boolean isConstant() {
2073       return _expr.isConstant();
2074     }
2075 
2076     @Override
2077     public void collectIdentifiers(Collection<Identifier> identifiers) {
2078       _expr.collectIdentifiers(identifiers);
2079     }
2080 
2081     @Override
2082     public String toString() {
2083       return toRawString();
2084     }
2085 
2086     protected Object evalValue(Value.Type resultType, EvalContext ctx) {
2087       Value val = _expr.eval(ctx);
2088 
2089       if(val.isNull()) {
2090         return null;
2091       }
2092 
2093       if(resultType == null) {
2094         // return as "native" type
2095         return val.get();
2096       }
2097 
2098       // FIXME possibly do some type coercion.  are there conversions here which don't work elsewhere? (string -> date, string -> number)?
2099       switch(resultType) {
2100       case STRING:
2101         return val.getAsString(ctx);
2102       case DATE:
2103       case TIME:
2104       case DATE_TIME:
2105         return val.getAsLocalDateTime(ctx);
2106       case LONG:
2107         return val.getAsLongInt(ctx);
2108       case DOUBLE:
2109         return val.getAsDouble(ctx);
2110       case BIG_DEC:
2111         return val.getAsBigDecimal(ctx);
2112       default:
2113         throw new IllegalStateException("unexpected result type " + resultType);
2114       }
2115     }
2116 
2117     protected Boolean evalCondition(EvalContext ctx) {
2118       Value val = _expr.eval(ctx);
2119 
2120       if(val.isNull()) {
2121         // null can't be coerced to a boolean
2122         throw new EvalException("Condition evaluated to Null");
2123       }
2124 
2125       return val.getAsBoolean(ctx);
2126     }
2127   }
2128 
2129   /**
2130    * Expression wrapper for an Expr which returns a value.
2131    */
2132   private static class ExprWrapper extends BaseExprWrapper
2133   {
2134     private final Value.Type _resultType;
2135 
2136     private ExprWrapper(String rawExprStr, Expr expr, Value.Type resultType) {
2137       super(rawExprStr, expr);
2138       _resultType = resultType;
2139     }
2140 
2141     @Override
2142     public Object eval(EvalContext ctx) {
2143       return evalValue(_resultType, ctx);
2144     }
2145   }
2146 
2147   /**
2148    * Expression wrapper for an Expr which returns a Boolean from a conditional
2149    * expression.
2150    */
2151   private static class CondExprWrapper extends BaseExprWrapper
2152   {
2153     private CondExprWrapper(String rawExprStr, Expr expr) {
2154       super(rawExprStr, expr);
2155     }
2156 
2157     @Override
2158     public Object eval(EvalContext ctx) {
2159       return evalCondition(ctx);
2160     }
2161   }
2162 
2163   /**
2164    * Expression wrapper for a <i>pure</i> Expr which caches the result of
2165    * evaluation.
2166    */
2167   private static final class MemoizedExprWrapper extends ExprWrapper
2168   {
2169     private Object _val;
2170 
2171     private MemoizedExprWrapper(String rawExprStr, Expr expr,
2172                                 Value.Type resultType) {
2173       super(rawExprStr, expr, resultType);
2174     }
2175 
2176     @Override
2177     public Object eval(EvalContext ctx) {
2178       if(_val == null) {
2179         _val = super.eval(ctx);
2180       }
2181       return _val;
2182     }
2183   }
2184 
2185   /**
2186    * Expression wrapper for a <i>pure</i> conditional Expr which caches the
2187    * result of evaluation.
2188    */
2189   private static final class MemoizedCondExprWrapper extends CondExprWrapper
2190   {
2191     private Object _val;
2192 
2193     private MemoizedCondExprWrapper(String rawExprStr, Expr expr) {
2194       super(rawExprStr, expr);
2195     }
2196 
2197     @Override
2198     public Object eval(EvalContext ctx) {
2199       if(_val == null) {
2200         _val = super.eval(ctx);
2201       }
2202       return _val;
2203     }
2204   }
2205 }