/* 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. */ /* * VarString.java * * Created on 18. November 2001, 14:49 */ package org.geogebra.common.kernel.arithmetic; import java.util.HashSet; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; 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.parser.FunctionParser; import org.geogebra.common.main.MyParseError; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; /** * * @author Markus Hohenwarter * */ public class Variable extends ValidExpression { private String name; private Kernel kernel; /** * Creates new VarString * * @param kernel * kernel * @param name * variable name **/ public Variable(Kernel kernel, String name) { this.name = name; this.kernel = kernel; } @Override public Variable deepCopy(Kernel kernel1) { return new Variable(kernel1, name); } /** * @param tpl * string template * @return variable name */ public String getName(StringTemplate tpl) { return toString(tpl); } @Override public boolean isConstant() { return false; } @Override public boolean isLeaf() { return true; } /** * Looks up the name of this variable in the kernel and returns the * according GeoElement object. */ private GeoElement resolve(boolean throwError) { return resolve(!kernel.isResolveUnkownVarsAsDummyGeos(), throwError); } /** * Looks up the name of this variable in the kernel and returns the * according GeoElement object. * * @param allowAutoCreateGeoElement * true to allow creating new objects * @param throwError * when true, error is thrown when geo not found. Otherwise null * is returned in such case. * @return GeoElement with same label */ protected GeoElement resolve(boolean allowAutoCreateGeoElement, boolean throwError) { // keep bound CAS variables when resolving a CAS expression if (kernel.isResolveUnkownVarsAsDummyGeos()) { // resolve unknown variable as dummy geo to keep its name and // avoid an "unknown variable" error message return new GeoDummyVariable(kernel.getConstruction(), name); } // lookup variable name, create missing variables automatically if // allowed GeoElement geo = kernel.lookupLabel(name, allowAutoCreateGeoElement, kernel.isResolveUnkownVarsAsDummyGeos()); if (geo != null || !throwError) { return geo; } // if we get here we couldn't resolve this variable name as a GeoElement String[] str = { "UndefinedVariable", name }; Log.debug(kernel.getClass()); throw new MyParseError(kernel.getApplication().getLocalization(), str); } /** * Looks up the name of this variable in the kernel and returns the * according GeoElement object. For absolute spreadsheet reference names * like A$1 or $A$1 a special ExpressionNode wrapper object is returned that * preserves this special name for displaying of the expression. * * @return GeoElement whose label is name of this variable or ExpressionNode * wrapping spreadsheet reference */ final public ExpressionValue resolveAsExpressionValue() { GeoElement geo = resolve(false); if (geo == null) { if (kernel.getConstruction().isRegistredFunctionVariable(name)) { return new FunctionVariable(kernel, name); } ExpressionValue ret = replacement(kernel, name); return ret instanceof Variable ? resolve(true) : ret; } // spreadsheet dollar sign reference // need to avoid CAS cell references, eg $1 (see #3206) if (name.indexOf('$') > -1 && !(geo instanceof GeoCasCell) && !(geo instanceof GeoDummyVariable)) { // row and/or column dollar sign present? boolean col$ = name.indexOf('$') == 0; boolean row$ = name.length() > 2 && name.indexOf('$', 1) > -1; Operation operation = Operation.NO_OPERATION; if (row$ && col$) { operation = Operation.$VAR_ROW_COL; } else if (row$) { operation = Operation.$VAR_ROW; } else { // if (col$) operation = Operation.$VAR_COL; } // build an expression node that wraps the resolved geo return new ExpressionNode(kernel, geo, operation, null); } // standard case: no dollar sign return geo; } /** * @param kernel * kernel * @param name * variable name * @return interpretation, eg axxx -> a*x*x */ public static ExpressionValue replacement(Kernel kernel, String name) { // holds powers of x,y,z: eg {"xxx","y","zzzzz"} if (name.endsWith("'") && kernel.getAlgebraProcessor().enableVectors()) { ExpressionValue ret = asDerivative(kernel, name); if (ret != null) { return ret; } } int[] exponents = new int[] { 0, 0, 0, 0, 0 }; int i; GeoElement geo2 = null; String nameNoX = name; int degPower = 0; while (nameNoX.length() > 0 && (geo2 == null) && nameNoX.endsWith("deg")) { int length = nameNoX.length(); degPower++; nameNoX = nameNoX.substring(0, length - 3); if (length > 3) { geo2 = kernel.lookupLabel(nameNoX); } } for (i = nameNoX.length() - 1; i >= 0; i--) { char c = name.charAt(i); if ((c < 'x' || c > 'z') && c != Unicode.theta && c != Unicode.pi) { break; } exponents[c == Unicode.pi ? 4 : (c == Unicode.theta ? 3 : c - 'x')]++; nameNoX = name.substring(0, i); geo2 = kernel.lookupLabel(nameNoX); Operation op = kernel.getApplication().getParserFunctions() .get(nameNoX, 1); if (op != null && op != Operation.XCOORD && op != Operation.YCOORD && op != Operation.ZCOORD) { return xyzPiDegPower(kernel, exponents, degPower).apply(op); } else if (nameNoX.startsWith("log_")) { ExpressionValue index = FunctionParser.getLogIndex(nameNoX, kernel); ExpressionValue arg = xyzPiDegPower(kernel, exponents, degPower); if (index != null) { return new ExpressionNode(kernel, index, Operation.LOGB, arg); } } if (geo2 != null) { break; } } while (nameNoX.length() > 0 && geo2 == null && (nameNoX.startsWith("pi") || nameNoX.charAt(0) == Unicode.pi)) { int chop = nameNoX.charAt(0) == Unicode.pi ? 1 : 2; exponents[4]++; nameNoX = nameNoX.substring(chop); if (i + 1 >= chop) { geo2 = kernel.lookupLabel(nameNoX); } if (geo2 != null) { break; } } if (nameNoX.length() > 0 && geo2 == null) { return new Variable(kernel, nameNoX); } ExpressionNode powers = xyzPowers(kernel, exponents); if (geo2 == null) { return exponents[4] == 0 && degPower == 0 ? powers : powers.multiply(piDegTo(exponents[4], degPower, kernel)); } return exponents[4] == 0 && degPower == 0 ? powers.multiply(geo2) : powers.multiply(geo2) .multiply(piDegTo(exponents[4], degPower, kernel)); } private static ExpressionNode xyzPiDegPower(Kernel kernel, int[] exponents, int degPower) { if (exponents[4] == 0 && degPower == 0) { return xyzPowers(kernel, exponents); } return xyzPowers(kernel, exponents) .multiply(piDegTo(exponents[4], degPower, kernel)); } private static ExpressionValue asDerivative(Kernel kernel, String funcName) { int index = funcName.length() - 1; int order = 0; while (index >= 0 && funcName.charAt(index) == '\'') { order++; index--; } GeoElement geo = null; while (index < funcName.length()) { String label = funcName.substring(0, index + 1); geo = kernel.lookupLabel(label); // stop if f' is defined but f is not defined, see #1444 if (geo != null && (geo.isGeoFunction() || geo.isGeoCurveCartesian())) { break; } order--; index++; } if (geo != null && (geo.isGeoFunction() || geo.isGeoCurveCartesian())) { return FunctionParser.derivativeNode(kernel, geo, order, geo.isGeoCurveCartesian(), new FunctionVariable(kernel)); } return null; } private static ExpressionNode xyzPowers(Kernel kernel, int[] exponents) { return new ExpressionNode(kernel, new FunctionVariable(kernel, "x")) .power(new MyDouble(kernel, exponents[0])) .multiplyR(new ExpressionNode(kernel, new FunctionVariable(kernel, "y")) .power(new MyDouble(kernel, exponents[1]))) .multiplyR(new ExpressionNode(kernel, new FunctionVariable(kernel, "z")) .power(new MyDouble(kernel, exponents[2]))) .multiplyR(new ExpressionNode(kernel, new FunctionVariable(kernel, Unicode.thetaStr)) .power(new MyDouble(kernel, exponents[3]))); } private static ExpressionNode piDegTo(int piPower, int degPower, Kernel kernel2) { ExpressionNode piExp = piPower > 0 ? new MySpecialDouble(kernel2, Math.PI, Unicode.PI_STRING) .wrap().power(piPower) : null; ExpressionNode degExp = degPower > 0 ? new MyDouble(kernel2, MyMath.DEG) .setAngle().wrap().power(degPower) : null; return degExp == null ? piExp : (piExp == null ? degExp : piExp.multiply(degExp)); } @Override public HashSet<GeoElement> getVariables() { HashSet<GeoElement> ret = new HashSet<GeoElement>(); ret.add(resolve(true)); return ret; } @Override public void resolveVariables(EvalInfo info) { // this has to be handled in ExpressionNode } @Override public String toString(StringTemplate tpl) { return tpl.printVariableName(name); } @Override public String toValueString(StringTemplate tpl) { return toString(tpl); } @Override final public String toLaTeXString(boolean symbolic, StringTemplate tpl) { return toString(tpl); } @Override public boolean isNumberValue() { return false; } @Override final public boolean isVariable() { return true; } @Override final public boolean contains(ExpressionValue ev) { return ev == this; } @Override public String toOutputValueString(StringTemplate tpl) { return toValueString(tpl); } /** * @return kernel */ public Kernel getKernel() { return kernel; } @Override public boolean hasCoords() { GeoElement ge = kernel.lookupLabel(name, false, true); if (ge != null && !(ge instanceof GeoDummyVariable)) { return ge.hasCoords(); } return false; } /** * force the name to s, used by RelativeCopy * * @param s * new name */ public void setName(String s) { name = s; } /** * @return variable name */ public String getName() { return name; } @Override public ExpressionNode wrap() { return new ExpressionNode(kernel, this); } @Override public ValueType getValueType() { return ValueType.UNKNOWN; } }