/* 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. */ package org.geogebra.common.kernel.commands; import java.util.ArrayList; import java.util.Iterator; import java.util.Set; import java.util.TreeSet; import org.geogebra.common.gui.inputfield.InputHelper; import org.geogebra.common.io.MathMLParser; import org.geogebra.common.kernel.CircularDefinitionException; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.KernelCAS; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.algos.AlgoDependentBoolean; import org.geogebra.common.kernel.algos.AlgoDependentConic; import org.geogebra.common.kernel.algos.AlgoDependentEquationList; import org.geogebra.common.kernel.algos.AlgoDependentFunction; import org.geogebra.common.kernel.algos.AlgoDependentFunctionNVar; import org.geogebra.common.kernel.algos.AlgoDependentGeoCopy; import org.geogebra.common.kernel.algos.AlgoDependentInterval; import org.geogebra.common.kernel.algos.AlgoDependentLine; import org.geogebra.common.kernel.algos.AlgoDependentListExpression; import org.geogebra.common.kernel.algos.AlgoDependentNumber; import org.geogebra.common.kernel.algos.AlgoDependentPoint; import org.geogebra.common.kernel.algos.AlgoDependentText; import org.geogebra.common.kernel.algos.AlgoDependentVector; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.AlgoLaTeX; import org.geogebra.common.kernel.arithmetic.AssignmentType; import org.geogebra.common.kernel.arithmetic.BooleanValue; import org.geogebra.common.kernel.arithmetic.Command; import org.geogebra.common.kernel.arithmetic.Equation; import org.geogebra.common.kernel.arithmetic.EquationValue; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.Function; import org.geogebra.common.kernel.arithmetic.FunctionNVar; import org.geogebra.common.kernel.arithmetic.FunctionVariable; import org.geogebra.common.kernel.arithmetic.Inspecting; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.MyList; import org.geogebra.common.kernel.arithmetic.MyStringBuffer; import org.geogebra.common.kernel.arithmetic.MyVecNode; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.arithmetic.Polynomial; import org.geogebra.common.kernel.arithmetic.TextValue; import org.geogebra.common.kernel.arithmetic.Traversing; import org.geogebra.common.kernel.arithmetic.Traversing.CollectUndefinedVariables; import org.geogebra.common.kernel.arithmetic.Traversing.CommandReplacer; import org.geogebra.common.kernel.arithmetic.Traversing.FVarCollector; import org.geogebra.common.kernel.arithmetic.Traversing.ReplaceUndefinedVariables; import org.geogebra.common.kernel.arithmetic.Traversing.VariableReplacer; import org.geogebra.common.kernel.arithmetic.ValidExpression; import org.geogebra.common.kernel.arithmetic.Variable; import org.geogebra.common.kernel.arithmetic.VectorValue; import org.geogebra.common.kernel.arithmetic3D.Vector3DValue; import org.geogebra.common.kernel.geos.GeoAngle; import org.geogebra.common.kernel.geos.GeoAngle.AngleStyle; import org.geogebra.common.kernel.geos.GeoBoolean; import org.geogebra.common.kernel.geos.GeoCasCell; import org.geogebra.common.kernel.geos.GeoConic; 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.GeoFunctionNVar; import org.geogebra.common.kernel.geos.GeoFunctionable; import org.geogebra.common.kernel.geos.GeoInterval; import org.geogebra.common.kernel.geos.GeoLine; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoNumberValue; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoScriptAction; import org.geogebra.common.kernel.geos.GeoText; import org.geogebra.common.kernel.geos.GeoVec2D; import org.geogebra.common.kernel.geos.GeoVec3D; import org.geogebra.common.kernel.geos.GeoVector; import org.geogebra.common.kernel.implicit.AlgoDependentImplicitPoly; import org.geogebra.common.kernel.implicit.GeoImplicit; import org.geogebra.common.kernel.implicit.GeoImplicitCurve; import org.geogebra.common.kernel.kernelND.GeoConicNDConstants; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.kernelND.GeoVectorND; import org.geogebra.common.kernel.parser.ParseException; import org.geogebra.common.kernel.parser.ParserInterface; import org.geogebra.common.main.App; import org.geogebra.common.main.Feature; import org.geogebra.common.main.Localization; import org.geogebra.common.main.MyError; import org.geogebra.common.main.error.ErrorHandler; import org.geogebra.common.main.error.ErrorHelper; import org.geogebra.common.plugin.Event; import org.geogebra.common.plugin.EventType; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.AsyncOperation; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; /** * Processes algebra input as Strings and valid expressions into GeoElements * * @author Markus * */ public class AlgebraProcessor { /** * String code returned from the dialog if the user wants to create a new * slider */ public static final String CREATE_SLIDER = "1"; /** kernel */ protected final Kernel kernel; /** construction */ protected final Construction cons; /** app */ protected final App app; private final Localization loc; private final ParserInterface parser; /** command dispatcher */ protected final CommandDispatcher cmdDispatcher; private boolean disableGcd = false; private MyStringBuffer xBracket = null, yBracket = null, zBracket = null, closeBracket = null; private boolean vectorsEnabled = true; /** * @param kernel * kernel * @param commandDispatcher * command dispatcher */ public AlgebraProcessor(Kernel kernel, CommandDispatcher commandDispatcher) { this.kernel = kernel; cons = kernel.getConstruction(); this.cmdDispatcher = commandDispatcher; app = kernel.getApplication(); loc = app.getLocalization(); parser = kernel.getParser(); } /** * Returns the localized name of a command subset. Indices are defined in * CommandDispatcher. * * @param index * commands subtable index * @return set of commands for given subtable */ public String getSubCommandSetName(int index) { return cmdDispatcher.getSubCommandSetName(index); } /** * Returns whether the given command name is supported in GeoGebra. * * @param cmd * command name * @return true if available */ public boolean isCommandAvailable(String cmd) { return cmdDispatcher.isCommandAvailable(cmd); } /** * @param c * command * @param info * flag for output label * @return resulting geos * @throws MyError * e.g. on syntax error */ final public GeoElement[] processCommand(Command c, EvalInfo info) throws MyError { return cmdDispatcher.processCommand(c, info); } /** * @param c * command * @param info * flag for output label * @return simplified expression * @throws MyError * error */ final public ExpressionValue simplifyCommand(Command c, EvalInfo info) throws MyError { return cmdDispatcher.simplifyCommand(c, info); } /** * Processes the given casCell, i.e. compute its output depending on its * input. Note that this may create an additional twin GeoElement. * * @param casCell * cas cell * @param isLastRow * whether this cell is in last row -- allows us to skip * recompuattion of the whole CAS * @throws MyError * e.g. on syntax error */ final public void processCasCell(GeoCasCell casCell, boolean isLastRow) throws MyError { // check for CircularDefinition if (casCell.isCircularDefinition()) { // set twin geo to undefined casCell.computeOutput(); casCell.updateCascade(); app.showError("CircularDefinition"); return; } AlgoElement algoParent = casCell.getParentAlgorithm(); boolean prevFree = algoParent == null; boolean nowFree = !casCell.hasVariablesOrCommands(); boolean needsRedefinition = false; // If we change dependencies of CAS cells, we need to update // construction // to make sure the CAS cells are painted in right order (#232) boolean needsConsUpdate = false; if (prevFree) { if (nowFree) { // free -> free, e.g. m := 7 -> m := 8 cons.addToConstructionList(casCell, true); casCell.computeOutput(); // create twinGeo if necessary casCell.setLabelOfTwinGeo(); needsRedefinition = false; } else { boolean isInCons = cons.isInConstructionList(casCell); // update output for existent casCell // needed for #4118 if (isInCons) { casCell.computeOutput(); casCell.setLabelOfTwinGeo(); needsRedefinition = false; } // free -> dependent, e.g. m := 7 -> m := c+2 else if (casCell.isOutputEmpty() && !casCell.hasChildren()) { // this is a new casCell cons.removeFromConstructionList(casCell); KernelCAS.dependentCasCell(casCell); needsRedefinition = false; needsConsUpdate = !isLastRow; } else { // existing casCell with possible twinGeo needsRedefinition = true; } } } else { // dependent -> free, e.g. m := c+2 -> m := 7 // algorithm will be removed through redefinition // OR // dependent -> dependent, e.g. m := c+2 -> m := c+d // we already have an algorithm but need redefinition // in order to move it to the right place in construction list needsRedefinition = true; } if (needsRedefinition) { try { // update construction order and // rebuild construction using XML app.getScriptManager().disableListeners(); cons.changeCasCell(casCell); app.getScriptManager().enableListeners(); app.dispatchEvent(new Event(EventType.UPDATE, casCell)); // the changeCasCell command computes the output // so we don't need to call computeOutput, // which also causes marble crashes // casCell.computeOutput(); // casCell.updateCascade(); } catch (Exception e) { app.getScriptManager().enableListeners(); e.printStackTrace(); casCell.setError("RedefinitionFailed"); // app.showError(e.getMessage()); } catch (Error er) { app.getScriptManager().enableListeners(); throw er; } } else { casCell.notifyAdd(); casCell.updateCascade(); if (needsConsUpdate) { cons.updateCasCells(); } } } /** * for AlgebraView changes in the tree selection and redefine dialog * * @param geo * old geo * @param newValue * new value * @param redefineIndependent * true to allow redefinition of free objects * @param storeUndoInfo * true to make undo step * @param handler * error handler * * @param callback * receives changed geo */ public void changeGeoElement(GeoElementND geo, String newValue, boolean redefineIndependent, boolean storeUndoInfo, ErrorHandler handler, AsyncOperation<GeoElementND> callback) { try { EvalInfo info = new EvalInfo(!cons.isSuppressLabelsActive(), redefineIndependent); changeGeoElementNoExceptionHandling(geo, newValue, info, storeUndoInfo, callback, handler); } catch (MyError e) { ErrorHelper.handleError(e, newValue, loc, handler); } catch (Exception e) { handler.showError(e.getMessage()); } } /** * for AlgebraView changes in the tree selection and redefine dialog * * @param geo * old geo * @param newValue * new value * @param info * evaluation flags * @param storeUndoInfo * true to make undo step * @param callback * what to do with the changed geo * @param handler * decides how to handle exceptions * @throws Exception * e.g. parse exception or circular definition * @throws MyError * eg assignment to fixed object * */ public void changeGeoElementNoExceptionHandling(GeoElementND geo, String newValue, EvalInfo info, boolean storeUndoInfo, AsyncOperation<GeoElementND> callback, ErrorHandler handler) throws Exception, MyError { try { ValidExpression ve = parser.parseGeoGebraExpression(newValue); if ("X".equals(ve.getLabel())) { ve = getParamProcessor().checkParametricEquationF(ve, ve, cons, new EvalInfo(!cons.isSuppressLabelsActive())); } changeGeoElementNoExceptionHandling(geo, ve, info, storeUndoInfo, callback, handler); } catch (Exception e) { Log.debug("EXCEPTION" + e.getMessage() + ":" + newValue); e.printStackTrace(); throw new Exception( loc.getError("InvalidInput") + ":\n" + newValue); } catch (MyError e) { e.printStackTrace(); throw e; } catch (Error e) { Log.debug("ERROR" + e.getMessage() + ":" + newValue); e.printStackTrace(); throw new Exception( loc.getError("InvalidInput") + ":\n" + newValue); } } /** * for AlgebraView changes in the tree selection and redefine dialog * * @param geo * old geo * @param newValue * new value * @param info * true to make sure independent are redefined instead of value * change * @param storeUndoInfo * true to makeundo step * * @param callback * what to do with the changed geo * @param handler * decides how to handle exceptions */ public void changeGeoElementNoExceptionHandling(final GeoElementND geo, ValidExpression newValue, EvalInfo info, final boolean storeUndoInfo, final AsyncOperation<GeoElementND> callback, ErrorHandler handler) { String oldLabel, newLabel; GeoElementND[] result; app.getCompanion().storeViewCreators(); oldLabel = geo.getLabel(StringTemplate.defaultTemplate); // need to check isDefined() eg redefine FitPoly[{A, B, C, D, E, F, // G, H, I}, 22] to FitPoly[{A, B, C, D, E, F, G, H, I}, 2] /* * if (geo instanceof GeoFunction && ((GeoFunction) geo).isDefined()) { * cons.registerFunctionVariable(((GeoFunction) geo).getFunction() * .getVarString(StringTemplate.defaultTemplate)); } */ newLabel = newValue.getLabel(); if (newLabel == null) { newLabel = oldLabel; newValue.setLabel(newLabel); } // make sure that points stay points and vectors stay vectors if (newValue instanceof ExpressionNode) { ExpressionNode n = (ExpressionNode) newValue; if (geo.isGeoPoint()) { n.setForcePoint(); } else if (geo.isGeoVector()) { n.setForceVector(); } else if (geo.isGeoFunction()) { n.setForceFunction(); } } if (newLabel.equals(oldLabel)) { // try to overwrite final boolean listeners = app.getScriptManager().hasListeners(); app.getScriptManager().disableListeners(); AsyncOperation<GeoElementND[]> changeCallback = new AsyncOperation<GeoElementND[]>() { @Override public void callback(GeoElementND[] obj) { if (listeners) { geo.updateCascade(); } if (obj != null) { app.getCompanion().recallViewCreators(); if (storeUndoInfo) { app.storeUndoInfo(); } if (callback != null) { callback.callback(obj.length > 0 ? obj[0] : null); } } } }; app.getScriptManager().enableListeners(); processAlgebraCommandNoExceptionHandling(newValue, false, handler, changeCallback, info.withSliders(true)); cons.registerFunctionVariable(null); return; } else if (cons.isFreeLabel(newLabel)) { newValue.setLabel(oldLabel); // rename to oldLabel to enable overwriting result = processAlgebraCommandNoExceptionHandling(newValue, false, handler, null, info.withSliders(true)); result[0].setLabel(newLabel); // now we rename app.getCompanion().recallViewCreators(); if (storeUndoInfo) { app.storeUndoInfo(); } if (result.length > 0 && callback != null) { callback.callback(result[0]); } } else { String str[] = { "NameUsed", newLabel }; throw new MyError(loc, str); } cons.registerFunctionVariable(null); } /* * methods for processing an input string */ // returns non-null GeoElement array when successful /** * @param cmd * string to process * @param storeUndo * true to make undo step * @return resulting geos */ public GeoElementND[] processAlgebraCommand(String cmd, boolean storeUndo) { try { return processAlgebraCommandNoExceptionHandling(cmd, storeUndo, app.getErrorHandler(), false, null); } catch (Exception e) { e.printStackTrace(); app.showError(e.getMessage()); return null; } } // G.Sturr 2010-7-5 // /** * normal usage ... default to show error dialog (Exceptions hidden) * * @param cmd * string to process * @param storeUndo * true to create undo step * @return resulting geos */ public GeoElementND[] processAlgebraCommandNoExceptions(String cmd, boolean storeUndo) { try { return processAlgebraCommandNoExceptionHandling(cmd, storeUndo, ErrorHelper.silent(), false, null); } catch (Exception e) { return null; } } /** * Processes the string and hides all errors * * @param str * string to process * @param storeUndo * true to create undo step * @return resulting elements */ public GeoElementND[] processAlgebraCommandNoExceptionsOrErrors(String str, boolean storeUndo) { try { return processAlgebraCommandNoExceptionHandling(str, storeUndo, ErrorHelper.silent(), false, null); } catch (Exception e) { return null; } catch (MyError e) { return null; } } private MathMLParser mathmlParserGGB; private MathMLParser mathmlParserLaTeX; /** * Parametric processor (shared with 3D) */ protected ParametricProcessor paramProcessor; // G.Sturr 2010-7-5 // added 'allowErrorDialog' flag to handle the case of unquoted text // entries in the spreadsheet /** * @param cmd * string to process * @param storeUndo * true to make undo step * @param handler * decides how to handle exceptions * @param autoCreateSliders * whether to show a popup for undefined variables * @param callback0 * callback after the geos are created * @return resulting geos */ public GeoElementND[] processAlgebraCommandNoExceptionHandling( final String cmd, final boolean storeUndo, final ErrorHandler handler, boolean autoCreateSliders, final AsyncOperation<GeoElementND[]> callback0) { return processAlgebraCommandNoExceptionHandling(cmd, storeUndo, handler, new EvalInfo(!cons.isSuppressLabelsActive(), true) .withSliders(autoCreateSliders), callback0); } /** * @param cmd * string to process * @param storeUndo * true to make undo step * @param handler * decides how to handle exceptions * @param info * flags for labeling output, using sliders etc. * @param callback0 * callback after the geos are created * @return resulting geos */ public GeoElementND[] processAlgebraCommandNoExceptionHandling( final String cmd, final boolean storeUndo, final ErrorHandler handler, EvalInfo info, final AsyncOperation<GeoElementND[]> callback0) { // both return this and call callback0 in case of success! GeoElementND[] rett; if (cmd.length() > 0 && cmd.charAt(0) == '<' && cmd.startsWith("<math")) { rett = parseMathml(cmd, storeUndo, handler, info.isAutocreateSliders(), callback0); if (rett != null && callback0 != null) { callback0.callback(rett); } return rett; } ValidExpression ve; try { ve = parser.parseGeoGebraExpression(cmd); GeoCasCell casEval = checkCasEval(ve.getLabel(), cmd, ve); if (casEval != null) { if (callback0 != null) { callback0.callback(array(casEval)); } return new GeoElement[0]; } return processAlgebraCommandNoExceptionHandling(ve, storeUndo, handler, callback0, info); } catch (Exception e) { e.printStackTrace(); ErrorHelper.handleException(e, app, handler); } catch (MyError e) { ErrorHelper.handleError(e, cmd, loc, handler); } catch (Error e) { // Sometimes TokenManagerError comes from parser ErrorHelper.handleException(new Exception(e), app, handler); } if (callback0 != null) { callback0.callback(null); } return null; } /** * @param ve * valid expression (already pasted) * @param storeUndo * true to make undo step * @param handler * defines how to deal with exceptions * @param callback0 * callback after the geos are created * @param info * flags: whether to label output, whether independent may be * redefined etc * @return resulting geos */ public GeoElementND[] processAlgebraCommandNoExceptionHandling( ValidExpression ve, final boolean storeUndo, final ErrorHandler handler, final AsyncOperation<GeoElementND[]> callback0, final EvalInfo info) { // collect undefined variables CollectUndefinedVariables collecter = new Traversing.CollectUndefinedVariables(); ve.traverse(collecter); final TreeSet<String> undefinedVariables = collecter.getResult(); GeoElement[] ret = getParamProcessor().checkParametricEquation(ve, undefinedVariables, callback0, new EvalInfo(!cons.isSuppressLabelsActive()) .withSliders(info.isAutocreateSliders())); final int step = cons.getStep(); if (ret != null) { if (storeUndo) { app.storeUndoInfo(); } runCallback(callback0, ret, step); return ret; } if (undefinedVariables.size() > 0) { // ========================== // step0: check if there's an error on processing // eg we don't want to create slider 't' for // Curve[t^3,t^2,t,0,2] // ========================== GeoElement[] geoElements = null; try { ValidExpression cp = ve.deepCopy(kernel); cp.setLabels(ve.getLabels()); geoElements = processValidExpression(cp, info); if (storeUndo && geoElements != null) { app.storeUndoInfo(); } } catch (Throwable ex) { // ex.printStackTrace(); // do nothing } if (geoElements != null) { kernel.getConstruction().registerFunctionVariable(null); // this was forgotten to do here, added by Arpad // TODO: maybe need to add this to more places here? runCallback(callback0, geoElements, step); return geoElements; } StringBuilder sb = new StringBuilder(); // ========================== // step3: make a list of undefined variables so we can ask the // user // ========================== Iterator<String> it = undefinedVariables.iterator(); while (it.hasNext()) { String label = it.next(); if (kernel.lookupLabel(label) == null) { Log.debug("not found" + label); sb.append(label); sb.append(", "); } else { Log.debug("found" + label); } } if (sb.toString().endsWith(", ")) { sb.setLength(sb.length() - 2); } // ========================== // step4: ask user // ========================== if (sb.length() > 0) { // eg from Spreadsheet we don't want a popup if (!info.isAutocreateSliders()) { GeoElementND[] rett = tryReplacingProducts(ve, handler); if (rett != null) { runCallback(callback0, rett, step); } return rett; } // boolean autoCreateSlidersAnswer = false; // "Create sliders for a, b?" Create Sliders / Cancel // Yes: create sliders and draw line // No: go back into input bar and allow user to change input final Localization loc2 = loc; AsyncOperation<String[]> callback = null; // final FunctionVariable fvX2 = fvX; final ValidExpression ve2 = ve; callback = new AsyncOperation<String[]>() { @Override public void callback(String[] dialogResult) { GeoElement[] geos = null; // TODO: need we to catch the Exception // here, // which can throw the // processAlgebraInputCommandNoExceptionHandling // function? if (CREATE_SLIDER.equals(dialogResult[0])) { // insertStarIfNeeded(undefinedVariables, // ve2, fvX2); replaceUndefinedVariables(ve2, new TreeSet<GeoNumeric>(), null); } try { geos = processValidExpression(storeUndo, handler, ve2, info); } catch (MyError ee) { ErrorHelper.handleError(ee, ve2.toString( StringTemplate.defaultTemplate), loc2, handler); return; } catch (Exception ee) { ErrorHelper.handleException(ee, app, handler); return; } runCallback(callback0, geos, step); } }; boolean autoCreateSlidersAnswer = handler .onUndefinedVariables(sb.toString(), callback); if (!autoCreateSlidersAnswer) { return null; } } // Log.debug("list of variables: "+sb.toString()); // ========================== // step5: replace undefined variables // ========================== replaceUndefinedVariables(ve, new TreeSet<GeoNumeric>(), null); } // process ValidExpression (built by parser) GeoElement[] geos = processValidExpression(storeUndo, handler, ve, info); runCallback(callback0, geos, step); return geos; } /** * Run callbackl on new geos if there are any or empty array otherwise * * @param callback0 * callback * @param ret * possible new geos * @param step * construction step before geos were created */ void runCallback(AsyncOperation<GeoElementND[]> callback0, GeoElementND[] ret, int step) { if (callback0 != null) { callback0.callback(ret); } } /** * create valid expression (if possible) from command string * * @param cmd * command * @return valid expression * @throws Exception * exception */ public ValidExpression getValidExpressionNoExceptionHandling( final String cmd) throws Exception { return parser.parseGeoGebraExpression(cmd); } /** * @param label * dollar label * @param input * whole command including label * @param parsed * parsed content of input * @return cell */ public GeoCasCell checkCasEval(String label, String input, ValidExpression parsed) { if (label != null && label.startsWith("$")) { Integer row = -1; try { row = Integer.parseInt(label.substring(1)) - 1; } catch (Exception e) { // eg $A$1 label, do nothing } if (row < 0) { return null; } if (app.getGuiManager() != null) { app.getGuiManager().getCasView().cancelEditItem(); } GeoCasCell cell = cons.getCasCell(row); if (cell == null) { cell = new GeoCasCell(cons); } String cmd = input == null ? parsed.toString(StringTemplate.defaultTemplate) : input; Log.debug(parsed); Log.debug(input + " MAY BE CAS"); if (parsed != null && parsed.unwrap() instanceof Command) { Command c = (Command) parsed.unwrap(); if ("Rename".equals(c.getName())) { cmd = "=" + c.getArgument(1) .traverse(CommandReplacer .getReplacer(kernel, true)) .toString(StringTemplate.defaultTemplate) + ":=" + c.getArgument(0) .toString(StringTemplate.defaultTemplate); } } int colonPos = cmd.indexOf(':') + 1; int eqPos = cmd.indexOf('=') + 1; int prefixLength = eqPos > 0 ? (colonPos > 0 ? Math.min(colonPos, eqPos) : eqPos) : colonPos; if (cmd.charAt(prefixLength) == '=') { prefixLength++; } cell.setInput(cmd.substring(prefixLength)); this.processCasCell(cell, false); return cell; } return null; } /** * @param ve * expression to check for parametrics * @param undefinedVariables * set of undefined variables * @return result of parametric evaluation if successfull */ /* * GeoElement[] checkParametricAfterUndefinedChanged( ValidExpression ve, * TreeSet<String> undefinedVariables) { replaceUndefinedVariables(ve, * undefinedVariables, false); GeoElement[] param2 = * getParamProcessor().checkParametricEquation(ve, undefinedVariables); if * (param2 != null) { return param2; } if (!undefinedVariables.isEmpty()) { * replaceUndefinedVariables(ve, undefinedVariables, true); } return null; } */ private GeoElementND[] tryReplacingProducts(ValidExpression ve, ErrorHandler eh) { ValidExpression ve2 = (ValidExpression) ve.traverse(new Traversing() { @Override public ExpressionValue process(ExpressionValue ev) { if (ev.isExpressionNode() && ((ExpressionNode) ev) .getOperation() == Operation.MULTIPLY) { String lt = ((ExpressionNode) ev).getLeft() .toString(StringTemplate.defaultTemplate) .replace(" ", ""); Operation op = app.getParserFunctions().get(lt, 1); if (op != null) { return new ExpressionNode(kernel, ((ExpressionNode) ev).getRight().traverse(this), op, null); } } return ev; } }); GeoElementND[] ret = null; try { ret = this.processValidExpression(ve2); } catch (MyError t) { ErrorHelper.handleError(t, null, loc, eh); } catch (Exception e) { ErrorHelper.handleException(e, app, eh); } return ret; } /** * TODO figure out how to handle sliders here * * @param autoCreateSliders * whether sliders should be autocreated */ private GeoElementND[] parseMathml(String cmd, final boolean storeUndo, ErrorHandler handler, boolean autoCreateSliders, final AsyncOperation<GeoElementND[]> callback0) { if (mathmlParserGGB == null) { mathmlParserGGB = new MathMLParser(true); } GeoElementND[] ret = null; try { String ggb = mathmlParserGGB.parse(cmd, false, true); RegExp assignment = RegExp.compile("^(\\w+) \\(x\\)=(.*)$"); MatchResult lhs = assignment.exec(ggb); if (lhs != null) { ggb = lhs.getGroup(1) + "(x)=" + lhs.getGroup(2); } Log.debug(ggb); ret = this.processAlgebraCommandNoExceptionHandling(ggb, storeUndo, handler, autoCreateSliders, callback0); } catch (Throwable t) { Log.warn(t.getMessage()); } if (ret != null && ret.length != 0) { return ret; } if (mathmlParserLaTeX == null) { mathmlParserLaTeX = new MathMLParser(false); } String latex = mathmlParserLaTeX.parse(cmd, false, false); GeoText arg = new GeoText(cons, latex); AlgoLaTeX texAlgo = new AlgoLaTeX(cons, null, arg); return array(texAlgo.getOutput(0)); } /** * @param storeUndo * whether to create an undo point * @param handler * handles exceptions * @param ve * input expression * @param info * processing information * @return processed expression */ public GeoElement[] processValidExpression(boolean storeUndo, ErrorHandler handler, ValidExpression ve, EvalInfo info) { GeoElement[] geoElements = null; try { geoElements = processValidExpression(ve, info); if (storeUndo && geoElements != null) { app.storeUndoInfo(); } } catch (MyError e) { e.printStackTrace(); ErrorHelper.handleError(e, ve == null ? null : ve.toString(StringTemplate.defaultTemplate), loc, handler); } catch (Exception ex) { Log.debug("Exception" + ex.getLocalizedMessage()); ErrorHelper.handleException(ex, app, handler); } finally { kernel.getConstruction().registerFunctionVariable(null); } return geoElements; } /** * Replaces undefined variables inside of expression * * @param ve * expression * @param undefined * list of variables undefined so far; items will be removed from * it as we go * @param except * list of variable names that should not be replaced, null means * replace everything */ public void replaceUndefinedVariables(ValidExpression ve, TreeSet<GeoNumeric> undefined, String[] except) { ReplaceUndefinedVariables replacer = new Traversing.ReplaceUndefinedVariables( this.kernel, undefined, except); ve.traverse(replacer); } /** * Parses given String str and tries to evaluate it to a double. Returns * Double.NaN if something went wrong. * * @param str * string to process * @return result as double */ public double evaluateToDouble(String str) { return evaluateToDouble(str, false, null); } /** * Parses given String str and tries to evaluate it to a double. Returns * Double.NaN if something went wrong. * * @param str * string to process * @param suppressErrors * false to show error messages (only stacktrace otherwise) * @param forGeo * geo that can receive the value and definition * @return result as double */ public double evaluateToDouble(String str, boolean suppressErrors, GeoNumeric forGeo) { try { ValidExpression ve = parser.parseExpression(str); ExpressionNode en = (ExpressionNode) ve; en.resolveVariables(new EvalInfo(false)); NumberValue nv = (NumberValue) en .evaluate(StringTemplate.defaultTemplate); if (forGeo != null) { forGeo.setValue(nv.getDouble()); // if forGeo is a slider, the value might be out of range // in which case we mustn't set the definition if (Kernel.isEqual(forGeo.getDouble(), nv.getDouble()) && en.isConstant()) { forGeo.setDefinition(en); } } return nv.getDouble(); } catch (Exception e) { e.printStackTrace(); if (!suppressErrors) { app.showError("InvalidInput", str); } if (forGeo != null) { forGeo.setUndefined(); } return Double.NaN; } catch (MyError e) { e.printStackTrace(); if (!suppressErrors) { app.showError(e); } if (forGeo != null) { forGeo.setUndefined(); } return Double.NaN; } catch (Error e) { e.printStackTrace(); if (!suppressErrors) { app.showError("InvalidInput", str); } if (forGeo != null) { forGeo.setUndefined(); } return Double.NaN; } } /** * Parses given String str and tries to evaluate it to a GeoBoolean object. * Returns null if something went wrong. * * @param str * string to process * @param handler * takes care of errors * @return resulting boolean */ public GeoBoolean evaluateToBoolean(String str, ErrorHandler handler) { boolean oldMacroMode = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); GeoBoolean bool = null; try { ValidExpression ve = parser.parseGeoGebraExpression(str); // A=B as comparison, not assignment if (ve.getLabel() != null) { ve = new ExpressionNode(kernel, new Variable(kernel, ve.getLabel()), Operation.EQUAL_BOOLEAN, ve); // A+B=C as comparison, not equation } else if (ve.unwrap() instanceof Equation) { Equation eq = (Equation) ve.unwrap(); ve = new ExpressionNode(kernel, eq.getLHS(), Operation.EQUAL_BOOLEAN, eq.getRHS()); } GeoElementND[] temp = processValidExpression(ve); // GGB-1043 GWT: can't rely on ClassCast Exception if (temp[0] instanceof GeoBoolean) { bool = (GeoBoolean) temp[0]; } else { handler.showError(loc.getError("InvalidInput")); } } catch (Exception e) { ErrorHelper.handleException(e, app, handler); } catch (MyError e) { ErrorHelper.handleError(e, str, loc, handler); } catch (Error e) { e.printStackTrace(); handler.showError(loc.getError("InvalidInput")); } cons.setSuppressLabelCreation(oldMacroMode); return bool; } /** * Parses given String str and tries to evaluate it to a List object. * Returns null if something went wrong. Michael Borcherds 2008-04-02 * * @param str * input string * @return resulting list */ public GeoList evaluateToList(String str) { boolean oldMacroMode = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); GeoList list = null; try { ValidExpression ve = parser.parseGeoGebraExpression(str); GeoElementND[] temp = processValidExpression(ve); // CAS in GeoGebraWeb dies badly if we don't handle this case // (Simon's hack): // list = (GeoList) temp[0]; if (temp[0] instanceof GeoList) { list = (GeoList) temp[0]; } else { Log.error("return value was not a list"); } } catch (CircularDefinitionException e) { Log.debug("CircularDefinition"); // app.showError("CircularDefinition"); } catch (Exception e) { e.printStackTrace(); // app.showError("InvalidInput", str); } catch (MyError e) { e.printStackTrace(); // app.showError(e); } catch (Error e) { e.printStackTrace(); // app.showError("InvalidInput", str); } cons.setSuppressLabelCreation(oldMacroMode); return list; } /** * Parses given String str and tries to evaluate it to a GeoFunction Returns * null if something went wrong. Michael Borcherds 2008-04-04 * * @param str * input string * @param suppressErrors * false to show error messages (only stacktrace otherwise) * @return resulting function */ public GeoFunction evaluateToFunction(String str, boolean suppressErrors) { return evaluateToFunction(str, suppressErrors, false); } /** * Parses given String str and tries to evaluate it to a GeoFunction Returns * null if something went wrong. Michael Borcherds 2008-04-04 * * @param str * input string * @param suppressErrors * false to show error messages (only stacktrace otherwise) * @param revertArbconst * whether to replace c_1 back with arbconst(1) * @return resulting function */ public GeoFunction evaluateToFunction(String str, boolean suppressErrors, boolean revertArbconst) { boolean oldMacroMode = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); GeoFunction func = null; try { ValidExpression ve = parser.parseGeoGebraExpression(str); String[] varName = kernel.getConstruction() .getRegisteredFunctionVariables(); FunctionVariable[] fv = new FunctionVariable[varName.length]; ExpressionNode exp = ve.wrap(); replaceVariables(exp, varName, fv); if (revertArbconst) { exp = exp.traverse(new Traversing() { @Override public ExpressionValue process(ExpressionValue ev) { if (ev instanceof Variable) { GeoElement geo = kernel .lookupLabel(((Variable) ev).getName()); String[] parts = ((Variable) ev).getName() .split("_"); if (geo == null && parts.length == 2) { try { int idx = Integer.parseInt(parts[1] .replace("{", "").replace("}", "")); return new ExpressionNode(kernel, new MyDouble(kernel, idx), Operation.ARBCONST, null); } catch (Exception e) { Log.debug("Invalid variable"); } } else if (geo != null) { return geo; } } return ev; } }).wrap(); } GeoElementND[] temp = processValidExpression(exp); if (temp[0].isGeoFunctionable()) { GeoFunctionable f = (GeoFunctionable) temp[0]; func = f.getGeoFunction(); } else if (!suppressErrors) { app.showError("InvalidInput", str); } } catch (CircularDefinitionException e) { Log.debug("CircularDefinition"); if (!suppressErrors) { app.showError("CircularDefinition"); } } catch (Exception e) { e.printStackTrace(); if (!suppressErrors) { app.showError("InvalidInput", str); } } catch (MyError e) { e.printStackTrace(); if (!suppressErrors) { app.showError(e); } } catch (Error e) { e.printStackTrace(); if (!suppressErrors) { app.showError("InvalidInput", str); } } cons.setSuppressLabelCreation(oldMacroMode); return func; } /** * @param argument * expression * @param varName * variable names to be replaced * @param fv * function variables * @return number of replacements */ public int replaceVariables(ExpressionNode argument, String[] varName, FunctionVariable[] fv) { int rep = 0; for (int i = 0; i < varName.length; i++) { if (fv[i] == null) { fv[i] = new FunctionVariable(kernel, varName[i]); } rep += argument.replaceVariables(varName[i], fv[i]); } return rep; } /** * * @param str * input string * @param suppressErrors * true to suppress error messages * @return str parsed to multivariate function */ public GeoFunctionNVar evaluateToFunctionNVar(String str, boolean suppressErrors) { boolean oldMacroMode = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); GeoFunctionNVar func = null; try { ValidExpression ve = parser.parseGeoGebraExpression(str); GeoElementND[] temp = processValidExpression(ve); if (temp[0] instanceof GeoFunctionNVar) { func = (GeoFunctionNVar) temp[0]; } else if (temp[0] instanceof GeoFunction) { FunctionVariable[] funVars; if (((GeoFunction) temp[0]).isFunctionOfY()) { funVars = new FunctionVariable[] { new FunctionVariable(kernel, "x"), ((GeoFunction) temp[0]).getFunction() .getFunctionVariable() }; } else { funVars = new FunctionVariable[] { ((GeoFunction) temp[0]).getFunction() .getFunctionVariable(), new FunctionVariable(kernel, "y") }; } FunctionNVar fn = new FunctionNVar( ((GeoFunction) temp[0]).getFunctionExpression(), funVars); func = new GeoFunctionNVar(cons, fn); } else if (temp[0] instanceof GeoNumeric) { FunctionVariable[] funVars = new FunctionVariable[] { new FunctionVariable(kernel, "x"), new FunctionVariable(kernel, "y") }; FunctionNVar fn = new FunctionNVar( new ExpressionNode(kernel, temp[0]), funVars); func = new GeoFunctionNVar(cons, fn); } if (!suppressErrors) { app.showError("InvalidInput", str); } } catch (CircularDefinitionException e) { Log.debug("CircularDefinition"); if (!suppressErrors) { app.showError("CircularDefinition"); } } catch (Exception e) { e.printStackTrace(); if (!suppressErrors) { app.showError("InvalidInput", str); } } catch (MyError e) { e.printStackTrace(); if (!suppressErrors) { app.showError(e); } } catch (Error e) { e.printStackTrace(); if (!suppressErrors) { app.showError("InvalidInput", str); } } cons.setSuppressLabelCreation(oldMacroMode); return func; } /** * Parses given String str and tries to evaluate it to a NumberValue Returns * null if something went wrong. Michael Borcherds 2008-08-13 * * @param str * string to parse * @param suppressErrors * false to show error messages (only stacktrace otherwise) * @return resulting number */ public GeoNumberValue evaluateToNumeric(String str, boolean suppressErrors) { return evaluateToNumeric(str, suppressErrors ? ErrorHelper.silent() : app.getDefaultErrorHandler()); } /** * Parses given String str and tries to evaluate it to a NumberValue Returns * null if something went wrong. * * @param str * string to parse * @param handler * callback for handling errors * @return resulting number */ public GeoNumberValue evaluateToNumeric(String str, ErrorHandler handler) { if (str == null || "".equals(str)) { ErrorHelper.handleInvalidInput(str, loc, handler); return new GeoNumeric(cons, Double.NaN); } boolean oldMacroMode = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); GeoNumberValue num = null; try { ValidExpression ve = parser.parseGeoGebraExpression(str); GeoElementND[] temp = processValidExpression(ve); if (temp[0] instanceof GeoNumberValue) { num = (GeoNumberValue) temp[0]; } else { num = new GeoNumeric(cons, Double.NaN); ErrorHelper.handleInvalidInput(str, loc, handler); } } catch (Exception e) { ErrorHelper.handleException(e, app, handler); } catch (MyError e) { e.printStackTrace(); ErrorHelper.handleError(e, str, loc, handler); } catch (Error e) { e.printStackTrace(); ErrorHelper.handleException(new Exception(e), app, handler); } cons.setSuppressLabelCreation(oldMacroMode); return num; } /** * Parses given String str and tries to evaluate it to a GeoPoint. Returns * null if something went wrong. * * @param str * string to process * @param handler * error handler * @param suppressLabels * true to suppress labeling * @return resulting point */ public GeoPointND evaluateToPoint(String str, ErrorHandler handler, boolean suppressLabels) { boolean oldMacroMode = cons.isSuppressLabelsActive(); if (suppressLabels) { cons.setSuppressLabelCreation(true); } GeoPointND p = null; GeoElementND[] temp = null; try { ValidExpression ve = parser.parseGeoGebraExpression(str); if (ve instanceof ExpressionNode) { ExpressionNode en = (ExpressionNode) ve; en.setForcePoint(); } temp = processValidExpression(ve); if (temp[0] instanceof GeoVectorND) { p = kernel.wrapInPoint((GeoVectorND) temp[0]); } else if (temp[0] instanceof GeoPointND) { p = (GeoPointND) temp[0]; } else { handler.showError(loc.getError("VectorExpected")); } } catch (Exception e) { ErrorHelper.handleException(e, app, handler); } catch (MyError e) { ErrorHelper.handleError(e, str, loc, handler); } catch (Error e) { ErrorHelper.handleException(new Exception(e), app, handler); } if (suppressLabels) { cons.setSuppressLabelCreation(oldMacroMode); } return p; } /** * Parses given String str and tries to evaluate it to a GeoText. Returns * null if something went wrong. * * @param str * input string * @param createLabel * true to label result * @param showErrors * true to show error messages (only stacktrace otherwise) * @return resulting text */ public GeoText evaluateToText(String str, boolean createLabel, boolean showErrors) { boolean oldMacroMode = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(!createLabel); GeoText text = null; GeoElementND[] temp = null; try { ValidExpression ve = parser.parseGeoGebraExpression(str); temp = processValidExpression(ve); text = (GeoText) temp[0]; } catch (CircularDefinitionException e) { if (showErrors) { Log.debug("CircularDefinition"); app.showError("CircularDefinition"); } } catch (Exception e) { if (showErrors) { e.printStackTrace(); app.showError("InvalidInput", str); } } catch (MyError e) { if (showErrors) { e.printStackTrace(); app.showError(e); } } catch (Error e) { if (showErrors) { e.printStackTrace(); app.showError("InvalidInput", str); } } cons.setSuppressLabelCreation(oldMacroMode); return text; } /** * Parses given String str and tries to evaluate it to a GeoImplicitPoly * object. Returns null if something went wrong. * * @param str * stringInput * @param showErrors * if false, only stacktraces are printed * @return implicit polygon or null */ public GeoElementND evaluateToGeoElement(String str, boolean showErrors) { boolean oldMacroMode = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); GeoElementND geo = null; try { ValidExpression ve = parser.parseGeoGebraExpression(str); GeoElementND[] temp = processValidExpression(ve); geo = temp[0]; } catch (CircularDefinitionException e) { Log.debug("CircularDefinition"); app.showError("CircularDefinition"); } catch (Exception e) { e.printStackTrace(); if (showErrors) { app.showError("InvalidInput", str); } } catch (MyError e) { e.printStackTrace(); if (showErrors) { app.showError(e); } } catch (Error e) { e.printStackTrace(); if (showErrors) { app.showError("InvalidInput", str); } } cons.setSuppressLabelCreation(oldMacroMode); return geo; } /** * Checks if label is valid. * * @param label * potential label * @return valid label * @throws ParseException * if label is invalid */ public String parseLabel(String label) throws ParseException { return parser.parseLabel(label); } /** * @param ve * expression to process * @return resulting elements * @throws MyError * e.g. for wrong syntax * @throws Exception * e.g. for circular definition */ public GeoElement[] processValidExpression(ValidExpression ve) throws MyError, Exception { return processValidExpression(ve, new EvalInfo(!cons.isSuppressLabelsActive(), true)); } /** * processes valid expression. * * @param ve * expression to process * @param info * processing information * @throws MyError * e.g. on wrong syntax * @throws Exception * e.g. for circular definition * @return resulting geos */ public GeoElement[] processValidExpression(ValidExpression ve, EvalInfo info) throws MyError, Exception { // check for existing labels String[] labels = ve.getLabels(); GeoElement replaceable = getReplaceable(labels); GeoElement[] ret; boolean oldMacroMode = cons.isSuppressLabelsActive(); if (replaceable != null) { cons.setSuppressLabelCreation(true); } // we have to make sure that the macro mode is // set back at the end try { ret = doProcessValidExpression(ve, info); if (ret == null) { // eg (1,2,3) running in 2D Log.warn("Unhandled ValidExpression : " + ve); throw new MyError(loc, loc.getError("InvalidInput") + ":\n" + ve); } } finally { cons.setSuppressLabelCreation(oldMacroMode); } processReplace(replaceable, ret, ve, info); return ret; } /** * @param labels * output labels of a command/expr * @return geo with same label as one of the outputs */ GeoElement getReplaceable(String[] labels) { GeoElement replaceable = null; if (labels != null && labels.length > 0) { boolean firstTime = true; for (int i = 0; i < labels.length; i++) { GeoElement geo = kernel.lookupLabel(labels[i]); if (geo != null) { if (geo.isProtected(EventType.UPDATE)) { String[] strs = { "IllegalAssignment", "AssignmentToFixed", ":\n", geo.getLongDescription() }; throw new MyError(loc, strs); } // replace (overwrite or redefine) geo if (firstTime) { // only one geo can be replaced replaceable = geo; firstTime = false; } } } } return replaceable; } /** * * @param replaceable * old geo with same label * @param ret * result of processing * @param ve * original expression * @param info * evaluation flags * @throws CircularDefinitionException * when circular definition occurs */ void processReplace(GeoElement replaceable, GeoElement[] ret, ValidExpression ve, EvalInfo info) throws CircularDefinitionException { // try to replace replaceable geo by ret[0] if (replaceable != null && ret.length > 0) { // a changeable replaceable is not redefined: // it gets the value of ret[0] // (note: texts are always redefined) if (!info.mayRedefineIndependent() && replaceable.isChangeable() && !(replaceable.isGeoText())) { try { replaceable.set(ret[0]); replaceable.updateRepaint(); ret[0] = replaceable; } catch (Exception e) { String errStr = loc.getError("IllegalAssignment") + "\n" + replaceable.getLongDescription() + " = " + ret[0].getLongDescription(); throw new MyError(loc, errStr); } } // redefine else { try { // SPECIAL CASE: set value // new and old object are both independent and have same // type: // simply assign value and don't redefine if (replaceable.isIndependent() && ret[0].isIndependent() && compatibleTypes(replaceable.getGeoClassType(), ret[0].getGeoClassType())) { if (replaceable instanceof GeoNumeric) { ((GeoNumeric) replaceable).extendMinMax(ret[0]); } replaceable.set(ret[0]); if (replaceable instanceof GeoFunction && !((GeoFunction) replaceable) .validate(true)) { replaceable.setUndefined(); } else { replaceable.setDefinition(ret[0].getDefinition()); } replaceable.updateRepaint(); ret[0] = replaceable; } // STANDARD CASE: REDFINED else { GeoElement newGeo = ret[0]; GeoCasCell cell = replaceable.getCorrespondingCasCell(); if (cell != null) { // this is a ValidExpression since we don't get // GeoElements from parsing ValidExpression vexp = (ValidExpression) ve .unwrap(); cell.setAssignmentType(AssignmentType.DEFAULT); cell.setInput(vexp.toAssignmentString( StringTemplate.defaultTemplate, cell.getAssignmentType())); processCasCell(cell, false); } else { cons.replace(replaceable, newGeo); } // now all objects have changed // get the new object with same label as our result String newLabel = newGeo.isLabelSet() ? newGeo.getLabelSimple() : replaceable.getLabelSimple(); ret[0] = kernel.lookupLabel(newLabel); } } catch (CircularDefinitionException e) { throw e; } catch (Exception e) { e.printStackTrace(); throw new MyError(loc, "ReplaceFailed"); } catch (MyError e) { e.printStackTrace(); throw new MyError(loc, "ReplaceFailed"); } } } } private static boolean compatibleTypes(GeoClass type, GeoClass type2) { if (type2.equals(type)) { return true; } if (type2.equals(GeoClass.NUMERIC) && type.equals(GeoClass.ANGLE)) { return true; } if (type.equals(GeoClass.NUMERIC) && type2.equals(GeoClass.ANGLE)) { return true; } return false; } /** * Processes valid expression * * @param ve * expression to process * @param info * flags for processing * @return array of geos * @throws MyError * if syntax error occurs * @throws CircularDefinitionException * if circular definition occurs */ public final GeoElement[] doProcessValidExpression(final ValidExpression ve, EvalInfo info) throws MyError, CircularDefinitionException { GeoElement[] ret = null; if (ve instanceof ExpressionNode) { ret = processExpressionNode((ExpressionNode) ve, info); if (ret != null && ret.length > 0 && ret[0] instanceof GeoScriptAction) { if (info.isScripting()) { ((GeoScriptAction) ret[0]).perform(); } return new GeoElement[] {}; } else if (ret != null && ret.length > 0 && ret[0] instanceof GeoList) { int actions = ((GeoList) ret[0]).performScriptActions(info); if (actions > 0) { ret[0].remove(); return new GeoElement[] {}; } } } // Command else if (ve instanceof Command) { ret = cmdDispatcher.processCommand((Command) ve, new EvalInfo(true)); } // Equation in x,y (linear or quadratic are valid): line or conic else if (ve instanceof Equation) { ret = processEquation((Equation) ve, ve.wrap(), info); } // explicit Function in one variable else if (ve instanceof Function) { ret = processFunction((Function) ve, info); } // explicit Function in multiple variables else if (ve instanceof FunctionNVar) { ret = processFunctionNVar((FunctionNVar) ve, info); } // // Assignment: variable // else if (ve instanceof Assignment) { // ret = processAssignment((Assignment) ve); // } return ret; } /** * Wraps given function into GeoFunction, if dependent, * AlgoDependentFunction is created. * * @param fun * function * @param info * processing information * @return GeoFunction */ public final GeoElement[] processFunction(Function fun, EvalInfo info) { String varName = fun.getVarString(StringTemplate.defaultTemplate); if (varName.equals(Unicode.thetaStr) && !kernel.getConstruction() .isRegistredFunctionVariable(Unicode.thetaStr) && fun.getExpression().evaluatesToNumber(true)) { String label = fun.getLabel(); ValidExpression ve = new MyVecNode(kernel, fun.getExpression(), fun.getFunctionVariable().wrap()); ((MyVecNode) ve).setMode(Kernel.COORD_POLAR); // TODO the "r" check is there to allow r=theta in the // future if (!"r".equals(label)) { ve.setLabel(label); } ExpressionNode exp = ve.deepCopy(kernel).traverse(VariableReplacer .getReplacer(varName, fun.getFunctionVariable(), kernel)) .wrap(); exp.resolveVariables(info); GeoElement[] ret = getParamProcessor().processParametricFunction( exp, exp.evaluate(StringTemplate.defaultTemplate), new FunctionVariable[] { fun.getFunctionVariable() }, "X".equals(ve.getLabel()) ? null : ve.getLabel(), info); if (ret != null) { return ret; } } if (!fun.initFunction(info.isSimplifyingIntegers())) { ExpressionNode copy = fun.getExpression().deepCopy(kernel); return getParamProcessor().processParametricFunction( fun.getExpression(), copy.evaluate(StringTemplate.defaultTemplate), new FunctionVariable[] { fun.getFunctionVariable() }, fun.getLabel(), info); } String label = fun.getLabel(); GeoFunction f; GeoElement[] vars = fun.getGeoElementVariables(); boolean isIndependent = true; for (int i = 0; vars != null && i < vars.length; i++) { if (Inspecting.dynamicGeosFinder.check(vars[i])) { isIndependent = false; } } // check for interval ExpressionNode en = fun.getExpression(); if (en.getOperation().equals(Operation.AND) || en.getOperation().equals(Operation.AND_INTERVAL)) { ExpressionValue left = en.getLeft(); ExpressionValue right = en.getRight(); if (left.isExpressionNode() && right.isExpressionNode()) { ExpressionNode enLeft = (ExpressionNode) left; ExpressionNode enRight = (ExpressionNode) right; // directions of inequalities, need one + and one - for an // interval int leftDir = getDirection(enLeft); int rightDir = getDirection(enRight); // opposite directions -> OK if (leftDir * rightDir < 0) { if (isIndependent) { f = new GeoInterval(cons, fun); } else { f = dependentInterval(fun); } f.setLabel(label); return array(f); } // AbstractApplication.debug(enLeft.operation+""); // AbstractApplication.debug(enLeft.left.getClass()+""); // AbstractApplication.debug(enLeft.right.getClass()+""); } // AbstractApplication.debug(left.getClass()+""); // AbstractApplication.debug(right.getClass()+""); // AbstractApplication.debug(""); } else if (en.getOperation().equals(Operation.FUNCTION)) { ExpressionValue left = en.getLeft(); ExpressionValue right = en.getRight(); // the isConstant() here makes difference between f(1) and f(x), see // #2155 if (left.isLeaf() && left.isGeoElement() && right.isLeaf() && right.isNumberValue() && !right.isConstant() && !isIndependent) { f = (GeoFunction) dependentGeoCopy( ((GeoFunctionable) left).getGeoFunction()); f.setLabel(label); return array(f); } } if (isIndependent) { f = new GeoFunction(cons, fun, info.isSimplifyingIntegers()); } else { f = kernel.getAlgoDispatcher().DependentFunction(fun, info); if (label == null) { label = AlgoDependentFunction.getDerivativeLabel(fun); } } if (f.validate(label == null)) { f.setLabel(label); return array(f); } f.remove(); throw new MyError(loc, "InvalidFunction"); } /** * @return parametric processor */ public ParametricProcessor getParamProcessor() { if (this.paramProcessor == null) { paramProcessor = new ParametricProcessor(kernel, this); } return paramProcessor; } /** * @param length * length * @return array of zeros with given length */ protected ExpressionValue[] arrayOfZeros(int length) { ExpressionValue[] ret = new ExpressionValue[length]; for (int i = 0; i < length; i++) { ret[i] = new MyDouble(kernel, 0); } return ret; } /** * @param cx * expression * @param coefX * output array for coeffs * @param mult * multiplicator * @param loc2 * variable * @return degree if successful, -1 otherwise */ public int getPolyCoeffs(ExpressionNode cx, ExpressionValue[] coefX, ExpressionNode mult, GeoNumeric loc2) { if (!cx.containsDeep(loc2)) { add(coefX, 0, mult.multiply(cx)); return 0; } else if (cx.getOperation() == Operation.PLUS) { int deg1 = getPolyCoeffs(cx.getLeftTree(), coefX, mult, loc2); int deg2 = getPolyCoeffs(cx.getRightTree(), coefX, mult, loc2); if (deg1 < 0 || deg2 < 0) { return -1; } return Math.max(deg1, deg2); } else if (cx.getOperation() == Operation.MINUS) { int deg1 = getPolyCoeffs(cx.getLeftTree(), coefX, mult, loc2); int deg2 = getPolyCoeffs(cx.getRightTree(), coefX, mult.multiply(-1), loc2); if (deg1 < 0 || deg2 < 0) { return -1; } return Math.max(deg1, deg2); } else if (cx.getOperation() == Operation.MULTIPLY) { if (!cx.getLeft().contains(loc2)) { return getPolyCoeffs(cx.getRightTree(), coefX, mult.multiply(cx.getLeft().unwrap()), loc2); } else if (!cx.getRight().contains(loc2)) { return getPolyCoeffs(cx.getLeftTree(), coefX, mult.multiply(cx.getRight().unwrap()), loc2); } else { ExpressionValue[] left = arrayOfZeros(3); ExpressionValue[] right = arrayOfZeros(3); int degL = getPolyCoeffs(cx.getLeftTree(), left, mult, loc2); int degR = getPolyCoeffs(cx.getRightTree(), right, new ExpressionNode(kernel, 1), loc2); if (degL == 1 && degR == 1) { add(coefX, 0, left[0].wrap().multiply(right[0])); add(coefX, 1, left[1].wrap().multiply(right[0])); add(coefX, 1, left[0].wrap().multiply(right[1])); add(coefX, 2, left[1].wrap().multiply(right[1])); return 2; } return -1; } } else if (cx.getOperation() == Operation.POWER) { if (cx.getRight().unwrap() instanceof MyDouble && Kernel.isEqual(2, cx.getRight().evaluateDouble())) { ExpressionValue[] left = arrayOfZeros(3); int degL = getPolyCoeffs(cx.getLeftTree(), left, new ExpressionNode(kernel, 1), loc2); if (degL == 1) { add(coefX, 0, left[0].wrap().power(2).multiply(mult)); add(coefX, 1, left[1].wrap().multiply(left[0]).multiply(2) .multiply(mult)); add(coefX, 2, left[1].wrap().power(2).multiply(mult)); return 2; } return -1; } } else if (cx.unwrap() == loc2) { add(coefX, 1, mult); return 1; } return -1; } /** * @param cx * expression * @param coefX * output array for coefficients * @param scale * multiplicator * * @param var * variable * @return cx is in one of the forms a+b sin(var)+c*cos(var), a+b * sinh(var)+c*cosh(var) */ public boolean getTrigCoeffs(ExpressionNode cx, ExpressionValue[] coefX, ExpressionNode scale, GeoElement var) { boolean childrenOK = true; if (cx.getOperation() == Operation.PLUS) { childrenOK = getTrigCoeffs(cx.getLeftTree(), coefX, scale, var) && getTrigCoeffs(cx.getRightTree(), coefX, scale, var); } else if (cx.getOperation() == Operation.MINUS) { childrenOK = getTrigCoeffs(cx.getLeftTree(), coefX, scale, var) && getTrigCoeffs(cx.getRightTree(), coefX, scale.multiply(-1), var); } else if (cx.getOperation() == Operation.MULTIPLY) { if (cx.getLeft().evaluatesToNumber(false) && !cx.getLeft().wrap().containsDeep(var)) { return getTrigCoeffs(cx.getRightTree(), coefX, scale.multiply(cx.getLeft().unwrap()), var); } else if (cx.getRight().evaluatesToNumber(false) && !cx.getRight().wrap().containsDeep(var)) { return getTrigCoeffs(cx.getLeftTree(), coefX, scale.multiply(cx.getRight().unwrap()), var); } return false; } else if (cx.getOperation() == Operation.SIN) { if (cx.getLeft().unwrap() != var) { return false; } add(coefX, 1, scale); } else if (cx.getOperation() == Operation.COS) { if (cx.getLeft().unwrap() != var) { return false; } add(coefX, 2, scale); } else if (cx.getOperation() == Operation.SINH) { if (cx.getLeft().unwrap() != var) { return false; } add(coefX, 3, scale); } else if (cx.getOperation() == Operation.COSH) { if (cx.getLeft().unwrap() != var) { return false; } add(coefX, 4, scale); } else if (cx.isLeaf()) { if (cx.getLeft().contains(var)) { return false; } add(coefX, 0, cx.multiply(scale)); } else { return false; } return childrenOK && ((coefX[1] == null && coefX[2] == null) || (coefX[3] == null && coefX[4] == null)); } private static void add(ExpressionValue[] coefX, int i, ExpressionNode scale) { if (coefX[i] == null) { coefX[i] = scale; } else { coefX[i] = scale.plus(coefX[i]); } } private static int getDirection(ExpressionNode enLeft) { int dir = 0; ExpressionValue left = enLeft.getLeft(); ExpressionValue right = enLeft.getRight(); Operation op = enLeft.getOperation(); if ((op.equals(Operation.LESS) || op.equals(Operation.LESS_EQUAL))) { if (left instanceof FunctionVariable && right.isNumberValue() && right.isConstant()) { dir = -1; } else if (right instanceof FunctionVariable && left.isNumberValue() && left.isConstant()) { dir = +1; } } else if ((op.equals(Operation.GREATER) || op.equals(Operation.GREATER_EQUAL))) { if (left instanceof FunctionVariable && right.isNumberValue() && right.isConstant()) { dir = +1; } else if (right instanceof FunctionVariable && left.isNumberValue() && left.isConstant()) { dir = -1; } } return dir; } /** * Interval dependent on coefficients of arithmetic expressions with * variables, represented by trees. e.g. x > a && x < b */ final private GeoFunction dependentInterval(Function fun) { AlgoDependentInterval algo = new AlgoDependentInterval(cons, fun); GeoFunction f = algo.getFunction(); return f; } final private GeoElement dependentGeoCopy(GeoElement origGeoNode) { AlgoDependentGeoCopy algo = new AlgoDependentGeoCopy(cons, origGeoNode); return algo.getGeo(); } /** * Wraps given functionNVar into GeoFunctionNVar, if dependent, * AlgoDependentFunctionNVar is created. * * @param fun * function * @param info * processing information * @return GeoFunctionNVar */ public GeoElement[] processFunctionNVar(FunctionNVar fun, EvalInfo info) { if (!fun.initFunction(info.isSimplifyingIntegers())) { return getParamProcessor().processParametricFunction( fun.getExpression(), fun.getExpression() .evaluate(StringTemplate.defaultTemplate), fun.getFunctionVariables(), fun.getLabel(), info); } String label = fun.getLabel(); GeoFunctionNVar gf; GeoElement[] vars = fun.getGeoElementVariables(); boolean isIndependent = (vars == null || vars.length == 0); if (isIndependent) { gf = new GeoFunctionNVar(cons, fun, info.isSimplifyingIntegers()); gf.setLabel(label); } else { gf = dependentFunctionNVar(label, fun); } if (!gf.validate(label == null)) { gf.remove(); throw new MyError(loc, "InvalidInput"); } return array(gf); } /** * Multivariate Function depending on coefficients of arithmetic expressions * with variables, e.g. f(x,y) = a x^2 + b y^2 */ final private GeoFunctionNVar dependentFunctionNVar(String label, FunctionNVar fun) { AlgoDependentFunctionNVar algo = new AlgoDependentFunctionNVar(cons, label, fun); GeoFunctionNVar f = algo.getFunction(); return f; } /** * Processes given equation to an array containing single line / conic / * implicit polynomial. Throws MyError for degree 0 equations, eg. 1=2 or * x=x. * * @param equ * equation * @param def * definition node (not same as equation in case of list1(2)) * @param info * processing information * @return line, conic, implicit poly or plane * @throws MyError * e.g. for invalid operation */ public final GeoElement[] processEquation(Equation equ, ExpressionNode def, EvalInfo info) throws MyError { ExpressionValue lhs = equ.getLHS().unwrap(); // z = 7 if (lhs instanceof FunctionVariable && !equ.getRHS().containsFreeFunctionVariable(null) && !equ.getRHS().evaluatesToNumber(true)) { equ.getRHS().setLabel(lhs.toString(StringTemplate.defaultTemplate)); try { return processValidExpression(equ.getRHS()); } catch (Exception e) { e.printStackTrace(); } } // s = t^2 String singleLeftVariable = null; if ((lhs instanceof Variable || lhs instanceof GeoDummyVariable)) { singleLeftVariable = lhs.toString(StringTemplate.defaultTemplate); if (kernel.lookupLabel(singleLeftVariable) != null) { singleLeftVariable = null; } } if ("X".equals(singleLeftVariable)) { return getParamProcessor().processXEquation(equ, info); } if ("r".equals(singleLeftVariable)) { try { equ.getRHS().setLabel(equ.getLabel()); return processValidExpression(equ.getRHS()); } catch (Exception e) { e.printStackTrace(); } } if (singleLeftVariable != null && equ.getLabel() == null) { GeoCasCell c = this.checkCasEval(((Variable) lhs).getName(), null, equ); if (c != null) { return new GeoElement[0]; } equ.getRHS().setLabel(lhs.toString(StringTemplate.defaultTemplate)); try { return processValidExpression(equ.getRHS()); } catch (Exception e) { e.printStackTrace(); } } if (lhs instanceof MyDouble && MyDouble.exactEqual(lhs.evaluateDouble(), MyMath.DEG)) { equ.getRHS().setLabel("deg"); try { return processValidExpression(equ.getRHS()); } catch (Exception e) { e.printStackTrace(); } } // z(x) = sin(x), see #5484 if (lhs instanceof ExpressionNode && ((ExpressionNode) lhs).getOperation() == Operation.ZCOORD && ((ExpressionNode) lhs).getLeft() .unwrap() instanceof FunctionVariable) { equ.getRHS().setLabel("z"); try { return processValidExpression(equ.getRHS()); } catch (Exception e) { e.printStackTrace(); } } return processEquation(equ, def, kernel.getConstruction().isFileLoading()); } /** * @param equ * equation * @param def * defining expression (either wrapped equation or something like * list1(1)) * @param allowConstant * true to allow equations like 2=3 or x=x, false to throw * MyError for those * @return line, conic, implicit poly or plane * @throws MyError * e.g. for invalid operation */ public final GeoElement[] processEquation(Equation equ, ExpressionNode def, boolean allowConstant) throws MyError { // AbstractApplication.debug("EQUATION: " + equ); // AbstractApplication.debug("NORMALFORM POLYNOMIAL: " + // equ.getNormalForm()); equ.initEquation(); // check no terms in z checkNoTermsInZ(equ); checkNoTheta(equ); if (app.has(Feature.EQUATION_LIST) && (equ.getLHS().evaluatesToList() || equ.getRHS().evaluatesToList())) { AlgoDependentEquationList algo = new AlgoDependentEquationList(cons, equ); GeoList list = algo.getList(); list.setLabel(equ.getLabel()); return list.asArray(); } if (equ.isFunctionDependent()) { return processImplicitPoly(equ, def); } int deg = equ.mayBePolynomial() && !equ.hasVariableDegree() ? equ.degree() : -1; // consider algebraic degree of equation // check not equation of eg plane switch (deg) { // linear equation -> LINE case 1: return processLine(equ, def); // quadratic equation -> CONIC case 2: return processConic(equ, def); // pi = 3 is not an equation, #1391 case 0: if (!allowConstant) { throw new MyError(app.getLocalization(), "InvalidEquation"); } // if constants are allowed, build implicit poly default: // test for "y= <rhs>" here as well String lhsStr = equ.getLHS().toString(StringTemplate.xmlTemplate) .trim(); if ("y".equals(lhsStr) && !equ.getRHS().containsFreeFunctionVariable("y")) { Function fun = new Function(equ.getRHS()); // try to use label of equation fun.setLabel(equ.getLabel()); return processFunction(fun, new EvalInfo(!cons.isSuppressLabelsActive())); } if ("z".equals(lhsStr) && !equ.getRHS().containsFreeFunctionVariable("z") && kernel.lookupLabel("z") == null) { FunctionVariable x = new FunctionVariable(kernel, "x"); FunctionVariable y = new FunctionVariable(kernel, "y"); FunctionNVar fun = new FunctionNVar(equ.getRHS(), new FunctionVariable[] { x, y }); // try to use label of equation fun.setLabel(equ.getLabel()); return processFunctionNVar(fun, new EvalInfo(!cons.isSuppressLabelsActive())); } return processImplicitPoly(equ, def); } } private void checkNoTheta(Equation equ) { if (equ.getRHS().containsFreeFunctionVariable(Unicode.thetaStr) || equ .getRHS().containsFreeFunctionVariable(Unicode.thetaStr)) { String[] errors = { "InvalidEquation" }; throw new MyError(loc, errors); } } /** * @param equ * equation */ protected void checkNoTermsInZ(Equation equ) { if (!equ.getNormalForm().isFreeOf('z')) { equ.setIsPolynomial(false); } } /** * @param equ * equation * @param def * definition expression (equation without any simplifications) * @return resulting line */ protected GeoElement[] processLine(Equation equ, ExpressionNode def) { double a = 0, b = 0, c = 0; GeoLine line; String label = equ.getLabel(); Polynomial lhs = equ.getNormalForm(); boolean isExplicit = equ.isExplicit("y"); boolean isIndependent = lhs.isConstant(); if (isIndependent) { // get coefficients a = lhs.getCoeffValue("x"); b = lhs.getCoeffValue("y"); c = lhs.getCoeffValue(""); line = new GeoLine(cons, a, b, c); } else { line = dependentLine(equ); } line.setDefinition(def); if (isExplicit) { line.setToExplicit(); } line.showUndefinedInAlgebraView(true); line.setLabel(label); return array(line); } /** * Line dependent on coefficients of arithmetic expressions with variables, * represented by trees. e.g. y = k x + d */ final private GeoLine dependentLine(Equation equ) { AlgoDependentLine algo = new AlgoDependentLine(cons, equ); GeoLine line = algo.getLine(); return line; } /** * @param equ * equation * @param def * definition expression * @return resulting conic */ public GeoElement[] processConic(Equation equ, ExpressionNode def) { double a = 0, b = 0, c = 0, d = 0, e = 0, f = 0; GeoConic conic; String label = equ.getLabel(); Polynomial lhs = equ.getNormalForm(); boolean isExplicit = equ.isExplicit("y"); boolean isSpecific = !isExplicit && (equ.isExplicit("yy") || equ.isExplicit("xx")); boolean isIndependent = lhs.isConstant(); if (isIndependent) { a = lhs.getCoeffValue("xx"); b = lhs.getCoeffValue("xy"); c = lhs.getCoeffValue("yy"); d = lhs.getCoeffValue("x"); e = lhs.getCoeffValue("y"); f = lhs.getCoeffValue(""); double[] coeffs = { a, b, c, d, e, f }; conic = new GeoConic(cons, coeffs); } else { conic = dependentConic(equ); } if (isExplicit) { conic.setToExplicit(); } else if (isSpecific || conic.getType() == GeoConicNDConstants.CONIC_CIRCLE) { conic.setToSpecific(); } conic.setDefinition(def); conic.setLabel(label); return array(conic); } /** * Conic dependent on coefficients of arithmetic expressions with variables, * represented by trees. */ final private GeoConic dependentConic(Equation equ) { AlgoDependentConic algo = new AlgoDependentConic(cons, equ); GeoConic conic = algo.getConic(); return conic; } /** * @param equ * equation * @param definition * definition node (not same as equation in case of list1(2)) * @return resulting implicit polynomial */ protected GeoElement[] processImplicitPoly(Equation equ, ExpressionNode definition) { String label = equ.getLabel(); Polynomial lhs = equ.getNormalForm(); boolean isIndependent = !equ.isFunctionDependent() && lhs.isConstant() && !equ.hasVariableDegree(); GeoImplicit poly; GeoElement geo = null; boolean is3d = equ.isForcedSurface() || equ.isForcedQuadric() || equ.isForcedPlane(); if (isIndependent || is3d) { poly = new GeoImplicitCurve(cons, equ); poly.setDefinition(equ.wrap()); geo = poly.toGeoElement(); if (is3d) { geo.setUndefined(); } } else { AlgoDependentImplicitPoly algo = new AlgoDependentImplicitPoly(cons, equ, definition, true); geo = algo.getGeo(); // might also return // Line or Conic geo.setDefinition(definition); } // AbstractApplication.debug("User Input: "+equ); geo.setLabel(label); return array(geo); } /** * @param node * expression * @param info * processing information * @return resulting geos * @throws MyError * on invalid operation */ public final GeoElement[] processExpressionNode(ExpressionNode node, EvalInfo info) throws MyError { ExpressionNode n = node; // command is leaf: process command if (n.isLeaf()) { ExpressionValue leaf = n.getLeft(); if (leaf instanceof Command) { Command c = (Command) leaf; c.setLabels(n.getLabels()); return cmdDispatcher.processCommand(c, info); } else if (leaf instanceof Equation) { Equation eqn = (Equation) leaf; eqn.setLabels(n.getLabels()); return processEquation(eqn, n, info); } else if (leaf instanceof Function) { Function fun = (Function) leaf; fun.setLabels(n.getLabels()); return processFunction(fun, info); } else if (leaf instanceof FunctionNVar) { FunctionNVar fun = (FunctionNVar) leaf; fun.setLabels(n.getLabels()); return processFunctionNVar(fun, info); } } ExpressionValue eval; // ggb3D : used by AlgebraProcessor3D in // extended processExpressionNode // ELSE: resolve variables and evaluate expressionnode n.resolveVariables(info); String label = n.getLabel(); if (n.containsFreeFunctionVariable(null)) { n = makeFunctionNVar(n).wrap(); } eval = n.evaluate(StringTemplate.defaultTemplate); if (eval instanceof ValidExpression && label != null) { ((ValidExpression) eval).setLabel(label); } boolean dollarLabelFound = false; ExpressionNode myNode = n; if (myNode.isLeaf()) { myNode = myNode.getLeftTree(); } // leaf (no new label specified): just return the existing GeoElement if (eval.isGeoElement() && n.getLabel() == null && !myNode.getOperation().equals(Operation.ELEMENT_OF) && !myNode.getOperation().equals(Operation.IF_ELSE)) { // take care of spreadsheet $ names: don't loose the wrapper // ExpressionNode here // check if we have a Variable switch (myNode.getOperation()) { case $VAR_COL: case $VAR_ROW: case $VAR_ROW_COL: // don't do anything here: we need to keep the wrapper // ExpressionNode // and must not return the GeoElement here dollarLabelFound = true; break; default: // return the GeoElement GeoElement[] ret = { (GeoElement) eval }; return ret; } } if (eval instanceof BooleanValue) { return processBoolean(n, eval); } else if (eval instanceof NumberValue) { return processNumber(n, eval, info); } else if (eval instanceof VectorValue) { return processPointVector(n, eval); } else if (eval instanceof Vector3DValue) { return processPointVector3D(n, eval); } else if (eval instanceof TextValue) { return processText(n, eval); } else if (eval instanceof MyList) { return processList(n, (MyList) eval); } else if (eval instanceof EquationValue) { Equation eq = ((EquationValue) eval).getEquation(); eq.setFunctionDependent(true); eq.setLabel(n.getLabel()); return processEquation(eq, n, info); } else if (eval instanceof Function) { return processFunction((Function) eval, info); } else if (eval instanceof FunctionNVar) { return processFunctionNVar((FunctionNVar) eval, info); } // we have to process list in case list=matrix1(1), but not when // list=list2 else if (eval instanceof GeoList && myNode.hasOperations()) { return processList(n, ((GeoList) eval).getMyList()); } else if (eval.isGeoElement()) { // e.g. B1 = A1 where A1 is a GeoElement and B1 does not exist yet // create a copy of A1 if (n.getLabel() != null || dollarLabelFound) { return array(dependentGeoCopy(n.getLabel(), n)); } } // REMOVED due to issue 131: // http://code.google.com/p/geogebra/issues/detail?id=131 // // expressions like 2 a (where a:x + y = 1) // // A1=b doesn't work for these objects // else if (eval instanceof GeoLine) { // if (((GeoLine)eval).getParentAlgorithm() instanceof // AlgoDependentLine) { // GeoElement[] ret = {(GeoElement) eval }; // return ret; // } // // } // if we get here, nothing worked Log.debug("Unhandled ExpressionNode: " + eval + ", " + eval.getClass()); return null; } /** * Make function or nvar function from expression, using all function * variables it has * * @param n * exression * @return function or nvar function */ public FunctionNVar makeFunctionNVar(ExpressionNode n) { Set<String> fvSet = new TreeSet<String>(); FVarCollector fvc = FVarCollector.getCollector(fvSet); n.traverse(fvc); if (fvSet.size() == 1) { return new Function(n, new FunctionVariable(kernel, fvSet .iterator().next())); } FunctionVariable[] fvArray = new FunctionVariable[fvSet.size()]; Iterator<String> it = fvSet.iterator(); int i = 0; while (it.hasNext()) { fvArray[i++] = new FunctionVariable(kernel, it.next()); } return new FunctionNVar(n, fvArray); } /** * @param n * expression node * @param evaluate * evaluated node * @param info * flags for setting label, using symbolic mode * @return value */ GeoElement[] processNumber(ExpressionNode n, ExpressionValue evaluate, EvalInfo info) { GeoElement ret; boolean isIndependent = !n.inspect(Inspecting.dynamicGeosFinder); MyDouble val = ((NumberValue) evaluate).getNumber(); boolean isAngle = val.isAngle(); double value = val.getDouble(); if (isIndependent) { if (isAngle) { ret = new GeoAngle(cons, value, AngleStyle.UNBOUNDED); } else { ret = new GeoNumeric(cons, value); } ret.setDefinition(n); } else { ret = dependentNumber(n, isAngle, evaluate).toGeoElement(); } if (n.isForcedFunction()) { ret = ((GeoFunctionable) ret).getGeoFunction(); } if (info.isFractions()) { InputHelper.updateSymbolicMode(ret); } if (info.isLabelOutput()) { String label = n.getLabel(); ret.setLabel(label); } else { cons.removeFromConstructionList(ret); } return array(ret); } /** * Number dependent on arithmetic expression with variables, represented by * a tree. e.g. t = 6z - 2 */ final private GeoNumberValue dependentNumber(ExpressionNode root, boolean isAngle, ExpressionValue evaluate) { AlgoDependentNumber algo = new AlgoDependentNumber(cons, root, isAngle, evaluate); GeoNumberValue number = algo.getNumber(); return number; } private GeoElement[] processList(ExpressionNode n, MyList evalList) { String label = n.getLabel(); GeoElement ret; // no operations or no variables are present, e.g. // { a, b, 7 } or { 2, 3, 5 } + {1, 2, 4} if (!n.hasOperations() || n.isConstant()) { // PROCESS list items to generate a list of geoElements ArrayList<GeoElement> geoElements = new ArrayList<GeoElement>(); boolean isIndependent = true; // make sure we don't create any labels for the list elements boolean oldMacroMode = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); int size = evalList.size(); for (int i = 0; i < size; i++) { ExpressionNode en = evalList.getListElement(i).wrap(); // we only take one resulting object GeoElement[] results = processExpressionNode(en, new EvalInfo(false)); GeoElement geo = results[0]; // add to list geoElements.add(geo); if (geo.isLabelSet() || !geo.isIndependent()) { isIndependent = false; } } cons.setSuppressLabelCreation(oldMacroMode); // Create GeoList object ret = kernel.getAlgoDispatcher().List(label, geoElements, isIndependent); if (!evalList.isDefined()) { ret.setUndefined(); ret.updateRepaint(); } ret.setDefinition(n); } // operations and variables are present // e.g. {3, 2, 1} + {a, b, 2} else { ret = listExpression(n); ret.setLabel(label); } return array(ret); } /** * Creates a dependent list object with the given label, e.g. {3, 2, 1} + * {a, b, 2} * * @param root * expression defining the dependent list * @return resulting list */ final public GeoList listExpression(ExpressionNode root) { AlgoDependentListExpression algo = new AlgoDependentListExpression(cons, root); return algo.getList(); } private GeoElement[] processText(ExpressionNode n, ExpressionValue evaluate) { GeoElement ret; String label = n.getLabel(); boolean isIndependent = n.isConstant(); if (isIndependent) { MyStringBuffer val = ((TextValue) evaluate).getText(); ret = text(val.toValueString(StringTemplate.defaultTemplate)); } else { ret = dependentText(n); } ret.setLabel(label); return array(ret); } /** * Text dependent on coefficients of arithmetic expressions with variables, * represented by trees. e.g. text = "Radius: " + r */ final private GeoText dependentText(ExpressionNode root) { AlgoDependentText algo = new AlgoDependentText(cons, root, true); GeoText t = algo.getGeoText(); return t; } /** * @param text * content of the text * @return resulting text */ final public GeoText text(String text) { GeoText t = new GeoText(cons); t.setTextString(text); return t; } private GeoElement[] processBoolean(ExpressionNode n, ExpressionValue evaluate) { GeoBoolean ret; String label = n.getLabel(); boolean isIndependent = !n.inspect(Inspecting.dynamicGeosFinder); if (isIndependent) { ret = new GeoBoolean(cons); ret.setValue(((BooleanValue) evaluate).getBoolean()); ret.setDefinition(n); } else { ret = (new AlgoDependentBoolean(cons, n)).getGeoBoolean(); } ret.setLabel(label); return array(ret); } private static boolean isEquation(ExpressionValue ev) { return ev.unwrap() instanceof EquationValue && !(ev.unwrap() instanceof NumberValue); } private GeoElement[] processPointVector(ExpressionNode n, ExpressionValue evaluate) { String label = n.getLabel(); if (evaluate instanceof MyVecNode) { // force vector for CAS vector GGB-1492 if (((MyVecNode) evaluate).isCASVector()) { n.setForceVector(); } ExpressionValue x = ((MyVecNode) evaluate).getX(); ExpressionValue y = ((MyVecNode) evaluate).getY(); if (isEquation(x) && isEquation(y)) { return processEquationIntersect(x, y); } } GeoVec2D p = ((VectorValue) evaluate).getVector(); boolean polar = p.getMode() == Kernel.COORD_POLAR; // we want z = 3 + i to give a (complex) GeoPoint not a GeoVector boolean complex = p.getMode() == Kernel.COORD_COMPLEX; GeoVec3D[] ret = new GeoVec3D[1]; boolean isIndependent = !n.inspect(Inspecting.dynamicGeosFinder); // make point if complex parts are present, e.g. 3 + i if (complex) { n.setForcePoint(); } else if (label != null) { if (!(n.isForcedPoint() || n.isForcedVector())) { // may be set by // MyXMLHandler if (Character.isLowerCase(label.charAt(0))) { n.setForceVector(); } else { n.setForcePoint(); } } } boolean isVector = n.shouldEvaluateToGeoVector(); if (isIndependent) { // get coords double x = p.getX(); double y = p.getY(); if (isVector) { ret[0] = kernel.getAlgoDispatcher().Vector(x, y); } else { ret[0] = kernel.getAlgoDispatcher().Point(x, y, complex); } ret[0].setDefinition(n); ret[0].setLabel(label); } else { if (isVector) { ret[0] = dependentVector(label, n); } else { ret[0] = dependentPoint(label, n, complex); } } if (polar) { ret[0].setMode(Kernel.COORD_POLAR); ret[0].updateRepaint(); } else if (complex) { ret[0].setMode(Kernel.COORD_COMPLEX); ret[0].updateRepaint(); } return ret; } private GeoElement[] processEquationIntersect(ExpressionValue x, ExpressionValue y) { GeoElement[] ret = processCommand(intersectCommand(x, y), new EvalInfo(true)); if (ret[0].getParentAlgorithm() instanceof HasShortSyntax) { ((HasShortSyntax) ret[0].getParentAlgorithm()).setShortSyntax(true); } return ret; } /** * @param x * first equation * @param y * second equation * @return intersection line as command */ private Command intersectCommand(ExpressionValue x, ExpressionValue y) { if (y.unwrap() instanceof Equation && x.unwrap() instanceof Equation) { boolean yHasZ = ((Equation) y.unwrap()) .containsFreeFunctionVariable("z"); boolean xHasZ = ((Equation) x.unwrap()) .containsFreeFunctionVariable("z"); if (xHasZ != yHasZ) { Equation needsFix = (Equation) (xHasZ ? y.unwrap() : x.unwrap()); ExpressionNode lhs = needsFix.getLHS() .plus(new ExpressionNode(kernel, new MyDouble(kernel, 0), Operation.MULTIPLY, new FunctionVariable(kernel, "z"))); needsFix.setLHS(lhs); } } Command inter = new Command(kernel, "Intersect", false); inter.addArgument(x.wrap()); inter.addArgument(y.wrap()); return inter; } /** * Point dependent on arithmetic expression with variables, represented by a * tree. e.g. P = (4t, 2s) */ final private GeoPoint dependentPoint(String label, ExpressionNode root, boolean complex) { AlgoDependentPoint algo = new AlgoDependentPoint(cons, label, root, complex); GeoPoint P = algo.getPoint(); return P; } /** * Vector dependent on arithmetic expression with variables, represented by * a tree. e.g. v = u + 3 w */ final private GeoVector dependentVector(String label, ExpressionNode root) { AlgoDependentVector algo = new AlgoDependentVector(cons, label, root); GeoVector v = algo.getVector(); return v; } /** * empty method in 2D : see AlgebraProcessor3D to see implementation in 3D * * @param n * 3D point expression * @param evaluate * evaluated node n * @return null */ protected GeoElement[] processPointVector3D(ExpressionNode n, ExpressionValue evaluate) { return null; } /** * Creates a dependent copy of origGeo with label */ final private GeoElement dependentGeoCopy(String label, ExpressionNode origGeoNode) { AlgoDependentGeoCopy algo = new AlgoDependentGeoCopy(cons, origGeoNode); algo.getGeo().setLabel(label); return algo.getGeo(); } /** * Show error dialog * * @param key * key for error.properties */ public void showError(String key) { app.showError(key); } /** @return "x(" */ public MyStringBuffer getXBracket() { if (xBracket == null) { xBracket = new MyStringBuffer(kernel, "x("); } return xBracket; } /** @return "y(" */ public MyStringBuffer getYBracket() { if (yBracket == null) { yBracket = new MyStringBuffer(kernel, "y("); } return yBracket; } /** @return "z(" */ public MyStringBuffer getZBracket() { if (zBracket == null) { zBracket = new MyStringBuffer(kernel, "z("); } return zBracket; } /** @return ")" */ public MyStringBuffer getCloseBracket() { if (closeBracket == null) { closeBracket = new MyStringBuffer(kernel, ")"); } return closeBracket; } /** * @return flag for disabled GCD when parsing lines from CAS */ public boolean getDisableGcd() { return disableGcd; } /** * @param disableGcd * flag for disabled GCD when parsing lines from CAS */ public void setDisableGcd(boolean disableGcd) { this.disableGcd = disableGcd; } /** * Reinitialize the set of commands that can be handled after some commands * were disabled */ public void reinitCommands() { if (this.cmdDispatcher != null) { cmdDispatcher.initCmdTable(); } } /** * @return reference to kernel */ public Kernel getKernel() { return kernel; } private static GeoElement[] array(GeoElement geo) { return new GeoElement[] { geo }; } /** * @param geoConic * element represented by equation * @return equation of the element */ public Equation parseEquation(GeoElementND geoConic) { ValidExpression ret = null; try { ret = kernel.getParser().parseGeoGebraExpression( geoConic.toValueString(StringTemplate.maxPrecision)); } catch (ParseException e) { // could be ParseException or Classcast Exception // https://play.google.com/apps/publish/?dev_acc=05873811091523087820#ErrorClusterDetailsPlace:p=org.geogebra.android&et=CRASH&lr=LAST_7_DAYS&ecn=java.lang.StringIndexOutOfBoundsException&tf=String.java&tc=java.lang.String&tm=startEndAndLength&nid&an&c&s=new_status_desc&ed=0 e.printStackTrace(); } if (ret instanceof Equation) { return (Equation) ret; } if (geoConic.isDefined()) { Log.debug(geoConic.toValueString(StringTemplate.maxPrecision)); } return new Equation(kernel, new ExpressionNode(kernel, Double.NaN), new ExpressionNode(kernel, Double.NaN)); } /** * @param enable * whether commands should be enabled */ public void setCommandsEnabled(boolean enable) { this.vectorsEnabled = enable; cmdDispatcher.setEnabled(enable); } /** * @return whether vector parsing is enabled */ public boolean enableVectors() { return vectorsEnabled; } /** * @return whether commands dispatching is enabled */ public boolean isCommandsEnabled() { return cmdDispatcher.isEnabled(); } }