1 /*
2  * Copyright (C) 2015 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 
17 package com.android.calculator2;
18 
19 
20 import com.hp.creals.CR;
21 import com.hp.creals.UnaryCRFunction;
22 
23 import android.content.Context;
24 import android.text.SpannableString;
25 import android.text.SpannableStringBuilder;
26 import android.text.Spanned;
27 import android.text.style.TtsSpan;
28 import android.text.style.TtsSpan.TextBuilder;
29 import android.util.Log;
30 
31 import java.math.BigInteger;
32 import java.io.DataInput;
33 import java.io.DataOutput;
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.IdentityHashMap;
38 
39 /**
40  * A mathematical expression represented as a sequence of "tokens".
41  * Many tokens are represented by button ids for the corresponding operator.
42  * A token may also represent the result of a previously evaluated expression.
43  * The add() method adds a token to the end of the expression.  The delete method() removes one.
44  * Clear() deletes the entire expression contents. Eval() evaluates the expression,
45  * producing both a constructive real (CR), and possibly a BoundedRational result.
46  * Expressions are parsed only during evaluation; no explicit parse tree is maintained.
47  *
48  * The write() method is used to save the current expression.  Note that CR provides no
49  * serialization facility.  Thus we save all previously computed values by writing out the
50  * expression that was used to compute them, and reevaluate on input.
51  */
52 class CalculatorExpr {
53     private ArrayList<Token> mExpr;  // The actual representation
54                                      // as a list of tokens.  Constant
55                                      // tokens are always nonempty.
56 
57     private static enum TokenKind { CONSTANT, OPERATOR, PRE_EVAL };
58     private static TokenKind[] tokenKindValues = TokenKind.values();
59     private final static BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
60     private final static BigInteger BIG_BILLION = BigInteger.valueOf(1000000000);
61 
62     private static abstract class Token {
kind()63         abstract TokenKind kind();
64 
65         /**
66          * Write kind as Byte followed by data needed by subclass constructor.
67          */
write(DataOutput out)68         abstract void write(DataOutput out) throws IOException;
69 
70         /**
71          * Return a textual representation of the token.
72          * The result is suitable for either display as part od the formula or TalkBack use.
73          * It may be a SpannableString that includes added TalkBack information.
74          * @param context context used for converting button ids to strings
75          */
toCharSequence(Context context)76         abstract CharSequence toCharSequence(Context context);
77     }
78 
79     /**
80      * Representation of an operator token
81      */
82     private static class Operator extends Token {
83         public final int id; // We use the button resource id
Operator(int resId)84         Operator(int resId) {
85             id = resId;
86         }
Operator(DataInput in)87         Operator(DataInput in) throws IOException {
88             id = in.readInt();
89         }
90         @Override
write(DataOutput out)91         void write(DataOutput out) throws IOException {
92             out.writeByte(TokenKind.OPERATOR.ordinal());
93             out.writeInt(id);
94         }
95         @Override
toCharSequence(Context context)96         public CharSequence toCharSequence(Context context) {
97             String desc = KeyMaps.toDescriptiveString(context, id);
98             if (desc != null) {
99                 SpannableString result = new SpannableString(KeyMaps.toString(context, id));
100                 Object descSpan = new TtsSpan.TextBuilder(desc).build();
101                 result.setSpan(descSpan, 0, result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
102                 return result;
103             } else {
104                 return KeyMaps.toString(context, id);
105             }
106         }
107         @Override
kind()108         TokenKind kind() { return TokenKind.OPERATOR; }
109     }
110 
111     /**
112      * Representation of a (possibly incomplete) numerical constant.
113      * Supports addition and removal of trailing characters; hence mutable.
114      */
115     private static class Constant extends Token implements Cloneable {
116         private boolean mSawDecimal;
117         private String mWhole;  // String preceding decimal point.
118         private String mFraction; // String after decimal point.
119         private int mExponent;  // Explicit exponent, only generated through addExponent.
120 
Constant()121         Constant() {
122             mWhole = "";
123             mFraction = "";
124             mSawDecimal = false;
125             mExponent = 0;
126         };
127 
Constant(DataInput in)128         Constant(DataInput in) throws IOException {
129             mWhole = in.readUTF();
130             mSawDecimal = in.readBoolean();
131             mFraction = in.readUTF();
132             mExponent = in.readInt();
133         }
134 
135         @Override
write(DataOutput out)136         void write(DataOutput out) throws IOException {
137             out.writeByte(TokenKind.CONSTANT.ordinal());
138             out.writeUTF(mWhole);
139             out.writeBoolean(mSawDecimal);
140             out.writeUTF(mFraction);
141             out.writeInt(mExponent);
142         }
143 
144         // Given a button press, append corresponding digit.
145         // We assume id is a digit or decimal point.
146         // Just return false if this was the second (or later) decimal point
147         // in this constant.
148         // Assumes that this constant does not have an exponent.
add(int id)149         public boolean add(int id) {
150             if (id == R.id.dec_point) {
151                 if (mSawDecimal || mExponent != 0) return false;
152                 mSawDecimal = true;
153                 return true;
154             }
155             int val = KeyMaps.digVal(id);
156             if (mExponent != 0) {
157                 if (Math.abs(mExponent) <= 10000) {
158                     if (mExponent > 0) {
159                         mExponent = 10 * mExponent + val;
160                     } else {
161                         mExponent = 10 * mExponent - val;
162                     }
163                     return true;
164                 } else {  // Too large; refuse
165                     return false;
166                 }
167             }
168             if (mSawDecimal) {
169                 mFraction += val;
170             } else {
171                 mWhole += val;
172             }
173             return true;
174         }
175 
addExponent(int exp)176         public void addExponent(int exp) {
177             // Note that adding a 0 exponent is a no-op.  That's OK.
178             mExponent = exp;
179         }
180 
181         /**
182          * Undo the last add or remove last exponent digit.
183          * Assumes the constant is nonempty.
184          */
delete()185         public void delete() {
186             if (mExponent != 0) {
187                 mExponent /= 10;
188                 // Once zero, it can only be added back with addExponent.
189             } else if (!mFraction.isEmpty()) {
190                 mFraction = mFraction.substring(0, mFraction.length() - 1);
191             } else if (mSawDecimal) {
192                 mSawDecimal = false;
193             } else {
194                 mWhole = mWhole.substring(0, mWhole.length() - 1);
195             }
196         }
197 
isEmpty()198         public boolean isEmpty() {
199             return (mSawDecimal == false && mWhole.isEmpty());
200         }
201 
202         /**
203          * Produce human-readable string representation of constant, as typed.
204          * Result is internationalized.
205          */
206         @Override
toString()207         public String toString() {
208             String result = mWhole;
209             if (mSawDecimal) {
210                 result += '.';
211                 result += mFraction;
212             }
213             if (mExponent != 0) {
214                 result += "E" + mExponent;
215             }
216             return KeyMaps.translateResult(result);
217         }
218 
219         /**
220          * Return BoundedRational representation of constant, if well-formed.
221          * Result is never null.
222          */
toRational()223         public BoundedRational toRational() throws SyntaxException {
224             String whole = mWhole;
225             if (whole.isEmpty()) {
226                 if (mFraction.isEmpty()) {
227                     // Decimal point without digits.
228                     throw new SyntaxException();
229                 } else {
230                     whole = "0";
231                 }
232             }
233             BigInteger num = new BigInteger(whole + mFraction);
234             BigInteger den = BigInteger.TEN.pow(mFraction.length());
235             if (mExponent > 0) {
236                 num = num.multiply(BigInteger.TEN.pow(mExponent));
237             }
238             if (mExponent < 0) {
239                 den = den.multiply(BigInteger.TEN.pow(-mExponent));
240             }
241             return new BoundedRational(num, den);
242         }
243 
244         @Override
toCharSequence(Context context)245         public CharSequence toCharSequence(Context context) {
246             return toString();
247         }
248 
249         @Override
kind()250         public TokenKind kind() {
251             return TokenKind.CONSTANT;
252         }
253 
254         // Override clone to make it public
255         @Override
clone()256         public Object clone() {
257             Constant result = new Constant();
258             result.mWhole = mWhole;
259             result.mFraction = mFraction;
260             result.mSawDecimal = mSawDecimal;
261             result.mExponent = mExponent;
262             return result;
263         }
264     }
265 
266     // Hash maps used to detect duplicate subexpressions when we write out CalculatorExprs and
267     // read them back in.
268     private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
269             new ThreadLocal<IdentityHashMap<CR,Integer>>();
270         // Maps expressions to indices on output
271     private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
272             new ThreadLocal<HashMap<Integer,PreEval>>();
273         // Maps expressions to indices on output
274     private static final ThreadLocal<Integer> exprIndex = new ThreadLocal<Integer>();
275 
276     /**
277      * Prepare for expression output.
278      * Initializes map that will lbe used to avoid duplicating shared subexpressions.
279      * This avoids a potential exponential blow-up in the expression size.
280      */
initExprOutput()281     public static void initExprOutput() {
282         outMap.set(new IdentityHashMap<CR,Integer>());
283         exprIndex.set(Integer.valueOf(0));
284     }
285 
286     /**
287      * Prepare for expression input.
288      * Initializes map that will be used to reconstruct shared subexpressions.
289      */
initExprInput()290     public static void initExprInput() {
291         inMap.set(new HashMap<Integer,PreEval>());
292     }
293 
294     /**
295      * The "token" class for previously evaluated subexpressions.
296      * We treat previously evaluated subexpressions as tokens.  These are inserted when we either
297      * continue an expression after evaluating some of it, or copy an expression and paste it back
298      * in.
299      * The representation includes both CR and possibly BoundedRational values.  In order to
300      * support saving and restoring, we also include the underlying expression itself, and the
301      * context (currently just degree mode) used to evaluate it.  The short string representation
302      * is also stored in order to avoid potentially expensive recomputation in the UI thread.
303      */
304     private static class PreEval extends Token {
305         public final CR value;
306         public final BoundedRational ratValue;
307         private final CalculatorExpr mExpr;
308         private final EvalContext mContext;
309         private final String mShortRep;  // Not internationalized.
PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr, EvalContext ec, String shortRep)310         PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr,
311                 EvalContext ec, String shortRep) {
312             value = val;
313             ratValue = ratVal;
314             mExpr = expr;
315             mContext = ec;
316             mShortRep = shortRep;
317         }
318         // In writing out PreEvals, we are careful to avoid writing
319         // out duplicates.  We assume that two expressions are
320         // duplicates if they have the same CR value.  This avoids a
321         // potential exponential blow up in certain off cases and
322         // redundant evaluation after reading them back in.
323         // The parameter hash map maps expressions we've seen
324         // before to their index.
325         @Override
write(DataOutput out)326         public void write(DataOutput out) throws IOException {
327             out.writeByte(TokenKind.PRE_EVAL.ordinal());
328             Integer index = outMap.get().get(value);
329             if (index == null) {
330                 int nextIndex = exprIndex.get() + 1;
331                 exprIndex.set(nextIndex);
332                 outMap.get().put(value, nextIndex);
333                 out.writeInt(nextIndex);
334                 mExpr.write(out);
335                 mContext.write(out);
336                 out.writeUTF(mShortRep);
337             } else {
338                 // Just write out the index
339                 out.writeInt(index);
340             }
341         }
PreEval(DataInput in)342         PreEval(DataInput in) throws IOException {
343             int index = in.readInt();
344             PreEval prev = inMap.get().get(index);
345             if (prev == null) {
346                 mExpr = new CalculatorExpr(in);
347                 mContext = new EvalContext(in, mExpr.mExpr.size());
348                 // Recompute other fields We currently do this in the UI thread, but we only
349                 // create PreEval expressions that were previously successfully evaluated, and
350                 // thus don't diverge.  We also only evaluate to a constructive real, which
351                 // involves substantial work only in fairly contrived circumstances.
352                 // TODO: Deal better with slow evaluations.
353                 EvalRet res = null;
354                 try {
355                     res = mExpr.evalExpr(0, mContext);
356                 } catch (SyntaxException e) {
357                     // Should be impossible, since we only write out
358                     // expressions that can be evaluated.
359                     Log.e("Calculator", "Unexpected syntax exception" + e);
360                 }
361                 value = res.val;
362                 ratValue = res.ratVal;
363                 mShortRep = in.readUTF();
364                 inMap.get().put(index, this);
365             } else {
366                 value = prev.value;
367                 ratValue = prev.ratValue;
368                 mExpr = prev.mExpr;
369                 mContext = prev.mContext;
370                 mShortRep = prev.mShortRep;
371             }
372         }
373         @Override
toCharSequence(Context context)374         public CharSequence toCharSequence(Context context) {
375             return KeyMaps.translateResult(mShortRep);
376         }
377         @Override
kind()378         public TokenKind kind() {
379             return TokenKind.PRE_EVAL;
380         }
hasEllipsis()381         public boolean hasEllipsis() {
382             return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
383         }
384     }
385 
386     /**
387      * Read token from in.
388      */
newToken(DataInput in)389     public static Token newToken(DataInput in) throws IOException {
390         TokenKind kind = tokenKindValues[in.readByte()];
391         switch(kind) {
392         case CONSTANT:
393             return new Constant(in);
394         case OPERATOR:
395             return new Operator(in);
396         case PRE_EVAL:
397             return new PreEval(in);
398         default: throw new IOException("Bad save file format");
399         }
400     }
401 
CalculatorExpr()402     CalculatorExpr() {
403         mExpr = new ArrayList<Token>();
404     }
405 
CalculatorExpr(ArrayList<Token> expr)406     private CalculatorExpr(ArrayList<Token> expr) {
407         mExpr = expr;
408     }
409 
410     /**
411      * Construct CalculatorExpr, by reading it from in.
412      */
CalculatorExpr(DataInput in)413     CalculatorExpr(DataInput in) throws IOException {
414         mExpr = new ArrayList<Token>();
415         int size = in.readInt();
416         for (int i = 0; i < size; ++i) {
417             mExpr.add(newToken(in));
418         }
419     }
420 
421     /**
422      * Write this expression to out.
423      */
write(DataOutput out)424     public void write(DataOutput out) throws IOException {
425         int size = mExpr.size();
426         out.writeInt(size);
427         for (int i = 0; i < size; ++i) {
428             mExpr.get(i).write(out);
429         }
430     }
431 
432     /**
433      * Does this expression end with a numeric constant?
434      * As opposed to an operator or preevaluated expression.
435      */
hasTrailingConstant()436     boolean hasTrailingConstant() {
437         int s = mExpr.size();
438         if (s == 0) {
439             return false;
440         }
441         Token t = mExpr.get(s-1);
442         return t instanceof Constant;
443     }
444 
445     /**
446      * Does this expression end with a binary operator?
447      */
hasTrailingBinary()448     private boolean hasTrailingBinary() {
449         int s = mExpr.size();
450         if (s == 0) return false;
451         Token t = mExpr.get(s-1);
452         if (!(t instanceof Operator)) return false;
453         Operator o = (Operator)t;
454         return (KeyMaps.isBinary(o.id));
455     }
456 
457     /**
458      * Append press of button with given id to expression.
459      * If the insertion would clearly result in a syntax error, either just return false
460      * and do nothing, or make an adjustment to avoid the problem.  We do the latter only
461      * for unambiguous consecutive binary operators, in which case we delete the first
462      * operator.
463      */
add(int id)464     boolean add(int id) {
465         int s = mExpr.size();
466         final int d = KeyMaps.digVal(id);
467         final boolean binary = KeyMaps.isBinary(id);
468         Token lastTok = s == 0 ? null : mExpr.get(s-1);
469         int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
470         // Quietly replace a trailing binary operator with another one, unless the second
471         // operator is minus, in which case we just allow it as a unary minus.
472         if (binary && !KeyMaps.isPrefix(id)) {
473             if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
474                     || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
475                 return false;
476             }
477             while (hasTrailingBinary()) {
478                 delete();
479             }
480             // s invalid and not used below.
481         }
482         final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
483         if (isConstPiece) {
484             // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
485             if (s == 0) {
486                 mExpr.add(new Constant());
487                 s++;
488             } else {
489                 Token last = mExpr.get(s-1);
490                 if(!(last instanceof Constant)) {
491                     if (last instanceof PreEval) {
492                         // Add explicit multiplication to avoid confusing display.
493                         mExpr.add(new Operator(R.id.op_mul));
494                         s++;
495                     }
496                     mExpr.add(new Constant());
497                     s++;
498                 }
499             }
500             return ((Constant)(mExpr.get(s-1))).add(id);
501         } else {
502             mExpr.add(new Operator(id));
503             return true;
504         }
505     }
506 
507     /**
508      * Add exponent to the constant at the end of the expression.
509      * Assumes there is a constant at the end of the expression.
510      */
addExponent(int exp)511     void addExponent(int exp) {
512         Token lastTok = mExpr.get(mExpr.size() - 1);
513         ((Constant) lastTok).addExponent(exp);
514     }
515 
516     /**
517      * Remove trailing op_add and op_sub operators.
518      */
removeTrailingAdditiveOperators()519     void removeTrailingAdditiveOperators() {
520         while (true) {
521             int s = mExpr.size();
522             if (s == 0) {
523                 break;
524             }
525             Token lastTok = mExpr.get(s-1);
526             if (!(lastTok instanceof Operator)) {
527                 break;
528             }
529             int lastOp = ((Operator) lastTok).id;
530             if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
531                 break;
532             }
533             delete();
534         }
535     }
536 
537     /**
538      * Append the contents of the argument expression.
539      * It is assumed that the argument expression will not change, and thus its pieces can be
540      * reused directly.
541      */
append(CalculatorExpr expr2)542     public void append(CalculatorExpr expr2) {
543         int s = mExpr.size();
544         int s2 = expr2.mExpr.size();
545         // Check that we're not concatenating Constant or PreEval tokens, since the result would
546         // look like a single constant, with very mysterious results for the user.
547         if (s != 0 && s2 != 0) {
548             Token last = mExpr.get(s-1);
549             Token first = expr2.mExpr.get(0);
550             if (!(first instanceof Operator) && !(last instanceof Operator)) {
551                 // Fudge it by adding an explicit multiplication.  We would have interpreted it as
552                 // such anyway, and this makes it recognizable to the user.
553                 mExpr.add(new Operator(R.id.op_mul));
554             }
555         }
556         for (int i = 0; i < s2; ++i) {
557             mExpr.add(expr2.mExpr.get(i));
558         }
559     }
560 
561     /**
562      * Undo the last key addition, if any.
563      * Or possibly remove a trailing exponent digit.
564      */
delete()565     public void delete() {
566         final int s = mExpr.size();
567         if (s == 0) {
568             return;
569         }
570         Token last = mExpr.get(s-1);
571         if (last instanceof Constant) {
572             Constant c = (Constant)last;
573             c.delete();
574             if (!c.isEmpty()) {
575                 return;
576             }
577         }
578         mExpr.remove(s-1);
579     }
580 
581     /**
582      * Remove all tokens from the expression.
583      */
clear()584     public void clear() {
585         mExpr.clear();
586     }
587 
isEmpty()588     public boolean isEmpty() {
589         return mExpr.isEmpty();
590     }
591 
592     /**
593      * Returns a logical deep copy of the CalculatorExpr.
594      * Operator and PreEval tokens are immutable, and thus aren't really copied.
595      */
clone()596     public Object clone() {
597         CalculatorExpr result = new CalculatorExpr();
598         for (Token t: mExpr) {
599             if (t instanceof Constant) {
600                 result.mExpr.add((Token)(((Constant)t).clone()));
601             } else {
602                 result.mExpr.add(t);
603             }
604         }
605         return result;
606     }
607 
608     // Am I just a constant?
isConstant()609     public boolean isConstant() {
610         if (mExpr.size() != 1) {
611             return false;
612         }
613         return mExpr.get(0) instanceof Constant;
614     }
615 
616     /**
617      * Return a new expression consisting of a single token representing the current pre-evaluated
618      * expression.
619      * The caller supplies the value, degree mode, and short string representation, which must
620      * have been previously computed.  Thus this is guaranteed to terminate reasonably quickly.
621      */
abbreviate(CR val, BoundedRational ratVal, boolean dm, String sr)622     public CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
623                               boolean dm, String sr) {
624         CalculatorExpr result = new CalculatorExpr();
625         Token t = new PreEval(val, ratVal, new CalculatorExpr((ArrayList<Token>) mExpr.clone()),
626                 new EvalContext(dm, mExpr.size()), sr);
627         result.mExpr.add(t);
628         return result;
629     }
630 
631     /**
632      * Internal evaluation functions return an EvalRet triple.
633      * We compute rational (BoundedRational) results when possible, both as a performance
634      * optimization, and to detect errors exactly when we can.
635      */
636     private static class EvalRet {
637         public int pos; // Next position (expression index) to be parsed.
638         public final CR val; // Constructive Real result of evaluating subexpression.
639         public final BoundedRational ratVal;  // Exact Rational value or null.
EvalRet(int p, CR v, BoundedRational r)640         EvalRet(int p, CR v, BoundedRational r) {
641             pos = p;
642             val = v;
643             ratVal = r;
644         }
645     }
646 
647     /**
648      * Internal evaluation functions take an EvalContext argument.
649      */
650     private static class EvalContext {
651         public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
652         public final boolean mDegreeMode;
653         // If we add any other kinds of evaluation modes, they go here.
EvalContext(boolean degreeMode, int len)654         EvalContext(boolean degreeMode, int len) {
655             mDegreeMode = degreeMode;
656             mPrefixLength = len;
657         }
EvalContext(DataInput in, int len)658         EvalContext(DataInput in, int len) throws IOException {
659             mDegreeMode = in.readBoolean();
660             mPrefixLength = len;
661         }
write(DataOutput out)662         void write(DataOutput out) throws IOException {
663             out.writeBoolean(mDegreeMode);
664         }
665     }
666 
667     private final CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
668 
669     private final CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
670 
toRadians(CR x, EvalContext ec)671     private CR toRadians(CR x, EvalContext ec) {
672         if (ec.mDegreeMode) {
673             return x.multiply(RADIANS_PER_DEGREE);
674         } else {
675             return x;
676         }
677     }
678 
fromRadians(CR x, EvalContext ec)679     private CR fromRadians(CR x, EvalContext ec) {
680         if (ec.mDegreeMode) {
681             return x.multiply(DEGREES_PER_RADIAN);
682         } else {
683             return x;
684         }
685     }
686 
687     // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
688     // error.  We expect that to be caught in eval below.
689 
isOperatorUnchecked(int i, int op)690     private boolean isOperatorUnchecked(int i, int op) {
691         Token t = mExpr.get(i);
692         if (!(t instanceof Operator)) {
693             return false;
694         }
695         return ((Operator)(t)).id == op;
696     }
697 
isOperator(int i, int op, EvalContext ec)698     private boolean isOperator(int i, int op, EvalContext ec) {
699         if (i >= ec.mPrefixLength) {
700             return false;
701         }
702         return isOperatorUnchecked(i, op);
703     }
704 
705     public static class SyntaxException extends Exception {
SyntaxException()706         public SyntaxException() {
707             super();
708         }
SyntaxException(String s)709         public SyntaxException(String s) {
710             super(s);
711         }
712     }
713 
714     // The following functions all evaluate some kind of expression starting at position i in
715     // mExpr in a specified evaluation context.  They return both the expression value (as
716     // constructive real and, if applicable, as BoundedRational) and the position of the next token
717     // that was not used as part of the evaluation.
718     // This is essentially a simple recursive descent parser combined with expression evaluation.
719 
evalUnary(int i, EvalContext ec)720     private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
721         final Token t = mExpr.get(i);
722         BoundedRational ratVal;
723         if (t instanceof Constant) {
724             Constant c = (Constant)t;
725             ratVal = c.toRational();
726             return new EvalRet(i+1, ratVal.CRValue(), ratVal);
727         }
728         if (t instanceof PreEval) {
729             final PreEval p = (PreEval)t;
730             return new EvalRet(i+1, p.value, p.ratValue);
731         }
732         EvalRet argVal;
733         switch(((Operator)(t)).id) {
734         case R.id.const_pi:
735             return new EvalRet(i+1, CR.PI, null);
736         case R.id.const_e:
737             return new EvalRet(i+1, REAL_E, null);
738         case R.id.op_sqrt:
739             // Seems to have highest precedence.
740             // Does not add implicit paren.
741             // Does seem to accept a leading minus.
742             if (isOperator(i+1, R.id.op_sub, ec)) {
743                 argVal = evalUnary(i+2, ec);
744                 ratVal = BoundedRational.sqrt(BoundedRational.negate(argVal.ratVal));
745                 if (ratVal != null) {
746                     break;
747                 }
748                 return new EvalRet(argVal.pos,
749                                    argVal.val.negate().sqrt(), null);
750             } else {
751                 argVal = evalUnary(i+1, ec);
752                 ratVal = BoundedRational.sqrt(argVal.ratVal);
753                 if (ratVal != null) {
754                     break;
755                 }
756                 return new EvalRet(argVal.pos, argVal.val.sqrt(), null);
757             }
758         case R.id.lparen:
759             argVal = evalExpr(i+1, ec);
760             if (isOperator(argVal.pos, R.id.rparen, ec)) {
761                 argVal.pos++;
762             }
763             return new EvalRet(argVal.pos, argVal.val, argVal.ratVal);
764         case R.id.fun_sin:
765             argVal = evalExpr(i+1, ec);
766             if (isOperator(argVal.pos, R.id.rparen, ec)) {
767                 argVal.pos++;
768             }
769             ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.ratVal)
770                                      : BoundedRational.sin(argVal.ratVal);
771             if (ratVal != null) {
772                 break;
773             }
774             return new EvalRet(argVal.pos, toRadians(argVal.val,ec).sin(), null);
775         case R.id.fun_cos:
776             argVal = evalExpr(i+1, ec);
777             if (isOperator(argVal.pos, R.id.rparen, ec)) {
778                 argVal.pos++;
779             }
780             ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.ratVal)
781                                      : BoundedRational.cos(argVal.ratVal);
782             if (ratVal != null) {
783                 break;
784             }
785             return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos(), null);
786         case R.id.fun_tan:
787             argVal = evalExpr(i+1, ec);
788             if (isOperator(argVal.pos, R.id.rparen, ec)) {
789                 argVal.pos++;
790             }
791             ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.ratVal)
792                                      : BoundedRational.tan(argVal.ratVal);
793             if (ratVal != null) {
794                 break;
795             }
796             CR argCR = toRadians(argVal.val, ec);
797             return new EvalRet(argVal.pos, argCR.sin().divide(argCR.cos()), null);
798         case R.id.fun_ln:
799             argVal = evalExpr(i+1, ec);
800             if (isOperator(argVal.pos, R.id.rparen, ec)) {
801                 argVal.pos++;
802             }
803             ratVal = BoundedRational.ln(argVal.ratVal);
804             if (ratVal != null) {
805                 break;
806             }
807             return new EvalRet(argVal.pos, argVal.val.ln(), null);
808         case R.id.fun_exp:
809             argVal = evalExpr(i+1, ec);
810             if (isOperator(argVal.pos, R.id.rparen, ec)) {
811                 argVal.pos++;
812             }
813             ratVal = BoundedRational.exp(argVal.ratVal);
814             if (ratVal != null) {
815                 break;
816             }
817             return new EvalRet(argVal.pos, argVal.val.exp(), null);
818         case R.id.fun_log:
819             argVal = evalExpr(i+1, ec);
820             if (isOperator(argVal.pos, R.id.rparen, ec)) {
821                 argVal.pos++;
822             }
823             ratVal = BoundedRational.log(argVal.ratVal);
824             if (ratVal != null) {
825                 break;
826             }
827             return new EvalRet(argVal.pos, argVal.val.ln().divide(CR.valueOf(10).ln()), null);
828         case R.id.fun_arcsin:
829             argVal = evalExpr(i+1, ec);
830             if (isOperator(argVal.pos, R.id.rparen, ec)) {
831                 argVal.pos++;
832             }
833             ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.ratVal)
834                                      : BoundedRational.asin(argVal.ratVal);
835             if (ratVal != null) {
836                 break;
837             }
838             return new EvalRet(argVal.pos,
839                     fromRadians(UnaryCRFunction.asinFunction.execute(argVal.val),ec), null);
840         case R.id.fun_arccos:
841             argVal = evalExpr(i+1, ec);
842             if (isOperator(argVal.pos, R.id.rparen, ec)) {
843                 argVal.pos++;
844             }
845             ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.ratVal)
846                                      : BoundedRational.acos(argVal.ratVal);
847             if (ratVal != null) {
848                 break;
849             }
850             return new EvalRet(argVal.pos,
851                     fromRadians(UnaryCRFunction.acosFunction.execute(argVal.val),ec), null);
852         case R.id.fun_arctan:
853             argVal = evalExpr(i+1, ec);
854             if (isOperator(argVal.pos, R.id.rparen, ec)) {
855                 argVal.pos++;
856             }
857             ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.ratVal)
858                                      : BoundedRational.atan(argVal.ratVal);
859             if (ratVal != null) {
860                 break;
861             }
862             return new EvalRet(argVal.pos,
863                     fromRadians(UnaryCRFunction.atanFunction.execute(argVal.val),ec), null);
864         default:
865             throw new SyntaxException("Unrecognized token in expression");
866         }
867         // We have a rational value.
868         return new EvalRet(argVal.pos, ratVal.CRValue(), ratVal);
869     }
870 
871     /**
872      * Compute an integral power of a constructive real.
873      * Unlike the "general" case using logarithms, this handles a negative base.
874      */
pow(CR base, BigInteger exp)875     private static CR pow(CR base, BigInteger exp) {
876         if (exp.compareTo(BigInteger.ZERO) < 0) {
877             return pow(base, exp.negate()).inverse();
878         }
879         if (exp.equals(BigInteger.ONE)) {
880             return base;
881         }
882         if (exp.and(BigInteger.ONE).intValue() == 1) {
883             return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
884         }
885         if (exp.equals(BigInteger.ZERO)) {
886             return CR.valueOf(1);
887         }
888         CR tmp = pow(base, exp.shiftRight(1));
889         return tmp.multiply(tmp);
890     }
891 
892     // Number of bits past binary point to test for integer-ness.
893     private static final int TEST_PREC = -100;
894     private static final BigInteger MASK =
895             BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
896     private static final CR REAL_E = CR.valueOf(1).exp();
897     private static final CR REAL_ONE_HUNDREDTH = CR.valueOf(100).inverse();
898     private static final BoundedRational RATIONAL_ONE_HUNDREDTH = new BoundedRational(1,100);
isApprInt(CR x)899     private static boolean isApprInt(CR x) {
900         BigInteger appr = x.get_appr(TEST_PREC);
901         return appr.and(MASK).signum() == 0;
902     }
903 
evalSuffix(int i, EvalContext ec)904     private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
905         final EvalRet tmp = evalUnary(i, ec);
906         int cpos = tmp.pos;
907         CR crVal = tmp.val;
908         BoundedRational ratVal = tmp.ratVal;
909         boolean isFact;
910         boolean isSquared = false;
911         while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
912                 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
913                 isOperator(cpos, R.id.op_pct, ec)) {
914             if (isFact) {
915                 if (ratVal == null) {
916                     // Assume it was an integer, but we didn't figure it out.
917                     // KitKat may have used the Gamma function.
918                     if (!isApprInt(crVal)) {
919                         throw new ArithmeticException("factorial(non-integer)");
920                     }
921                     ratVal = new BoundedRational(crVal.BigIntegerValue());
922                 }
923                 ratVal = BoundedRational.fact(ratVal);
924                 crVal = ratVal.CRValue();
925             } else if (isSquared) {
926                 ratVal = BoundedRational.multiply(ratVal, ratVal);
927                 if (ratVal == null) {
928                     crVal = crVal.multiply(crVal);
929                 } else {
930                     crVal = ratVal.CRValue();
931                 }
932             } else /* percent */ {
933                 ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH);
934                 if (ratVal == null) {
935                     crVal = crVal.multiply(REAL_ONE_HUNDREDTH);
936                 } else {
937                     crVal = ratVal.CRValue();
938                 }
939             }
940             ++cpos;
941         }
942         return new EvalRet(cpos, crVal, ratVal);
943     }
944 
evalFactor(int i, EvalContext ec)945     private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
946         final EvalRet result1 = evalSuffix(i, ec);
947         int cpos = result1.pos;  // current position
948         CR crVal = result1.val;   // value so far
949         BoundedRational ratVal = result1.ratVal;  // int value so far
950         if (isOperator(cpos, R.id.op_pow, ec)) {
951             final EvalRet exp = evalSignedFactor(cpos + 1, ec);
952             cpos = exp.pos;
953             // Try completely rational evaluation first.
954             ratVal = BoundedRational.pow(ratVal, exp.ratVal);
955             if (ratVal != null) {
956                 return new EvalRet(cpos, ratVal.CRValue(), ratVal);
957             }
958             // Power with integer exponent is defined for negative base.
959             // Thus we handle that case separately.
960             // We punt if the exponent is an integer computed from irrational
961             // values.  That wouldn't work reliably with floating point either.
962             BigInteger int_exp = BoundedRational.asBigInteger(exp.ratVal);
963             if (int_exp != null) {
964                 crVal = pow(crVal, int_exp);
965             } else {
966                 crVal = crVal.ln().multiply(exp.val).exp();
967             }
968             ratVal = null;
969         }
970         return new EvalRet(cpos, crVal, ratVal);
971     }
972 
evalSignedFactor(int i, EvalContext ec)973     private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
974         final boolean negative = isOperator(i, R.id.op_sub, ec);
975         int cpos = negative ? i + 1 : i;
976         EvalRet tmp = evalFactor(cpos, ec);
977         cpos = tmp.pos;
978         CR crVal = negative ? tmp.val.negate() : tmp.val;
979         BoundedRational ratVal = negative ? BoundedRational.negate(tmp.ratVal)
980                                          : tmp.ratVal;
981         return new EvalRet(cpos, crVal, ratVal);
982     }
983 
canStartFactor(int i)984     private boolean canStartFactor(int i) {
985         if (i >= mExpr.size()) return false;
986         Token t = mExpr.get(i);
987         if (!(t instanceof Operator)) return true;
988         int id = ((Operator)(t)).id;
989         if (KeyMaps.isBinary(id)) return false;
990         switch (id) {
991             case R.id.op_fact:
992             case R.id.rparen:
993                 return false;
994             default:
995                 return true;
996         }
997     }
998 
evalTerm(int i, EvalContext ec)999     private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
1000         EvalRet tmp = evalSignedFactor(i, ec);
1001         boolean is_mul = false;
1002         boolean is_div = false;
1003         int cpos = tmp.pos;   // Current position in expression.
1004         CR crVal = tmp.val;    // Current value.
1005         BoundedRational ratVal = tmp.ratVal; // Current rational value.
1006         while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
1007                || (is_div = isOperator(cpos, R.id.op_div, ec))
1008                || canStartFactor(cpos)) {
1009             if (is_mul || is_div) ++cpos;
1010             tmp = evalSignedFactor(cpos, ec);
1011             if (is_div) {
1012                 ratVal = BoundedRational.divide(ratVal, tmp.ratVal);
1013                 if (ratVal == null) {
1014                     crVal = crVal.divide(tmp.val);
1015                 } else {
1016                     crVal = ratVal.CRValue();
1017                 }
1018             } else {
1019                 ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
1020                 if (ratVal == null) {
1021                     crVal = crVal.multiply(tmp.val);
1022                 } else {
1023                     crVal = ratVal.CRValue();
1024                 }
1025             }
1026             cpos = tmp.pos;
1027             is_mul = is_div = false;
1028         }
1029         return new EvalRet(cpos, crVal, ratVal);
1030     }
1031 
1032     /**
1033      * Is the subexpression starting at pos a simple percent constant?
1034      * This is used to recognize exppressions like 200+10%, which we handle specially.
1035      * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
1036      * by either nothing or an additive operator.
1037      * Note that we are intentionally far more restrictive in recognizing such expressions than
1038      * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
1039      * When in doubt, we fall back to the the naive interpretation of % as 1/100.
1040      * Note that 100+(10)% yields 100.1 while 100+10% yields 110.  This may be controversial,
1041      * but is consistent with Google web search.
1042      */
isPercent(int pos)1043     private boolean isPercent(int pos) {
1044         if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
1045             return false;
1046         }
1047         Token number = mExpr.get(pos);
1048         if (number instanceof Operator) {
1049             return false;
1050         }
1051         if (mExpr.size() == pos + 2) {
1052             return true;
1053         }
1054         if (!(mExpr.get(pos + 2) instanceof Operator)) {
1055             return false;
1056         }
1057         Operator op = (Operator) mExpr.get(pos + 2);
1058         return op.id == R.id.op_add || op.id == R.id.op_sub;
1059     }
1060 
1061     /**
1062      * Compute the multiplicative factor corresponding to an N% addition or subtraction.
1063      * @param pos position of Constant or PreEval expression token corresponding to N
1064      * @param isSubtraction this is a subtraction, as opposed to addition
1065      * @param ec usable evaluation contex; only length matters
1066      * @return Rational and CR values; position is pos + 2, i.e. after percent sign
1067      */
getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)1068     private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
1069             throws SyntaxException {
1070         EvalRet tmp = evalUnary(pos, ec);
1071         BoundedRational ratVal = isSubtraction ? BoundedRational.negate(tmp.ratVal)
1072                 : tmp.ratVal;
1073         CR crVal = isSubtraction ? tmp.val.negate() : tmp.val;
1074         ratVal = BoundedRational.add(BoundedRational.ONE,
1075                 BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH));
1076         if (ratVal == null) {
1077             crVal = CR.ONE.add(crVal.multiply(REAL_ONE_HUNDREDTH));
1078         } else {
1079             crVal = ratVal.CRValue();
1080         }
1081         return new EvalRet(pos + 2 /* after percent sign */, crVal, ratVal);
1082     }
1083 
evalExpr(int i, EvalContext ec)1084     private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
1085         EvalRet tmp = evalTerm(i, ec);
1086         boolean is_plus;
1087         int cpos = tmp.pos;
1088         CR crVal = tmp.val;
1089         BoundedRational ratVal = tmp.ratVal;
1090         while ((is_plus = isOperator(cpos, R.id.op_add, ec))
1091                || isOperator(cpos, R.id.op_sub, ec)) {
1092             if (isPercent(cpos + 1)) {
1093                 tmp = getPercentFactor(cpos + 1, !is_plus, ec);
1094                 ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
1095                 if (ratVal == null) {
1096                     crVal = crVal.multiply(tmp.val);
1097                 } else {
1098                     crVal = ratVal.CRValue();
1099                 }
1100             } else {
1101                 tmp = evalTerm(cpos + 1, ec);
1102                 if (is_plus) {
1103                     ratVal = BoundedRational.add(ratVal, tmp.ratVal);
1104                     if (ratVal == null) {
1105                         crVal = crVal.add(tmp.val);
1106                     } else {
1107                         crVal = ratVal.CRValue();
1108                     }
1109                 } else {
1110                     ratVal = BoundedRational.subtract(ratVal, tmp.ratVal);
1111                     if (ratVal == null) {
1112                         crVal = crVal.subtract(tmp.val);
1113                     } else {
1114                         crVal = ratVal.CRValue();
1115                     }
1116                 }
1117             }
1118             cpos = tmp.pos;
1119         }
1120         return new EvalRet(cpos, crVal, ratVal);
1121     }
1122 
1123     /**
1124      * Externally visible evaluation result.
1125      */
1126     public static class EvalResult {
1127         public final CR val;
1128         public final BoundedRational ratVal;
EvalResult(CR v, BoundedRational rv)1129         EvalResult (CR v, BoundedRational rv) {
1130             val = v;
1131             ratVal = rv;
1132         }
1133     }
1134 
1135     /**
1136      * Return the starting position of the sequence of trailing binary operators.
1137      */
trailingBinaryOpsStart()1138     private int trailingBinaryOpsStart() {
1139         int result = mExpr.size();
1140         while (result > 0) {
1141             Token last = mExpr.get(result - 1);
1142             if (!(last instanceof Operator)) break;
1143             Operator o = (Operator)last;
1144             if (!KeyMaps.isBinary(o.id)) break;
1145             --result;
1146         }
1147         return result;
1148     }
1149 
1150     /**
1151      * Is the current expression worth evaluating?
1152      */
hasInterestingOps()1153     public boolean hasInterestingOps() {
1154         int last = trailingBinaryOpsStart();
1155         int first = 0;
1156         if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
1157             // Leading minus is not by itself interesting.
1158             first++;
1159         }
1160         for (int i = first; i < last; ++i) {
1161             Token t1 = mExpr.get(i);
1162             if (t1 instanceof Operator
1163                     || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
1164                 return true;
1165             }
1166         }
1167         return false;
1168     }
1169 
1170     /**
1171      * Evaluate the expression excluding trailing binary operators.
1172      * Errors result in exceptions, most of which are unchecked.  Should not be called
1173      * concurrently with modification of the expression.  May take a very long time; avoid calling
1174      * from UI thread.
1175      *
1176      * @param degreeMode use degrees rather than radians
1177      */
eval(boolean degreeMode)1178     EvalResult eval(boolean degreeMode) throws SyntaxException
1179                         // And unchecked exceptions thrown by CR
1180                         // and BoundedRational.
1181     {
1182         try {
1183             // We currently never include trailing binary operators, but include other trailing
1184             // operators.  Thus we usually, but not always, display results for prefixes of valid
1185             // expressions, and don't generate an error where we previously displayed an instant
1186             // result.  This reflects the Android L design.
1187             int prefixLen = trailingBinaryOpsStart();
1188             EvalContext ec = new EvalContext(degreeMode, prefixLen);
1189             EvalRet res = evalExpr(0, ec);
1190             if (res.pos != prefixLen) {
1191                 throw new SyntaxException("Failed to parse full expression");
1192             }
1193             return new EvalResult(res.val, res.ratVal);
1194         } catch (IndexOutOfBoundsException e) {
1195             throw new SyntaxException("Unexpected expression end");
1196         }
1197     }
1198 
1199     // Produce a string representation of the expression itself
toSpannableStringBuilder(Context context)1200     SpannableStringBuilder toSpannableStringBuilder(Context context) {
1201         SpannableStringBuilder ssb = new SpannableStringBuilder();
1202         for (Token t: mExpr) {
1203             ssb.append(t.toCharSequence(context));
1204         }
1205         return ssb;
1206     }
1207 }
1208