package org.geogebra.common.kernel.parser; import java.util.ArrayList; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.arithmetic.Command; import org.geogebra.common.kernel.arithmetic.Evaluatable; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.MyList; import org.geogebra.common.kernel.arithmetic.MyNumberPair; import org.geogebra.common.kernel.arithmetic.MyVecNode; import org.geogebra.common.kernel.arithmetic.Variable; import org.geogebra.common.kernel.arithmetic3D.MyVec3DNode; import org.geogebra.common.kernel.geos.GeoCasCell; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunctionNVar; import org.geogebra.common.kernel.geos.ParametricCurve; import org.geogebra.common.kernel.parser.cashandlers.CommandDispatcherGiac; import org.geogebra.common.main.App; import org.geogebra.common.main.MyParseError; import org.geogebra.common.plugin.Operation; /** * Class for building function nodes from parser * */ public class FunctionParser { private final Kernel kernel; private final App app; /** * @param kernel * kernel */ public FunctionParser(Kernel kernel) { this.kernel = kernel; this.app = kernel.getApplication(); } /** * @param cimage * function name+bracket, e.g. "f(" * @param myList * list of arguments * @param undecided * list of nodes that may be either fns or multiplications * @param GiacParsing * whether this is for Giac * @return function node */ public ExpressionNode makeFunctionNode(String cimage, MyList myList, ArrayList<ExpressionNode> undecided, boolean GiacParsing) { String funcName = cimage.substring(0, cimage.length() - 1); ExpressionNode en; if (GiacParsing) { // check for special Giac functions, e.g. diff, Psi etc. en = CommandDispatcherGiac.processCommand(funcName, myList, kernel); if (en != null) { return en.wrap(); } } boolean forceCommand = cimage.charAt(cimage.length() - 1) == '['; GeoElement geo = null; GeoElement cell = null; // check for derivative using f'' notation int order = 0; String label = funcName; if (!forceCommand) { // f(t)=t(t+1) if (kernel.getConstruction() .isRegistredFunctionVariable(funcName)) { ExpressionNode expr = new ExpressionNode(kernel, new Variable(kernel, funcName), Operation.MULTIPLY_OR_FUNCTION, myList.getListElement(0)); undecided.add(expr); return expr; } geo = kernel.lookupLabel(funcName); cell = kernel.lookupCasCellLabel(funcName); if (cell == null && (geo == null || !(geo.isGeoFunction() || geo.isGeoCurveCartesian()))) { if (label.startsWith("log_")) { ExpressionValue indexVal = getLogIndex(label, kernel); return new ExpressionNode(kernel, indexVal, Operation.LOGB, myList.getListElement(0)); } int index = funcName.length() - 1; while (index >= 0 && cimage.charAt(index) == '\'' && kernel.getAlgebraProcessor().enableVectors()) { order++; index--; } while (index < funcName.length()) { label = funcName.substring(0, index + 1); geo = kernel.lookupLabel(label); cell = kernel.lookupCasCellLabel(label); // stop if f' is defined but f is not defined, see #1444 if (cell != null || (geo != null && (geo.isGeoFunction() || geo.isGeoCurveCartesian()))) { break; } order--; index++; } } } if (forceCommand || (geo == null && cell == null)) { Operation op = app.getParserFunctions().get(funcName, myList.size()); if (op != null) { return buildOpNode(op, myList); } // function name does not exist: return command Command cmd = new Command(kernel, funcName, true, !GiacParsing); for (int i = 0; i < myList.size(); i++) { cmd.addArgument(myList.getListElement(i).wrap()); } return new ExpressionNode(kernel, cmd); } // make sure we don't send 0th derivative to CAS if (cell != null && order > 0) { return derivativeNode(kernel, cell, order, false, myList.getItem(0)); } boolean list = geo != null && geo.isGeoList(); // f(t):=(t,t) produces line, we do not want CAS to use that line // Perhaps we should prefer cell over geo in all cases ? if (cell != null && (geo == null || geo.isGeoLine() || geo.isGeoConic() || geo.isGeoSurfaceCartesian() || list)) { if (((GeoCasCell) cell).getFunctionVariables().length < 2) { return new ExpressionNode(kernel, cell, list ? Operation.ELEMENT_OF : Operation.FUNCTION, list ? myList : myList.getListElement(0)); } return new ExpressionNode(kernel, cell, list ? Operation.ELEMENT_OF : Operation.FUNCTION_NVAR, myList); } // create variable object for label to make sure // to handle lables like $A$1 correctly and keep the expression Variable geoVar = new Variable(kernel, label); ExpressionValue geoExp = geoVar.resolveAsExpressionValue(); // numer of arguments if (order > 0) { // derivative // n-th derivative of geo if (geo.isGeoFunction() || geo.isGeoCurveCartesian()) {// function kernel.getConstruction() .registerFunctionVariable(((ParametricCurve) geo) .getFunctionVariables()[0].toString( StringTemplate.defaultTemplate)); return derivativeNode(kernel, geoExp, order, geo.isGeoCurveCartesian(), myList.getListElement(0)); } String[] str = { "FunctionExpected", funcName }; throw new MyParseError(kernel.getLocalization(), str); } if (geo instanceof Evaluatable) {// function if (geo instanceof ParametricCurve && ((ParametricCurve) geo).getFunctionVariables() != null) { kernel.getConstruction() .registerFunctionVariable(((ParametricCurve) geo) .getFunctionVariables()[0].toString( StringTemplate.defaultTemplate)); } return new ExpressionNode(kernel, geoExp, Operation.FUNCTION, myList.getListElement(0)); } else if (geo instanceof GeoFunctionNVar) { return new ExpressionNode(kernel, geoExp, Operation.FUNCTION_NVAR, myList); } else if (geo.isGeoCurveCartesian()) { // vector function // at this point we have eg myList={{1,2}}, so we need first element // of myList return new ExpressionNode(kernel, geoExp, Operation.VEC_FUNCTION, myList.getListElement(0)); } else if (geo.isGeoSurfaceCartesian()) { // vector function // at this point we have eg myList={{1,2}}, so we need first element // of myList return new ExpressionNode(kernel, geoExp, Operation.VEC_FUNCTION, myList); } // list1(1) to get first element of list1 #1115 else if (list) { return new ExpressionNode(kernel, geoExp, Operation.ELEMENT_OF, myList); // String [] str = { "FunctionExpected", funcName }; // throw new MyParseError(loc, str); } // a(b) becomes a*b because a is not a function, no list, and no curve // e.g. a(1+x) = a*(1+x) when a is a number ExpressionNode expr = new ExpressionNode(kernel, geoExp, Operation.MULTIPLY_OR_FUNCTION, toFunctionArgument(myList, funcName)); undecided.add(expr); return expr; } /** * @param label * label starting with log_ * @param kernel * kernel * @return MyDouble if numeric index is present, null otherwise */ public static ExpressionValue getLogIndex(String label, Kernel kernel) { String logIndex = label.substring(4); if (logIndex.startsWith("{")) { logIndex = logIndex.substring(1, logIndex.length() - 1); } ExpressionValue ret; try { if (logIndex.matches("[0-9]*")) { ret = new MyDouble(kernel, MyDouble .parseDouble(kernel.getLocalization(), logIndex)); } ret = new GParser(kernel, kernel.getConstruction()) .parseGeoGebraExpression(logIndex); } catch (Throwable e1) { return null; } return ret; } private ExpressionValue toFunctionArgument(MyList list, String funcName) { switch (list.size()) { case 1: return list.getListElement(0); case 2: return new MyVecNode(kernel, list.getListElement(0), list.getListElement(1)); case 3: return new MyVec3DNode(kernel, list.getListElement(0), list.getListElement(1), list.getListElement(2)); } String[] str = { "FunctionExpected", funcName }; throw new MyParseError(kernel.getLocalization(), str); } /** * @param op * operation * @param list * argument list * @return expression */ public ExpressionNode buildOpNode(Operation op, MyList list) { switch (list.size()) { case 1: return new ExpressionNode(kernel, list.getListElement(0), op, null); case 2: return new ExpressionNode(kernel, list.getListElement(0), op, list.getListElement(1)); // for beta regularized case 3: return new ExpressionNode(kernel, new MyNumberPair(kernel, list.getListElement(0), list.getListElement(1)), op, list.getListElement(2)); // for sum (from CAS) case 4: return new ExpressionNode(kernel, new MyNumberPair(kernel, list.getListElement(0), list.getListElement(1)), op, new MyNumberPair(kernel, list.getListElement(2), list.getListElement(3))); default: return null; } } /** * @param kernel2 * kernel * @param geo * function * @param order * derivative order * @param curve * whether geo is a curve * @param at * function argument * @return expression for geo'''(at) */ public static ExpressionNode derivativeNode(Kernel kernel2, ExpressionValue geo, int order, boolean curve, ExpressionValue at) { return new ExpressionNode(kernel2, new ExpressionNode(kernel2, geo, Operation.DERIVATIVE, new MyDouble(kernel2, order)), curve ? Operation.VEC_FUNCTION : Operation.FUNCTION, at); } }