package de.gaalop.clucalc.input; import de.gaalop.CheckGAVisitor; import de.gaalop.Notifications; import de.gaalop.UsedVariablesVisitor; import de.gaalop.cfg.*; import de.gaalop.dfg.Expression; import de.gaalop.dfg.FloatConstant; import de.gaalop.dfg.MacroCall; import de.gaalop.dfg.Variable; import de.gaalop.dfg.MathFunction; import de.gaalop.dfg.MathFunctionCall; import java.awt.Dimension; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * This is a utility class used by the CluCalcTransformer to build a control flow graph while parsing the CluCalc AST. */ public final class GraphBuilder { private class SetLocalAndInputVariables implements ControlFlowVisitor { SetLocalAndInputVariables() { // empty non-private constructor (prevent synthetic accessors) } /** * Searches the expression for variable references. If an undeclared reference is found, it is added to the * input variables of the graph. * * @param expression The expression to search in. * @param inMacro */ private void findUndeclaredVariables(Expression expression) { UsedVariablesVisitor visitor = new UsedVariablesVisitor(); expression.accept(visitor); for (Variable usedVariable : visitor.getVariables()) { checkIllegalVariable(usedVariable); if (!graph.getLocalVariables().contains(usedVariable)) { // in case we have pragmas giving ranges for the variable, add them if (graph.getPragmaMinValue().containsKey(usedVariable.getName())) { usedVariable.setMinValue(graph.getPragmaMinValue().get(usedVariable.getName())); } if (graph.getPragmaMaxValue().containsKey(usedVariable.getName())) { usedVariable.setMaxValue(graph.getPragmaMaxValue().get(usedVariable.getName())); } graph.addInputVariable(usedVariable); } } } @Override public void visit(StartNode node) { node.getSuccessor().accept(this); } @Override public void visit(AssignmentNode node) { findUndeclaredVariables(node.getValue()); graph.addLocalVariable(node.getVariable()); node.getSuccessor().accept(this); } @Override public void visit(StoreResultNode node) { findUndeclaredVariables(node.getValue()); node.getSuccessor().accept(this); } @Override public void visit(IfThenElseNode node) { findUndeclaredVariables(node.getCondition()); node.getPositive().accept(this); node.getNegative().accept(this); node.getSuccessor().accept(this); } @Override public void visit(BlockEndNode node) { } @Override public void visit(LoopNode node) { node.getBody().accept(this); node.getSuccessor().accept(this); } @Override public void visit(BreakNode node) { } @Override public void visit(Macro node) { // ignore body of macro node.getSuccessor().accept(this); } @Override public void visit(ExpressionStatement node) { findUndeclaredVariables(node.getExpression()); node.getSuccessor().accept(this); } @Override public void visit(EndNode node) { } @Override public void visit(ColorNode node) { node.getSuccessor().accept(this); } } private final Map<String, ColorNode> COLORS; private static final Map<String, String> illegalNames; static { illegalNames = new HashMap<String, String>(); illegalNames.put("B", "B is the metric matrix in Maple"); // illegalNames.put("condition_", "synthetic variable inserted by Gaalop"); illegalNames.put("norm", "protected in Maple"); illegalNames.put("normal", "protected in Maple"); illegalNames.put("length", "protected in Maple"); illegalNames.put("point", "protected in Maple"); } final ControlFlowGraph graph; private SequentialNode lastNode; private CluCalcFileHeader header; private boolean setMode = false; private Set<String> macros = new HashSet<String>(); private String currentMacroDefinition; private VariableScope currentScope = VariableScope.GLOBAL; public void beginNewScope() { currentScope = new VariableScope(currentScope); } public void endNewScope() { currentScope = currentScope.getParent(); } public GraphBuilder() { graph = new ControlFlowGraph(); lastNode = graph.getStartNode(); { COLORS = new HashMap<String, ColorNode>(); COLORS.put("Black", getRBGColor(0, 0, 0)); COLORS.put("Blue", getRBGColor(0, 0, 1)); COLORS.put("Cyan", getRBGColor(0, 1, 1)); COLORS.put("Green", getRBGColor(0, 1, 0)); COLORS.put("Magenta", getRBGColor(1, 0, 1)); COLORS.put("Orange", getRBGColor(1, 0.7f, 0)); COLORS.put("Red", getRBGColor(1, 0, 0)); COLORS.put("White", getRBGColor(1, 1, 1)); COLORS.put("Yellow", getRBGColor(1, 1, 0)); } // Get annotation for graphs start node header = CluCalcFileHeader.get(graph.getStartNode()); if (header == null) { header = new CluCalcFileHeader(graph.getStartNode()); } } /** * Adds a variable name to the control flow graph as Pragma output marked. * * @param variable The variable */ public void addPragmaOutputVariable(String variable) { graph.addPragmaOutputVariable(variable); } /** * Adds a variable name to the control flow graph as Pragma onlyEvaluate marked. * * @param variable The variable */ public void addPragmaOnlyEvaluateVariable(String variable) { graph.addPragmaOnlyEvaluateVariable(variable); } /** * Adds a pragma hint for a variable, which defines value range for it. The pragma must be set before the variable * is added to the input variables, i.e. the pragma must appear for the use of the variable */ public void addPragmaMinMaxValues(String variable, String min, String max) { graph.addPragmaMinMaxValues(variable, min, max); } /** * Adds a node to the end of the graph. * * @param node The node that should be added. */ private void addNode(SequentialNode node) { lastNode.insertAfter(node); lastNode = node; } public ControlFlowGraph getGraph() { return graph; } /** * Add an assignment node to the end of this graph. * * @param variable The variable that is assigned to. * @param expression The expression that is assigned to the variable. */ public AssignmentNode handleAssignment(Variable variable, Expression expression) { checkIllegalVariable(variable); CheckGAVisitor gaVisitor = new CheckGAVisitor(); expression.accept(gaVisitor); if (gaVisitor.isGA()) { gaVisitor.addGAVariable(variable); } AssignmentNode assignment = new AssignmentNode(graph, variable, expression); addNode(assignment); return assignment; } void checkIllegalVariable(Variable variable) { String name = variable.getName(); String reason = illegalNames.get(name); if (reason != null) { throw new IllegalArgumentException("Illegal variable name '" + name + "' (" + reason + ")." + " Please use another variable."); } if (variable.getName().startsWith("re")) { throw new IllegalArgumentException("Variable '" + variable + "' cannot be used in Maple because of prefix 're' which is protected." + " Please choose another name."); } } /** * Sets the null space for the CLUCalc header object. * * @param ns null space to be set (IPNS / OPNS) */ public void handleNullSpace(NullSpace ns) { header.setNullSpace(ns); } /** * Handle the CluCalc print operator '?'. * * This only works for variables. * * @param variable The variable that should be printed. */ public StoreResultNode handlePrint(Expression variable) { if (variable instanceof Variable) { StoreResultNode storeResult = new StoreResultNode(graph, (Variable) variable); addNode(storeResult); return storeResult; } else { throw new IllegalArgumentException("Only variables can be marked for optimization."); } } /** * Handles an if statement. The statement consists of a condition expression, a mandatory then part (block) and an * optional else part. * * @param condition condition expression * @param then_part list of statements belonging to then part * @param else_part optional list of statements belonging to else part (empty list in case of no else part) * @return new {@link IfThenElseNode} representing this statement. */ public IfThenElseNode handleIfStatement(Expression condition, List<SequentialNode> then_part, List<SequentialNode> else_part) { IfThenElseNode ifthenelse = new IfThenElseNode(graph, condition); addNode(ifthenelse); rewireNodes(then_part, ifthenelse); rewireNodes(else_part, ifthenelse); ifthenelse.setPositive(then_part.get(0)); ifthenelse.setNegative((else_part != null && else_part.size() > 0) ? else_part.get(0) : new BlockEndNode(graph, ifthenelse)); return ifthenelse; } /** * Handles a loop statement. A CluCalc loop consists of a body of statements, typically containing the break * keyword. * * @param body list of statements belonging to body * @param iterations (optional) number of iterations for loop unrolling * @return new {@link LoopNode} representing this statement. */ public LoopNode handleLoop(List<SequentialNode> body, String iterations) { LoopNode loop = new LoopNode(graph); addNode(loop); rewireNodes(body, loop); loop.setBody((body != null && body.size() > 0) ? body.get(0) : new BlockEndNode(graph, loop)); // save number of iterations and reset value for next loops if (iterations != null) { loop.setIterations(Integer.parseInt(iterations)); } return loop; } /** * Handles the break keyword in a loop statement. * * @see #handleLoop(List) * @return new {@link BreakNode} representing this statement. */ public BreakNode handleBreak() { BreakNode brk = new BreakNode(graph); addNode(brk); return brk; } public void handleSlider(Variable var, String label, double min, double max, double step, double init) { Slider slider = new Slider(var, label, min, max, step, init); graph.addInputVariable(var); graph.addSlider(slider); } public ColorNode handleColor(List<Expression> args) { ColorNode color = getRGBAColor(args); addNode(color); return color; } public ColorNode handleColor(String name) { ColorNode color = (ColorNode) COLORS.get(name).copy(); if (color == null) { throw new IllegalArgumentException("Color " + name + " is not known."); } addNode(color); return color; } public void handleBGColor(List<Expression> args) { graph.setBGColor(getRGBAColor(args)); } private ColorNode getRGBAColor(List<Expression> args) { if (args.size() < 3) { throw new IllegalArgumentException("Argument must have 3 values for R, G and B"); } Expression r = args.get(0); Expression g = args.get(1); Expression b = args.get(2); Expression alpha = (args.size() == 4) ? args.get(3) : null; ColorNode color; if (alpha == null) { color = new ColorNode(graph, r, g, b); } else { color = new ColorNode(graph, r, g, b, alpha); } return color; } private ColorNode getRBGColor(double r, double g, double b) { return new ColorNode(graph, new FloatConstant(r), new FloatConstant(g), new FloatConstant(b)); } public Macro handleMacroDefinition(String id, List<SequentialNode> body, Expression ret) { // reset current macro name to allow further calls currentMacroDefinition = ""; Macro macro = new Macro(graph, id, body, ret); addNode(macro); graph.addMacro(macro); rewireNodes(body, macro); return macro; } public void addMacroName(String name) { macros.add(name); currentMacroDefinition = name; } /** * Removes the nodes given by <code>list</code> from the control flow graph and rewires them to be a sequence of * separate nodes, e.g. in the body of an if-statement. The first node has <code>base</code> as predecessor. For the * last node in the list, base's successor will be set as successor, e.g. the first statement after an if-then-else * statement. * * @param list list of nodes from a block * @param base basis of block, e.g. an if-statement */ private void rewireNodes(List<SequentialNode> list, SequentialNode base) { if (list == null || list.size() == 0) { return; } Iterator<SequentialNode> it = list.iterator(); SequentialNode current = it.next(); graph.removeNode(current); current.addPredecessor(base); // first node has if-then-else node as predecessor while (it.hasNext()) { SequentialNode next = it.next(); graph.removeNode(next); current.replaceSuccessor(current.getSuccessor(), next); // could be redundant next.addPredecessor(current); current = next; } current.replaceSuccessor(current.getSuccessor(), new BlockEndNode(graph, base)); // mark the end of this block } // Creates an expression from an identifier and takes constants into account public Expression processIdentifier(String name) { Variable v = new Variable(name); currentScope.addVariable(v); return v; } public Expression processFunction(String name, List<Expression> args) { /* for (AlgebraMode mode : ALGEBRA_MODES) { if (mode.getDefinitionMethod().equals(name)) { setMode(mode); return null; } } if (functionFactory.isDefined(name)) { Expression[] argsArray = args.toArray(new Expression[args.size()]); return functionFactory.createExpression(name, argsArray); } for (MathFunction mathFunction : MathFunction.values()) { if (mathFunction.toString().toLowerCase().equals(name)) { if (args.size() == 1) { return new MathFunctionCall(args.get(0), mathFunction); } else { throw new IllegalArgumentException("Calling math function " + mathFunction + " with more than one" + " argument: " + args); } } } if (name.startsWith("::")) { name = name.substring(2); } if (macros.contains(name)) { if (name.equals(currentMacroDefinition)) { throw new IllegalArgumentException("Recursive macro calls are not supported: " + name); } else { return new MacroCall(name, args); } } throw new IllegalArgumentException("Call to undefined function " + name + "(" + args + ").\n" + "Maybe this function is not defined in " + mode + "\n" + "Also make sure that macros are defined before they are called."); */ return new MacroCall(name, args); } public ExpressionStatement processExpressionStatement(Expression e) { ExpressionStatement statement = new ExpressionStatement(graph, e); addNode(statement); return statement; } public void addVisualizerExpression(ExpressionStatement expr) { graph.visualizerExpressions.add(expr); } /** * Should be called to notify the graph builder that the parsing process has finished. If needed, post-processing of * the graph can be performed here. */ public void finish() { //Check, if the cluscript contains at least one '?'-mark is moved to algebra plugin, //because of problems in macros.clu parsing in algebra plugin SetCallerVisitor visitor = new SetCallerVisitor(); graph.accept(visitor); SetLocalAndInputVariables inputFinder = new SetLocalAndInputVariables(); graph.accept(inputFinder); } }