package com.applang; import java.io.Reader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Stack; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.velocity.Template; import org.apache.velocity.app.Velocity; import org.apache.velocity.context.AbstractContext; import org.apache.velocity.context.Context; import org.apache.velocity.context.InternalContextAdapter; import org.apache.velocity.exception.ExtendedParseException; import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.runtime.RuntimeSingleton; import org.apache.velocity.runtime.directive.Directive; import org.apache.velocity.runtime.directive.MacroParseException; import org.apache.velocity.runtime.parser.ParseException; import org.apache.velocity.runtime.parser.Token; import org.apache.velocity.runtime.parser.node.ASTAddNode; import org.apache.velocity.runtime.parser.node.ASTAndNode; import org.apache.velocity.runtime.parser.node.ASTAssignment; import org.apache.velocity.runtime.parser.node.ASTBlock; import org.apache.velocity.runtime.parser.node.ASTComment; import org.apache.velocity.runtime.parser.node.ASTDirective; import org.apache.velocity.runtime.parser.node.ASTDivNode; import org.apache.velocity.runtime.parser.node.ASTEQNode; import org.apache.velocity.runtime.parser.node.ASTElseIfStatement; import org.apache.velocity.runtime.parser.node.ASTElseStatement; import org.apache.velocity.runtime.parser.node.ASTEscape; import org.apache.velocity.runtime.parser.node.ASTEscapedDirective; import org.apache.velocity.runtime.parser.node.ASTExpression; import org.apache.velocity.runtime.parser.node.ASTFalse; import org.apache.velocity.runtime.parser.node.ASTFloatingPointLiteral; import org.apache.velocity.runtime.parser.node.ASTGENode; import org.apache.velocity.runtime.parser.node.ASTGTNode; import org.apache.velocity.runtime.parser.node.ASTIdentifier; import org.apache.velocity.runtime.parser.node.ASTIfStatement; import org.apache.velocity.runtime.parser.node.ASTIntegerLiteral; import org.apache.velocity.runtime.parser.node.ASTIntegerRange; import org.apache.velocity.runtime.parser.node.ASTLENode; import org.apache.velocity.runtime.parser.node.ASTLTNode; import org.apache.velocity.runtime.parser.node.ASTMap; import org.apache.velocity.runtime.parser.node.ASTMethod; import org.apache.velocity.runtime.parser.node.ASTModNode; import org.apache.velocity.runtime.parser.node.ASTMulNode; import org.apache.velocity.runtime.parser.node.ASTNENode; import org.apache.velocity.runtime.parser.node.ASTNotNode; import org.apache.velocity.runtime.parser.node.ASTObjectArray; import org.apache.velocity.runtime.parser.node.ASTOrNode; import org.apache.velocity.runtime.parser.node.ASTReference; import org.apache.velocity.runtime.parser.node.ASTSetDirective; import org.apache.velocity.runtime.parser.node.ASTStringLiteral; import org.apache.velocity.runtime.parser.node.ASTSubtractNode; import org.apache.velocity.runtime.parser.node.ASTText; import org.apache.velocity.runtime.parser.node.ASTTrue; import org.apache.velocity.runtime.parser.node.ASTWord; import org.apache.velocity.runtime.parser.node.ASTprocess; import org.apache.velocity.runtime.parser.node.Node; import org.apache.velocity.runtime.parser.node.SimpleNode; import org.apache.velocity.runtime.visitor.BaseVisitor; import android.util.Log; import static com.applang.Util.*; public class VelocityUtil { public static final Character VRI = '$'; // Reference Indicator public static final Character VDI = '#'; // Directive Indicator public static final Character ARGUMENT_TYPER = '_'; public static final String[] ARGUMENT_SEPARATORS = {", ", " "}; public static final Pattern SUFFIX_PATTERN = Pattern.compile("\\d*$"); public static int argumentTyperIndex(String arg) { int offs = 0; if (arg.startsWith(String.valueOf(ARGUMENT_TYPER))) { arg = strip(Constraint.START, arg, ARGUMENT_TYPER); offs = 1; } return offs + arg.indexOf(ARGUMENT_TYPER); } public static String argumentName(String arg) { int index = argumentTyperIndex(arg); if (index < 0) return strip(Constraint.START, arg, ARGUMENT_TYPER, VRI); else return strip(Constraint.START, arg.substring(0, index), ARGUMENT_TYPER, VRI); } public static MatchResult argumentSuffix(String arg) { int index = argumentTyperIndex(arg); return findFirstIn(arg.substring(0, index), SUFFIX_PATTERN); } public static boolean isOptionalArgument(String arg) { return arg.startsWith(ARGUMENT_TYPER + ""); } public static String optionalize(String arg) { return isOptionalArgument(arg) ? arg : String.valueOf(ARGUMENT_TYPER).concat(arg); } public static String argumentType(String arg) { int index = argumentTyperIndex(arg); if (index < 0) return ""; else return arg.substring(index + 1); } public static abstract class CustomDirective extends Directive { protected static final String TAG = CustomDirective.class.getSimpleName(); protected static final org.apache.velocity.runtime.log.Log log = new org.apache.velocity.runtime.log.Log(); protected String[] arguments = null; public String signature(boolean full) { return VDI + getName() + argumentListString(full ? arguments : strings()) + (BLOCK == getType() ? BLOCK_END : ""); } public String argumentListString(String... args) { return "(" + join(ARGUMENT_SEPARATORS[0], args) + ")"; } protected String argumentErrMsg(String name) { return signature(false) + " error : " + enclose("'", name, "' "); } /** * Returns the indexed argument as a required value. */ protected Object getRequiredValue(Node node, int idx, String argumentName, InternalContextAdapter context) throws MethodInvocationException { if (!requireArgument(node, idx, argumentName)) { return null; } Object obj = node.jjtGetChild(idx).value(context); if (obj == null) { rsvc.getLog().error(argumentErrMsg(argumentName) + "value of argument cannot be null"); return null; } return obj; } /** * Returns the indexed argument as a required literal. */ protected String getRequiredLiteral(Node node, int idx, String argumentName) { if (!requireArgument(node, idx, argumentName)) { return null; } return node.jjtGetChild(idx).literal(); } /** * Returns the index argument as a required variable name. */ protected String getRequiredVariable(Node node, int idx, String argumentName) { String var = getRequiredLiteral(node, idx, argumentName); return var == null ? null : var.substring(1); } /** * Returns the indexed argument as an optional boolean. */ protected boolean getOptionalBoolean(Node node, int idx, InternalContextAdapter ctx) throws MethodInvocationException { return getOptionalBoolean(node, idx, ctx, false); } /** * Returns the indexed argument as an optional boolean. */ protected boolean getOptionalBoolean(Node node, int idx, InternalContextAdapter ctx, boolean defaultValue) throws MethodInvocationException { Object obj = getOptionalValue(node, idx, ctx); if (obj == null || !(obj instanceof Boolean)) return defaultValue; return ((Boolean) obj).booleanValue(); } /** * Returns the indexed argument as an optional string. */ protected String getOptionalString(Node node, int idx, InternalContextAdapter ctx, String defaultValue) throws MethodInvocationException { Object obj = getOptionalValue(node, idx, ctx); if (obj == null || !(obj instanceof String)) return defaultValue; return (String) obj; } /** * Returns an optional argument as a value. */ protected Object getOptionalValue(Node node, int idx, InternalContextAdapter context) throws MethodInvocationException { Node target = getOptionalNode(node, idx); if (target == null) { return null; } return target.value(context); } /** * Returns an optional node. */ protected Node getOptionalNode(Node parent, int idx) { if (hasArgument(parent, idx)) { return parent.jjtGetChild(idx); } return null; } /** * Validates that a required argument is available. */ protected boolean requireArgument(Node node, int idx, String argName) { if (!hasArgument(node, idx)) { rsvc.getLog().error(argumentErrMsg(argName) + "argument required"); return false; } return true; } /** * Returns <code>true</code> if the given specified argument exists. */ protected boolean hasArgument(Node node, int idx) { return idx < node.jjtGetNumChildren(); } /** * Returns the block child node. */ protected Node getBlockNode(Node parent) { for (int i=0; i<parent.jjtGetNumChildren(); i++) { if (parent.jjtGetChild(i) instanceof ASTBlock) return parent.jjtGetChild(i); } return null; } } public static final int CONTENT = 0; public static final int DIRECTIVE = 1; public static final int REFERENCE = 2; public static final int EXPRESSION = 3; public static final int IDENTIFIER = 4; public static final int METHOD = 0; public static class Visitor extends BaseVisitor { protected static final String TAG = CustomContext.class.getSimpleName(); protected static final org.apache.velocity.runtime.log.Log log = new org.apache.velocity.runtime.log.Log(); public static Stack<Object[]> lostAndFound = new Stack<Object[]>(); public static void visitLostAndFound(Job<Object[]> checkout, Object[] params, int...lc) { for (int i = lostAndFound.size() - 1; i > -1; i--) { Object[] objects = lostAndFound.get(i); Token t = (Token) objects[2]; if (compareLC(t, lc) > 0) { try { checkout.perform(objects, params); lostAndFound.remove(objects); } catch (Exception e) { log.error(TAG, e); } } } } public static Predicate<Node> isEssential = new Predicate<Node>() { @Override public boolean apply(Node node) { int group = nodeGroup(node); if (group == DIRECTIVE) { Token lastToken = node.getLastToken(); if (isEndToken(lastToken)) { lostAndFound.push(new Object[] { indent, node, lastToken}); } return true; } int groupParent = nodeGroup(node.jjtGetParent()); if (group == IDENTIFIER) { if (groupParent == METHOD) return false; else return true; } else { if (groupParent == EXPRESSION) return false; } if (group == REFERENCE) return true; else if (group == EXPRESSION) return true; else return false; } }; public static int nodeGroup(Object node) { if (node == null) return -1; String name = node.getClass().getSimpleName(); int ast = name.indexOf("AST"); if (ast > -1) name = name.substring(ast + 3); if ("ObjectArray".equals(name) || "Map".equals(name)) return EXPRESSION; if (name.endsWith("Literal")) return EXPRESSION; if (name.endsWith("Range")) return EXPRESSION; if (name.endsWith("Expression")) return EXPRESSION; if (name.endsWith("True")) return EXPRESSION; if (name.endsWith("False")) return EXPRESSION; if (name.endsWith("Text")) return EXPRESSION; if (name.endsWith("EQNode")) return EXPRESSION; if (name.endsWith("NENode")) return EXPRESSION; if (name.endsWith("GENode")) return EXPRESSION; if (name.endsWith("GTNode")) return EXPRESSION; if (name.endsWith("LENode")) return EXPRESSION; if (name.endsWith("LTNode")) return EXPRESSION; if (name.endsWith("Directive")) return DIRECTIVE; if (name.endsWith("Statement")) return DIRECTIVE; if (name.endsWith("Reference")) return REFERENCE; if (name.endsWith("Method")) return METHOD; if (name.endsWith("Identifier")) return IDENTIFIER; return CONTENT; }; public static String nodeCategory(Object node) { switch (nodeGroup(node)) { case -1: return ""; case DIRECTIVE: return "Directive"; case REFERENCE: return "Reference"; case METHOD: return "Method"; case EXPRESSION: return "Expression"; case IDENTIFIER: return "Identifier"; default: return "Content"; } } public static int blockDepth(Node node) { int blocks = 0; if (node != null) { while ((node = node.jjtGetParent()) != null) { if (node instanceof ASTBlock) blocks++; } } return blocks; } public static int partOfIfStatement(Object node) { if (node instanceof ASTIfStatement) return 1; else if (node instanceof ASTElseIfStatement) return 2; else if (node instanceof ASTElseStatement) return 3; else return 0; } public static boolean isProcessNode(Object node) { return node instanceof ASTprocess; } public static boolean isMethodNode(Object node) { return node instanceof ASTReference && ((Node)node).jjtGetNumChildren() > 0 && ((Node)node).jjtGetChild(0) instanceof ASTMethod; } public static String nodeInfo(Node node, Object...params) { Token first = node.getFirstToken(); int detail = param(0, 0, params); if (detail > 0) { String tokens = ""; tokens = formatLC(beginLC(node)) + " - " + formatLC(endLC(node)); tokens += TAB + enclose("'", totalImage(first)); return node.getClass().getSimpleName() + TAB + tokens; } else { int group = nodeGroup(node); switch (group) { case EXPRESSION: Token last = node.getLastToken(); StringBuilder sb = new StringBuilder(); for (Token t = first; t != null; ) { if (t.next != null) { sb.append(t.image); if (t == last) break; } t = t.next; } return sb.toString(); case DIRECTIVE: return VDI + firstIdentifierFrom(first.image); case REFERENCE: if (isMethodNode(node)) return VRI + firstMethodFrom(tokens(node)); } return first.image; } } public static String totalImage(Token t) { String special = ""; if (t.specialToken != null && ! t.specialToken.image.startsWith("##")) special = t.specialToken.image; return special + t.image; } public static int[] span(Object nodeOrToken) { if (nodeOrToken instanceof Node) { Node node = (Node) nodeOrToken; Token first = node.getFirstToken(); Token last = node.getLastToken(); return ints( first.beginLine, first.beginColumn, last.endLine, last.endColumn ); } else if (nodeOrToken instanceof Token) { Token t = (Token) nodeOrToken; return ints( t.beginLine, t.beginColumn, t.endLine, t.endColumn ); } else return null; } public static boolean isEndToken(Token t) { Matcher matcher = END_PATTERN.matcher(t.image); return matcher.find(); } public static String tokens(Node node, Object...params) { String tokens = ""; if (node != null) { char stumble = param(Character.valueOf((char) 0), 0, params); Token last = node.getLastToken(); for (Token t = node.getFirstToken(); t != null; t = t.next) { if (t.next != null) { String image = t.image; int limit = image.indexOf(stumble); if (limit < 0 || tokens.length() < 1) tokens += image; else { tokens += image.substring(0, limit); break; } if (t == last) break; } } } return tokens; } public static String tail(Node node) { StringBuilder sb = new StringBuilder(); Token t = node.getFirstToken(); while (t != null) { if (t.next != null) sb.append(t.image); t = t.next; } return sb.toString(); } public static ValList getEssentials(Node parent) { ValList list = vlist(); for (int i = 0; i < parent.jjtGetNumChildren(); i++) { Node child = parent.jjtGetChild(i); if (isEssential.apply(child)) list.add(tokens(child)); } return list; } public static void putEssentials(Node parent, ValList list) { for (int i = 0, j = 0; i < parent.jjtGetNumChildren() && j < list.size(); i++) { Node child = parent.jjtGetChild(i); if (isEssential.apply(child)) { String value = list.get(j).toString(); update(child, value); j++; } } } public static int[] getBeginToken(Token t) { return ints(t.beginLine, t.beginColumn); } public static int[] getEndToken(Token t) { return ints(t.endLine, t.endColumn); } public static void setBeginToken(Token t, int...lc) { t.beginLine = lc[0]; t.beginColumn = lc[1]; } public static void setEndToken(Token t, int...lc) { t.endLine = lc[0]; t.endColumn = lc[1]; } public static void changeToken(Token t, String image, int...begin) { setBeginToken(t, begin); MatchResult[] m = findAllIn(image, LINE_BREAK_PATTERN); int lines = m.length; int len = image.length(); setEndToken(t, begin[0] + lines, lines < 1 ? begin[1] + len - 1 : len - m[m.length - 1].end()); t.image = image; t.specialToken = null; } private static int[] diffLC(int[] minuend, int[] subtrahend) { return ints( minuend[0] - subtrahend[0], minuend[1] - subtrahend[1], minuend[0]); } private static void shiftLC(Token t, int...diff) { int line = diff[2]; if (t.beginLine == line) t.beginColumn -= diff[1]; if (t.endLine == line) t.endColumn -= diff[1]; t.beginLine -= diff[0]; t.endLine -= diff[0]; } public static String formatLC(int...lc) { return "L" + lc[0] + "C" + lc[1]; } public static int compareLC(Token t, int...lc) { int cmp; switch (cmp = lc[0] - t.beginLine) { case 0: cmp = lc[1] - t.beginColumn; return cmp < 0 ? -1 : 1; default: return cmp < 0 ? -1 : 1; } } public static int[] beginLC(Node node) { return getBeginToken(node.getFirstToken()); } public static int[] endLC(Node node) { return getEndToken(node.getLastToken()); } public static void update(Node node, Object value) { Token first = node.getFirstToken(); int[] beginFirst = getBeginToken(first); Token last = node.getLastToken(); int[] endLast = getEndToken(last); changeToken(first, value.toString(), beginFirst); int[] endFirst = getEndToken(first); int[] diff = diffLC(endLast, endFirst); boolean erase = last != first; if (erase || diff[0] != 0 || diff[1] != 0) { int[] beginNext = endFirst; beginNext[1] += 1; for (Token t = first.next; t != null; t = t.next) { if (erase) changeToken(t, "", beginNext); else shiftLC(t, diff); if (t == last) erase = false; } } } private static class ThrowableNode extends RuntimeException { public Node node; public ThrowableNode(Node node) { this.node = node; } } public static Node find(SimpleNode document, int[] startLC, final String regex) { try { Object[] params = new Object[] {startLC, Pattern.compile(regex)}; walk(document, new Function<Object>() { public Object apply(Object...params) { Node node = param(null, 0, params); Object[] data = param(null, 2, params); int[] startLC = (int[]) data[0]; if (!isProcessNode(node) && compareLC(node.getFirstToken(), startLC) < 0) { Pattern pattern = (Pattern) data[1]; String tokens = tokens(node); if (pattern.matcher(tokens).find()) throw new ThrowableNode(node); } return data; } }, params); return null; } catch (ThrowableNode found) { return found.node; } } public static Object walk(SimpleNode document, Function<Object> func, Object...params) { lostAndFound.clear(); Visitor visitor = new Visitor(func); return document.jjtAccept(visitor, params); } Function<Object> func = null; public Visitor(Function<Object> func) { super(); this.func = func; } private static int indent = 0; private Object selectNode(Node node, Object data) { if (func != null) data = func.apply(node, indent, data); ++indent; data = node.childrenAccept(this, data); --indent; return data; } @Override public Object visit(SimpleNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTprocess node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTExpression node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTAssignment node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTOrNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTAndNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTEQNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTNENode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTLTNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTGTNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTLENode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTGENode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTAddNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTSubtractNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTMulNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTDivNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTModNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTNotNode node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTIntegerLiteral node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTFloatingPointLiteral node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTStringLiteral node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTIdentifier node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTMethod node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTReference node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTTrue node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTFalse node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTBlock node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTText node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTIfStatement node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTElseStatement node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTElseIfStatement node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTComment node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTObjectArray node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTWord node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTSetDirective node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTDirective node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTEscapedDirective node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTEscape node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTMap node, Object data) { return selectNode(node, data); } @Override public Object visit(ASTIntegerRange node, Object data) { return selectNode(node, data); } public static boolean checkLC(Token t, int[] lc, boolean begin) { int[] tlc = begin ? getBeginToken(t) : getEndToken(t); if (tlc[0] == lc[0] && tlc[1] == lc[1]) return true; Log.d(TAG, String.format("%s : %sLC expected %s, but was %s", t.image, begin ? "begin" : "end", Arrays.toString(lc), Arrays.toString(tlc))); return false; } } public static boolean checkTokenConsistency(SimpleNode document) { boolean retval = true; int[] lc = ints(1,1); Token first = document.getFirstToken(); Token last = document.getLastToken(); for (Token t = first; t != null; t = t.next) { if (t == last) break; String image = t.image; retval &= Visitor.checkLC(t, lc, true); t.beginLine = lc[0]; t.beginColumn = lc[1]; int index; while ((index = image.substring(0, image.length() - 1).indexOf(NEWLINE)) > -1) { lc[0]++; lc[1] = 1; image = image.substring(index + 1); } int length = image.length(); lc[1] = length == t.image.length() ? lc[1] + length - 1 : length; retval &= Visitor.checkLC(t, lc, false); t.endLine = lc[0]; t.endColumn = lc[1]; if (image.endsWith(NEWLINE)) { lc[0]++; lc[1] = 1; } else lc[1]++; } return retval; } public static SimpleNode parse(Reader reader, String templateName) { try { problem = null; SimpleNode doc = RuntimeSingleton.parse(reader, templateName); if (!checkTokenConsistency(doc)) { Log.d(TAG, templateName + " : token inconsistencies encountered"); } return doc; } catch (ParseException e) { problem = e; return null; } } private static ParseException problem = null; public static boolean problem() { return problem != null; } public static String getMessage() { return problem.getMessage(); } public static int[] getProblemCoordinates() { if (problem instanceof ExtendedParseException) { ExtendedParseException epe = (ExtendedParseException) problem; return ints(epe.getLineNumber(), epe.getColumnNumber()); } else { Token tok = problem.currentToken; return ints(tok.endLine, tok.endColumn); } } public static int[] getTextOffsets(String text, int[] lineColumns) { MatchResult[] mr = findAllIn(text, LINE_BREAK_PATTERN); int[] offsets = new int[lineColumns.length / 2]; for (int i = 0; i < lineColumns.length - 1; i+=2) { int line = lineColumns[i]; int column = lineColumns[i+1]; int offset = column - 1; if (line > 1) { if (line - 1 <= mr.length) offset += mr[line - 2].end(); } if (i > 0) offset++; offsets[i/2] = offset; } return offsets; } private static Pattern ARGUMENT_EXTRACTOR = Pattern.compile("arg \\#(\\d+)"); public static Integer getProblemArgumentInfo() { if (problem instanceof MacroParseException) { MatchResult m = findFirstIn(problem.getMessage(), ARGUMENT_EXTRACTOR); if (m != null) return Integer.parseInt(m.group(1)); } return null; } private static Pattern MACRO_EXTRACTOR = Pattern.compile("\\#([^\\d\\s]+)"); public static String getProblemMacroInfo() { if (problem instanceof MacroParseException) { MatchResult m = findFirstIn(problem.getMessage(), MACRO_EXTRACTOR); if (m != null) return m.group(1); } return ""; } public static ArrayList<String> getSuggestionsFromProblem() { ArrayList<String> list = alist(); if (problem != null) { int[][] expTokSeq = problem.expectedTokenSequences; if (expTokSeq != null) { for (int i = 0; i < expTokSeq.length; i++) { String expected = ""; for (int j = 0; j < expTokSeq[i].length; j++) { expected += problem.tokenImage[expTokSeq[i][j]] + " "; } if (expTokSeq[i][expTokSeq[i].length - 1] != 0) { expected += UNKNOWN; } list.add(expected); } } } return list; } public static String evaluate(Context context, String template, String logTag) { StringWriter w = new StringWriter(); Velocity.evaluate( context, w, logTag, template ); return w.toString(); } public static String merge(Context context, Template template) { StringWriter w = new StringWriter(); template.merge( context, w ); return w.toString(); } public static class CustomContext extends AbstractContext { protected static final String TAG = CustomContext.class.getSimpleName(); protected static final org.apache.velocity.runtime.log.Log log = new org.apache.velocity.runtime.log.Log(); protected ValMap map; public CustomContext(ValMap map) { this.map = map; } @Override public Object internalGet(String key) { return map == null ? null : map.get(key); } @Override public Object internalPut(String key, Object value) { return map == null ? null : map.put(key, value); } @Override public boolean internalContainsKey(Object key) { return map == null ? false : map.containsKey(key); } @Override public Object[] internalGetKeys() { return map == null ? new Object[0] : map.keySet().toArray(); } @Override public Object internalRemove(Object key) { return map == null ? null : map.remove(key); } } @SuppressWarnings("rawtypes") public static String[] arrayOfStrings(Object value) { Object[] values; if (value == null) return strings(); else if (value instanceof String[]) return (String[]) value; else if (value instanceof ArrayList) values = ((ArrayList) value).toArray(); else values = new Object[]{value}; for (int i = 0; i < values.length; i++) if (!(values[i] instanceof CharSequence)) { String string = String.valueOf(values[i]); values[i] = "null".equals(string) ? null : string; } return toStrings(values); } public static final Pattern VRI_PATTERN = Pattern.compile("\\" + VRI); public static final Pattern IDENTIFIER_PATTERN = Pattern.compile("[a-zA-Z]+[a-zA-Z0-9_\\-]*"); public static final Pattern IDENTIFIER_PATTERN2 = Pattern.compile(IDENTIFIER_PATTERN + "(\\." + IDENTIFIER_PATTERN + ")?"); public static final Pattern REFERENCE_PATTERN = Pattern.compile("\\" + VRI + "\\!?\\{?(" + IDENTIFIER_PATTERN + ")\\}?"); public static final Pattern REFERENCE_PATTERN2 = Pattern.compile("\\" + VRI + "\\!?\\{?(" + IDENTIFIER_PATTERN2 + ")\\}?"); public static final Pattern PARENS_TERM = Pattern.compile("([^\\)]+)?"); public static final Pattern METHOD_PATTERN = Pattern.compile("\\" + VRI + "\\!?\\{?(" + IDENTIFIER_PATTERN + DOT_REGEX + IDENTIFIER_PATTERN + ")" + "\\(\\s*" + PARENS_TERM + "\\s*\\)\\}?"); public static List<String> referencesIn(String template) { ArrayList<String> list = alist(); for (MatchResult m : findAllIn(template, REFERENCE_PATTERN2)) list.add(m.group()); return list; } public static boolean isName(String string) { return string.matches("^" + IDENTIFIER_PATTERN + "$"); } public static boolean isReference(String string) { return string.matches("^" + REFERENCE_PATTERN2 + "$"); } public static boolean isMethodCall(String string) { return string.matches("^" + METHOD_PATTERN + "$"); } public static boolean isDirective(String string) { return string.matches("^" + DIRECTIVE_PATTERN + "$"); } public static final Pattern VDI_PATTERN = Pattern.compile("\\" + VDI); public static final String UNKNOWN = "..."; public static final Pattern UNKNOWN_PATTERN = Pattern.compile("[\\.]{3}"); public static final Pattern CONTENT_PATTERN = Pattern.compile(".+"); public static final String END = "end"; public static final Pattern END_PATTERN = Pattern.compile(VDI_PATTERN + enclose("\\{?", END, "\\}?")); public static final String BLOCK_END = UNKNOWN + VDI + enclose(Util1.BRACES[0], END, Util1.BRACES[1]); public static final Pattern DIRECTIVE_PATTERN = Pattern.compile(VDI_PATTERN + "\\!?\\{?(" + IDENTIFIER_PATTERN + ")\\}?" + "\\(\\s*" + PARENS_TERM + "\\s*\\)"); public static Pattern directivePattern(String name) { return Pattern.compile(VDI_PATTERN + "\\!?\\{?(" + name + ")\\}?"); } public static final String[] UNARY_OPERATORS = strings("!","not",""); public static final String[] ASSIGN_OPERATORS = strings("=","in"); public static final String[] COMPARE_OPERATORS = strings("==","!=","<",">","<=",">=","eq","ne","lt","gt","le","ge"); public static final String[] ARITHMETIC_OPERATORS = strings("+","-","*","/","%"); public static ArrayList<Pattern> termPatterns = alist(); static { for (String op : ASSIGN_OPERATORS) { termPatterns.add(Pattern.compile("^(\\S+)\\s*" + op + "\\s*(\\S+)$")); } for (String op : COMPARE_OPERATORS) { termPatterns.add(Pattern.compile("^(\\S+)\\s*" + op + "\\s*(\\S+)$")); } for (String op : UNARY_OPERATORS) { termPatterns.add(Pattern.compile("^" + op + "\\s*(\\S+)$")); } for (String op : ARITHMETIC_OPERATORS) { termPatterns.add(Pattern.compile("^(\\S+)\\s*\\" + op + "\\s*(\\S+)$")); } } public static final int CONDITIONAL_OPERATORS_INDEX = 2; public static final int ARITHMETIC_OPERATORS_INDEX = 17; public static String[] dissolve(String term, int startAt, int stopAt) { for (int i = startAt; i < termPatterns.size(); i++) { if (stopAt > -1 && i >= stopAt) break; MatchResult m = findFirstIn(term, termPatterns.get(i)); if (m != null) { if (m.groupCount() < 2) return strings(m.group(1)); else return strings(m.group(1), m.group(2)); } } return strings(); } public static final String[] DATA_TYPES = {"R", "S", "I", "F", "B", "L", "M", "N", "U", "X", "Y"}; public static final int IDENTIFIER_TYPE_INDEX = 7; public static final int URI_TYPE_INDEX = 8; public static final int EXPR_TYPE_INDEX = 9; public static final String ANY = DATA_TYPES[DATA_TYPES.length - 1]; public static boolean isType(int index, String type) { return DATA_TYPES[index].compareToIgnoreCase(type) == 0; } private static final ValMap DUMMIES = vmap(); static { for (int i = 0; i < DATA_TYPES.length; i++) { Object dummy; switch (i) { case 0: dummy = new Object[]{VRI + UNKNOWN, VRI + enclose("{", UNKNOWN, "}")}; break; case 1: dummy = new Object[]{enclose("'", UNKNOWN), enclose("\"", UNKNOWN)}; break; case 2: dummy = new Object[]{"###"}; break; case 3: dummy = new Object[]{"-###.##"}; break; case 4: dummy = new Object[]{"false", "true"}; break; case 5: dummy = new Object[]{enclose("[", UNKNOWN, "]")}; break; case 6: dummy = new Object[]{enclose("{", UNKNOWN, "}")}; break; case 7: dummy = new Object[]{"xxx"}; break; case 8: dummy = new Object[]{"uri"}; break; case 9: dummy = new Object[]{UNKNOWN}; break; default: dummy = new Object[0]; break; } DUMMIES.put(DATA_TYPES[i], dummy); } } public static Object[] getDummies(String type) { return (Object[])DUMMIES.get(type.toUpperCase(Locale.getDefault())); } public static Object[] getDummies(int typeIndex) { return (Object[])DUMMIES.get(DATA_TYPES[typeIndex]); } public static boolean isDummy(int typeIndex, String text) { return asList(getDummies(typeIndex)).contains(text); } public static Boolean compliesWith(int typeIndex, String text) { if (isDummy(typeIndex, text)) return true; switch (typeIndex) { case 0: return isReference(text); case 1: return (text.startsWith("'") && text.endsWith("'")) || (text.startsWith("\"") && text.endsWith("\"")); case 2: return toInt(Integer.MIN_VALUE, text) != Integer.MIN_VALUE; case 3: return !toDouble(Double.NaN, text).isNaN(); case 4: return toBool(null, text) != null; case 5: return text.startsWith("[") && text.endsWith("]"); case 6: return text.startsWith("{") && text.endsWith("}"); case 7: return isName(text); default: return null; } } public static final Pattern LINE_BREAK_PATTERN = Pattern.compile(NEWLINE_REGEX); public static final Pattern TAB_PATTERN = Pattern.compile(TAB_REGEX); public static final Pattern ARGUMENT_PATTERN = Pattern.compile("((" + ARGUMENT_TYPER + ")?" + "(" + VRI_PATTERN + ")?" + IDENTIFIER_PATTERN + ARGUMENT_TYPER + "(?i)[" + join("", DATA_TYPES) + "](?-i))|" + "(" + UNKNOWN_PATTERN + "(?=\\s*\\)))"); public static String firstIdentifierFrom(String particle) { MatchResult mr = findFirstIn(particle, IDENTIFIER_PATTERN); if (mr != null) return mr.group(); else return null; } public static String firstMethodFrom(String particle) { MatchResult mr = findFirstIn(particle, IDENTIFIER_PATTERN2); if (mr != null) return mr.group(); else return null; } public static ValList argumentsFrom(String signature) { ValList args = vlist(); for (MatchResult m : findAllIn(signature, ARGUMENT_PATTERN)) args.add(m.group()); return args; } public static String assemble(String signature, ValList list) { StringBuffer sb = new StringBuffer(); Matcher matcher = ARGUMENT_PATTERN.matcher(signature); int sep = 0; int index = 0; while (matcher.find()) { String found = matcher.toMatchResult().group(); boolean ellipsis = UNKNOWN.equals(found); String replacement = ""; if (index < list.size()) { if (ellipsis) { do { if (replacement.length() > 0) replacement += ARGUMENT_SEPARATORS[sep]; replacement += list.get(index).toString(); index++; } while (index < list.size()); } else replacement = list.get(index).toString(); replacement = replacement.replaceAll("\\$", "\\\\\\$"); } else if (ellipsis) replacement = ARGUMENT_SEPARATORS[sep]; matcher.appendReplacement(sb, replacement); index++; } matcher.appendTail(sb); String s = sb.toString(); s = s.replaceAll("(" + ARGUMENT_SEPARATORS[sep] + ")+(?=\\s*?\\))", ""); return s; } public static Map<String,String> signatures() { Map<String,String> map = new LinkedHashMap<String,String>(); map.put("set", "#set( $reference_R = $expression_X )"); map.put("foreach", "#foreach( $reference_R in $list_L )" + BLOCK_END); map.put("break", "#{break}"); map.put("if", "#if( $condition_X )" + BLOCK_END); map.put("elseif", "#elseif( $condition_X )" + UNKNOWN); map.put("else", "#{else}" + UNKNOWN); map.put("stop", "#{stop}"); map.put("macro", "#macro( $name_N _$argument_R " + UNKNOWN + ")" + BLOCK_END); map.put("include", "#include( $file_S " + UNKNOWN + ")"); map.put("parse", "#parse( $file_S )"); map.put("evaluate", "#evaluate( $expression_X )"); map.put("define", "#define( $reference_R )" + BLOCK_END); map.put("line comment", "## " + UNKNOWN); map.put("block comment", "#*" + UNKNOWN + "*#"); map.put("unparsed content", "#[[" + UNKNOWN + "]]#"); return map; } public static final String MATH_TOOL = VRI + "math"; public static Map<String,String> math_tool() { Map<String,String> map = new LinkedHashMap<String,String>(); map.put("random", "$math.random( [$lower_F], [$upper_F] )"); map.put("round", "$math.roundTo( $decimals_I, $number_F )"); map.put("roundToInt", "$math.roundToInt( $number_F )"); map.put("add", "$math.add( $summand1_F, $summand2_F )"); map.put("sub", "$math.sub( $minuend_F, $subtrahend_F )"); map.put("mul", "$math.mul( $factor1_F, $factor2_F )"); map.put("div", "$math.div( $dividend_F, $divisor_F )"); map.put("pow", "$math.pow( $basis_F, $exponent_F )"); map.put("min", "$math.min( $number1_F, $number2_F )"); map.put("max", "$math.max( $number1_F, $number2_F )"); return map; } public static final String NODE = "node"; public static final String TOKEN = "token"; public static final int DELETE = 0; public static final int INSERT = 1; public static final int MODIFY = 2; public static ValMap nodeMap(Object node, int indents, Object token) { ValMap map = vmap(); map.put(NODE, node); map.put(INDENTS, indents); if (token != null) map.put(TOKEN, token); return map; } public static boolean hasEndToken(ValMap map) { return map.containsKey(TOKEN) && Visitor.isEndToken((Token)map.get(TOKEN)); } public static boolean isPossible(int actionCode, ValMap map) { int group = Visitor.nodeGroup(map.get(NODE)); boolean endToken = hasEndToken(map); switch (actionCode) { case DELETE: return group == DIRECTIVE && !endToken; case INSERT: return group == DIRECTIVE; case MODIFY: return (group == DIRECTIVE && !endToken) || group == REFERENCE || group == EXPRESSION; default: return false; } } public static final String INDENTS = "indents"; public static final String BLOCKS = "blocks"; public static Function<String> indentor = new Function<String>() { public String apply(Object... params) { int indents = param(0, 0, params); int blocks = param(0, 1, params); char indentChar = param(' ', 2, params); char blockChar = param(' ', 3, params); int indentLength = param(2, 4, params); StringBuilder sb = new StringBuilder(); for (int i = 0; i < indents * indentLength; i++) if (blocks > i) sb.append(blockChar); else sb.append(indentChar); return sb.toString(); } }; }