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.StringUtil;
46  import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token;
47  import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType;
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<>();
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<>(
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     return parse(exprType, exprStr, resultType, context, true);
414   }
415 
416   /**
417    * @param allowLiteralDefault whether a default value which is not an
418    *        expression may be taken as a literal.  Only a column which holds
419    *        free text can do that
420    */
421   public static Expression parse(Type exprType, String exprStr,
422                                  Value.Type resultType,
423                                  ParseContext context,
424                                  boolean allowLiteralDefault) {
425 
426     List<Token> tokens = trimSpaces(
427         ExpressionTokenizer.tokenize(exprType, exprStr, context));
428 
429     if(tokens == null) {
430       throw new ParseException("null/empty expression");
431     }
432 
433     TokBuf buf = new TokBuf(exprType, tokens, context);
434 
435     if(isLiteralDefaultValue(buf, resultType, exprStr, allowLiteralDefault)) {
436 
437       // this is handled as a literal string value, not an expression.  no
438       // need to memo-ize cause it's a simple literal value
439       return new ExprWrapper(exprStr,
440           new ELiteralValue(Value.Type.STRING, exprStr), resultType);
441     }
442 
443     // normal expression handling
444     Expr expr = parseExpression(buf, false);
445 
446     if((exprType == Type.FIELD_VALIDATOR) && !expr.isValidationExpr()) {
447       // a non-validation expression for a FIELD_VALIDATOR treats the result
448       // as an equality comparison with the field in question.  so, transform
449       // the expression accordingly
450       expr = new EImplicitCompOp(expr);
451     }
452 
453     switch(exprType) {
454     case DEFAULT_VALUE:
455     case EXPRESSION:
456       return (expr.isConstant() ?
457               // for now, just cache at top-level for speed (could in theory
458               // cache intermediate values?)
459               new MemoizedExprWrapper(exprStr, expr, resultType) :
460               new ExprWrapper(exprStr, expr, resultType));
461     case FIELD_VALIDATOR:
462     case RECORD_VALIDATOR:
463       return (expr.isConstant() ?
464               // for now, just cache at top-level for speed (could in theory
465               // cache intermediate values?)
466               new MemoizedCondExprWrapper(exprStr, expr) :
467               new CondExprWrapper(exprStr, expr));
468     default:
469       throw new ParseException("unexpected expression type " + exprType);
470     }
471   }
472 
473   private static List<Token> trimSpaces(List<Token> tokens) {
474     if(tokens == null) {
475       return null;
476     }
477 
478     // for the most part, spaces are superfluous except for one situation(?).
479     // when they appear between a string literal and '(' they help distinguish
480     // a function call from another expression form
481     for(int i = 1; i < (tokens.size() - 1); ++i) {
482       Token t = tokens.get(i);
483       if(t.getType() == TokenType.SPACE) {
484         if((tokens.get(i - 1).getType() == TokenType.STRING) &&
485            isDelim(tokens.get(i + 1), FUNC_START_DELIM)) {
486           // we want to keep this space
487         } else {
488           tokens.remove(i);
489           --i;
490         }
491       }
492     }
493     return tokens;
494   }
495 
496   private static Expr parseExpression(TokBuf buf, boolean singleExpr)
497   {
498     while(buf.hasNext()) {
499       Token t = buf.next();
500 
501       switch(t.getType()) {
502       case OBJ_NAME:
503 
504         parseObjectRefExpression(t, buf);
505         break;
506 
507       case LITERAL:
508 
509         buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue()));
510         break;
511 
512       case OP:
513 
514         WordType wordType = getWordType(t);
515         if(wordType == null) {
516           // shouldn't happen
517           throw new ParseException("Invalid operator " + t);
518         }
519 
520         // this can only be an OP or a COMP (those are the only words that the
521         // tokenizer would define as TokenType.OP)
522         switch(wordType) {
523         case OP:
524           parseOperatorExpression(t, buf);
525           break;
526 
527         case COMP:
528 
529           parseCompOpExpression(t, buf);
530           break;
531 
532         default:
533           throw new ParseException("Unexpected OP word type " + wordType);
534         }
535 
536         break;
537 
538       case DELIM:
539 
540         parseDelimExpression(t, buf);
541         break;
542 
543       case STRING:
544 
545         // see if it's a special word?
546         wordType = getWordType(t);
547         if(wordType == null) {
548 
549           // is it a function call?
550           if(!maybeParseFuncCallExpression(t, buf)) {
551 
552             // is it an object name?
553             Token next = buf.peekNext();
554             if((next != null) && isObjNameSep(next)) {
555 
556               parseObjectRefExpression(t, buf);
557 
558             } else {
559 
560               // FIXME maybe bare obj name, maybe string literal?
561               throw new UnsupportedOperationException("FIXME");
562             }
563           }
564 
565         } else {
566 
567           // this could be anything but COMP or DELIM (all COMPs would be
568           // returned as TokenType.OP and all DELIMs would be TokenType.DELIM)
569           switch(wordType) {
570           case OP:
571 
572             parseOperatorExpression(t, buf);
573             break;
574 
575           case LOG_OP:
576 
577             parseLogicalOpExpression(t, buf);
578             break;
579 
580           case CONST:
581 
582             parseConstExpression(t, buf);
583             break;
584 
585           case SPEC_OP_PREFIX:
586 
587             parseSpecOpExpression(t, buf);
588             break;
589 
590           default:
591             throw new ParseException("Unexpected STRING word type "
592                                        + wordType);
593           }
594         }
595 
596         break;
597 
598       case SPACE:
599         // top-level space is irrelevant (and we strip them anyway)
600         break;
601 
602       default:
603         throw new ParseException("unknown token type " + t);
604       }
605 
606       if(singleExpr && buf.hasPendingExpr()) {
607         break;
608       }
609     }
610 
611     Expr expr = buf.takePendingExpr();
612     if(expr == null) {
613       throw new ParseException("No expression found? " + buf);
614     }
615 
616     return expr;
617   }
618 
619   private static void parseObjectRefExpression(Token firstTok, TokBuf buf) {
620 
621     // object references may be joined by '.' or '!'. access syntac docs claim
622     // object identifiers can be formatted like:
623     //     "[Collection name]![Object name].[Property name]"
624     // However, in practice, they only ever seem to be (at most) two levels
625     // and only use '.'.  Apparently '!' is actually a special late-bind
626     // operator (not sure it makes a difference for this code?), see:
627     // http://bytecomb.com/the-bang-exclamation-operator-in-vba/
628     Deque<String> objNames = new LinkedList<>();
629     objNames.add(firstTok.getValueStr());
630 
631     Token t = null;
632     boolean atSep = false;
633     while((t = buf.peekNext()) != null) {
634       if(!atSep) {
635         if(isObjNameSep(t)) {
636           buf.next();
637           atSep = true;
638           continue;
639         }
640       } else {
641         if((t.getType() == TokenType.OBJ_NAME) ||
642            (t.getType() == TokenType.STRING)) {
643           buf.next();
644           // always insert at beginning of list so names are in reverse order
645           objNames.addFirst(t.getValueStr());
646           atSep = false;
647           continue;
648         }
649       }
650       break;
651     }
652 
653     int numNames = objNames.size();
654     if(atSep || (numNames > 3)) {
655       throw new ParseException("Invalid object reference " + buf);
656     }
657 
658     // names are in reverse order
659     String propName = null;
660     if(numNames == 3) {
661       propName = objNames.poll();
662     }
663     String objName = objNames.poll();
664     String collectionName = objNames.poll();
665 
666     buf.setPendingExpr(
667         new EObjValue(new Identifier(collectionName, objName, propName)));
668   }
669 
670   private static void parseDelimExpression(Token firstTok, TokBuf buf) {
671     // the only "top-level" delim we expect to find is open paren, and
672     // there shouldn't be any pending expression
673     if(!isDelim(firstTok, OPEN_PAREN) || buf.hasPendingExpr()) {
674       throw new ParseException("Unexpected delimiter " +
675                                          firstTok.getValue() + " " + buf);
676     }
677 
678     Expr subExpr = findParenExprs(buf, false).get(0);
679     buf.setPendingExpr(new EParen(subExpr));
680   }
681 
682   private static boolean maybeParseFuncCallExpression(
683       Token firstTok, TokBuf buf) {
684 
685     int startPos = buf.curPos();
686     boolean foundFunc = false;
687 
688     try {
689       Token t = buf.peekNext();
690       if((t != null) && (t.getType() == TokenType.SPACE)) {
691         // ms access takes a space between the name and the bracket, as in
692         // "Now ()", and writes one itself.  the tokenizer keeps that space
693         // because it is the only thing which tells a function call from an
694         // object name, so step over it here
695         buf.next();
696         t = buf.peekNext();
697       }
698       if(!isDelim(t, FUNC_START_DELIM)) {
699         // not a function call
700         return false;
701       }
702 
703       buf.next();
704       List<Expr> params = findParenExprs(buf, true);
705       String funcName = firstTok.getValueStr();
706       Function func = buf.getFunction(funcName);
707       if(func == null) {
708         throw new ParseException("Could not find function '" +
709                                            funcName + "' " + buf);
710       }
711       buf.setPendingExpr(new EFunc(func, params));
712       foundFunc = true;
713       return true;
714 
715     } finally {
716       if(!foundFunc) {
717         buf.reset(startPos);
718       }
719     }
720   }
721 
722   private static List<Expr> findParenExprs(
723       TokBuf buf, boolean allowMulti) {
724 
725     if(allowMulti) {
726       // simple case, no nested expr
727       Token t = buf.peekNext();
728       if(isDelim(t, CLOSE_PAREN)) {
729         buf.next();
730         return Collections.emptyList();
731       }
732     }
733 
734     // find closing ")", handle nested parens
735     List<Expr> exprs = new ArrayList<>(3);
736     int level = 1;
737     int startPos = buf.curPos();
738     while(buf.hasNext()) {
739 
740       Token t = buf.next();
741 
742       if(isDelim(t, OPEN_PAREN)) {
743 
744         ++level;
745 
746       } else if(isDelim(t, CLOSE_PAREN)) {
747 
748         --level;
749         if(level == 0) {
750           TokBuf subBuf = buf.subBuf(startPos, buf.prevPos());
751           exprs.add(parseExpression(subBuf, false));
752           return exprs;
753         }
754 
755       } else if(allowMulti && (level == 1) && isDelim(t, FUNC_PARAM_SEP)) {
756 
757         TokBuf subBuf = buf.subBuf(startPos, buf.prevPos());
758         exprs.add(parseExpression(subBuf, false));
759         startPos = buf.curPos();
760       }
761     }
762 
763     throw new ParseException("Missing closing '" + CLOSE_PAREN
764                                        + " " + buf);
765   }
766 
767   private static void parseOperatorExpression(Token t, TokBuf buf) {
768 
769     // most ops are two argument except that '-' could be negation, "+" could
770     // be pos-ation
771     if(buf.hasPendingExpr()) {
772       parseBinaryOpExpression(t, buf);
773     } else if(isEitherOp(t, "-", "+")) {
774       parseUnaryOpExpression(t, buf);
775     } else {
776       throw new ParseException(
777           "Missing left expression for binary operator " + t.getValue() +
778           " " + buf);
779     }
780   }
781 
782   private static void parseBinaryOpExpression(Token firstTok, TokBuf buf) {
783     BinaryOp op = getOpType(firstTok, BinaryOp.class);
784     Expr leftExpr = buf.takePendingExpr();
785     Expr rightExpr = parseExpression(buf, true);
786 
787     buf.setPendingExpr(new EBinaryOp(op, leftExpr, rightExpr));
788   }
789 
790   private static void parseUnaryOpExpression(Token firstTok, TokBuf buf) {
791     UnaryOp op = getOpType(firstTok, UnaryOp.class);
792 
793     UnaryOp numOp = op.getUnaryNumOp();
794     if(numOp != null) {
795       // if this operator is immediately preceding a number, it has a higher
796       // precedence
797       Token nextTok = buf.peekNext();
798       if((nextTok != null) && (nextTok.getType() == TokenType.LITERAL) &&
799          nextTok.getValueType().isNumeric()) {
800         op = numOp;
801       }
802     }
803 
804     Expr val = parseExpression(buf, true);
805 
806     buf.setPendingExpr(new EUnaryOp(op, val));
807   }
808 
809   private static void parseCompOpExpression(Token firstTok, TokBuf buf) {
810 
811     if(!buf.hasPendingExpr()) {
812       if(buf.getExprType() == Type.FIELD_VALIDATOR) {
813         // comparison operators for field validators can implicitly use
814         // the current field value for the left value
815         buf.setPendingExpr(THIS_COL_VALUE);
816       } else {
817         throw new ParseException(
818             "Missing left expression for comparison operator " +
819             firstTok.getValue() + " " + buf);
820       }
821     }
822 
823     CompOp op = getOpType(firstTok, CompOp.class);
824     Expr leftExpr = buf.takePendingExpr();
825     Expr rightExpr = parseExpression(buf, true);
826 
827     buf.setPendingExpr(new ECompOp(op, leftExpr, rightExpr));
828   }
829 
830   private static void parseLogicalOpExpression(Token firstTok, TokBuf buf) {
831 
832     if(!buf.hasPendingExpr()) {
833       throw new ParseException(
834           "Missing left expression for logical operator " +
835           firstTok.getValue() + " " + buf);
836     }
837 
838     LogOp op = getOpType(firstTok, LogOp.class);
839     Expr leftExpr = buf.takePendingExpr();
840     Expr rightExpr = parseExpression(buf, true);
841 
842     buf.setPendingExpr(new ELogicalOp(op, leftExpr, rightExpr));
843   }
844 
845   private static void parseSpecOpExpression(Token firstTok, TokBuf buf) {
846 
847     SpecOp specOp = getSpecialOperator(firstTok, buf);
848 
849     if(specOp == SpecOp.NOT) {
850       // this is the unary prefix operator
851       parseUnaryOpExpression(firstTok, buf);
852       return;
853     }
854 
855     if(!buf.hasPendingExpr()) {
856       if(buf.getExprType() == Type.FIELD_VALIDATOR) {
857         // comparison operators for field validators can implicitly use
858         // the current field value for the left value
859         buf.setPendingExpr(THIS_COL_VALUE);
860       } else {
861         throw new ParseException(
862             "Missing left expression for comparison operator " +
863             specOp + " " + buf);
864       }
865     }
866 
867     Expr expr = buf.takePendingExpr();
868 
869     Expr specOpExpr = null;
870     switch(specOp) {
871     case IS_NULL:
872     case IS_NOT_NULL:
873       specOpExpr = new ENullOp(specOp, expr);
874       break;
875 
876     case LIKE:
877     case NOT_LIKE:
878       Token t = buf.peekNext();
879       boolean literalPattern = false;
880       if((t != null) && (t.getType() == TokenType.LITERAL) &&
881          (t.getValueType() == Value.Type.STRING)) {
882         buf.next();
883         // an arithmetic operator binds tighter than the Like, so the pattern
884         // of Like "a" & "b" is the whole of "a" & "b" rather than "a" alone
885         literalPattern = !isArithmeticOp(buf.peekNext());
886         if(!literalPattern) {
887           buf.reset(buf.prevPos());
888         }
889       }
890       if(literalPattern) {
891         // the common case, a literal pattern, which compiles once
892         specOpExpr = new ELikeOp(specOp, expr, t.getValueStr());
893       } else {
894         // ms access also takes an expression for the pattern, such as another
895         // column, which is only known when the expression is evaluated
896         specOpExpr = new EDynamicLikeOp(specOp, expr,
897                                         parseExpression(buf, true));
898       }
899       break;
900 
901     case BETWEEN:
902     case NOT_BETWEEN:
903 
904       // the "rest" of a between expression is of the form "X And Y".  we are
905       // going to speculatively parse forward until we find the "And"
906       // operator.
907       Expr startRangeExpr = null;
908       while(true) {
909 
910         Expr tmpExpr = parseExpression(buf, true);
911         Token tmpT = buf.peekNext();
912 
913         if(tmpT == null) {
914           // ran out of expression?
915           throw new ParseException(
916               "Missing 'And' for 'Between' expression " + buf);
917         }
918 
919         if(isString(tmpT, "and")) {
920           buf.next();
921           startRangeExpr = tmpExpr;
922           break;
923         }
924 
925         // put the pending expression back and try parsing some more
926         buf.restorePendingExpr(tmpExpr);
927       }
928 
929       Expr endRangeExpr = parseExpression(buf, true);
930 
931       specOpExpr = new EBetweenOp(specOp, expr, startRangeExpr, endRangeExpr);
932       break;
933 
934     case IN:
935     case NOT_IN:
936 
937       // there might be a space before open paren
938       t = buf.next();
939       if(t.getType() == TokenType.SPACE) {
940         t = buf.next();
941       }
942       if(!isDelim(t, OPEN_PAREN)) {
943         throw new ParseException("Malformed 'In' expression " + buf);
944       }
945 
946       List<Expr> exprs = findParenExprs(buf, true);
947       specOpExpr = new EInOp(specOp, expr, exprs);
948       break;
949 
950     default:
951       throw new ParseException("Unexpected special op " + specOp);
952     }
953 
954     buf.setPendingExpr(specOpExpr);
955   }
956 
957   private static SpecOp getSpecialOperator(Token firstTok, TokBuf buf) {
958     String opStr = StringUtil.toLowerCase(firstTok.getValueStr());
959 
960     if("is".equals(opStr)) {
961       Token t = buf.peekNext();
962       if(isString(t, "null")) {
963         buf.next();
964         return SpecOp.IS_NULL;
965       } else if(isString(t, "not")) {
966         buf.next();
967         t = buf.peekNext();
968         if(isString(t, "null")) {
969           buf.next();
970           return SpecOp.IS_NOT_NULL;
971         }
972       }
973     } else if("like".equals(opStr)) {
974       return SpecOp.LIKE;
975     } else if("between".equals(opStr)) {
976       return SpecOp.BETWEEN;
977     } else if("in".equals(opStr)) {
978       return SpecOp.IN;
979     } else if("not".equals(opStr)) {
980       Token t = buf.peekNext();
981       if(isString(t, "between")) {
982         buf.next();
983         return SpecOp.NOT_BETWEEN;
984       } else if(isString(t, "in")) {
985         buf.next();
986         return SpecOp.NOT_IN;
987       } else if(isString(t, "like")) {
988         buf.next();
989         return SpecOp.NOT_LIKE;
990       }
991       return SpecOp.NOT;
992     }
993 
994     throw new ParseException(
995         "Malformed special operator " + opStr + " " + buf);
996   }
997 
998   private static void parseConstExpression(Token firstTok, TokBuf buf) {
999     Expr constExpr = null;
1000     String tokStr = StringUtil.toLowerCase(firstTok.getValueStr());
1001     if(TRUE_STRS.contains(tokStr)) {
1002       constExpr = TRUE_VALUE;
1003     } else if(FALSE_STRS.contains(tokStr)) {
1004       constExpr = FALSE_VALUE;
1005     } else if("null".equals(tokStr)) {
1006       constExpr = NULL_VALUE;
1007     } else {
1008       throw new ParseException("Unexpected CONST word "
1009                                  + firstTok.getValue());
1010     }
1011     buf.setPendingExpr(constExpr);
1012   }
1013 
1014   private static boolean isObjNameSep(Token t) {
1015     return (isDelim(t, ".") || isDelim(t, "!"));
1016   }
1017 
1018   private static boolean isOp(Token t, String opStr) {
1019     return ((t != null) && (t.getType() == TokenType.OP) &&
1020             opStr.equalsIgnoreCase(t.getValueStr()));
1021   }
1022 
1023   private static boolean isEitherOp(Token t, String opStr1, String opStr2) {
1024     return ((t != null) && (t.getType() == TokenType.OP) &&
1025             (opStr1.equalsIgnoreCase(t.getValueStr()) ||
1026              opStr2.equalsIgnoreCase(t.getValueStr())));
1027   }
1028 
1029   private static boolean isDelim(Token t, String opStr) {
1030     return ((t != null) && (t.getType() == TokenType.DELIM) &&
1031             opStr.equalsIgnoreCase(t.getValueStr()));
1032   }
1033 
1034   private static boolean isString(Token t, String opStr) {
1035     return ((t != null) && (t.getType() == TokenType.STRING) &&
1036             opStr.equalsIgnoreCase(t.getValueStr()));
1037   }
1038 
1039   private static WordType getWordType(Token t) {
1040     return WORD_TYPES.get(StringUtil.toLowerCase(t.getValueStr()));
1041   }
1042 
1043   /**
1044    * @return whether the given token is an arithmetic or concatenation
1045    *         operator, which binds tighter than any special operator.  Mod is
1046    *         a word and the others are symbols, so the two token types which
1047    *         can carry one are both read
1048    */
1049   private static boolean isArithmeticOp(Token t) {
1050     if((t == null) ||
1051        ((t.getType() != TokenType.OP) && (t.getType() != TokenType.STRING))) {
1052       return false;
1053     }
1054     return (getWordType(t) == WordType.OP);
1055   }
1056 
1057   private static void setWordType(WordType type, String... words) {
1058     for(String w : words) {
1059       WORD_TYPES.put(w, type);
1060     }
1061   }
1062 
1063   private static <T extends Enum<T>> T getOpType(Token t, Class<T> opClazz) {
1064     String str = t.getValueStr();
1065     for(T op : opClazz.getEnumConstants()) {
1066       if(str.equalsIgnoreCase(op.toString())) {
1067         return op;
1068       }
1069     }
1070     throw new ParseException("Unexpected op string " + t.getValueStr());
1071   }
1072 
1073   private static StringBuilder appendLeadingExpr(
1074       Expr expr, LocaleContext ctx, StringBuilder sb, boolean isDebug)
1075   {
1076     int len = sb.length();
1077     expr.toString(ctx, sb, isDebug);
1078     if(sb.length() > len) {
1079       // only add space if the leading expr added some text
1080       sb.append(" ");
1081     }
1082     return sb;
1083   }
1084 
1085   private static final class TokBuf
1086   {
1087     private final Type _exprType;
1088     private final List<Token> _tokens;
1089     private final TokBuf _parent;
1090     private final int _parentOff;
1091     private final ParseContext _ctx;
1092     private int _pos;
1093     private Expr _pendingExpr;
1094 
1095     private TokBuf(Type exprType, List<Token> tokens, ParseContext context) {
1096       this(exprType, tokens, null, 0, context);
1097     }
1098 
1099     private TokBuf(List<Token> tokens, TokBuf parent, int parentOff) {
1100       this(parent._exprType, tokens, parent, parentOff, parent._ctx);
1101     }
1102 
1103     private TokBuf(Type exprType, List<Token> tokens, TokBuf parent,
1104                    int parentOff, ParseContext context) {
1105       _exprType = exprType;
1106       _tokens = tokens;
1107       _parent = parent;
1108       _parentOff = parentOff;
1109       _ctx = context;
1110     }
1111 
1112     public Type getExprType() {
1113       return _exprType;
1114     }
1115 
1116     public int curPos() {
1117       return _pos;
1118     }
1119 
1120     public int prevPos() {
1121       return _pos - 1;
1122     }
1123 
1124     public boolean hasNext() {
1125       return (_pos < _tokens.size());
1126     }
1127 
1128     public Token peekNext() {
1129       if(!hasNext()) {
1130         return null;
1131       }
1132       return _tokens.get(_pos);
1133     }
1134 
1135     public Token next() {
1136       if(!hasNext()) {
1137         throw new ParseException(
1138             "Unexpected end of expression " + this);
1139       }
1140       return _tokens.get(_pos++);
1141     }
1142 
1143     public void reset(int pos) {
1144       _pos = pos;
1145     }
1146 
1147     public TokBuf subBuf(int start, int end) {
1148       return new TokBuf(_tokens.subList(start, end), this, start);
1149     }
1150 
1151     public void setPendingExpr(Expr expr) {
1152       if(_pendingExpr != null) {
1153         throw new ParseException(
1154             "Found multiple expressions with no operator " + this);
1155       }
1156       _pendingExpr = expr.resolveOrderOfOperations();
1157     }
1158 
1159     public void restorePendingExpr(Expr expr) {
1160       // this is an expression which was previously set, so no need to re-resolve
1161       _pendingExpr = expr;
1162     }
1163 
1164     public Expr takePendingExpr() {
1165       Expr expr = _pendingExpr;
1166       _pendingExpr = null;
1167       return expr;
1168     }
1169 
1170     public boolean hasPendingExpr() {
1171       return (_pendingExpr != null);
1172     }
1173 
1174     private Map.Entry<Integer,List<Token>> getTopPos() {
1175       int pos = _pos;
1176       List<Token> toks = _tokens;
1177       TokBuf cur = this;
1178       while(cur._parent != null) {
1179         pos += cur._parentOff;
1180         cur = cur._parent;
1181         toks = cur._tokens;
1182       }
1183       return ExpressionTokenizer.newEntry(pos, toks);
1184     }
1185 
1186     public Function getFunction(String funcName) {
1187       return _ctx.getFunctionLookup().getFunction(funcName);
1188     }
1189 
1190     @Override
1191     public String toString() {
1192 
1193       Map.Entry<Integer,List<Token>> e = getTopPos();
1194 
1195       // TODO actually format expression?
1196       StringBuilder sb = new StringBuilder()
1197         .append("[token ").append(e.getKey()).append("] (");
1198 
1199       for(Iterator<Token> iter = e.getValue().iterator(); iter.hasNext(); ) {
1200         Token t = iter.next();
1201         sb.append("'").append(t.getValueStr()).append("'");
1202         if(iter.hasNext()) {
1203           sb.append(",");
1204         }
1205       }
1206 
1207       sb.append(")");
1208 
1209       if(_pendingExpr != null) {
1210         sb.append(" [pending '").append(_pendingExpr.toDebugString(_ctx))
1211           .append("']");
1212       }
1213 
1214       return sb.toString();
1215     }
1216   }
1217 
1218   private static boolean isHigherPrecendence(OpType op1, OpType op2) {
1219     int prec1 = PRECENDENCE.get(op1);
1220     int prec2 = PRECENDENCE.get(op2);
1221 
1222     // higher preceendence ops have lower numbers
1223     return (prec1 < prec2);
1224   }
1225 
1226   private static final Map<OpType, Integer> buildPrecedenceMap(
1227       OpType[]... opArrs) {
1228     Map<OpType, Integer> prec = new HashMap<>();
1229 
1230     int level = 0;
1231     for(OpType[] ops : opArrs) {
1232       for(OpType op : ops) {
1233         prec.put(op, level);
1234       }
1235       ++level;
1236     }
1237 
1238     return prec;
1239   }
1240 
1241   private static void exprListToString(
1242       List<Expr> exprs, String sep, LocaleContext ctx, StringBuilder sb,
1243       boolean isDebug) {
1244     Iterator<Expr> iter = exprs.iterator();
1245     iter.next().toString(ctx, sb, isDebug);
1246     while(iter.hasNext()) {
1247       sb.append(sep);
1248       iter.next().toString(ctx, sb, isDebug);
1249     }
1250   }
1251 
1252   private static Value[] exprListToValues(
1253       List<Expr> exprs, EvalContext ctx) {
1254     Valueess/expr/Value.html#Value">Value[] paramVals = new Value[exprs.size()];
1255     for(int i = 0; i < exprs.size(); ++i) {
1256       paramVals[i] = exprs.get(i).eval(ctx);
1257     }
1258     return paramVals;
1259   }
1260 
1261   private static Value[] exprListToDelayedValues(
1262       List<Expr> exprs, EvalContext ctx) {
1263     Valueess/expr/Value.html#Value">Value[] paramVals = new Value[exprs.size()];
1264     for(int i = 0; i < exprs.size(); ++i) {
1265       paramVals[i] = new DelayedValue(exprs.get(i), ctx);
1266     }
1267     return paramVals;
1268   }
1269 
1270   private static boolean areConstant(List<Expr> exprs) {
1271     for(Expr expr : exprs) {
1272       if(!expr.isConstant()) {
1273         return false;
1274       }
1275     }
1276     return true;
1277   }
1278 
1279   private static boolean areConstant(Expr... exprs) {
1280     for(Expr expr : exprs) {
1281       if(!expr.isConstant()) {
1282         return false;
1283       }
1284     }
1285     return true;
1286   }
1287 
1288   private static void literalStrToString(String str, StringBuilder sb) {
1289     sb.append("\"")
1290       .append(StringUtil.replace(str, "\"", "\"\""))
1291       .append("\"");
1292   }
1293 
1294   /**
1295    * Converts an ms access like pattern to a java regex, always matching case
1296    * insensitively.
1297    */
1298   public static Pattern likePatternToRegex(String pattern) {
1299 
1300     StringBuilder sb = new StringBuilder(pattern.length());
1301 
1302     // Access LIKE pattern supports (note, matching is case-insensitive):
1303     // - '*' -> 0 or more chars
1304     // - '?' -> single character
1305     // - '#' -> single digit
1306     // - '[...]' -> character class, '[!...]' -> not in char class
1307 
1308     for(int i = 0; i < pattern.length(); ++i) {
1309       char c = pattern.charAt(i);
1310 
1311       if(c == '*') {
1312         sb.append(".*");
1313       } else if(c == '?') {
1314         sb.append('.');
1315       } else if(c == '#') {
1316         sb.append("\\d");
1317       } else if(c == '[') {
1318 
1319         // find closing brace
1320         int startPos = i + 1;
1321         int endPos = -1;
1322         for(int j = startPos; j < pattern.length(); ++j) {
1323           if(pattern.charAt(j) == ']') {
1324             endPos = j;
1325             break;
1326           }
1327         }
1328 
1329         // access treats invalid expression like "unmatchable"
1330         if(endPos == -1) {
1331           return UNMATCHABLE_REGEX;
1332         }
1333 
1334         String charClass = pattern.substring(startPos, endPos);
1335 
1336         if((charClass.length() > 0) && (charClass.charAt(0) == '!')) {
1337           // this is a negated char class
1338           charClass = '^' + charClass.substring(1);
1339         }
1340 
1341         sb.append('[').append(charClass).append(']');
1342         i += (endPos - startPos) + 1;
1343 
1344       } else if(isRegexSpecialChar(c)) {
1345         // this char is special in regexes, so escape it
1346         sb.append('\\').append(c);
1347       } else {
1348         sb.append(c);
1349       }
1350     }
1351 
1352     try {
1353       return Pattern.compile(sb.toString(),
1354                              Pattern.CASE_INSENSITIVE | Pattern.DOTALL |
1355                              Pattern.UNICODE_CASE);
1356     } catch(PatternSyntaxException ignored) {
1357       return UNMATCHABLE_REGEX;
1358     }
1359   }
1360 
1361   public static boolean isRegexSpecialChar(char c) {
1362     return REGEX_SPEC_CHARS.contains(c);
1363   }
1364 
1365   private static Value toLiteralValue(Value.Type valType, Object value) {
1366     switch(valType) {
1367     case STRING:
1368       return ValueSupport.toValue((String)value);
1369     case DATE:
1370     case TIME:
1371     case DATE_TIME:
1372       return ValueSupport.toValue(valType, (LocalDateTime)value);
1373     case LONG:
1374       return ValueSupport.toValue((Integer)value);
1375     case DOUBLE:
1376       return ValueSupport.toValue((Double)value);
1377     case BIG_DEC:
1378       return ValueSupport.toValue((BigDecimal)value);
1379     default:
1380       throw new ParseException("unexpected literal type " + valType);
1381     }
1382   }
1383 
1384   private static boolean isLiteralDefaultValue(
1385       TokBuf buf, Value.Type resultType, String exprStr,
1386       boolean allowLiteralDefault) {
1387 
1388     // if a default value expression does not start with an '=' and is used in
1389     // a string context, then it is taken as a literal value unless it starts
1390     // with a " char
1391 
1392     if(buf.getExprType() != Type.DEFAULT_VALUE) {
1393       return false;
1394     }
1395 
1396     // a leading "=" indicates "full" expression handling for a DEFAULT_VALUE
1397     // (consume this value once we detect it)
1398     if(isOp(buf.peekNext(), "=")) {
1399       buf.next();
1400       return false;
1401     }
1402 
1403     return(allowLiteralDefault && (resultType == Value.Type.STRING) &&
1404            ((exprStr.length() == 0) ||
1405             (exprStr.charAt(0) != ExpressionTokenizer.QUOTED_STR_CHAR)));
1406   }
1407 
1408   private interface LeftAssocExpr {
1409     public OpType getOp();
1410     public Expr getLeft();
1411     public void setLeft(Expr left);
1412   }
1413 
1414   private interface RightAssocExpr {
1415     public OpType getOp();
1416     public Expr getRight();
1417     public void setRight(Expr right);
1418   }
1419 
1420   private static final class DelayedValue extends BaseDelayedValue
1421   {
1422     private final Expr _expr;
1423     private final EvalContext _ctx;
1424 
1425     private DelayedValue(Expr expr, EvalContext ctx) {
1426       _expr = expr;
1427       _ctx = ctx;
1428     }
1429 
1430     @Override
1431     public Value eval() {
1432       return _expr.eval(_ctx);
1433     }
1434   }
1435 
1436 
1437   private static abstract class Expr
1438   {
1439     public String toCleanString(LocaleContext ctx) {
1440       return toString(ctx, new StringBuilder(), false).toString();
1441     }
1442 
1443     public String toDebugString(LocaleContext ctx) {
1444       return toString(ctx, new StringBuilder(), true).toString();
1445     }
1446 
1447     protected boolean isValidationExpr() {
1448       return false;
1449     }
1450 
1451     protected StringBuilder toString(
1452         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1453       if(isDebug) {
1454         sb.append("<").append(getClass().getSimpleName()).append(">{");
1455       }
1456       toExprString(ctx, sb, isDebug);
1457       if(isDebug) {
1458         sb.append("}");
1459       }
1460       return sb;
1461     }
1462 
1463     protected Expr resolveOrderOfOperations() {
1464 
1465       if(!(this instanceof LeftAssocExpr)) {
1466         // nothing we can do
1467         return this;
1468       }
1469 
1470       // in order to get the precedence right, we need to first associate this
1471       // expression with the "rightmost" expression preceding it, then adjust
1472       // this expression "down" (lower precedence) as the precedence of the
1473       // operations dictates.  since we parse from left to right, the initial
1474       // "left" value isn't the immediate left expression, instead it's based
1475       // on how the preceding operator precedence worked out.  we need to
1476       // adjust "this" expression to the closest preceding expression before
1477       // we can correctly resolve precedence.
1478 
1479       Expr outerExpr = this;
1480       final LeftAssocExpr thisExpr = (LeftAssocExpr)this;
1481       final Expr thisLeft = thisExpr.getLeft();
1482 
1483       // current: <this>{<left>{A op1 B} op2 <right>{C}}
1484       if(thisLeft instanceof RightAssocExpr) {
1485 
1486         RightAssocExpr leftOp = (RightAssocExpr)thisLeft;
1487 
1488         // target: <left>{A op1 <this>{B op2 <right>{C}}}
1489 
1490         thisExpr.setLeft(leftOp.getRight());
1491 
1492         // give the new version of this expression an opportunity to further
1493         // swap (since the swapped expression may itself be a binary
1494         // expression)
1495         leftOp.setRight(resolveOrderOfOperations());
1496         outerExpr = thisLeft;
1497 
1498         // at this point, this expression has been pushed all the way to the
1499         // rightmost preceding expression (we artifically gave "this" the
1500         // highest precedence).  now, we want to adjust precedence as
1501         // necessary (shift it back down if the operator precedence is
1502         // incorrect).  note, we only need to check precedence against "this",
1503         // as all other precedence has been resolved in previous parsing
1504         // rounds.
1505         if((leftOp.getRight() == this) &&
1506            !isHigherPrecendence(thisExpr.getOp(), leftOp.getOp())) {
1507 
1508           // doh, "this" is lower (or the same) precedence, restore the
1509           // original order of things
1510           leftOp.setRight(thisExpr.getLeft());
1511           thisExpr.setLeft(thisLeft);
1512           outerExpr = this;
1513         }
1514       }
1515 
1516       return outerExpr;
1517     }
1518 
1519     public abstract boolean isConstant();
1520 
1521     public abstract Value eval(EvalContext ctx);
1522 
1523     public abstract void collectIdentifiers(Collection<Identifier> identifiers);
1524 
1525     protected abstract void toExprString(
1526         LocaleContext ctx, StringBuilder sb, boolean isDebug);
1527   }
1528 
1529   private static final class EConstValue extends Expr
1530   {
1531     private final Value _val;
1532     private final String _str;
1533 
1534     private EConstValue(Value val, String str) {
1535       _val = val;
1536       _str = str;
1537     }
1538 
1539     @Override
1540     public boolean isConstant() {
1541       return true;
1542     }
1543 
1544     @Override
1545     public Value eval(EvalContext ctx) {
1546       return _val;
1547     }
1548 
1549     @Override
1550     public void collectIdentifiers(Collection<Identifier> identifiers) {
1551       // none
1552     }
1553 
1554     @Override
1555     protected void toExprString(
1556         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1557       sb.append(_str);
1558     }
1559   }
1560 
1561   private static final class EThisValue extends Expr
1562   {
1563     @Override
1564     public boolean isConstant() {
1565       return false;
1566     }
1567     @Override
1568     public Value eval(EvalContext ctx) {
1569       return ctx.getThisColumnValue();
1570     }
1571     @Override
1572     public void collectIdentifiers(Collection<Identifier> identifiers) {
1573       // none
1574     }
1575     @Override
1576     protected void toExprString(
1577         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1578       if(isDebug) {
1579         sb.append("<THIS_COL>");
1580       }
1581     }
1582   }
1583 
1584   private static final class ELiteralValue extends Expr
1585   {
1586     private final Value _val;
1587 
1588     private ELiteralValue(Value.Type valType, Object value) {
1589       _val = toLiteralValue(valType, value);
1590     }
1591 
1592     @Override
1593     public boolean isConstant() {
1594       return true;
1595     }
1596 
1597     @Override
1598     public Value eval(EvalContext ctx) {
1599       return _val;
1600     }
1601 
1602     @Override
1603     public void collectIdentifiers(Collection<Identifier> identifiers) {
1604       // none
1605     }
1606 
1607     @Override
1608     protected void toExprString(
1609         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1610       if(_val.getType() == Value.Type.STRING) {
1611         literalStrToString((String)_val.get(), sb);
1612       } else if(_val.getType().isTemporal()) {
1613         sb.append("#").append(_val.getAsString(ctx)).append("#");
1614       } else {
1615         sb.append(_val.get());
1616       }
1617     }
1618   }
1619 
1620   private static final class EObjValue extends Expr
1621   {
1622     private final Identifier _identifier;
1623 
1624     private EObjValue(Identifier identifier) {
1625       _identifier = identifier;
1626     }
1627 
1628     @Override
1629     public boolean isConstant() {
1630       return false;
1631     }
1632 
1633     @Override
1634     public Value eval(EvalContext ctx) {
1635       return ctx.getIdentifierValue(_identifier);
1636     }
1637 
1638     @Override
1639     public void collectIdentifiers(Collection<Identifier> identifiers) {
1640       identifiers.add(_identifier);
1641     }
1642 
1643     @Override
1644     protected void toExprString(
1645         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1646       sb.append(_identifier);
1647     }
1648   }
1649 
1650   private static class EParen extends Expr
1651   {
1652     private final Expr _expr;
1653 
1654     private EParen(Expr expr) {
1655       _expr = expr;
1656     }
1657 
1658     @Override
1659     public boolean isConstant() {
1660       return _expr.isConstant();
1661     }
1662 
1663     @Override
1664     protected boolean isValidationExpr() {
1665       return _expr.isValidationExpr();
1666     }
1667 
1668     @Override
1669     public Value eval(EvalContext ctx) {
1670       return _expr.eval(ctx);
1671     }
1672 
1673     @Override
1674     public void collectIdentifiers(Collection<Identifier> identifiers) {
1675       _expr.collectIdentifiers(identifiers);
1676     }
1677 
1678     @Override
1679     protected void toExprString(
1680         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1681       sb.append("(");
1682       _expr.toString(ctx, sb, isDebug);
1683       sb.append(")");
1684     }
1685   }
1686 
1687   private static class EFunc extends Expr
1688   {
1689     private final Function _func;
1690     private final List<Expr> _params;
1691 
1692     private EFunc(Function func, List<Expr> params) {
1693       _func = func;
1694       _params = params;
1695     }
1696 
1697     @Override
1698     public boolean isConstant() {
1699       return _func.isPure() && areConstant(_params);
1700     }
1701 
1702     @Override
1703     public Value eval(EvalContext ctx) {
1704       return _func.eval(ctx, exprListToValues(_params, ctx));
1705     }
1706 
1707     @Override
1708     public void collectIdentifiers(Collection<Identifier> identifiers) {
1709       for(Expr param : _params) {
1710         param.collectIdentifiers(identifiers);
1711       }
1712     }
1713 
1714     @Override
1715     protected void toExprString(
1716         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1717       sb.append(_func.getName()).append("(");
1718 
1719       if(!_params.isEmpty()) {
1720         exprListToString(_params, ",", ctx, sb, isDebug);
1721       }
1722 
1723       sb.append(")");
1724     }
1725   }
1726 
1727   private static abstract class EBaseBinaryOp extends Expr
1728     implements LeftAssocExpr, RightAssocExpr
1729   {
1730     protected final OpType _op;
1731     protected Expr _left;
1732     protected Expr _right;
1733 
1734     private EBaseBinaryOp(OpType op, Expr left, Expr right) {
1735       _op = op;
1736       _left = left;
1737       _right = right;
1738     }
1739 
1740     @Override
1741     public boolean isConstant() {
1742       return areConstant(_left, _right);
1743     }
1744 
1745     @Override
1746     public OpType getOp() {
1747       return _op;
1748     }
1749 
1750     @Override
1751     public Expr getLeft() {
1752       return _left;
1753     }
1754 
1755     @Override
1756     public void setLeft(Expr left) {
1757       _left = left;
1758     }
1759 
1760     @Override
1761     public Expr getRight() {
1762       return _right;
1763     }
1764 
1765     @Override
1766     public void setRight(Expr right) {
1767       _right = right;
1768     }
1769 
1770     @Override
1771     public void collectIdentifiers(Collection<Identifier> identifiers) {
1772       _left.collectIdentifiers(identifiers);
1773       _right.collectIdentifiers(identifiers);
1774     }
1775 
1776     @Override
1777     protected void toExprString(
1778         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1779       appendLeadingExpr(_left, ctx, sb, isDebug)
1780         .append(_op).append(" ");
1781       _right.toString(ctx, sb, isDebug);
1782     }
1783   }
1784 
1785   private static class EBinaryOp extends EBaseBinaryOp
1786   {
1787     private EBinaryOp(BinaryOp op, Expr left, Expr right) {
1788       super(op, left, right);
1789     }
1790 
1791     @Override
1792     public Value eval(EvalContext ctx) {
1793       return ((BinaryOp)_op).eval(ctx, _left.eval(ctx), _right.eval(ctx));
1794     }
1795   }
1796 
1797   private static class EUnaryOp extends Expr
1798     implements RightAssocExpr
1799   {
1800     private final OpType _op;
1801     private Expr _expr;
1802 
1803     private EUnaryOp(UnaryOp op, Expr expr) {
1804       _op = op;
1805       _expr = expr;
1806     }
1807 
1808     @Override
1809     public boolean isConstant() {
1810       return _expr.isConstant();
1811     }
1812 
1813     @Override
1814     public OpType getOp() {
1815       return _op;
1816     }
1817 
1818     @Override
1819     public Expr getRight() {
1820       return _expr;
1821     }
1822 
1823     @Override
1824     public void setRight(Expr right) {
1825       _expr = right;
1826     }
1827 
1828     @Override
1829     public Value eval(EvalContext ctx) {
1830       return ((UnaryOp)_op).eval(ctx, _expr.eval(ctx));
1831     }
1832 
1833     @Override
1834     public void collectIdentifiers(Collection<Identifier> identifiers) {
1835       _expr.collectIdentifiers(identifiers);
1836     }
1837 
1838     @Override
1839     protected void toExprString(
1840         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1841       sb.append(_op);
1842       if(isDebug || ((UnaryOp)_op).needsSpace()) {
1843         sb.append(" ");
1844       }
1845       _expr.toString(ctx, sb, isDebug);
1846     }
1847   }
1848 
1849   private static class ECompOp extends EBaseBinaryOp
1850   {
1851     private ECompOp(CompOp op, Expr left, Expr right) {
1852       super(op, left, right);
1853     }
1854 
1855     @Override
1856     protected boolean isValidationExpr() {
1857       return true;
1858     }
1859 
1860     @Override
1861     public Value eval(EvalContext ctx) {
1862       return ((CompOp)_op).eval(ctx, _left.eval(ctx), _right.eval(ctx));
1863     }
1864   }
1865 
1866   private static class EImplicitCompOp extends ECompOp
1867   {
1868     private EImplicitCompOp(Expr right) {
1869       super(CompOp.EQ, THIS_COL_VALUE, right);
1870     }
1871 
1872     @Override
1873     protected void toExprString(
1874         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1875       // only output the full "implicit" comparison in debug mode
1876       if(isDebug) {
1877         super.toExprString(ctx, sb, isDebug);
1878       } else {
1879         // just output the explicit part of the expression
1880         _right.toString(ctx, sb, isDebug);
1881       }
1882     }
1883   }
1884 
1885   private static class ELogicalOp extends EBaseBinaryOp
1886   {
1887     private ELogicalOp(LogOp op, Expr left, Expr right) {
1888       super(op, left, right);
1889     }
1890 
1891     @Override
1892     protected boolean isValidationExpr() {
1893       return true;
1894     }
1895 
1896     @Override
1897     public Value eval(final EvalContext ctx) {
1898 
1899       // logical operations do short circuit evaluation, so we need to delay
1900       // computing results until necessary
1901       return ((LogOp)_op).eval(ctx, new DelayedValue(_left, ctx),
1902                                new DelayedValue(_right, ctx));
1903     }
1904   }
1905 
1906   private static abstract class ESpecOp extends Expr
1907     implements LeftAssocExpr
1908   {
1909     protected final SpecOp _op;
1910     protected Expr _expr;
1911 
1912     private ESpecOp(SpecOp op, Expr expr) {
1913       _op = op;
1914       _expr = expr;
1915     }
1916 
1917     @Override
1918     public boolean isConstant() {
1919       return _expr.isConstant();
1920     }
1921 
1922     @Override
1923     public OpType getOp() {
1924       return _op;
1925     }
1926 
1927     @Override
1928     public Expr getLeft() {
1929       return _expr;
1930     }
1931 
1932     @Override
1933     public void setLeft(Expr left) {
1934       _expr = left;
1935     }
1936 
1937     @Override
1938     public void collectIdentifiers(Collection<Identifier> identifiers) {
1939       _expr.collectIdentifiers(identifiers);
1940     }
1941 
1942     @Override
1943     protected boolean isValidationExpr() {
1944       return true;
1945     }
1946   }
1947 
1948   private static class ENullOp extends ESpecOp
1949   {
1950     private ENullOp(SpecOp op, Expr expr) {
1951       super(op, expr);
1952     }
1953 
1954     @Override
1955     public Value eval(EvalContext ctx) {
1956       return _op.eval(ctx, _expr.eval(ctx), null, null);
1957     }
1958 
1959     @Override
1960     protected void toExprString(
1961         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1962       appendLeadingExpr(_expr, ctx, sb, isDebug)
1963         .append(_op);
1964     }
1965   }
1966 
1967   private static class ELikeOp extends ESpecOp
1968   {
1969     private final String _patternStr;
1970     private Pattern _pattern;
1971 
1972     private ELikeOp(SpecOp op, Expr expr, String patternStr) {
1973       super(op, expr);
1974       _patternStr = patternStr;
1975     }
1976 
1977     private Pattern getPattern()
1978     {
1979       if(_pattern == null) {
1980         _pattern = likePatternToRegex(_patternStr);
1981       }
1982       return _pattern;
1983     }
1984 
1985     @Override
1986     public Value eval(EvalContext ctx) {
1987       return _op.eval(ctx, _expr.eval(ctx), getPattern(), null);
1988     }
1989 
1990     @Override
1991     protected void toExprString(
1992         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
1993       appendLeadingExpr(_expr, ctx, sb, isDebug)
1994         .append(_op).append(" ");
1995       literalStrToString(_patternStr, sb);
1996       if(isDebug) {
1997         sb.append("(").append(getPattern()).append(")");
1998       }
1999     }
2000   }
2001 
2002   /**
2003    * A Like whose pattern is an expression rather than a literal, such as a
2004    * comparison of one column against another.  The pattern is not known until
2005    * the expression is evaluated, so the regex is compiled then and kept for as
2006    * long as the pattern stays the same.
2007    */
2008   private static class EDynamicLikeOp extends ESpecOp
2009     implements RightAssocExpr
2010   {
2011     private Expr _patternExpr;
2012     /** the last pattern and the regex of it, held together so a thread which
2013         reads the pair gets a regex which goes with the string */
2014     private volatile CompiledPattern _compiled;
2015 
2016     private EDynamicLikeOp(SpecOp op, Expr expr, Expr patternExpr) {
2017       super(op, expr);
2018       _patternExpr = patternExpr;
2019     }
2020 
2021     @Override
2022     public boolean isConstant() {
2023       return super.isConstant() && _patternExpr.isConstant();
2024     }
2025 
2026     @Override
2027     public Expr getRight() {
2028       return _patternExpr;
2029     }
2030 
2031     @Override
2032     public void setRight(Expr right) {
2033       _patternExpr = right;
2034     }
2035 
2036     @Override
2037     public Value eval(EvalContext ctx) {
2038       Value patternVal = _patternExpr.eval(ctx);
2039       if(patternVal.isNull()) {
2040         return ValueSupport.NULL_VAL;
2041       }
2042       String patternStr = patternVal.getAsString(ctx);
2043       CompiledPattern compiled = _compiled;
2044       if((compiled == null) || !patternStr.equals(compiled._patternStr)) {
2045         compiled = new CompiledPattern(patternStr,
2046                                        likePatternToRegex(patternStr));
2047         _compiled = compiled;
2048       }
2049       return _op.eval(ctx, _expr.eval(ctx), compiled._pattern, null);
2050     }
2051 
2052     @Override
2053     public void collectIdentifiers(Collection<Identifier> identifiers) {
2054       super.collectIdentifiers(identifiers);
2055       _patternExpr.collectIdentifiers(identifiers);
2056     }
2057 
2058     @Override
2059     protected void toExprString(
2060         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
2061       appendLeadingExpr(_expr, ctx, sb, isDebug)
2062         .append(_op).append(" ");
2063       _patternExpr.toString(ctx, sb, isDebug);
2064     }
2065   }
2066 
2067   /** a Like pattern and the regex compiled from it */
2068   private static final class CompiledPattern
2069   {
2070     private final String _patternStr;
2071     private final Pattern _pattern;
2072 
2073     private CompiledPattern(String patternStr, Pattern pattern) {
2074       _patternStr = patternStr;
2075       _pattern = pattern;
2076     }
2077   }
2078 
2079   private static class EInOp extends ESpecOp
2080   {
2081     private final List<Expr> _exprs;
2082 
2083     private EInOp(SpecOp op, Expr expr, List<Expr> exprs) {
2084       super(op, expr);
2085       _exprs = exprs;
2086     }
2087 
2088     @Override
2089     public boolean isConstant() {
2090       return super.isConstant() && areConstant(_exprs);
2091     }
2092 
2093     @Override
2094     public Value eval(EvalContext ctx) {
2095       return _op.eval(ctx, _expr.eval(ctx),
2096                       exprListToDelayedValues(_exprs, ctx), null);
2097     }
2098 
2099     @Override
2100     public void collectIdentifiers(Collection<Identifier> identifiers) {
2101       for(Expr expr : _exprs) {
2102         expr.collectIdentifiers(identifiers);
2103       }
2104     }
2105 
2106     @Override
2107     protected void toExprString(
2108         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
2109       appendLeadingExpr(_expr, ctx, sb, isDebug)
2110         .append(_op).append(" (");
2111       exprListToString(_exprs, ",", ctx, sb, isDebug);
2112       sb.append(")");
2113     }
2114   }
2115 
2116   private static class EBetweenOp extends ESpecOp
2117     implements RightAssocExpr
2118   {
2119     private final Expr _startRangeExpr;
2120     private Expr _endRangeExpr;
2121 
2122     private EBetweenOp(SpecOp op, Expr expr, Expr startRangeExpr,
2123                        Expr endRangeExpr) {
2124       super(op, expr);
2125       _startRangeExpr = startRangeExpr;
2126       _endRangeExpr = endRangeExpr;
2127     }
2128 
2129     @Override
2130     public boolean isConstant() {
2131       return _expr.isConstant() && areConstant(_startRangeExpr, _endRangeExpr);
2132     }
2133 
2134     @Override
2135     public Expr getRight() {
2136       return _endRangeExpr;
2137     }
2138 
2139     @Override
2140     public void setRight(Expr right) {
2141       _endRangeExpr = right;
2142     }
2143 
2144     @Override
2145     public Value eval(EvalContext ctx) {
2146       return _op.eval(ctx, _expr.eval(ctx),
2147                       new DelayedValue(_startRangeExpr, ctx),
2148                       new DelayedValue(_endRangeExpr, ctx));
2149     }
2150 
2151     @Override
2152     public void collectIdentifiers(Collection<Identifier> identifiers) {
2153       super.collectIdentifiers(identifiers);
2154       _startRangeExpr.collectIdentifiers(identifiers);
2155       _endRangeExpr.collectIdentifiers(identifiers);
2156     }
2157 
2158     @Override
2159     protected void toExprString(
2160         LocaleContext ctx, StringBuilder sb, boolean isDebug) {
2161       appendLeadingExpr(_expr, ctx, sb, isDebug)
2162         .append(_op).append(" ");
2163       _startRangeExpr.toString(ctx, sb, isDebug);
2164       sb.append(" And ");
2165       _endRangeExpr.toString(ctx, sb, isDebug);
2166     }
2167   }
2168 
2169   /**
2170    * Base Expression wrapper for an Expr.
2171    */
2172   private static abstract class BaseExprWrapper implements Expression
2173   {
2174     private final String _rawExprStr;
2175     private final Expr _expr;
2176 
2177     private BaseExprWrapper(String rawExprStr, Expr expr) {
2178       _rawExprStr = rawExprStr;
2179       _expr = expr;
2180     }
2181 
2182     @Override
2183     public String toDebugString(LocaleContext ctx) {
2184       return _expr.toDebugString(ctx);
2185     }
2186 
2187     @Override
2188     public String toRawString() {
2189       return _rawExprStr;
2190     }
2191 
2192     @Override
2193     public String toCleanString(LocaleContext ctx) {
2194       return _expr.toCleanString(ctx);
2195     }
2196 
2197     @Override
2198     public boolean isConstant() {
2199       return _expr.isConstant();
2200     }
2201 
2202     @Override
2203     public void collectIdentifiers(Collection<Identifier> identifiers) {
2204       _expr.collectIdentifiers(identifiers);
2205     }
2206 
2207     @Override
2208     public String toString() {
2209       return toRawString();
2210     }
2211 
2212     protected Object evalValue(Value.Type resultType, EvalContext ctx) {
2213       Value val = _expr.eval(ctx);
2214 
2215       if(val.isNull()) {
2216         return null;
2217       }
2218 
2219       if(resultType == null) {
2220         // return as "native" type
2221         return val.get();
2222       }
2223 
2224       // FIXME possibly do some type coercion.  are there conversions here which don't work elsewhere? (string -> date, string -> number)?
2225       switch(resultType) {
2226       case STRING:
2227         return val.getAsString(ctx);
2228       case DATE:
2229       case TIME:
2230       case DATE_TIME:
2231         return val.getAsLocalDateTime(ctx);
2232       case LONG:
2233         return val.getAsLongInt(ctx);
2234       case DOUBLE:
2235         return val.getAsDouble(ctx);
2236       case BIG_DEC:
2237         return val.getAsBigDecimal(ctx);
2238       default:
2239         throw new IllegalStateException("unexpected result type " + resultType);
2240       }
2241     }
2242 
2243     protected Boolean evalCondition(EvalContext ctx) {
2244       Value val = _expr.eval(ctx);
2245 
2246       if(val.isNull()) {
2247         // null can't be coerced to a boolean
2248         throw new EvalException("Condition evaluated to Null");
2249       }
2250 
2251       return val.getAsBoolean(ctx);
2252     }
2253   }
2254 
2255   /**
2256    * Expression wrapper for an Expr which returns a value.
2257    */
2258   private static class ExprWrapper extends BaseExprWrapper
2259   {
2260     private final Value.Type _resultType;
2261 
2262     private ExprWrapper(String rawExprStr, Expr expr, Value.Type resultType) {
2263       super(rawExprStr, expr);
2264       _resultType = resultType;
2265     }
2266 
2267     @Override
2268     public Object eval(EvalContext ctx) {
2269       return evalValue(_resultType, ctx);
2270     }
2271   }
2272 
2273   /**
2274    * Expression wrapper for an Expr which returns a Boolean from a conditional
2275    * expression.
2276    */
2277   private static class CondExprWrapper extends BaseExprWrapper
2278   {
2279     private CondExprWrapper(String rawExprStr, Expr expr) {
2280       super(rawExprStr, expr);
2281     }
2282 
2283     @Override
2284     public Object eval(EvalContext ctx) {
2285       return evalCondition(ctx);
2286     }
2287   }
2288 
2289   /**
2290    * Expression wrapper for a <i>pure</i> Expr which caches the result of
2291    * evaluation.
2292    */
2293   private static final class MemoizedExprWrapper extends ExprWrapper
2294   {
2295     private Object _val;
2296 
2297     private MemoizedExprWrapper(String rawExprStr, Expr expr,
2298                                 Value.Type resultType) {
2299       super(rawExprStr, expr, resultType);
2300     }
2301 
2302     @Override
2303     public Object eval(EvalContext ctx) {
2304       if(_val == null) {
2305         _val = super.eval(ctx);
2306       }
2307       return _val;
2308     }
2309   }
2310 
2311   /**
2312    * Expression wrapper for a <i>pure</i> conditional Expr which caches the
2313    * result of evaluation.
2314    */
2315   private static final class MemoizedCondExprWrapper extends CondExprWrapper
2316   {
2317     private Object _val;
2318 
2319     private MemoizedCondExprWrapper(String rawExprStr, Expr expr) {
2320       super(rawExprStr, expr);
2321     }
2322 
2323     @Override
2324     public Object eval(EvalContext ctx) {
2325       if(_val == null) {
2326         _val = super.eval(ctx);
2327       }
2328       return _val;
2329     }
2330   }
2331 }