001: /**
002: * MVEL (The MVFLEX Expression Language)
003: *
004: * Copyright (C) 2007 Christopher Brock, MVFLEX/Valhalla Project and the Codehaus
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: */package org.mvel.compiler;
019:
020: import org.mvel.CompileException;
021: import org.mvel.Operator;
022: import org.mvel.ParserContext;
023: import static org.mvel.Soundex.soundex;
024: import org.mvel.ast.ASTNode;
025: import org.mvel.ast.Assignment;
026: import org.mvel.ast.LiteralNode;
027: import org.mvel.ast.Substatement;
028: import org.mvel.util.ASTLinkedList;
029: import static org.mvel.util.CompilerTools.optimizeAST;
030: import org.mvel.util.ExecutionStack;
031: import static org.mvel.util.ParseTools.doOperations;
032: import static org.mvel.util.PropertyTools.isEmpty;
033: import static org.mvel.util.PropertyTools.similarity;
034: import org.mvel.util.Stack;
035: import org.mvel.util.StringAppender;
036:
037: import static java.lang.String.valueOf;
038: import java.util.regex.Pattern;
039:
040: public class ExpressionCompiler extends AbstractParser {
041: private final Stack stk = new ExecutionStack();
042:
043: private Class returnType;
044:
045: private boolean verifying = true;
046: private boolean secondPassOptimization = false;
047:
048: private ParserContext pCtx;
049:
050: public CompiledExpression compile() {
051: return compile(new ParserContext());
052: }
053:
054: public CompiledExpression compile(ParserContext ctx) {
055: if (debugSymbols) {
056: ctx.setDebugSymbols(debugSymbols);
057: } else if (ctx.isDebugSymbols()) {
058: debugSymbols = true;
059: }
060:
061: try {
062: newContext(ctx);
063: return _compile();
064: } finally {
065: removeContext();
066: if (pCtx.isFatalError()) {
067: contextControl(REMOVE, null, null);
068: //noinspection ThrowFromFinallyBlock
069: throw new CompileException("Failed to compile: "
070: + pCtx.getErrorList().size()
071: + " compilation error(s)", pCtx.getErrorList());
072: }
073: }
074: }
075:
076: /**
077: * Initiate an in-context compile. This method should really only be called by the internal API.
078: *
079: * @return compiled expression object
080: */
081: public CompiledExpression _compile() {
082: ASTNode tk;
083: ASTNode tkOp;
084: ASTNode tkOp2;
085: ASTNode tkLA;
086: ASTNode tkLA2;
087:
088: ASTLinkedList astBuild = new ASTLinkedList();
089:
090: boolean firstLA;
091:
092: debugSymbols = (pCtx = getParserContext()).isDebugSymbols();
093:
094: try {
095: if (verifying) {
096: pCtx.initializeTables();
097: }
098:
099: fields |= ASTNode.COMPILE_IMMEDIATE;
100:
101: while ((tk = nextToken()) != null) {
102: /**
103: * If this is a debug symbol, just add it and continue.
104: */
105: if (tk.fields == -1) {
106: astBuild.addTokenNode(tk);
107: continue;
108: }
109:
110: returnType = tk.getEgressType();
111:
112: if (tk instanceof Substatement) {
113: ExpressionCompiler subCompiler = new ExpressionCompiler(
114: tk.getNameAsArray(), pCtx);
115: tk.setAccessor(subCompiler._compile());
116:
117: returnType = subCompiler.getReturnType();
118: }
119:
120: /**
121: * This kludge of code is to handle compile-time literal reduction. We need to avoid
122: * reducing for certain literals like, 'this', ternary and ternary else.
123: */
124: if (tk.isLiteral()) {
125: literalOnly = true;
126:
127: if ((tkOp = nextTokenSkipSymbols()) != null
128: && tkOp.isOperator()
129: && !tkOp.isOperator(Operator.TERNARY)
130: && !tkOp.isOperator(Operator.TERNARY_ELSE)) {
131:
132: /**
133: * If the next token is ALSO a literal, then we have a candidate for a compile-time literal
134: * reduction.
135: */
136: if ((tkLA = nextTokenSkipSymbols()) != null
137: && tkLA.isLiteral()) {
138: stk.push(tk.getLiteralValue(), tkLA
139: .getLiteralValue(), tkOp
140: .getLiteralValue());
141:
142: /**
143: * Reduce the token now.
144: */
145:
146: reduce();
147:
148: firstLA = true;
149:
150: /**
151: * Now we need to check to see if this is a continuing reduction.
152: */
153: while ((tkOp2 = nextTokenSkipSymbols()) != null) {
154: if (isBooleanOperator(tkOp2
155: .getOperator())) {
156: astBuild.addTokenNode(
157: new LiteralNode(stk.pop()),
158: verify(pCtx, tkOp2));
159: break;
160: } else if ((tkLA2 = nextTokenSkipSymbols()) != null
161: && tkLA2.isLiteral()) {
162:
163: stk.push(tkLA2.getLiteralValue(),
164: tkOp2.getLiteralValue());
165:
166: reduce();
167: firstLA = false;
168: literalOnly = false;
169: } else {
170: if (firstLA) {
171: /**
172: * There are more tokens, but we can't reduce anymore. So
173: * we create a reduced token for what we've got.
174: */
175: astBuild
176: .addTokenNode(new LiteralNode(
177: stk.pop()));
178: } else {
179: /**
180: * We have reduced additional tokens, but we can't reduce
181: * anymore.
182: */
183: astBuild.addTokenNode(
184: new LiteralNode(stk
185: .pop()), tkOp);
186:
187: if (tkLA2 != null)
188: astBuild
189: .addTokenNode(tkLA2);
190: }
191: break;
192: }
193: }
194:
195: /**
196: * If there are no more tokens left to parse, we check to see if
197: * we've been doing any reducing, and if so we create the token
198: * now.
199: */
200: if (!stk.isEmpty())
201: astBuild.addTokenNode(new LiteralNode(
202: stk.pop()));
203:
204: continue;
205: } else {
206: astBuild.addTokenNode(verify(pCtx, tk),
207: verify(pCtx, tkOp));
208: if (tkLA != null)
209: astBuild
210: .addTokenNode(verify(pCtx, tkLA));
211: continue;
212: }
213: } else {
214: literalOnly = false;
215: astBuild.addTokenNode(verify(pCtx, tk));
216: if (tkOp != null)
217: astBuild.addTokenNode(verify(pCtx, tkOp));
218:
219: continue;
220: }
221: } else {
222: literalOnly = false;
223: }
224:
225: astBuild.addTokenNode(verify(pCtx, tk));
226: }
227:
228: astBuild.finish();
229:
230: if (verifying) {
231: pCtx.processTables();
232: }
233:
234: if (!stk.isEmpty())
235: throw new CompileException(
236: "COMPILE ERROR: non-empty stack after compile.");
237:
238: return new CompiledExpression(optimizeAST(astBuild,
239: secondPassOptimization),
240: getCurrentSourceFileName(), returnType, pCtx,
241: literalOnly);
242: } catch (Throwable e) {
243: parserContext.set(null);
244: if (e instanceof RuntimeException)
245: throw (RuntimeException) e;
246: else {
247: throw new CompileException(e.getMessage(), e);
248: }
249: }
250:
251: }
252:
253: private static boolean isBooleanOperator(int operator) {
254: return operator == Operator.AND || operator == Operator.OR;
255: }
256:
257: protected ASTNode verify(ParserContext pCtx, ASTNode tk) {
258: if (tk.isOperator()) {
259: if (tk.isOperator(Operator.AND)
260: || tk.isOperator(Operator.OR)) {
261: secondPassOptimization = true;
262: }
263: }
264:
265: if (tk.isDiscard() || tk.isOperator()) {
266: return tk;
267: } else if (tk.isLiteral()) {
268: if ((fields & ASTNode.COMPILE_IMMEDIATE) != 0
269: && tk.getClass() == ASTNode.class) {
270: return new LiteralNode(tk.getLiteralValue());
271: } else {
272: return tk;
273: }
274: }
275:
276: if (verifying) {
277: // if (tk.isAssignment()) {
278: // String varName = ((Assignment) tk).getAssignmentVar();
279: //
280: // if (isReservedWord(varName)) {
281: // addFatalError("invalid assignment - variable name is a reserved keyword: " + varName);
282: // }
283: //
284: // new ExpressionCompiler(new String(((Assignment) tk).getExpression()).trim())._compile();
285: //
286: // if (((Assignment) tk).isNewDeclaration() && pCtx.hasVarOrInput(varName)) {
287: // throw new CompileException("statically-typed variable '" + varName + "' defined more than once in scope: "
288: // + tk.getClass().getName());
289: // }
290: //
291: // pCtx.addVariable(varName, returnType = tk.getEgressType());
292: // }
293: // else
294: if (tk.isIdentifier()) {
295: PropertyVerifier propVerifier = new PropertyVerifier(tk
296: .getNameAsArray(), getParserContext());
297: returnType = propVerifier.analyze();
298:
299: if (propVerifier.isResolvedExternally()) {
300: pCtx.addInput(tk.getAbsoluteName(), returnType);
301: }
302: } else {
303: returnType = tk.getEgressType();
304: }
305: }
306: return tk;
307: }
308:
309: /**
310: * This method is called when we reach the point where we must subEval a trinary operation in the expression.
311: * (ie. val1 op val2). This is not the same as a binary operation, although binary operations would appear
312: * to have 3 structures as well. A binary structure (or also a junction in the expression) compares the
313: * current state against 2 downrange structures (usually an op and a val).
314: */
315: private void reduce() {
316: Object v1 = null, v2 = null;
317: Integer operator;
318: try {
319: while (stk.size() > 1) {
320: operator = (Integer) stk.pop();
321: v1 = stk.pop();
322: v2 = stk.pop();
323:
324: switch (operator) {
325: case Operator.ADD:
326: case Operator.SUB:
327: case Operator.DIV:
328: case Operator.MULT:
329: case Operator.MOD:
330: case Operator.EQUAL:
331: case Operator.NEQUAL:
332: case Operator.GTHAN:
333: case Operator.LTHAN:
334: case Operator.GETHAN:
335: case Operator.LETHAN:
336: case Operator.POWER:
337: stk.push(doOperations(v2, operator, v1));
338: break;
339:
340: case Operator.AND:
341: stk.push(((Boolean) v2) && ((Boolean) v1));
342: break;
343:
344: case Operator.OR:
345: stk.push(((Boolean) v2) || ((Boolean) v1));
346: break;
347:
348: case Operator.CHOR:
349: if (!isEmpty(v2) || !isEmpty(v1)) {
350: stk.clear();
351: stk.push(!isEmpty(v2) ? v2 : v1);
352: return;
353: } else
354: stk.push(null);
355: break;
356:
357: case Operator.REGEX:
358: stk.push(Pattern.compile(valueOf(v1)).matcher(
359: valueOf(v2)).matches());
360: break;
361:
362: case Operator.BW_AND:
363: stk.push(asInt(v2) & asInt(v1));
364: break;
365:
366: case Operator.BW_OR:
367: stk.push(asInt(v2) | asInt(v1));
368: break;
369:
370: case Operator.BW_XOR:
371: stk.push(asInt(v2) ^ asInt(v1));
372: break;
373:
374: case Operator.BW_SHIFT_LEFT:
375: stk.push(asInt(v2) << asInt(v1));
376: break;
377:
378: case Operator.BW_USHIFT_LEFT:
379: int iv2 = asInt(v2);
380: if (iv2 < 0)
381: iv2 *= -1;
382: stk.push(iv2 << asInt(v1));
383: break;
384:
385: case Operator.BW_SHIFT_RIGHT:
386: stk.push(asInt(v2) >> asInt(v1));
387: break;
388:
389: case Operator.BW_USHIFT_RIGHT:
390: stk.push(asInt(v2) >>> asInt(v1));
391: break;
392:
393: case Operator.STR_APPEND:
394: stk.push(new StringAppender(valueOf(v2)).append(
395: valueOf(v1)).toString());
396: break;
397:
398: case Operator.SOUNDEX:
399: stk.push(soundex(valueOf(v1)).equals(
400: soundex(valueOf(v2))));
401: break;
402:
403: case Operator.SIMILARITY:
404: stk.push(similarity(valueOf(v1), valueOf(v2)));
405: break;
406: }
407: }
408: } catch (ClassCastException e) {
409: throw new CompileException(
410: "syntax error or incomptable types (left="
411: + (v1 != null ? v1.getClass().getName()
412: : "null")
413: + ", right="
414: + (v2 != null ? v2.getClass().getName()
415: : "null") + ")", expr, cursor, e);
416:
417: } catch (Exception e) {
418: throw new CompileException(
419: "failed to subEval expression: <<"
420: + new String(expr) + ">>", e);
421: }
422:
423: }
424:
425: private static int asInt(final Object o) {
426: return (Integer) o;
427: }
428:
429: public ExpressionCompiler(String expression) {
430: setExpression(expression);
431: }
432:
433: public ExpressionCompiler(String expression, boolean verifying) {
434: setExpression(expression);
435: this .verifying = verifying;
436: }
437:
438: public ExpressionCompiler(char[] expression) {
439: setExpression(expression);
440: }
441:
442: public ExpressionCompiler(String expression, ParserContext ctx) {
443: setExpression(expression);
444: contextControl(SET, ctx, this );
445: }
446:
447: public ExpressionCompiler(char[] expression, ParserContext ctx) {
448: setExpression(expression);
449: contextControl(SET, ctx, this );
450: }
451:
452: public boolean isVerifying() {
453: return verifying;
454: }
455:
456: public void setVerifying(boolean verifying) {
457: this .verifying = verifying;
458: }
459:
460: public Class getReturnType() {
461: return returnType;
462: }
463:
464: public void setReturnType(Class returnType) {
465: this .returnType = returnType;
466: }
467:
468: public String getExpression() {
469: return new String(expr);
470: }
471:
472: public ParserContext getParserContextState() {
473: return pCtx;
474: }
475:
476: public void removeParserContext() {
477: removeContext();
478: }
479:
480: public boolean isLiteralOnly() {
481: return literalOnly;
482: }
483: }
|