001 /*
002 * Copyright (c) 2005 Peter Palotas, Fredrik Johansson, Einar Pehrson,
003 * Sebastian Kekkonen, Lars Magnus Lång, Malin Johansson and Sofia Nilsson
004 *
005 * This file is part of
006 * CleanSheets Extension for Assertions
007 *
008 * CleanSheets Extension for Assertions is free software; you can
009 * redistribute it and/or modify it under the terms of the GNU General Public
010 * License as published by the Free Software Foundation; either version 2 of
011 * the License, or (at your option) any later version.
012 *
013 * CleanSheets Extension for Assertions is distributed in the hope that
014 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
015 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016 * See the GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with CleanSheets Extension for Assertions; if not, write to the
020 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
021 * Boston, MA 02111-1307 USA
022 */
023 package csheets.ext.assertion;
024
025 import java.util.ArrayList;
026 import java.util.List;
027 import java.util.Set;
028 import java.util.Stack;
029 import java.util.TreeSet;
030
031 import csheets.core.Cell;
032 import csheets.core.IllegalValueTypeException;
033 import csheets.core.formula.BinaryOperation;
034 import csheets.core.formula.Expression;
035 import csheets.core.formula.FunctionCall;
036 import csheets.core.formula.Literal;
037 import csheets.core.formula.Operator;
038 import csheets.core.formula.Reference;
039 import csheets.core.formula.UnaryOperation;
040 import csheets.core.formula.lang.Adder;
041 import csheets.core.formula.lang.Divider;
042 import csheets.core.formula.lang.Exponentiator;
043 import csheets.core.formula.lang.Multiplier;
044 import csheets.core.formula.lang.Negator;
045 import csheets.core.formula.lang.Subtracter;
046 import csheets.core.formula.util.ExpressionVisitor;
047
048 /** A Visitor for calculating System Generated assertions for a formula
049 in the form of an Expression tree. */
050 public class AssertionArithmeticVisitor implements ExpressionVisitor {
051
052 /** A stack used to calculate the final interval */
053 private Stack<List<MultiInterval>> intervalStack = new Stack<List<MultiInterval>>();
054
055 private Set<Cell> referencedCells = new TreeSet<Cell>();
056
057 /**
058 * Constructs a new AssertionArithmeticVisitor.
059 */
060 public AssertionArithmeticVisitor() {}
061
062 /** Retrieve the result of the arithmetic calculations performed by this visitor.
063 <p><b>NOTE!</b> This function should only be called after the visitor
064 has been used to traverse some Expression tree (by calling Expression.accept() passing
065 this visitor as an argument. Otherwise an exception will be thrown.
066 @param expression the expression from which the
067 @return The resulting interval from the performed calculations.
068 @throws AssertionArithmeticException if no result has been calculated yet, or if
069 the calculations resulted in more than one result. (Indicates an error in the formula). */
070 public MultiInterval getResult(Expression expression)
071 throws AssertionArithmeticException, MathException {
072 // Clears collections
073 intervalStack.clear();
074 referencedCells.clear();
075
076 // Builds intervals
077 expression.accept(this);
078
079 // intervalStack == null set temporary? by Fredrik
080 if (intervalStack == null || intervalStack.size() != 1) {
081 throw new AssertionArithmeticException("Result from assertion arithmetics was errenous. Multiple results found. Error in formula?");
082 }
083
084 List<MultiInterval> list = intervalStack.peek();
085
086 if (list.size() != 1) {
087 throw new AssertionArithmeticException("Result from assertion arithmetics was errenous. Single result with multiple intervals. Error in formula?");
088 }
089
090 return list.get(0);
091 }
092
093 public Object visitBinaryOperation(BinaryOperation operation)
094 throws AssertionArithmeticException, MathException {
095
096 Operator operator = operation.getOperator();
097
098 operation.getLeftOperand().accept(this);
099 List<MultiInterval> leftList = intervalStack.pop();
100
101 operation.getRightOperand().accept(this);
102 List<MultiInterval> rightList = intervalStack.pop();
103
104 if (leftList.size() != 1 || rightList.size() != 1)
105 throw new AssertionArithmeticException("No supported binary operator exist for ranges.");
106
107 MultiInterval left = leftList.get(0);
108 MultiInterval right = rightList.get(0);
109
110 List<MultiInterval> list = new ArrayList<MultiInterval>(1);
111 if (operator instanceof Multiplier) {
112 list.add(MultiInterval.mul(left, right));
113 intervalStack.push(list);
114 } else if (operator instanceof Adder) {
115 list.add(MultiInterval.add(left, right));
116 intervalStack.push(list);
117 } else if (operator instanceof Subtracter) {
118 list.add(MultiInterval.sub(left, right));
119 intervalStack.push(list);
120 } else if (operator instanceof Divider) {
121 list.add(MultiInterval.div(left, right));
122 intervalStack.push(list);
123 } else if (operator instanceof Exponentiator) {
124 list.add(MultiInterval.pow(left, right));
125 intervalStack.push(list);
126 } else {
127 throw new AssertionArithmeticException("Unsupported binary operator " + operator + " found.");
128 }
129 return operation;
130 }
131
132 public Object visitFunctionCall(FunctionCall call)
133 throws AssertionArithmeticException {
134
135 List<MultiInterval> paramList = new ArrayList<MultiInterval>();
136 for (Expression argument : call.getArguments()) {
137 argument.accept(this);
138 List<MultiInterval> list = intervalStack.pop();
139 paramList.addAll(list);
140 }
141
142 List<MultiInterval> list = new ArrayList<MultiInterval>(1);
143
144 String funcName = call.getFunction().getIdentifier().toUpperCase();
145
146 if (funcName.equals("RAND")) {
147 list.add(MultiInterval.rand());
148 } else if (funcName.equals("COS")) {
149 list.add(MultiInterval.cos(paramList.get(0)));
150 } else if (funcName.equals("SIN")) {
151 list.add(MultiInterval.sin(paramList.get(0)));
152 } else if (funcName.equals("TAN")) {
153 list.add(MultiInterval.tan(paramList.get(0)));
154 } else if (funcName.equals("ABS")) {
155 list.add(MultiInterval.abs(paramList.get(0)));
156 } else if (funcName.equals("INTEGER")) {
157 list.add(MultiInterval.toInt(paramList.get(0)));
158 } else if (funcName.equals("SQRT")) {
159 list.add(MultiInterval.sqrt(paramList.get(0)));
160 } else if (funcName.equals("EXP")) {
161 list.add(MultiInterval.exp(paramList.get(0)));
162 } else if (funcName.equals("LOG")) {
163 list.add(MultiInterval.log10(paramList.get(0)));
164 } else if (funcName.equals("LN")) {
165 list.add(MultiInterval.ln(paramList.get(0)));
166 } else if (funcName.equals("FACT")) {
167 list.add(MultiInterval.fact(paramList.get(0)));
168 } else if (funcName.equals("SUM")) {
169 list.add(MultiInterval.sum(paramList));
170 } else if (funcName.equals("AVG")) {
171 list.add(MultiInterval.avg(paramList));
172 } else {
173 throw new AssertionArithmeticException(
174 "Call to unsupported function " + call.getFunction() + " found.");
175 }
176
177 intervalStack.push(list);
178 return call;
179 }
180
181 public Object visitLiteral(Literal literal) throws AssertionArithmeticException {
182 try {
183 double value = literal.getValue().toDouble();
184 MultiInterval literalInterval = new MultiInterval();
185 literalInterval.include(new Interval(value));
186 List<MultiInterval> list = new ArrayList<MultiInterval>(1);
187 list.add(literalInterval);
188 intervalStack.push(list);
189 } catch (IllegalValueTypeException e) {
190 throw new AssertionArithmeticException("Non-numeric value found in formula.");
191 }
192 return literal;
193 }
194
195 public Object visitReference(Reference reference) throws AssertionArithmeticException {
196 List<MultiInterval> list = new ArrayList<MultiInterval>(1);
197
198 for (Cell cell : reference.getCells()) {
199
200 AssertableCell c = (AssertableCell)cell.getExtension(AssertionExtension.NAME);
201 checkReference(c);
202
203 if (!c.isAsserted())
204 throw new AssertionArithmeticException("Referenced cell "
205 + c + " does not have an assertion associated with it.");
206
207 Assertion ass = c.getSGAssertion();
208 if (ass == null)
209 ass = c.getUSAssertion();
210
211 list.add(ass.getMultiInterval());
212 }
213
214 intervalStack.push(list);
215 return reference;
216 }
217
218 public Object visitUnaryOperation(UnaryOperation operation)
219 throws AssertionArithmeticException {
220
221 operation.getOperand().accept(this);
222
223 List<MultiInterval> operandList = intervalStack.pop();
224
225 if (operandList.size() != 1)
226 throw new AssertionArithmeticException("No supported unary operator exist for ranges.");
227
228 MultiInterval operandInterval = operandList.get(0);
229 Operator operator = operation.getOperator();
230
231 if (operator instanceof Negator) {
232 List<MultiInterval> negList = new ArrayList<MultiInterval>(1);
233 negList.add(MultiInterval.negate(operandInterval));
234 intervalStack.push(negList);
235 } else {
236 throw new AssertionArithmeticException("Unsupported unary operator " + operator + " found.");
237 }
238 return operation;
239 }
240
241 /** Checks that multiple references to the same cell does not exist within formula.
242 Even checks indirect references.
243 @param cell the cell to check
244 @throws AssertionArithmeticException if a multiple reference to the same cell was found. */
245 private void checkReference(Cell cell) {
246 if (referencedCells.contains(cell))
247 throw new AssertionArithmeticException("Multiple references to the same cell found in formula. Cannot generate assertion.");
248 referencedCells.add(cell);
249 for (Cell c : cell.getPrecedents())
250 checkReference(c);
251 }
252 }