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   @SuppressWarnings("deprecation")
1241   private static void literalStrToString(String str, StringBuilder sb) {
1242     sb.append("\"")
1243       .append(StringUtils.replace(str, "\"", "\"\""))
1244       .append("\"");
1245   }
1246 
1247   /**
1248    * Converts an ms access like pattern to a java regex, always matching case
1249    * insensitively.
1250    */
1251   public static Pattern likePatternToRegex(String pattern) {
1252 
1253     StringBuilder sb = new StringBuilder(pattern.length());
1254 
1255     // Access LIKE pattern supports (note, matching is case-insensitive):
1256     // - '*' -> 0 or more chars
1257     // - '?' -> single character
1258     // - '#' -> single digit
1259     // - '[...]' -> character class, '[!...]' -> not in char class
1260 
1261     for(int i = 0; i < pattern.length(); ++i) {
1262       char c = pattern.charAt(i);
1263 
1264       if(c == '*') {
1265         sb.append(".*");
1266       } else if(c == '?') {
1267         sb.append('.');
1268       } else if(c == '#') {
1269         sb.append("\\d");
1270       } else if(c == '[') {
1271 
1272         // find closing brace
1273         int startPos = i + 1;
1274         int endPos = -1;
1275         for(int j = startPos; j < pattern.length(); ++j) {
1276           if(pattern.charAt(j) == ']') {
1277             endPos = j;
1278             break;
1279           }
1280         }
1281 
1282         // access treats invalid expression like "unmatchable"
1283         if(endPos == -1) {
1284           return UNMATCHABLE_REGEX;
1285         }
1286 
1287         String charClass = pattern.substring(startPos, endPos);
1288 
1289         if((charClass.length() > 0) && (charClass.charAt(0) == '!')) {
1290           // this is a negated char class
1291           charClass = '^' + charClass.substring(1);
1292         }
1293 
1294         sb.append('[').append(charClass).append(']');
1295         i += (endPos - startPos) + 1;
1296 
1297       } else if(isRegexSpecialChar(c)) {
1298         // this char is special in regexes, so escape it
1299         sb.append('\\').append(c);
1300       } else {
1301         sb.append(c);
1302       }
1303     }
1304 
1305     try {
1306       return Pattern.compile(sb.toString(),
1307                              Pattern.CASE_INSENSITIVE | Pattern.DOTALL |
1308                              Pattern.UNICODE_CASE);
1309     } catch(PatternSyntaxException ignored) {
1310       return UNMATCHABLE_REGEX;
1311     }
1312   }
1313 
1314   public static boolean isRegexSpecialChar(char c) {
1315     return REGEX_SPEC_CHARS.contains(c);
1316   }
1317 
1318   private static Value toLiteralValue(Value.Type valType, Object value) {
1319     switch(valType) {
1320     case STRING:
1321       return ValueSupport.toValue((String)value);
1322     case DATE:
1323     case TIME:
1324     case DATE_TIME:
1325       return ValueSupport.toValue(valType, (LocalDateTime)value);
1326     case LONG:
1327       return ValueSupport.toValue((Integer)value);
1328     case DOUBLE:
1329       return ValueSupport.toValue((Double)value);
1330     case BIG_DEC:
1331       return ValueSupport.toValue((BigDecimal)value);
1332     default:
1333       throw new ParseException("unexpected literal type " + valType);
1334     }
1335   }
1336 
1337   private static boolean isLiteralDefaultValue(
1338       TokBuf buf, Value.Type resultType, String exprStr) {
1339 
1340     // if a default value expression does not start with an '=' and is used in
1341     // a string context, then it is taken as a literal value unless it starts
1342     // with a " char
1343 
1344     if(buf.getExprType() != Type.DEFAULT_VALUE) {
1345       return false;
1346     }
1347 
1348     // a leading "=" indicates "full" expression handling for a DEFAULT_VALUE
1349     // (consume this value once we detect it)
1350     if(isOp(buf.peekNext(), "=")) {
1351       buf.next();
1352       return false;
1353     }
1354 
1355     return((resultType == Value.Type.STRING) &&
1356            ((exprStr.length() == 0) ||
1357             (exprStr.charAt(0) != ExpressionTokenizer.QUOTED_STR_CHAR)));
1358   }
1359 
1360   private interface LeftAssocExpr {
1361     public OpType getOp();
1362     public Expr getLeft();
1363     public void setLeft(Expr left);
1364   }
1365 
1366   private interface RightAssocExpr {
1367     public OpType getOp();
1368     public Expr getRight();
1369     public void setRight(Expr right);
1370   }
1371 
1372   private static final class DelayedValue extends BaseDelayedValue
1373   {
1374     private final Expr _expr;
1375     private final EvalContext _ctx;
1376 
1377     private DelayedValue(Expr expr, EvalContext ctx) {
1378       _expr = expr;
1379       _ctx = ctx;
1380     }
1381 
1382     @Override
1383     public Value eval() {
1384       return _expr.eval(_ctx);
1385     }
1386   }
1387 
1388 
1389   private static abstract class Expr
1390   {
1391     public String toCleanString(LocaleContext ctx) {
1392       return toString(ctx, new StringBuilder(), false).toString();
1393     }
1394 
1395     public String toDebugString(LocaleContext ctx) {
1396       return toString(ctx, new StringBuilder(), true).toString();
1397     }
1398 
1399     protected boolean isValidationExpr() {
1400       return false;
1401     }
1402 
1403     protected StringBuilder toString(
1404         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1405       if(isDebug) {
1406         sb.append("<").append(getClass().getSimpleName()).append(">{");
1407       }
1408       toExprString(ctx, sb, isDebug);
1409       if(isDebug) {
1410         sb.append("}");
1411       }
1412       return sb;
1413     }
1414 
1415     protected Expr resolveOrderOfOperations() {
1416 
1417       if(!(this instanceof LeftAssocExpr)) {
1418         // nothing we can do
1419         return this;
1420       }
1421 
1422       // in order to get the precedence right, we need to first associate this
1423       // expression with the "rightmost" expression preceding it, then adjust
1424       // this expression "down" (lower precedence) as the precedence of the
1425       // operations dictates.  since we parse from left to right, the initial
1426       // "left" value isn't the immediate left expression, instead it's based
1427       // on how the preceding operator precedence worked out.  we need to
1428       // adjust "this" expression to the closest preceding expression before
1429       // we can correctly resolve precedence.
1430 
1431       Expr outerExpr = this;
1432       final LeftAssocExpr thisExpr = (LeftAssocExpr)this;
1433       final Expr thisLeft = thisExpr.getLeft();
1434 
1435       // current: <this>{<left>{A op1 B} op2 <right>{C}}
1436       if(thisLeft instanceof RightAssocExpr) {
1437 
1438         RightAssocExpr leftOp = (RightAssocExpr)thisLeft;
1439 
1440         // target: <left>{A op1 <this>{B op2 <right>{C}}}
1441 
1442         thisExpr.setLeft(leftOp.getRight());
1443 
1444         // give the new version of this expression an opportunity to further
1445         // swap (since the swapped expression may itself be a binary
1446         // expression)
1447         leftOp.setRight(resolveOrderOfOperations());
1448         outerExpr = thisLeft;
1449 
1450         // at this point, this expression has been pushed all the way to the
1451         // rightmost preceding expression (we artifically gave "this" the
1452         // highest precedence).  now, we want to adjust precedence as
1453         // necessary (shift it back down if the operator precedence is
1454         // incorrect).  note, we only need to check precedence against "this",
1455         // as all other precedence has been resolved in previous parsing
1456         // rounds.
1457         if((leftOp.getRight() == this) &&
1458            !isHigherPrecendence(thisExpr.getOp(), leftOp.getOp())) {
1459 
1460           // doh, "this" is lower (or the same) precedence, restore the
1461           // original order of things
1462           leftOp.setRight(thisExpr.getLeft());
1463           thisExpr.setLeft(thisLeft);
1464           outerExpr = this;
1465         }
1466       }
1467 
1468       return outerExpr;
1469     }
1470 
1471     public abstract boolean isConstant();
1472 
1473     public abstract Value eval(EvalContext ctx);
1474 
1475     public abstract void collectIdentifiers(Collection<Identifier> identifiers);
1476 
1477     protected abstract void toExprString(
1478         LocaleContext ctx, StringBuilder sb, boolean isDebug);
1479   }
1480 
1481   private static final class EConstValue extends Expr
1482   {
1483     private final Value _val;
1484     private final String _str;
1485 
1486     private EConstValue(Value val, String str) {
1487       _val = val;
1488       _str = str;
1489     }
1490 
1491     @Override
1492     public boolean isConstant() {
1493       return true;
1494     }
1495 
1496     @Override
1497     public Value eval(EvalContext ctx) {
1498       return _val;
1499     }
1500 
1501     @Override
1502     public void collectIdentifiers(Collection<Identifier> identifiers) {
1503       // none
1504     }
1505 
1506     @Override
1507     protected void toExprString(
1508         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1509       sb.append(_str);
1510     }
1511   }
1512 
1513   private static final class EThisValue extends Expr
1514   {
1515     @Override
1516     public boolean isConstant() {
1517       return false;
1518     }
1519     @Override
1520     public Value eval(EvalContext ctx) {
1521       return ctx.getThisColumnValue();
1522     }
1523     @Override
1524     public void collectIdentifiers(Collection<Identifier> identifiers) {
1525       // none
1526     }
1527     @Override
1528     protected void toExprString(
1529         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1530       if(isDebug) {
1531         sb.append("<THIS_COL>");
1532       }
1533     }
1534   }
1535 
1536   private static final class ELiteralValue extends Expr
1537   {
1538     private final Value _val;
1539 
1540     private ELiteralValue(Value.Type valType, Object value) {
1541       _val = toLiteralValue(valType, value);
1542     }
1543 
1544     @Override
1545     public boolean isConstant() {
1546       return true;
1547     }
1548 
1549     @Override
1550     public Value eval(EvalContext ctx) {
1551       return _val;
1552     }
1553 
1554     @Override
1555     public void collectIdentifiers(Collection<Identifier> identifiers) {
1556       // none
1557     }
1558 
1559     @Override
1560     protected void toExprString(
1561         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1562       if(_val.getType() == Value.Type.STRING) {
1563         literalStrToString((String)_val.get(), sb);
1564       } else if(_val.getType().isTemporal()) {
1565         sb.append("#").append(_val.getAsString(ctx)).append("#");
1566       } else {
1567         sb.append(_val.get());
1568       }
1569     }
1570   }
1571 
1572   private static final class EObjValue extends Expr
1573   {
1574     private final Identifier _identifier;
1575 
1576     private EObjValue(Identifier identifier) {
1577       _identifier = identifier;
1578     }
1579 
1580     @Override
1581     public boolean isConstant() {
1582       return false;
1583     }
1584 
1585     @Override
1586     public Value eval(EvalContext ctx) {
1587       return ctx.getIdentifierValue(_identifier);
1588     }
1589 
1590     @Override
1591     public void collectIdentifiers(Collection<Identifier> identifiers) {
1592       identifiers.add(_identifier);
1593     }
1594 
1595     @Override
1596     protected void toExprString(
1597         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1598       sb.append(_identifier);
1599     }
1600   }
1601 
1602   private static class EParen extends Expr
1603   {
1604     private final Expr _expr;
1605 
1606     private EParen(Expr expr) {
1607       _expr = expr;
1608     }
1609 
1610     @Override
1611     public boolean isConstant() {
1612       return _expr.isConstant();
1613     }
1614 
1615     @Override
1616     protected boolean isValidationExpr() {
1617       return _expr.isValidationExpr();
1618     }
1619 
1620     @Override
1621     public Value eval(EvalContext ctx) {
1622       return _expr.eval(ctx);
1623     }
1624 
1625     @Override
1626     public void collectIdentifiers(Collection<Identifier> identifiers) {
1627       _expr.collectIdentifiers(identifiers);
1628     }
1629 
1630     @Override
1631     protected void toExprString(
1632         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1633       sb.append("(");
1634       _expr.toString(ctx, sb, isDebug);
1635       sb.append(")");
1636     }
1637   }
1638 
1639   private static class EFunc extends Expr
1640   {
1641     private final Function _func;
1642     private final List<Expr> _params;
1643 
1644     private EFunc(Function func, List<Expr> params) {
1645       _func = func;
1646       _params = params;
1647     }
1648 
1649     @Override
1650     public boolean isConstant() {
1651       return _func.isPure() && areConstant(_params);
1652     }
1653 
1654     @Override
1655     public Value eval(EvalContext ctx) {
1656       return _func.eval(ctx, exprListToValues(_params, ctx));
1657     }
1658 
1659     @Override
1660     public void collectIdentifiers(Collection<Identifier> identifiers) {
1661       for(Expr param : _params) {
1662         param.collectIdentifiers(identifiers);
1663       }
1664     }
1665 
1666     @Override
1667     protected void toExprString(
1668         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1669       sb.append(_func.getName()).append("(");
1670 
1671       if(!_params.isEmpty()) {
1672         exprListToString(_params, ",", ctx, sb, isDebug);
1673       }
1674 
1675       sb.append(")");
1676     }
1677   }
1678 
1679   private static abstract class EBaseBinaryOp extends Expr
1680     implements LeftAssocExpr, RightAssocExpr
1681   {
1682     protected final OpType _op;
1683     protected Expr _left;
1684     protected Expr _right;
1685 
1686     private EBaseBinaryOp(OpType op, Expr left, Expr right) {
1687       _op = op;
1688       _left = left;
1689       _right = right;
1690     }
1691 
1692     @Override
1693     public boolean isConstant() {
1694       return areConstant(_left, _right);
1695     }
1696 
1697     @Override
1698     public OpType getOp() {
1699       return _op;
1700     }
1701 
1702     @Override
1703     public Expr getLeft() {
1704       return _left;
1705     }
1706 
1707     @Override
1708     public void setLeft(Expr left) {
1709       _left = left;
1710     }
1711 
1712     @Override
1713     public Expr getRight() {
1714       return _right;
1715     }
1716 
1717     @Override
1718     public void setRight(Expr right) {
1719       _right = right;
1720     }
1721 
1722     @Override
1723     public void collectIdentifiers(Collection<Identifier> identifiers) {
1724       _left.collectIdentifiers(identifiers);
1725       _right.collectIdentifiers(identifiers);
1726     }
1727 
1728     @Override
1729     protected void toExprString(
1730         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1731       appendLeadingExpr(_left, ctx, sb, isDebug)
1732         .append(_op).append(" ");
1733       _right.toString(ctx, sb, isDebug);
1734     }
1735   }
1736 
1737   private static class EBinaryOp extends EBaseBinaryOp
1738   {
1739     private EBinaryOp(BinaryOp op, Expr left, Expr right) {
1740       super(op, left, right);
1741     }
1742 
1743     @Override
1744     public Value eval(EvalContext ctx) {
1745       return ((BinaryOp)_op).eval(ctx, _left.eval(ctx), _right.eval(ctx));
1746     }
1747   }
1748 
1749   private static class EUnaryOp extends Expr
1750     implements RightAssocExpr
1751   {
1752     private final OpType _op;
1753     private Expr _expr;
1754 
1755     private EUnaryOp(UnaryOp op, Expr expr) {
1756       _op = op;
1757       _expr = expr;
1758     }
1759 
1760     @Override
1761     public boolean isConstant() {
1762       return _expr.isConstant();
1763     }
1764 
1765     @Override
1766     public OpType getOp() {
1767       return _op;
1768     }
1769 
1770     @Override
1771     public Expr getRight() {
1772       return _expr;
1773     }
1774 
1775     @Override
1776     public void setRight(Expr right) {
1777       _expr = right;
1778     }
1779 
1780     @Override
1781     public Value eval(EvalContext ctx) {
1782       return ((UnaryOp)_op).eval(ctx, _expr.eval(ctx));
1783     }
1784 
1785     @Override
1786     public void collectIdentifiers(Collection<Identifier> identifiers) {
1787       _expr.collectIdentifiers(identifiers);
1788     }
1789 
1790     @Override
1791     protected void toExprString(
1792         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1793       sb.append(_op);
1794       if(isDebug || ((UnaryOp)_op).needsSpace()) {
1795         sb.append(" ");
1796       }
1797       _expr.toString(ctx, sb, isDebug);
1798     }
1799   }
1800 
1801   private static class ECompOp extends EBaseBinaryOp
1802   {
1803     private ECompOp(CompOp op, Expr left, Expr right) {
1804       super(op, left, right);
1805     }
1806 
1807     @Override
1808     protected boolean isValidationExpr() {
1809       return true;
1810     }
1811 
1812     @Override
1813     public Value eval(EvalContext ctx) {
1814       return ((CompOp)_op).eval(ctx, _left.eval(ctx), _right.eval(ctx));
1815     }
1816   }
1817 
1818   private static class EImplicitCompOp extends ECompOp
1819   {
1820     private EImplicitCompOp(Expr right) {
1821       super(CompOp.EQ, THIS_COL_VALUE, right);
1822     }
1823 
1824     @Override
1825     protected void toExprString(
1826         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1827       // only output the full "implicit" comparison in debug mode
1828       if(isDebug) {
1829         super.toExprString(ctx, sb, isDebug);
1830       } else {
1831         // just output the explicit part of the expression
1832         _right.toString(ctx, sb, isDebug);
1833       }
1834     }
1835   }
1836 
1837   private static class ELogicalOp extends EBaseBinaryOp
1838   {
1839     private ELogicalOp(LogOp op, Expr left, Expr right) {
1840       super(op, left, right);
1841     }
1842 
1843     @Override
1844     protected boolean isValidationExpr() {
1845       return true;
1846     }
1847 
1848     @Override
1849     public Value eval(final EvalContext ctx) {
1850 
1851       // logical operations do short circuit evaluation, so we need to delay
1852       // computing results until necessary
1853       return ((LogOp)_op).eval(ctx, new DelayedValue(_left, ctx),
1854                                new DelayedValue(_right, ctx));
1855     }
1856   }
1857 
1858   private static abstract class ESpecOp extends Expr
1859     implements LeftAssocExpr
1860   {
1861     protected final SpecOp _op;
1862     protected Expr _expr;
1863 
1864     private ESpecOp(SpecOp op, Expr expr) {
1865       _op = op;
1866       _expr = expr;
1867     }
1868 
1869     @Override
1870     public boolean isConstant() {
1871       return _expr.isConstant();
1872     }
1873 
1874     @Override
1875     public OpType getOp() {
1876       return _op;
1877     }
1878 
1879     @Override
1880     public Expr getLeft() {
1881       return _expr;
1882     }
1883 
1884     @Override
1885     public void setLeft(Expr left) {
1886       _expr = left;
1887     }
1888 
1889     @Override
1890     public void collectIdentifiers(Collection<Identifier> identifiers) {
1891       _expr.collectIdentifiers(identifiers);
1892     }
1893 
1894     @Override
1895     protected boolean isValidationExpr() {
1896       return true;
1897     }
1898   }
1899 
1900   private static class ENullOp extends ESpecOp
1901   {
1902     private ENullOp(SpecOp op, Expr expr) {
1903       super(op, expr);
1904     }
1905 
1906     @Override
1907     public Value eval(EvalContext ctx) {
1908       return _op.eval(ctx, _expr.eval(ctx), null, null);
1909     }
1910 
1911     @Override
1912     protected void toExprString(
1913         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1914       appendLeadingExpr(_expr, ctx, sb, isDebug)
1915         .append(_op);
1916     }
1917   }
1918 
1919   private static class ELikeOp extends ESpecOp
1920   {
1921     private final String _patternStr;
1922     private Pattern _pattern;
1923 
1924     private ELikeOp(SpecOp op, Expr expr, String patternStr) {
1925       super(op, expr);
1926       _patternStr = patternStr;
1927     }
1928 
1929     private Pattern getPattern()
1930     {
1931       if(_pattern == null) {
1932         _pattern = likePatternToRegex(_patternStr);
1933       }
1934       return _pattern;
1935     }
1936 
1937     @Override
1938     public Value eval(EvalContext ctx) {
1939       return _op.eval(ctx, _expr.eval(ctx), getPattern(), null);
1940     }
1941 
1942     @Override
1943     protected void toExprString(
1944         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1945       appendLeadingExpr(_expr, ctx, sb, isDebug)
1946         .append(_op).append(" ");
1947       literalStrToString(_patternStr, sb);
1948       if(isDebug) {
1949         sb.append("(").append(getPattern()).append(")");
1950       }
1951     }
1952   }
1953 
1954   private static class EInOp extends ESpecOp
1955   {
1956     private final List<Expr> _exprs;
1957 
1958     private EInOp(SpecOp op, Expr expr, List<Expr> exprs) {
1959       super(op, expr);
1960       _exprs = exprs;
1961     }
1962 
1963     @Override
1964     public boolean isConstant() {
1965       return super.isConstant() && areConstant(_exprs);
1966     }
1967 
1968     @Override
1969     public Value eval(EvalContext ctx) {
1970       return _op.eval(ctx, _expr.eval(ctx),
1971                       exprListToDelayedValues(_exprs, ctx), null);
1972     }
1973 
1974     @Override
1975     public void collectIdentifiers(Collection<Identifier> identifiers) {
1976       for(Expr expr : _exprs) {
1977         expr.collectIdentifiers(identifiers);
1978       }
1979     }
1980 
1981     @Override
1982     protected void toExprString(
1983         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1984       appendLeadingExpr(_expr, ctx, sb, isDebug)
1985         .append(_op).append(" (");
1986       exprListToString(_exprs, ",", ctx, sb, isDebug);
1987       sb.append(")");
1988     }
1989   }
1990 
1991   private static class EBetweenOp extends ESpecOp
1992     implements RightAssocExpr
1993   {
1994     private final Expr _startRangeExpr;
1995     private Expr _endRangeExpr;
1996 
1997     private EBetweenOp(SpecOp op, Expr expr, Expr startRangeExpr,
1998                        Expr endRangeExpr) {
1999       super(op, expr);
2000       _startRangeExpr = startRangeExpr;
2001       _endRangeExpr = endRangeExpr;
2002     }
2003 
2004     @Override
2005     public boolean isConstant() {
2006       return _expr.isConstant() && areConstant(_startRangeExpr, _endRangeExpr);
2007     }
2008 
2009     @Override
2010     public Expr getRight() {
2011       return _endRangeExpr;
2012     }
2013 
2014     @Override
2015     public void setRight(Expr right) {
2016       _endRangeExpr = right;
2017     }
2018 
2019     @Override
2020     public Value eval(EvalContext ctx) {
2021       return _op.eval(ctx, _expr.eval(ctx),
2022                       new DelayedValue(_startRangeExpr, ctx),
2023                       new DelayedValue(_endRangeExpr, ctx));
2024     }
2025 
2026     @Override
2027     public void collectIdentifiers(Collection<Identifier> identifiers) {
2028       super.collectIdentifiers(identifiers);
2029       _startRangeExpr.collectIdentifiers(identifiers);
2030       _endRangeExpr.collectIdentifiers(identifiers);
2031     }
2032 
2033     @Override
2034     protected void toExprString(
2035         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
2036       appendLeadingExpr(_expr, ctx, sb, isDebug)
2037         .append(_op).append(" ");
2038       _startRangeExpr.toString(ctx, sb, isDebug);
2039       sb.append(" And ");
2040       _endRangeExpr.toString(ctx, sb, isDebug);
2041     }
2042   }
2043 
2044   /**
2045    * Base Expression wrapper for an Expr.
2046    */
2047   private static abstract class BaseExprWrapper implements Expression
2048   {
2049     private final String _rawExprStr;
2050     private final Expr _expr;
2051 
2052     private BaseExprWrapper(String rawExprStr, Expr expr) {
2053       _rawExprStr = rawExprStr;
2054       _expr = expr;
2055     }
2056 
2057     @Override
2058     public String toDebugString(LocaleContext ctx) {
2059       return _expr.toDebugString(ctx);
2060     }
2061 
2062     @Override
2063     public String toRawString() {
2064       return _rawExprStr;
2065     }
2066 
2067     @Override
2068     public String toCleanString(LocaleContext ctx) {
2069       return _expr.toCleanString(ctx);
2070     }
2071 
2072     @Override
2073     public boolean isConstant() {
2074       return _expr.isConstant();
2075     }
2076 
2077     @Override
2078     public void collectIdentifiers(Collection<Identifier> identifiers) {
2079       _expr.collectIdentifiers(identifiers);
2080     }
2081 
2082     @Override
2083     public String toString() {
2084       return toRawString();
2085     }
2086 
2087     protected Object evalValue(Value.Type resultType, EvalContext ctx) {
2088       Value val = _expr.eval(ctx);
2089 
2090       if(val.isNull()) {
2091         return null;
2092       }
2093 
2094       if(resultType == null) {
2095         // return as "native" type
2096         return val.get();
2097       }
2098 
2099       // FIXME possibly do some type coercion.  are there conversions here which don't work elsewhere? (string -> date, string -> number)?
2100       switch(resultType) {
2101       case STRING:
2102         return val.getAsString(ctx);
2103       case DATE:
2104       case TIME:
2105       case DATE_TIME:
2106         return val.getAsLocalDateTime(ctx);
2107       case LONG:
2108         return val.getAsLongInt(ctx);
2109       case DOUBLE:
2110         return val.getAsDouble(ctx);
2111       case BIG_DEC:
2112         return val.getAsBigDecimal(ctx);
2113       default:
2114         throw new IllegalStateException("unexpected result type " + resultType);
2115       }
2116     }
2117 
2118     protected Boolean evalCondition(EvalContext ctx) {
2119       Value val = _expr.eval(ctx);
2120 
2121       if(val.isNull()) {
2122         // null can't be coerced to a boolean
2123         throw new EvalException("Condition evaluated to Null");
2124       }
2125 
2126       return val.getAsBoolean(ctx);
2127     }
2128   }
2129 
2130   /**
2131    * Expression wrapper for an Expr which returns a value.
2132    */
2133   private static class ExprWrapper extends BaseExprWrapper
2134   {
2135     private final Value.Type _resultType;
2136 
2137     private ExprWrapper(String rawExprStr, Expr expr, Value.Type resultType) {
2138       super(rawExprStr, expr);
2139       _resultType = resultType;
2140     }
2141 
2142     @Override
2143     public Object eval(EvalContext ctx) {
2144       return evalValue(_resultType, ctx);
2145     }
2146   }
2147 
2148   /**
2149    * Expression wrapper for an Expr which returns a Boolean from a conditional
2150    * expression.
2151    */
2152   private static class CondExprWrapper extends BaseExprWrapper
2153   {
2154     private CondExprWrapper(String rawExprStr, Expr expr) {
2155       super(rawExprStr, expr);
2156     }
2157 
2158     @Override
2159     public Object eval(EvalContext ctx) {
2160       return evalCondition(ctx);
2161     }
2162   }
2163 
2164   /**
2165    * Expression wrapper for a <i>pure</i> Expr which caches the result of
2166    * evaluation.
2167    */
2168   private static final class MemoizedExprWrapper extends ExprWrapper
2169   {
2170     private Object _val;
2171 
2172     private MemoizedExprWrapper(String rawExprStr, Expr expr,
2173                                 Value.Type resultType) {
2174       super(rawExprStr, expr, resultType);
2175     }
2176 
2177     @Override
2178     public Object eval(EvalContext ctx) {
2179       if(_val == null) {
2180         _val = super.eval(ctx);
2181       }
2182       return _val;
2183     }
2184   }
2185 
2186   /**
2187    * Expression wrapper for a <i>pure</i> conditional Expr which caches the
2188    * result of evaluation.
2189    */
2190   private static final class MemoizedCondExprWrapper extends CondExprWrapper
2191   {
2192     private Object _val;
2193 
2194     private MemoizedCondExprWrapper(String rawExprStr, Expr expr) {
2195       super(rawExprStr, expr);
2196     }
2197 
2198     @Override
2199     public Object eval(EvalContext ctx) {
2200       if(_val == null) {
2201         _val = super.eval(ctx);
2202       }
2203       return _val;
2204     }
2205   }
2206 }