blob: 3023b5c6f820a0810e12e29672d40c81bdfa6aca [file] [log] [blame]
Hans Boehm84614952014-11-25 18:46:17 -08001/*
Hans Boehme4579122015-05-26 17:52:32 -07002 * Copyright (C) 2015 The Android Open Source Project
Hans Boehm84614952014-11-25 18:46:17 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calculator2;
18
Hans Boehm84614952014-11-25 18:46:17 -080019
20import com.hp.creals.CR;
21import com.hp.creals.UnaryCRFunction;
Hans Boehm84614952014-11-25 18:46:17 -080022
23import android.content.Context;
Hans Boehmc023b732015-04-29 11:30:47 -070024import android.util.Log;
Hans Boehm84614952014-11-25 18:46:17 -080025
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070026import java.math.BigInteger;
27import java.io.DataInput;
28import java.io.DataOutput;
29import java.io.IOException;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070030import java.util.ArrayList;
31import java.util.HashMap;
32import java.util.IdentityHashMap;
33
Hans Boehm84614952014-11-25 18:46:17 -080034// A mathematical expression represented as a sequence of "tokens".
35// Many tokes are represented by button ids for the corresponding operator.
36// Parsed only when we evaluate the expression using the "eval" method.
37class CalculatorExpr {
38 private ArrayList<Token> mExpr; // The actual representation
39 // as a list of tokens. Constant
40 // tokens are always nonempty.
41
42 private static enum TokenKind { CONSTANT, OPERATOR, PRE_EVAL };
43 private static TokenKind[] tokenKindValues = TokenKind.values();
44 private final static BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
45 private final static BigInteger BIG_BILLION = BigInteger.valueOf(1000000000);
46
47 private static abstract class Token {
48 abstract TokenKind kind();
49 abstract void write(DataOutput out) throws IOException;
50 // Implementation writes kind as Byte followed by
51 // data read by constructor.
52 abstract String toString(Context context);
53 // We need the context to convert button ids to strings.
54 }
55
56 // An operator token
57 private static class Operator extends Token {
58 final int mId; // We use the button resource id
59 Operator(int resId) {
60 mId = resId;
61 }
62 Operator(DataInput in) throws IOException {
63 mId = in.readInt();
64 }
65 @Override
66 void write(DataOutput out) throws IOException {
67 out.writeByte(TokenKind.OPERATOR.ordinal());
68 out.writeInt(mId);
69 }
70 @Override
71 public String toString(Context context) {
Justin Klaassene2711cb2015-05-28 11:13:17 -070072 return KeyMaps.toString(context, mId);
Hans Boehm84614952014-11-25 18:46:17 -080073 }
74 @Override
75 TokenKind kind() { return TokenKind.OPERATOR; }
76 }
77
Hans Boehm682ff5e2015-03-09 14:40:25 -070078 // A (possibly incomplete) numerical constant.
79 // Supports addition and removal of trailing characters; hence mutable.
Hans Boehm84614952014-11-25 18:46:17 -080080 private static class Constant extends Token implements Cloneable {
81 private boolean mSawDecimal;
Hans Boehm0b9806f2015-06-29 16:07:15 -070082 String mWhole; // String preceding decimal point.
83 private String mFraction; // String after decimal point.
84 private int mExponent; // Explicit exponent, only generated through addExponent.
Hans Boehm84614952014-11-25 18:46:17 -080085
86 Constant() {
87 mWhole = "";
88 mFraction = "";
Hans Boehm0b9806f2015-06-29 16:07:15 -070089 mSawDecimal = false;
90 mExponent = 0;
Hans Boehm84614952014-11-25 18:46:17 -080091 };
92
93 Constant(DataInput in) throws IOException {
94 mWhole = in.readUTF();
95 mSawDecimal = in.readBoolean();
96 mFraction = in.readUTF();
Hans Boehm0b9806f2015-06-29 16:07:15 -070097 mExponent = in.readInt();
Hans Boehm84614952014-11-25 18:46:17 -080098 }
99
100 @Override
101 void write(DataOutput out) throws IOException {
102 out.writeByte(TokenKind.CONSTANT.ordinal());
103 out.writeUTF(mWhole);
104 out.writeBoolean(mSawDecimal);
105 out.writeUTF(mFraction);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700106 out.writeInt(mExponent);
Hans Boehm84614952014-11-25 18:46:17 -0800107 }
108
109 // Given a button press, append corresponding digit.
110 // We assume id is a digit or decimal point.
111 // Just return false if this was the second (or later) decimal point
112 // in this constant.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700113 // Assumes that this constant does not have an exponent.
Hans Boehm84614952014-11-25 18:46:17 -0800114 boolean add(int id) {
115 if (id == R.id.dec_point) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700116 if (mSawDecimal || mExponent != 0) return false;
Hans Boehm84614952014-11-25 18:46:17 -0800117 mSawDecimal = true;
118 return true;
119 }
120 int val = KeyMaps.digVal(id);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700121 if (mExponent != 0) {
122 if (Math.abs(mExponent) <= 10000) {
123 if (mExponent > 0) {
124 mExponent = 10 * mExponent + val;
125 } else {
126 mExponent = 10 * mExponent - val;
127 }
128 return true;
129 } else { // Too large; refuse
130 return false;
131 }
132 }
Hans Boehm84614952014-11-25 18:46:17 -0800133 if (mSawDecimal) {
134 mFraction += val;
135 } else {
136 mWhole += val;
137 }
138 return true;
139 }
140
Hans Boehm0b9806f2015-06-29 16:07:15 -0700141 void addExponent(int exp) {
142 // Note that adding a 0 exponent is a no-op. That's OK.
143 mExponent = exp;
144 }
145
Hans Boehm84614952014-11-25 18:46:17 -0800146 // Undo the last add.
147 // Assumes the constant is nonempty.
148 void delete() {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700149 if (mExponent != 0) {
150 mExponent /= 10;
151 // Once zero, it can only be added back with addExponent.
152 } else if (!mFraction.isEmpty()) {
Hans Boehm84614952014-11-25 18:46:17 -0800153 mFraction = mFraction.substring(0, mFraction.length() - 1);
154 } else if (mSawDecimal) {
155 mSawDecimal = false;
156 } else {
157 mWhole = mWhole.substring(0, mWhole.length() - 1);
158 }
159 }
160
161 boolean isEmpty() {
162 return (mSawDecimal == false && mWhole.isEmpty());
163 }
164
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700165 // Produces human-readable string, as typed.
Hans Boehm013969e2015-04-13 20:29:47 -0700166 // Result is internationalized.
Hans Boehm84614952014-11-25 18:46:17 -0800167 @Override
168 public String toString() {
169 String result = mWhole;
170 if (mSawDecimal) {
Hans Boehm013969e2015-04-13 20:29:47 -0700171 result += '.';
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700172 result += mFraction;
173 }
Hans Boehm0b9806f2015-06-29 16:07:15 -0700174 if (mExponent != 0) {
175 result += "E" + mExponent;
176 }
Hans Boehm013969e2015-04-13 20:29:47 -0700177 return KeyMaps.translateResult(result);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700178 }
179
Hans Boehm0b9806f2015-06-29 16:07:15 -0700180 // Return non-null BoundedRational representation.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700181 public BoundedRational toRational() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700182 String whole = mWhole;
183 if (whole.isEmpty()) whole = "0";
184 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700185 BigInteger den = BigInteger.TEN.pow(mFraction.length());
Hans Boehm0b9806f2015-06-29 16:07:15 -0700186 if (mExponent > 0) {
187 num = num.multiply(BigInteger.TEN.pow(mExponent));
188 }
189 if (mExponent < 0) {
190 den = den.multiply(BigInteger.TEN.pow(-mExponent));
191 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700192 return new BoundedRational(num, den);
193 }
194
Hans Boehm84614952014-11-25 18:46:17 -0800195 @Override
196 String toString(Context context) {
197 return toString();
198 }
199
200 @Override
201 TokenKind kind() { return TokenKind.CONSTANT; }
202
203 // Override clone to make it public
204 @Override
205 public Object clone() {
206 Constant res = new Constant();
207 res.mWhole = mWhole;
208 res.mFraction = mFraction;
209 res.mSawDecimal = mSawDecimal;
Hans Boehm0b9806f2015-06-29 16:07:15 -0700210 res.mExponent = mExponent;
Hans Boehm84614952014-11-25 18:46:17 -0800211 return res;
212 }
213 }
214
215 // Hash maps used to detect duplicate subexpressions when
216 // we write out CalculatorExprs and read them back in.
217 private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
218 new ThreadLocal<IdentityHashMap<CR,Integer>>();
219 // Maps expressions to indices on output
220 private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
221 new ThreadLocal<HashMap<Integer,PreEval>>();
222 // Maps expressions to indices on output
223 private static final ThreadLocal<Integer> exprIndex =
224 new ThreadLocal<Integer>();
225
226 static void initExprOutput() {
227 outMap.set(new IdentityHashMap<CR,Integer>());
228 exprIndex.set(Integer.valueOf(0));
229 }
230
231 static void initExprInput() {
232 inMap.set(new HashMap<Integer,PreEval>());
233 }
234
235 // We treat previously evaluated subexpressions as tokens
236 // These are inserted when either:
237 // - We continue an expression after evaluating some of it.
238 // - TODO: When we copy/paste expressions.
239 // The representation includes three different representations
240 // of the expression:
241 // 1) The CR value for use in computation.
242 // 2) The integer value for use in the computations,
243 // if the expression evaluates to an integer.
244 // 3a) The corresponding CalculatorExpr, together with
245 // 3b) The context (currently just deg/rad mode) used to evaluate
246 // the expression.
247 // 4) A short string representation that is used to
248 // Display the expression.
249 //
250 // (3) is present only so that we can persist the object.
251 // (4) is stored explicitly to avoid waiting for recomputation in the UI
252 // thread.
253 private static class PreEval extends Token {
254 final CR mValue;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700255 final BoundedRational mRatValue;
Hans Boehm84614952014-11-25 18:46:17 -0800256 private final CalculatorExpr mExpr;
257 private final EvalContext mContext;
Hans Boehm50ed3202015-06-09 14:35:49 -0700258 private final String mShortRep; // Not internationalized.
Hans Boehm013969e2015-04-13 20:29:47 -0700259 PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr,
260 EvalContext ec, String shortRep) {
Hans Boehm84614952014-11-25 18:46:17 -0800261 mValue = val;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700262 mRatValue = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800263 mExpr = expr;
264 mContext = ec;
265 mShortRep = shortRep;
266 }
267 // In writing out PreEvals, we are careful to avoid writing
268 // out duplicates. We assume that two expressions are
269 // duplicates if they have the same mVal. This avoids a
270 // potential exponential blow up in certain off cases and
271 // redundant evaluation after reading them back in.
272 // The parameter hash map maps expressions we've seen
273 // before to their index.
274 @Override
275 void write(DataOutput out) throws IOException {
276 out.writeByte(TokenKind.PRE_EVAL.ordinal());
277 Integer index = outMap.get().get(mValue);
278 if (index == null) {
279 int nextIndex = exprIndex.get() + 1;
280 exprIndex.set(nextIndex);
281 outMap.get().put(mValue, nextIndex);
282 out.writeInt(nextIndex);
283 mExpr.write(out);
284 mContext.write(out);
285 out.writeUTF(mShortRep);
286 } else {
287 // Just write out the index
288 out.writeInt(index);
289 }
290 }
291 PreEval(DataInput in) throws IOException {
292 int index = in.readInt();
293 PreEval prev = inMap.get().get(index);
294 if (prev == null) {
295 mExpr = new CalculatorExpr(in);
Hans Boehmc023b732015-04-29 11:30:47 -0700296 mContext = new EvalContext(in, mExpr.mExpr.size());
Hans Boehm84614952014-11-25 18:46:17 -0800297 // Recompute other fields
298 // We currently do this in the UI thread, but we
299 // only create PreEval expressions that were
300 // previously successfully evaluated, and thus
301 // don't diverge. We also only evaluate to a
302 // constructive real, which involves substantial
303 // work only in fairly contrived circumstances.
304 // TODO: Deal better with slow evaluations.
Hans Boehmc023b732015-04-29 11:30:47 -0700305 EvalRet res = null;
306 try {
307 res = mExpr.evalExpr(0, mContext);
308 } catch (SyntaxException e) {
309 // Should be impossible, since we only write out
310 // expressions that can be evaluated.
311 Log.e("Calculator", "Unexpected syntax exception" + e);
312 }
Hans Boehm84614952014-11-25 18:46:17 -0800313 mValue = res.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700314 mRatValue = res.mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800315 mShortRep = in.readUTF();
316 inMap.get().put(index, this);
317 } else {
318 mValue = prev.mValue;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700319 mRatValue = prev.mRatValue;
Hans Boehm84614952014-11-25 18:46:17 -0800320 mExpr = prev.mExpr;
321 mContext = prev.mContext;
322 mShortRep = prev.mShortRep;
323 }
324 }
325 @Override
326 String toString(Context context) {
Hans Boehm50ed3202015-06-09 14:35:49 -0700327 return KeyMaps.translateResult(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800328 }
329 @Override
Hans Boehm187d3e92015-06-09 18:04:26 -0700330 TokenKind kind() {
331 return TokenKind.PRE_EVAL;
332 }
333 boolean hasEllipsis() {
334 return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
335 }
Hans Boehm84614952014-11-25 18:46:17 -0800336 }
337
338 static Token newToken(DataInput in) throws IOException {
339 TokenKind kind = tokenKindValues[in.readByte()];
340 switch(kind) {
341 case CONSTANT:
342 return new Constant(in);
343 case OPERATOR:
344 return new Operator(in);
345 case PRE_EVAL:
346 return new PreEval(in);
347 default: throw new IOException("Bad save file format");
348 }
349 }
350
351 CalculatorExpr() {
352 mExpr = new ArrayList<Token>();
353 }
354
355 private CalculatorExpr(ArrayList<Token> expr) {
356 mExpr = expr;
357 }
358
359 CalculatorExpr(DataInput in) throws IOException {
360 mExpr = new ArrayList<Token>();
361 int size = in.readInt();
362 for (int i = 0; i < size; ++i) {
363 mExpr.add(newToken(in));
364 }
365 }
366
367 void write(DataOutput out) throws IOException {
368 int size = mExpr.size();
369 out.writeInt(size);
370 for (int i = 0; i < size; ++i) {
371 mExpr.get(i).write(out);
372 }
373 }
374
Hans Boehm0b9806f2015-06-29 16:07:15 -0700375 boolean hasTrailingConstant() {
376 int s = mExpr.size();
377 if (s == 0) {
378 return false;
379 }
380 Token t = mExpr.get(s-1);
381 return t instanceof Constant;
382 }
383
Hans Boehm84614952014-11-25 18:46:17 -0800384 private boolean hasTrailingBinary() {
385 int s = mExpr.size();
386 if (s == 0) return false;
387 Token t = mExpr.get(s-1);
388 if (!(t instanceof Operator)) return false;
389 Operator o = (Operator)t;
390 return (KeyMaps.isBinary(o.mId));
391 }
392
Hans Boehm017de982015-06-10 17:46:03 -0700393 /**
394 * Append press of button with given id to expression.
395 * If the insertion would clearly result in a syntax error, either just return false
396 * and do nothing, or make an adjustment to avoid the problem. We do the latter only
397 * for unambiguous consecutive binary operators, in which case we delete the first
398 * operator.
399 */
Hans Boehm84614952014-11-25 18:46:17 -0800400 boolean add(int id) {
401 int s = mExpr.size();
402 int d = KeyMaps.digVal(id);
403 boolean binary = KeyMaps.isBinary(id);
Hans Boehm017de982015-06-10 17:46:03 -0700404 Token lastTok = s == 0 ? null : mExpr.get(s-1);
405 int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).mId : 0;
406 // Quietly replace a trailing binary operator with another one, unless the second
407 // operator is minus, in which case we just allow it as a unary minus.
408 if (binary && !KeyMaps.isPrefix(id)) {
409 if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
410 || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
411 return false;
412 }
413 while (hasTrailingBinary()) {
414 delete();
415 }
416 // s invalid and not used below.
Hans Boehm84614952014-11-25 18:46:17 -0800417 }
418 boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
419 if (isConstPiece) {
Hans Boehm017de982015-06-10 17:46:03 -0700420 // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
Hans Boehm84614952014-11-25 18:46:17 -0800421 if (s == 0) {
422 mExpr.add(new Constant());
423 s++;
424 } else {
425 Token last = mExpr.get(s-1);
426 if(!(last instanceof Constant)) {
Hans Boehm017de982015-06-10 17:46:03 -0700427 if (last instanceof PreEval) {
428 // Add explicit multiplication to avoid confusing display.
429 mExpr.add(new Operator(R.id.op_mul));
430 s++;
Hans Boehm84614952014-11-25 18:46:17 -0800431 }
432 mExpr.add(new Constant());
433 s++;
434 }
435 }
436 return ((Constant)(mExpr.get(s-1))).add(id);
437 } else {
438 mExpr.add(new Operator(id));
439 return true;
440 }
441 }
442
Hans Boehm017de982015-06-10 17:46:03 -0700443 /**
Hans Boehm0b9806f2015-06-29 16:07:15 -0700444 * Add exponent to the constant at the end of the expression.
445 * Assumes there is a constant at the end of the expression.
446 */
447 void addExponent(int exp) {
448 Token lastTok = mExpr.get(mExpr.size() - 1);
449 ((Constant) lastTok).addExponent(exp);
450 }
451
452 /**
Hans Boehm017de982015-06-10 17:46:03 -0700453 * Remove trailing op_add and op_sub operators.
454 */
455 void removeTrailingAdditiveOperators() {
456 while (true) {
457 int s = mExpr.size();
458 if (s == 0) break;
459 Token lastTok = mExpr.get(s-1);
460 if (!(lastTok instanceof Operator)) break;
461 int lastOp = ((Operator) lastTok).mId;
462 if (lastOp != R.id.op_add && lastOp != R.id.op_sub) break;
463 delete();
464 }
465 }
466
Hans Boehm84614952014-11-25 18:46:17 -0800467 // Append the contents of the argument expression.
468 // It is assumed that the argument expression will not change,
469 // and thus its pieces can be reused directly.
470 // TODO: We probably only need this for expressions consisting of
471 // a single PreEval "token", and may want to check that.
472 void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700473 // Check that we're not concatenating Constant or PreEval
474 // tokens, since the result would look like a single constant
475 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800476 int s2 = expr2.mExpr.size();
Hans Boehmfbcef702015-04-27 18:07:47 -0700477 // Check that we're not concatenating Constant or PreEval
478 // tokens, since the result would look like a single constant,
479 // with very mysterious results for the user.
480 if (s != 0 && s2 != 0) {
481 Token last = mExpr.get(s-1);
482 Token first = expr2.mExpr.get(0);
483 if (!(first instanceof Operator) && !(last instanceof Operator)) {
484 // Fudge it by adding an explicit multiplication.
485 // We would have interpreted it as such anyway, and this
486 // makes it recognizable to the user.
487 mExpr.add(new Operator(R.id.op_mul));
488 }
489 }
Hans Boehm84614952014-11-25 18:46:17 -0800490 for (int i = 0; i < s2; ++i) {
491 mExpr.add(expr2.mExpr.get(i));
492 }
493 }
494
495 // Undo the last key addition, if any.
496 void delete() {
497 int s = mExpr.size();
498 if (s == 0) return;
499 Token last = mExpr.get(s-1);
500 if (last instanceof Constant) {
501 Constant c = (Constant)last;
502 c.delete();
503 if (!c.isEmpty()) return;
504 }
505 mExpr.remove(s-1);
506 }
507
508 void clear() {
509 mExpr.clear();
510 }
511
512 boolean isEmpty() {
513 return mExpr.isEmpty();
514 }
515
516 // Returns a logical deep copy of the CalculatorExpr.
517 // Operator and PreEval tokens are immutable, and thus
518 // aren't really copied.
519 public Object clone() {
520 CalculatorExpr res = new CalculatorExpr();
521 for (Token t: mExpr) {
522 if (t instanceof Constant) {
523 res.mExpr.add((Token)(((Constant)t).clone()));
524 } else {
525 res.mExpr.add(t);
526 }
527 }
528 return res;
529 }
530
531 // Am I just a constant?
532 boolean isConstant() {
533 if (mExpr.size() != 1) return false;
534 return mExpr.get(0) instanceof Constant;
535 }
536
537 // Return a new expression consisting of a single PreEval token
538 // representing the current expression.
539 // The caller supplies the value, degree mode, and short
540 // string representation, which must have been previously computed.
541 // Thus this is guaranteed to terminate reasonably quickly.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700542 CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800543 boolean dm, String sr) {
544 CalculatorExpr result = new CalculatorExpr();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700545 Token t = new PreEval(val, ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800546 new CalculatorExpr(
547 (ArrayList<Token>)mExpr.clone()),
Hans Boehmc023b732015-04-29 11:30:47 -0700548 new EvalContext(dm, mExpr.size()), sr);
Hans Boehm84614952014-11-25 18:46:17 -0800549 result.mExpr.add(t);
550 return result;
551 }
552
553 // Internal evaluation functions return an EvalRet triple.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700554 // We compute rational (BoundedRational) results when possible, both as
Hans Boehm84614952014-11-25 18:46:17 -0800555 // a performance optimization, and to detect errors exactly when we can.
556 private class EvalRet {
557 int mPos; // Next position (expression index) to be parsed
558 final CR mVal; // Constructive Real result of evaluating subexpression
Hans Boehm682ff5e2015-03-09 14:40:25 -0700559 final BoundedRational mRatVal; // Exact Rational value or null if
560 // irrational or hard to compute.
561 EvalRet(int p, CR v, BoundedRational r) {
Hans Boehm84614952014-11-25 18:46:17 -0800562 mPos = p;
563 mVal = v;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700564 mRatVal = r;
Hans Boehm84614952014-11-25 18:46:17 -0800565 }
566 }
567
568 // And take a context argument:
569 private static class EvalContext {
Hans Boehmc023b732015-04-29 11:30:47 -0700570 public final int mPrefixLength; // Length of prefix to evaluate.
571 // Not explicitly saved.
572 public final boolean mDegreeMode;
Hans Boehm84614952014-11-25 18:46:17 -0800573 // If we add any other kinds of evaluation modes, they go here.
Hans Boehmc023b732015-04-29 11:30:47 -0700574 EvalContext(boolean degreeMode, int len) {
Hans Boehm84614952014-11-25 18:46:17 -0800575 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700576 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800577 }
Hans Boehmc023b732015-04-29 11:30:47 -0700578 EvalContext(DataInput in, int len) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800579 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700580 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800581 }
582 void write(DataOutput out) throws IOException {
583 out.writeBoolean(mDegreeMode);
584 }
585 }
586
587 private final CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
588
589 private final CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
590
591 private CR toRadians(CR x, EvalContext ec) {
592 if (ec.mDegreeMode) {
593 return x.multiply(RADIANS_PER_DEGREE);
594 } else {
595 return x;
596 }
597 }
598
599 private CR fromRadians(CR x, EvalContext ec) {
600 if (ec.mDegreeMode) {
601 return x.multiply(DEGREES_PER_RADIAN);
602 } else {
603 return x;
604 }
605 }
606
607 // The following methods can all throw IndexOutOfBoundsException
608 // in the event of a syntax error. We expect that to be caught in
609 // eval below.
610
Hans Boehmc023b732015-04-29 11:30:47 -0700611 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800612 Token t = mExpr.get(i);
613 if (!(t instanceof Operator)) return false;
614 return ((Operator)(t)).mId == op;
615 }
616
Hans Boehmc023b732015-04-29 11:30:47 -0700617 private boolean isOperator(int i, int op, EvalContext ec) {
618 if (i >= ec.mPrefixLength) return false;
619 return isOperatorUnchecked(i, op);
620 }
621
622 static class SyntaxException extends Exception {
623 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800624 super();
625 }
Hans Boehmc023b732015-04-29 11:30:47 -0700626 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800627 super(s);
628 }
629 }
630
631 // The following functions all evaluate some kind of expression
632 // starting at position i in mExpr in a specified evaluation context.
633 // They return both the expression value (as constructive real and,
634 // if applicable, as BigInteger) and the position of the next token
635 // that was not used as part of the evaluation.
Hans Boehmc023b732015-04-29 11:30:47 -0700636 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800637 Token t = mExpr.get(i);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700638 BoundedRational ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800639 CR value;
640 if (t instanceof Constant) {
641 Constant c = (Constant)t;
Hans Boehm0b9806f2015-06-29 16:07:15 -0700642 ratVal = c.toRational();
643 value = ratVal.CRValue();
644 return new EvalRet(i+1, value, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800645 }
646 if (t instanceof PreEval) {
647 PreEval p = (PreEval)t;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700648 return new EvalRet(i+1, p.mValue, p.mRatValue);
Hans Boehm84614952014-11-25 18:46:17 -0800649 }
650 EvalRet argVal;
651 switch(((Operator)(t)).mId) {
652 case R.id.const_pi:
653 return new EvalRet(i+1, CR.PI, null);
654 case R.id.const_e:
Hans Boehm4db31b42015-05-31 12:19:05 -0700655 return new EvalRet(i+1, REAL_E, null);
Hans Boehm84614952014-11-25 18:46:17 -0800656 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700657 // Seems to have highest precedence.
658 // Does not add implicit paren.
659 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700660 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700661 argVal = evalUnary(i+2, ec);
662 ratVal = BoundedRational.sqrt(
663 BoundedRational.negate(argVal.mRatVal));
664 if (ratVal != null) break;
665 return new EvalRet(argVal.mPos,
666 argVal.mVal.negate().sqrt(), null);
667 } else {
668 argVal = evalUnary(i+1, ec);
669 ratVal = BoundedRational.sqrt(argVal.mRatVal);
670 if (ratVal != null) break;
671 return new EvalRet(argVal.mPos, argVal.mVal.sqrt(), null);
672 }
Hans Boehm84614952014-11-25 18:46:17 -0800673 case R.id.lparen:
674 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700675 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700676 return new EvalRet(argVal.mPos, argVal.mVal, argVal.mRatVal);
Hans Boehm84614952014-11-25 18:46:17 -0800677 case R.id.fun_sin:
678 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700679 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700680 ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700681 : BoundedRational.sin(argVal.mRatVal);
682 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800683 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700684 toRadians(argVal.mVal,ec).sin(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800685 case R.id.fun_cos:
686 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700687 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700688 ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700689 : BoundedRational.cos(argVal.mRatVal);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700690 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800691 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700692 toRadians(argVal.mVal,ec).cos(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800693 case R.id.fun_tan:
694 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700695 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700696 ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700697 : BoundedRational.tan(argVal.mRatVal);
698 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800699 CR argCR = toRadians(argVal.mVal, ec);
700 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700701 argCR.sin().divide(argCR.cos()), null);
Hans Boehm84614952014-11-25 18:46:17 -0800702 case R.id.fun_ln:
703 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700704 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700705 ratVal = BoundedRational.ln(argVal.mRatVal);
706 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800707 return new EvalRet(argVal.mPos, argVal.mVal.ln(), null);
Hans Boehm4db31b42015-05-31 12:19:05 -0700708 case R.id.fun_exp:
709 argVal = evalExpr(i+1, ec);
710 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
711 ratVal = BoundedRational.exp(argVal.mRatVal);
712 if (ratVal != null) break;
713 return new EvalRet(argVal.mPos, argVal.mVal.exp(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800714 case R.id.fun_log:
715 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700716 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700717 ratVal = BoundedRational.log(argVal.mRatVal);
718 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800719 return new EvalRet(argVal.mPos,
720 argVal.mVal.ln().divide(CR.valueOf(10).ln()),
721 null);
722 case R.id.fun_arcsin:
723 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700724 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700725 ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700726 : BoundedRational.asin(argVal.mRatVal);
727 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800728 return new EvalRet(argVal.mPos,
729 fromRadians(UnaryCRFunction
730 .asinFunction.execute(argVal.mVal),ec),
731 null);
732 case R.id.fun_arccos:
733 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700734 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700735 ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700736 : BoundedRational.acos(argVal.mRatVal);
737 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800738 return new EvalRet(argVal.mPos,
739 fromRadians(UnaryCRFunction
740 .acosFunction.execute(argVal.mVal),ec),
741 null);
742 case R.id.fun_arctan:
743 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700744 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700745 ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700746 : BoundedRational.atan(argVal.mRatVal);
747 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800748 return new EvalRet(argVal.mPos,
749 fromRadians(UnaryCRFunction
750 .atanFunction.execute(argVal.mVal),ec),
751 null);
752 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700753 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800754 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700755 // We have a rational value.
756 return new EvalRet(argVal.mPos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800757 }
758
759 // Compute an integral power of a constructive real.
760 // Unlike the "general" case using logarithms, this handles a negative
761 // base.
762 private static CR pow(CR base, BigInteger exp) {
763 if (exp.compareTo(BigInteger.ZERO) < 0) {
764 return pow(base, exp.negate()).inverse();
765 }
766 if (exp.equals(BigInteger.ONE)) return base;
767 if (exp.and(BigInteger.ONE).intValue() == 1) {
768 return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
769 }
770 if (exp.equals(BigInteger.ZERO)) {
771 return CR.valueOf(1);
772 }
773 CR tmp = pow(base, exp.shiftRight(1));
774 return tmp.multiply(tmp);
775 }
776
Hans Boehm682ff5e2015-03-09 14:40:25 -0700777 private static final int TEST_PREC = -100;
778 // Test for integer-ness to 100 bits past binary point.
779 private static final BigInteger MASK =
780 BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
Hans Boehm4db31b42015-05-31 12:19:05 -0700781 private static final CR REAL_E = CR.valueOf(1).exp();
782 private static final CR REAL_ONE_HUNDREDTH = CR.valueOf(100).inverse();
783 private static final BoundedRational RATIONAL_ONE_HUNDREDTH =
784 new BoundedRational(1,100);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700785 private static boolean isApprInt(CR x) {
786 BigInteger appr = x.get_appr(TEST_PREC);
787 return appr.and(MASK).signum() == 0;
788 }
789
Hans Boehm4db31b42015-05-31 12:19:05 -0700790 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800791 EvalRet tmp = evalUnary(i, ec);
792 int cpos = tmp.mPos;
793 CR cval = tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700794 BoundedRational ratVal = tmp.mRatVal;
Hans Boehm4db31b42015-05-31 12:19:05 -0700795 boolean isFact;
796 boolean isSquared = false;
797 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
798 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
799 isOperator(cpos, R.id.op_pct, ec)) {
800 if (isFact) {
801 if (ratVal == null) {
802 // Assume it was an integer, but we
803 // didn't figure it out.
804 // KitKat may have used the Gamma function.
805 if (!isApprInt(cval)) {
806 throw new ArithmeticException("factorial(non-integer)");
807 }
808 ratVal = new BoundedRational(cval.BigIntegerValue());
Hans Boehm682ff5e2015-03-09 14:40:25 -0700809 }
Hans Boehm4db31b42015-05-31 12:19:05 -0700810 ratVal = BoundedRational.fact(ratVal);
811 cval = ratVal.CRValue();
812 } else if (isSquared) {
813 ratVal = BoundedRational.multiply(ratVal, ratVal);
814 if (ratVal == null) {
815 cval = cval.multiply(cval);
816 } else {
817 cval = ratVal.CRValue();
818 }
819 } else /* percent */ {
820 ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH);
821 if (ratVal == null) {
822 cval = cval.multiply(REAL_ONE_HUNDREDTH);
823 } else {
824 cval = ratVal.CRValue();
825 }
Hans Boehm84614952014-11-25 18:46:17 -0800826 }
Hans Boehm84614952014-11-25 18:46:17 -0800827 ++cpos;
828 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700829 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800830 }
831
Hans Boehmc023b732015-04-29 11:30:47 -0700832 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700833 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800834 int cpos = result1.mPos; // current position
835 CR cval = result1.mVal; // value so far
Hans Boehm682ff5e2015-03-09 14:40:25 -0700836 BoundedRational ratVal = result1.mRatVal; // int value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700837 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm84614952014-11-25 18:46:17 -0800838 final EvalRet exp = evalSignedFactor(cpos+1, ec);
839 cpos = exp.mPos;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700840 // Try completely rational evaluation first.
841 ratVal = BoundedRational.pow(ratVal, exp.mRatVal);
842 if (ratVal != null) {
843 return new EvalRet(cpos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800844 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700845 // Power with integer exponent is defined for negative base.
846 // Thus we handle that case separately.
847 // We punt if the exponent is an integer computed from irrational
848 // values. That wouldn't work reliably with floating point either.
849 BigInteger int_exp = BoundedRational.asBigInteger(exp.mRatVal);
850 if (int_exp != null) {
851 cval = pow(cval, int_exp);
Hans Boehm84614952014-11-25 18:46:17 -0800852 } else {
Hans Boehm84614952014-11-25 18:46:17 -0800853 cval = cval.ln().multiply(exp.mVal).exp();
854 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700855 ratVal = null;
Hans Boehm84614952014-11-25 18:46:17 -0800856 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700857 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800858 }
859
Hans Boehmc023b732015-04-29 11:30:47 -0700860 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
861 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700862 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800863 EvalRet tmp = evalFactor(cpos, ec);
864 cpos = tmp.mPos;
Hans Boehm08e8f322015-04-21 13:18:38 -0700865 CR cval = negative ? tmp.mVal.negate() : tmp.mVal;
866 BoundedRational ratVal = negative ? BoundedRational.negate(tmp.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700867 : tmp.mRatVal;
868 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800869 }
870
871 private boolean canStartFactor(int i) {
872 if (i >= mExpr.size()) return false;
873 Token t = mExpr.get(i);
874 if (!(t instanceof Operator)) return true;
875 int id = ((Operator)(t)).mId;
876 if (KeyMaps.isBinary(id)) return false;
877 switch (id) {
878 case R.id.op_fact:
879 case R.id.rparen:
880 return false;
881 default:
882 return true;
883 }
884 }
885
Hans Boehmc023b732015-04-29 11:30:47 -0700886 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800887 EvalRet tmp = evalSignedFactor(i, ec);
888 boolean is_mul = false;
889 boolean is_div = false;
890 int cpos = tmp.mPos; // Current position in expression.
891 CR cval = tmp.mVal; // Current value.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700892 BoundedRational ratVal = tmp.mRatVal; // Current rational value.
Hans Boehmc023b732015-04-29 11:30:47 -0700893 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
894 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -0800895 || canStartFactor(cpos)) {
896 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700897 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800898 if (is_div) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700899 ratVal = BoundedRational.divide(ratVal, tmp.mRatVal);
900 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800901 cval = cval.divide(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700902 } else {
903 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800904 }
905 } else {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700906 ratVal = BoundedRational.multiply(ratVal, tmp.mRatVal);
907 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800908 cval = cval.multiply(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700909 } else {
910 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800911 }
912 }
913 cpos = tmp.mPos;
914 is_mul = is_div = false;
915 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700916 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800917 }
918
Hans Boehmc023b732015-04-29 11:30:47 -0700919 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800920 EvalRet tmp = evalTerm(i, ec);
921 boolean is_plus;
922 int cpos = tmp.mPos;
923 CR cval = tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700924 BoundedRational ratVal = tmp.mRatVal;
Hans Boehmc023b732015-04-29 11:30:47 -0700925 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
926 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm84614952014-11-25 18:46:17 -0800927 tmp = evalTerm(cpos+1, ec);
928 if (is_plus) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700929 ratVal = BoundedRational.add(ratVal, tmp.mRatVal);
930 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800931 cval = cval.add(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700932 } else {
933 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800934 }
935 } else {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700936 ratVal = BoundedRational.subtract(ratVal, tmp.mRatVal);
937 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800938 cval = cval.subtract(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700939 } else {
940 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800941 }
942 }
943 cpos = tmp.mPos;
944 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700945 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800946 }
947
948 // Externally visible evaluation result.
949 public class EvalResult {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700950 EvalResult (CR val, BoundedRational ratVal) {
Hans Boehm84614952014-11-25 18:46:17 -0800951 mVal = val;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700952 mRatVal = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800953 }
954 final CR mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700955 final BoundedRational mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800956 }
957
Hans Boehme8553762015-06-26 17:56:52 -0700958 /**
959 * Return the starting position of the sequence of trailing binary operators.
960 */
961 private int trailingBinaryOpsStart() {
Hans Boehmc023b732015-04-29 11:30:47 -0700962 int result = mExpr.size();
963 while (result > 0) {
964 Token last = mExpr.get(result - 1);
965 if (!(last instanceof Operator)) break;
966 Operator o = (Operator)last;
Hans Boehme8553762015-06-26 17:56:52 -0700967 if (!KeyMaps.isBinary(o.mId)) break;
Hans Boehmc023b732015-04-29 11:30:47 -0700968 --result;
969 }
970 return result;
971 }
972
Hans Boehmc023b732015-04-29 11:30:47 -0700973 // Is the current expression worth evaluating?
974 public boolean hasInterestingOps() {
Hans Boehme8553762015-06-26 17:56:52 -0700975 int last = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -0700976 int first = 0;
977 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
978 // Leading minus is not by itself interesting.
979 first++;
980 }
981 for (int i = first; i < last; ++i) {
982 Token t1 = mExpr.get(i);
Hans Boehm187d3e92015-06-09 18:04:26 -0700983 if (t1 instanceof Operator
984 || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
985 return true;
986 }
Hans Boehmc023b732015-04-29 11:30:47 -0700987 }
988 return false;
989 }
990
Hans Boehme8553762015-06-26 17:56:52 -0700991 /**
992 * Evaluate the expression excluding trailing binary operators.
993 * Errors result in exceptions, most of which are unchecked.
994 * Should not be called concurrently with modification of the expression.
995 * May take a very long time; avoid calling from UI thread.
996 *
997 * @param degreeMode use degrees rather than radians
998 */
999 EvalResult eval(boolean degreeMode) throws SyntaxException
Hans Boehmc023b732015-04-29 11:30:47 -07001000 // And unchecked exceptions thrown by CR
1001 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -08001002 {
1003 try {
Hans Boehme8553762015-06-26 17:56:52 -07001004 // We currently never include trailing binary operators, but include
1005 // other trailing operators.
1006 // Thus we usually, but not always, display results for prefixes
1007 // of valid expressions, and don't generate an error where we previously
1008 // displayed an instant result. This reflects the Android L design.
1009 int prefixLen = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -07001010 EvalContext ec = new EvalContext(degreeMode, prefixLen);
Hans Boehm84614952014-11-25 18:46:17 -08001011 EvalRet res = evalExpr(0, ec);
Hans Boehmc023b732015-04-29 11:30:47 -07001012 if (res.mPos != prefixLen) {
1013 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -07001014 }
Hans Boehm682ff5e2015-03-09 14:40:25 -07001015 return new EvalResult(res.mVal, res.mRatVal);
Hans Boehm84614952014-11-25 18:46:17 -08001016 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -07001017 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -08001018 }
1019 }
1020
1021 // Produce a string representation of the expression itself
1022 String toString(Context context) {
1023 StringBuilder sb = new StringBuilder();
1024 for (Token t: mExpr) {
1025 sb.append(t.toString(context));
1026 }
1027 return sb.toString();
1028 }
1029}