package org.geogebra.common.kernel.arithmetic; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.arithmetic3D.Vector3DValue; import org.geogebra.common.kernel.geos.GeoCasCell; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoFunctionNVar; import org.geogebra.common.kernel.geos.GeoFunctionable; import org.geogebra.common.kernel.geos.GeoLine; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoVec2D; import org.geogebra.common.kernel.kernelND.Geo3DVecInterface; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoVecInterface; import org.geogebra.common.main.Localization; import org.geogebra.common.main.MyError; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.debug.Log; /** * @author ggb3D * * Evaluator for ExpressionNode (used in Operation.evaluate()) */ public class ExpressionNodeEvaluator implements ExpressionNodeConstants { private static final StringTemplate errorTemplate = StringTemplate.defaultTemplate; private Localization l10n; /** * Kernel used to create the results */ protected Kernel kernel; /** * Creates new expression node evaluator * * @param l10n * localization for errors * @param kernel * kernel */ public ExpressionNodeEvaluator(Localization l10n, Kernel kernel) { this.l10n = l10n; this.kernel = kernel; } /** * Evaluates the ExpressionNode described by the parameters * * @param expressionNode * ExpressionNode to evaluate * @param tpl * template needed for nodes containing string concatenation * @return corresponding ExpressionValue */ public ExpressionValue evaluate(ExpressionNode expressionNode, StringTemplate tpl) { boolean leaf = expressionNode.leaf; ExpressionValue left = expressionNode.getLeft(); if (leaf) { return left.evaluate(tpl); // for wrapping ExpressionValues as // ValidExpression } ExpressionValue right = expressionNode.getRight(); Operation operation = expressionNode.getOperation(); boolean holdsLaTeXtext = expressionNode.holdsLaTeXtext; ExpressionValue lt, rt; lt = left.evaluate(tpl); // left tree // TODO Evaluation of equations is expensive, but better soln needed // #4816 if (left instanceof Equation) { expressionNode.setLeft(lt); } if (operation.equals(Operation.NO_OPERATION)) { return lt; } rt = right.evaluate(tpl); // right tree // handle list operations first ExpressionValue special = handleSpecial(lt, rt, left, right, operation, tpl); if (special != null) { return special; } // NON-List operations (apart from EQUAL_BOOLEAN and list + text) return handleOp(operation, lt, rt, left, right, tpl, holdsLaTeXtext); } /** * @param op * operation * @param lt * left evaluated * @param rt * right evaluated * @param left * left expression * @param right * right expression * @param tpl * template for string ops * @param holdsLaTeX * whether result should be latex * @return operation result */ protected ExpressionValue handleOp(Operation op, ExpressionValue lt, ExpressionValue rt, ExpressionValue left, ExpressionValue right, StringTemplate tpl, boolean holdsLaTeX) { return op.handle(this, lt, rt, left, right, tpl, holdsLaTeX); } /** * * @param myList * list (matrix) * @param rt * vector * @return list (matrix) * vector/point */ protected ExpressionValue multiply(MyList myList, VectorNDValue rt) { if (rt instanceof VectorValue) { return multiply2D(myList, myList.getMatrixRows(), myList.getMatrixCols(), (VectorValue) rt); } return null; } /** * * @param myList * list (matrix) * @param rows * matrix rows length * @param cols * matrix cols length * @param rt * vector * @return list (matrix) * 2D vector / point */ final static protected ExpressionValue multiply2D(MyList myList, int rows, int cols, VectorValue rt) { return multiply2D(myList, rows, cols, rt, rt.getVector()); } /** * @param myList * list (matrix) * @param rows * matrix rows length * @param cols * matrix cols length * @param rt * vector * @param myVec * vector set to result * @return list (matrix) * 2D vector / point */ final protected static ExpressionValue multiply2D(MyList myList, int rows, int cols, VectorNDValue rt, GeoVec2D myVec) { if ((rows == 2) && (cols == 2)) { // 2x2 matrix GeoVec2D.multiplyMatrix(myList, rt.getVector(), myVec); return myVec; } else if ((rows == 3) && (cols == 3)) { // 3x3 matrix, assume it's affine myVec.multiplyMatrixAffine(myList, rt); return myVec; } return null; } private ExpressionValue handleSpecial(ExpressionValue lt, ExpressionValue rt, ExpressionValue left, ExpressionValue right, Operation operation, StringTemplate tpl) { if (lt instanceof ListValue) { if ((operation == Operation.MULTIPLY) && rt instanceof VectorNDValue) { MyList myList = ((ListValue) lt).getMyList(); if (myList.isMatrix()) { ExpressionValue ret = multiply(myList, (VectorNDValue) rt); if (ret != null) { return ret; } } } else if ((operation == Operation.VECTORPRODUCT) && rt instanceof ListValue) { MyList listL = ((ListValue) lt.evaluate(tpl)).getMyList(); MyList listR = ((ListValue) rt.evaluate(tpl)).getMyList(); if (((listL.size() == 3) && (listR.size() == 3)) || ((listL.size() == 2) && (listR.size() == 2))) { listL.vectorProduct(listR); return listL; } } // we cannot use elseif here as we might need multiplication if ((operation != Operation.IF_LIST) && (operation != Operation.MATRIXTOVECTOR) && (operation != Operation.EQUAL_BOOLEAN) && (operation != Operation.NOT_EQUAL // ditto ) && (operation != Operation.IS_SUBSET_OF // ditto ) && (operation != Operation.IS_SUBSET_OF_STRICT // ditto ) && (operation != Operation.SET_DIFFERENCE // ditto ) && (operation != Operation.ELEMENT_OF // list1(1) to get // first element ) && (operation != Operation.IS_ELEMENT_OF // list1(1) to // get // first element ) && !(rt instanceof VectorValue) // eg {1,2} + (1,2) && !(rt instanceof TextValue)) // bugfix "" + {1,2} Michael // Borcherds // 2008-06-05 { // list lt operation rt return listOperation((ListValue) lt, operation, rt, right, true, tpl); } } else if (rt instanceof ListValue && !operation.equals(Operation.EQUAL_BOOLEAN) // added // EQUAL_BOOLEAN // Michael // Borcherds // 2008-04-12 && !operation.equals(Operation.NOT_EQUAL) // ditto && !operation.equals(Operation.FUNCTION_NVAR) // ditto && !(operation.equals(Operation.VEC_FUNCTION) && lt.isGeoElement() && ((GeoElement) lt).isGeoSurfaceCartesian()) // ditto && !operation.equals(Operation.FREEHAND) // ditto && !operation.equals(Operation.DATA) // ditto && !(lt instanceof VectorValue && operation.isPlusorMinus()) // eg // {1,2} // + // (1,2) && !(lt instanceof TextValue) // bugfix "" + {1,2} Michael // Borcherds // 2008-06-05 && !operation.equals(Operation.IS_ELEMENT_OF)) { if (operation == Operation.MULTIPLY && lt instanceof VectorValue) { MyList myList = ((ListValue) rt).getMyList(); boolean isMatrix = myList.isMatrix(); int rows = myList.getMatrixRows(); int cols = myList.getMatrixCols(); if (isMatrix && (rows == 2) && (cols == 2)) { GeoVec2D myVec = ((VectorValue) lt).getVector(); // 2x2 matrix myVec.multiplyMatrixLeft(myList); return myVec; } } // lt operation list rt return listOperation((ListValue) rt, operation, lt, left, false, tpl); } else if ((lt instanceof FunctionalNVar) && (rt instanceof FunctionalNVar) && !operation.equals(Operation.EQUAL_BOOLEAN) && !operation.equals(Operation.NOT_EQUAL)) { return GeoFunction.operationSymb(operation, (FunctionalNVar) lt, (FunctionalNVar) rt); } // we want to use function arithmetic in cases like f*2 or f+x^2, but // not for f(2), f'(2) etc. else if ((lt instanceof FunctionalNVar) && rt instanceof NumberValue && (operation.ordinal() < Operation.FUNCTION.ordinal())) { return GeoFunction.applyNumberSymb(operation, (FunctionalNVar) lt, right, true); } else if ((rt instanceof FunctionalNVar) && lt instanceof NumberValue) { return GeoFunction.applyNumberSymb(operation, (FunctionalNVar) rt, left, false); } return null; } private ExpressionValue listOperation(ListValue lt, Operation operation, ExpressionValue rt, ExpressionValue right, boolean b, StringTemplate tpl) { boolean symbolic = right.wrap().containsFreeFunctionVariable(null); ExpressionValue myRt = symbolic ? right : rt; MyList myList = symbolic ? lt.getMyList().deepCopy(kernel) : lt .getMyList(); // list lt operation rt myList.apply(operation, myRt, b, tpl); return myList; } /** * Checks whether first object equals second * * @param kernel * kernel * @param lt * first object * @param rt * second object * @return false if not defined */ public static MyBoolean evalEquals(Kernel kernel, ExpressionValue lt, ExpressionValue rt) { StringTemplate tpl = StringTemplate.defaultTemplate; // booleans if (lt instanceof BooleanValue && rt instanceof BooleanValue) { return new MyBoolean(kernel, ((BooleanValue) lt) .getBoolean() == ((BooleanValue) rt).getBoolean()); } else if (lt instanceof NumberValue && rt instanceof NumberValue) { return new MyBoolean(kernel, Kernel.isEqual(((NumberValue) lt).getDouble(), ((NumberValue) rt).getDouble())); } else if (lt instanceof TextValue && rt instanceof TextValue) { String strL = ((TextValue) lt).toValueString(tpl); String strR = ((TextValue) rt).toValueString(tpl); // needed for eg Sequence[If[Element[list1,i]=="b",0,1],i,i,i] if ((strL == null) || (strR == null)) { return new MyBoolean(kernel, false); } return new MyBoolean(kernel, strL.equals(strR)); } else if (lt instanceof ListValue && rt instanceof ListValue) { MyList list1 = ((ListValue) lt).getMyList(); MyList list2 = ((ListValue) rt).getMyList(); int size = list1.size(); if (size != list2.size()) { return new MyBoolean(kernel, false); } for (int i = 0; i < size; i++) { if (!evalEquals(kernel, list1.getListElement(i).evaluate(tpl), list2.getListElement(i).evaluate(tpl)).getBoolean()) { return new MyBoolean(kernel, false); } } return new MyBoolean(kernel, true); } else if (lt.isGeoElement() && rt.isGeoElement()) { GeoElement geo1 = (GeoElement) lt; GeoElement geo2 = (GeoElement) rt; return new MyBoolean(kernel, geo1.isEqual(geo2)); } else if (lt instanceof VectorValue && rt instanceof VectorValue) { VectorValue vec1 = (VectorValue) lt; VectorValue vec2 = (VectorValue) rt; return new MyBoolean(kernel, vec1.getVector().isEqual(vec2.getVector())); } else if (lt instanceof Vector3DValue && rt instanceof Vector3DValue) { Vector3DValue vec1 = (Vector3DValue) lt; Vector3DValue vec2 = (Vector3DValue) rt; return new MyBoolean(kernel, vec1.getVector().isEqual(vec2.getVector())); } return new MyBoolean(kernel, false); } /** * @param arg * vector or line * @param op * XCOORD or REAL * @return x coordinate */ public double handleXcoord(ExpressionValue arg, Operation op) { if (arg instanceof VectorValue) { return ((VectorValue) arg).getVector().getX(); } else if (arg instanceof Vector3DValue) { return ((Vector3DValue) arg).getPointAsDouble()[0]; } else if (arg instanceof GeoLine) { return ((GeoLine) arg).x; } else if (op == Operation.REAL && arg instanceof NumberValue) { // real(3) should return 3 return arg.evaluateDouble(); } else { polynomialOrDie(arg, op, op == Operation.XCOORD ? "x(" : "real("); return Double.NaN; } } /** * @param arg * vector or line * @param op * YCOORD or IMAGINARY * @return y coordinate */ public double handleYcoord(ExpressionValue arg, Operation op) { // y(vector) if (arg instanceof VectorValue) { return ((VectorValue) arg).getVector().getY(); } else if (arg instanceof Vector3DValue) { return ((Vector3DValue) arg).getPointAsDouble()[1]; } else if (arg instanceof GeoLine) { return ((GeoLine) arg).y; } else if (op == Operation.IMAGINARY && arg instanceof NumberValue) { // imaginary(3) should return 0 return 0; } else { polynomialOrDie(arg, op, op == Operation.YCOORD ? "y(" : "imaginary("); return Double.NaN; } } /** * @param lt * vector or line * @return z coordinate */ public double handleZcoord(ExpressionValue lt) { if (lt instanceof VectorValue) { return 0; } else if (lt instanceof Vector3DValue) { return ((Vector3DValue) lt).getPointAsDouble()[2]; } else if (lt instanceof GeoLine) { return ((GeoLine) lt).z; } polynomialOrDie(lt, Operation.YCOORD, "z("); return Double.NaN; } /** * Performs multiplication * * @param lt * left argument * @param rt * right argument * @param tpl * string template (may be string concatenation) * @param holdsLaTeXtext * whether parent node holds LaTeX * @return result */ public ExpressionValue handleMult(ExpressionValue lt, ExpressionValue rt, StringTemplate tpl, boolean holdsLaTeXtext) { MyDouble num; MyStringBuffer msb; // Log.debug(lt.getClass()+" "+lt.toString()); // Log.debug(rt.getClass()+" "+rt.toString()); if (lt instanceof NumberValue) { // number * number if (rt instanceof NumberValue) { num = ((NumberValue) lt).getNumber(); MyDouble.mult(num, (NumberValue) rt, num); return num; } // number * vector else if (rt instanceof VectorNDValue) { return multiply((NumberValue) lt, (VectorNDValue) rt); } // number * boolean -- already in number * number } // text concatenation (left) if (lt instanceof TextValue) { msb = ((TextValue) lt).getText(); if (holdsLaTeXtext) { msb.append(rt.toLaTeXString(false, tpl)); } else { if (rt.isGeoElement()) { GeoElement geo = (GeoElement) rt; msb.append(geo.toDefinedValueString(tpl)); } else { msb.append(rt.toValueString(tpl)); } } return msb; } // text concatenation (right) else if (rt instanceof TextValue) { msb = ((TextValue) rt).getText(); if (holdsLaTeXtext) { msb.insert(0, lt.toLaTeXString(false, tpl)); } else { if (lt.isGeoElement()) { GeoElement geo = (GeoElement) lt; msb.insert(0, geo.toDefinedValueString(tpl)); } else { msb.insert(0, lt.toValueString(tpl)); } } return msb; } else // number * ... // boolean * number if (lt instanceof BooleanValue && rt instanceof NumberValue) { num = ((NumberValue) rt).getNumber(); MyDouble.mult(num, ((BooleanValue) lt).getDouble(), num); return num; } // vector * ... else if (lt instanceof VectorNDValue) { // vector * number if (rt instanceof NumberValue) { return multiply((NumberValue) rt, (VectorNDValue) lt); } // vector * vector (inner/dot product) else if (rt instanceof VectorNDValue) { if (((VectorNDValue) lt).getMode() == Kernel.COORD_COMPLEX || ((VectorNDValue) rt) .getMode() == Kernel.COORD_COMPLEX) { // complex multiply return complexMult((VectorNDValue) lt, (VectorNDValue) rt, kernel); } return innerProduct((VectorNDValue) lt, (VectorNDValue) rt, kernel); } return illegalBinary(lt, rt, "IllegalMultiplication", "*"); } // polynomial * polynomial else if (lt instanceof TextValue) { msb = ((TextValue) lt).getText(); if (holdsLaTeXtext) { msb.append(rt.toLaTeXString(false, tpl)); } else { if (rt.isGeoElement()) { GeoElement geo = (GeoElement) rt; msb.append(geo.toDefinedValueString(tpl)); } else { msb.append(rt.toValueString(tpl)); } } return msb; } // text concatenation (right) else if (rt instanceof TextValue) { msb = ((TextValue) rt).getText(); if (holdsLaTeXtext) { msb.insert(0, lt.toLaTeXString(false, tpl)); } else { if (lt.isGeoElement()) { GeoElement geo = (GeoElement) lt; msb.insert(0, geo.toDefinedValueString(tpl)); } else { msb.insert(0, lt.toValueString(tpl)); } } return msb; } Log.debug(lt); Log.debug(rt); return illegalBinary(lt, rt, "IllegalMultiplication", "*"); } /** * * @param en * number * @param ev * vector * @return en*ev */ protected ExpressionValue multiply(NumberValue en, VectorNDValue ev) { if (ev instanceof VectorValue) { GeoVec2D vec = ((VectorValue) ev).getVector(); GeoVec2D.mult(vec, en.getDouble(), vec); return vec; } Geo3DVecInterface vec = ((Vector3DValue) ev).getVector(); vec.mult(en.getDouble()); return vec; } /** * * @param ev1 * first vector * @param ev2 * second vector * @param kernel0 * kernel * @return ev1*ev2 complex product */ protected ExpressionValue complexMult(VectorNDValue ev1, VectorNDValue ev2, Kernel kernel0) { GeoVec2D vec = ((VectorValue) ev1).getVector(); GeoVec2D.complexMultiply(vec, ((VectorValue) ev2).getVector(), vec); return vec; } /** * * @param ev1 * first vector * @param ev2 * second vector * @param kernel0 * kernel * @return ev1*ev2 inner product */ protected ExpressionValue innerProduct(VectorNDValue ev1, VectorNDValue ev2, Kernel kernel0) { MyDouble num = new MyDouble(kernel0); GeoVec2D.inner(((VectorValue) ev1).getVector(), ((VectorValue) ev2).getVector(), num); return num; } /** * Performs addition * * @param lt * left argument * @param rt * right argument * @param tpl * string template (may be string concatenation) * @param holdsLaTeXtext * whether parent node holds LaTeX * @return result */ public ExpressionValue handlePlus(ExpressionValue lt, ExpressionValue rt, StringTemplate tpl, boolean holdsLaTeXtext) { String[] str; MyDouble num; GeoVec2D vec; MyStringBuffer msb; if (lt instanceof NumberValue && rt instanceof NumberValue) { num = ((NumberValue) lt).getNumber(); MyDouble.add(num, ((NumberValue) rt).getNumber(), num); return num; } // vector + vector else if (lt instanceof VectorValue && rt instanceof VectorValue) { vec = ((VectorValue) lt).getVector(); GeoVec2D.add(vec, ((VectorValue) rt).getVector(), vec); return vec; } // vector + number (for complex addition) else if (lt instanceof VectorValue && rt instanceof NumberValue) { vec = ((VectorValue) lt).getVector(); GeoVec2D.add(vec, ((NumberValue) rt), vec); return vec; } // number + vector (for complex addition) else if (lt instanceof NumberValue && rt instanceof VectorValue) { vec = ((VectorValue) rt).getVector(); GeoVec2D.add(vec, ((NumberValue) lt), vec); return vec; } // list + vector else if (lt instanceof ListValue && rt instanceof VectorValue) { MyList list = ((ListValue) lt).getMyList(); if (list.size() > 0) { ExpressionValue ev = list.getListElement(0); if (ev instanceof NumberValue) { // eg {1,2} + (1,2) treat as // point, ev is evaluated // before // + point vec = ((VectorValue) rt).getVector(); GeoVec2D.add(vec, ((ListValue) lt), vec); return vec; } } // not a list with numbers, do list operation MyList myList = ((ListValue) lt).getMyList(); // list lt operation rt myList.applyRight(Operation.PLUS, rt, tpl); return myList; } // vector + list else if (rt instanceof ListValue && lt instanceof VectorValue) { MyList list = ((ListValue) rt).getMyList(); if (list.size() > 0) { ExpressionValue ev = list.getListElement(0); if (ev instanceof NumberValue) { // eg {1,2} + (1,2) treat as // point, ev is evaluated // before // + point vec = ((VectorValue) lt).getVector(); GeoVec2D.add(vec, ((ListValue) rt), vec); return vec; } } // not a list with numbers, do list operation MyList myList = ((ListValue) rt).getMyList(); // lt operation list rt myList.applyLeft(Operation.PLUS, lt, tpl); return myList; } // text concatenation (left) else if (lt instanceof TextValue) { msb = ((TextValue) lt).getText(); if (holdsLaTeXtext) { msb.append(rt.toLaTeXString(false, tpl)); } else { if (rt.isGeoElement()) { GeoElement geo = (GeoElement) rt; msb.append(geo.toDefinedValueString(tpl)); } else { msb.append(rt.toValueString(tpl)); } } return msb; } // text concatenation (right) else if (rt instanceof TextValue) { msb = ((TextValue) rt).getText(); if (holdsLaTeXtext) { msb.insert(0, lt.toLaTeXString(false, tpl)); } else { if (lt.isGeoElement()) { GeoElement geo = (GeoElement) lt; msb.insert(0, geo.toDefinedValueString(tpl)); } else { msb.insert(0, lt.toValueString(tpl)); } } return msb; } // polynomial + polynomial else { str = new String[] { "IllegalAddition", lt.toString(errorTemplate), "+", rt.toString(errorTemplate) }; Log.error(lt.getValueType() + "+" + rt.getValueType()); throw new MyError(l10n, str); } } /** * Performs division * * @param lt * left argument (evaluated) * @param rt * right argument (evaluated) * @param left * left argument before evaluation * @param right * right argument before evaluation * * @return result */ public ExpressionValue handleDivide(ExpressionValue lt, ExpressionValue rt, ExpressionValue left, ExpressionValue right) { // sin(number) String[] str; MyDouble num; GeoVec2D vec; if (rt instanceof NumberValue) { // number / number if (lt instanceof NumberValue) { num = ((NumberValue) lt).getNumber(); MyDouble.div(num, ((NumberValue) rt).getNumber(), num); return num; } // vector / number else if (lt instanceof VectorValue) { vec = ((VectorValue) lt).getVector(); GeoVec2D.div(vec, ((NumberValue) rt).getDouble(), vec); return vec; } else if (lt instanceof GeoFunction) { return GeoFunction.applyNumberSymb(Operation.DIVIDE, (GeoFunction) lt, right, true); } /* * // number * 3D vector else if (lt.isVector3DValue()) { Geo3DVec * vec3D = ((Vector3DValue)lt).get3DVec(); Geo3DVec.div(vec3D, * ((NumberValue)rt).getDouble(), vec3D); return vec3D; } */ else { str = new String[] { "IllegalDivision", lt.toString(errorTemplate), "/", rt.toString(errorTemplate) }; throw new MyError(l10n, str); } } // polynomial / polynomial // vector / vector (complex division Michael Borcherds 2007-12-09) else if (lt instanceof VectorValue && rt instanceof VectorValue) { vec = ((VectorValue) lt).getVector(); GeoVec2D.complexDivide(vec, ((VectorValue) rt).getVector(), vec); return vec; } // number / vector (complex division Michael Borcherds 2007-12-09) else if (lt instanceof NumberValue && rt instanceof VectorValue) { vec = ((VectorValue) rt).getVector(); // just to // initialise // vec GeoVec2D.complexDivide((NumberValue) lt, ((VectorValue) rt).getVector(), vec); return vec; } else if ((rt instanceof GeoFunction) && lt instanceof NumberValue) { return GeoFunction.applyNumberSymb(Operation.DIVIDE, (GeoFunction) rt, left, false); } else { str = new String[] { "IllegalDivision", lt.toString(errorTemplate), "/", rt.toString(errorTemplate) }; throw new MyError(l10n, str); } } /** * Performs subtraction * * @param lt * left argument (evaluated) * @param rt * right argument (evaluated) * @return result */ public ExpressionValue handleMinus(ExpressionValue lt, ExpressionValue rt) { String[] str; MyDouble num; GeoVec2D vec; // number - number if (lt instanceof NumberValue && rt instanceof NumberValue) { num = ((NumberValue) lt).getNumber(); MyDouble.sub(num, (NumberValue) rt, num); return num; } // vector - vector else if (lt instanceof VectorValue && rt instanceof VectorValue) { vec = ((VectorValue) lt).getVector(); GeoVec2D.sub(vec, ((VectorValue) rt).getVector(), vec); return vec; } // 3D vector - 3D vector /* * else if (lt.isVector3DValue() && rt.isVector3DValue()) { Geo3DVec * vec3D = ((Vector3DValue)lt).get3DVec(); Geo3DVec.sub(vec3D, * ((Vector3DValue)rt).get3DVec(), vec3D); return vec3D; } */ // vector - number (for complex subtraction) else if (lt instanceof VectorValue && rt instanceof NumberValue) { vec = ((VectorValue) lt).getVector(); GeoVec2D.sub(vec, ((NumberValue) rt), vec); return vec; } // number - vector (for complex subtraction) else if (lt instanceof NumberValue && rt instanceof VectorValue) { vec = ((VectorValue) rt).getVector(); GeoVec2D.sub(((NumberValue) lt), vec, vec); return vec; } // list - vector else if (lt instanceof ListValue && rt instanceof VectorValue) { vec = ((VectorValue) rt).getVector(); GeoVec2D.sub(vec, ((ListValue) lt), vec, false); return vec; } // vector - list else if (rt instanceof ListValue && lt instanceof VectorValue) { vec = ((VectorValue) lt).getVector(); GeoVec2D.sub(vec, ((ListValue) rt), vec, true); return vec; } else if (lt instanceof TextValue) { return handlePlus(lt, rt.wrap().multiply(-1) .evaluate(StringTemplate.defaultTemplate), StringTemplate.defaultTemplate, false); } // polynomial - polynomial else { str = new String[] { "IllegalSubtraction", lt.toString(errorTemplate), "-", rt.toString(errorTemplate) }; Log.error(lt.getValueType() + " - " + rt.getValueType()); throw new MyError(l10n, str); } } /** * Performs power * * @param lt * left argument (evaluated) * @param rt * right argument (evaluated) * @param right * right argument before evaluation * * @return result */ public ExpressionValue handlePower(ExpressionValue lt, ExpressionValue rt, ExpressionValue right) { String[] str; MyDouble num; GeoVec2D vec, vec2; // number ^ number if (lt instanceof NumberValue && rt instanceof NumberValue) { num = ((NumberValue) lt).getNumber(); double base = num.getDouble(); MyDouble exponent = ((NumberValue) rt).getNumber(); // special case: e^exponent (Euler number) if (MyDouble.exactEqual(base, Math.E)) { return exponent.exp(); } // special case: left side is negative and // right side is a fraction a/b with a and b integers // x^(a/b) := (x^a)^(1/b) if ((base < 0) && right.isExpressionNode() && ((ExpressionNode) right) .getOperation() == Operation.DIVIDE) { num.set(ExpressionNodeEvaluator.negPower(base, right)); return num; } // standard case MyDouble.pow(num, exponent, num); return num; } /* * // vector ^ 2 (inner product) (3D) else if (lt.isVector3DValue() && * rt.isNumberValue()) { num = ((NumberValue)rt).getNumber(); Geo3DVec * vec3D = ((Vector3DValue)lt).get3DVec(); if (num.getDouble() == 2.0) { * Geo3DVec.inner(vec3D, vec3D, num); } else { num.set(Double.NaN); } * return num; } */ // vector ^ 2 (inner product) else if (lt instanceof VectorValue && rt instanceof NumberValue) { // if (!rt.isConstant()) { // String [] str = new String[]{ "ExponentMustBeConstant", // lt.toString(), // "^", rt.toString() }; // throw new MyError(l10n, str); // } vec = ((VectorValue) lt).getVector(); if (vec.getMode() == Kernel.COORD_COMPLEX) { // complex power GeoVec2D.complexPower(vec, ((NumberValue) rt), vec); return vec; } num = ((NumberValue) rt).getNumber(); // inner/scalar/dot product if (num.getDouble() == 2.0) { GeoVec2D.inner(vec, vec, num); return num; } num.set(Double.NaN); return num; // String [] str = new String[]{ "IllegalExponent", // lt.toString(), // "^", rt.toString() }; // throw new MyError(l10n, str); } else if (lt instanceof TextValue && rt instanceof NumberValue) { String txt = ((TextValue) lt).getTextString(); MyStringBuffer result = new MyStringBuffer(kernel, ""); for (int i = 0; i < rt.evaluateDouble(); i++) { result.append(txt); } return result; } else if (lt instanceof VectorValue && rt instanceof VectorValue) { // if (!rt.isConstant()) { // String [] str = new String[]{ "ExponentMustBeConstant", // lt.toString(), // "^", rt.toString() }; // throw new MyError(l10n, str); // } vec = ((VectorValue) lt).getVector(); vec2 = ((VectorValue) rt).getVector(); // complex power GeoVec2D.complexPower(vec, vec2, vec); return vec; } else if (lt instanceof NumberValue && rt instanceof VectorValue) { // if (!rt.isConstant()) { // String [] str = new String[]{ "ExponentMustBeConstant", // lt.toString(), // "^", rt.toString() }; // throw new MyError(l10n, str); // } num = ((NumberValue) lt).getNumber(); vec = ((VectorValue) rt).getVector(); // real ^ complex GeoVec2D.complexPower(num, vec, vec); return vec; } // polynomial ^ number else { Log.error(lt.getValueType() + "^" + rt.getValueType()); str = new String[] { "IllegalExponent", lt.toString(errorTemplate), "^", rt.toString(errorTemplate) }; throw new MyError(l10n, str); } } /** * @param base0 * base * @param right * exponent, must be expression of the form a/b * @return base^exponent */ static double negPower(double base0, ExpressionValue right) { double base = base0; ExpressionNode node = (ExpressionNode) right; // check if we have a/b with a and b integers double a = node.getLeft().evaluateDouble(); long al = Math.round(a); if (Kernel.isEqual(a, al)) { // a is integer double b = node.getRight().evaluateDouble(); long bl = Math.round(b); if (b == 0) { // (x^a)^(1/0) return (Double.NaN); } else if (Kernel.isEqual(b, bl)) { // b is // integer // divide through greatest common divisor of a // and b long gcd = Kernel.gcd(al, bl); // fix for java.lang.ArithmeticException: divide by zero // https://play.google.com/apps/publish/?dev_acc=05873811091523087820#ErrorClusterDetailsPlace:p=org.geogebra.android&et=CRASH&lr=LAST_7_DAYS&ecn=java.lang.ArithmeticException&tf=SourceFile&tc=org.geogebra.common.kernel.arithmetic.ExpressionNodeEvaluator&tm=negPower&nid&an&c&s=new_status_desc&ed=0 if (gcd == 0) { return Double.NaN; } al = al / gcd; bl = bl / gcd; // we will now evaluate (x^a)^(1/b) instead of // x^(a/b) // set base = x^a if (al != 1) { base = Math.pow(base, al); } if (base > 0) { // base > 0 => base^(1/b) is no problem return Math.pow(base, 1d / bl); } boolean oddB = (Math.abs(bl) % 2) == 1; if (oddB) { // base < 0 and b odd: (base)^(1/b) = // -(-base^(1/b)) return (-Math.pow(-base, 1d / bl)); } // base < 0 and a & b even: (base)^(1/b) // = undefined return (Double.NaN); } } return MyDouble.pow(base, right.evaluateDouble()); } /** * Computes value of function in given point (or throws error) * * @param lt * function * @param rt * value of variable * @param left * left (function) before evaluation * @return value of function at given point */ public ExpressionValue handleFunction(ExpressionValue lt, ExpressionValue rt, ExpressionValue left) { String[] str; // function(number) if (rt instanceof NumberValue) { if (lt instanceof Evaluatable) { NumberValue arg = (NumberValue) rt; if ((lt instanceof GeoFunction) && ((GeoFunction) lt).isBooleanFunction()) { return new MyBoolean(kernel, ((GeoFunction) lt) .evaluateBoolean(arg.getDouble())); } return arg.getNumber().apply((Evaluatable) lt); } else if (lt instanceof GeoCasCell && ((GeoCasCell) lt) .getOutputValidExpression() instanceof Function) { // first we give the expression to the cas // and then the result of that to the geogebra // so that the cas result will be converted ExpressionNode node = new ExpressionNode(kernel, lt, Operation.FUNCTION, rt); FunctionExpander fex = FunctionExpander.getCollector(); node = (ExpressionNode) node.wrap().getCopy(kernel) .traverse(fex); String result = kernel.getGeoGebraCAS().evaluateGeoGebraCAS( node, null, StringTemplate.numericNoLocal, null, kernel); boolean mode = kernel.isSilentMode(); kernel.setSilentMode(true); GeoElementND geo = kernel.getAlgebraProcessor() .processAlgebraCommand(result, false)[0]; kernel.setSilentMode(mode); return geo; } else if (left instanceof GeoCasCell && ((GeoCasCell) left).getTwinGeo() instanceof GeoLine) { return ((NumberValue) rt).getNumber() .apply((Evaluatable) ((GeoCasCell) left).getTwinGeo()); } else { Log.debug(lt); } } else if (rt instanceof VectorNDValue) { if (lt instanceof Evaluatable) { VectorNDValue pt = (VectorNDValue) rt; if (lt instanceof GeoFunction) { FunctionNVar fun = ((GeoFunction) lt).getFunction(); if (fun.isBooleanFunction()) { return new MyBoolean(kernel, fun.evaluate(pt) > 0); } return new MyDouble(kernel, fun.evaluate(pt)); } else if (lt instanceof GeoFunctionable) { // eg GeoLine return new MyDouble(kernel, ((GeoFunctionable) lt) .getGeoFunction().getFunction().evaluate(pt)); } else { Log.warn("missing case in ExpressionNodeEvaluator"); } } } // Application.debug("FUNCTION lt: " + lt + ", " + lt.getClass() // + " rt: " + rt + ", " + rt.getClass()); str = new String[] { "IllegalArgument", rt.toString(errorTemplate) }; throw new MyError(l10n, str); } /** * Evaluate function in multiple variables * * @param lt * left argument (function) * @param rt * right argument (MyList of variable values) * @return result (number) */ public ExpressionValue handleFunctionNVar(ExpressionValue lt, ExpressionValue rt) { if (rt instanceof ListValue && (lt instanceof FunctionalNVar)) { FunctionNVar funN = ((FunctionalNVar) lt).getFunction(); ListValue list = (ListValue) rt; if (funN.getVarNumber() == list.size() || funN.getVarNumber() == 1) { double[] args = list.toDouble(0); if (args != null) { if (funN.isBooleanFunction()) { return new MyBoolean(kernel, funN.evaluateBoolean(args)); } return new MyDouble(kernel, funN.evaluate(args)); } // let's assume that we called this as f(x,y) and we // actually want the function return lt; } else if (list.size() == 1) { ExpressionValue ev = list.getMyList().getListElement(0) .evaluate(StringTemplate.defaultTemplate); if ((funN.getVarNumber() == 2 || funN.getVarNumber() == 3) && (ev instanceof VectorNDValue)) { VectorNDValue pt = (VectorNDValue) ev; if (funN.isBooleanFunction()) { return new MyBoolean(kernel, funN.evaluate(pt) > 0); } return new MyDouble(kernel, funN.evaluate(pt)); } else if ((ev instanceof ListValue) && ((ListValue) ev) .getMyList().getListElement(0).evaluate( StringTemplate.defaultTemplate) instanceof NumberValue) { // TODO can we avoid evaluate here double[] vals = ((ListValue) ev).toDouble(0); if (vals != null) { if (funN.isBooleanFunction()) { return new MyBoolean(kernel, funN.evaluateBoolean(vals)); } return new MyDouble(kernel, funN.evaluate(vals)); } } else if (ev instanceof ListValue) { // f(x,y) called with // list of points MyList l = ((ListValue) ev).getMyList(); MyList ret = new MyList(kernel); for (int i = 0; i < l.size(); i++) { MyList lArg = new MyList(kernel); // need to wrap // arguments to // f(x,y) in // MyList lArg.addListElement(l.getListElement(i)); ret.addListElement(new ExpressionNode(kernel, funN, Operation.FUNCTION_NVAR, lArg)); } return ret; } // let's assume that we called this as f(x,y) and we // actually want the function return lt; } } // Application.debug("FUNCTION lt: " + lt + ", " + lt.getClass() + // " rt: " + rt + ", " + rt.getClass()); String[] str3 = { "IllegalArgument", rt.toString(errorTemplate) }; throw new MyError(l10n, str3); } /** * Throw error for unary boolean operation * * @param arg * operation argument * @param opname * operation string * @return nothing (error is thrown) * @throws MyError * (always) */ public ExpressionValue illegalBoolean(ExpressionValue arg, String opname) { String[] str = new String[] { "IllegalBoolean", opname, arg.toString(errorTemplate) }; throw new MyError(l10n, str); } /** * Throw illegal argument exception for multivariable builtin function * * @param lt * left argument * @param rt * right argument * @param opname * operation name * @return nothing (error is thrown) * @throws MyError * (always) */ public ExpressionValue illegalArgument(ExpressionValue lt, ExpressionValue rt, String opname) { String[] str = new String[] { "IllegalArgument", opname, lt.toString(errorTemplate), ",", rt.toString(errorTemplate), ")" }; throw new MyError(l10n, str); } /** * Throw simple illegal argument exception * * @param arg * argument * @return nothing (error is thrown) * @throws MyError * (always) */ public ExpressionValue illegalArgument(ExpressionValue arg) { String[] str = new String[] { "IllegalArgument", arg.toString(errorTemplate) }; throw new MyError(l10n, str); } /** * Throw error for infix binary operation * * @param lt * left argument * @param rt * right argument * @param type * type (InvalidMultiplication, InvalidAddition, ...) * @param opname * operator string * @return nothing (error is thrown) * @throws MyError * (always) */ public ExpressionValue illegalBinary(ExpressionValue lt, ExpressionValue rt, String type, String opname) { String[] str = new String[] { type, lt.toString(errorTemplate), opname, rt.toString(errorTemplate) }; throw new MyError(l10n, str); } /** * Throw illegal comparison error * * @param lt * left argument * @param rt * rigt argument * @param opname * comparison operator * @return nothing (error is thrown) * @throws MyError * (always) */ public ExpressionValue illegalComparison(ExpressionValue lt, ExpressionValue rt, String opname) { String[] str = new String[] { "IllegalComparison", lt.toString(errorTemplate), opname, rt.toString(errorTemplate) }; throw new MyError(l10n, str); } /** * Throw illegal list operation error * * @param lt * left argument * @param rt * rigt argument * @param opname * list operator * @return nothing (error is thrown) * @throws MyError * (always) */ public ExpressionValue illegalListOp(ExpressionValue lt, ExpressionValue rt, String opname) { String[] str = new String[] { "IllegalListOperation", lt.toString(errorTemplate), opname, rt.toString(errorTemplate) }; throw new MyError(l10n, str); } /** * Check whether lt is constant polynomial and compute op(lt) if it is; if * not throw illegal argument "opname lt)" * * @param lt * argument * @param op * operation * @param opname * operation name (including "(") * @return op(lt) or error * @throws MyError * if not polynomial or not constant */ public ExpressionValue polynomialOrDie(ExpressionValue lt, Operation op, String opname) { return polynomialOrDie(lt, op, opname, ")"); } /** * Check whether lt is constant polynomial and compute op(lt) if it is; if * not throw illegal argument "prefix lt suffix" * * @param lt * argument * @param op * operation * @param prefix * prefix of error message * @param suffix * of error message * @return op(lt) if lt is constant poly * @throws MyError * if not polynomial or not constant */ public ExpressionValue polynomialOrDie(ExpressionValue lt, Operation op, String prefix, String suffix) { String[] strings = new String[] { "IllegalArgument", prefix, lt.toString(errorTemplate), suffix }; throw new MyError(l10n, strings); } /** * Performs vector product * * @param lt * left argument * @param rt * right argument * @param tpl * string template (may be string concatenation) * @param holdsLaTeXtext * whether parent node holds LaTeX * @return result */ public ExpressionValue handleVectorProduct(ExpressionValue lt, ExpressionValue rt, StringTemplate tpl, boolean holdsLaTeXtext) { if (lt instanceof VectorNDValue && rt instanceof VectorNDValue) { return vectorProduct((VectorNDValue) lt, (VectorNDValue) rt); } return illegalBinary(lt, rt, "IllegalMultiplication", ExpressionNodeConstants.strVECTORPRODUCT); } /** * * @param v1 * first vector * @param v2 * second vector * @return v1 * v2 vector product */ protected ExpressionValue vectorProduct(VectorNDValue v1, VectorNDValue v2) { GeoVecInterface vec1 = v1.getVector(); GeoVecInterface vec2 = v2.getVector(); MyDouble num = new MyDouble(kernel); GeoVec2D.vectorProduct(vec1, vec2, num); return num; } /** * @return kernel */ public Kernel getKernel() { return kernel; } /** * @param lt * list from which element is to be chosen * @param rt * list of indices * @param skip * 0 to evaluate completely, >0 to skip last skip arguments * @return list element */ public ExpressionValue handleElementOf(ExpressionValue lt, ExpressionValue rt, int skip) { // TODO not implemented #1115 // Application.debug(rt.getClass()+" "+rt.getClass()); if (lt instanceof GeoList && rt instanceof ListValue) { GeoList sublist = ((GeoList) lt); ListValue lv = (ListValue) rt; int idx = -1; // convert list1(1,2) into Element[Element[list1,1],2] boolean sublistUndefined = false; for (int i = 0; i < lv.size(); i++) { ExpressionNode ith = (ExpressionNode) lv.getMyList() .getListElement(i); idx = (int) Math.round(ith.evaluateDouble()) - 1; if (i < lv.size() - 1) { GeoElement nextSublist; if (idx < 0) { idx = sublist.size() + 1 + idx; } if (idx >= 0 && idx < sublist.size()) { nextSublist = sublist.get(idx); } else { nextSublist = sublist.createTemplateElement(); sublistUndefined = true; nextSublist.setUndefined(); } if (nextSublist instanceof GeoList) { sublist = (GeoList) nextSublist; } else if (i == lv.size() - 2 && nextSublist instanceof GeoFunction) { if (skip > 0) { return functionOrUndefined(nextSublist); } return new MyDouble(getKernel(), ((GeoFunction) nextSublist) .value(lv.getListElement(i + 1) .evaluateDouble())); } else if (nextSublist instanceof GeoFunctionNVar && i == lv.size() - ((GeoFunctionNVar) nextSublist) .getVarNumber() - 1) { if (skip > 0) { return functionNvarOrUndefined(nextSublist); } return new MyDouble(getKernel(), ((GeoFunctionNVar) nextSublist) .evaluate(lv.toDouble(1))); } else { Log.debug("Wrong depth for Element: " + nextSublist + " :" + (lv.size() - i - 1)); return new MyDouble(getKernel(), Double.NaN); } } } if (idx < 0) { idx = sublist.size() + 1 + idx; } GeoElement ret; if (idx >= 0 && idx < sublist.size() && !sublistUndefined) { ret = sublist.get(idx).copyInternal(sublist.getConstruction()); } else { ret = sublist.createTemplateElement(); ret.setUndefined(); } if (ret instanceof GeoFunction) { MyList list = lv.getMyList(); FunctionVariable fv = new FunctionVariable(kernel); list.addListElement(fv); return new Function(new ExpressionNode(kernel, lt, Operation.ELEMENT_OF, list), fv); } if (ret instanceof GeoFunctionNVar) { MyList list = lv.getMyList(); FunctionVariable[] vars = ((GeoFunctionNVar) ret) .getFunctionVariables(); for (int i = 0; i < vars.length; i++) { list.addListElement(vars[i]); } return new FunctionNVar(new ExpressionNode(kernel, lt, Operation.ELEMENT_OF, list), vars); } return ret; } return illegalArgument(lt); } private ExpressionValue functionOrUndefined(GeoElement nextSublist) { return nextSublist.isDefined() ? nextSublist : new Function(new ExpressionNode(getKernel(), Double.NaN), new FunctionVariable(getKernel())); } private ExpressionValue functionNvarOrUndefined(GeoElement nextSublist) { return nextSublist.isDefined() ? nextSublist : new FunctionNVar(new ExpressionNode(getKernel(), Double.NaN), new FunctionVariable[] {}); } }