package org.geogebra.common.cas.view; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.geogebra.common.cas.GeoGebraCAS; import org.geogebra.common.cas.giac.CASgiac.CustomFunctions; import org.geogebra.common.kernel.CASException; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.arithmetic.AssignmentType; import org.geogebra.common.kernel.arithmetic.Command; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.FunctionExpander; 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.MyList; import org.geogebra.common.kernel.arithmetic.MySpecialDouble; import org.geogebra.common.kernel.arithmetic.ValidExpression; 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.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoText; import org.geogebra.common.kernel.parser.ParseException; import org.geogebra.common.util.StringUtil; /** * Handles CAS input */ public class CASInputHandler { private CASView casView; private Kernel kernel; private CASTable consoleTable; private CASCellProcessor casCellProcessor; /** * @param view * CAS view */ public CASInputHandler(CASView view) { this.casView = view; kernel = view.getApp().getKernel(); consoleTable = view.getConsoleTable(); casCellProcessor = new CASCellProcessor( view.getApp().getLocalization()); } /** * Process input of current row. * * @param command * command like "Factor" or "Integral" * @param focus * whether focus should stay in CAS */ public void processCurrentRow(String command, boolean focus) { String ggbcmd = command; int selRow = consoleTable.getSelectedRow(); GeoCasCell cellValue = consoleTable.getGeoCasCell(selRow); if (cellValue == null) { return; } // Text cells do not need the processing below if (cellValue.isUseAsText()) { processRowThenEdit(selRow, true); return; } // Multiple cells selected and solve button clicked if (("Solve".equalsIgnoreCase(ggbcmd) || "NSolve" .equalsIgnoreCase(ggbcmd)) && (consoleTable.getSelectedRows().length > 1)) { processMultipleRows(ggbcmd); return; } cellValue.setError(null); // get editor CASTableCellEditor cellEditor = consoleTable.getEditor(); // get possibly selected text String selectedText = cellEditor.getInputSelectedText(); int selStart = cellEditor.getInputSelectionStart(); int selEnd = cellEditor.getInputSelectionEnd(); String selRowInput = cellEditor.getInput(); // needed for GGB-517 if (cellValue.getInput(StringTemplate.defaultTemplate).equals("")) { cellValue.setInput(selRowInput); } // hack for debugging the underlying cas if (selRowInput != null && selRowInput.startsWith("@")) { try { String s = kernel.getGeoGebraCAS() .evaluateRaw(selRowInput.substring(1)); GeoText text = kernel .lookupLabel("casOutput") instanceof GeoText ? (GeoText) kernel.lookupLabel("casOutput") : new GeoText(kernel.getConstruction()); if (!text.isLabelSet()) { text.setLabel("casOutput"); } // Log.debug(s); text.setTextString(s); text.update(); } catch (Throwable e) { e.printStackTrace(); } return; } if (selRowInput == null || selRowInput.length() == 0) { if (consoleTable.getSelectedRow() != -1) { consoleTable.startEditingRow(consoleTable.getSelectedRow()); GeoCasCell cell = consoleTable .getGeoCasCell(consoleTable.getSelectedRow()); if (cell.getInputVE() != null) { selRowInput = cell.getInputVE() .toString(StringTemplate.numericDefault); } } // process empty row if (selRowInput.length() == 0) { // not first row if (selRow > 0) { selRowInput = wrapPrevCell(selRow, cellValue); } else { return; } } } // save the edited value into the table model consoleTable.stopEditing(); // STANDARD CASE: GeoGebraCAS input // break text into prefix, evalText, postfix String prefix, evalText, postfix; boolean hasSelectedText = meaningfulSelection(selectedText); if (hasSelectedText) { // selected text: break it up into prefix, evalText, and postfix prefix = selRowInput.substring(0, selStart).trim() + " "; if (selStart > 0 || selEnd < selRowInput.length()) { // part of input is selected evalText = "(" + selectedText + ")"; // for avoiding splitting text like 7-(x-2) as 7(-(x-2)) // as they are different, a '+' is appended to the prefix char firstEvalChar = selectedText.trim().charAt(0); if (firstEvalChar == '+' || firstEvalChar == '-') { prefix = prefix + "+"; } } else { // full input is selected evalText = selectedText; } postfix = selRowInput.substring(selEnd).trim(); } else { // no selected text: evaluate input using current cell prefix = ""; evalText = selRowInput; postfix = ""; } try { // resolve static row references and change input field accordingly boolean staticReferenceFound = false; String newPrefix = resolveCASrowReferences(prefix, selRow, GeoCasCell.ROW_REFERENCE_STATIC, false); if (!newPrefix.equals(prefix)) { staticReferenceFound = true; prefix = newPrefix; } String newEvalText = resolveCASrowReferences(evalText, selRow, GeoCasCell.ROW_REFERENCE_STATIC, hasSelectedText); if (!newEvalText.equals(evalText)) { staticReferenceFound = true; evalText = newEvalText; } String newPostfix = resolveCASrowReferences(postfix, selRow, GeoCasCell.ROW_REFERENCE_STATIC, false); if (!newPostfix.equals(postfix)) { staticReferenceFound = true; postfix = newPostfix; } if (staticReferenceFound) { // change input if necessary cellValue.setInput(newPrefix + newEvalText + newPostfix); } if ("NSolve".equals(ggbcmd)) { String inputStrForNSolve = handleNSolve(cellValue, evalText); // get input string for NSolve if (inputStrForNSolve != null) { StringBuilder sb = new StringBuilder(); sb.append(inputStrForNSolve); // sb.append("]"); if (!cellValue.getInput(StringTemplate.defaultTemplate) .equals(sb.toString())) { cellValue.setNSolveCmdNeeded(true); cellValue.setInput(sb.toString()); selRowInput = sb.toString(); evalText = sb.toString(); } } } if (cellValue.getNSolveCmdNeeded() && !"NSolve".equals(ggbcmd)) { if (cellValue.getInputVE() != null && cellValue.getInputVE() .getTopLevelCommand() != null) { cellValue.setNSolveCmdNeeded(false); } else { ggbcmd = "NSolve"; } } // FIX common INPUT ERRORS in evalText if (!hasSelectedText && ("Evaluate".equals(ggbcmd) || "KeepInput".equals(ggbcmd))) { String fix = casCellProcessor.fixInput(cellValue, selRowInput, staticReferenceFound); if (fix != null) { evalText = fix; } } // we want to avoid user selecting a+b in (a+b)/c // TODO cache this somehow boolean structureOK = cellValue.isStructurallyEqualToLocalizedInput( prefix + evalText + postfix); if (!structureOK) { // show current selection again consoleTable.startEditingRow(selRow); cellEditor = consoleTable.getEditor(); cellEditor.setInputSelectionStart(selStart); cellEditor.setInputSelectionEnd(selEnd); return; } boolean isAssignment = cellValue.getAssignmentVariable() != null; boolean isEvaluate = "Evaluate".equals(ggbcmd); boolean isNumeric = "Numeric".equals(ggbcmd); boolean isKeepInput = "KeepInput".equals(ggbcmd); // Substitute dialog if ("Substitute".equals(ggbcmd)) { // if cell has assignment and nothing other is selected -> use // input without defnition // eg. a:=b+c // use only b+c if (isAssignment && !hasSelectedText) { evalText = cellValue.getInputVE() .toString(StringTemplate.defaultTemplate); } // show substitute dialog casView.showSubstituteDialog(prefix, evalText, postfix, selRow); return; } // assignments are processed immediately, the ggbcmd creates a new // row below if (isAssignment) { processAssignment(ggbcmd, cellValue, prefix, postfix, focus, selRow, isKeepInput || isEvaluate || isNumeric); return; } // standard case: build eval command // don't wrap Numeric[pi, 20] with a second Numeric command // as this would remove precision // don't wrap in KeepInput neither boolean wrapEvalText = !isEvaluate && !isKeepInput && !(isNumeric && (evalText.startsWith("Numeric[") || evalText.startsWith("Numeric("))); if (wrapEvalText) { // prepare evalText as ggbcmd[ evalText, parameters ... ] StringBuilder sb = new StringBuilder(); sb.append(ggbcmd); sb.append("["); sb.append(evalText); sb.append("]"); evalText = sb.toString(); } // remember evalText and selection for future calls of processRow() cellValue.setProcessingInformation(prefix, evalText, postfix); cellValue.setEvalCommand(ggbcmd); } catch (CASException ex) { cellValue.setError(ex.getKey()); } // process given row and below, then start editing processRowThenEdit(selRow, focus); } private String wrapPrevCell(int selRow, GeoCasCell cellValue) { // get previous cell GeoCasCell prevCell = consoleTable.getGeoCasCell(selRow - 1); if (prevCell != null && prevCell.getOutputValidExpression() != null) { // get output of previous cell StringBuilder prevCellName = new StringBuilder(); prevCellName.append(prevCell.getAssignmentVariable()); if (!prevCellName.toString().equals("null")) { if (prevCell.getFunctionVariables() != null) { prevCellName.append("("); FunctionVariable[] fVars = prevCell.getFunctionVariables(); for (int i = 0; i < fVars.length; i++) { prevCellName.append(fVars[i] .toString(StringTemplate.defaultTemplate)); if (i != fVars.length - 1) { prevCellName.append(","); } } prevCellName.append(")"); } cellValue.setInput(prevCellName.toString()); return prevCellName.toString(); } cellValue.setInput("$" + (selRow)); return "$" + (selRow); } return ""; } private void processAssignment(String ggbcmd, GeoCasCell cellValue, String prefix, String postfix, boolean focus, int selRow, boolean isBasicTool) { boolean isNumeric = "Numeric".equals(ggbcmd); ValidExpression inVE = cellValue.getInputVE(); // if evaluation mode is Numeric, only the evaluation text is // wrapped, input is left unchanged if (isNumeric && inVE != null) { // evaluation text is wrapped only if the input is not // already wrapped if (inVE.getTopLevelCommand() == null || !inVE.getTopLevelCommand().getName().equals("Numeric")) { cellValue.setProcessingInformation(prefix, ggbcmd + "[" + inVE.toString(StringTemplate.numericNoLocal) + "]", postfix); } // otherwise set the evaluation text to input } else { cellValue.setProcessingInformation(prefix, cellValue.getInput(StringTemplate.defaultTemplate), postfix); } if (isBasicTool) { cellValue.setEvalCommand(ggbcmd); } // evaluate assignment row boolean needInsertRow = !isBasicTool; boolean success = processRowThenEdit(selRow, !needInsertRow && focus); // insert a new row below with the assignment label and process // it // using the current command if (success && needInsertRow) { String ggbcmd1 = ggbcmd; ValidExpression outputVE = cellValue.getOutputValidExpression(); String assignmentLabel = outputVE.getLabelForAssignment(); String label = cellValue.getEvalVE().getLabelForAssignment(); GeoCasCell newRowValue = new GeoCasCell(kernel.getConstruction()); StringBuilder sb = new StringBuilder(label); boolean isDerivative = "Derivative".equals(ggbcmd); boolean isIntegral = !isDerivative && "Integral".equals(ggbcmd); if ((isDerivative || isIntegral) && outputVE.unwrap() instanceof FunctionNVar) { if (isDerivative) { sb.append('\''); } sb.append('(') .append(((FunctionNVar) outputVE.unwrap()) .getVarString(StringTemplate.defaultTemplate)) .append(')'); sb.append(outputVE.getAssignmentOperator()); sb.append(ggbcmd).append('[').append(assignmentLabel) .append(']'); ggbcmd1 = "Evaluate"; } newRowValue.setInput(sb.toString()); casView.insertRow(newRowValue, true); processCurrentRow(ggbcmd1, focus); } } // function to handle NSolve input for non-polynomial equations private String handleNSolve(GeoCasCell cellValue, String evalText) { boolean isEquList = false; StringBuilder sb = new StringBuilder(); // sb.append("NSolve["); sb.append(cellValue.getInput(StringTemplate.defaultTemplate)); ExpressionValue expandValidExp = null; // case input is a cell if (evalText.charAt(0) == (GeoCasCell.ROW_REFERENCE_DYNAMIC)) { int row = Integer.parseInt(evalText.substring(1, 2)); GeoCasCell geoCasCell = consoleTable.getGeoCasCell(row - 1); expandValidExp = geoCasCell.getInputVE(); } else { try { expandValidExp = (kernel.getGeoGebraCAS()).getCASparser() .parseGeoGebraCASInput(evalText, null) .traverse(FunctionExpander.getCollector()); } catch (Exception e) { return null; } } GeoGebraCAS cas = (GeoGebraCAS) kernel.getGeoGebraCAS(); try { String casResult = ""; if (expandValidExp == null) { return sb.toString(); } // use NSolve tool with list of equations else if (expandValidExp.isExpressionNode() && ((ExpressionNode) expandValidExp) .getLeft() instanceof MyList && ((ExpressionNode) expandValidExp).getRight() == null) { isEquList = true; MyList equList = (MyList) (((ExpressionNode) expandValidExp) .getLeft()); // handle case list with two equations // TODO handle list with n equations casResult = cas.getCurrentCAS() .evaluateRaw( CustomFunctions.GGBIS_POLYNOMIAL + "(" + equList.getListElement(0).toString( StringTemplate.giacTemplate) + ") && " + CustomFunctions.GGBIS_POLYNOMIAL + "(" + equList.getListElement(1).toString( StringTemplate.giacTemplate) + ")"); } // use NSolve tool with one equation else { casResult = cas.getCurrentCAS() .evaluateRaw(CustomFunctions.GGBIS_POLYNOMIAL + "(" + expandValidExp.toString(StringTemplate.giacTemplate) + ")"); } // case it is not if ("0".equals(casResult) || "false".equals(casResult)) { ValidExpression ve = cellValue.getEvalVE(); HashSet<GeoElement> vars = ve.getVariables(); if (!vars.isEmpty()) { Iterator<GeoElement> it = vars.iterator(); while (it.hasNext()) { GeoElement next = it.next(); if (next instanceof GeoDummyVariable) { // for non-polynomial equation list // we have to add all vars if (isEquList) { sb.append(",{"); Set<String> varsStrSet = getVariableStrSet( vars); if (!varsStrSet.isEmpty()) { Iterator<String> itStrSet = varsStrSet .iterator(); while (itStrSet.hasNext()) { String nextStr = itStrSet.next(); sb.append(nextStr); sb.append("=1"); sb.append(","); } sb.setLength(sb.length() - 1); sb.append("}"); } varsStrSet.clear(); } else { // add var=1 String var = next.toString( StringTemplate.defaultTemplate); sb.append(","); sb.append(var); sb.append("=1"); } break; } if (next instanceof GeoCasCell) { String var = next .toString(StringTemplate.defaultTemplate); GeoElement geo = kernel.getConstruction() .lookupLabel(var); if (geo instanceof GeoFunction) { FunctionVariable[] varsOfFunc = ((GeoFunction) geo) .getFunction().getFunctionVariables(); if (varsOfFunc.length > 0) { var = varsOfFunc[0].toString( StringTemplate.defaultTemplate); } } else { break; } sb.append(","); sb.append(var); sb.append("=1"); break; } } } vars.clear(); } else { if (sb.toString().contains("$")) { cellValue.setNSolveCmdNeeded(true); } } } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); } return sb.toString(); } /** * @return the set with geoDummy names */ private static Set<String> getVariableStrSet(HashSet<GeoElement> vars) { Set<String> varsStrSet = new HashSet<String>(); if (!vars.isEmpty()) { Iterator<GeoElement> it = vars.iterator(); while (it.hasNext()) { GeoElement next = it.next(); if (next instanceof GeoDummyVariable) { String var = next.toString(StringTemplate.defaultTemplate); if (!varsStrSet.contains(var)) { varsStrSet.add(var); } } } } return varsStrSet; } /** * We want to ignore selected text if it's just ) or ] because of double * click * * @param text * @return whether it is meaningful to consider this as a selection */ private static boolean meaningfulSelection(String text) { if (text == null) { return false; } String trimmed = text.trim(); if (trimmed.length() == 0) { return false; } if (trimmed.length() == 1 && "]})".indexOf(trimmed) > -1) { return false; } return true; } /** * Deletes current row, including all dependent objects */ public void deleteCurrentRow() { int[] selected = consoleTable.getSelectedRows(); for (int current : selected) { GeoCasCell cell = consoleTable.getGeoCasCell(current); if (cell != null) { cell.remove(); consoleTable.getApplication().storeUndoInfo(); } } } /** * Determines the selected rows and tries to solve (solve is the only * implemented function up to now) the equations or lists of equations found * in the selected rows. The result is written into the active cell. * * @param ggbcmd * is the given command (just Solve is supported) * @param params * the list of parameters */ private void processMultipleRows(String ggbcmd) { StringTemplate tpl = StringTemplate.defaultTemplate; // get current row and input text consoleTable.stopEditing(); int selRow = consoleTable.getSelectedRow(); if (selRow < 0) { selRow = consoleTable.getRowCount() - 1; } int currentRow = selRow; int[] selectedIndices = consoleTable.getSelectedRows(); int nrEquations; // remove empty cells because empty cells' inputVE vars are null ArrayList<Integer> l = new ArrayList<Integer>(); for (int i = 0; i < selectedIndices.length; i++) { if (!casView.isRowEmpty(selectedIndices[i])) { l.add(selectedIndices[i]); } } selectedIndices = new int[l.size()]; for (int i = 0; i < l.size(); i++) { selectedIndices[i] = l.get(i); } boolean oneRowOnly = false; if (selectedIndices.length == 1) { oneRowOnly = true; nrEquations = 1; } else { nrEquations = selectedIndices.length; } GeoCasCell cellValue; try { cellValue = consoleTable.getGeoCasCell(currentRow); } catch (ArrayIndexOutOfBoundsException e) { cellValue = null; } // insert new row if the row below the last selected row is not empty if (cellValue == null || (!cellValue.isEmpty() && !oneRowOnly)) { cellValue = new GeoCasCell(kernel.getConstruction()); currentRow = consoleTable.getRowCount() - 1; casView.insertRow(cellValue, false); } ArrayList<GeoElement> vars = new ArrayList<GeoElement>(); boolean foundNonPolynomial = false; // generates an array of references (e.g. $1,a,...) and // an array of equations int counter = 0; String[] references = new String[nrEquations]; for (int i = 0; i < selectedIndices.length; i++) { GeoCasCell selCellValue = consoleTable .getGeoCasCell(selectedIndices[i]); if ("NSolve".equals(ggbcmd) && selCellValue != null) { GeoGebraCAS cas = (GeoGebraCAS) kernel.getGeoGebraCAS(); try { StringBuilder inputStr = new StringBuilder(); // check if input is polynomial inputStr.append(CustomFunctions.GGBIS_POLYNOMIAL); inputStr.append("("); inputStr.append(selCellValue.getOutputValidExpression() .toString(StringTemplate.giacTemplate)); inputStr.append(")"); String casResult = cas.getCurrentCAS() .evaluateRaw(inputStr.toString()); HashSet<GeoElement> cellVars = selCellValue.getInputVE() .getVariables(); Iterator<GeoElement> it = cellVars.iterator(); while (it.hasNext()) { GeoElement curr = it.next(); // if input was geoCasCell if (curr instanceof GeoCasCell) { // we should use only the variables from output HashSet<GeoElement> currCellVars = ((GeoCasCell) curr) .getOutputValidExpression().getVariables(); Iterator<GeoElement> currIt = currCellVars .iterator(); if (vars.isEmpty()) { vars.addAll(currCellVars); } else { while (currIt.hasNext()) { GeoElement currEl = currIt.next(); int j; for (j = 0; j < vars.size(); j++) { if (currEl .toString( StringTemplate.defaultTemplate) .equals(vars.get(j).toString( StringTemplate.defaultTemplate))) { break; } } if (j == vars.size()) { vars.add(currEl); } } } continue; } if (vars.isEmpty()) { vars.add(curr); } else { int j; for (j = 0; j < vars.size(); j++) { if (curr.toString( StringTemplate.defaultTemplate) .equals(vars.get(j).toString( StringTemplate.defaultTemplate))) { break; } } if (j == vars.size()) { vars.add(curr); } } } // case it is not if ("false".equals(casResult) || "0".equals(casResult)) { foundNonPolynomial = true; } } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); } } String cellText; String assignedVariable = selCellValue != null ? selCellValue.getAssignmentVariable() : null; boolean inTheSelectedRow = currentRow == selectedIndices[i]; if (assignedVariable != null) { references[i] = assignedVariable; } else { cellText = selCellValue.getInputVE().toString(tpl); cellText = resolveCASrowReferences(cellText, selectedIndices[i], GeoCasCell.ROW_REFERENCE_STATIC, false); if (!inTheSelectedRow) { references[i] = "$" + (selectedIndices[i] + 1); } else { assert (false) : "this should not be possible"; references[counter] = cellText; } } } String evalText; StringBuilder cellText = new StringBuilder("{"); for (int i = 0; i < nrEquations; i++) { if (i != 0) { cellText.append(", "); } cellText.append(references[i]); } cellText.append("}"); if (!vars.isEmpty() && foundNonPolynomial) { cellText.append(",{"); boolean first = true; for (int i = 0; i < vars.size(); i++) { if (!first) { cellText.append(","); } if (vars.get(i) instanceof GeoDummyVariable) { first = false; cellText.append(vars.get(i).toString( StringTemplate.defaultTemplate)); cellText.append("=1"); } } cellText.append("}"); } // FIX common INPUT ERRORS in evalText if (("Evaluate".equals(ggbcmd) || "KeepInput".equals(ggbcmd))) { String fixedInput = casCellProcessor .fixInputErrors(cellText.toString()); if (!fixedInput.equals(cellText.toString())) { evalText = fixedInput; } } cellValue.setInput(cellText.toString()); // prepare evalText as ggbcmd[ evalText, parameters ... ] StringBuilder sb = new StringBuilder(); sb.append(ggbcmd); sb.append("["); sb.append(cellText); sb.append("]"); evalText = sb.toString(); // remember evalText and selection for future calls of processRow() cellValue.setProcessingInformation("", evalText, ""); // TODO: write some evaluation comment // cellValue.setEvalComment(paramString); // process given row and below, then start editing processRowThenEdit(currentRow, true); } /** * Processes given row. * * @param selRow * row index * @param startEditing * start editing * @return success */ public boolean processRowThenEdit(int selRow, boolean startEditing) { GeoCasCell cellValue = consoleTable.getGeoCasCell(selRow); boolean success; boolean isLastRow = consoleTable.getRowCount() <= selRow + 1; if (!cellValue.isError() && !cellValue.isUseAsText()) { // evaluate output and update twin geo kernel.getAlgebraProcessor().processCasCell(cellValue, isLastRow); } else if (cellValue.isIndependent() && !cellValue.isUseAsText()) { // make sure the cell is in construction list, so CAS cells have the // right index, see #3241 kernel.getConstruction().addToConstructionList(cellValue, true); } else if (cellValue.isUseAsText()) { kernel.getConstruction().addToConstructionList(cellValue, true); kernel.notifyAdd(cellValue); } kernel.notifyRepaint(); // if redefinition occurred, the row number could have changed // we need to update the variables int rowNum = cellValue.getRowNumber(); isLastRow = consoleTable.getRowCount() <= rowNum + 1; // check success success = !cellValue.isError(); if (startEditing || consoleTable.keepEditing(!success, rowNum)) { // start editing row below successful evaluation boolean goDown = success && // we are in last row or next row is empty (isLastRow || casView.isRowOutputEmpty(rowNum + 1)); consoleTable.startEditingRow(goDown ? rowNum + 1 : rowNum); } return success; } /** * Replaces references to other rows (e.g. #, #3, $3, ##, #3#, $$, $3$) in * the input string by the values from those rows. Warning: dynamic * references (with $) are also replaced statically. * * @param str * the input expression * @param selectedRow * the row this expression is in * @param delimiter * the delimiter to look for * @param noParentheses * if true no parentheses will be added in every case<br/> * if false parentheses will be added around replaced references * except the replacement is just a positive number, a variable * or the whole term (given by parameter str) was nothing but the * reference * @return the string with resolved references. * @author Johannes Renner * @throws CASException * if the number of the row reference is invalid (the number is * higher than the current number of rows or the reference * number is the number of the current row) then an * {@link CASException} is thrown */ public String resolveCASrowReferences(String str, int selectedRow, char delimiter, boolean noParentheses) throws CASException { boolean newNoParentheses = noParentheses; StringBuilder sb = new StringBuilder(); // switch (delimiter) { // case GeoCasCell.ROW_REFERENCE_DYNAMIC: // case GeoCasCell.ROW_REFERENCE_STATIC: // Log.debug(selectedRow + ": " + str); boolean foundReference = false; boolean addParentheses = false; boolean startOfReferenceNumber = false; boolean needOutput = true; // -1 means reference without a number (to the previous row) int referenceNumber = -1; for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (foundReference) { if (StringUtil.isDigit(c)) { if (startOfReferenceNumber) { startOfReferenceNumber = false; referenceNumber = 0; } referenceNumber = referenceNumber * 10 + Character.digit(c, 10); continue; } else if (c == delimiter) { // ## or $$ or #n# or $n$ needOutput = false; continue; } foundReference = false; // needed if the reference is the first term in the // expression, because in this case addParantheses isn't // true yet if (c != ')') { addParentheses = true; newNoParentheses = false; } handleReference(sb, selectedRow, referenceNumber, addParentheses, newNoParentheses, needOutput); } if (c != delimiter) { sb.append(c); addParentheses = true; // if a part of the expression was selected the given String // str has parentheses in the beginning and the end // --> if just the reference is between these parentheses no // more parenthesis should be added (--> leave // newNoParentheses true), otherwise newNoParentheses will // be set to false above in the for-loop if (i == 0 && c != '(' || i > 0 && c != ')') { newNoParentheses = false; } } else { foundReference = true; startOfReferenceNumber = true; } } if (foundReference) { handleReference(sb, selectedRow, referenceNumber, addParentheses, newNoParentheses, needOutput); } // break; // } return sb.toString(); } private void handleReference(StringBuilder sb, int selectedRow, int referenceNumber, boolean addParentheses, boolean noParentheses, boolean needOutput) throws CASException { if (referenceNumber > 0 && referenceNumber != selectedRow + 1 && referenceNumber <= casView.getRowCount()) { String reference; if (needOutput) { // a # (or $) with a following number is in the the input (for // example #3) reference = casView.getRowOutputValue(referenceNumber - 1); } else { // a # (or $) with a following number and the same delimiter // again is in the the input (for example #3#) reference = casView.getRowInputValue(referenceNumber - 1); } appendReference(sb, reference, addParentheses, noParentheses); } else if (referenceNumber == -1 && selectedRow > 0) { String reference; if (needOutput) { // just a # (or $) is in the input (without a number) reference = casView.getRowOutputValue(selectedRow - 1); } else { // ## or $$ reference = casView.getRowInputValue(selectedRow - 1); } appendReference(sb, reference, addParentheses, noParentheses); } else { CASException ex = new CASException("CAS.InvalidReferenceError"); ex.setKey("CAS.InvalidReferenceError"); throw ex; } } private void appendReference(StringBuilder sb, String reference, boolean addParentheses, boolean noParentheses) { boolean parantheses = addParentheses; // don't add parenthesis if the given expression is just a positive // number if (isPositiveNumber(reference)) { parantheses = false; } // or if the given reference is just one variable else { try { String parsed = kernel.getParser().parseLabel(reference); // since parseLabel parses only the first label we need to check // if the parsed String is the full reference if (parsed.equals(reference)) { parantheses = false; } } catch (ParseException e) { // do nothing because the reference isn't a label } } if (parantheses && !noParentheses) { sb.append("(" + reference + ")"); } else { sb.append(reference); } } private static boolean isPositiveNumber(String s) { try { double d = Double.parseDouble(s); return d >= 0; } catch (NumberFormatException ex) { return false; } } /** * @param cell * cell whose state should be displayed in the barble * @param renderer * renderer of the cell */ public static void handleMarble(GeoCasCell cell, MarbleRenderer renderer) { boolean marbleShown = cell.hasTwinGeo() && cell.getTwinGeo().isEuclidianVisible() && cell.getTwinGeo().isEuclidianShowable(); ValidExpression ve = cell.getOutputValidExpression(); boolean isPlottable = true; int dim = cell.getKernel().getApplication().is3D() ? 3 : 2; if (ve != null) { if (ve.unwrap() instanceof MyList) { MyList ml = (MyList) ve.unwrap(); int i = 0; while (i < ml.size() && isPlottable) { isPlottable &= !(ml.getItem(i) .unwrap() instanceof MySpecialDouble) && !ml.getItem(i++).unwrap() .inspect(Inspecting.UnplottableChecker .getChecker(dim)); } } else if (ve.unwrap() instanceof Command) { isPlottable &= ((Command) ve.unwrap()).getName().equals("If"); } else { isPlottable = false; } } if (ve != null && !cell.getAssignmentType().equals(AssignmentType.DELAYED)) { if (cell.showOutput() && !cell.isError() && (isPlottable || !ve.unwrap().inspect( Inspecting.UnplottableChecker.getChecker(dim)))) { renderer.setMarbleValue(marbleShown); renderer.setMarbleVisible(true); } else { renderer.setMarbleVisible(false); } } else { renderer.setMarbleVisible(false); } } }