/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ /* * Command.java * * Created on 05. September 2001, 12:05 */ package org.geogebra.common.kernel.arithmetic; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.Macro; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants.StringType; import org.geogebra.common.kernel.arithmetic.Traversing.GeoDummyReplacer; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.commands.EvalInfo; import org.geogebra.common.kernel.geos.GeoCasCell; import org.geogebra.common.kernel.geos.GeoDummyVariable; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoVec2D; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.main.App; import org.geogebra.common.main.MyError; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; /** * * @author Markus */ public class Command extends ValidExpression implements ReplaceChildrenByValues, GetItem { // list of arguments private ArrayList<ExpressionNode> args = new ArrayList<ExpressionNode>(); private String name; // internal command name (in English) private Kernel kernel; private App app; private GeoElementND[] evalGeos; // evaluated Elements private Macro macro; // command may correspond to a macro private boolean allowEvaluationForTypeCheck = true; /** * Creates a new command object. * * @param kernel * kernel * @param name * internal name or translated name * @param translateName * true to translate name to internal * */ public Command(Kernel kernel, String name, boolean translateName) { this(kernel, name, translateName, true); } /** * Creates a new command object. * * @param kernel * kernel * @param name * internal name or translated name * @param translateName * true to translate name to internal * @param allowEvaluationForTypeCheck * whether this command is allowed to be evaluated in type checks * like isTextValue() */ public Command(Kernel kernel, String name, boolean translateName, boolean allowEvaluationForTypeCheck) { this.kernel = kernel; app = kernel.getApplication(); this.allowEvaluationForTypeCheck = allowEvaluationForTypeCheck; /* * need to check app.isUsingInternalCommandNames() due to clash with * BinomialDist=Binomial Binomial=BinomialCoefficient Should also allow * other languages to use English names for different commands */ if (translateName && !kernel.isUsingInternalCommandNames()) { // translate command name to internal name this.name = app.getReverseCommand(name); // in CAS functions get parsed as commands as well and we want to // keep the name if (this.name == null) { this.name = name; } } else { this.name = name; } } /** * @return kernel */ public Kernel getKernel() { return kernel; } /** * @param arg * argument to add */ public void addArgument(ExpressionNode arg) { args.add(arg); } /** * Returns the name of the variable at the specified argument position. If * there is no variable name at this position, null is returned. * * @param i * position * @return name of the variable at the specified argument position */ public String getVariableName(int i) { if (i >= args.size()) { return null; } ExpressionValue ev = args.get(i).unwrap(); if (ev instanceof Variable) { return ((Variable) ev).getName(StringTemplate.defaultTemplate); } else if (ev instanceof GeoElement) { // XML Handler looks up labels of GeoElements // so we may end up having a GeoElement object here // return its name to use as local variable name GeoElement geo = ((GeoElement) ev); if (geo.isLabelSet() || geo.isLocalVariable()) { return ((GeoElement) ev).getLabelSimple(); } } else if (ev instanceof FunctionVariable) { return ((FunctionVariable) ev).getSetVarString(); } else if (ev instanceof Function) { String str = ev.toString(StringTemplate.defaultTemplate); if (str.length() == 1 && StringUtil.isLetter(str.charAt(0))) { return str; } } else if (ev instanceof GeoVec2D) { if (((GeoVec2D) ev).isImaginaryUnit()) { return Unicode.IMAGINARY; } } else if (ev instanceof MySpecialDouble) { if (((MySpecialDouble) ev).isEulerConstant()) { return Unicode.EULER_STRING; } } else if (ev instanceof ValidExpression) { Log.debug(((ValidExpression) ev).getLabel() + " valid expression label"); } return null; } /** * @return array of arguments */ public ExpressionNode[] getArguments() { return args.toArray(new ExpressionNode[0]); } /** * @param i * index * @return i-th argument */ public ExpressionNode getArgument(int i) { return args.get(i); } /** * @param i * index * @param en * argument */ public void setArgument(int i, ExpressionNode en) { args.set(i, en); } /** * @return number of arguments */ public int getArgumentNumber() { return args.size(); } /** * @return internal command name */ public String getName() { return name; } @Override public String toString(StringTemplate tpl) { return toString(true, false, tpl); } @Override public String toValueString(StringTemplate tpl) { return toString(false, false, tpl); } @Override public String toLaTeXString(boolean symbolic, StringTemplate tpl) { return toString(symbolic, true, tpl); } private String toString(boolean symbolic, boolean LaTeX, StringTemplate tpl) { switch (tpl.getStringType()) { case GIAC: return (kernel.getGeoGebraCAS()).getCASCommand(name, args, symbolic, tpl); case LATEX: if (sbToString == null) { sbToString = new StringBuilder(); } sbToString.setLength(0); if ("Integral".equals(name)) { sbToString.append("\\int"); Set<GeoElement> vars = getArgument(0).getVariables(); String var = "x"; if (vars != null && !vars.isEmpty()) { Iterator<GeoElement> ite = vars.iterator(); while (ite.hasNext()) { GeoElement geo = ite.next(); // get function from construction GeoElement geoFunc = getKernel().getConstruction() .geoTableVarLookup(geo.getLabel( StringTemplate.defaultTemplate)); GeoElement geoCASCell = getKernel().getConstruction() .lookupCasCellLabel(geo.getLabel( StringTemplate.defaultTemplate)); // if input function was without parameter // replace geoDummyVariable with function if (geo instanceof GeoDummyVariable && geoFunc != null && geoFunc.isGeoFunction()) { ExpressionNode funcNode = new ExpressionNode(kernel, geoFunc, Operation.FUNCTION, ((GeoFunction) geoFunc) .getFunctionVariables()[0]); getArgument(0) .traverse(GeoDummyReplacer.getReplacer( geo.getLabel( StringTemplate.defaultTemplate), funcNode, true)); } // if we couldn't find the function // just as GeoCasCell if (geo instanceof GeoDummyVariable && geoCASCell != null && geoCASCell.isGeoCasCell() && ((GeoCasCell) geoCASCell) .getInputVE() instanceof Function) { ExpressionNode funcNode = new ExpressionNode(kernel, geo, Operation.FUNCTION, ((GeoCasCell) geoCASCell) .getFunctionVariables()[0]); String funcStr = getArgument(0) .toString(StringTemplate.defaultTemplate); // geoDummyVariable wasn't already changed if (!funcStr .contains("(" + ((GeoCasCell) geoCASCell) .getFunctionVariables()[0] + ")")) { getArgument(0) .traverse(GeoDummyReplacer.getReplacer( geo.getLabel( StringTemplate.defaultTemplate), funcNode, true)); } } // make sure that we get from set the variable and not // the // function // needed for TRAC-5364 if (geo instanceof GeoDummyVariable && geoFunc == null && geoCASCell == null) { var = geo.toString(tpl); } } } switch (getArgumentNumber()) { case 1: sbToString.append(" "); sbToString.append(getArgument(0).toString(tpl)); break; case 2: sbToString.append(" "); sbToString.append(getArgument(0).toString(tpl)); var = getArgument(1).toString(tpl); break; case 3: sbToString.append("\\limits_{"); sbToString.append(getArgument(1).toString(tpl)); sbToString.append("}^{"); sbToString.append(getArgument(2).toString(tpl)); sbToString.append("}"); sbToString.append(getArgument(0).toString(tpl)); break; case 4: sbToString.append("\\limits_{"); sbToString.append(getArgument(2).toString(tpl)); sbToString.append("}^{"); sbToString.append(getArgument(3).toString(tpl)); sbToString.append("}"); sbToString.append(getArgument(0).toString(tpl)); var = getArgument(1).toString(tpl); break; default: break; } sbToString.append("\\,\\mathrm{d}"); sbToString.append(var); return sbToString.toString(); } else if ("Sum".equals(name) && getArgumentNumber() == 4) { sbToString.append("\\sum_{"); sbToString.append(args.get(1).toString(tpl)); sbToString.append("="); sbToString.append(args.get(2).toString(tpl)); sbToString.append("}^{"); sbToString.append(args.get(3).toString(tpl)); sbToString.append("}"); sbToString.append(args.get(0).toString(tpl)); return sbToString.toString(); } else if ("Product".equals(name) && getArgumentNumber() == 4) { sbToString.append("\\prod_{"); sbToString.append(args.get(1).toString(tpl)); sbToString.append("="); sbToString.append(args.get(2).toString(tpl)); sbToString.append("}^{"); sbToString.append(args.get(3).toString(tpl)); sbToString.append("}"); sbToString.append(args.get(0).toString(tpl)); return sbToString.toString(); } default: if (sbToString == null) { sbToString = new StringBuilder(); } sbToString.setLength(0); // GeoGebra command syntax if (tpl.isPrintLocalizedCommandNames()) { sbToString.append(app.getLocalization().getCommand(name)); } else { sbToString.append(name); } if (LaTeX || tpl.hasType(StringType.LATEX)) { sbToString.append(" \\left"); } sbToString.append('['); int size = args.size(); for (int i = 0; i < size; i++) { sbToString.append(toString(args.get(i), symbolic, LaTeX, tpl)); // Integral[f,0,1] // make sure that we add the parameter of the function too if ("Integral".equals(name)) { if (i == 0 && args.get(0).isExpressionNode() && args.get(0).getLeft() instanceof GeoCasCell) { if (((GeoCasCell) args.get(0).getLeft()) .isAssignmentVariableDefined() && args.get(0).getRight() == null) { sbToString .append("(" + ((GeoCasCell) args.get(0) .getLeft()) .getFunctionVariable() + ")"); } } } sbToString.append(','); } if (size > 0) { sbToString.deleteCharAt(sbToString.length() - 1); } if (LaTeX || tpl.hasType(StringType.LATEX)) { sbToString.append(" \\right"); } sbToString.append(']'); return sbToString.toString(); } } private StringBuilder sbToString; private static String toString(ExpressionValue ev, boolean symbolic, boolean LaTeX, StringTemplate tpl) { if (LaTeX) { return ev.toLaTeXString(symbolic, tpl); } return symbolic ? ev.toString(tpl) : ev.toValueString(tpl); } /** * @param info * context for evaluation * @return array of resulting geos */ public GeoElementND[] evaluateMultiple(EvalInfo info) { GeoElementND[] geos = null; geos = kernel.getAlgebraProcessor().processCommand(this, info); return geos; } @Override public ExpressionValue evaluate(StringTemplate tpl) { // not yet evaluated: process command if (evalGeos == null) { evalGeos = evaluateMultiple(new EvalInfo(false)); } if (evalGeos != null && evalGeos.length >= 1) { return evalGeos[0]; } Log.debug("invalid command evaluation: " + name); throw new MyError(app.getLocalization(), app.getLocalization().getError("InvalidInput") + ":\n" + this); } /** * Like evaluate, but does not necessarily produce GeoElement * * @param info * evaluation flags * @return evaluation result */ public ExpressionValue simplify(EvalInfo info) { // not yet evaluated: process command ExpressionValue result = kernel.getAlgebraProcessor() .simplifyCommand(this, info.withLabels(false)); if (result instanceof GeoElement) { evalGeos = new GeoElement[] { (GeoElement) result }; } if (result != null) { return result; } Log.debug("invalid command evaluation: " + name); throw new MyError(app.getLocalization(), app.getLocalization().getError("InvalidInput") + ":\n" + this); } @Override public void resolveVariables(EvalInfo info) { // standard case: // nothing to do here: argument variables are resolved // while command processing (see evaluate()) // CAS parsing case: we need to resolve arguments also if (kernel.isResolveUnkownVarsAsDummyGeos()) { for (int i = 0; i < args.size(); i++) { args.get(i).resolveVariables(info); } // avoid evaluation of command allowEvaluationForTypeCheck = false; } } // rewritten to cope with {Root[f]} // Michael Borcherds 2008-10-02 @Override public boolean isConstant() { // not yet evaluated: process command if (evalGeos == null) { evalGeos = evaluateMultiple(new EvalInfo(false)); } if (evalGeos == null || evalGeos.length == 0) { throw new MyError(app.getLocalization(), app.getLocalization().getError("InvalidInput") + ":\n" + this); } for (int i = 0; i < evalGeos.length; i++) { if (!evalGeos[i].isConstant()) { return false; } } return true; } @Override public boolean isLeaf() { // return evaluate().isLeaf(); return true; } /* * Type checking with evaluate Try to evaluate using GeoGebra if fails, try * with CAS else throw Exception */ @Override public boolean isNumberValue() { return evaluatesToNumber(false); } private ValueType lastType = null; @Override public ValueType getValueType() { if ("Sequence".equals(name) || "IterationList".equals(name) || "KeepIf".equals(name)) { return ValueType.LIST; } if ("Function".equals(name)) { return ValueType.FUNCTION; } if ("Surface".equals(name) || ("Curve".equals(name) && args.size() > 5)) { return ValueType.PARAMETRIC3D; } if ("CurveCartesian".equals(name)) { return ValueType.PARAMETRIC2D; } if ("Vector".equals(name) && (args.size() > 0) && args.get(0).getValueType() == ValueType.VECTOR3D) { return ValueType.VECTOR3D; } if ("Vector".equals(name)) { return ValueType.NONCOMPLEX2D; } if ("Evaluate".equals(name) && args.size() > 0) { return args.get(0).getValueType(); } if (lastType != null) { return lastType; } if (!allowEvaluationForTypeCheck) { return ValueType.UNKNOWN; } try { lastType = evaluate(StringTemplate.defaultTemplate).getValueType(); } catch (Throwable ex) { ExpressionValue ev = kernel.getGeoGebraCAS().getCurrentCAS() .evaluateToExpression(this, null, kernel); if (ev != null) { lastType = ev.getValueType(); } else { throw wrapError(ex); } } return lastType; } private MyError wrapError(Throwable ex) { return ex instanceof MyError ? (MyError) ex : new MyError(kernel.getLocalization(), ex.getMessage()); } @Override public boolean evaluatesToVectorNotPoint() { if (!allowEvaluationForTypeCheck) { return false; } try { return evaluate( StringTemplate.defaultTemplate) instanceof VectorValue; } catch (MyError ex) { ExpressionValue ev = kernel.getGeoGebraCAS().getCurrentCAS() .evaluateToExpression(this, null, kernel); if (ev != null) { return ev.unwrap().evaluatesToNonComplex2DVector(); } throw ex; } } @Override public boolean evaluatesToText() { if (!allowEvaluationForTypeCheck) { return false; } if (app.getInternalCommand(name) == null && kernel.getMacro(name) == null) { return false; } try { return evaluate(StringTemplate.defaultTemplate).evaluatesToText(); } catch (MyError ex) { ExpressionValue ev = kernel.getGeoGebraCAS().getCurrentCAS() .evaluateToExpression(this, null, kernel); if (ev != null) { return ev.unwrap().evaluatesToText(); } throw ex; } } @Override public Command deepCopy(Kernel kernel1) { Command c = new Command(kernel1, name, false); // copy arguments int size = args.size(); for (int i = 0; i < size; i++) { c.addArgument(args.get(i).getCopy(kernel1)); } return c; } @Override public void replaceChildrenByValues(GeoElement geo) { int size = args.size(); for (int i = 0; i < size; i++) { args.get(i).replaceChildrenByValues(geo); } } @Override public HashSet<GeoElement> getVariables() { HashSet<GeoElement> set = new HashSet<GeoElement>(); int size = args.size(); for (int i = 0; i < size; i++) { Set<GeoElement> s = args.get(i).getVariables(); if (s != null) { set.addAll(s); } } return set; } @Override final public boolean contains(ExpressionValue ev) { return ev == this; } @Override public int getListDepth() { if ("x".equals(getName()) || "y".equals(getName()) || "z".equals(getName()) || "If".equals(getName())) { return this.getArgument(0).getListDepth(); } // There we might add more commands that evaluate to matrix if ("Identity".equals(getName())) { return 2; } if (!allowEvaluationForTypeCheck) { return 0; } try { return evaluate(StringTemplate.defaultTemplate).getListDepth(); } catch (MyError ex) { ExpressionValue ev = kernel.getGeoGebraCAS().getCurrentCAS() .evaluateToExpression(this, null, kernel); if (ev != null) { return ev.unwrap().getListDepth(); } throw ex; } } /** * @return macro macro associated with this command */ public final Macro getMacro() { return macro; } /** * @param macro * macro associated with this command */ public final void setMacro(Macro macro) { this.macro = macro; } @Override public boolean isTopLevelCommand() { return true; } @Override public boolean isTopLevelCommand(String checkName) { return name.equals(checkName); } @Override public Command getTopLevelCommand() { return this; } @Override public String toOutputValueString(StringTemplate tpl) { return toValueString(tpl); } @Override public ExpressionValue traverse(Traversing t) { ExpressionValue v = t.process(this); if (v != this) { return v; } for (int i = 0; i < args.size(); i++) { ExpressionNode en = args.get(i).traverse(t).wrap(); args.set(i, en); } return this; } @Override public boolean inspect(Inspecting t) { if (t.check(this)) { return true; } for (int i = 0; i < args.size(); i++) { if (args.get(i).inspect(t)) { return true; } } return false; } @Override public ExpressionValue getItem(int i) { return args.get(i); } @Override public boolean hasCoords() { if ("x".equals(name) || "y".equals(name) || "z".equals(name)) { return false; } return true; } /** * for commands with different output types and that need to know each * lenght to set labels correctly */ private int[] outputSizes; /** * set output sizes * * @param sizes * output sizes */ public void setOutputSizes(int[] sizes) { outputSizes = sizes; } /** * * @return output sizes */ public int[] getOutputSizes() { return outputSizes; } @Override public int getLength() { return getArgumentNumber(); } /** * Replaces all Variable objects with the given varName in the arguments by * the given FunctionVariable object. * * @param varName * variable name * @param fVar * replacement variable * @return number of replacements done */ public int replaceVariables(String varName, FunctionVariable fVar) { int replacements = 0; for (ExpressionNode arg : args) { replacements += arg.replaceVariables(varName, fVar); } return replacements; } @Override public ExpressionNode wrap() { return new ExpressionNode(kernel, this); } /** * Helps pars x(expr) to either command, x-coord function or multiplication * by x. * * @param en * parameter * @param i * coordinate (0=x,1=y,2=z); * @param mayCheck * whether we may compute the command to find output type * @param undecided * array to which we can push the result if not clear whether * it's multiplication or function * @return parsed expression */ public static ExpressionNode xyzCAS(ValidExpression en, int i, boolean mayCheck, ArrayList<ExpressionNode> undecided) { Operation[] ops = new Operation[] { Operation.XCOORD, Operation.YCOORD, Operation.ZCOORD }; Kernel k = en.wrap().getKernel(); ExpressionNode en2; if (en.evaluatesToList()) { Command cmd = new Command(k, "Element", true, mayCheck); cmd.addArgument(en.wrap()); // Element uses 1 for first element cmd.addArgument(new MyDouble(k, i + 1).wrap()); en2 = cmd.wrap(); } else if (en.hasCoords()) { en2 = new ExpressionNode(k, en.unwrap(), ops[i], null); /* * char funName = (char) ('x'+i); Command cmd = new Command(k, * funName+"", true, mayCheck ); cmd.addArgument( en ); en2 = * cmd.wrap(); */ } else { char funName = (char) ('x' + i); en2 = new ExpressionNode(k, new FunctionVariable(k, funName + ""), Operation.MULTIPLY_OR_FUNCTION, en); undecided.add(en2); } // App.printStacktrace(""); return en2; } /** * Change command name, useful eg for processing Rotate as RotateText * * @param string * new name for this command */ public void setName(String string) { this.name = string; } /** * @return whether this command has a name of GeoGebra supported command */ public boolean isAvailable() { Commands c = null; try { c = Commands.valueOf(name); } catch (Exception e) { // not found } return c != null; } }