/**
* 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; either version 2 of the License, or (at your option) any later
* version.
*/
package org.geogebra.common.kernel.commands;
import java.util.ArrayList;
import org.geogebra.common.kernel.CircularDefinitionException;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.AlgoDispatcher;
import org.geogebra.common.kernel.algos.AlgoFunctionFreehand;
import org.geogebra.common.kernel.arithmetic.Command;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.ExpressionValue;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.arithmetic.Traversing.CommandFunctionReplacer;
import org.geogebra.common.kernel.arithmetic.Traversing.CommandReplacer;
import org.geogebra.common.kernel.arithmetic.Traversing.GeoDummyReplacer;
import org.geogebra.common.kernel.arithmetic.Traversing.Replacer;
import org.geogebra.common.kernel.arithmetic.Variable;
import org.geogebra.common.kernel.geos.GeoCasCell;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.parser.ParseException;
import org.geogebra.common.main.App;
import org.geogebra.common.main.Localization;
import org.geogebra.common.main.MyError;
import org.geogebra.common.plugin.GeoClass;
import org.geogebra.common.util.debug.Log;
import org.geogebra.common.util.lang.Unicode;
/**
* Resolves arguments of the command, checks their validity and creates
* resulting geos via appropriate Kernel methods
*/
public abstract class CommandProcessor {
/** application */
protected App app;
/** localization */
protected Localization loc;
/** kernel */
protected Kernel kernelA;
/** construction */
protected Construction cons;
private AlgebraProcessor algProcessor;
/**
* Creates new command processor
*
* @param kernel
* kernel
*/
public CommandProcessor(Kernel kernel) {
this.kernelA = kernel;
cons = kernel.getConstruction();
app = kernel.getApplication();
loc = app.getLocalization();
algProcessor = kernel.getAlgebraProcessor();
}
/**
* Every CommandProcessor has to implement this method
*
* @param c
* command
* @return list of resulting geos
* @throws MyError
* for wrong number / type of parameters
* @throws CircularDefinitionException
* if circular definition occurs
*/
public GeoElement[] process(Command c)
throws MyError, CircularDefinitionException {
return process(c, null);
}
/**
* Every CommandProcessor has to implement this method
*
* @param c
* command
* @param info
* flags for geo labeling
* @return list of resulting geos
* @throws MyError
* for wrong number / type of parameters
* @throws CircularDefinitionException
* if circular definition occurs
*/
public GeoElement[] process(Command c, EvalInfo info)
throws MyError, CircularDefinitionException {
return process(c);
}
/**
* Resolves arguments. When argument produces mor geos, only first is taken.
*
* @param c
* command
* @return array of arguments
* @throws MyError
* if processing of some argument causes error (i.e. wrong
* syntax of subcommand)
*/
protected final GeoElement[] resArgs(Command c) throws MyError {
return resArgs(c, new EvalInfo(false));
}
/**
* Resolves arguments. When argument produces mor geos, only first is taken.
*
* @param c
* command
* @param info
* context for evaluation -- labelling is overridden to false in
* this method
* @return array of arguments
* @throws MyError
* if processing of some argument causes error (i.e. wrong
* syntax of subcommand)
*/
protected final GeoElement[] resArgs(Command c, EvalInfo info)
throws MyError {
boolean oldMacroMode = cons.isSuppressLabelsActive();
cons.setSuppressLabelCreation(true);
// resolve arguments to get GeoElements
ExpressionNode[] arg = c.getArguments();
// name of replace variable of "x"/"y"
EvalInfo argInfo = info.withLabels(false);
String[] newXYZ = replaceXYarguments(arg);
GeoElement[] result = new GeoElement[arg.length];
for (int i = 0; i < arg.length; ++i) {
// resolve variables in argument expression
arg[i].resolveVariables(argInfo);
// resolve i-th argument and get GeoElements
// use only first resolved argument object for result
result[i] = resArg(arg[i], argInfo)[0];
}
// remove added variables from construction
if (newXYZ != null) {
for (int i = 0; i < 3; i++) {
cons.removeLocalVariable(newXYZ[i]);
}
}
cons.setSuppressLabelCreation(oldMacroMode);
return result;
}
/**
* @param arg
* arguments
* @return local variable names
*/
protected String[] replaceXYarguments(ExpressionNode[] arg) {
return null;
}
/**
* @param arg
* arguments
* @param i
* local variable position
* @param var
* variable name
* @param subst
* new variable name
* @param argsToCheck
* number of arguments that need replacing
* @return new variable name (subst or subst + index if subst is used by
* another object)
*/
protected String checkReplaced(ExpressionNode[] arg, int i, String var,
String subst, int argsToCheck) {
if (arg[i] != null && arg[i].unwrap() instanceof GeoNumeric
&& ((GeoNumeric) arg[i].getLeft()).getLabelSimple() != null
&& ((GeoNumeric) arg[i].getLeft()).getLabelSimple()
.equals(var)) {
// get free variable to replace "x" with
String newXVarStr = ((GeoElement) arg[i].getLeft())
.getFreeLabel(subst);
Variable newVar = new Variable(cons.getKernel(), newXVarStr);
GeoNumeric gn = new GeoNumeric(cons);
kernelA.getConstruction().addLocalVariable(newXVarStr, gn);
GeoDummyReplacer replacer = GeoDummyReplacer.getReplacer(var,
newVar, true);
// replace "x" in expressions
for (int j = 0; j < argsToCheck; j++) {
arg[j].traverse(replacer);
}
arg[i].setLeft(gn);
return newXVarStr;
}
return null;
}
/**
* @param c
* command
* @param keepCAScells
* false = replace CAS cells by twin geos, true = keep cells
* @param info
* evaluation flags
* @return processed arguments
* @throws MyError
* when arguments contain errors, eg. invalid operation in exp
* node
*/
protected final GeoElement[] resArgs(Command c, boolean keepCAScells,
EvalInfo info) throws MyError {
boolean oldMacroMode = cons.isSuppressLabelsActive();
cons.setSuppressLabelCreation(true);
// resolve arguments to get GeoElements
ExpressionNode[] arg = c.getArguments();
GeoElement[] result = new GeoElement[arg.length];
EvalInfo argInfo = info.withLabels(false);
for (int i = 0; i < arg.length; ++i) {
// resolve variables in argument expression
arg[i].resolveVariables(argInfo);
if (keepCAScells && arg[i].unwrap() instanceof GeoCasCell) {
result[i] = (GeoElement) arg[i].unwrap();
} else {
// resolve i-th argument and get GeoElements
// use only first resolved argument object for result
result[i] = resArg(arg[i], argInfo)[0];
}
}
cons.setSuppressLabelCreation(oldMacroMode);
return result;
}
/**
* Resolves argument
*
* @param arg
* argument
* @param info
* context for evaluation, labelling is overridden here
* @return array of arguments
* @throws MyError
* if processing argument causes error (i.e. wrong syntax of
* subcommand)
*/
protected final GeoElement[] resArg(ExpressionNode arg, EvalInfo info)
throws MyError {
GeoElement[] geos = algProcessor.processExpressionNode(arg,
info.withLabels(false));
if (geos != null) {
return geos;
}
String[] str = { "IllegalArgument",
arg.toString(StringTemplate.defaultTemplate) };
throw new MyError(loc, str);
}
/**
* Resolve arguments of a command that has a local numeric variable at the
* position varPos. Initializes the variable with the NumberValue at
* initPos.
*
* @param c
* command
* @param varPos
* position of variable
* @param initPos
* position of initial value
* @return Array of arguments
*/
protected final GeoElement[] resArgsLocalNumVar(Command c, int varPos,
int initPos) {
// check if there is a local variable in arguments
String localVarName = c.getVariableName(varPos);
if (localVarName == null) {
throw argErr(app, c, c.getArgument(varPos));
}
// imaginary unit as local variable name
else if (localVarName.equals(Unicode.IMAGINARY)) {
// replace all imaginary unit objects in command arguments by a
// variable "i"object
localVarName = "i";
Variable localVar = new Variable(kernelA, localVarName);
c.traverse(
Replacer.getReplacer(kernelA.getImaginaryUnit(), localVar));
}
// Euler constant as local variable name
else if (localVarName.equals(Unicode.EULER_STRING)) {
// replace all imaginary unit objects in command arguments by a
// variable "i"object
localVarName = "e";
Variable localVar = new Variable(kernelA, localVarName);
c.traverse(
Replacer.getReplacer(kernelA.getEulerNumber(), localVar));
}
// add local variable name to construction
Construction cmdCons = c.getKernel().getConstruction();
GeoNumeric num = new GeoNumeric(cmdCons);
cmdCons.addLocalVariable(localVarName, num);
replaceZvarIfNeeded(localVarName, c, varPos);
// initialize first value of local numeric variable from initPos
if (initPos != varPos) {
boolean oldval = cons.isSuppressLabelsActive();
cons.setSuppressLabelCreation(true);
NumberValue initValue = (NumberValue) resArg(c.getArgument(initPos),
new EvalInfo(false))[0];
cons.setSuppressLabelCreation(oldval);
num.setValue(initValue.getDouble());
}
// set local variable as our varPos argument
c.setArgument(varPos, new ExpressionNode(c.getKernel(), num));
// resolve all command arguments including the local variable just
// created
GeoElement[] arg = resArgs(c);
// remove local variable name from kernel again
cmdCons.removeLocalVariable(localVarName);
return arg;
}
private void replaceZvarIfNeeded(String name, Command c,
int argsToReplace) {
if ("z".equals(name)) {
// parse again to undo z*z -> Function
try {
for (int i = 0; i < argsToReplace; i++) {
c.setArgument(i, kernelA.getParser()
.parseGeoGebraExpression(c.getArgument(i)
.toString(StringTemplate.xmlTemplate))
.wrap());
}
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* Resolves arguments, creates local variables and fills the vars and
* overlists
*
* @param c
* zip command
* @param vars
* variables
* @param over
* lists from which the vars should be taken
* @return list of arguments
*/
protected final GeoElement resArgsForZip(Command c, GeoElement[] vars,
GeoList[] over) {
// check if there is a local variable in arguments
int numArgs = c.getArgumentNumber();
Construction cmdCons = c.getKernel().getConstruction();
EvalInfo argInfo = new EvalInfo(false);
for (int varPos = 1; varPos < numArgs; varPos += 2) {
String localVarName = c.getVariableName(varPos);
if (localVarName == null
&& c.getArgument(varPos).isTopLevelCommand()) {
localVarName = c.getArgument(varPos).getTopLevelCommand()
.getVariableName(0);
}
if (localVarName == null) {
throw argErr(app, c, c.getArgument(varPos));
}
// add local variable name to construction
GeoElement num = null;
// initialize first value of local numeric variable from initPos
GeoList gl = null;
if (c.getArgumentNumber() > varPos + 1) {
gl = (GeoList) resArg(c.getArgument(varPos + 1), argInfo)[0];
}
if (gl == null) {
num = new GeoNumeric(cons);
} else {
num = gl.createTemplateElement();
}
cmdCons.addLocalVariable(localVarName, num);
replaceZvarIfNeeded(localVarName, c, 1);
// set local variable as our varPos argument
c.setArgument(varPos, new ExpressionNode(c.getKernel(), num));
vars[varPos / 2] = num.toGeoElement();
if (gl != null) {
over[varPos / 2] = gl;
}
// resolve all command arguments including the local variable just
// created
// remove local variable name from kernel again
}
ExpressionNode def = c.getArgument(0)
.traverse(CommandReplacer.getReplacer(kernelA, false)).wrap();
GeoElement[] arg = resArg(def, argInfo);
return arg[0];
}
/**
* Resolve argument for Iteration command
*
* @param c
* command to use in the iteration
* @param vars
* variables
* @param over
* list from which the variables should be taken
* @param number
* number of iterations
* @return arguments for Iteration(List)
*/
protected final GeoElement resArgsForIteration(Command c, GeoElement[] vars,
GeoList[] over, GeoNumeric[] number) {
// check if there is a local variable in arguments
int numArgs = c.getArgumentNumber();
Construction cmdCons = c.getKernel().getConstruction();
EvalInfo argInfo = new EvalInfo(false);
GeoElement geo = resArg(c.getArgument(numArgs - 2), argInfo)[0];
if (geo != null && !(geo instanceof GeoList)) {
throw argErr(app, c, c.getArgument(numArgs - 2));
}
GeoList gl = (GeoList) geo;
GeoElement num = null;
if (gl == null) {
num = new GeoNumeric(cons);
} else if (gl.size() == 0) {
if (gl.getTypeStringForXML() != null) {
num = kernelA.createGeoElement(cons, gl.getTypeStringForXML());
} else {
// guess
num = new GeoNumeric(cons);
}
} else {
// list not zero length
num = gl.get(0).copyInternal(cons);
}
over[0] = gl;
for (int varPos = 1; varPos < numArgs - 2; varPos += 1) {
String localVarName = c.getVariableName(varPos);
if (localVarName == null
&& c.getArgument(varPos).isTopLevelCommand()) {
localVarName = c.getArgument(varPos).getTopLevelCommand()
.getVariableName(0);
}
if (localVarName == null) {
throw argErr(app, c, c.getArgument(varPos));
}
// add local variable name to construction
// initialize first value of local numeric variable from initPos
cmdCons.addLocalVariable(localVarName, num);
replaceZvarIfNeeded(localVarName, c, 1);
// set local variable as our varPos argument
c.setArgument(varPos, new ExpressionNode(c.getKernel(), num));
vars[varPos - 1] = num.toGeoElement();
// replace for Iteration[f(1/(1-x)),f,{x},21]
if (!isCmdName(localVarName)) {
c.getArgument(0).traverse(CommandFunctionReplacer
.getReplacer(app, localVarName, num));
}
if (varPos < numArgs - 3) {
num = num.copy();
}
// resolve all command arguments including the local variable just
// created
// remove local variable name from kernel again
}
number[0] = (GeoNumeric) resArg(c.getArgument(numArgs - 1), argInfo)[0];
GeoElement[] arg = resArg(c.getArgument(0), argInfo);
return arg[0];
}
/**
* @param cmdName
* command name
* @return whether such command exists
*/
public static boolean isCmdName(String cmdName) {
Throwable t = null;
try {
Commands.valueOf(cmdName);
} catch (Throwable t1) {
t = t1;
}
return t == null;
}
/**
* Resolve arguments of a command that has a several local numeric variable
* at the position varPos. Initializes the variable with the NumberValue at
* initPos.
*
* @param c
* command
* @param varPos
* positions of local variables
* @param initPos
* positions of vars to be initialized
* @return array of arguments
*/
protected final GeoElement[] resArgsLocalNumVar(Command c, int varPos[],
int initPos[]) {
String[] localVarName = new String[varPos.length];
for (int i = 0; i < varPos.length; i++) {
// check if there is a local variable in arguments
localVarName[i] = c.getVariableName(varPos[i]);
if (localVarName[i] == null) {
throw argErr(app, c, c.getArgument(varPos[i]));
}
// imaginary unit as local variable name
else if (localVarName[i].equals(Unicode.IMAGINARY)) {
// replace all imaginary unit objects in command arguments by a
// variable "i"object
localVarName[i] = "i";
Variable localVar = new Variable(kernelA, localVarName[i]);
c.traverse(Replacer.getReplacer(kernelA.getImaginaryUnit(),
localVar));
}
// Euler constant as local variable name
else if (localVarName[i].equals(Unicode.EULER_STRING)) {
// replace all imaginary unit objects in command arguments by a
// variable "i"object
localVarName[i] = "e";
Variable localVar = new Variable(kernelA, localVarName[i]);
c.traverse(Replacer.getReplacer(kernelA.getEulerNumber(),
localVar));
}
}
// add local variable name to construction
Construction cmdCons = c.getKernel().getConstruction();
GeoNumeric[] num = new GeoNumeric[varPos.length];
for (int i = 0; i < varPos.length; i++) {
num[i] = new GeoNumeric(cmdCons);
cmdCons.addLocalVariable(localVarName[i], num[i]);
replaceZvarIfNeeded(localVarName[i], c, varPos[0]);
}
EvalInfo argInfo = new EvalInfo(false);
// initialize first value of local numeric variable from initPos
for (int i = 0; i < varPos.length; i++) {
if (initPos[i] != varPos[i]) {
boolean oldval = cons.isSuppressLabelsActive();
cons.setSuppressLabelCreation(true);
NumberValue initValue = (NumberValue) resArg(
c.getArgument(initPos[i]), argInfo)[0];
cons.setSuppressLabelCreation(oldval);
num[i].setValue(initValue.getDouble());
}
}
// set local variable as our varPos argument
for (int i = 0; i < varPos.length; i++) {
c.setArgument(varPos[i], new ExpressionNode(c.getKernel(), num[i]));
}
// resolve all command arguments including the local variable just
// created
GeoElement[] arg = resArgs(c);
// remove local variable name from kernel again
for (int i = 0; i < varPos.length; i++) {
cmdCons.removeLocalVariable(localVarName[i]);
}
return arg;
}
private StringBuilder sb;
/**
* Creates wrong argument error
*
* @param app1
* application
* @param cmd
* command name
* @param arg
* faulty argument
* @return wrong argument error
*/
public final MyError argErr(App app1, Command cmd, ExpressionValue arg) {
return argErr(app1.getLocalization(), cmd.getName(), arg);
}
/**
* Creates wrong argument error
*
* @param app1
* application
* @param cmd
* command name
* @param arg
* faulty argument
* @return wrong argument error
*/
protected final MyError argErr(Localization app1, String cmd,
ExpressionValue arg) {
String localName = app1.getCommand(cmd);
if (sb == null) {
sb = new StringBuilder();
} else {
sb.setLength(0);
}
final boolean reverseOrder = app1.isReverseNameDescriptionLanguage();
if (!reverseOrder) {
// standard order: "Command ..."
sb.append(app1.getCommand("Command"));
sb.append(' ');
sb.append(localName);
} else {
// reverse order: "... command"
sb.append(localName);
sb.append(' ');
sb.append(app1.getCommand("Command").toLowerCase());
}
sb.append(":\n");
sb.append(app1.getError("IllegalArgument"));
sb.append(": ");
if (arg instanceof GeoElement) {
sb.append(((GeoElement) arg).getNameDescription());
} else if (arg != null) {
sb.append(arg.toString(StringTemplate.defaultTemplate));
}
sb.append("\n\n");
sb.append(app1.getPlain("Syntax"));
sb.append(":\n");
sb.append(app1.getCommandSyntax(cmd));
return new MyError(app1, sb.toString(), cmd, null);
}
/**
* Creates wrong parameter count error
*
* @param app1
* application
* @param cmd
* command name
* @param argNumber
* (-1 for just show syntax)
* @return wrong parameter count error
*/
protected final MyError argNumErr(App app1, Command cmd, int argNumber) {
if (sb == null) {
sb = new StringBuilder();
} else {
sb.setLength(0);
}
getCommandSyntax(sb, app1.getLocalization(), cmd.getName(), argNumber);
return new MyError(app1.getLocalization(), sb.toString(), cmd.getName(),
null);
}
/**
* Copies error syntax into a StringBuilder
*
* @param sb
* string builder to store result
* @param app
* application
* @param cmd
* command name (internal)
* @param argNumber
* (-1 for just show syntax)
*/
public static void getCommandSyntax(StringBuilder sb, Localization app,
String cmd, int argNumber) {
final boolean reverseOrder = app.isReverseNameDescriptionLanguage();
if (!reverseOrder) {
// standard order: "Command ..."
sb.append(app.getCommand("Command"));
sb.append(' ');
sb.append(app.getCommand(cmd));
} else {
// reverse order: "... command"
sb.append(app.getCommand(cmd));
sb.append(' ');
sb.append(app.getCommand("Command").toLowerCase());
}
if (argNumber > -1) {
sb.append(":\n");
sb.append(app.getError("IllegalArgumentNumber"));
sb.append(": ");
sb.append(argNumber);
}
sb.append("\n\n");
sb.append(app.getPlain("Syntax"));
sb.append(":\n");
sb.append(app.getCommandSyntax(cmd));
}
/**
* Creates change dependent error
*
* @param app1
* application
* @param geo
* dependent geo
* @return change dependent error
*/
final static MyError chDepErr(App app1, GeoElement geo) {
String[] strs = { "ChangeDependent", geo.getLongDescription() };
return new MyError(app1.getLocalization(), strs);
}
/**
* Returns bad argument (according to ok array) and throws error if no was
* found.
*
* @param ok
* array of "bad" flags
* @param arg
* array of arguments
* @return bad argument
*/
protected static GeoElement getBadArg(boolean[] ok, GeoElement[] arg) {
for (int i = 0; i < ok.length; i++) {
if (!ok[i]) {
return arg[i];
}
}
return arg[arg.length - 1];
}
/**
* Creates a dependent list with all GeoElement objects from the given
* array.
*
* @param args
* array of arguments
* @param type
* -1 for any GeoElement object type; GeoElement.GEO_CLASS_ANGLE,
* etc. for specific types
* @return null if GeoElement objects did not have the correct type
* @author Markus Hohenwarter
* @param kernel
* kernel
* @param length
* number of arguments
* @date Jan 26, 2008
*/
public static GeoList wrapInList(Kernel kernel, GeoElement[] args,
int length, GeoClass type) {
Construction cons = kernel.getConstruction();
boolean correctType = true;
ArrayList<GeoElement> geoElementList = new ArrayList<GeoElement>();
for (int i = 0; i < length; i++) {
if (type.equals(GeoClass.DEFAULT)
|| args[i].getGeoClassType() == type) {
geoElementList.add(args[i]);
} else {
correctType = false;
break;
}
}
GeoList list = null;
if (correctType) {
boolean oldMacroMode = cons.isSuppressLabelsActive();
cons.setSuppressLabelCreation(true);
list = kernel.getAlgoDispatcher().List(null, geoElementList, false);
cons.setSuppressLabelCreation(oldMacroMode);
}
// list of zero size is not wanted
if (list != null && list.size() == 0) {
list = null;
}
return list;
}
/**
* Used by eg FitSin to allow a freehand function to be passed as an arg
*
* converts a list of y-coordinates into a list of GeoPoints
*
* @param kernelA
* kernel
* @param algo
* function's parent algorithm
* @return list of points on the function
*/
public static GeoList wrapFreehandFunctionArgInList(Kernel kernelA,
AlgoFunctionFreehand algo) {
Construction cons = kernelA.getConstruction();
GeoList list = (GeoList) algo.getInput()[0];
// first 2 points in list are start and end, rest are y-coordinates
double start = ((NumberValue) list.get(0)).getDouble();
double end = ((NumberValue) list.get(1)).getDouble();
int size = list.size() - 2;
double step = (end - start) / (size - 1);
ArrayList<GeoElement> geoElementList = new ArrayList<GeoElement>();
for (int i = 0; i < size; i++) {
GeoPoint p = new GeoPoint(cons, start + i * step,
((NumberValue) list.get(2 + i)).getDouble(), 1.0);
geoElementList.add(p);
}
boolean oldMacroMode = cons.isSuppressLabelsActive();
cons.setSuppressLabelCreation(true);
list = kernelA.getAlgoDispatcher().List(null, geoElementList, false);
cons.setSuppressLabelCreation(oldMacroMode);
return list;
}
/**
* Check if arg(i) depends on arg(j) and either throw an error or write
* warning see #2552
*
* @param arg
* arguments
* @param c
* command
* @param i
* index of possibly dependent argument
* @param j
* index of independent argument
*/
protected void checkDependency(GeoElement[] arg, Command c, int i,
int j) {
if (arg[i].isChildOrEqual(arg[j])) {
if (kernelA.getConstruction().isFileLoading()) {
// make sure old files can be loaded (and fixed)
Log.warn("wrong dependency in " + c.getName());
} else {
throw argErr(app, c, arg[i]);
}
}
}
/**
* @return algo dispatcher
*/
protected AlgoDispatcher getAlgoDispatcher() {
return kernelA.getAlgoDispatcher();
}
/**
* Reduces the command to expression node or gives null if not possible
*
* @param c
* command
* @return command output
*/
public ExpressionValue simplify(Command c) {
return null;
}
/**
* @param geo
* problematic geo
* @param c
* command
* @return throws error
*/
protected final MyError argErr(GeoElement geo, Command c) {
return argErr(app, c, geo);
}
/**
* @param n
* argument number
* @param c
* command
* @return throws error
*/
protected final MyError argNumErr(int n, Command c) {
return argNumErr(app, c, n);
}
}