blob: 0815a6d85b911f8e2eccd0028aef85839807e9e0 [file] [log] [blame]
Hans Boehm84614952014-11-25 18:46:17 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
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
19import java.util.ArrayList;
20import java.util.HashMap;
21import java.util.IdentityHashMap;
22import java.math.BigInteger;
23import java.io.DataInput;
24import java.io.DataOutput;
25import java.io.IOException;
26
27import com.hp.creals.CR;
28import com.hp.creals.UnaryCRFunction;
29import com.hp.creals.PrecisionOverflowError;
30import com.hp.creals.AbortedError;
31
32import android.content.Context;
33
34// 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) {
72 return KeyMaps.toString(mId, context);
73 }
74 @Override
75 TokenKind kind() { return TokenKind.OPERATOR; }
76 }
77
78 // A (possibly incomplete) numerical constant
79 private static class Constant extends Token implements Cloneable {
80 private boolean mSawDecimal;
81 String mWhole; // part before decimal point
82 private String mFraction; // part after decimal point
83
84 Constant() {
85 mWhole = "";
86 mFraction = "";
87 mSawDecimal = false;
88 };
89
90 Constant(DataInput in) throws IOException {
91 mWhole = in.readUTF();
92 mSawDecimal = in.readBoolean();
93 mFraction = in.readUTF();
94 }
95
96 @Override
97 void write(DataOutput out) throws IOException {
98 out.writeByte(TokenKind.CONSTANT.ordinal());
99 out.writeUTF(mWhole);
100 out.writeBoolean(mSawDecimal);
101 out.writeUTF(mFraction);
102 }
103
104 // Given a button press, append corresponding digit.
105 // We assume id is a digit or decimal point.
106 // Just return false if this was the second (or later) decimal point
107 // in this constant.
108 boolean add(int id) {
109 if (id == R.id.dec_point) {
110 if (mSawDecimal) return false;
111 mSawDecimal = true;
112 return true;
113 }
114 int val = KeyMaps.digVal(id);
115 if (mSawDecimal) {
116 mFraction += val;
117 } else {
118 mWhole += val;
119 }
120 return true;
121 }
122
123 // Undo the last add.
124 // Assumes the constant is nonempty.
125 void delete() {
126 if (!mFraction.isEmpty()) {
127 mFraction = mFraction.substring(0, mFraction.length() - 1);
128 } else if (mSawDecimal) {
129 mSawDecimal = false;
130 } else {
131 mWhole = mWhole.substring(0, mWhole.length() - 1);
132 }
133 }
134
135 boolean isEmpty() {
136 return (mSawDecimal == false && mWhole.isEmpty());
137 }
138
139 boolean isInt() {
140 return !mSawDecimal || mFraction.isEmpty()
141 || new BigInteger(mFraction).equals(BigInteger.ZERO);
142 }
143
144 @Override
145 public String toString() {
146 String result = mWhole;
147 if (mSawDecimal) {
148 result += '.';
149 result += mFraction;
150 }
151 return result;
152 }
153
154 @Override
155 String toString(Context context) {
156 return toString();
157 }
158
159 @Override
160 TokenKind kind() { return TokenKind.CONSTANT; }
161
162 // Override clone to make it public
163 @Override
164 public Object clone() {
165 Constant res = new Constant();
166 res.mWhole = mWhole;
167 res.mFraction = mFraction;
168 res.mSawDecimal = mSawDecimal;
169 return res;
170 }
171 }
172
173 // Hash maps used to detect duplicate subexpressions when
174 // we write out CalculatorExprs and read them back in.
175 private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
176 new ThreadLocal<IdentityHashMap<CR,Integer>>();
177 // Maps expressions to indices on output
178 private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
179 new ThreadLocal<HashMap<Integer,PreEval>>();
180 // Maps expressions to indices on output
181 private static final ThreadLocal<Integer> exprIndex =
182 new ThreadLocal<Integer>();
183
184 static void initExprOutput() {
185 outMap.set(new IdentityHashMap<CR,Integer>());
186 exprIndex.set(Integer.valueOf(0));
187 }
188
189 static void initExprInput() {
190 inMap.set(new HashMap<Integer,PreEval>());
191 }
192
193 // We treat previously evaluated subexpressions as tokens
194 // These are inserted when either:
195 // - We continue an expression after evaluating some of it.
196 // - TODO: When we copy/paste expressions.
197 // The representation includes three different representations
198 // of the expression:
199 // 1) The CR value for use in computation.
200 // 2) The integer value for use in the computations,
201 // if the expression evaluates to an integer.
202 // 3a) The corresponding CalculatorExpr, together with
203 // 3b) The context (currently just deg/rad mode) used to evaluate
204 // the expression.
205 // 4) A short string representation that is used to
206 // Display the expression.
207 //
208 // (3) is present only so that we can persist the object.
209 // (4) is stored explicitly to avoid waiting for recomputation in the UI
210 // thread.
211 private static class PreEval extends Token {
212 final CR mValue;
213 final BigInteger mIntValue;
214 private final CalculatorExpr mExpr;
215 private final EvalContext mContext;
216 private final String mShortRep;
217 PreEval(CR val, BigInteger intVal, CalculatorExpr expr, EvalContext ec,
218 String shortRep) {
219 mValue = val;
220 mIntValue = intVal;
221 mExpr = expr;
222 mContext = ec;
223 mShortRep = shortRep;
224 }
225 // In writing out PreEvals, we are careful to avoid writing
226 // out duplicates. We assume that two expressions are
227 // duplicates if they have the same mVal. This avoids a
228 // potential exponential blow up in certain off cases and
229 // redundant evaluation after reading them back in.
230 // The parameter hash map maps expressions we've seen
231 // before to their index.
232 @Override
233 void write(DataOutput out) throws IOException {
234 out.writeByte(TokenKind.PRE_EVAL.ordinal());
235 Integer index = outMap.get().get(mValue);
236 if (index == null) {
237 int nextIndex = exprIndex.get() + 1;
238 exprIndex.set(nextIndex);
239 outMap.get().put(mValue, nextIndex);
240 out.writeInt(nextIndex);
241 mExpr.write(out);
242 mContext.write(out);
243 out.writeUTF(mShortRep);
244 } else {
245 // Just write out the index
246 out.writeInt(index);
247 }
248 }
249 PreEval(DataInput in) throws IOException {
250 int index = in.readInt();
251 PreEval prev = inMap.get().get(index);
252 if (prev == null) {
253 mExpr = new CalculatorExpr(in);
254 mContext = new EvalContext(in);
255 // Recompute other fields
256 // We currently do this in the UI thread, but we
257 // only create PreEval expressions that were
258 // previously successfully evaluated, and thus
259 // don't diverge. We also only evaluate to a
260 // constructive real, which involves substantial
261 // work only in fairly contrived circumstances.
262 // TODO: Deal better with slow evaluations.
263 EvalRet res = mExpr.evalExpr(0,mContext);
264 mValue = res.mVal;
265 mIntValue = res.mIntVal;
266 mShortRep = in.readUTF();
267 inMap.get().put(index, this);
268 } else {
269 mValue = prev.mValue;
270 mIntValue = prev.mIntValue;
271 mExpr = prev.mExpr;
272 mContext = prev.mContext;
273 mShortRep = prev.mShortRep;
274 }
275 }
276 @Override
277 String toString(Context context) {
278 return mShortRep;
279 }
280 @Override
281 TokenKind kind() { return TokenKind.PRE_EVAL; }
282 }
283
284 static Token newToken(DataInput in) throws IOException {
285 TokenKind kind = tokenKindValues[in.readByte()];
286 switch(kind) {
287 case CONSTANT:
288 return new Constant(in);
289 case OPERATOR:
290 return new Operator(in);
291 case PRE_EVAL:
292 return new PreEval(in);
293 default: throw new IOException("Bad save file format");
294 }
295 }
296
297 CalculatorExpr() {
298 mExpr = new ArrayList<Token>();
299 }
300
301 private CalculatorExpr(ArrayList<Token> expr) {
302 mExpr = expr;
303 }
304
305 CalculatorExpr(DataInput in) throws IOException {
306 mExpr = new ArrayList<Token>();
307 int size = in.readInt();
308 for (int i = 0; i < size; ++i) {
309 mExpr.add(newToken(in));
310 }
311 }
312
313 void write(DataOutput out) throws IOException {
314 int size = mExpr.size();
315 out.writeInt(size);
316 for (int i = 0; i < size; ++i) {
317 mExpr.get(i).write(out);
318 }
319 }
320
321 private boolean hasTrailingBinary() {
322 int s = mExpr.size();
323 if (s == 0) return false;
324 Token t = mExpr.get(s-1);
325 if (!(t instanceof Operator)) return false;
326 Operator o = (Operator)t;
327 return (KeyMaps.isBinary(o.mId));
328 }
329
330 // Append press of button with given id to expression.
331 // Returns false and does nothing if this would clearly
332 // result in a syntax error.
333 boolean add(int id) {
334 int s = mExpr.size();
335 int d = KeyMaps.digVal(id);
336 boolean binary = KeyMaps.isBinary(id);
337 if (s == 0 && binary && id != R.id.op_sub) return false;
338 if (binary && hasTrailingBinary()
339 && (id != R.id.op_sub || isOperator(s-1, R.id.op_sub))) {
340 return false;
341 }
342 boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
343 if (isConstPiece) {
344 if (s == 0) {
345 mExpr.add(new Constant());
346 s++;
347 } else {
348 Token last = mExpr.get(s-1);
349 if(!(last instanceof Constant)) {
350 if (!(last instanceof Operator)) {
351 return false;
352 }
353 int lastOp = ((Operator)last).mId;
354 if (lastOp == R.id.const_e || lastOp == R.id.const_pi
355 || lastOp == R.id.op_fact
356 || lastOp == R.id.rparen) {
357 // Constant cannot possibly follow; reject immediately
358 return false;
359 }
360 mExpr.add(new Constant());
361 s++;
362 }
363 }
364 return ((Constant)(mExpr.get(s-1))).add(id);
365 } else {
366 mExpr.add(new Operator(id));
367 return true;
368 }
369 }
370
371 // Append the contents of the argument expression.
372 // It is assumed that the argument expression will not change,
373 // and thus its pieces can be reused directly.
374 // TODO: We probably only need this for expressions consisting of
375 // a single PreEval "token", and may want to check that.
376 void append(CalculatorExpr expr2) {
377 int s2 = expr2.mExpr.size();
378 for (int i = 0; i < s2; ++i) {
379 mExpr.add(expr2.mExpr.get(i));
380 }
381 }
382
383 // Undo the last key addition, if any.
384 void delete() {
385 int s = mExpr.size();
386 if (s == 0) return;
387 Token last = mExpr.get(s-1);
388 if (last instanceof Constant) {
389 Constant c = (Constant)last;
390 c.delete();
391 if (!c.isEmpty()) return;
392 }
393 mExpr.remove(s-1);
394 }
395
396 void clear() {
397 mExpr.clear();
398 }
399
400 boolean isEmpty() {
401 return mExpr.isEmpty();
402 }
403
404 // Returns a logical deep copy of the CalculatorExpr.
405 // Operator and PreEval tokens are immutable, and thus
406 // aren't really copied.
407 public Object clone() {
408 CalculatorExpr res = new CalculatorExpr();
409 for (Token t: mExpr) {
410 if (t instanceof Constant) {
411 res.mExpr.add((Token)(((Constant)t).clone()));
412 } else {
413 res.mExpr.add(t);
414 }
415 }
416 return res;
417 }
418
419 // Am I just a constant?
420 boolean isConstant() {
421 if (mExpr.size() != 1) return false;
422 return mExpr.get(0) instanceof Constant;
423 }
424
425 // Return a new expression consisting of a single PreEval token
426 // representing the current expression.
427 // The caller supplies the value, degree mode, and short
428 // string representation, which must have been previously computed.
429 // Thus this is guaranteed to terminate reasonably quickly.
430 CalculatorExpr abbreviate(CR val, BigInteger intVal,
431 boolean dm, String sr) {
432 CalculatorExpr result = new CalculatorExpr();
433 Token t = new PreEval(val, intVal,
434 new CalculatorExpr(
435 (ArrayList<Token>)mExpr.clone()),
436 new EvalContext(dm), sr);
437 result.mExpr.add(t);
438 return result;
439 }
440
441 // Internal evaluation functions return an EvalRet triple.
442 // We compute integer (BigInteger) results when possible, both as
443 // a performance optimization, and to detect errors exactly when we can.
444 private class EvalRet {
445 int mPos; // Next position (expression index) to be parsed
446 final CR mVal; // Constructive Real result of evaluating subexpression
447 final BigInteger mIntVal; // Exact Integer value or null if not integer
448 EvalRet(int p, CR v, BigInteger i) {
449 mPos = p;
450 mVal = v;
451 mIntVal = i;
452 }
453 }
454
455 // And take a context argument:
456 private static class EvalContext {
457 // Memory register contents are not included here,
458 // since we now make that an explicit part of the expression
459 // If we add any other kinds of evaluation modes, they go here.
460 boolean mDegreeMode;
461 EvalContext(boolean degreeMode) {
462 mDegreeMode = degreeMode;
463 }
464 EvalContext(DataInput in) throws IOException {
465 mDegreeMode = in.readBoolean();
466 }
467 void write(DataOutput out) throws IOException {
468 out.writeBoolean(mDegreeMode);
469 }
470 }
471
472 private final CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
473
474 private final CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
475
476 private CR toRadians(CR x, EvalContext ec) {
477 if (ec.mDegreeMode) {
478 return x.multiply(RADIANS_PER_DEGREE);
479 } else {
480 return x;
481 }
482 }
483
484 private CR fromRadians(CR x, EvalContext ec) {
485 if (ec.mDegreeMode) {
486 return x.multiply(DEGREES_PER_RADIAN);
487 } else {
488 return x;
489 }
490 }
491
492 // The following methods can all throw IndexOutOfBoundsException
493 // in the event of a syntax error. We expect that to be caught in
494 // eval below.
495
496 private boolean isOperator(int i, int op) {
497 if (i >= mExpr.size()) return false;
498 Token t = mExpr.get(i);
499 if (!(t instanceof Operator)) return false;
500 return ((Operator)(t)).mId == op;
501 }
502
503 static class SyntaxError extends Error {
504 public SyntaxError() {
505 super();
506 }
507 public SyntaxError(String s) {
508 super(s);
509 }
510 }
511
512 // The following functions all evaluate some kind of expression
513 // starting at position i in mExpr in a specified evaluation context.
514 // They return both the expression value (as constructive real and,
515 // if applicable, as BigInteger) and the position of the next token
516 // that was not used as part of the evaluation.
517 private EvalRet evalUnary(int i, EvalContext ec)
518 throws ArithmeticException {
519 Token t = mExpr.get(i);
520 CR value;
521 if (t instanceof Constant) {
522 Constant c = (Constant)t;
523 value = CR.valueOf(c.toString(),10);
524 return new EvalRet(i+1, value,
525 c.isInt()? new BigInteger(c.mWhole) : null);
526 }
527 if (t instanceof PreEval) {
528 PreEval p = (PreEval)t;
529 return new EvalRet(i+1, p.mValue, p.mIntValue);
530 }
531 EvalRet argVal;
532 switch(((Operator)(t)).mId) {
533 case R.id.const_pi:
534 return new EvalRet(i+1, CR.PI, null);
535 case R.id.const_e:
536 return new EvalRet(i+1, CR.valueOf(1).exp(), null);
537 case R.id.op_sqrt:
538 // Seems to have highest precedence
539 // Does not add implicit paren
540 argVal = evalUnary(i+1, ec);
541 return new EvalRet(argVal.mPos, argVal.mVal.sqrt(), null);
542 case R.id.lparen:
543 argVal = evalExpr(i+1, ec);
544 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
545 return new EvalRet(argVal.mPos, argVal.mVal, argVal.mIntVal);
546 case R.id.fun_sin:
547 argVal = evalExpr(i+1, ec);
548 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
549 return new EvalRet(argVal.mPos,
550 toRadians(argVal.mVal,ec).sin(), null);
551 case R.id.fun_cos:
552 argVal = evalExpr(i+1, ec);
553 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
554 return new EvalRet(argVal.mPos,
555 toRadians(argVal.mVal,ec).cos(), null);
556 case R.id.fun_tan:
557 argVal = evalExpr(i+1, ec);
558 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
559 CR argCR = toRadians(argVal.mVal, ec);
560 return new EvalRet(argVal.mPos,
561 argCR.sin().divide(argCR.cos()), null);
562 case R.id.fun_ln:
563 argVal = evalExpr(i+1, ec);
564 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
565 return new EvalRet(argVal.mPos, argVal.mVal.ln(), null);
566 case R.id.fun_log:
567 argVal = evalExpr(i+1, ec);
568 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
569 // FIXME: Heuristically test argument sign
570 return new EvalRet(argVal.mPos,
571 argVal.mVal.ln().divide(CR.valueOf(10).ln()),
572 null);
573 case R.id.fun_arcsin:
574 argVal = evalExpr(i+1, ec);
575 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
576 // FIXME: Heuristically test argument in range
577 return new EvalRet(argVal.mPos,
578 fromRadians(UnaryCRFunction
579 .asinFunction.execute(argVal.mVal),ec),
580 null);
581 case R.id.fun_arccos:
582 argVal = evalExpr(i+1, ec);
583 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
584 // FIXME: Heuristically test argument in range
585 return new EvalRet(argVal.mPos,
586 fromRadians(UnaryCRFunction
587 .acosFunction.execute(argVal.mVal),ec),
588 null);
589 case R.id.fun_arctan:
590 argVal = evalExpr(i+1, ec);
591 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
592 return new EvalRet(argVal.mPos,
593 fromRadians(UnaryCRFunction
594 .atanFunction.execute(argVal.mVal),ec),
595 null);
596 default:
597 throw new SyntaxError("Unrecognized token in expression");
598 }
599 }
600
601 // Generalized factorial.
602 // Compute n * (n - step) * (n - 2 * step) * ...
603 // This can be used to compute factorial a bit faster, especially
604 // if BigInteger uses sub-quadratic multiplication.
605 private static BigInteger genFactorial(long n, long step) {
606 if (n > 4 * step) {
607 BigInteger prod1 = genFactorial(n, 2 * step);
608 BigInteger prod2 = genFactorial(n - step, 2 * step);
609 return prod1.multiply(prod2);
610 } else {
611 BigInteger res = BigInteger.valueOf(n);
612 for (long i = n - step; i > 1; i -= step) {
613 res = res.multiply(BigInteger.valueOf(i));
614 }
615 return res;
616 }
617 }
618
619 // Compute an integral power of a constructive real.
620 // Unlike the "general" case using logarithms, this handles a negative
621 // base.
622 private static CR pow(CR base, BigInteger exp) {
623 if (exp.compareTo(BigInteger.ZERO) < 0) {
624 return pow(base, exp.negate()).inverse();
625 }
626 if (exp.equals(BigInteger.ONE)) return base;
627 if (exp.and(BigInteger.ONE).intValue() == 1) {
628 return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
629 }
630 if (exp.equals(BigInteger.ZERO)) {
631 return CR.valueOf(1);
632 }
633 CR tmp = pow(base, exp.shiftRight(1));
634 return tmp.multiply(tmp);
635 }
636
637 private EvalRet evalFactorial(int i, EvalContext ec) {
638 EvalRet tmp = evalUnary(i, ec);
639 int cpos = tmp.mPos;
640 CR cval = tmp.mVal;
641 BigInteger ival = tmp.mIntVal;
642 while (isOperator(cpos, R.id.op_fact)) {
643 if (ival == null) {
644 // Assume it was an integer, but we
645 // didn't figure it out.
646 // Calculator2 may have used the Gamma function.
647 ival = cval.BigIntegerValue();
648 }
649 if (ival.compareTo(BigInteger.ZERO) <= 0
650 || ival.compareTo(BIG_BILLION) > 0) {
651 throw new ArithmeticException("Bad factorial argument");
652 }
653 ival = genFactorial(ival.longValue(), 1);
654 ++cpos;
655 }
656 if (ival != null) cval = CR.valueOf(ival);
657 return new EvalRet(cpos, cval, ival);
658 }
659
660 private EvalRet evalFactor(int i, EvalContext ec)
661 throws ArithmeticException {
662 final EvalRet result1 = evalFactorial(i, ec);
663 int cpos = result1.mPos; // current position
664 CR cval = result1.mVal; // value so far
665 BigInteger ival = result1.mIntVal; // int value so far
666 if (isOperator(cpos, R.id.op_pow)) {
667 final EvalRet exp = evalSignedFactor(cpos+1, ec);
668 cpos = exp.mPos;
669 if (ival != null && ival.equals(BigInteger.ONE)) {
670 // 1^x = 1
671 return new EvalRet(cpos, cval, ival);
672 }
673 if (exp.mIntVal != null) {
674 if (ival != null
675 && exp.mIntVal.compareTo(BigInteger.ZERO) >= 0
676 && exp.mIntVal.compareTo(BIG_MILLION) < 0
677 && ival.abs().compareTo(BIG_MILLION) < 0) {
678 // Use pure integer exponentiation
679 ival = ival.pow(exp.mIntVal.intValue());
680 cval = CR.valueOf(ival);
681 } else {
682 // Integer exponent, cval may be negative;
683 // use repeated squaring. Unsafe to use ln().
684 ival = null;
685 cval = pow(cval, exp.mIntVal);
686 }
687 } else {
688 ival = null;
689 cval = cval.ln().multiply(exp.mVal).exp();
690 }
691 }
692 return new EvalRet(cpos, cval, ival);
693 }
694
695 private EvalRet evalSignedFactor(int i, EvalContext ec)
696 throws ArithmeticException {
697 final boolean negative = isOperator(i, R.id.op_sub);
698 int cpos = negative? i + 1 : i;
699 EvalRet tmp = evalFactor(cpos, ec);
700 cpos = tmp.mPos;
701 CR cval = negative? tmp.mVal.negate() : tmp.mVal;
702 BigInteger ival = (negative && tmp.mIntVal != null)?
703 tmp.mIntVal.negate()
704 : tmp.mIntVal;
705 return new EvalRet(cpos, cval, ival);
706 }
707
708 private boolean canStartFactor(int i) {
709 if (i >= mExpr.size()) return false;
710 Token t = mExpr.get(i);
711 if (!(t instanceof Operator)) return true;
712 int id = ((Operator)(t)).mId;
713 if (KeyMaps.isBinary(id)) return false;
714 switch (id) {
715 case R.id.op_fact:
716 case R.id.rparen:
717 return false;
718 default:
719 return true;
720 }
721 }
722
723 private EvalRet evalTerm(int i, EvalContext ec)
724 throws ArithmeticException {
725 EvalRet tmp = evalSignedFactor(i, ec);
726 boolean is_mul = false;
727 boolean is_div = false;
728 int cpos = tmp.mPos; // Current position in expression.
729 CR cval = tmp.mVal; // Current value.
730 BigInteger ival = tmp.mIntVal;
731 while ((is_mul = isOperator(cpos, R.id.op_mul))
732 || (is_div = isOperator(cpos, R.id.op_div))
733 || canStartFactor(cpos)) {
734 if (is_mul || is_div) ++cpos;
735 tmp = evalTerm(cpos, ec);
736 if (is_div) {
737 if (ival != null && tmp.mIntVal != null
738 && ival.mod(tmp.mIntVal) == BigInteger.ZERO) {
739 ival = ival.divide(tmp.mIntVal);
740 cval = CR.valueOf(ival);
741 } else {
742 cval = cval.divide(tmp.mVal);
743 ival = null;
744 }
745 } else {
746 if (ival != null && tmp.mIntVal != null) {
747 ival = ival.multiply(tmp.mIntVal);
748 cval = CR.valueOf(ival);
749 } else {
750 cval = cval.multiply(tmp.mVal);
751 ival = null;
752 }
753 }
754 cpos = tmp.mPos;
755 is_mul = is_div = false;
756 }
757 return new EvalRet(cpos, cval, ival);
758 }
759
760 private EvalRet evalExpr(int i, EvalContext ec) throws ArithmeticException {
761 EvalRet tmp = evalTerm(i, ec);
762 boolean is_plus;
763 int cpos = tmp.mPos;
764 CR cval = tmp.mVal;
765 BigInteger ival = tmp.mIntVal;
766 while ((is_plus = isOperator(cpos, R.id.op_add))
767 || isOperator(cpos, R.id.op_sub)) {
768 tmp = evalTerm(cpos+1, ec);
769 if (is_plus) {
770 if (ival != null && tmp.mIntVal != null) {
771 ival = ival.add(tmp.mIntVal);
772 cval = CR.valueOf(ival);
773 } else {
774 cval = cval.add(tmp.mVal);
775 ival = null;
776 }
777 } else {
778 if (ival != null && tmp.mIntVal != null) {
779 ival = ival.subtract(tmp.mIntVal);
780 cval = CR.valueOf(ival);
781 } else {
782 cval = cval.subtract(tmp.mVal);
783 ival = null;
784 }
785 }
786 cpos = tmp.mPos;
787 }
788 return new EvalRet(cpos, cval, ival);
789 }
790
791 // Externally visible evaluation result.
792 public class EvalResult {
793 EvalResult (CR val, BigInteger intVal) {
794 mVal = val;
795 mIntVal = intVal;
796 }
797 final CR mVal;
798 final BigInteger mIntVal;
799 }
800
801 // Evaluate the entire expression, returning null in the event
802 // of an error.
803 // Not called from the UI thread, but should not be called
804 // concurrently with modifications to the expression.
805 EvalResult eval(boolean degreeMode) throws SyntaxError,
806 ArithmeticException, PrecisionOverflowError
807 {
808 try {
809 EvalContext ec = new EvalContext(degreeMode);
810 EvalRet res = evalExpr(0, ec);
811 if (res.mPos != mExpr.size()) return null;
812 return new EvalResult(res.mVal, res.mIntVal);
813 } catch (IndexOutOfBoundsException e) {
814 throw new SyntaxError("Unexpected expression end");
815 }
816 }
817
818 // Produce a string representation of the expression itself
819 String toString(Context context) {
820 StringBuilder sb = new StringBuilder();
821 for (Token t: mExpr) {
822 sb.append(t.toString(context));
823 }
824 return sb.toString();
825 }
826}