package org.mvel2.util; import org.mvel2.CompileException; import org.mvel2.ParserContext; import org.mvel2.ast.ASTNode; import org.mvel2.ast.EndOfStatement; import org.mvel2.ast.Proto; import org.mvel2.compiler.ExecutableStatement; import java.util.*; import static org.mvel2.util.ParseTools.*; public class ProtoParser { private char[] expr; private ParserContext pCtx; private int endOffset; private int cursor; private String protoName; String tk1 = null; String tk2 = null; private Class type; private String name; private String deferredName; private boolean interpreted = false; private ExecutionStack splitAccumulator; private static ThreadLocal<Queue<DeferredTypeResolve>> deferred = new ThreadLocal<Queue<DeferredTypeResolve>>(); public ProtoParser(char[] expr, int offset, int offsetEnd, String protoName, ParserContext pCtx, int fields, ExecutionStack splitAccumulator) { this.expr = expr; this.cursor = offset; this.endOffset = offsetEnd; this.protoName = protoName; this.pCtx = pCtx; this.interpreted = (ASTNode.COMPILE_IMMEDIATE & fields) == 0; this.splitAccumulator = splitAccumulator; } public Proto parse() { Proto proto = new Proto(protoName, pCtx); Mainloop: while (cursor < endOffset) { cursor = ParseTools.skipWhitespace(expr, cursor); int start = cursor; if (tk2 == null) { while (cursor < endOffset && isIdentifierPart(expr[cursor])) cursor++; if (cursor > start) { tk1 = new String(expr, start, cursor - start); if ("def".equals(tk1) || "function".equals(tk1)) { cursor++; cursor = ParseTools.skipWhitespace(expr, cursor); start = cursor; while (cursor < endOffset && isIdentifierPart(expr[cursor])) cursor++; if (start == cursor) { throw new CompileException("attempt to declare an anonymous function as a prototype member", expr, start); } FunctionParser parser = new FunctionParser(new String(expr, start, cursor - start), cursor, endOffset, expr, 0, pCtx, null); proto.declareReceiver(parser.getName(), parser.parse()); cursor = parser.getCursor() + 1; tk1 = null; continue; } } cursor = ParseTools.skipWhitespace(expr, cursor); } if (cursor > endOffset) { throw new CompileException("unexpected end of statement in proto declaration: " + protoName, expr, start); } switch (expr[cursor]) { case ';': cursor++; calculateDecl(); if (interpreted && type == DeferredTypeResolve.class) { /** * If this type could not be immediately resolved, it may be a look-ahead case, so * we defer resolution of the type until later and place it in the wait queue. */ enqueueReceiverForLateResolution(deferredName, proto.declareReceiver(name, Proto.ReceiverType.DEFERRED, null), null); } else { proto.declareReceiver(name, type, null); } break; case '=': cursor++; cursor = ParseTools.skipWhitespace(expr, cursor); start = cursor; Loop: while (cursor < endOffset) { switch (expr[cursor]) { case '{': case '[': case '(': case '\'': case '"': cursor = balancedCaptureWithLineAccounting(expr, cursor, endOffset, expr[cursor], pCtx); break; case ';': break Loop; } cursor++; } calculateDecl(); String initString = new String(expr, start, cursor++ - start); if (interpreted && type == DeferredTypeResolve.class) { enqueueReceiverForLateResolution(deferredName, proto.declareReceiver(name, Proto.ReceiverType.DEFERRED, null), initString); } else { proto.declareReceiver(name, type, (ExecutableStatement) subCompileExpression(initString, pCtx)); } break; default: start = cursor; while (cursor < endOffset && isIdentifierPart(expr[cursor])) cursor++; if (cursor > start) { tk2 = new String(expr, start, cursor - start); } } } cursor++; /** * Check if the function is manually terminated. */ if (splitAccumulator != null && ParseTools.isStatementNotManuallyTerminated(expr, cursor)) { /** * Add an EndOfStatement to the split accumulator in the parser. */ splitAccumulator.add(new EndOfStatement(pCtx)); } return proto; } private void calculateDecl() { if (tk2 != null) { try { if (pCtx.hasProtoImport(tk1)) { type = Proto.class; } else { type = ParseTools.findClass(null, tk1, pCtx); } name = tk2; } catch (ClassNotFoundException e) { if (interpreted) { type = DeferredTypeResolve.class; deferredName = tk1; name = tk2; } else { throw new CompileException("could not resolve class: " + tk1, expr, cursor, e); } } } else { type = Object.class; name = tk1; } tk1 = null; tk2 = null; } private interface DeferredTypeResolve { public boolean isWaitingFor(Proto proto); public String getName(); } private void enqueueReceiverForLateResolution(final String name, final Proto.Receiver receiver, final String initializer) { Queue<DeferredTypeResolve> recv = deferred.get(); if (recv == null) { deferred.set(recv = new LinkedList<DeferredTypeResolve>()); } recv.add(new DeferredTypeResolve() { public boolean isWaitingFor(Proto proto) { if (name.equals(proto.getName())) { receiver.setType(Proto.ReceiverType.PROPERTY); receiver.setInitValue((ExecutableStatement) subCompileExpression(initializer, pCtx)); return true; } return false; } public String getName() { return name; } }); } public static void notifyForLateResolution(final Proto proto) { if (deferred.get() != null) { Queue<DeferredTypeResolve> recv = deferred.get(); Set<DeferredTypeResolve> remove = new HashSet<DeferredTypeResolve>(); for (DeferredTypeResolve r : recv) { if (r.isWaitingFor(proto)) { remove.add(r); } } for (DeferredTypeResolve r : remove) { recv.remove(r); } } } public int getCursor() { return cursor; } /** * This is such a horrible hack, but it's more performant than any other horrible hack I can think of * right now. * * @param expr * @param cursor * @param pCtx */ public static void checkForPossibleUnresolvedViolations(char[] expr, int cursor, ParserContext pCtx) { if (isUnresolvedWaiting()) { LinkedHashMap<String, Object> imports = (LinkedHashMap<String, Object>) pCtx.getParserConfiguration().getImports(); Object o = imports.values().toArray()[imports.size() - 1]; if (o instanceof Proto) { Proto proto = (Proto) o; int last = proto.getCursorEnd(); cursor--; /** * We walk backwards to ensure that the last valid statement was a proto declaration. */ while (cursor > last && ParseTools.isWhitespace(expr[cursor])) cursor--; while (cursor > last && ParseTools.isIdentifierPart(expr[cursor])) cursor--; while (cursor > last && (ParseTools.isWhitespace(expr[cursor]) || expr[cursor] == ';')) cursor--; if (cursor != last) { throw new CompileException("unresolved reference (possible illegal forward-reference?): " + ProtoParser.getNextUnresolvedWaiting(), expr, proto.getCursorStart()); } } } } public static boolean isUnresolvedWaiting() { return deferred.get() != null && !deferred.get().isEmpty(); } public static String getNextUnresolvedWaiting() { if (deferred.get() != null && !deferred.get().isEmpty()) { return deferred.get().poll().getName(); } return null; } }