summaryrefslogtreecommitdiffstats
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.java702
1 files changed, 395 insertions, 307 deletions
diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java
index 8a008b8..b387e8b 100644
--- a/src/com/android/calculator2/CalculatorExpr.java
+++ b/src/com/android/calculator2/CalculatorExpr.java
@@ -36,9 +36,19 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
-// A mathematical expression represented as a sequence of "tokens".
-// Many tokes are represented by button ids for the corresponding operator.
-// Parsed only when we evaluate the expression using the "eval" method.
+/**
+ * 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 both a constructive real (CR), and possibly a BoundedRational 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 CR provides no
+ * serialization facility. Thus we save all previously computed values by writing out the
+ * expression that was used to compute them, and reevaluate on input.
+ */
class CalculatorExpr {
private ArrayList<Token> mExpr; // The actual representation
// as a list of tokens. Constant
@@ -66,41 +76,45 @@ class CalculatorExpr {
abstract CharSequence toCharSequence(Context context);
}
- // An operator token
+ /**
+ * Representation of an operator token
+ */
private static class Operator extends Token {
- final int mId; // We use the button resource id
+ public final int id; // We use the button resource id
Operator(int resId) {
- mId = resId;
+ id = resId;
}
Operator(DataInput in) throws IOException {
- mId = in.readInt();
+ id = in.readInt();
}
@Override
void write(DataOutput out) throws IOException {
out.writeByte(TokenKind.OPERATOR.ordinal());
- out.writeInt(mId);
+ out.writeInt(id);
}
@Override
public CharSequence toCharSequence(Context context) {
- String desc = KeyMaps.toDescriptiveString(context, mId);
+ String desc = KeyMaps.toDescriptiveString(context, id);
if (desc != null) {
- SpannableString result = new SpannableString(KeyMaps.toString(context, mId));
+ 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, mId);
+ return KeyMaps.toString(context, id);
}
}
@Override
TokenKind kind() { return TokenKind.OPERATOR; }
}
- // A (possibly incomplete) numerical constant.
- // Supports addition and removal of trailing characters; hence mutable.
+ /**
+ * 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;
- String mWhole; // String preceding decimal point.
+ private String mWhole; // String preceding decimal point.
private String mFraction; // String after decimal point.
private int mExponent; // Explicit exponent, only generated through addExponent.
@@ -132,7 +146,7 @@ class CalculatorExpr {
// Just return false if this was the second (or later) decimal point
// in this constant.
// Assumes that this constant does not have an exponent.
- boolean add(int id) {
+ public boolean add(int id) {
if (id == R.id.dec_point) {
if (mSawDecimal || mExponent != 0) return false;
mSawDecimal = true;
@@ -159,14 +173,16 @@ class CalculatorExpr {
return true;
}
- void addExponent(int exp) {
+ public void addExponent(int exp) {
// Note that adding a 0 exponent is a no-op. That's OK.
mExponent = exp;
}
- // Undo the last add.
- // Assumes the constant is nonempty.
- void delete() {
+ /**
+ * 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.
@@ -179,12 +195,14 @@ class CalculatorExpr {
}
}
- boolean isEmpty() {
+ public boolean isEmpty() {
return (mSawDecimal == false && mWhole.isEmpty());
}
- // Produces human-readable string, as typed.
- // Result is internationalized.
+ /**
+ * Produce human-readable string representation of constant, as typed.
+ * Result is internationalized.
+ */
@Override
public String toString() {
String result = mWhole;
@@ -198,7 +216,10 @@ class CalculatorExpr {
return KeyMaps.translateResult(result);
}
- // Return non-null BoundedRational representation.
+ /**
+ * Return BoundedRational representation of constant.
+ * Never null.
+ */
public BoundedRational toRational() {
String whole = mWhole;
if (whole.isEmpty()) whole = "0";
@@ -214,73 +235,75 @@ class CalculatorExpr {
}
@Override
- CharSequence toCharSequence(Context context) {
+ public CharSequence toCharSequence(Context context) {
return toString();
}
@Override
- TokenKind kind() { return TokenKind.CONSTANT; }
+ public TokenKind kind() {
+ return TokenKind.CONSTANT;
+ }
// Override clone to make it public
@Override
public Object clone() {
- Constant res = new Constant();
- res.mWhole = mWhole;
- res.mFraction = mFraction;
- res.mSawDecimal = mSawDecimal;
- res.mExponent = mExponent;
- return res;
+ Constant result = new Constant();
+ result.mWhole = mWhole;
+ result.mFraction = mFraction;
+ result.mSawDecimal = mSawDecimal;
+ result.mExponent = mExponent;
+ return result;
}
}
- // Hash maps used to detect duplicate subexpressions when
- // we write out CalculatorExprs and read them back in.
+ // Hash maps used to detect duplicate subexpressions when we write out CalculatorExprs and
+ // read them back in.
private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
- new ThreadLocal<IdentityHashMap<CR,Integer>>();
+ new ThreadLocal<IdentityHashMap<CR,Integer>>();
// Maps expressions to indices on output
private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
- new ThreadLocal<HashMap<Integer,PreEval>>();
+ new ThreadLocal<HashMap<Integer,PreEval>>();
// Maps expressions to indices on output
- private static final ThreadLocal<Integer> exprIndex =
- new ThreadLocal<Integer>();
+ private static final ThreadLocal<Integer> exprIndex = new ThreadLocal<Integer>();
- static void initExprOutput() {
+ /**
+ * Prepare for expression output.
+ * Initializes map that will lbe used to avoid duplicating shared subexpressions.
+ * This avoids a potential exponential blow-up in the expression size.
+ */
+ public static void initExprOutput() {
outMap.set(new IdentityHashMap<CR,Integer>());
exprIndex.set(Integer.valueOf(0));
}
- static void initExprInput() {
+ /**
+ * Prepare for expression input.
+ * Initializes map that will be used to reconstruct shared subexpressions.
+ */
+ public static void initExprInput() {
inMap.set(new HashMap<Integer,PreEval>());
}
- // We treat previously evaluated subexpressions as tokens
- // These are inserted when either:
- // - We continue an expression after evaluating some of it.
- // - TODO: When we copy/paste expressions.
- // The representation includes three different representations
- // of the expression:
- // 1) The CR value for use in computation.
- // 2) The integer value for use in the computations,
- // if the expression evaluates to an integer.
- // 3a) The corresponding CalculatorExpr, together with
- // 3b) The context (currently just deg/rad mode) used to evaluate
- // the expression.
- // 4) A short string representation that is used to
- // Display the expression.
- //
- // (3) is present only so that we can persist the object.
- // (4) is stored explicitly to avoid waiting for recomputation in the UI
- // thread.
+ /**
+ * 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.
+ * The representation includes both CR and possibly BoundedRational values. 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 {
- final CR mValue;
- final BoundedRational mRatValue;
+ public final CR value;
+ public final BoundedRational ratValue;
private final CalculatorExpr mExpr;
private final EvalContext mContext;
private final String mShortRep; // Not internationalized.
PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr,
EvalContext ec, String shortRep) {
- mValue = val;
- mRatValue = ratVal;
+ value = val;
+ ratValue = ratVal;
mExpr = expr;
mContext = ec;
mShortRep = shortRep;
@@ -293,13 +316,13 @@ class CalculatorExpr {
// The parameter hash map maps expressions we've seen
// before to their index.
@Override
- void write(DataOutput out) throws IOException {
+ public void write(DataOutput out) throws IOException {
out.writeByte(TokenKind.PRE_EVAL.ordinal());
- Integer index = outMap.get().get(mValue);
+ Integer index = outMap.get().get(value);
if (index == null) {
int nextIndex = exprIndex.get() + 1;
exprIndex.set(nextIndex);
- outMap.get().put(mValue, nextIndex);
+ outMap.get().put(value, nextIndex);
out.writeInt(nextIndex);
mExpr.write(out);
mContext.write(out);
@@ -315,13 +338,10 @@ class CalculatorExpr {
if (prev == null) {
mExpr = new CalculatorExpr(in);
mContext = new EvalContext(in, mExpr.mExpr.size());
- // Recompute other fields
- // We currently do this in the UI thread, but we
- // only create PreEval expressions that were
- // previously successfully evaluated, and thus
- // don't diverge. We also only evaluate to a
- // constructive real, which involves substantial
- // work only in fairly contrived circumstances.
+ // Recompute other fields We currently do this in the UI thread, but we only
+ // create PreEval expressions that were previously successfully evaluated, and
+ // thus don't diverge. We also only evaluate to a constructive real, which
+ // involves substantial work only in fairly contrived circumstances.
// TODO: Deal better with slow evaluations.
EvalRet res = null;
try {
@@ -331,32 +351,35 @@ class CalculatorExpr {
// expressions that can be evaluated.
Log.e("Calculator", "Unexpected syntax exception" + e);
}
- mValue = res.mVal;
- mRatValue = res.mRatVal;
+ value = res.val;
+ ratValue = res.ratVal;
mShortRep = in.readUTF();
inMap.get().put(index, this);
} else {
- mValue = prev.mValue;
- mRatValue = prev.mRatValue;
+ value = prev.value;
+ ratValue = prev.ratValue;
mExpr = prev.mExpr;
mContext = prev.mContext;
mShortRep = prev.mShortRep;
}
}
@Override
- CharSequence toCharSequence(Context context) {
+ public CharSequence toCharSequence(Context context) {
return KeyMaps.translateResult(mShortRep);
}
@Override
- TokenKind kind() {
+ public TokenKind kind() {
return TokenKind.PRE_EVAL;
}
- boolean hasEllipsis() {
+ public boolean hasEllipsis() {
return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
}
}
- static Token newToken(DataInput in) throws IOException {
+ /**
+ * Read token from in.
+ */
+ public static Token newToken(DataInput in) throws IOException {
TokenKind kind = tokenKindValues[in.readByte()];
switch(kind) {
case CONSTANT:
@@ -377,6 +400,9 @@ class CalculatorExpr {
mExpr = expr;
}
+ /**
+ * Construct CalculatorExpr, by reading it from in.
+ */
CalculatorExpr(DataInput in) throws IOException {
mExpr = new ArrayList<Token>();
int size = in.readInt();
@@ -385,7 +411,10 @@ class CalculatorExpr {
}
}
- void write(DataOutput out) throws IOException {
+ /**
+ * 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) {
@@ -393,6 +422,10 @@ class CalculatorExpr {
}
}
+ /**
+ * 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) {
@@ -402,13 +435,16 @@ class CalculatorExpr {
return t instanceof Constant;
}
+ /**
+ * Does this expression end with a binary operator?
+ */
private 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.mId));
+ return (KeyMaps.isBinary(o.id));
}
/**
@@ -420,10 +456,10 @@ class CalculatorExpr {
*/
boolean add(int id) {
int s = mExpr.size();
- int d = KeyMaps.digVal(id);
- boolean binary = KeyMaps.isBinary(id);
+ 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).mId : 0;
+ 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)) {
@@ -436,7 +472,7 @@ class CalculatorExpr {
}
// s invalid and not used below.
}
- boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
+ 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) {
@@ -476,35 +512,37 @@ class CalculatorExpr {
void removeTrailingAdditiveOperators() {
while (true) {
int s = mExpr.size();
- if (s == 0) break;
+ if (s == 0) {
+ break;
+ }
Token lastTok = mExpr.get(s-1);
- if (!(lastTok instanceof Operator)) break;
- int lastOp = ((Operator) lastTok).mId;
- if (lastOp != R.id.op_add && lastOp != R.id.op_sub) break;
+ 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.
- // TODO: We probably only need this for expressions consisting of
- // a single PreEval "token", and may want to check that.
- void append(CalculatorExpr expr2) {
- // Check that we're not concatenating Constant or PreEval
- // tokens, since the result would look like a single constant
+ /**
+ * 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.
+ // 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.
+ // 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));
}
}
@@ -513,83 +551,97 @@ class CalculatorExpr {
}
}
- // Undo the last key addition, if any.
- void delete() {
- int s = mExpr.size();
- if (s == 0) return;
+ /**
+ * 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;
+ if (!c.isEmpty()) {
+ return;
+ }
}
mExpr.remove(s-1);
}
- void clear() {
+ /**
+ * Remove all tokens from the expression.
+ */
+ public void clear() {
mExpr.clear();
}
- boolean isEmpty() {
+ 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.
+ /**
+ * Returns a logical deep copy of the CalculatorExpr.
+ * Operator and PreEval tokens are immutable, and thus aren't really copied.
+ */
public Object clone() {
- CalculatorExpr res = new CalculatorExpr();
+ CalculatorExpr result = new CalculatorExpr();
for (Token t: mExpr) {
if (t instanceof Constant) {
- res.mExpr.add((Token)(((Constant)t).clone()));
+ result.mExpr.add((Token)(((Constant)t).clone()));
} else {
- res.mExpr.add(t);
+ result.mExpr.add(t);
}
}
- return res;
+ return result;
}
// Am I just a constant?
- boolean isConstant() {
- if (mExpr.size() != 1) return false;
+ public boolean isConstant() {
+ if (mExpr.size() != 1) {
+ return false;
+ }
return mExpr.get(0) instanceof Constant;
}
- // Return a new expression consisting of a single PreEval token
- // representing the current expression.
- // The caller supplies the value, degree mode, and short
- // string representation, which must have been previously computed.
- // Thus this is guaranteed to terminate reasonably quickly.
- CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
+ /**
+ * Return a new expression consisting of a single token representing the current pre-evaluated
+ * expression.
+ * The caller supplies the value, degree mode, and short string representation, which must
+ * have been previously computed. Thus this is guaranteed to terminate reasonably quickly.
+ */
+ public CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
boolean dm, String sr) {
CalculatorExpr result = new CalculatorExpr();
- Token t = new PreEval(val, ratVal,
- new CalculatorExpr(
- (ArrayList<Token>)mExpr.clone()),
- new EvalContext(dm, mExpr.size()), sr);
+ Token t = new PreEval(val, ratVal, new CalculatorExpr((ArrayList<Token>) mExpr.clone()),
+ new EvalContext(dm, mExpr.size()), sr);
result.mExpr.add(t);
return result;
}
- // Internal evaluation functions return an EvalRet triple.
- // We compute rational (BoundedRational) results when possible, both as
- // a performance optimization, and to detect errors exactly when we can.
- private class EvalRet {
- int mPos; // Next position (expression index) to be parsed
- final CR mVal; // Constructive Real result of evaluating subexpression
- final BoundedRational mRatVal; // Exact Rational value or null if
- // irrational or hard to compute.
+ /**
+ * Internal evaluation functions return an EvalRet triple.
+ * 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 CR val; // Constructive Real result of evaluating subexpression.
+ public final BoundedRational ratVal; // Exact Rational value or null.
EvalRet(int p, CR v, BoundedRational r) {
- mPos = p;
- mVal = v;
- mRatVal = r;
+ pos = p;
+ val = v;
+ ratVal = r;
}
}
- // And take a context argument:
+ /**
+ * 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 int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
public final boolean mDegreeMode;
// If we add any other kinds of evaluation modes, they go here.
EvalContext(boolean degreeMode, int len) {
@@ -625,22 +677,25 @@ class CalculatorExpr {
}
}
- // The following methods can all throw IndexOutOfBoundsException
- // in the event of a syntax error. We expect that to be caught in
- // eval below.
+ // 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)).mId == op;
+ 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;
+ if (i >= ec.mPrefixLength) {
+ return false;
+ }
return isOperatorUnchecked(i, op);
}
- static class SyntaxException extends Exception {
+ public static class SyntaxException extends Exception {
public SyntaxException() {
super();
}
@@ -649,27 +704,26 @@ class CalculatorExpr {
}
}
- // 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 BigInteger) and the position of the next token
+ // 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 {
- Token t = mExpr.get(i);
+ final Token t = mExpr.get(i);
BoundedRational ratVal;
- CR value;
if (t instanceof Constant) {
Constant c = (Constant)t;
ratVal = c.toRational();
- value = ratVal.CRValue();
- return new EvalRet(i+1, value, ratVal);
+ return new EvalRet(i+1, ratVal.CRValue(), ratVal);
}
if (t instanceof PreEval) {
- PreEval p = (PreEval)t;
- return new EvalRet(i+1, p.mValue, p.mRatValue);
+ final PreEval p = (PreEval)t;
+ return new EvalRet(i+1, p.value, p.ratValue);
}
EvalRet argVal;
- switch(((Operator)(t)).mId) {
+ switch(((Operator)(t)).id) {
case R.id.const_pi:
return new EvalRet(i+1, CR.PI, null);
case R.id.const_e:
@@ -680,111 +734,144 @@ class CalculatorExpr {
// Does seem to accept a leading minus.
if (isOperator(i+1, R.id.op_sub, ec)) {
argVal = evalUnary(i+2, ec);
- ratVal = BoundedRational.sqrt(
- BoundedRational.negate(argVal.mRatVal));
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- argVal.mVal.negate().sqrt(), null);
+ ratVal = BoundedRational.sqrt(BoundedRational.negate(argVal.ratVal));
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos,
+ argVal.val.negate().sqrt(), null);
} else {
argVal = evalUnary(i+1, ec);
- ratVal = BoundedRational.sqrt(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos, argVal.mVal.sqrt(), null);
+ ratVal = BoundedRational.sqrt(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, argVal.val.sqrt(), null);
}
case R.id.lparen:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- return new EvalRet(argVal.mPos, argVal.mVal, argVal.mRatVal);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ return new EvalRet(argVal.pos, argVal.val, argVal.ratVal);
case R.id.fun_sin:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.mRatVal)
- : BoundedRational.sin(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- toRadians(argVal.mVal,ec).sin(), null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.ratVal)
+ : BoundedRational.sin(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, toRadians(argVal.val,ec).sin(), null);
case R.id.fun_cos:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.mRatVal)
- : BoundedRational.cos(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- toRadians(argVal.mVal,ec).cos(), null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.ratVal)
+ : BoundedRational.cos(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos(), null);
case R.id.fun_tan:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.mRatVal)
- : BoundedRational.tan(argVal.mRatVal);
- if (ratVal != null) break;
- CR argCR = toRadians(argVal.mVal, ec);
- return new EvalRet(argVal.mPos,
- argCR.sin().divide(argCR.cos()), null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.ratVal)
+ : BoundedRational.tan(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ CR argCR = toRadians(argVal.val, ec);
+ return new EvalRet(argVal.pos, argCR.sin().divide(argCR.cos()), null);
case R.id.fun_ln:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = BoundedRational.ln(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos, argVal.mVal.ln(), null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = BoundedRational.ln(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, argVal.val.ln(), null);
case R.id.fun_exp:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = BoundedRational.exp(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos, argVal.mVal.exp(), null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = BoundedRational.exp(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, argVal.val.exp(), null);
case R.id.fun_log:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = BoundedRational.log(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- argVal.mVal.ln().divide(CR.valueOf(10).ln()),
- null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = BoundedRational.log(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, argVal.val.ln().divide(CR.valueOf(10).ln()), null);
case R.id.fun_arcsin:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.mRatVal)
- : BoundedRational.asin(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- fromRadians(UnaryCRFunction
- .asinFunction.execute(argVal.mVal),ec),
- null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.ratVal)
+ : BoundedRational.asin(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos,
+ fromRadians(UnaryCRFunction.asinFunction.execute(argVal.val),ec), null);
case R.id.fun_arccos:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.mRatVal)
- : BoundedRational.acos(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- fromRadians(UnaryCRFunction
- .acosFunction.execute(argVal.mVal),ec),
- null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.ratVal)
+ : BoundedRational.acos(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos,
+ fromRadians(UnaryCRFunction.acosFunction.execute(argVal.val),ec), null);
case R.id.fun_arctan:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.mRatVal)
- : BoundedRational.atan(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- fromRadians(UnaryCRFunction
- .atanFunction.execute(argVal.mVal),ec),
- null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.ratVal)
+ : BoundedRational.atan(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos,
+ fromRadians(UnaryCRFunction.atanFunction.execute(argVal.val),ec), null);
default:
throw new SyntaxException("Unrecognized token in expression");
}
// We have a rational value.
- return new EvalRet(argVal.mPos, ratVal.CRValue(), ratVal);
+ return new EvalRet(argVal.pos, ratVal.CRValue(), ratVal);
}
- // Compute an integral power of a constructive real.
- // Unlike the "general" case using logarithms, this handles a negative
- // base.
+ /**
+ * Compute an integral power of a constructive real.
+ * Unlike the "general" case using logarithms, this handles a negative base.
+ */
private static CR pow(CR base, BigInteger exp) {
if (exp.compareTo(BigInteger.ZERO) < 0) {
return pow(base, exp.negate()).inverse();
}
- if (exp.equals(BigInteger.ONE)) return base;
+ if (exp.equals(BigInteger.ONE)) {
+ return base;
+ }
if (exp.and(BigInteger.ONE).intValue() == 1) {
return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
}
@@ -795,24 +882,23 @@ class CalculatorExpr {
return tmp.multiply(tmp);
}
+ // Number of bits past binary point to test for integer-ness.
private static final int TEST_PREC = -100;
- // Test for integer-ness to 100 bits past binary point.
private static final BigInteger MASK =
BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
private static final CR REAL_E = CR.valueOf(1).exp();
private static final CR REAL_ONE_HUNDREDTH = CR.valueOf(100).inverse();
- private static final BoundedRational RATIONAL_ONE_HUNDREDTH =
- new BoundedRational(1,100);
+ private static final BoundedRational RATIONAL_ONE_HUNDREDTH = new BoundedRational(1,100);
private static boolean isApprInt(CR x) {
BigInteger appr = x.get_appr(TEST_PREC);
return appr.and(MASK).signum() == 0;
}
private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
- EvalRet tmp = evalUnary(i, ec);
- int cpos = tmp.mPos;
- CR cval = tmp.mVal;
- BoundedRational ratVal = tmp.mRatVal;
+ final EvalRet tmp = evalUnary(i, ec);
+ int cpos = tmp.pos;
+ CR crVal = tmp.val;
+ BoundedRational ratVal = tmp.ratVal;
boolean isFact;
boolean isSquared = false;
while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
@@ -820,46 +906,45 @@ class CalculatorExpr {
isOperator(cpos, R.id.op_pct, ec)) {
if (isFact) {
if (ratVal == null) {
- // Assume it was an integer, but we
- // didn't figure it out.
+ // Assume it was an integer, but we didn't figure it out.
// KitKat may have used the Gamma function.
- if (!isApprInt(cval)) {
+ if (!isApprInt(crVal)) {
throw new ArithmeticException("factorial(non-integer)");
}
- ratVal = new BoundedRational(cval.BigIntegerValue());
+ ratVal = new BoundedRational(crVal.BigIntegerValue());
}
ratVal = BoundedRational.fact(ratVal);
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
} else if (isSquared) {
ratVal = BoundedRational.multiply(ratVal, ratVal);
if (ratVal == null) {
- cval = cval.multiply(cval);
+ crVal = crVal.multiply(crVal);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
} else /* percent */ {
ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH);
if (ratVal == null) {
- cval = cval.multiply(REAL_ONE_HUNDREDTH);
+ crVal = crVal.multiply(REAL_ONE_HUNDREDTH);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
}
++cpos;
}
- return new EvalRet(cpos, cval, ratVal);
+ return new EvalRet(cpos, crVal, ratVal);
}
private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
final EvalRet result1 = evalSuffix(i, ec);
- int cpos = result1.mPos; // current position
- CR cval = result1.mVal; // value so far
- BoundedRational ratVal = result1.mRatVal; // int value so far
+ int cpos = result1.pos; // current position
+ CR crVal = result1.val; // value so far
+ BoundedRational ratVal = result1.ratVal; // int value so far
if (isOperator(cpos, R.id.op_pow, ec)) {
- final EvalRet exp = evalSignedFactor(cpos+1, ec);
- cpos = exp.mPos;
+ final EvalRet exp = evalSignedFactor(cpos + 1, ec);
+ cpos = exp.pos;
// Try completely rational evaluation first.
- ratVal = BoundedRational.pow(ratVal, exp.mRatVal);
+ ratVal = BoundedRational.pow(ratVal, exp.ratVal);
if (ratVal != null) {
return new EvalRet(cpos, ratVal.CRValue(), ratVal);
}
@@ -867,33 +952,33 @@ class CalculatorExpr {
// Thus we handle that case separately.
// We punt if the exponent is an integer computed from irrational
// values. That wouldn't work reliably with floating point either.
- BigInteger int_exp = BoundedRational.asBigInteger(exp.mRatVal);
+ BigInteger int_exp = BoundedRational.asBigInteger(exp.ratVal);
if (int_exp != null) {
- cval = pow(cval, int_exp);
+ crVal = pow(crVal, int_exp);
} else {
- cval = cval.ln().multiply(exp.mVal).exp();
+ crVal = crVal.ln().multiply(exp.val).exp();
}
ratVal = null;
}
- return new EvalRet(cpos, cval, ratVal);
+ return new EvalRet(cpos, crVal, ratVal);
}
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.mPos;
- CR cval = negative ? tmp.mVal.negate() : tmp.mVal;
- BoundedRational ratVal = negative ? BoundedRational.negate(tmp.mRatVal)
- : tmp.mRatVal;
- return new EvalRet(cpos, cval, ratVal);
+ cpos = tmp.pos;
+ CR crVal = negative ? tmp.val.negate() : tmp.val;
+ BoundedRational ratVal = negative ? BoundedRational.negate(tmp.ratVal)
+ : tmp.ratVal;
+ return new EvalRet(cpos, crVal, ratVal);
}
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)).mId;
+ int id = ((Operator)(t)).id;
if (KeyMaps.isBinary(id)) return false;
switch (id) {
case R.id.op_fact:
@@ -908,72 +993,74 @@ class CalculatorExpr {
EvalRet tmp = evalSignedFactor(i, ec);
boolean is_mul = false;
boolean is_div = false;
- int cpos = tmp.mPos; // Current position in expression.
- CR cval = tmp.mVal; // Current value.
- BoundedRational ratVal = tmp.mRatVal; // Current rational value.
+ int cpos = tmp.pos; // Current position in expression.
+ CR crVal = tmp.val; // Current value.
+ BoundedRational ratVal = tmp.ratVal; // Current rational 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) {
- ratVal = BoundedRational.divide(ratVal, tmp.mRatVal);
+ ratVal = BoundedRational.divide(ratVal, tmp.ratVal);
if (ratVal == null) {
- cval = cval.divide(tmp.mVal);
+ crVal = crVal.divide(tmp.val);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
} else {
- ratVal = BoundedRational.multiply(ratVal, tmp.mRatVal);
+ ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
if (ratVal == null) {
- cval = cval.multiply(tmp.mVal);
+ crVal = crVal.multiply(tmp.val);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
}
- cpos = tmp.mPos;
+ cpos = tmp.pos;
is_mul = is_div = false;
}
- return new EvalRet(cpos, cval, ratVal);
+ return new EvalRet(cpos, crVal, ratVal);
}
private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
EvalRet tmp = evalTerm(i, ec);
boolean is_plus;
- int cpos = tmp.mPos;
- CR cval = tmp.mVal;
- BoundedRational ratVal = tmp.mRatVal;
+ int cpos = tmp.pos;
+ CR crVal = tmp.val;
+ BoundedRational ratVal = tmp.ratVal;
while ((is_plus = isOperator(cpos, R.id.op_add, ec))
|| isOperator(cpos, R.id.op_sub, ec)) {
tmp = evalTerm(cpos+1, ec);
if (is_plus) {
- ratVal = BoundedRational.add(ratVal, tmp.mRatVal);
+ ratVal = BoundedRational.add(ratVal, tmp.ratVal);
if (ratVal == null) {
- cval = cval.add(tmp.mVal);
+ crVal = crVal.add(tmp.val);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
} else {
- ratVal = BoundedRational.subtract(ratVal, tmp.mRatVal);
+ ratVal = BoundedRational.subtract(ratVal, tmp.ratVal);
if (ratVal == null) {
- cval = cval.subtract(tmp.mVal);
+ crVal = crVal.subtract(tmp.val);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
}
- cpos = tmp.mPos;
+ cpos = tmp.pos;
}
- return new EvalRet(cpos, cval, ratVal);
+ return new EvalRet(cpos, crVal, ratVal);
}
- // Externally visible evaluation result.
- public class EvalResult {
- EvalResult (CR val, BoundedRational ratVal) {
- mVal = val;
- mRatVal = ratVal;
+ /**
+ * Externally visible evaluation result.
+ */
+ public static class EvalResult {
+ public final CR val;
+ public final BoundedRational ratVal;
+ EvalResult (CR v, BoundedRational rv) {
+ val = v;
+ ratVal = rv;
}
- final CR mVal;
- final BoundedRational mRatVal;
}
/**
@@ -985,13 +1072,15 @@ class CalculatorExpr {
Token last = mExpr.get(result - 1);
if (!(last instanceof Operator)) break;
Operator o = (Operator)last;
- if (!KeyMaps.isBinary(o.mId)) break;
+ if (!KeyMaps.isBinary(o.id)) break;
--result;
}
return result;
}
- // Is the current expression worth evaluating?
+ /**
+ * Is the current expression worth evaluating?
+ */
public boolean hasInterestingOps() {
int last = trailingBinaryOpsStart();
int first = 0;
@@ -1011,9 +1100,9 @@ class CalculatorExpr {
/**
* 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.
+ * 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
*/
@@ -1022,18 +1111,17 @@ class CalculatorExpr {
// and BoundedRational.
{
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.
+ // 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);
EvalRet res = evalExpr(0, ec);
- if (res.mPos != prefixLen) {
+ if (res.pos != prefixLen) {
throw new SyntaxException("Failed to parse full expression");
}
- return new EvalResult(res.mVal, res.mRatVal);
+ return new EvalResult(res.val, res.ratVal);
} catch (IndexOutOfBoundsException e) {
throw new SyntaxException("Unexpected expression end");
}