1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206 |
- /*
- 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.time.LocalDateTime;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- 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.Value;
- import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token;
- import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType;
- import org.apache.commons.lang3.StringUtils;
-
-
- /**
- *
- * @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 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(ctx, 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(ctx, param1, param2);
- }
- },
- DIV("/") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.divide(ctx, param1, param2);
- }
- },
- INT_DIV("\\") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.intDivide(ctx, param1, param2);
- }
- },
- EXP("^") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.exp(ctx, param1, param2);
- }
- },
- CONCAT("&") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.concat(ctx, param1, param2);
- }
- },
- MOD("Mod") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.mod(ctx, 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(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.lessThan(ctx, param1, param2);
- }
- },
- LTE("<=") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.lessThanEq(ctx, param1, param2);
- }
- },
- GT(">") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.greaterThan(ctx, param1, param2);
- }
- },
- GTE(">=") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.greaterThanEq(ctx, param1, param2);
- }
- },
- EQ("=") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.equals(ctx, param1, param2);
- }
- },
- NE("<>") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.notEquals(ctx, param1, param2);
- }
- };
-
- private final String _str;
-
- private CompOp(String str) {
- _str = str;
- }
-
- @Override
- public String toString() {
- return _str;
- }
-
- public abstract Value eval(EvalContext ctx, Value param1, Value param2);
- }
-
- private enum LogOp implements OpType {
- AND("And") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.and(ctx, param1, param2);
- }
- },
- OR("Or") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.or(ctx, param1, param2);
- }
- },
- EQV("Eqv") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.eqv(ctx, param1, param2);
- }
- },
- XOR("Xor") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.xor(ctx, param1, param2);
- }
- },
- IMP("Imp") {
- @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
- return BuiltinOperators.imp(ctx, param1, param2);
- }
- };
-
- private final String _str;
-
- private LogOp(String str) {
- _str = str;
- }
-
- @Override
- public String toString() {
- return _str;
- }
-
- public abstract Value eval(EvalContext ctx, 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(EvalContext ctx, Value param1, Object param2, Object param3) {
- throw new UnsupportedOperationException();
- }
- },
- IS_NULL("Is Null") {
- @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
- return BuiltinOperators.isNull(param1);
- }
- },
- IS_NOT_NULL("Is Not Null") {
- @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
- return BuiltinOperators.isNotNull(param1);
- }
- },
- LIKE("Like") {
- @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
- return BuiltinOperators.like(ctx, param1, (Pattern)param2);
- }
- },
- NOT_LIKE("Not Like") {
- @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
- return BuiltinOperators.notLike(ctx, param1, (Pattern)param2);
- }
- },
- BETWEEN("Between") {
- @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
- return BuiltinOperators.between(ctx, param1, (Value)param2, (Value)param3);
- }
- },
- NOT_BETWEEN("Not Between") {
- @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
- return BuiltinOperators.notBetween(ctx, param1, (Value)param2, (Value)param3);
- }
- },
- IN("In") {
- @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
- return BuiltinOperators.in(ctx, param1, (Value[])param2);
- }
- },
- NOT_IN("Not In") {
- @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
- return BuiltinOperators.notIn(ctx, param1, (Value[])param2);
- }
- };
-
- private final String _str;
-
- private SpecOp(String str) {
- _str = str;
- }
-
- @Override
- public String toString() {
- return _str;
- }
-
- public abstract Value eval(EvalContext ctx, 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), 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()));
- 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 StringBuilder appendLeadingExpr(
- Expr expr, LocaleContext ctx, StringBuilder sb, boolean isDebug)
- {
- int len = sb.length();
- expr.toString(ctx, sb, isDebug);
- if(sb.length() > len) {
- // only add space if the leading expr added some text
- sb.append(" ");
- }
- return sb;
- }
-
- 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 _ctx;
- 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._ctx);
- }
-
- private TokBuf(Type exprType, List<Token> tokens, TokBuf parent,
- int parentOff, ParseContext context) {
- _exprType = exprType;
- _tokens = tokens;
- _parent = parent;
- _parentOff = parentOff;
- _ctx = 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 _ctx.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(_ctx))
- .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, LocaleContext ctx, StringBuilder sb,
- boolean isDebug) {
- Iterator<Expr> iter = exprs.iterator();
- iter.next().toString(ctx, sb, isDebug);
- while(iter.hasNext()) {
- sb.append(sep);
- iter.next().toString(ctx, 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(StringUtils.replace(str, "\"", "\"\""))
- .append("\"");
- }
-
- /**
- * Converts an ms access like pattern to a java regex, always matching case
- * insensitively.
- */
- public 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(isRegexSpecialChar(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;
- }
- }
-
- public static boolean isRegexSpecialChar(char c) {
- return REGEX_SPEC_CHARS.contains(c);
- }
-
- private static Value toLiteralValue(Value.Type valType, Object value) {
- switch(valType) {
- case STRING:
- return ValueSupport.toValue((String)value);
- case DATE:
- case TIME:
- case DATE_TIME:
- return ValueSupport.toValue(valType, (LocalDateTime)value);
- 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
- {
- public String toCleanString(LocaleContext ctx) {
- return toString(ctx, new StringBuilder(), false).toString();
- }
-
- public String toDebugString(LocaleContext ctx) {
- return toString(ctx, new StringBuilder(), true).toString();
- }
-
- protected boolean isValidationExpr() {
- return false;
- }
-
- protected StringBuilder toString(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- if(isDebug) {
- sb.append("<").append(getClass().getSimpleName()).append(">{");
- }
- toExprString(ctx, sb, isDebug);
- if(isDebug) {
- sb.append("}");
- }
- return sb;
- }
-
- 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(
- LocaleContext ctx, 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(
- LocaleContext ctx, 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(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- if(isDebug) {
- sb.append("<THIS_COL>");
- }
- }
- }
-
- private static final class ELiteralValue extends Expr
- {
- private final Value _val;
-
- private ELiteralValue(Value.Type valType, Object value) {
- _val = toLiteralValue(valType, value);
- }
-
- @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(
- LocaleContext ctx, 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(ctx)).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(
- LocaleContext ctx, 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(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- sb.append("(");
- _expr.toString(ctx, 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(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- sb.append(_func.getName()).append("(");
-
- if(!_params.isEmpty()) {
- exprListToString(_params, ",", ctx, 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);
- }
-
- @Override
- public OpType getOp() {
- return _op;
- }
-
- @Override
- public Expr getLeft() {
- return _left;
- }
-
- @Override
- public void setLeft(Expr left) {
- _left = left;
- }
-
- @Override
- public Expr getRight() {
- return _right;
- }
-
- @Override
- public void setRight(Expr right) {
- _right = right;
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- _left.collectIdentifiers(identifiers);
- _right.collectIdentifiers(identifiers);
- }
-
- @Override
- protected void toExprString(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- appendLeadingExpr(_left, ctx, sb, isDebug)
- .append(_op).append(" ");
- _right.toString(ctx, 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();
- }
-
- @Override
- public OpType getOp() {
- return _op;
- }
-
- @Override
- public Expr getRight() {
- return _expr;
- }
-
- @Override
- 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(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- sb.append(_op);
- if(isDebug || ((UnaryOp)_op).needsSpace()) {
- sb.append(" ");
- }
- _expr.toString(ctx, 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(ctx, _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(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- // only output the full "implicit" comparison in debug mode
- if(isDebug) {
- super.toExprString(ctx, sb, isDebug);
- } else {
- // just output the explicit part of the expression
- _right.toString(ctx, 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(ctx, 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();
- }
-
- @Override
- public OpType getOp() {
- return _op;
- }
-
- @Override
- public Expr getLeft() {
- return _expr;
- }
-
- @Override
- 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(ctx, _expr.eval(ctx), null, null);
- }
-
- @Override
- protected void toExprString(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- appendLeadingExpr(_expr, ctx, sb, isDebug)
- .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(ctx, _expr.eval(ctx), getPattern(), null);
- }
-
- @Override
- protected void toExprString(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- appendLeadingExpr(_expr, ctx, sb, isDebug)
- .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(ctx, _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(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- appendLeadingExpr(_expr, ctx, sb, isDebug)
- .append(_op).append(" (");
- exprListToString(_exprs, ",", ctx, 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);
- }
-
- @Override
- public Expr getRight() {
- return _endRangeExpr;
- }
-
- @Override
- public void setRight(Expr right) {
- _endRangeExpr = right;
- }
-
- @Override
- public Value eval(EvalContext ctx) {
- return _op.eval(ctx, _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(
- LocaleContext ctx, StringBuilder sb, boolean isDebug) {
- appendLeadingExpr(_expr, ctx, sb, isDebug)
- .append(_op).append(" ");
- _startRangeExpr.toString(ctx, sb, isDebug);
- sb.append(" And ");
- _endRangeExpr.toString(ctx, 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;
- }
-
- @Override
- public String toDebugString(LocaleContext ctx) {
- return _expr.toDebugString(ctx);
- }
-
- @Override
- public String toRawString() {
- return _rawExprStr;
- }
-
- @Override
- public String toCleanString(LocaleContext ctx) {
- return _expr.toCleanString(ctx);
- }
-
- @Override
- public boolean isConstant() {
- return _expr.isConstant();
- }
-
- @Override
- public void collectIdentifiers(Collection<Identifier> identifiers) {
- _expr.collectIdentifiers(identifiers);
- }
-
- @Override
- public String toString() {
- return toRawString();
- }
-
- 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(ctx);
- case DATE:
- case TIME:
- case DATE_TIME:
- return val.getAsLocalDateTime(ctx);
- case LONG:
- return val.getAsLongInt(ctx);
- case DOUBLE:
- return val.getAsDouble(ctx);
- case BIG_DEC:
- return val.getAsBigDecimal(ctx);
- 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(ctx);
- }
- }
-
- /**
- * 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;
- }
-
- @Override
- 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);
- }
-
- @Override
- 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;
- }
- }
- }
|