]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.spreadsheet/src/org/simantics/spreadsheet/solver/formula/CellValueVisitor.java
Adopt spreadsheet changes made in Balas development
[simantics/platform.git] / bundles / org.simantics.spreadsheet / src / org / simantics / spreadsheet / solver / formula / CellValueVisitor.java
diff --git a/bundles/org.simantics.spreadsheet/src/org/simantics/spreadsheet/solver/formula/CellValueVisitor.java b/bundles/org.simantics.spreadsheet/src/org/simantics/spreadsheet/solver/formula/CellValueVisitor.java
new file mode 100644 (file)
index 0000000..fd5ecd1
--- /dev/null
@@ -0,0 +1,481 @@
+package org.simantics.spreadsheet.solver.formula;
+
+import org.simantics.databoard.binding.mutable.Variant;
+import org.simantics.spreadsheet.Range;
+import org.simantics.spreadsheet.SpreadsheetMatrix;
+import org.simantics.spreadsheet.Spreadsheets;
+import org.simantics.spreadsheet.solver.SpreadsheetBook;
+import org.simantics.spreadsheet.solver.SpreadsheetCell;
+import org.simantics.spreadsheet.solver.SpreadsheetEngine;
+import org.simantics.spreadsheet.solver.SpreadsheetLine;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstApply;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstArgList;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstArithmeticExpression;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstArray;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstArrayFormulaReference;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstBoolean;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstDouble;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstFactor;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstIdentifier;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstInteger;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstNothing;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstNull;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstRange;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstRelation;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstString;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstTerm;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstValue;
+import org.simantics.spreadsheet.solver.formula.parser.ast.AstValueVisitor;
+
+import it.unimi.dsi.fastutil.longs.AbstractLongList;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+
+public class CellValueVisitor implements AstValueVisitor<Object> {
+
+    final public SpreadsheetBook book;
+    final private SpreadsheetEvaluationEnvironment env;
+    final private SpreadsheetCell thisCell;
+    final private LongArrayList references = new LongArrayList();
+
+    public CellValueVisitor(SpreadsheetEvaluationEnvironment env, SpreadsheetCell thisCell) {
+        this.book = env.getBook();
+        this.env = env;
+        this.thisCell = thisCell;
+    }
+
+    public void addReference(long ref) {
+        references.add(ref);
+    }
+
+    public AbstractLongList getReferences() {
+        return references;
+    }
+
+    @Override
+    public Object visit(AstBoolean astBoolean) {
+        return astBoolean.value;
+    }
+
+    @Override
+    public Object visit(AstDouble astFloat) {
+        return astFloat.value;
+    }
+
+    @Override
+    public Object visit(AstInteger astInteger) {
+        return astInteger.value;
+    }
+
+    @Override
+    public Object visit(AstNull astNull) {
+        throw new IllegalStateException();
+    }
+
+    @Override
+    public Object visit(AstString astString) {
+        return astString.value;
+    }
+
+    @Override
+    public Object visit(AstRange astRange) {
+
+        if(astRange.isRef()){
+            return FormulaError2.REF.getString();
+        }
+
+        if(astRange.isCell()) {
+            String ref = astRange.first;
+            Range r = Spreadsheets.decodeCell(ref, 0, 0);
+            String sheetName = astRange.sheetName != null ? astRange.sheetName : thisCell.getEngine().getName();
+            SpreadsheetCell cell = thisCell.getBook().get(sheetName, r.startRow, r.startColumn);
+            if(cell == null) {
+                SpreadsheetEngine eng = thisCell.getBook().getEngine(sheetName);
+                SpreadsheetLine line = eng.getLine(r.startRow);
+
+                if (line == null) {
+                    line = new SpreadsheetLine(eng.lines, r.startRow);
+                    eng.lines.lines.put(-r.startRow, line);
+                }
+                cell = SpreadsheetCell.empty(line, r.startColumn);
+            }
+            return cell.evaluate(env, this);
+
+        } else {
+
+            Object cached = thisCell.getEngine().getCachedRange(astRange);
+            if(cached != null) {
+
+                Range r_ = Spreadsheets.decodeRange(astRange.first + ":" + astRange.second);
+                String sheetName = astRange.sheetName != null ? astRange.sheetName : thisCell.getEngine().getName();
+                SpreadsheetEngine eng = thisCell.getBook().getEngine(sheetName);
+                Range r = eng.actualRange(r_);
+                for(int row=0; row < r.height();row++) {
+                    SpreadsheetLine line = eng.getLine(r.startRow + row);
+                    if(line != null) {
+                        for(int column=0; column < r.width();column++) {
+                            int col = r.startColumn + column;
+                            if(line.cells.size() > col) {
+                                SpreadsheetCell cell = line.cells.get(r.startColumn + column);
+                                //Add references, but do not evaluate if there exists a cached range.
+                                addReference(cell.makeReferenceKey());
+                            }
+                        }
+                    }
+                }      
+                return cached;
+            }
+
+            Range r_ = Spreadsheets.decodeRange(astRange.first + ":" + astRange.second);
+            String sheetName = astRange.sheetName != null ? astRange.sheetName : thisCell.getEngine().getName();
+            SpreadsheetEngine eng = thisCell.getBook().getEngine(sheetName);
+            // Full ranges are resolved here
+            Range r = eng.actualRange(r_);
+            SpreadsheetMatrix result = new SpreadsheetMatrix(r.width(), r.height());
+            for(int row=0; row < r.height();row++) {
+                SpreadsheetLine line = eng.getLine(r.startRow + row);
+                if(line != null) {
+                    for(int column=0; column < r.width();column++) {
+                        int col = r.startColumn + column;
+                        if(line.cells.size() > col) {
+                            SpreadsheetCell cell = line.cells.get(r.startColumn + column);
+                            result.values[r.width()*row + column] = cell.evaluate(env, this);
+                        }
+                    }
+                }
+            }
+            thisCell.getEngine().cacheRange(astRange, result);
+            return result;
+        }
+    }
+
+    @Override
+    public Object visit(AstArgList astArgList) {
+        throw new IllegalStateException();
+    }
+
+    @Override
+    public Object visit(AstApply astApply) {
+        CellFormulaFunction<?> fn = env.getFunction(astApply.value);
+        if(fn != null) {
+            return fn.evaluate(this, astApply.args);
+        } else {
+            return FormulaError2.NAME.getString();
+        }
+    }
+
+    @Override
+    public Object visit(AstRelation astRelation) {
+
+        Object leftResult = astRelation.left.accept(this);
+        Object rightResult = astRelation.right.accept(this);
+
+        FormulaError2 err = FormulaError2.forObject(leftResult);
+        if(err!=null) return err.getString();
+        FormulaError2 err2 = FormulaError2.forObject(rightResult);
+        if(err2!=null) return err2.getString();
+
+        if(leftResult instanceof Variant){
+            Object leftTemp = ((Variant)leftResult).getValue();
+            Double leftVal = Spreadsheets.asDoubleWhereEmptyStringIsZero(leftTemp);
+            if(leftVal==null) leftResult = leftTemp.toString();
+            else leftResult = leftVal;
+        }
+        if(rightResult instanceof Variant){
+            Object rightTemp = ((Variant)rightResult).getValue();
+            Double rightVal = Spreadsheets.asDoubleWhereEmptyStringIsZero(rightTemp);
+            if(rightVal==null) rightResult = rightTemp.toString();
+            else rightResult = rightVal;
+        }
+        if ((leftResult instanceof String) && (rightResult instanceof String)) {
+            String leftString = (leftResult.toString()).toLowerCase();
+            String rightString = (rightResult.toString()).toLowerCase();
+            if("<".equals(astRelation.op.trim())) return leftString.compareTo(rightString) < 0;
+            else if(">".equals(astRelation.op.trim())) return leftString.compareTo(rightString) > 0;
+            else if("=".equals(astRelation.op.trim())) return leftString.compareTo(rightString) == 0;
+            else if("<>".equals(astRelation.op.trim())) return leftString.compareTo(rightString) != 0 ;
+            else if("<=".equals(astRelation.op.trim())) return leftString.compareTo(rightString) <= 0 ;
+            else if(">=".equals(astRelation.op.trim())) return leftString.compareTo(rightString) >= 0 ;
+            else throw new IllegalStateException();
+        } else {
+            Number leftNumber = Spreadsheets.asDoubleWhereEmptyStringIsZero(leftResult);
+            Number rightNumber = Spreadsheets.asDoubleWhereEmptyStringIsZero(rightResult);
+            if(leftNumber==null || rightNumber==null) return false;
+            if("<".equals(astRelation.op.trim())) return leftNumber.doubleValue() < rightNumber.doubleValue();
+            else if(">".equals(astRelation.op.trim())) return leftNumber.doubleValue() > rightNumber.doubleValue();
+            else if("=".equals(astRelation.op.trim())) return leftNumber.doubleValue() == rightNumber.doubleValue();
+            else if("<>".equals(astRelation.op.trim())) return leftNumber.doubleValue() != rightNumber.doubleValue();
+            else if("<=".equals(astRelation.op.trim())) return leftNumber.doubleValue() <= rightNumber.doubleValue();
+            else if(">=".equals(astRelation.op.trim())) return leftNumber.doubleValue() >= rightNumber.doubleValue();
+            else throw new IllegalStateException();
+        }
+    }
+
+    Object leftValueWithPrefix(Object result, AstValue value, String prefix, boolean forceNumber) {
+        if(result == null) {
+            Object obj = value.accept(this);
+            FormulaError2 err = FormulaError2.forObject(obj);
+            if(err!=null) return err.getString();
+
+            if("-".equals(prefix)) {
+                result = Spreadsheets.asNumber(obj);
+                return -((Number)result).doubleValue();
+            } else {
+                if(forceNumber) return Spreadsheets.asNumber(obj);
+                else return obj;
+            }
+        }
+        try{
+            return (Number)Double.parseDouble(result.toString());
+        } catch (NumberFormatException e){
+            return result;
+        }
+    }
+
+    @Override
+    public Object visit(AstArithmeticExpression exp) {
+        Object result = null;
+        Object other = null;
+        AstValue value = null;
+        Object acceptedValue = null;
+
+        for(int i=0;i<exp.rightCount();i++) {
+            String op = exp.rightOp(i);
+            value = exp.rightValue(i);
+            acceptedValue = value.accept(this);
+            if("+".equals(op)) {
+                result = leftValueWithPrefix(result, exp.left, exp.prefix, false);
+
+                if(!(result instanceof Number)) {
+                    FormulaError2 err = FormulaError2.forObject(result);
+                    if(err!=null) return err.getString();
+
+                    if(result instanceof String && !((String) result).isEmpty()){
+                        Number num = Spreadsheets.asValidNumber(result);
+                        if(num == null) {
+                            return FormulaError2.VALUE.getString();
+                        } else {
+                            result = num;
+                        }
+                    } 
+                    else if(result instanceof Variant){
+                        Object val = ((Variant)result).getValue();
+                        if(val instanceof String && (val.toString().isEmpty())){
+                            result = 0.0;
+                        } else {
+                            Number resNum = Spreadsheets.asDoubleWhereEmptyStringIsZero(val);
+                            if(resNum==null) return FormulaError2.VALUE.getString();
+                            else result = resNum;
+                        }
+                    } else {
+                        result = 0.0;
+                    }
+                }
+
+                FormulaError2 err2 = FormulaError2.forObject(acceptedValue);
+                if(err2!=null) return err2.getString();
+
+                other = Spreadsheets.asDoubleWhereEmptyStringIsZero(acceptedValue);
+                if(other==null)
+                    return FormulaError2.handleErrorCall(acceptedValue);
+
+                result = ((Number)result).doubleValue() + ((Number)other).doubleValue();
+
+            } else if("-".equals(op)) {
+                result = leftValueWithPrefix(result, exp.left, exp.prefix, false);
+
+                if(!(result instanceof Number)) {
+                    FormulaError2 err = FormulaError2.forObject(result);
+                    if(err!=null) return err.getString();
+
+                    if(result instanceof String && !((String) result).isEmpty()){
+                        Number num = Spreadsheets.asValidNumber(result);
+                        if(num == null) {
+                            return FormulaError2.VALUE.getString();
+                        } else {
+                            result = num;
+                        }
+                    } 
+                    else if(result instanceof Variant){
+                        Object val = ((Variant)result).getValue();
+                        if(val instanceof String && (val.toString().isEmpty())){
+                            result = 0.0;
+                        } else {
+                            Number resNum = Spreadsheets.asDoubleWhereEmptyStringIsZero(val);
+                            if(resNum==null)
+                                return FormulaError2.VALUE.getString();
+                            else result = resNum;
+                        }
+                    } else {
+                        result = 0.0;
+                    }
+                }
+                FormulaError2 err2 = FormulaError2.forObject(acceptedValue);
+                if(err2!=null) return err2.getString();
+
+                other = Spreadsheets.asDoubleWhereEmptyStringIsZero(acceptedValue);
+                if(other==null)
+                    return FormulaError2.handleErrorCall(acceptedValue);
+
+                result = ((Number)result).doubleValue() - ((Number)other).doubleValue();
+
+            } else if("&".equals(op)) {
+                result = leftValueWithPrefix(result, exp.left, exp.prefix, false);
+                FormulaError2 err = FormulaError2.forObject(result);
+                if(err!=null) return err.getString();
+                FormulaError2 err2 = FormulaError2.forObject(acceptedValue);
+                if(err2!=null) return err2.getString();
+
+                result = Spreadsheets.asString(result);
+                other = Spreadsheets.asString(acceptedValue);
+
+                result += (String)other;
+            }
+        }
+        return leftValueWithPrefix(result, exp.left, exp.prefix, false);
+
+    }
+
+    @Override
+    public Object visit(AstTerm exp) {
+        Number result = null;
+        for(int i=0;i<exp.rightCount();i++) {
+            String op = exp.rightOp(i);
+            AstValue value = exp.rightValue(i);
+            Object leftValue = exp.left.accept(this);
+            Object rightValue = value.accept(this);
+            if("*".equals(op)) {
+                if(result == null) {
+                    result = Spreadsheets.asDoubleWhereEmptyStringIsZero(leftValue);
+                    if(result == null)
+                        return FormulaError2.handleErrorCall(leftValue);
+                }
+                Number other = Spreadsheets.asDoubleWhereEmptyStringIsZero(rightValue);
+                if(other==null) return FormulaError2.handleErrorCall(rightValue);
+
+                result = new Double(result.doubleValue() * other.doubleValue());
+
+            } else if("/".equals(op)) {
+                if(result == null) {
+                    result = Spreadsheets.asDoubleWhereEmptyStringIsZero(leftValue);
+                    if(result == null)
+                        return FormulaError2.handleErrorCall(leftValue);
+                }
+                Number other = Spreadsheets.asDoubleWhereEmptyStringIsZero(rightValue);
+                if(other==null) return FormulaError2.handleErrorCall(rightValue);
+                if(other.doubleValue()==0.0) return FormulaError2.DIV0.getString();
+
+                result = new Double(result.doubleValue() / other.doubleValue());       
+            }
+        }
+        if(result == null) result = Spreadsheets.asNumber(exp.left.accept(this));
+        return result;
+    }
+
+    @Override
+    public Object visit(AstFactor exp) {
+        Object result = null;
+        for(int i=0;i<exp.rightCount();i++) {
+            String op = exp.rightOp(i);
+            AstValue value = exp.rightValue(i);
+            if("^".equals(op)) {
+                if(result == null) {
+                    Object leftValue = exp.left.accept(this);
+
+                    FormulaError2 err = FormulaError2.forObject(leftValue);
+                    if(err!=null) return err.getString();
+
+                    if(leftValue instanceof Variant){
+                        Object leftTemp = ((Variant)leftValue).getValue();
+                        Double leftV = Spreadsheets.asDoubleWhereEmptyStringIsZero(leftTemp);
+                        if(leftV==null) leftValue = leftTemp.toString();
+                        else leftValue = leftV;
+                    }
+
+                    if(leftValue instanceof String){
+                        if((leftValue.toString()).isEmpty())
+                            result = 0;
+                        else 
+                            return FormulaError2.VALUE.getString();
+                    }
+                    else if(leftValue instanceof SpreadsheetMatrix) 
+                        result = leftValue;
+                    else 
+                        result = Spreadsheets.asNumber(leftValue);
+                }
+                Object otherValue = value.accept(this);
+
+                FormulaError2 err2 = FormulaError2.forObject(otherValue);
+                if(err2!=null) return err2.getString();
+
+                if(otherValue instanceof Variant){
+                    Object otherTemp = ((Variant)otherValue).getValue();
+                    Double otherV = Spreadsheets.asDoubleWhereEmptyStringIsZero(otherTemp);
+                    if(otherV==null) otherValue = otherTemp.toString();
+                    else otherValue = otherV;
+                }
+
+                if(otherValue instanceof String){
+                    if((otherValue.toString()).isEmpty())
+                        otherValue = 0;
+                    else 
+                        return FormulaError2.VALUE.getString();
+                }
+
+                if(result instanceof SpreadsheetMatrix) {
+                    result = ((SpreadsheetMatrix)result).pow(otherValue);
+                } else {
+                    if(otherValue instanceof SpreadsheetMatrix) {
+                        throw new IllegalStateException();
+                    } else {
+                        Double base = ((Number)result).doubleValue();
+                        Double exponent = Spreadsheets.asNumber(otherValue);
+                        if(exponent==0 && base==0)
+                            return FormulaError2.NUM.getString();
+                        if(exponent<0 && base==0)
+                            return FormulaError2.DIV0.getString();
+                        result = Math.pow(base, exponent);
+                        if(result instanceof Double && Double.isInfinite((Double)result)){
+                            return FormulaError2.NUM.getString();
+                        }
+                    }
+                }
+            }
+        }
+        if(result == null) result = Spreadsheets.asNumber(exp.left.accept(this));
+        return result;
+    }
+
+    @Override
+    public Object visit(AstIdentifier id) {
+        return FormulaError2.NAME.getString();
+        //throw new IllegalStateException();
+    }
+
+    @Override
+    public Object visit(AstArray array) {
+        SpreadsheetMatrix m = new SpreadsheetMatrix(array.values.size(), 1);
+        for(int i=0;i<array.values.size();i++) {
+            m.values[i] = array.values.get(i).accept(this);
+        }
+        return m; 
+    }
+
+    @Override
+    public Object visit(AstNothing array) {
+        return AstNothing.NOTHING;
+    }
+
+    @Override
+    public Object visit(AstArrayFormulaReference ref) {
+
+        Range thisRange = Spreadsheets.decodeRange(thisCell.getName());
+        Range arrayRange = Spreadsheets.decodeRange(ref.range);
+        int x = thisRange.startColumn - arrayRange.startColumn;
+        int y = thisRange.startRow - arrayRange.startRow;
+
+        SpreadsheetMatrix m = (SpreadsheetMatrix)ref.value.accept(this);
+        return m.get(y, x);
+
+    }
+
+}