visicalc bloody hell

This commit is contained in:
Denis Molony 2016-03-09 21:38:53 +11:00
parent 2417e0aa07
commit 239820c41e
14 changed files with 381 additions and 261 deletions

View File

@ -73,7 +73,7 @@ class Address implements Comparable<Address>
@Override
public String toString ()
{
return String.format ("%-4s %3d %3d", text, row, column);
return String.format ("%-4s %3d %3d %4d", text, row, column, sortValue);
}
@Override

View File

@ -1,24 +1,24 @@
package com.bytezone.diskbrowser.visicalc;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Cell implements Comparable<Cell>
class Cell implements Comparable<Cell>, Value
{
private static final Pattern cellContents =
Pattern.compile ("([-+/*]?)(([A-Z]{1,2}[0-9]{1,3})|([0-9.]+)|(@[^-+/*]+))");
// private static final Pattern cellContents =
// Pattern.compile ("([-+/*]?)(([A-Z]{1,2}[0-9]{1,3})|([0-9.]+)|(@[^-+/*]+))");
final Address address;
private final Sheet parent;
private String label;
private double value;
private String formulaText;
// private double value;
// private String formulaText;
private char format = ' ';
private char repeatingChar;
private String repeat = "";
private boolean valid;
// private boolean valid;
private String expressionText;
private Expression expression;
public Cell (Sheet parent, Address address)
{
@ -56,16 +56,23 @@ class Cell implements Comparable<Cell>
break;
default:
if (command.matches ("^[0-9.]+$")) // contains only numbers or .
this.value = Float.parseFloat (command);
else
formulaText = command;
expressionText = command;
}
// FUTURE.VC
if (address.sortValue == 67)
expressionText = "50";
if (address.sortValue == 131)
expressionText = ".04";
if (address.sortValue == 195)
expressionText = "12";
if (address.sortValue == 259)
expressionText = "5";
}
boolean hasValue ()
{
return label == null && repeatingChar == 0;
return expressionText != null;
}
char getFormat ()
@ -73,21 +80,34 @@ class Cell implements Comparable<Cell>
return format;
}
double getValue ()
String getText ()
{
if (valid || formulaText == null)
return value;
if (label != null)
return label;
if (repeatingChar > 0)
return repeat;
return "bollocks";
}
double result = 0.0;
double interim = 0.0;
if (formulaText.startsWith ("@LOOKUP("))
{
Lookup lookup = new Lookup (parent, formulaText);
return lookup.getValue ();
}
System.out.printf ("Matching:[%s]%n", formulaText);
@Override
public double getValue ()
{
if (expression == null)
expression = new Expression (parent, expressionText);
return expression.getValue ();
// if (valid || formulaText == null)
// return value;
//
// double result = 0.0;
// double interim = 0.0;
//
// if (formulaText.startsWith ("@LOOKUP("))
// {
// Lookup lookup = new Lookup (parent, formulaText);
// return lookup.getValue ();
// }
//
// System.out.printf ("Matching:[%s]%n", formulaText);
// [@IF(@ISERROR(BK24),0,BK24)]
// [@IF(D4=0,0,1)]
// [@IF(D4=0,0,B32+1)]
@ -98,79 +118,72 @@ class Cell implements Comparable<Cell>
// [.3*(B4+B7+B8+B9)]
// [+N12+(P12*(.2*K12+K9-O12))]
Matcher m = cellContents.matcher (formulaText);
while (m.find ())
{
valid = true;
char operator = m.group (1).isEmpty () ? '+' : m.group (1).charAt (0);
if (m.group (3) != null) // address
{
Address address = new Address (m.group (3));
Cell cell = parent.getCell (address);
if (cell != null)
interim = cell.getValue ();
}
else if (m.group (4) != null) // constant
try
{
interim = Double.parseDouble (m.group (4));
}
catch (NumberFormatException e)
{
System.out.printf ("NFE: %s [%s]%n", m.group (4), formulaText);
}
else
{
// interim = parent.evaluateFunction (m.group (5)); // function
Function function = Function.getInstance (parent, m.group (5));
if (function != null)
interim = function.getValue ();
}
if (operator == '+')
result += interim;
else if (operator == '-')
result -= interim;
else if (operator == '*')
result *= interim;
else if (operator == '/')
result = interim == 0.0 ? 0 : result / interim;
}
if (valid)
{
value = result;
return result;
}
System.out.println ("?? " + formulaText);
return value;
}
String value ()
{
if (label != null)
return label;
if (repeatingChar > 0)
return repeat;
if (formulaText != null)
if (formulaText.length () >= 12)
return formulaText.substring (0, 12);
else
return formulaText;
return value + "";
// Matcher m = cellContents.matcher (formulaText);
// while (m.find ())
// {
// valid = true;
// char operator = m.group (1).isEmpty () ? '+' : m.group (1).charAt (0);
//
// if (m.group (3) != null) // address
// {
// Address address = new Address (m.group (3));
// Cell cell = parent.getCell (address);
// if (cell != null)
// interim = cell.getValue ();
// }
// else if (m.group (4) != null) // constant
// try
// {
// interim = Double.parseDouble (m.group (4));
// }
// catch (NumberFormatException e)
// {
// System.out.printf ("NFE: %s [%s]%n", m.group (4), formulaText);
// }
// else
// {
// // interim = parent.evaluateFunction (m.group (5)); // function
// Function function = Function.getInstance (parent, m.group (5));
// if (function != null)
// interim = function.getValue ();
// }
//
// if (operator == '+')
// result += interim;
// else if (operator == '-')
// result -= interim;
// else if (operator == '*')
// result *= interim;
// else if (operator == '/')
// result = interim == 0.0 ? 0 : result / interim;
// }
//
// if (valid)
// {
// value = result;
// return result;
// }
//
// System.out.println ("?? " + formulaText);
//
// return value;
}
@Override
public String toString ()
{
String value = repeatingChar == 0
? label == null ? formulaText == null ? ", Value : " + this.value
: ", Formula: " + formulaText : ", Label : " + label
: ", Repeat : " + repeatingChar;
return String.format ("[Cell:%5s %-2s%s]", address, format, value);
// String value = repeatingChar == 0
// ? label == null ? formulaText == null ? ", Value : " + this.value
// : ", Formula: " + formulaText : ", Label : " + label
// : ", Repeat : " + repeatingChar;
String contents = "";
if (label != null)
contents = "Labl: " + label;
else if (repeatingChar != 0)
contents = "Rept: " + repeatingChar;
else if (expressionText != null)
contents = "Exp : " + expressionText;
return String.format ("[Cell:%5s %s]", address, contents);
}
@Override

View File

@ -3,11 +3,10 @@ package com.bytezone.diskbrowser.visicalc;
public class Count extends Function
{
Range range;
Sheet parent;
public Count (Sheet parent, String text)
{
this.parent = parent;
super (parent, text);
range = getRange (text);
}

View File

@ -0,0 +1,15 @@
package com.bytezone.diskbrowser.visicalc;
public class Error extends Function
{
public Error (Sheet parent, String text)
{
super (parent, text);
}
@Override
public double getValue ()
{
return 0;
}
}

View File

@ -1,8 +1,10 @@
package com.bytezone.diskbrowser.visicalc;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class Expression
public class Expression implements Value
{
// Expressions:
// - number
@ -12,152 +14,116 @@ public class 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 #.
private static final Pattern pattern = Pattern.compile ("");
private boolean isUnavailable;
private boolean isError;
private boolean hasValue;
private double value;
private Function function;
private Address address;
private Expression expression1;
private char operator;
private Expression expression2;
private final Sheet parent;
private final List<Value> values = new ArrayList<Value> ();
private final List<String> operators = new ArrayList<String> ();
public Expression (Sheet parent, String input)
{
this.parent = parent;
String text = input.trim ();
String line = input.trim ();
System.out.printf ("New expression:[%s]%n", input);
char firstChar = text.charAt (0);
if (firstChar == '-')
// System.out.printf ("New expression [%s]%n", input);
if (line.startsWith ("-"))
line = "0" + line;
else if (line.startsWith ("+"))
line = line.substring (1);
int ptr = 0;
while (ptr < line.length ())
{
char secondChar = text.charAt (1);
if ((secondChar >= '0' && secondChar <= '9') || secondChar == '.')
char ch = line.charAt (ptr);
switch (ch)
{
String text2 = text.substring (1);
String numberText = getNumberText (text2);
char op = getOperator (numberText, text2);
if (op == ' ')
{
value = Double.parseDouble (numberText) * -1;
hasValue = true;
}
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 ();
while (bracketText.startsWith ("(") && bracketText.endsWith (")"))
bracketText = bracketText.substring (1, bracketText.length () - 1);
values.add (new Expression (parent, bracketText));
break;
default:
if (ch == '.' || (ch >= '0' && ch <= '9')) // number
{
String numberText = getNumberText (line.substring (ptr));
ptr += numberText.length ();
values.add (new Number (Double.parseDouble (numberText)));
}
else if (ch >= 'A' && ch <= 'Z') // cell address
{
String addressText = getAddressText (line.substring (ptr));
ptr += addressText.length ();
values.add (parent.getCell (new Address (addressText)));
}
else
{
System.out.printf ("Unknown character [%s] in [%s]%n", ch, line);
return;
}
}
if (ptr < line.length ())
{
ch = line.charAt (ptr);
if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '^')
operators.add (line.substring (ptr, ++ptr));
else
expression1 = new Expression (parent, "-" + numberText);
}
else
{
operator = '-';
expression1 = new Expression (parent, text.substring (1));
{
System.out.printf ("Unknown operator [%s] in [%s]%n", ch, line);
return;
}
}
}
else if (firstChar == '=' || firstChar == '+')
{
expression1 = new Expression (parent, text.substring (1));
}
else if (firstChar == '@')
{
String functionText = getFunctionText (text);
char op = getOperator (functionText, text);
if (op == ' ')
function = Function.getInstance (parent, functionText);
}
else if (firstChar == '(')
{
String bracketText = getFunctionText (text);
char op = getOperator (bracketText, text);
if (op == ' ')
expression1 =
new Expression (parent, bracketText.substring (1, bracketText.length () - 2));
}
else if ((firstChar >= '0' && firstChar <= '9') || firstChar == '.')
{
String numberText = getNumberText (text);
char op = getOperator (numberText, text);
if (op == ' ')
{
value = Double.parseDouble (numberText);
hasValue = true;
}
}
else if (firstChar >= 'A' && firstChar <= 'Z')
{
String addressText = getAddressText (text);
char op = getOperator (addressText, text);
if (op == ' ')
address = new Address (addressText);
}
else
System.out.printf ("Error processing [%s]%n", text);
assert values.size () > 0;
// ptr = 0;
// for (Value val : values)
// {
// System.out.println (val.getValue ());
// if (ptr < operators.size ())
// System.out.println (operators.get (ptr++));
// }
}
private char getOperator (String text1, String text2)
@Override
public double getValue ()
{
if (text1.length () == text2.length ())
return ' ';
char op = text2.charAt (text1.length ());
if (op == '+' || op == '-' || op == '*' || op == '/' || op == '^')
double value = values.get (0).getValue ();
for (int i = 1; i < values.size (); i++)
{
expression1 = new Expression (parent, text1);
operator = op;
expression2 = new Expression (parent, text2.substring (text1.length () + 1));
return op;
double nextValue = values.get (i).getValue ();
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);
}
System.out.println ("error");
// error
return '!';
}
double getValue ()
{
if (hasValue)
return value;
if (function != null)
return function.getValue ();
if (address != null)
{
Cell cell = parent.getCell (address);
if (cell != null)
return parent.getCell (address).getValue ();
System.out.println ("Error with address");
return 0;
}
if (expression2 == null)
{
if (operator == '-')
return expression1.getValue () * -1;
return expression1.getValue ();
}
switch (operator)
{
case ' ':
return expression1.getValue ();
case '+':
return expression1.getValue () + expression2.getValue ();
case '-':
return expression1.getValue () - expression2.getValue ();
case '*':
return expression1.getValue () * expression2.getValue ();
case '/':
return expression1.getValue () / expression2.getValue ();
case '^':
return Math.pow (expression1.getValue (), expression2.getValue ());
}
System.out.println ("Unresolved value");
return 0;
return value;
}
private String getFunctionText (String text)
@ -175,7 +141,7 @@ public class Expression
else if (text.charAt (ptr) == '(')
++depth;
}
return text.substring (0, ptr);
return text.substring (0, ptr + 1); // include closing parenthesis
}
private String getNumberText (String text)
@ -207,20 +173,27 @@ public class Expression
{
StringBuilder text = new StringBuilder ();
text.append (String.format ("Has value ......... %s%n", hasValue));
text.append (String.format ("Value ............. %f%n", value));
text.append (String.format ("Function .......... %s%n", function));
text.append (String.format ("Address ........... %s%n", address));
text.append (String.format ("Operator .......... %s%n", operator));
text.append (String.format ("Expression1 ....... %s%n", expression1));
text.append (String.format ("Expression2 ....... %s%n", expression2));
// text.append (String.format ("Has value ......... %s%n", hasValue));
// text.append (String.format ("Value ............. %f%n", value));
// text.append (String.format ("Function .......... %s%n", function));
// text.append (String.format ("Address ........... %s%n", address));
// text.append (String.format ("Operator .......... %s%n", operator));
// text.append (String.format ("Expression1 ....... %s%n", expression1));
// text.append (String.format ("Expression2 ....... %s%n", expression2));
int ptr = 0;
for (Value value : values)
{
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+12-6");
Expression ex = new Expression (null, "5+((4-(10-2)+6/3))*2");
System.out.println (ex.getValue ());
}
}

View File

@ -30,12 +30,15 @@ import java.util.regex.Pattern;
// @OR
// @AND
public abstract class Function
public abstract class Function implements Value
{
private static final Pattern functionPattern = Pattern
.compile ("\\(([A-B]?[A-Z])([0-9]{1,3})\\.\\.\\.([A-B]?[A-Z])([0-9]{1,3})\\)?");
private static final Pattern addressList = Pattern.compile ("\\(([^,]+(,[^,]+)*)\\)");
Sheet parent;
String text;
static Function getInstance (Sheet parent, String text)
{
if (text.startsWith ("@LOOKUP("))
@ -53,11 +56,18 @@ public abstract class Function
if (text.startsWith ("@SUM("))
return new Sum (parent, text);
if (text.startsWith ("@IF("))
return new If (parent, text);
System.out.printf ("Unknown function: %s%n", text);
return null;
return new Error (parent, "@ERROR()");
}
abstract double getValue ();
public Function (Sheet parent, String text)
{
this.parent = parent;
this.text = text;
}
Range getRange (String text)
{
@ -99,4 +109,10 @@ public abstract class Function
return range;
}
@Override
public String toString ()
{
return String.format ("Function: %s", text);
}
}

View File

@ -0,0 +1,46 @@
package com.bytezone.diskbrowser.visicalc;
public class If extends Function
{
private static String[] comparators = { "<>", "<=", ">=", "=", "<", ">" };
private final String condition;
private final String textTrue;
private final String textFalse;
private String comparator;
private String cond;
private String value;
public If (Sheet parent, String text)
{
super (parent, text);
text = text.substring (4, text.length () - 1);
System.out.println (text);
int pos1 = text.indexOf (',');
int pos2 = text.indexOf (',', pos1 + 1);
condition = text.substring (0, pos1);
textTrue = text.substring (pos1 + 1, pos2);
textFalse = text.substring (pos2 + 1);
System.out.printf ("Cond:%s, true=%s, false=%s%n", condition, textTrue, textFalse);
for (String comp : comparators)
{
int pos = condition.indexOf (comp);
if (pos > 0)
{
cond = condition.substring (0, pos);
value = condition.substring (pos + comp.length ());
comparator = comp;
break;
}
}
System.out.printf ("cond=%s, op=%s, value=%s%n", cond, comparator, value);
}
@Override
public double getValue ()
{
return 0;
}
}

View File

@ -3,19 +3,21 @@ package com.bytezone.diskbrowser.visicalc;
public class Lookup extends Function
{
Range range;
Cell source;
Sheet parent;
boolean hasValue;
String sourceText;
String rangeText;
Expression source;
public Lookup (Sheet parent, String text)
{
this.parent = parent;
super (parent, text);
int pos = text.indexOf (',');
sourceText = text.substring (8, pos);
rangeText = text.substring (pos + 1, text.length () - 1);
source = new Expression (parent, sourceText);
range = getRange (rangeText);
}
// need a mechanism to return NA and ERROR
@ -27,16 +29,7 @@ public class Lookup extends Function
@Override
public double getValue ()
{
// source could be a formula - @LOOKUP(.2*K8+K7,H3...H16)
source = parent.getCell (new Address (sourceText));
if (source == null)
{
System.out.println ("Null source:" + sourceText);
return 0;
}
double sourceValue = source.getValue ();
range = getRange (rangeText);
Address target = null;
for (Address address : range)

View File

@ -3,11 +3,11 @@ package com.bytezone.diskbrowser.visicalc;
public class Max extends Function
{
Range range;
Sheet parent;
public Max (Sheet parent, String text)
{
this.parent = parent;
super (parent, text);
range = getRange (text);
}

View File

@ -3,11 +3,11 @@ package com.bytezone.diskbrowser.visicalc;
public class Min extends Function
{
Range range;
Sheet parent;
public Min (Sheet parent, String text)
{
this.parent = parent;
super (parent, text);
range = getRange (text);
}

View File

@ -0,0 +1,23 @@
package com.bytezone.diskbrowser.visicalc;
public class Number implements Value
{
private final double value;
public Number (double value)
{
this.value = value;
}
@Override
public double getValue ()
{
return value;
}
@Override
public String toString ()
{
return String.format ("Number: %f", value);
}
}

View File

@ -30,6 +30,8 @@ public class Sheet implements Iterable<Cell>
private int columnWidth = 12;
private char recalculation = ' ';
private char recalculationOrder = ' ';
private int columns;
private int rows;
// Maximum cell = BK254
@ -249,7 +251,13 @@ public class Sheet implements Iterable<Cell>
{
currentCell = new Cell (this, address);
if (!line.startsWith ("/G"))
{
sheet.put (currentCell.address.sortValue, currentCell);
if (address.row > rows)
rows = address.row;
if (address.column > columns)
columns = address.column;
}
}
}
else
@ -306,7 +314,13 @@ public class Sheet implements Iterable<Cell>
Cell getCell (Address address)
{
return sheet.get (address.sortValue);
Cell cell = sheet.get (address.sortValue);
if (cell == null)
{
cell = new Cell (this, address);
sheet.put (address.sortValue, cell);
}
return cell;
}
public int size ()
@ -333,16 +347,38 @@ public class Sheet implements Iterable<Cell>
DecimalFormat nf = new DecimalFormat ("$#####0.00");
// NumberFormat nf = NumberFormat.getCurrencyInstance ();
int lastRow = 0;
int lastRow = -1;
int lastColumn = 0;
StringBuilder heading = new StringBuilder (" ");
for (int cellNo = 0; cellNo <= columns; cellNo++)
{
int width = columnWidth;
if (columnWidths.containsKey (cellNo))
width = columnWidths.get (cellNo);
if (width == 1)
{
heading.append ("=");
}
else
{
char letter1 = cellNo < 26 ? ' ' : cellNo < 676 ? 'A' : 'B';
char letter2 = (char) ((cellNo % 26) + 'A');
String fmt =
String.format ("%s%s%%%d.%ds", letter1, letter2, (width - 2), (width - 2));
heading.append (String.format (fmt, "--------------------------------------"));
}
}
text.append (heading);
for (Cell cell : sheet.values ())
{
while (lastRow < cell.address.row)
{
text.append ("\n");
++lastRow;
lastColumn = 0;
text.append (String.format ("%n%03d:", lastRow + 1));
}
while (lastColumn < cell.address.column)
@ -363,6 +399,7 @@ public class Sheet implements Iterable<Cell>
if (format == ' ')
format = defaultFormat;
// System.out.println (cell.address);
if (cell.hasValue ())
{
if (format == 'I')
@ -403,13 +440,13 @@ public class Sheet implements Iterable<Cell>
{
String labelFormat = String.format ("%%%d.%ds", colWidth, colWidth);
// System.out.printf ("Label format:%s%n", labelFormat);
text.append (String.format (labelFormat, cell.value ()));
text.append (String.format (labelFormat, cell.getText ()));
}
else
{
String labelFormat = String.format ("%%-%d.%ds", colWidth, colWidth);
// System.out.printf ("Label format:%s%n", labelFormat);
text.append (String.format (labelFormat, cell.value ()));
text.append (String.format (labelFormat, cell.getText ()));
}
}
}

View File

@ -3,11 +3,10 @@ package com.bytezone.diskbrowser.visicalc;
public class Sum extends Function
{
Range range;
Sheet parent;
public Sum (Sheet parent, String text)
{
this.parent = parent;
super (parent, text);
range = getRange (text);
}

View File

@ -0,0 +1,6 @@
package com.bytezone.diskbrowser.visicalc;
public interface Value
{
public double getValue ();
}