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