package com.bytezone.diskbrowser.visicalc; import java.util.ArrayList; import java.util.Iterator; import java.util.List; class Expression extends AbstractValue implements Iterable { // Expressions: // number // cell address // function // expression [+-*/^] expression // [+-] expression // ( expression ) // From the reference card: // Expressions are evaluated strictly from left to right except as modified by // parentheses. You must start an expression with a +, a digit (0-9), or one of // the symbols @-(. or # // [@IF(@ISERROR(BK24),0,BK24)] // [@IF(D4=0,0,1)] // [@IF(D4=0,0,B32+1)] // [@IF(D4=0,0,1+(D3/100/D4)^D4-1*100)] // [@SUM(C4...F4)] // [+C4-@SUM(C5...C12)] // [+D5/100/12] // [.3*(B4+B7+B8+B9)] // [+N12+(P12*(.2*K12+K9-O12))] private final Cell cell; private final List operators = new ArrayList (); private final List signs = new ArrayList (); private final String text; public Expression (Sheet parent, Cell cell, String text) { super ("Exp"); this.cell = cell; this.text = text; String line = balanceBrackets (text); // add trailing right brackets if required int ptr = 0; while (ptr < line.length ()) { // check for optional leading + or - char ch = line.charAt (ptr); if (ch == '-') { signs.add ("(-)"); ch = line.charAt (++ptr); } else { signs.add ("(+)"); if (ch == '+') ch = line.charAt (++ptr); } // check for mandatory function/sub-expression/number/cell reference switch (ch) { case '@': // function String functionText = getBalancedText (line.substring (ptr)); // System.out.println ("Func : " + functionText); ptr += functionText.length (); values.add (Function.getInstance (parent, cell, functionText)); break; case '(': // parentheses block String bracketText = getBalancedText (line.substring (ptr)); ptr += bracketText.length (); values.add (new Expression (parent, cell, bracketText.substring (1, bracketText.length () - 1))); break; case '#': // no idea System.out.printf ("Hash character [%s] in [%s]%n", ch, line); ptr++; break; default: if (ch == '.' || (ch >= '0' && ch <= '9')) // number { String numberText = getNumberText (line.substring (ptr)); ptr += numberText.length (); values.add (new Number (numberText)); } else if (ch >= 'A' && ch <= 'Z') // cell address { String addressText = getAddressText (line.substring (ptr)); ptr += addressText.length (); values.add (parent.getCell (addressText)); } else { System.out.printf ("Unexpected character [%s] in [%s]%n", ch, line); return; } } // check for optional continuation operator if (ptr < line.length ()) { ch = line.charAt (ptr); if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '^') operators.add (line.substring (ptr, ++ptr)); else { System.out.printf ("Unknown operator [%s] in [%s]%n", ch, line); return; } } } assert values.size () > 0; // if (values.size () == 0) // System.out.printf ("Nothing[%s]%n", text); } Value reduce () { if (values.size () == 1 && signs.get (0).equals ("(+)")) return values.get (0); return this; } int size () { return values.size (); } Value get (int index) { return values.get (index); } @Override public void calculate () { if (values.size () == 0) { System.out.println ("nothing to calculate: " + text); return; } // System.out.printf (" calc %-6s %s%n", cell.getAddressText (), text); if (!isVolatile) return; boolean currentVolatile = false; // boolean debug = cell.getAddress ().matches ("C8"); boolean debug = false; if (debug) { System.out.println (this); System.out.printf ("(1) exp %s is currently %svolatile%n", text, isVolatile ? " " : "not "); } try { Value thisValue = values.get (0); thisValue.calculate (); if (debug) { System.out.println (this); System.out.printf ("(2) exp %s is currently %svolatile%n", thisValue.getText (), thisValue.isVolatile () ? " " : "not "); } value = 0; if (!thisValue.isValueType (ValueType.VALUE)) { valueType = thisValue.getValueType (); return; } value = thisValue.getValue (); if (!currentVolatile) currentVolatile = thisValue.isVolatile (); String sign = signs.get (0); if (sign.equals ("(-)")) value *= -1; for (int i = 1; i < values.size (); i++) { thisValue = values.get (i); thisValue.calculate (); if (!thisValue.isValueType (ValueType.VALUE)) { valueType = thisValue.getValueType (); return; } double nextValue = thisValue.getValue (); if (debug) { System.out.println (this); System.out.printf ("(3.%d) exp %s is currently %svolatile%n", i, thisValue.getText (), thisValue.isVolatile () ? " " : "not "); } if (!currentVolatile) currentVolatile = thisValue.isVolatile (); sign = signs.get (i); if (sign.equals ("(-)")) nextValue *= -1; String operator = operators.get (i - 1); if (operator.equals ("+")) value += nextValue; else if (operator.equals ("-")) value -= nextValue; else if (operator.equals ("*")) value *= nextValue; else if (operator.equals ("/")) { if (nextValue == 0) { valueType = ValueType.ERROR; return; } value /= nextValue; } else if (operator.equals ("^")) value = Math.pow (value, nextValue); } if (Double.isNaN (value)) valueType = ValueType.ERROR; else valueType = ValueType.VALUE; } catch (Exception e) { valueType = ValueType.ERROR; e.printStackTrace (); return; } isVolatile = currentVolatile; if (debug) { System.out.println (this); System.out.printf ("(4) exp %s is currently %svolatile%n", text, isVolatile ? " " : "not "); System.out.println (); } } private String balanceBrackets (String input) { String line = input.trim (); int leftBracket = 0; int rightBracket = 0; for (char c : line.toCharArray ()) if (c == '(') leftBracket++; else if (c == ')') rightBracket++; if (leftBracket != rightBracket) { if (rightBracket > leftBracket) { System.out.printf ("**** Unbalanced brackets: left:%d, right:%d ****%n", leftBracket, rightBracket); System.out.println (input); return "@ERROR"; } while (rightBracket < leftBracket) { line = line + ")"; rightBracket++; } } return line; } // called for functions and expressions static String getBalancedText (String text) { int ptr = text.indexOf ('('); // find first left parenthesis if (ptr < 0) return text; int depth = 1; while (++ptr < text.length ()) // find matching right parenthesis { if (text.charAt (ptr) == ')') { --depth; if (depth == 0) break; } else if (text.charAt (ptr) == '(') ++depth; } return text.substring (0, ptr + 1); // include closing parenthesis } static String getFunctionName (String text, int offset) { int pos1 = text.indexOf ('(', offset); int pos2 = text.indexOf (',', offset); if (pos1 > offset && pos1 < pos2) return text.substring (offset, pos1); return text.substring (offset, pos2); } private String getNumberText (String text) { int ptr = 0; while (++ptr < text.length ()) { char c = text.charAt (ptr); if (c != '.' && (c < '0' || c > '9')) break; } return text.substring (0, ptr); } private String getAddressText (String text) { int ptr = 0; while (++ptr < text.length ()) { char c = text.charAt (ptr); if ((c < '0' || c > '9') && (c < 'A' || c > 'Z')) break; } return text.substring (0, ptr); } public String fullText () { StringBuilder text = new StringBuilder (); int ptr = 0; for (Value value : values) { assert value != null; text.append (signs.get (ptr)); text.append (value.getValue ()); if (ptr < operators.size ()) text.append (operators.get (ptr++)); } return text.toString (); } @Override public String toString () { return "Expression : " + text; } public static void main (String[] args) { Expression ex = new Expression (null, null, "-5+((-4-(20-(2^3))+6/3))*-2"); System.out.println (ex.getValue ()); System.out.println (ex); } @Override public Iterator iterator () { return values.iterator (); } }