123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157 |
- /*
- Copyright (c) 2016 James Ahlborn
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
-
- package com.healthmarketscience.jackcess.impl.expr;
-
- import java.math.BigDecimal;
- import java.text.DateFormat;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Date;
- import java.util.Deque;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.regex.Pattern;
- import java.util.regex.PatternSyntaxException;
-
- import com.healthmarketscience.jackcess.expr.EvalContext;
- import com.healthmarketscience.jackcess.expr.EvalException;
- import com.healthmarketscience.jackcess.expr.Expression;
- import com.healthmarketscience.jackcess.expr.Function;
- import com.healthmarketscience.jackcess.expr.FunctionLookup;
- import com.healthmarketscience.jackcess.expr.Identifier;
- import com.healthmarketscience.jackcess.expr.LocaleContext;
- import com.healthmarketscience.jackcess.expr.ParseException;
- import com.healthmarketscience.jackcess.expr.TemporalConfig;
- import com.healthmarketscience.jackcess.expr.Value;
- import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token;
- import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType;
-
-
- /**
- *
- * @author James Ahlborn
- */
- public class Expressionator
- {
-
- // Useful links:
- // - syntax: https://support.office.com/en-us/article/Guide-to-expression-syntax-ebc770bc-8486-4adc-a9ec-7427cce39a90
- // - examples: https://support.office.com/en-us/article/Examples-of-expressions-d3901e11-c04e-4649-b40b-8b6ec5aed41f
- // - validation rule usage: https://support.office.com/en-us/article/Restrict-data-input-by-using-a-validation-rule-6c0b2ce1-76fa-4be0-8ae9-038b52652320
-
-
- public enum Type {
- DEFAULT_VALUE, EXPRESSION, FIELD_VALIDATOR, RECORD_VALIDATOR;
- }
-
- public interface ParseContext extends LocaleContext {
- public TemporalConfig getTemporalConfig();
- public SimpleDateFormat createDateFormat(String formatStr);
- public FunctionLookup getFunctionLookup();
- }
-
- private enum WordType {
- OP, COMP, LOG_OP, CONST, SPEC_OP_PREFIX, DELIM;
- }
-
- private static final String FUNC_START_DELIM = "(";
- private static final String FUNC_END_DELIM = ")";
- private static final String OPEN_PAREN = "(";
- private static final String CLOSE_PAREN = ")";
- private static final String FUNC_PARAM_SEP = ",";
-
- private static final Map<String,WordType> WORD_TYPES =
- new HashMap<String,WordType>();
-
- static {
- setWordType(WordType.OP, "+", "-", "*", "/", "\\", "^", "&", "mod");
- setWordType(WordType.COMP, "<", "<=", ">", ">=", "=", "<>");
- setWordType(WordType.LOG_OP, "and", "or", "eqv", "xor", "imp");
- setWordType(WordType.CONST, "true", "false", "null", "on", "off",
- "yes", "no");
- setWordType(WordType.SPEC_OP_PREFIX, "is", "like", "between", "in", "not");
- // "X is null", "X is not null", "X like P", "X between A and B",
- // "X not between A and B", "X in (A, B, C...)", "X not in (A, B, C...)",
- // "not X"
- setWordType(WordType.DELIM, ".", "!", ",", "(", ")");
- }
-
- private static final Collection<String> TRUE_STRS =
- Arrays.asList("true", "yes", "on");
- private static final Collection<String> FALSE_STRS =
- Arrays.asList("false", "no", "off");
-
- private interface OpType {}
-
- private enum UnaryOp implements OpType {
- NEG("-", false) {
- @Override public Value eval(EvalContext ctx, Value param1) {
- return BuiltinOperators.negate(ctx, param1);
- }
- @Override public UnaryOp getUnaryNumOp() {
- return UnaryOp.NEG_NUM;
- }
- },
- POS("+", false) {
- @Override public Value eval(EvalContext ctx, Value param1) {
- // basically a no-op
- return param1;
- }
- @Override public UnaryOp getUnaryNumOp() {
- return UnaryOp.POS_NUM;
- }
- },
- NOT("Not", true) {
- @Override public Value eval(EvalContext ctx, Value param1) {
- return BuiltinOperators.not(param1);
- }
- },
- // when a '-' immediately precedes a number, it needs "highest" precedence
- NEG_NUM("-", false) {
- @Override public Value eval(EvalContext ctx, Value param1) {
- return BuiltinOperators.negate(ctx, param1);
- }
- },
- // when a '+' immediately precedes a number, it needs "highest" precedence
- POS_NUM("+", false) {
- @Override public Value eval(EvalContext ctx, Value param1) {
- // basically a no-op
- return param1;
- }
- };
-
- private final String _str;
- private final boolean _needSpace;
-
- private UnaryOp(String str, boolean needSpace) {
- _str = str;
- _needSpace = needSpace;
- }
-
- public boolean needsSpace() {
- return _needSpace;
- }
-
- @Override
- public String toString() {
- return _str;
- }
-
- public UnaryOp getUnaryNumOp() {
- return null;
- }
-
- public abstract Value eval(EvalContext ctx, Value param1);
- }
-
- private enum BinaryOp implements OpType {
- PLUS("+") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.add(ctx, param1, param2);
- }
- },
- MINUS("-") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.subtract(ctx, param1, param2);
- }
- },
- MULT("*") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.multiply(param1, param2);
- }
- },
- DIV("/") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.divide(param1, param2);
- }
- },
- INT_DIV("\\") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.intDivide(param1, param2);
- }
- },
- EXP("^") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.exp(param1, param2);
- }
- },
- CONCAT("&") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.concat(param1, param2);
- }
- },
- MOD("Mod") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.mod(param1, param2);
- }
- };
-
- private final String _str;
-
- private BinaryOp(String str) {
- _str = str;
- }
-
- @Override
- public String toString() {
- return _str;
- }
-
- public abstract Value eval(EvalContext ctx, Value param1, Value param2);
- }
-
- private enum CompOp implements OpType {
- LT("<") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.lessThan(param1, param2);
- }
- },
- LTE("<=") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.lessThanEq(param1, param2);
- }
- },
- GT(">") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.greaterThan(param1, param2);
- }
- },
- GTE(">=") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.greaterThanEq(param1, param2);
- }
- },
- EQ("=") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.equals(param1, param2);
- }
- },
- NE("<>") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.notEquals(param1, param2);
- }
- };
-
- private final String _str;
-
- private CompOp(String str) {
- _str = str;
- }
-
- @Override
- public String toString() {
- return _str;
- }
-
- public abstract Value eval(Value param1, Value param2);
- }
-
- private enum LogOp implements OpType {
- AND("And") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.and(param1, param2);
- }
- },
- OR("Or") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.or(param1, param2);
- }
- },
- EQV("Eqv") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.eqv(param1, param2);
- }
- },
- XOR("Xor") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.xor(param1, param2);
- }
- },
- IMP("Imp") {
- @Override public Value eval(Value param1, Value param2) {
- return BuiltinOperators.imp(param1, param2);
- }
- };
-
- private final String _str;
-
- private LogOp(String str) {
- _str = str;
- }
-
- @Override
- public String toString() {
- return _str;
- }
-
- public abstract Value eval(Value param1, Value param2);
- }
-
- private enum SpecOp implements OpType {
- // note, "NOT" is not actually used as a special operation, always
- // replaced with UnaryOp.NOT
- NOT("Not") {
- @Override public Value eval(Value param1, Object param2, Object param3) {
- throw new UnsupportedOperationException();
- }
- },
- IS_NULL("Is Null") {
- @Override public Value eval(Value param1, Object param2, Object param3) {
- return BuiltinOperators.isNull(param1);
- }
- },
- IS_NOT_NULL("Is Not Null") {
- @Override public Value eval(Value param1, Object param2, Object param3) {
- return BuiltinOperators.isNotNull(param1);
- }
- },
- LIKE("Like") {
- @Override public Value eval(Value param1, Object param2, Object param3) {
- return BuiltinOperators.like(param1, (Pattern)param2);
- }
- },
- NOT_LIKE("Not Like") {
- @Override public Value eval(Value param1, Object param2, Object param3) {
- return BuiltinOperators.notLike(param1, (Pattern)param2);
- }
- },
- BETWEEN("Between") {
- @Override public Value eval(Value param1, Object param2, Object param3) {
- return BuiltinOperators.between(param1, (Value)param2, (Value)param3);
- }
- },
- NOT_BETWEEN("Not Between") {
- @Override public Value eval(Value param1, Object param2, Object param3) {
- return BuiltinOperators.notBetween(param1, (Value)param2, (Value)param3);
- }
- },
- IN("In") {
- @Override public Value eval(Value param1, Object param2, Object param3) {
- return BuiltinOperators.in(param1, (Value[])param2);
- }
- },
- NOT_IN("Not In") {
- @Override public Value eval(Value param1, Object param2, Object param3) {
- return BuiltinOperators.notIn(param1, (Value[])param2);
- }
- };
-
- private final String _str;
-
- private SpecOp(String str) {
- _str = str;
- }
-
- @Override
- public String toString() {
- return _str;
- }
-
- public abstract Value eval(Value param1, Object param2, Object param3);
- }
-
- private static final Map<OpType, Integer> PRECENDENCE =
- buildPrecedenceMap(
- new OpType[]{UnaryOp.NEG_NUM, UnaryOp.POS_NUM},
- new OpType[]{BinaryOp.EXP},
- new OpType[]{UnaryOp.NEG, UnaryOp.POS},
- new OpType[]{BinaryOp.MULT, BinaryOp.DIV},
- new OpType[]{BinaryOp.INT_DIV},
- new OpType[]{BinaryOp.MOD},
- new OpType[]{BinaryOp.PLUS, BinaryOp.MINUS},
- new OpType[]{BinaryOp.CONCAT},
- new OpType[]{CompOp.LT, CompOp.GT, CompOp.NE, CompOp.LTE, CompOp.GTE,
- CompOp.EQ, SpecOp.LIKE, SpecOp.NOT_LIKE,
- SpecOp.IS_NULL, SpecOp.IS_NOT_NULL},
- new OpType[]{UnaryOp.NOT},
- new OpType[]{LogOp.AND},
- new OpType[]{LogOp.OR},
- new OpType[]{LogOp.XOR},
- new OpType[]{LogOp.EQV},
- new OpType[]{LogOp.IMP},
- new OpType[]{SpecOp.IN, SpecOp.NOT_IN, SpecOp.BETWEEN,
- SpecOp.NOT_BETWEEN});
-
- private static final Set<Character> REGEX_SPEC_CHARS = new HashSet<Character>(
- Arrays.asList('\\','.','%','=','+', '$','^','|','(',')','{','}','&'));
- // this is a regular expression which will never match any string
- private static final Pattern UNMATCHABLE_REGEX = Pattern.compile("(?!)");
-
- private static final Expr THIS_COL_VALUE = new EThisValue();
-
- private static final Expr NULL_VALUE = new EConstValue(
- ValueSupport.NULL_VAL, "Null");
- private static final Expr TRUE_VALUE = new EConstValue(
- ValueSupport.TRUE_VAL, "True");
- private static final Expr FALSE_VALUE = new EConstValue(
- ValueSupport.FALSE_VAL, "False");
-
-
- private Expressionator() {}
-
- public static Expression parse(Type exprType, String exprStr,
- Value.Type resultType,
- ParseContext context) {
-
- List<Token> tokens = trimSpaces(
- ExpressionTokenizer.tokenize(exprType, exprStr, context));
-
- if(tokens == null) {
- throw new ParseException("null/empty expression");
- }
-
- TokBuf buf = new TokBuf(exprType, tokens, context);
-
- if(isLiteralDefaultValue(buf, resultType, exprStr)) {
-
- // this is handled as a literal string value, not an expression. no
- // need to memo-ize cause it's a simple literal value
- return new ExprWrapper(exprStr,
- new ELiteralValue(Value.Type.STRING, exprStr, null), resultType);
- }
-
- // normal expression handling
- Expr expr = parseExpression(buf, false);
-
- if((exprType == Type.FIELD_VALIDATOR) && !expr.isValidationExpr()) {
- // a non-validation expression for a FIELD_VALIDATOR treats the result
- // as an equality comparison with the field in question. so, transform
- // the expression accordingly
- expr = new EImplicitCompOp(expr);
- }
-
- switch(exprType) {
- case DEFAULT_VALUE:
- case EXPRESSION:
- return (expr.isConstant() ?
- // for now, just cache at top-level for speed (could in theory
- // cache intermediate values?)
- new MemoizedExprWrapper(exprStr, expr, resultType) :
- new ExprWrapper(exprStr, expr, resultType));
- case FIELD_VALIDATOR:
- case RECORD_VALIDATOR:
- return (expr.isConstant() ?
- // for now, just cache at top-level for speed (could in theory
- // cache intermediate values?)
- new MemoizedCondExprWrapper(exprStr, expr) :
- new CondExprWrapper(exprStr, expr));
- default:
- throw new ParseException("unexpected expression type " + exprType);
- }
- }
-
- private static List<Token> trimSpaces(List<Token> tokens) {
- if(tokens == null) {
- return null;
- }
-
- // for the most part, spaces are superfluous except for one situation(?).
- // when they appear between a string literal and '(' they help distinguish
- // a function call from another expression form
- for(int i = 1; i < (tokens.size() - 1); ++i) {
- Token t = tokens.get(i);
- if(t.getType() == TokenType.SPACE) {
- if((tokens.get(i - 1).getType() == TokenType.STRING) &&
- isDelim(tokens.get(i + 1), FUNC_START_DELIM)) {
- // we want to keep this space
- } else {
- tokens.remove(i);
- --i;
- }
- }
- }
- return tokens;
- }
-
- private static Expr parseExpression(TokBuf buf, boolean singleExpr)
- {
- while(buf.hasNext()) {
- Token t = buf.next();
-
- switch(t.getType()) {
- case OBJ_NAME:
-
- parseObjectRefExpression(t, buf);
- break;
-
- case LITERAL:
-
- buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue(),
- t.getDateFormat()));
- break;
-
- case OP:
-
- WordType wordType = getWordType(t);
- if(wordType == null) {
- // shouldn't happen
- throw new ParseException("Invalid operator " + t);
- }
-
- // this can only be an OP or a COMP (those are the only words that the
- // tokenizer would define as TokenType.OP)
- switch(wordType) {
- case OP:
- parseOperatorExpression(t, buf);
- break;
-
- case COMP:
-
- parseCompOpExpression(t, buf);
- break;
-
- default:
- throw new ParseException("Unexpected OP word type " + wordType);
- }
-
- break;
-
- case DELIM:
-
- parseDelimExpression(t, buf);
- break;
-
- case STRING:
-
- // see if it's a special word?
- wordType = getWordType(t);
- if(wordType == null) {
-
- // is it a function call?
- if(!maybeParseFuncCallExpression(t, buf)) {
-
- // is it an object name?
- Token next = buf.peekNext();
- if((next != null) && isObjNameSep(next)) {
-
- parseObjectRefExpression(t, buf);
-
- } else {
-
- // FIXME maybe bare obj name, maybe string literal?
- throw new UnsupportedOperationException("FIXME");
- }
- }
-
- } else {
-
- // this could be anything but COMP or DELIM (all COMPs would be
- // returned as TokenType.OP and all DELIMs would be TokenType.DELIM)
- switch(wordType) {
- case OP:
-
- parseOperatorExpression(t, buf);
- break;
-
- case LOG_OP:
-
- parseLogicalOpExpression(t, buf);
- break;
-
- case CONST:
-
- parseConstExpression(t, buf);
- break;
-
- case SPEC_OP_PREFIX:
-
- parseSpecOpExpression(t, buf);
- break;
-
- default:
- throw new ParseException("Unexpected STRING word type "
- + wordType);
- }
- }
-
- break;
-
- case SPACE:
- // top-level space is irrelevant (and we strip them anyway)
- break;
-
- default:
- throw new ParseException("unknown token type " + t);
- }
-
- if(singleExpr && buf.hasPendingExpr()) {
- break;
- }
- }
-
- Expr expr = buf.takePendingExpr();
- if(expr == null) {
- throw new ParseException("No expression found? " + buf);
- }
-
- return expr;
- }
-
- private static void parseObjectRefExpression(Token firstTok, TokBuf buf) {
-
- // object references may be joined by '.' or '!'. access syntac docs claim
- // object identifiers can be formatted like:
- // "[Collection name]![Object name].[Property name]"
- // However, in practice, they only ever seem to be (at most) two levels
- // and only use '.'. Apparently '!' is actually a special late-bind
- // operator (not sure it makes a difference for this code?), see:
- // http://bytecomb.com/the-bang-exclamation-operator-in-vba/
- Deque<String> objNames = new LinkedList<String>();
- objNames.add(firstTok.getValueStr());
-
- Token t = null;
- boolean atSep = false;
- while((t = buf.peekNext()) != null) {
- if(!atSep) {
- if(isObjNameSep(t)) {
- buf.next();
- atSep = true;
- continue;
- }
- } else {
- if((t.getType() == TokenType.OBJ_NAME) ||
- (t.getType() == TokenType.STRING)) {
- buf.next();
- // always insert at beginning of list so names are in reverse order
- objNames.addFirst(t.getValueStr());
- atSep = false;
- continue;
- }
- }
- break;
- }
-
- int numNames = objNames.size();
- if(atSep || (numNames > 3)) {
- throw new ParseException("Invalid object reference " + buf);
- }
-
- // names are in reverse order
- String propName = null;
- if(numNames == 3) {
- propName = objNames.poll();
- }
- String objName = objNames.poll();
- String collectionName = objNames.poll();
-
- buf.setPendingExpr(
- new EObjValue(new Identifier(collectionName, objName, propName)));
- }
-
- private static void parseDelimExpression(Token firstTok, TokBuf buf) {
- // the only "top-level" delim we expect to find is open paren, and
- // there shouldn't be any pending expression
- if(!isDelim(firstTok, OPEN_PAREN) || buf.hasPendingExpr()) {
- throw new ParseException("Unexpected delimiter " +
- firstTok.getValue() + " " + buf);
- }
-
- Expr subExpr = findParenExprs(buf, false).get(0);
- buf.setPendingExpr(new EParen(subExpr));
- }
-
- private static boolean maybeParseFuncCallExpression(
- Token firstTok, TokBuf buf) {
-
- int startPos = buf.curPos();
- boolean foundFunc = false;
-
- try {
- Token t = buf.peekNext();
- if(!isDelim(t, FUNC_START_DELIM)) {
- // not a function call
- return false;
- }
-
- buf.next();
- List<Expr> params = findParenExprs(buf, true);
- String funcName = firstTok.getValueStr();
- Function func = buf.getFunction(funcName);
- if(func == null) {
- throw new ParseException("Could not find function '" +
- funcName + "' " + buf);
- }
- buf.setPendingExpr(new EFunc(func, params));
- foundFunc = true;
- return true;
-
- } finally {
- if(!foundFunc) {
- buf.reset(startPos);
- }
- }
- }
-
- private static List<Expr> findParenExprs(
- TokBuf buf, boolean allowMulti) {
-
- if(allowMulti) {
- // simple case, no nested expr
- Token t = buf.peekNext();
- if(isDelim(t, CLOSE_PAREN)) {
- buf.next();
- return Collections.emptyList();
- }
- }
-
- // find closing ")", handle nested parens
- List<Expr> exprs = new ArrayList<Expr>(3);
- int level = 1;
- int startPos = buf.curPos();
- while(buf.hasNext()) {
-
- Token t = buf.next();
-
- if(isDelim(t, OPEN_PAREN)) {
-
- ++level;
-
- } else if(isDelim(t, CLOSE_PAREN)) {
-
- --level;
- if(level == 0) {
- TokBuf subBuf = buf.subBuf(startPos, buf.prevPos());
- exprs.add(parseExpression(subBuf, false));
- return exprs;
- }
-
- } else if(allowMulti && (level == 1) && isDelim(t, FUNC_PARAM_SEP)) {
-
- TokBuf subBuf = buf.subBuf(startPos, buf.prevPos());
- exprs.add(parseExpression(subBuf, false));
- startPos = buf.curPos();
- }
- }
-
- throw new ParseException("Missing closing '" + CLOSE_PAREN
- + " " + buf);
- }
-
- private static void parseOperatorExpression(Token t, TokBuf buf) {
-
- // most ops are two argument except that '-' could be negation, "+" could
- // be pos-ation
- if(buf.hasPendingExpr()) {
- parseBinaryOpExpression(t, buf);
- } else if(isEitherOp(t, "-", "+")) {
- parseUnaryOpExpression(t, buf);
- } else {
- throw new ParseException(
- "Missing left expression for binary operator " + t.getValue() +
- " " + buf);
- }
- }
-
- private static void parseBinaryOpExpression(Token firstTok, TokBuf buf) {
- BinaryOp op = getOpType(firstTok, BinaryOp.class);
- Expr leftExpr = buf.takePendingExpr();
- Expr rightExpr = parseExpression(buf, true);
-
- buf.setPendingExpr(new EBinaryOp(op, leftExpr, rightExpr));
- }
-
- private static void parseUnaryOpExpression(Token firstTok, TokBuf buf) {
- UnaryOp op = getOpType(firstTok, UnaryOp.class);
-
- UnaryOp numOp = op.getUnaryNumOp();
- if(numOp != null) {
- // if this operator is immediately preceding a number, it has a higher
- // precedence
- Token nextTok = buf.peekNext();
- if((nextTok != null) && (nextTok.getType() == TokenType.LITERAL) &&
- nextTok.getValueType().isNumeric()) {
- op = numOp;
- }
- }
-
- Expr val = parseExpression(buf, true);
-
- buf.setPendingExpr(new EUnaryOp(op, val));
- }
-
- private static void parseCompOpExpression(Token firstTok, TokBuf buf) {
-
- if(!buf.hasPendingExpr()) {
- if(buf.getExprType() == Type.FIELD_VALIDATOR) {
- // comparison operators for field validators can implicitly use
- // the current field value for the left value
- buf.setPendingExpr(THIS_COL_VALUE);
- } else {
- throw new ParseException(
- "Missing left expression for comparison operator " +
- firstTok.getValue() + " " + buf);
- }
- }
-
- CompOp op = getOpType(firstTok, CompOp.class);
- Expr leftExpr = buf.takePendingExpr();
- Expr rightExpr = parseExpression(buf, true);
-
- buf.setPendingExpr(new ECompOp(op, leftExpr, rightExpr));
- }
-
- private static void parseLogicalOpExpression(Token firstTok, TokBuf buf) {
-
- if(!buf.hasPendingExpr()) {
- throw new ParseException(
- "Missing left expression for logical operator " +
- firstTok.getValue() + " " + buf);
- }
-
- LogOp op = getOpType(firstTok, LogOp.class);
- Expr leftExpr = buf.takePendingExpr();
- Expr rightExpr = parseExpression(buf, true);
-
- buf.setPendingExpr(new ELogicalOp(op, leftExpr, rightExpr));
- }
-
- private static void parseSpecOpExpression(Token firstTok, TokBuf buf) {
-
- SpecOp specOp = getSpecialOperator(firstTok, buf);
-
- if(specOp == SpecOp.NOT) {
- // this is the unary prefix operator
- parseUnaryOpExpression(firstTok, buf);
- return;
- }
-
- if(!buf.hasPendingExpr()) {
- if(buf.getExprType() == Type.FIELD_VALIDATOR) {
- // comparison operators for field validators can implicitly use
- // the current field value for the left value
- buf.setPendingExpr(THIS_COL_VALUE);
- } else {
- throw new ParseException(
- "Missing left expression for comparison operator " +
- specOp + " " + buf);
- }
- }
-
- Expr expr = buf.takePendingExpr();
-
- Expr specOpExpr = null;
- switch(specOp) {
- case IS_NULL:
- case IS_NOT_NULL:
- specOpExpr = new ENullOp(specOp, expr);
- break;
-
- case LIKE:
- case NOT_LIKE:
- Token t = buf.next();
- if((t.getType() != TokenType.LITERAL) ||
- (t.getValueType() != Value.Type.STRING)) {
- throw new ParseException("Missing Like pattern " + buf);
- }
- String patternStr = t.getValueStr();
- specOpExpr = new ELikeOp(specOp, expr, patternStr);
- break;
-
- case BETWEEN:
- case NOT_BETWEEN:
-
- // the "rest" of a between expression is of the form "X And Y". we are
- // going to speculatively parse forward until we find the "And"
- // operator.
- Expr startRangeExpr = null;
- while(true) {
-
- Expr tmpExpr = parseExpression(buf, true);
- Token tmpT = buf.peekNext();
-
- if(tmpT == null) {
- // ran out of expression?
- throw new ParseException(
- "Missing 'And' for 'Between' expression " + buf);
- }
-
- if(isString(tmpT, "and")) {
- buf.next();
- startRangeExpr = tmpExpr;
- break;
- }
-
- // put the pending expression back and try parsing some more
- buf.restorePendingExpr(tmpExpr);
- }
-
- Expr endRangeExpr = parseExpression(buf, true);
-
- specOpExpr = new EBetweenOp(specOp, expr, startRangeExpr, endRangeExpr);
- break;
-
- case IN:
- case NOT_IN:
-
- // there might be a space before open paren
- t = buf.next();
- if(t.getType() == TokenType.SPACE) {
- t = buf.next();
- }
- if(!isDelim(t, OPEN_PAREN)) {
- throw new ParseException("Malformed 'In' expression " + buf);
- }
-
- List<Expr> exprs = findParenExprs(buf, true);
- specOpExpr = new EInOp(specOp, expr, exprs);
- break;
-
- default:
- throw new ParseException("Unexpected special op " + specOp);
- }
-
- buf.setPendingExpr(specOpExpr);
- }
-
- private static SpecOp getSpecialOperator(Token firstTok, TokBuf buf) {
- String opStr = firstTok.getValueStr().toLowerCase();
-
- if("is".equals(opStr)) {
- Token t = buf.peekNext();
- if(isString(t, "null")) {
- buf.next();
- return SpecOp.IS_NULL;
- } else if(isString(t, "not")) {
- buf.next();
- t = buf.peekNext();
- if(isString(t, "null")) {
- buf.next();
- return SpecOp.IS_NOT_NULL;
- }
- }
- } else if("like".equals(opStr)) {
- return SpecOp.LIKE;
- } else if("between".equals(opStr)) {
- return SpecOp.BETWEEN;
- } else if("in".equals(opStr)) {
- return SpecOp.IN;
- } else if("not".equals(opStr)) {
- Token t = buf.peekNext();
- if(isString(t, "between")) {
- buf.next();
- return SpecOp.NOT_BETWEEN;
- } else if(isString(t, "in")) {
- buf.next();
- return SpecOp.NOT_IN;
- } else if(isString(t, "like")) {
- buf.next();
- return SpecOp.NOT_LIKE;
- }
- return SpecOp.NOT;
- }
-
- throw new ParseException(
- "Malformed special operator " + opStr + " " + buf);
- }
-
- private static void parseConstExpression(Token firstTok, TokBuf buf) {
- Expr constExpr = null;
- String tokStr = firstTok.getValueStr().toLowerCase();
- if(TRUE_STRS.contains(tokStr)) {
- constExpr = TRUE_VALUE;
- } else if(FALSE_STRS.contains(tokStr)) {
- constExpr = FALSE_VALUE;
- } else if("null".equals(tokStr)) {
- constExpr = NULL_VALUE;
- } else {
- throw new ParseException("Unexpected CONST word "
- + firstTok.getValue());
- }
- buf.setPendingExpr(constExpr);
- }
-
- private static boolean isObjNameSep(Token t) {
- return (isDelim(t, ".") || isDelim(t, "!"));
- }
-
- private static boolean isOp(Token t, String opStr) {
- return ((t != null) && (t.getType() == TokenType.OP) &&
- opStr.equalsIgnoreCase(t.getValueStr()));
- }
-
- private static boolean isEitherOp(Token t, String opStr1, String opStr2) {
- return ((t != null) && (t.getType() == TokenType.OP) &&
- (opStr1.equalsIgnoreCase(t.getValueStr()) ||
- opStr2.equalsIgnoreCase(t.getValueStr())));
- }
-
- private static boolean isDelim(Token t, String opStr) {
- return ((t != null) && (t.getType() == TokenType.DELIM) &&
- opStr.equalsIgnoreCase(t.getValueStr()));
- }
-
- private static boolean isString(Token t, String opStr) {
- return ((t != null) && (t.getType() == TokenType.STRING) &&
- opStr.equalsIgnoreCase(t.getValueStr()));
- }
-
- private static WordType getWordType(Token t) {
- return WORD_TYPES.get(t.getValueStr().toLowerCase());
- }
-
- private static void setWordType(WordType type, String... words) {
- for(String w : words) {
- WORD_TYPES.put(w, type);
- }
- }
-
- private static <T extends Enum<T>> T getOpType(Token t, Class<T> opClazz) {
- String str = t.getValueStr();
- for(T op : opClazz.getEnumConstants()) {
- if(str.equalsIgnoreCase(op.toString())) {
- return op;
- }
- }
- throw new ParseException("Unexpected op string " + t.getValueStr());
- }
-
- private static final class TokBuf
- {
- private final Type _exprType;
- private final List<Token> _tokens;
- private final TokBuf _parent;
- private final int _parentOff;
- private final ParseContext _context;
- private int _pos;
- private Expr _pendingExpr;
-
- private TokBuf(Type exprType, List<Token> tokens, ParseContext context) {
- this(exprType, tokens, null, 0, context);
- }
-
- private TokBuf(List<Token> tokens, TokBuf parent, int parentOff) {
- this(parent._exprType, tokens, parent, parentOff, parent._context);
- }
-
- private TokBuf(Type exprType, List<Token> tokens, TokBuf parent,
- int parentOff, ParseContext context) {
- _exprType = exprType;
- _tokens = tokens;
- _parent = parent;
- _parentOff = parentOff;
- _context = context;
- }
-
- public Type getExprType() {
- return _exprType;
- }
-
- public int curPos() {
- return _pos;
- }
-
- public int prevPos() {
- return _pos - 1;
- }
-
- public boolean hasNext() {
- return (_pos < _tokens.size());
- }
-
- public Token peekNext() {
- if(!hasNext()) {
- return null;
- }
- return _tokens.get(_pos);
- }
-
- public Token next() {
- if(!hasNext()) {
- throw new ParseException(
- "Unexpected end of expression " + this);
- }
- return _tokens.get(_pos++);
- }
-
- public void reset(int pos) {
- _pos = pos;
- }
-
- public TokBuf subBuf(int start, int end) {
- return new TokBuf(_tokens.subList(start, end), this, start);
- }
-
- public void setPendingExpr(Expr expr) {
- if(_pendingExpr != null) {
- throw new ParseException(
- "Found multiple expressions with no operator " + this);
- }
- _pendingExpr = expr.resolveOrderOfOperations();
- }
-
- public void restorePendingExpr(Expr expr) {
- // this is an expression which was previously set, so no need to re-resolve
- _pendingExpr = expr;
- }
-
- public Expr takePendingExpr() {
- Expr expr = _pendingExpr;
- _pendingExpr = null;
- return expr;
- }
-
- public boolean hasPendingExpr() {
- return (_pendingExpr != null);
- }
-
- private Map.Entry<Integer,List<Token>> getTopPos() {
- int pos = _pos;
- List<Token> toks = _tokens;
- TokBuf cur = this;
- while(cur._parent != null) {
- pos += cur._parentOff;
- cur = cur._parent;
- toks = cur._tokens;
- }
- return ExpressionTokenizer.newEntry(pos, toks);
- }
-
- public Function getFunction(String funcName) {
- return _context.getFunctionLookup().getFunction(funcName);
- }
-
- @Override
- public String toString() {
-
- Map.Entry<Integer,List<Token>> e = getTopPos();
-
- // TODO actually format expression?
- StringBuilder sb = new StringBuilder()
- .append("[token ").append(e.getKey()).append("] (");
-
- for(Iterator<Token> iter = e.getValue().iterator(); iter.hasNext(); ) {
- Token t = iter.next();
- sb.append("'").append(t.getValueStr()).append("'");
- if(iter.hasNext()) {
- sb.append(",");
- }
- }
-
- sb.append(")");
-
- if(_pendingExpr != null) {
- sb.append(" [pending '").append(_pendingExpr.toDebugString())
- .append("']");
- }
-
- return sb.toString();
- }
- }
-
- private static boolean isHigherPrecendence(OpType op1, OpType op2) {
- int prec1 = PRECENDENCE.get(op1);
- int prec2 = PRECENDENCE.get(op2);
-
- // higher preceendence ops have lower numbers
- return (prec1 < prec2);
- }
-
- private static final Map<OpType, Integer> buildPrecedenceMap(
- OpType[]... opArrs) {
- Map<OpType, Integer> prec = new HashMap<OpType, Integer>();
-
- int level = 0;
- for(OpType[] ops : opArrs) {
- for(OpType op : ops) {
- prec.put(op, level);
- }
- ++level;
- }
-
- return prec;
- }
-
- private static void exprListToString(
- List<Expr> exprs, String sep, StringBuilder sb, boolean isDebug) {
- Iterator<Expr> iter = exprs.iterator();
- iter.next().toString(sb, isDebug);
- while(iter.hasNext()) {
- sb.append(sep);
- iter.next().toString(sb, isDebug);
- }
- }
-
- private static Value[] exprListToValues(
- List<Expr> exprs, EvalContext ctx) {
- Value[] paramVals = new Value[exprs.size()];
- for(int i = 0; i < exprs.size(); ++i) {
- paramVals[i] = exprs.get(i).eval(ctx);
- }
- return paramVals;
- }
-
- private static Value[] exprListToDelayedValues(
- List<Expr> exprs, EvalContext ctx) {
- Value[] paramVals = new Value[exprs.size()];
- for(int i = 0; i < exprs.size(); ++i) {
- paramVals[i] = new DelayedValue(exprs.get(i), ctx);
- }
- return paramVals;
- }
-
- private static boolean areConstant(List<Expr> exprs) {
- for(Expr expr : exprs) {
- if(!expr.isConstant()) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean areConstant(Expr... exprs) {
- for(Expr expr : exprs) {
- if(!expr.isConstant()) {
- return false;
- }
- }
- return true;
- }
-
- private static void literalStrToString(String str, StringBuilder sb) {
- sb.append("\"")
- .append(str.replace("\"", "\"\""))
- .append("\"");
- }
-
- private static Pattern likePatternToRegex(String pattern) {
-
- StringBuilder sb = new StringBuilder(pattern.length());
-
- // Access LIKE pattern supports (note, matching is case-insensitive):
- // - '*' -> 0 or more chars
- // - '?' -> single character
- // - '#' -> single digit
- // - '[...]' -> character class, '[!...]' -> not in char class
-
- for(int i = 0; i < pattern.length(); ++i) {
- char c = pattern.charAt(i);
-
- if(c == '*') {
- sb.append(".*");
- } else if(c == '?') {
- sb.append('.');
- } else if(c == '#') {
- sb.append("\\d");
- } else if(c == '[') {
-
- // find closing brace
- int startPos = i + 1;
- int endPos = -1;
- for(int j = startPos; j < pattern.length(); ++j) {
- if(pattern.charAt(j) == ']') {
- endPos = j;
- break;
- }
- }
-
- // access treats invalid expression like "unmatchable"
- if(endPos == -1) {
- return UNMATCHABLE_REGEX;
- }
-
- String charClass = pattern.substring(startPos, endPos);
-
- if((charClass.length() > 0) && (charClass.charAt(0) == '!')) {
- // this is a negated char class
- charClass = '^' + charClass.substring(1);
- }
-
- sb.append('[').append(charClass).append(']');
- i += (endPos - startPos) + 1;
-
- } else if(REGEX_SPEC_CHARS.contains(c)) {
- // this char is special in regexes, so escape it
- sb.append('\\').append(c);
- } else {
- sb.append(c);
- }
- }
-
- try {
- return Pattern.compile(sb.toString(),
- Pattern.CASE_INSENSITIVE | Pattern.DOTALL |
- Pattern.UNICODE_CASE);
- } catch(PatternSyntaxException ignored) {
- return UNMATCHABLE_REGEX;
- }
- }
-
- private static Value toLiteralValue(Value.Type valType, Object value,
- DateFormat sdf)
- {
- switch(valType) {
- case STRING:
- return ValueSupport.toValue((String)value);
- case DATE:
- return new DateValue((Date)value, sdf);
- case TIME:
- return new TimeValue((Date)value, sdf);
- case DATE_TIME:
- return new DateTimeValue((Date)value, sdf);
- case LONG:
- return ValueSupport.toValue((Integer)value);
- case DOUBLE:
- return ValueSupport.toValue((Double)value);
- case BIG_DEC:
- return ValueSupport.toValue((BigDecimal)value);
- default:
- throw new ParseException("unexpected literal type " + valType);
- }
- }
-
- private static boolean isLiteralDefaultValue(
- TokBuf buf, Value.Type resultType, String exprStr) {
-
- // if a default value expression does not start with an '=' and is used in
- // a string context, then it is taken as a literal value unless it starts
- // with a " char
-
- if(buf.getExprType() != Type.DEFAULT_VALUE) {
- return false;
- }
-
- // a leading "=" indicates "full" expression handling for a DEFAULT_VALUE
- // (consume this value once we detect it)
- if(isOp(buf.peekNext(), "=")) {
- buf.next();
- return false;
- }
-
- return((resultType == Value.Type.STRING) &&
- ((exprStr.length() == 0) ||
- (exprStr.charAt(0) != ExpressionTokenizer.QUOTED_STR_CHAR)));
- }
-
- private interface LeftAssocExpr {
- public OpType getOp();
- public Expr getLeft();
- public void setLeft(Expr left);
- }
-
- private interface RightAssocExpr {
- public OpType getOp();
- public Expr getRight();
- public void setRight(Expr right);
- }
-
- private static final class DelayedValue extends BaseDelayedValue
- {
- private final Expr _expr;
- private final EvalContext _ctx;
-
- private DelayedValue(Expr expr, EvalContext ctx) {
- _expr = expr;
- _ctx = ctx;
- }
-
- @Override
- public Value eval() {
- return _expr.eval(_ctx);
- }
- }
-
-
- private static abstract class Expr
- {
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- toString(sb, false);
- return sb.toString();
- }
-
- public String toDebugString() {
- StringBuilder sb = new StringBuilder();
- toString(sb, true);
- return sb.toString();
- }
-
- protected boolean isValidationExpr() {
- return false;
- }
-
- protected void toString(StringBuilder sb, boolean isDebug) {
- if(isDebug) {
- sb.append("<").append(getClass().getSimpleName()).append(">{");
- }
- toExprString(sb, isDebug);
- if(isDebug) {
- sb.append("}");
- }
- }
-
- protected Expr resolveOrderOfOperations() {
-
- if(!(this instanceof LeftAssocExpr)) {
- // nothing we can do
- return this;
- }
-
- // in order to get the precedence right, we need to first associate this
- // expression with the "rightmost" expression preceding it, then adjust
- // this expression "down" (lower precedence) as the precedence of the
- // operations dictates. since we parse from left to right, the initial
- // "left" value isn't the immediate left expression, instead it's based
- // on how the preceding operator precedence worked out. we need to
- // adjust "this" expression to the closest preceding expression before
- // we can correctly resolve precedence.
-
- Expr outerExpr = this;
- final LeftAssocExpr thisExpr = (LeftAssocExpr)this;
- final Expr thisLeft = thisExpr.getLeft();
-
- // current: <this>{<left>{A op1 B} op2 <right>{C}}
- if(thisLeft instanceof RightAssocExpr) {
-
- RightAssocExpr leftOp = (RightAssocExpr)thisLeft;
-
- // target: <left>{A op1 <this>{B op2 <right>{C}}}
-
- thisExpr.setLeft(leftOp.getRight());
-
- // give the new version of this expression an opportunity to further
- // swap (since the swapped expression may itself be a binary
- // expression)
- leftOp.setRight(resolveOrderOfOperations());
- outerExpr = thisLeft;
-
- // at this point, this expression has been pushed all the way to the
- // rightmost preceding expression (we artifically gave "this" the
- // highest precedence). now, we want to adjust precedence as
- // necessary (shift it back down if the operator precedence is
- // incorrect). note, we only need to check precedence against "this",
- // as all other precedence has been resolved in previous parsing
- // rounds.
- if((leftOp.getRight() == this) &&
- !isHigherPrecendence(thisExpr.getOp(), leftOp.getOp())) {
-
- // doh, "this" is lower (or the same) precedence, restore the
- // original order of things
- leftOp.setRight(thisExpr.getLeft());
- thisExpr.setLeft(thisLeft);
- outerExpr = this;
- }
- }
-
- return outerExpr;
- }
-
- public abstract boolean isConstant();
-
- public abstract Value eval(EvalContext ctx);
-
- public abstract void collectIdentifiers(Collection<Identifier> identifiers);
-
- protected abstract void toExprString(StringBuilder sb, boolean isDebug);
- }
-
- private static final class EConstValue extends Expr
- {
- private final Value _val;
- private final String _str;
-
- private EConstValue(Value val, String str) {
- _val = val;
- _str = str;
- }
-
- @Override
- public boolean isConstant() {
- return true;
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return _val;
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- // none
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- sb.append(_str);
- }
- }
-
- private static final class EThisValue extends Expr
- {
- @Override
- public boolean isConstant() {
- return false;
- }
- @Override
- public Value eval(EvalContext ctx) {
- return ctx.getThisColumnValue();
- }
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- // none
- }
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- sb.append("<THIS_COL>");
- }
- }
-
- private static final class ELiteralValue extends Expr
- {
- private final Value _val;
-
- private ELiteralValue(Value.Type valType, Object value,
- DateFormat sdf) {
- _val = toLiteralValue(valType, value, sdf);
- }
-
- @Override
- public boolean isConstant() {
- return true;
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return _val;
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- // none
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- if(_val.getType() == Value.Type.STRING) {
- literalStrToString((String)_val.get(), sb);
- } else if(_val.getType().isTemporal()) {
- sb.append("#").append(_val.getAsString()).append("#");
- } else {
- sb.append(_val.get());
- }
- }
- }
-
- private static final class EObjValue extends Expr
- {
- private final Identifier _identifier;
-
- private EObjValue(Identifier identifier) {
- _identifier = identifier;
- }
-
- @Override
- public boolean isConstant() {
- return false;
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return ctx.getIdentifierValue(_identifier);
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- identifiers.add(_identifier);
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- sb.append(_identifier);
- }
- }
-
- private static class EParen extends Expr
- {
- private final Expr _expr;
-
- private EParen(Expr expr) {
- _expr = expr;
- }
-
- @Override
- public boolean isConstant() {
- return _expr.isConstant();
- }
-
- @Override
- protected boolean isValidationExpr() {
- return _expr.isValidationExpr();
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return _expr.eval(ctx);
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- _expr.collectIdentifiers(identifiers);
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- sb.append("(");
- _expr.toString(sb, isDebug);
- sb.append(")");
- }
- }
-
- private static class EFunc extends Expr
- {
- private final Function _func;
- private final List<Expr> _params;
-
- private EFunc(Function func, List<Expr> params) {
- _func = func;
- _params = params;
- }
-
- @Override
- public boolean isConstant() {
- return _func.isPure() && areConstant(_params);
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return _func.eval(ctx, exprListToValues(_params, ctx));
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- for(Expr param : _params) {
- param.collectIdentifiers(identifiers);
- }
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- sb.append(_func.getName()).append("(");
-
- if(!_params.isEmpty()) {
- exprListToString(_params, ",", sb, isDebug);
- }
-
- sb.append(")");
- }
- }
-
- private static abstract class EBaseBinaryOp extends Expr
- implements LeftAssocExpr, RightAssocExpr
- {
- protected final OpType _op;
- protected Expr _left;
- protected Expr _right;
-
- private EBaseBinaryOp(OpType op, Expr left, Expr right) {
- _op = op;
- _left = left;
- _right = right;
- }
-
- @Override
- public boolean isConstant() {
- return areConstant(_left, _right);
- }
-
- public OpType getOp() {
- return _op;
- }
-
- public Expr getLeft() {
- return _left;
- }
-
- public void setLeft(Expr left) {
- _left = left;
- }
-
- public Expr getRight() {
- return _right;
- }
-
- public void setRight(Expr right) {
- _right = right;
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- _left.collectIdentifiers(identifiers);
- _right.collectIdentifiers(identifiers);
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- _left.toString(sb, isDebug);
- sb.append(" ").append(_op).append(" ");
- _right.toString(sb, isDebug);
- }
- }
-
- private static class EBinaryOp extends EBaseBinaryOp
- {
- private EBinaryOp(BinaryOp op, Expr left, Expr right) {
- super(op, left, right);
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return ((BinaryOp)_op).eval(ctx, _left.eval(ctx), _right.eval(ctx));
- }
- }
-
- private static class EUnaryOp extends Expr
- implements RightAssocExpr
- {
- private final OpType _op;
- private Expr _expr;
-
- private EUnaryOp(UnaryOp op, Expr expr) {
- _op = op;
- _expr = expr;
- }
-
- @Override
- public boolean isConstant() {
- return _expr.isConstant();
- }
-
- public OpType getOp() {
- return _op;
- }
-
- public Expr getRight() {
- return _expr;
- }
-
- public void setRight(Expr right) {
- _expr = right;
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return ((UnaryOp)_op).eval(ctx, _expr.eval(ctx));
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- _expr.collectIdentifiers(identifiers);
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- sb.append(_op);
- if(isDebug || ((UnaryOp)_op).needsSpace()) {
- sb.append(" ");
- }
- _expr.toString(sb, isDebug);
- }
- }
-
- private static class ECompOp extends EBaseBinaryOp
- {
- private ECompOp(CompOp op, Expr left, Expr right) {
- super(op, left, right);
- }
-
- @Override
- protected boolean isValidationExpr() {
- return true;
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return ((CompOp)_op).eval(_left.eval(ctx), _right.eval(ctx));
- }
- }
-
- private static class EImplicitCompOp extends ECompOp
- {
- private EImplicitCompOp(Expr right) {
- super(CompOp.EQ, THIS_COL_VALUE, right);
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- // only output the full "implicit" comparison in debug mode
- if(isDebug) {
- super.toExprString(sb, isDebug);
- } else {
- // just output the explicit part of the expression
- _right.toString(sb, isDebug);
- }
- }
- }
-
- private static class ELogicalOp extends EBaseBinaryOp
- {
- private ELogicalOp(LogOp op, Expr left, Expr right) {
- super(op, left, right);
- }
-
- @Override
- protected boolean isValidationExpr() {
- return true;
- }
-
- @Override
- public Value eval(final EvalContext ctx) {
-
- // logical operations do short circuit evaluation, so we need to delay
- // computing results until necessary
- return ((LogOp)_op).eval(new DelayedValue(_left, ctx),
- new DelayedValue(_right, ctx));
- }
- }
-
- private static abstract class ESpecOp extends Expr
- implements LeftAssocExpr
- {
- protected final SpecOp _op;
- protected Expr _expr;
-
- private ESpecOp(SpecOp op, Expr expr) {
- _op = op;
- _expr = expr;
- }
-
- @Override
- public boolean isConstant() {
- return _expr.isConstant();
- }
-
- public OpType getOp() {
- return _op;
- }
-
- public Expr getLeft() {
- return _expr;
- }
-
- public void setLeft(Expr left) {
- _expr = left;
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- _expr.collectIdentifiers(identifiers);
- }
-
- @Override
- protected boolean isValidationExpr() {
- return true;
- }
- }
-
- private static class ENullOp extends ESpecOp
- {
- private ENullOp(SpecOp op, Expr expr) {
- super(op, expr);
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return _op.eval(_expr.eval(ctx), null, null);
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- _expr.toString(sb, isDebug);
- sb.append(" ").append(_op);
- }
- }
-
- private static class ELikeOp extends ESpecOp
- {
- private final String _patternStr;
- private Pattern _pattern;
-
- private ELikeOp(SpecOp op, Expr expr, String patternStr) {
- super(op, expr);
- _patternStr = patternStr;
- }
-
- private Pattern getPattern()
- {
- if(_pattern == null) {
- _pattern = likePatternToRegex(_patternStr);
- }
- return _pattern;
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return _op.eval(_expr.eval(ctx), getPattern(), null);
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- _expr.toString(sb, isDebug);
- sb.append(" ").append(_op).append(" ");
- literalStrToString(_patternStr, sb);
- if(isDebug) {
- sb.append("(").append(getPattern()).append(")");
- }
- }
- }
-
- private static class EInOp extends ESpecOp
- {
- private final List<Expr> _exprs;
-
- private EInOp(SpecOp op, Expr expr, List<Expr> exprs) {
- super(op, expr);
- _exprs = exprs;
- }
-
- @Override
- public boolean isConstant() {
- return super.isConstant() && areConstant(_exprs);
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return _op.eval(_expr.eval(ctx),
- exprListToDelayedValues(_exprs, ctx), null);
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- for(Expr expr : _exprs) {
- expr.collectIdentifiers(identifiers);
- }
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- _expr.toString(sb, isDebug);
- sb.append(" ").append(_op).append(" (");
- exprListToString(_exprs, ",", sb, isDebug);
- sb.append(")");
- }
- }
-
- private static class EBetweenOp extends ESpecOp
- implements RightAssocExpr
- {
- private final Expr _startRangeExpr;
- private Expr _endRangeExpr;
-
- private EBetweenOp(SpecOp op, Expr expr, Expr startRangeExpr,
- Expr endRangeExpr) {
- super(op, expr);
- _startRangeExpr = startRangeExpr;
- _endRangeExpr = endRangeExpr;
- }
-
- @Override
- public boolean isConstant() {
- return _expr.isConstant() && areConstant(_startRangeExpr, _endRangeExpr);
- }
-
- public Expr getRight() {
- return _endRangeExpr;
- }
-
- public void setRight(Expr right) {
- _endRangeExpr = right;
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return _op.eval(_expr.eval(ctx),
- new DelayedValue(_startRangeExpr, ctx),
- new DelayedValue(_endRangeExpr, ctx));
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- super.collectIdentifiers(identifiers);
- _startRangeExpr.collectIdentifiers(identifiers);
- _endRangeExpr.collectIdentifiers(identifiers);
- }
-
- @Override
- protected void toExprString(StringBuilder sb, boolean isDebug) {
- _expr.toString(sb, isDebug);
- sb.append(" ").append(_op).append(" ");
- _startRangeExpr.toString(sb, isDebug);
- sb.append(" And ");
- _endRangeExpr.toString(sb, isDebug);
- }
- }
-
- /**
- * Base Expression wrapper for an Expr.
- */
- private static abstract class BaseExprWrapper implements Expression
- {
- private final String _rawExprStr;
- private final Expr _expr;
-
- private BaseExprWrapper(String rawExprStr, Expr expr) {
- _rawExprStr = rawExprStr;
- _expr = expr;
- }
-
- public String toDebugString() {
- return _expr.toDebugString();
- }
-
- public String toRawString() {
- return _rawExprStr;
- }
-
- public boolean isConstant() {
- return _expr.isConstant();
- }
-
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- _expr.collectIdentifiers(identifiers);
- }
-
- @Override
- public String toString() {
- return _expr.toString();
- }
-
- protected Object evalValue(Value.Type resultType, EvalContext ctx) {
- Value val = _expr.eval(ctx);
-
- if(val.isNull()) {
- return null;
- }
-
- if(resultType == null) {
- // return as "native" type
- return val.get();
- }
-
- // FIXME possibly do some type coercion. are there conversions here which don't work elsewhere? (string -> date, string -> number)?
- switch(resultType) {
- case STRING:
- return val.getAsString();
- case DATE:
- case TIME:
- case DATE_TIME:
- return val.getAsDateTime(ctx);
- case LONG:
- return val.getAsLongInt();
- case DOUBLE:
- return val.getAsDouble();
- case BIG_DEC:
- return val.getAsBigDecimal();
- default:
- throw new IllegalStateException("unexpected result type " + resultType);
- }
- }
-
- protected Boolean evalCondition(EvalContext ctx) {
- Value val = _expr.eval(ctx);
-
- if(val.isNull()) {
- // null can't be coerced to a boolean
- throw new EvalException("Condition evaluated to Null");
- }
-
- return val.getAsBoolean();
- }
- }
-
- /**
- * Expression wrapper for an Expr which returns a value.
- */
- private static class ExprWrapper extends BaseExprWrapper
- {
- private final Value.Type _resultType;
-
- private ExprWrapper(String rawExprStr, Expr expr, Value.Type resultType) {
- super(rawExprStr, expr);
- _resultType = resultType;
- }
-
- public Object eval(EvalContext ctx) {
- return evalValue(_resultType, ctx);
- }
- }
-
- /**
- * Expression wrapper for an Expr which returns a Boolean from a conditional
- * expression.
- */
- private static class CondExprWrapper extends BaseExprWrapper
- {
- private CondExprWrapper(String rawExprStr, Expr expr) {
- super(rawExprStr, expr);
- }
-
- public Object eval(EvalContext ctx) {
- return evalCondition(ctx);
- }
- }
-
- /**
- * Expression wrapper for a <i>pure</i> Expr which caches the result of
- * evaluation.
- */
- private static final class MemoizedExprWrapper extends ExprWrapper
- {
- private Object _val;
-
- private MemoizedExprWrapper(String rawExprStr, Expr expr,
- Value.Type resultType) {
- super(rawExprStr, expr, resultType);
- }
-
- @Override
- public Object eval(EvalContext ctx) {
- if(_val == null) {
- _val = super.eval(ctx);
- }
- return _val;
- }
- }
-
- /**
- * Expression wrapper for a <i>pure</i> conditional Expr which caches the
- * result of evaluation.
- */
- private static final class MemoizedCondExprWrapper extends CondExprWrapper
- {
- private Object _val;
-
- private MemoizedCondExprWrapper(String rawExprStr, Expr expr) {
- super(rawExprStr, expr);
- }
-
- @Override
- public Object eval(EvalContext ctx) {
- if(_val == null) {
- _val = super.eval(ctx);
- }
- return _val;
- }
- }
- }
|