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
17package com.android.calculator2;
18
19
20import com.hp.creals.CR;
21import com.hp.creals.UnaryCRFunction;
22
23import android.content.Context;
24import android.text.SpannableString;
25import android.text.SpannableStringBuilder;
26import android.text.Spanned;
27import android.text.style.TtsSpan;
28import android.text.style.TtsSpan.TextBuilder;
29import android.util.Log;
30
31import java.math.BigInteger;
32import java.io.DataInput;
33import java.io.DataOutput;
34import java.io.IOException;
35import java.util.ArrayList;
36import java.util.HashMap;
37import 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 */
52class 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 {
63        abstract TokenKind kind();
64
65        /**
66         * Write kind as Byte followed by data needed by subclass constructor.
67         */
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         */
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
84        Operator(int resId) {
85            id = resId;
86        }
87        Operator(DataInput in) throws IOException {
88            id = in.readInt();
89        }
90        @Override
91        void write(DataOutput out) throws IOException {
92            out.writeByte(TokenKind.OPERATOR.ordinal());
93            out.writeInt(id);
94        }
95        @Override
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
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
121        Constant() {
122            mWhole = "";
123            mFraction = "";
124            mSawDecimal = false;
125            mExponent = 0;
126        };
127
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
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.
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
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         */
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
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
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         */
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
245        public CharSequence toCharSequence(Context context) {
246            return toString();
247        }
248
249        @Override
250        public TokenKind kind() {
251            return TokenKind.CONSTANT;
252        }
253
254        // Override clone to make it public
255        @Override
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     */
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     */
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.
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
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        }
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
374        public CharSequence toCharSequence(Context context) {
375            return KeyMaps.translateResult(mShortRep);
376        }
377        @Override
378        public TokenKind kind() {
379            return TokenKind.PRE_EVAL;
380        }
381        public boolean hasEllipsis() {
382            return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
383        }
384    }
385
386    /**
387     * Read token from in.
388     */
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
402    CalculatorExpr() {
403        mExpr = new ArrayList<Token>();
404    }
405
406    private CalculatorExpr(ArrayList<Token> expr) {
407        mExpr = expr;
408    }
409
410    /**
411     * Construct CalculatorExpr, by reading it from in.
412     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
584    public void clear() {
585        mExpr.clear();
586    }
587
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     */
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?
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     */
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.
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.
654        EvalContext(boolean degreeMode, int len) {
655            mDegreeMode = degreeMode;
656            mPrefixLength = len;
657        }
658        EvalContext(DataInput in, int len) throws IOException {
659            mDegreeMode = in.readBoolean();
660            mPrefixLength = len;
661        }
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
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
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
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
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 {
706        public SyntaxException() {
707            super();
708        }
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
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     */
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);
899    private static boolean isApprInt(CR x) {
900        BigInteger appr = x.get_appr(TEST_PREC);
901        return appr.and(MASK).signum() == 0;
902    }
903
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
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
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
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
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     */
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     */
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
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;
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     */
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     */
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     */
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
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