package com.xxiivv.mute; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Stack; import org.antlr.v4.runtime.tree.ParseTree; public class InterpretingVisitor extends MuteBaseVisitor<Object> { private final Memory memory; private final Map<String, Module> modules = new HashMap<String, Module>(); private final Random random = new Random(); Statement currentStatement = null; public InterpretingVisitor(Memory memory) { this.memory = memory; } public void registerModule(Module module) { modules.put(module.getName(), module); module.initialize(memory); } public void close() { for (Module m : modules.values()) m.close(); modules.clear(); } @SuppressWarnings("unchecked") @Override public Object visitStatement(MuteParser.StatementContext ctx) { Statement statement; if (ctx.ID() != null) { String name = ctx.ID().getText(); if (!memory.contains(name)) { statement = new Statement(name); memory.put(statement); } else { // fetch statement from memory and work with it instead statement = memory.get(name); } } else statement = new Statement(); currentStatement = statement; boolean isModule = ctx.MODULE() != null; // conditions and statement accumulate, but clear if any one is defined for (int i=0; i<ctx.getChildCount(); i++) { if (ctx.getChild(i) instanceof MuteParser.ConditionStatementPartContext) statement.conditions.clear(); if (ctx.getChild(i) instanceof MuteParser.OperationStatementPartContext) statement.operations.clear(); } List<String> moduleOperations = null; if (isModule) moduleOperations = new ArrayList<String>(); boolean skipOperations = false; for (int i=0; i<ctx.getChildCount(); i++) { ParseTree child = ctx.getChild(i); if (child instanceof MuteParser.AssignmentStatementPartContext) { Collection<Value> values = (Collection<Value>) visitAssignmentStatementPart((MuteParser.AssignmentStatementPartContext) child); skipOperations |= assignValues(statement, values); } if (child instanceof MuteParser.ConditionStatementPartContext) { Object condition = visitConditionStatementPart((MuteParser.ConditionStatementPartContext) child); statement.conditions.add((Predicate) condition); } if (child instanceof MuteParser.OperationStatementPartContext) { if (isModule) { String operationText = child.getText(); if (operationText.length() > 2) moduleOperations.add(operationText.substring(1, operationText.length() - 1)); } else { Object operation = visitOperationStatementPart((MuteParser.OperationStatementPartContext) child); statement.operations.add((Func<String>) operation); } } } if (isModule) { // everything is redirected to the module String moduleName = ctx.MODULE().getText(); moduleName = moduleName.substring(1, moduleName.length() - 1); // load on demand if (!modules.containsKey(moduleName)) registerModule(new Module(moduleName, "modules" + File.separator + moduleName + ".js")); modules.get(moduleName).evaluate(statement, moduleOperations); } else if (!skipOperations) { String result = statement.execute(); if (result != null && result.length() > 0) System.out.println(result); } return statement; } private static boolean assignValues(Statement statement, Collection<Value> values) { boolean anyNamed = false; for (Value v : values) if (v.name != null) { anyNamed = true; break; } if (!anyNamed) statement.values.clear(); boolean skipOperations = false; for (Value value : values) { boolean flattenArray = false; if (value.value instanceof Statement) { Statement copySource = (Statement) value.value; // values are copied by-value Value[] valuesCopy = copySource.getValues().values; if (valuesCopy.length == 1 && valuesCopy[0].name instanceof Integer && (int)valuesCopy[0].name == 1) value.value = valuesCopy[0].value; else { value.value = valuesCopy; flattenArray = values.size() == 1 && value.name == null; } // operations and conditions carry over! if (copySource.conditions.size() > 0) { statement.conditions.clear(); statement.conditions.addAll(copySource.conditions); } if (copySource.operations.size() > 0) { skipOperations = true; statement.operations.clear(); statement.operations.addAll(copySource.operations); } } if (flattenArray) { Value[] subValues = (Value[]) value.value; for (Value subValue : subValues) statement.setValue(subValue); } else statement.setValue(value); } return skipOperations; } @Override public Object visitConditionStatementPart(MuteParser.ConditionStatementPartContext ctx) { return visit(ctx.condition()); } @Override public Object visitGenericCondition(MuteParser.GenericConditionContext ctx) { final MuteParser.GenericConditionContext conditionContext = ctx; final String operator = ctx.COMP_OPERATOR().getText(); return new Predicate() { @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public boolean evaluate() { Object lhs = unbox(visit(conditionContext.rValueExpression(0))); Object rhs = unbox(visit(conditionContext.rValueExpression(1))); if ((lhs == null || rhs == null) && !(lhs == null && rhs == null)) return false; if (lhs instanceof Comparable && rhs instanceof Comparable) { Comparable clhs = (Comparable) lhs; Comparable crhs = (Comparable) rhs; try { switch (operator) { case "=": return clhs.compareTo(crhs) == 0; case "<=": return clhs.compareTo(crhs) <= 0; case ">=": return clhs.compareTo(crhs) >= 0; case "<": return clhs.compareTo(crhs) < 0; case ">": return clhs.compareTo(crhs) > 0; default: throw new RuntimeException("Unrecognized operator : " + operator); } } catch (ClassCastException ex) { //throw new RuntimeException("Arguments of type " + lhs.getClass() + " and " + rhs.getClass() + " can not be compared!"); // TODO : Warning mechanism? return false; } } // fallback on equality switch (operator) { case "=": return lhs.equals(rhs); default: throw new RuntimeException("Unsupported or unrecognized operator : " + operator); } } }; } @Override public Object visitExistenceCondition(MuteParser.ExistenceConditionContext ctx) { final MuteParser.ExistenceConditionContext conditionContext = ctx; return new Predicate() { @Override public boolean evaluate() { MutableAccess value = (MutableAccess) visitLValueExpression(conditionContext.lValueExpression()); return value != null && value.exists() && value.getHostStatement().hasValue(); } }; } @Override public Object visitAssignmentStatementPart(MuteParser.AssignmentStatementPartContext ctx) { return visitAssignmentList(ctx.assignmentList()); } @Override public Object visitAssignmentList(MuteParser.AssignmentListContext ctx) { List<Value> assignmentList = new ArrayList<Value>(); for (int i=0; i<ctx.getChildCount(); i++) { ParseTree child = ctx.getChild(i); if (child instanceof MuteParser.AssignmentContext) { Object assignment = visitAssignment((MuteParser.AssignmentContext) child); // only happens for array concatenation if (assignment instanceof Value[]) { for (Value subAssignment : (Value[])assignment) assignmentList.add(subAssignment); } else assignmentList.add((Value) assignment); } } return assignmentList; } @Override public Object visitAssignment(MuteParser.AssignmentContext ctx) { Object rhs = visit(ctx.rValueExpression()); // array concatenation if (rhs instanceof Value[]) return rhs; Value value = new Value(rhs); if (ctx.ID() != null) value.name = ctx.ID().getText(); else if (ctx.INT() != null) value.name = Integer.parseInt(ctx.INT().getText()); return value; } @Override public Object visitGenericOperation(MuteParser.GenericOperationContext ctx) { final MuteParser.GenericOperationContext operationContext = ctx; final InterpretingVisitor outer = this; return new Func<String>() { @Override public String evaluate() { Object result = outer.visit(operationContext.rValueExpression()); if (result instanceof Statement) return ((Statement)result).execute(); return outer.visit(operationContext.rValueExpression()).toString(); } }; } @Override public Object visitAssignmentOperation(MuteParser.AssignmentOperationContext ctx) { final MuteParser.AssignmentOperationContext operationContext = ctx; return new Func<String>() { @SuppressWarnings("unchecked") @Override public String evaluate() { MutableAccess lValue = (MutableAccess) visitLValueExpression(operationContext.lValueExpression()); Collection<Value> values = (Collection<Value>) visitAssignmentList(operationContext.assignmentList()); if (!lValue.exists()) { Statement statement = new Statement(); if (operationContext.lValueExpression().ID() != null) { statement.name = operationContext.lValueExpression().ID().getText(); memory.put(statement); } lValue.set(statement); } assignValues(lValue.getHostStatement(), values); return null; } }; } @Override public Object visitLValueWrapper(MuteParser.LValueWrapperContext ctx) { MutableAccess ma = (MutableAccess) visitLValueExpression(ctx.lValueExpression()); return ma.get(); } @Override public Object visitNumericAtom(MuteParser.NumericAtomContext ctx) { return Integer.parseInt(ctx.INT().getText()); } @Override public Object visitRange(MuteParser.RangeContext ctx) { int from = (int) visit(ctx.rValueExpression(0)); int to = (int) visit(ctx.rValueExpression(1)); return random.nextInt(to - from + 1) + from; } @Override public Object visitUnaryExpression(MuteParser.UnaryExpressionContext ctx) { return -1 * (int) visit(ctx.rValueExpression()); } @Override public Object visitParenthezisedExpression(MuteParser.ParenthezisedExpressionContext ctx) { return visit(ctx.rValueExpression()); } static Object unbox(Object expression) { if (expression instanceof Statement) { Statement statement = (Statement) expression; if (statement.hasValue()) { if (statement.isSingleton()) return statement.getSingletonValue(); return statement.getValues(); } return null; } return expression; } @Override public Object visitBinaryNumericExpression(MuteParser.BinaryNumericExpressionContext ctx) { int lhs = (int) unbox(visit(ctx.rValueExpression(0))); int rhs = (int) unbox(visit(ctx.rValueExpression(1))); String op = ctx.getChild(1).getText(); switch (op) { case "-": return lhs - rhs; case "+": return lhs + rhs; case "%": return lhs % rhs; case "*": return lhs * rhs; case "/": return lhs / rhs; case "^": return (int) Math.pow(lhs, rhs); default: throw new RuntimeException("Unrecognized operator : " + op); } } @Override public Object visitStringExpansion(MuteParser.StringExpansionContext ctx) { String str = ctx.STRING().getText(); str = str.substring(1, str.length() - 1); // trim off the quotes for (int i=0; i<ctx.getChildCount(); i++) { ParseTree childAt = ctx.getChild(i); if (childAt instanceof MuteParser.RValueExpressionContext) { Object value = unbox(visit(childAt)); String replaced = value.toString(); str = str.replaceFirst("@", replaced); } } return str; } @Override public Object visitBinaryStringExpression(MuteParser.BinaryStringExpressionContext ctx) { switch (ctx.getChild(1).getText()) { case "&": { Object lhs = visit(ctx.rValueExpression(0)); if (lhs instanceof String) return (String) lhs + (String) visit(ctx.rValueExpression(1)); if (lhs instanceof Statement) { Statement rhs = (Statement) visit(ctx.rValueExpression(1)); Value[] retArray = new Value[((Statement)lhs).values.size() + rhs.values.size()]; int i = 0; for (Value v : ((Statement)lhs).getValues()) retArray[i++] = v; for (Value v : rhs.getValues()) { retArray[i] = v; retArray[i++].name = null; // will be filled in later } return retArray; } throw new RuntimeException("Unrecognized type : " + lhs.getClass()); } // case "|": default: { // substring operator (no length) Object lhs = visit(ctx.rValueExpression(0)); int rhs = (int) visit(ctx.rValueExpression(1)); if (lhs instanceof String) return ((String)lhs).substring(rhs); if (lhs instanceof Statement) { Value[] origArray = ((Statement)lhs).getValues().values; Value[] retArray = new Value[origArray.length - rhs]; int i = 0; for (int j = rhs; j < origArray.length; j++) { retArray[i] = origArray[j]; retArray[i++].name = null; // will be filled in later } return retArray; } throw new RuntimeException("Unrecognized type : " + lhs.getClass()); } } } @Override public Object visitLValueExpression(MuteParser.LValueExpressionContext ctx) { final Statement contextStatement = currentStatement; final MuteParser.LValueExpressionContext parseContext = ctx; return new MutableAccess() { //final Object snapshotValue = get(); // might be useful? Statement hostStatement = null; Object valueName = null; @Override public Object get() { hostStatement = null; valueName = null; Stack<Object> nameStack = new Stack<Object>(); MuteParser.LValueExpressionContext curCtx = parseContext; do { if (curCtx.INT() != null) nameStack.add((Integer) Integer.parseInt(curCtx.INT().getText())); else nameStack.add(curCtx.ID().getText()); curCtx = curCtx.lValueExpression(); } while (curCtx != null); String hostName = (String) nameStack.pop(); hostStatement = hostName.equals("$") ? contextStatement : memory.contains(hostName) ? memory.get(hostName) : null; Object referencedValue = null; while (!nameStack.empty()) { valueName = nameStack.pop(); if (valueName instanceof String && valueName.equals("$")) valueName = contextStatement.getSingletonValue(); referencedValue = hostStatement.getValue(valueName); if (referencedValue instanceof Statement) hostStatement = (Statement) referencedValue; else if (referencedValue instanceof Value[]) { // multi-dimensional arrays... kind of a hack hostStatement = new Statement(); assignValues(hostStatement, Arrays.asList((Value[]) referencedValue)); } } if (referencedValue == null) { if (hostStatement == null) return null; return hostStatement; } return referencedValue; } @Override public void set(Object newValue) { get(); if (valueName == null) hostStatement = (Statement) newValue; else hostStatement.setValue(new Value(newValue)); } @Override public boolean exists() { get(); return hostStatement != null && (valueName == null || hostStatement.values.containsKey(valueName)); } @Override public Statement getHostStatement() { return hostStatement; } }; } }