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