summaryrefslogtreecommitdiff
path: root/src/com/android/calculator2/CalculatorExpr.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/calculator2/CalculatorExpr.java')
-rw-r--r--src/com/android/calculator2/CalculatorExpr.java1118
1 files changed, 0 insertions, 1118 deletions
diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java
deleted file mode 100644
index 75ab1c9..0000000
--- a/src/com/android/calculator2/CalculatorExpr.java
+++ /dev/null
@@ -1,1118 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.calculator2;
-
-import android.content.Context;
-import android.text.SpannableString;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.TtsSpan;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-
-/**
- * A mathematical expression represented as a sequence of "tokens".
- * Many tokens are represented by button ids for the corresponding operator.
- * A token may also represent the result of a previously evaluated expression.
- * The add() method adds a token to the end of the expression. The delete method() removes one.
- * Clear() deletes the entire expression contents. Eval() evaluates the expression,
- * producing a UnifiedReal result.
- * Expressions are parsed only during evaluation; no explicit parse tree is maintained.
- *
- * The write() method is used to save the current expression. Note that neither UnifiedReal
- * nor the underlying CR provide a serialization facility. Thus we save all previously
- * computed values by writing out the expression that was used to compute them, and reevaluate
- * when reading it back in.
- */
-class CalculatorExpr {
- /**
- * An interface for resolving expression indices in embedded subexpressions to
- * the associated CalculatorExpr, and associating a UnifiedReal result with it.
- * All methods are thread-safe in the strong sense; they may be called asynchronously
- * at any time from any thread.
- */
- public interface ExprResolver {
- /*
- * Retrieve the expression corresponding to index.
- */
- CalculatorExpr getExpr(long index);
- /*
- * Retrieve the degree mode associated with the expression at index i.
- */
- boolean getDegreeMode(long index);
- /*
- * Retrieve the stored result for the expression at index, or return null.
- */
- UnifiedReal getResult(long index);
- /*
- * Atomically test for an existing result, and set it if there was none.
- * Return the prior result if there was one, or the new one if there was not.
- * May only be called after getExpr.
- */
- UnifiedReal putResultIfAbsent(long index, UnifiedReal result);
- }
-
- private ArrayList<Token> mExpr; // The actual representation
- // as a list of tokens. Constant
- // tokens are always nonempty.
-
- private static enum TokenKind { CONSTANT, OPERATOR, PRE_EVAL };
- private static TokenKind[] tokenKindValues = TokenKind.values();
- private final static BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
- private final static BigInteger BIG_BILLION = BigInteger.valueOf(1000000000);
-
- private static abstract class Token {
- abstract TokenKind kind();
-
- /**
- * Write token as either a very small Byte containing the TokenKind,
- * followed by data needed by subclass constructor,
- * or as a byte >= 0x20 directly describing the OPERATOR token.
- */
- abstract void write(DataOutput out) throws IOException;
-
- /**
- * Return a textual representation of the token.
- * The result is suitable for either display as part od the formula or TalkBack use.
- * It may be a SpannableString that includes added TalkBack information.
- * @param context context used for converting button ids to strings
- */
- abstract CharSequence toCharSequence(Context context);
- }
-
- /**
- * Representation of an operator token
- */
- private static class Operator extends Token {
- // TODO: rename id.
- public final int id; // We use the button resource id
- Operator(int resId) {
- id = resId;
- }
- Operator(byte op) throws IOException {
- id = KeyMaps.fromByte(op);
- }
- @Override
- void write(DataOutput out) throws IOException {
- out.writeByte(KeyMaps.toByte(id));
- }
- @Override
- public CharSequence toCharSequence(Context context) {
- String desc = KeyMaps.toDescriptiveString(context, id);
- if (desc != null) {
- SpannableString result = new SpannableString(KeyMaps.toString(context, id));
- Object descSpan = new TtsSpan.TextBuilder(desc).build();
- result.setSpan(descSpan, 0, result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- return result;
- } else {
- return KeyMaps.toString(context, id);
- }
- }
- @Override
- TokenKind kind() { return TokenKind.OPERATOR; }
- }
-
- /**
- * Representation of a (possibly incomplete) numerical constant.
- * Supports addition and removal of trailing characters; hence mutable.
- */
- private static class Constant extends Token implements Cloneable {
- private boolean mSawDecimal;
- private String mWhole; // String preceding decimal point.
- private String mFraction; // String after decimal point.
- private int mExponent; // Explicit exponent, only generated through addExponent.
- private static int SAW_DECIMAL = 0x1;
- private static int HAS_EXPONENT = 0x2;
-
- Constant() {
- mWhole = "";
- mFraction = "";
- // mSawDecimal = false;
- // mExponent = 0;
- };
-
- Constant(DataInput in) throws IOException {
- mWhole = in.readUTF();
- byte flags = in.readByte();
- if ((flags & SAW_DECIMAL) != 0) {
- mSawDecimal = true;
- mFraction = in.readUTF();
- } else {
- // mSawDecimal = false;
- mFraction = "";
- }
- if ((flags & HAS_EXPONENT) != 0) {
- mExponent = in.readInt();
- }
- }
-
- @Override
- void write(DataOutput out) throws IOException {
- byte flags = (byte)((mSawDecimal ? SAW_DECIMAL : 0)
- | (mExponent != 0 ? HAS_EXPONENT : 0));
- out.writeByte(TokenKind.CONSTANT.ordinal());
- out.writeUTF(mWhole);
- out.writeByte(flags);
- if (mSawDecimal) {
- out.writeUTF(mFraction);
- }
- if (mExponent != 0) {
- out.writeInt(mExponent);
- }
- }
-
- // Given a button press, append corresponding digit.
- // We assume id is a digit or decimal point.
- // Just return false if this was the second (or later) decimal point
- // in this constant.
- // Assumes that this constant does not have an exponent.
- public boolean add(int id) {
- if (id == R.id.dec_point) {
- if (mSawDecimal || mExponent != 0) return false;
- mSawDecimal = true;
- return true;
- }
- int val = KeyMaps.digVal(id);
- if (mExponent != 0) {
- if (Math.abs(mExponent) <= 10000) {
- if (mExponent > 0) {
- mExponent = 10 * mExponent + val;
- } else {
- mExponent = 10 * mExponent - val;
- }
- return true;
- } else { // Too large; refuse
- return false;
- }
- }
- if (mSawDecimal) {
- mFraction += val;
- } else {
- mWhole += val;
- }
- return true;
- }
-
- public void addExponent(int exp) {
- // Note that adding a 0 exponent is a no-op. That's OK.
- mExponent = exp;
- }
-
- /**
- * Undo the last add or remove last exponent digit.
- * Assumes the constant is nonempty.
- */
- public void delete() {
- if (mExponent != 0) {
- mExponent /= 10;
- // Once zero, it can only be added back with addExponent.
- } else if (!mFraction.isEmpty()) {
- mFraction = mFraction.substring(0, mFraction.length() - 1);
- } else if (mSawDecimal) {
- mSawDecimal = false;
- } else {
- mWhole = mWhole.substring(0, mWhole.length() - 1);
- }
- }
-
- public boolean isEmpty() {
- return (mSawDecimal == false && mWhole.isEmpty());
- }
-
- /**
- * Produce human-readable string representation of constant, as typed.
- * We do add digit grouping separators to the whole number, even if not typed.
- * Result is internationalized.
- */
- @Override
- public String toString() {
- String result;
- if (mExponent != 0) {
- result = mWhole;
- } else {
- result = StringUtils.addCommas(mWhole, 0, mWhole.length());
- }
- if (mSawDecimal) {
- result += '.';
- result += mFraction;
- }
- if (mExponent != 0) {
- result += "E" + mExponent;
- }
- return KeyMaps.translateResult(result);
- }
-
- /**
- * Return BoundedRational representation of constant, if well-formed.
- * Result is never null.
- */
- public BoundedRational toRational() throws SyntaxException {
- String whole = mWhole;
- if (whole.isEmpty()) {
- if (mFraction.isEmpty()) {
- // Decimal point without digits.
- throw new SyntaxException();
- } else {
- whole = "0";
- }
- }
- BigInteger num = new BigInteger(whole + mFraction);
- BigInteger den = BigInteger.TEN.pow(mFraction.length());
- if (mExponent > 0) {
- num = num.multiply(BigInteger.TEN.pow(mExponent));
- }
- if (mExponent < 0) {
- den = den.multiply(BigInteger.TEN.pow(-mExponent));
- }
- return new BoundedRational(num, den);
- }
-
- @Override
- public CharSequence toCharSequence(Context context) {
- return toString();
- }
-
- @Override
- public TokenKind kind() {
- return TokenKind.CONSTANT;
- }
-
- // Override clone to make it public
- @Override
- public Object clone() {
- Constant result = new Constant();
- result.mWhole = mWhole;
- result.mFraction = mFraction;
- result.mSawDecimal = mSawDecimal;
- result.mExponent = mExponent;
- return result;
- }
- }
-
- /**
- * The "token" class for previously evaluated subexpressions.
- * We treat previously evaluated subexpressions as tokens. These are inserted when we either
- * continue an expression after evaluating some of it, or copy an expression and paste it back
- * in.
- * This only contains enough information to allow us to display the expression in a
- * formula, or reevaluate the expression with the aid of an ExprResolver; we no longer
- * cache the result. The expression corresponding to the index can be obtained through
- * the ExprResolver, which looks it up in a subexpression database.
- * The representation includes a UnifiedReal value. In order to
- * support saving and restoring, we also include the underlying expression itself, and the
- * context (currently just degree mode) used to evaluate it. The short string representation
- * is also stored in order to avoid potentially expensive recomputation in the UI thread.
- */
- private static class PreEval extends Token {
- public final long mIndex;
- private final String mShortRep; // Not internationalized.
- PreEval(long index, String shortRep) {
- mIndex = index;
- mShortRep = shortRep;
- }
- @Override
- // This writes out only a shallow representation of the result, without
- // information about subexpressions. To write out a deep representation, we
- // find referenced subexpressions, and iteratively write those as well.
- public void write(DataOutput out) throws IOException {
- out.writeByte(TokenKind.PRE_EVAL.ordinal());
- if (mIndex > Integer.MAX_VALUE || mIndex < Integer.MIN_VALUE) {
- // This would be millions of expressions per day for the life of the device.
- throw new AssertionError("Expression index too big");
- }
- out.writeInt((int)mIndex);
- out.writeUTF(mShortRep);
- }
- PreEval(DataInput in) throws IOException {
- mIndex = in.readInt();
- mShortRep = in.readUTF();
- }
- @Override
- public CharSequence toCharSequence(Context context) {
- return KeyMaps.translateResult(mShortRep);
- }
- @Override
- public TokenKind kind() {
- return TokenKind.PRE_EVAL;
- }
- public boolean hasEllipsis() {
- return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
- }
- }
-
- /**
- * Read token from in.
- */
- public static Token newToken(DataInput in) throws IOException {
- byte kindByte = in.readByte();
- if (kindByte < 0x20) {
- TokenKind kind = tokenKindValues[kindByte];
- switch(kind) {
- case CONSTANT:
- return new Constant(in);
- case PRE_EVAL:
- PreEval pe = new PreEval(in);
- if (pe.mIndex == -1) {
- // Database corrupted by earlier bug.
- // Return a conspicuously wrong placeholder that won't lead to a crash.
- Constant result = new Constant();
- result.add(R.id.dec_point);
- return result;
- } else {
- return pe;
- }
- default: throw new IOException("Bad save file format");
- }
- } else {
- return new Operator(kindByte);
- }
- }
-
- CalculatorExpr() {
- mExpr = new ArrayList<Token>();
- }
-
- private CalculatorExpr(ArrayList<Token> expr) {
- mExpr = expr;
- }
-
- /**
- * Construct CalculatorExpr, by reading it from in.
- */
- CalculatorExpr(DataInput in) throws IOException {
- mExpr = new ArrayList<Token>();
- int size = in.readInt();
- for (int i = 0; i < size; ++i) {
- mExpr.add(newToken(in));
- }
- }
-
- /**
- * Write this expression to out.
- */
- public void write(DataOutput out) throws IOException {
- int size = mExpr.size();
- out.writeInt(size);
- for (int i = 0; i < size; ++i) {
- mExpr.get(i).write(out);
- }
- }
-
- /**
- * Use write() above to generate a byte array containing a serialized representation of
- * this expression.
- */
- public byte[] toBytes() {
- ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
- try (DataOutputStream out = new DataOutputStream(byteArrayStream)) {
- write(out);
- } catch (IOException e) {
- // Impossible; No IO involved.
- throw new AssertionError("Impossible IO exception", e);
- }
- return byteArrayStream.toByteArray();
- }
-
- /**
- * Does this expression end with a numeric constant?
- * As opposed to an operator or preevaluated expression.
- */
- boolean hasTrailingConstant() {
- int s = mExpr.size();
- if (s == 0) {
- return false;
- }
- Token t = mExpr.get(s-1);
- return t instanceof Constant;
- }
-
- /**
- * Does this expression end with a binary operator?
- */
- boolean hasTrailingBinary() {
- int s = mExpr.size();
- if (s == 0) return false;
- Token t = mExpr.get(s-1);
- if (!(t instanceof Operator)) return false;
- Operator o = (Operator)t;
- return (KeyMaps.isBinary(o.id));
- }
-
- /**
- * Append press of button with given id to expression.
- * If the insertion would clearly result in a syntax error, either just return false
- * and do nothing, or make an adjustment to avoid the problem. We do the latter only
- * for unambiguous consecutive binary operators, in which case we delete the first
- * operator.
- */
- boolean add(int id) {
- int s = mExpr.size();
- final int d = KeyMaps.digVal(id);
- final boolean binary = KeyMaps.isBinary(id);
- Token lastTok = s == 0 ? null : mExpr.get(s-1);
- int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
- // Quietly replace a trailing binary operator with another one, unless the second
- // operator is minus, in which case we just allow it as a unary minus.
- if (binary && !KeyMaps.isPrefix(id)) {
- if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
- || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
- return false;
- }
- while (hasTrailingBinary()) {
- delete();
- }
- // s invalid and not used below.
- }
- final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
- if (isConstPiece) {
- // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
- if (s == 0) {
- mExpr.add(new Constant());
- s++;
- } else {
- Token last = mExpr.get(s-1);
- if(!(last instanceof Constant)) {
- if (last instanceof PreEval) {
- // Add explicit multiplication to avoid confusing display.
- mExpr.add(new Operator(R.id.op_mul));
- s++;
- }
- mExpr.add(new Constant());
- s++;
- }
- }
- return ((Constant)(mExpr.get(s-1))).add(id);
- } else {
- mExpr.add(new Operator(id));
- return true;
- }
- }
-
- /**
- * Add exponent to the constant at the end of the expression.
- * Assumes there is a constant at the end of the expression.
- */
- void addExponent(int exp) {
- Token lastTok = mExpr.get(mExpr.size() - 1);
- ((Constant) lastTok).addExponent(exp);
- }
-
- /**
- * Remove trailing op_add and op_sub operators.
- */
- void removeTrailingAdditiveOperators() {
- while (true) {
- int s = mExpr.size();
- if (s == 0) {
- break;
- }
- Token lastTok = mExpr.get(s-1);
- if (!(lastTok instanceof Operator)) {
- break;
- }
- int lastOp = ((Operator) lastTok).id;
- if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
- break;
- }
- delete();
- }
- }
-
- /**
- * Append the contents of the argument expression.
- * It is assumed that the argument expression will not change, and thus its pieces can be
- * reused directly.
- */
- public void append(CalculatorExpr expr2) {
- int s = mExpr.size();
- int s2 = expr2.mExpr.size();
- // Check that we're not concatenating Constant or PreEval tokens, since the result would
- // look like a single constant, with very mysterious results for the user.
- if (s != 0 && s2 != 0) {
- Token last = mExpr.get(s-1);
- Token first = expr2.mExpr.get(0);
- if (!(first instanceof Operator) && !(last instanceof Operator)) {
- // Fudge it by adding an explicit multiplication. We would have interpreted it as
- // such anyway, and this makes it recognizable to the user.
- mExpr.add(new Operator(R.id.op_mul));
- }
- }
- for (int i = 0; i < s2; ++i) {
- mExpr.add(expr2.mExpr.get(i));
- }
- }
-
- /**
- * Undo the last key addition, if any.
- * Or possibly remove a trailing exponent digit.
- */
- public void delete() {
- final int s = mExpr.size();
- if (s == 0) {
- return;
- }
- Token last = mExpr.get(s-1);
- if (last instanceof Constant) {
- Constant c = (Constant)last;
- c.delete();
- if (!c.isEmpty()) {
- return;
- }
- }
- mExpr.remove(s-1);
- }
-
- /**
- * Remove all tokens from the expression.
- */
- public void clear() {
- mExpr.clear();
- }
-
- public boolean isEmpty() {
- return mExpr.isEmpty();
- }
-
- /**
- * Returns a logical deep copy of the CalculatorExpr.
- * Operator and PreEval tokens are immutable, and thus aren't really copied.
- */
- public Object clone() {
- CalculatorExpr result = new CalculatorExpr();
- for (Token t : mExpr) {
- if (t instanceof Constant) {
- result.mExpr.add((Token)(((Constant)t).clone()));
- } else {
- result.mExpr.add(t);
- }
- }
- return result;
- }
-
- // Am I just a constant?
- public boolean isConstant() {
- if (mExpr.size() != 1) {
- return false;
- }
- return mExpr.get(0) instanceof Constant;
- }
-
- /**
- * Return a new expression consisting of a single token representing the current pre-evaluated
- * expression.
- * The caller supplies the expression index and short string representation.
- * The expression must have been previously evaluated.
- */
- public CalculatorExpr abbreviate(long index, String sr) {
- CalculatorExpr result = new CalculatorExpr();
- @SuppressWarnings("unchecked")
- Token t = new PreEval(index, sr);
- result.mExpr.add(t);
- return result;
- }
-
- /**
- * Internal evaluation functions return an EvalRet pair.
- * We compute rational (BoundedRational) results when possible, both as a performance
- * optimization, and to detect errors exactly when we can.
- */
- private static class EvalRet {
- public int pos; // Next position (expression index) to be parsed.
- public final UnifiedReal val; // Constructive Real result of evaluating subexpression.
- EvalRet(int p, UnifiedReal v) {
- pos = p;
- val = v;
- }
- }
-
- /**
- * Internal evaluation functions take an EvalContext argument.
- */
- private static class EvalContext {
- public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
- public final boolean mDegreeMode;
- public final ExprResolver mExprResolver; // Reconstructed, not saved.
- // If we add any other kinds of evaluation modes, they go here.
- EvalContext(boolean degreeMode, int len, ExprResolver er) {
- mDegreeMode = degreeMode;
- mPrefixLength = len;
- mExprResolver = er;
- }
- EvalContext(DataInput in, int len, ExprResolver er) throws IOException {
- mDegreeMode = in.readBoolean();
- mPrefixLength = len;
- mExprResolver = er;
- }
- void write(DataOutput out) throws IOException {
- out.writeBoolean(mDegreeMode);
- }
- }
-
- private UnifiedReal toRadians(UnifiedReal x, EvalContext ec) {
- if (ec.mDegreeMode) {
- return x.multiply(UnifiedReal.RADIANS_PER_DEGREE);
- } else {
- return x;
- }
- }
-
- private UnifiedReal fromRadians(UnifiedReal x, EvalContext ec) {
- if (ec.mDegreeMode) {
- return x.divide(UnifiedReal.RADIANS_PER_DEGREE);
- } else {
- return x;
- }
- }
-
- // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
- // error. We expect that to be caught in eval below.
-
- private boolean isOperatorUnchecked(int i, int op) {
- Token t = mExpr.get(i);
- if (!(t instanceof Operator)) {
- return false;
- }
- return ((Operator)(t)).id == op;
- }
-
- private boolean isOperator(int i, int op, EvalContext ec) {
- if (i >= ec.mPrefixLength) {
- return false;
- }
- return isOperatorUnchecked(i, op);
- }
-
- public static class SyntaxException extends Exception {
- public SyntaxException() {
- super();
- }
- public SyntaxException(String s) {
- super(s);
- }
- }
-
- // The following functions all evaluate some kind of expression starting at position i in
- // mExpr in a specified evaluation context. They return both the expression value (as
- // constructive real and, if applicable, as BoundedRational) and the position of the next token
- // that was not used as part of the evaluation.
- // This is essentially a simple recursive descent parser combined with expression evaluation.
-
- private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
- final Token t = mExpr.get(i);
- if (t instanceof Constant) {
- Constant c = (Constant)t;
- return new EvalRet(i+1,new UnifiedReal(c.toRational()));
- }
- if (t instanceof PreEval) {
- final long index = ((PreEval)t).mIndex;
- UnifiedReal res = ec.mExprResolver.getResult(index);
- if (res == null) {
- // We try to minimize this recursive evaluation case, but currently don't
- // completely avoid it.
- res = nestedEval(index, ec.mExprResolver);
- }
- return new EvalRet(i+1, res);
- }
- EvalRet argVal;
- switch(((Operator)(t)).id) {
- case R.id.const_pi:
- return new EvalRet(i+1, UnifiedReal.PI);
- case R.id.const_e:
- return new EvalRet(i+1, UnifiedReal.E);
- case R.id.op_sqrt:
- // Seems to have highest precedence.
- // Does not add implicit paren.
- // Does seem to accept a leading minus.
- if (isOperator(i+1, R.id.op_sub, ec)) {
- argVal = evalUnary(i+2, ec);
- return new EvalRet(argVal.pos, argVal.val.negate().sqrt());
- } else {
- argVal = evalUnary(i+1, ec);
- return new EvalRet(argVal.pos, argVal.val.sqrt());
- }
- case R.id.lparen:
- argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.pos, R.id.rparen, ec)) {
- argVal.pos++;
- }
- return new EvalRet(argVal.pos, argVal.val);
- case R.id.fun_sin:
- argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.pos, R.id.rparen, ec)) {
- argVal.pos++;
- }
- return new EvalRet(argVal.pos, toRadians(argVal.val, ec).sin());
- case R.id.fun_cos:
- argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.pos, R.id.rparen, ec)) {
- argVal.pos++;
- }
- return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos());
- case R.id.fun_tan:
- argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.pos, R.id.rparen, ec)) {
- argVal.pos++;
- }
- UnifiedReal arg = toRadians(argVal.val, ec);
- return new EvalRet(argVal.pos, arg.sin().divide(arg.cos()));
- case R.id.fun_ln:
- argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.pos, R.id.rparen, ec)) {
- argVal.pos++;
- }
- return new EvalRet(argVal.pos, argVal.val.ln());
- case R.id.fun_exp:
- argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.pos, R.id.rparen, ec)) {
- argVal.pos++;
- }
- return new EvalRet(argVal.pos, argVal.val.exp());
- case R.id.fun_log:
- argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.pos, R.id.rparen, ec)) {
- argVal.pos++;
- }
- return new EvalRet(argVal.pos, argVal.val.ln().divide(UnifiedReal.TEN.ln()));
- case R.id.fun_arcsin:
- argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.pos, R.id.rparen, ec)) {
- argVal.pos++;
- }
- return new EvalRet(argVal.pos, fromRadians(argVal.val.asin(), ec));
- case R.id.fun_arccos:
- argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.pos, R.id.rparen, ec)) {
- argVal.pos++;
- }
- return new EvalRet(argVal.pos, fromRadians(argVal.val.acos(), ec));
- case R.id.fun_arctan:
- argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.pos, R.id.rparen, ec)) {
- argVal.pos++;
- }
- return new EvalRet(argVal.pos, fromRadians(argVal.val.atan(),ec));
- default:
- throw new SyntaxException("Unrecognized token in expression");
- }
- }
-
- private static final UnifiedReal ONE_HUNDREDTH = new UnifiedReal(100).inverse();
-
- private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
- final EvalRet tmp = evalUnary(i, ec);
- int cpos = tmp.pos;
- UnifiedReal val = tmp.val;
-
- boolean isFact;
- boolean isSquared = false;
- while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
- (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
- isOperator(cpos, R.id.op_pct, ec)) {
- if (isFact) {
- val = val.fact();
- } else if (isSquared) {
- val = val.multiply(val);
- } else /* percent */ {
- val = val.multiply(ONE_HUNDREDTH);
- }
- ++cpos;
- }
- return new EvalRet(cpos, val);
- }
-
- private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
- final EvalRet result1 = evalSuffix(i, ec);
- int cpos = result1.pos; // current position
- UnifiedReal val = result1.val; // value so far
- if (isOperator(cpos, R.id.op_pow, ec)) {
- final EvalRet exp = evalSignedFactor(cpos + 1, ec);
- cpos = exp.pos;
- val = val.pow(exp.val);
- }
- return new EvalRet(cpos, val);
- }
-
- private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
- final boolean negative = isOperator(i, R.id.op_sub, ec);
- int cpos = negative ? i + 1 : i;
- EvalRet tmp = evalFactor(cpos, ec);
- cpos = tmp.pos;
- final UnifiedReal result = negative ? tmp.val.negate() : tmp.val;
- return new EvalRet(cpos, result);
- }
-
- private boolean canStartFactor(int i) {
- if (i >= mExpr.size()) return false;
- Token t = mExpr.get(i);
- if (!(t instanceof Operator)) return true;
- int id = ((Operator)(t)).id;
- if (KeyMaps.isBinary(id)) return false;
- switch (id) {
- case R.id.op_fact:
- case R.id.rparen:
- return false;
- default:
- return true;
- }
- }
-
- private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
- EvalRet tmp = evalSignedFactor(i, ec);
- boolean is_mul = false;
- boolean is_div = false;
- int cpos = tmp.pos; // Current position in expression.
- UnifiedReal val = tmp.val; // Current value.
- while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
- || (is_div = isOperator(cpos, R.id.op_div, ec))
- || canStartFactor(cpos)) {
- if (is_mul || is_div) ++cpos;
- tmp = evalSignedFactor(cpos, ec);
- if (is_div) {
- val = val.divide(tmp.val);
- } else {
- val = val.multiply(tmp.val);
- }
- cpos = tmp.pos;
- is_mul = is_div = false;
- }
- return new EvalRet(cpos, val);
- }
-
- /**
- * Is the subexpression starting at pos a simple percent constant?
- * This is used to recognize exppressions like 200+10%, which we handle specially.
- * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
- * by either nothing or an additive operator.
- * Note that we are intentionally far more restrictive in recognizing such expressions than
- * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
- * When in doubt, we fall back to the the naive interpretation of % as 1/100.
- * Note that 100+(10)% yields 100.1 while 100+10% yields 110. This may be controversial,
- * but is consistent with Google web search.
- */
- private boolean isPercent(int pos) {
- if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
- return false;
- }
- Token number = mExpr.get(pos);
- if (number instanceof Operator) {
- return false;
- }
- if (mExpr.size() == pos + 2) {
- return true;
- }
- if (!(mExpr.get(pos + 2) instanceof Operator)) {
- return false;
- }
- Operator op = (Operator) mExpr.get(pos + 2);
- return op.id == R.id.op_add || op.id == R.id.op_sub || op.id == R.id.rparen;
- }
-
- /**
- * Compute the multiplicative factor corresponding to an N% addition or subtraction.
- * @param pos position of Constant or PreEval expression token corresponding to N.
- * @param isSubtraction this is a subtraction, as opposed to addition.
- * @param ec usable evaluation contex; only length matters.
- * @return UnifiedReal value and position, which is pos + 2, i.e. after percent sign
- */
- private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
- throws SyntaxException {
- EvalRet tmp = evalUnary(pos, ec);
- UnifiedReal val = isSubtraction ? tmp.val.negate() : tmp.val;
- val = UnifiedReal.ONE.add(val.multiply(ONE_HUNDREDTH));
- return new EvalRet(pos + 2 /* after percent sign */, val);
- }
-
- private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
- EvalRet tmp = evalTerm(i, ec);
- boolean is_plus;
- int cpos = tmp.pos;
- UnifiedReal val = tmp.val;
- while ((is_plus = isOperator(cpos, R.id.op_add, ec))
- || isOperator(cpos, R.id.op_sub, ec)) {
- if (isPercent(cpos + 1)) {
- tmp = getPercentFactor(cpos + 1, !is_plus, ec);
- val = val.multiply(tmp.val);
- } else {
- tmp = evalTerm(cpos + 1, ec);
- if (is_plus) {
- val = val.add(tmp.val);
- } else {
- val = val.subtract(tmp.val);
- }
- }
- cpos = tmp.pos;
- }
- return new EvalRet(cpos, val);
- }
-
- /**
- * Return the starting position of the sequence of trailing binary operators.
- */
- private int trailingBinaryOpsStart() {
- int result = mExpr.size();
- while (result > 0) {
- Token last = mExpr.get(result - 1);
- if (!(last instanceof Operator)) break;
- Operator o = (Operator)last;
- if (!KeyMaps.isBinary(o.id)) break;
- --result;
- }
- return result;
- }
-
- /**
- * Is the current expression worth evaluating?
- */
- public boolean hasInterestingOps() {
- final int last = trailingBinaryOpsStart();
- int first = 0;
- if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
- // Leading minus is not by itself interesting.
- first++;
- }
- for (int i = first; i < last; ++i) {
- Token t1 = mExpr.get(i);
- if (t1 instanceof Operator
- || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Does the expression contain trig operations?
- */
- public boolean hasTrigFuncs() {
- for (Token t : mExpr) {
- if (t instanceof Operator) {
- Operator o = (Operator)t;
- if (KeyMaps.isTrigFunc(o.id)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Add the indices of unevaluated PreEval expressions embedded in the current expression to
- * argument. This includes only directly referenced expressions e, not those indirectly
- * referenced by e. If the index was already present, it is not added. If the argument
- * contained no duplicates, the result will not either. New indices are added to the end of
- * the list.
- */
- private void addReferencedExprs(ArrayList<Long> list, ExprResolver er) {
- for (Token t : mExpr) {
- if (t instanceof PreEval) {
- Long index = ((PreEval) t).mIndex;
- if (er.getResult(index) == null && !list.contains(index)) {
- list.add(index);
- }
- }
- }
- }
-
- /**
- * Return a list of unevaluated expressions transitively referenced by the current one.
- * All expressions in the resulting list will have had er.getExpr() called on them.
- * The resulting list is ordered such that evaluating expressions in list order
- * should trigger few recursive evaluations.
- */
- public ArrayList<Long> getTransitivelyReferencedExprs(ExprResolver er) {
- // We could avoid triggering any recursive evaluations by actually building the
- // dependency graph and topologically sorting it. Note that sorting by index works
- // for positive and negative indices separately, but not their union. Currently we
- // just settle for reverse breadth-first-search order, which handles the common case
- // of simple dependency chains well.
- ArrayList<Long> list = new ArrayList<Long>();
- int scanned = 0; // We've added expressions referenced by [0, scanned) to the list
- addReferencedExprs(list, er);
- while (scanned != list.size()) {
- er.getExpr(list.get(scanned++)).addReferencedExprs(list, er);
- }
- Collections.reverse(list);
- return list;
- }
-
- /**
- * Evaluate the expression at the given index to a UnifiedReal.
- * Both saves and returns the result.
- */
- UnifiedReal nestedEval(long index, ExprResolver er) throws SyntaxException {
- CalculatorExpr nestedExpr = er.getExpr(index);
- EvalContext newEc = new EvalContext(er.getDegreeMode(index),
- nestedExpr.trailingBinaryOpsStart(), er);
- EvalRet new_res = nestedExpr.evalExpr(0, newEc);
- return er.putResultIfAbsent(index, new_res.val);
- }
-
- /**
- * Evaluate the expression excluding trailing binary operators.
- * Errors result in exceptions, most of which are unchecked. Should not be called
- * concurrently with modification of the expression. May take a very long time; avoid calling
- * from UI thread.
- *
- * @param degreeMode use degrees rather than radians
- */
- UnifiedReal eval(boolean degreeMode, ExprResolver er) throws SyntaxException
- // And unchecked exceptions thrown by UnifiedReal, CR,
- // and BoundedRational.
- {
- // First evaluate all indirectly referenced expressions in increasing index order.
- // This ensures that subsequent evaluation never encounters an embedded PreEval
- // expression that has not been previously evaluated.
- // We could do the embedded evaluations recursively, but that risks running out of
- // stack space.
- ArrayList<Long> referenced = getTransitivelyReferencedExprs(er);
- for (long index : referenced) {
- nestedEval(index, er);
- }
- try {
- // We currently never include trailing binary operators, but include other trailing
- // operators. Thus we usually, but not always, display results for prefixes of valid
- // expressions, and don't generate an error where we previously displayed an instant
- // result. This reflects the Android L design.
- int prefixLen = trailingBinaryOpsStart();
- EvalContext ec = new EvalContext(degreeMode, prefixLen, er);
- EvalRet res = evalExpr(0, ec);
- if (res.pos != prefixLen) {
- throw new SyntaxException("Failed to parse full expression");
- }
- return res.val;
- } catch (IndexOutOfBoundsException e) {
- throw new SyntaxException("Unexpected expression end");
- }
- }
-
- // Produce a string representation of the expression itself
- SpannableStringBuilder toSpannableStringBuilder(Context context) {
- SpannableStringBuilder ssb = new SpannableStringBuilder();
- for (Token t : mExpr) {
- ssb.append(t.toCharSequence(context));
- }
- return ssb;
- }
-}