package com.bytezone.diskbrowser.visicalc; import java.util.ArrayList; import java.util.List; class Expression implements Value { // 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 List values = new ArrayList (); private final List operators = new ArrayList (); private final List signs = new ArrayList (); private boolean hasValue; public Expression (Sheet parent, String input) { String line = checkBrackets (input); 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 = getFunctionText (line.substring (ptr)); ptr += functionText.length (); values.add (Function.getInstance (parent, functionText)); break; case '(': // parentheses block String bracketText = getFunctionText (line.substring (ptr)); ptr += bracketText.length (); values.add (new Expression (parent, bracketText.substring (1, bracketText.length () - 1))); break; case '#': System.out.printf ("Hash character [%s] in [%s]%n", ch, line); ptr++; // no idea 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; hasValue = true; } @Override public double getValue () { Value thisValue = values.get (0); double value = thisValue == null ? 0 : values.get (0).getValue (); String sign = signs.get (0); if (sign.equals ("(-)")) value *= -1; for (int i = 1; i < values.size (); i++) { thisValue = values.get (i); double nextValue = thisValue == null ? 0 : thisValue.getValue (); 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 ("/")) value /= nextValue; else if (operator.equals ("^")) value = Math.pow (value, nextValue); } return value; } @Override public boolean hasValue () { return hasValue; } @Override public String getError () { return hasValue ? "" : "Error"; } private String checkBrackets (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) { System.out.printf ("**** Unbalanced brackets: left:%d, right:%d ****%n", leftBracket, rightBracket); return "@ERROR()"; } return line; } private String getFunctionText (String text) { int ptr = text.indexOf ('('); // find first left parenthesis if (ptr < 0) return ""; 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 } 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); } @Override public String toString () { StringBuilder text = new StringBuilder (); int ptr = 0; for (Value value : values) { text.append (signs.get (ptr)); text.append (value.getValue ()); if (ptr < operators.size ()) text.append (operators.get (ptr++)); } return text.toString (); } public static void main (String[] args) { Expression ex = new Expression (null, "-5+((-4-(20-(2^3))+6/3))*-2"); System.out.println (ex.getValue ()); System.out.println (ex); } }