blob: b387e8b2482b0bb6d05c24dcc4eea96e4247fd5b [file] [log] [blame]
Hans Boehm84614952014-11-25 18:46:17 -08001/*
Hans Boehme4579122015-05-26 17:52:32 -07002 * Copyright (C) 2015 The Android Open Source Project
Hans Boehm84614952014-11-25 18:46:17 -08003 *
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
Hans Boehm84614952014-11-25 18:46:17 -080019
20import com.hp.creals.CR;
21import com.hp.creals.UnaryCRFunction;
Hans Boehm84614952014-11-25 18:46:17 -080022
23import android.content.Context;
Hans Boehm8a4f81c2015-07-09 10:41:25 -070024import android.text.SpannableString;
25import android.text.SpannableStringBuilder;
26import android.text.Spanned;
27import android.text.style.TtsSpan;
28import android.text.style.TtsSpan.TextBuilder;
Hans Boehmc023b732015-04-29 11:30:47 -070029import android.util.Log;
Hans Boehm84614952014-11-25 18:46:17 -080030
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070031import java.math.BigInteger;
32import java.io.DataInput;
33import java.io.DataOutput;
34import java.io.IOException;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070035import java.util.ArrayList;
36import java.util.HashMap;
37import java.util.IdentityHashMap;
38
Hans Boehm3666e632015-07-27 18:33:12 -070039/**
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 */
Hans Boehm84614952014-11-25 18:46:17 -080052class 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();
Hans Boehm8a4f81c2015-07-09 10:41:25 -070064
65 /**
66 * Write kind as Byte followed by data needed by subclass constructor.
67 */
Hans Boehm84614952014-11-25 18:46:17 -080068 abstract void write(DataOutput out) throws IOException;
Hans Boehm8a4f81c2015-07-09 10:41:25 -070069
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);
Hans Boehm84614952014-11-25 18:46:17 -080077 }
78
Hans Boehm3666e632015-07-27 18:33:12 -070079 /**
80 * Representation of an operator token
81 */
Hans Boehm84614952014-11-25 18:46:17 -080082 private static class Operator extends Token {
Hans Boehm3666e632015-07-27 18:33:12 -070083 public final int id; // We use the button resource id
Hans Boehm84614952014-11-25 18:46:17 -080084 Operator(int resId) {
Hans Boehm3666e632015-07-27 18:33:12 -070085 id = resId;
Hans Boehm84614952014-11-25 18:46:17 -080086 }
87 Operator(DataInput in) throws IOException {
Hans Boehm3666e632015-07-27 18:33:12 -070088 id = in.readInt();
Hans Boehm84614952014-11-25 18:46:17 -080089 }
90 @Override
91 void write(DataOutput out) throws IOException {
92 out.writeByte(TokenKind.OPERATOR.ordinal());
Hans Boehm3666e632015-07-27 18:33:12 -070093 out.writeInt(id);
Hans Boehm84614952014-11-25 18:46:17 -080094 }
95 @Override
Hans Boehm8a4f81c2015-07-09 10:41:25 -070096 public CharSequence toCharSequence(Context context) {
Hans Boehm3666e632015-07-27 18:33:12 -070097 String desc = KeyMaps.toDescriptiveString(context, id);
Hans Boehm8a4f81c2015-07-09 10:41:25 -070098 if (desc != null) {
Hans Boehm3666e632015-07-27 18:33:12 -070099 SpannableString result = new SpannableString(KeyMaps.toString(context, id));
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700100 Object descSpan = new TtsSpan.TextBuilder(desc).build();
101 result.setSpan(descSpan, 0, result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
102 return result;
103 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700104 return KeyMaps.toString(context, id);
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700105 }
Hans Boehm84614952014-11-25 18:46:17 -0800106 }
107 @Override
108 TokenKind kind() { return TokenKind.OPERATOR; }
109 }
110
Hans Boehm3666e632015-07-27 18:33:12 -0700111 /**
112 * Representation of a (possibly incomplete) numerical constant.
113 * Supports addition and removal of trailing characters; hence mutable.
114 */
Hans Boehm84614952014-11-25 18:46:17 -0800115 private static class Constant extends Token implements Cloneable {
116 private boolean mSawDecimal;
Hans Boehm3666e632015-07-27 18:33:12 -0700117 private String mWhole; // String preceding decimal point.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700118 private String mFraction; // String after decimal point.
119 private int mExponent; // Explicit exponent, only generated through addExponent.
Hans Boehm84614952014-11-25 18:46:17 -0800120
121 Constant() {
122 mWhole = "";
123 mFraction = "";
Hans Boehm0b9806f2015-06-29 16:07:15 -0700124 mSawDecimal = false;
125 mExponent = 0;
Hans Boehm84614952014-11-25 18:46:17 -0800126 };
127
128 Constant(DataInput in) throws IOException {
129 mWhole = in.readUTF();
130 mSawDecimal = in.readBoolean();
131 mFraction = in.readUTF();
Hans Boehm0b9806f2015-06-29 16:07:15 -0700132 mExponent = in.readInt();
Hans Boehm84614952014-11-25 18:46:17 -0800133 }
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);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700141 out.writeInt(mExponent);
Hans Boehm84614952014-11-25 18:46:17 -0800142 }
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.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700148 // Assumes that this constant does not have an exponent.
Hans Boehm3666e632015-07-27 18:33:12 -0700149 public boolean add(int id) {
Hans Boehm84614952014-11-25 18:46:17 -0800150 if (id == R.id.dec_point) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700151 if (mSawDecimal || mExponent != 0) return false;
Hans Boehm84614952014-11-25 18:46:17 -0800152 mSawDecimal = true;
153 return true;
154 }
155 int val = KeyMaps.digVal(id);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700156 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 }
Hans Boehm84614952014-11-25 18:46:17 -0800168 if (mSawDecimal) {
169 mFraction += val;
170 } else {
171 mWhole += val;
172 }
173 return true;
174 }
175
Hans Boehm3666e632015-07-27 18:33:12 -0700176 public void addExponent(int exp) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700177 // Note that adding a 0 exponent is a no-op. That's OK.
178 mExponent = exp;
179 }
180
Hans Boehm3666e632015-07-27 18:33:12 -0700181 /**
182 * Undo the last add or remove last exponent digit.
183 * Assumes the constant is nonempty.
184 */
185 public void delete() {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700186 if (mExponent != 0) {
187 mExponent /= 10;
188 // Once zero, it can only be added back with addExponent.
189 } else if (!mFraction.isEmpty()) {
Hans Boehm84614952014-11-25 18:46:17 -0800190 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
Hans Boehm3666e632015-07-27 18:33:12 -0700198 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800199 return (mSawDecimal == false && mWhole.isEmpty());
200 }
201
Hans Boehm3666e632015-07-27 18:33:12 -0700202 /**
203 * Produce human-readable string representation of constant, as typed.
204 * Result is internationalized.
205 */
Hans Boehm84614952014-11-25 18:46:17 -0800206 @Override
207 public String toString() {
208 String result = mWhole;
209 if (mSawDecimal) {
Hans Boehm013969e2015-04-13 20:29:47 -0700210 result += '.';
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700211 result += mFraction;
212 }
Hans Boehm0b9806f2015-06-29 16:07:15 -0700213 if (mExponent != 0) {
214 result += "E" + mExponent;
215 }
Hans Boehm013969e2015-04-13 20:29:47 -0700216 return KeyMaps.translateResult(result);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700217 }
218
Hans Boehm3666e632015-07-27 18:33:12 -0700219 /**
220 * Return BoundedRational representation of constant.
221 * Never null.
222 */
Hans Boehm682ff5e2015-03-09 14:40:25 -0700223 public BoundedRational toRational() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700224 String whole = mWhole;
225 if (whole.isEmpty()) whole = "0";
226 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700227 BigInteger den = BigInteger.TEN.pow(mFraction.length());
Hans Boehm0b9806f2015-06-29 16:07:15 -0700228 if (mExponent > 0) {
229 num = num.multiply(BigInteger.TEN.pow(mExponent));
230 }
231 if (mExponent < 0) {
232 den = den.multiply(BigInteger.TEN.pow(-mExponent));
233 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700234 return new BoundedRational(num, den);
235 }
236
Hans Boehm84614952014-11-25 18:46:17 -0800237 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700238 public CharSequence toCharSequence(Context context) {
Hans Boehm84614952014-11-25 18:46:17 -0800239 return toString();
240 }
241
242 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700243 public TokenKind kind() {
244 return TokenKind.CONSTANT;
245 }
Hans Boehm84614952014-11-25 18:46:17 -0800246
247 // Override clone to make it public
248 @Override
249 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700250 Constant result = new Constant();
251 result.mWhole = mWhole;
252 result.mFraction = mFraction;
253 result.mSawDecimal = mSawDecimal;
254 result.mExponent = mExponent;
255 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800256 }
257 }
258
Hans Boehm3666e632015-07-27 18:33:12 -0700259 // Hash maps used to detect duplicate subexpressions when we write out CalculatorExprs and
260 // read them back in.
Hans Boehm84614952014-11-25 18:46:17 -0800261 private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
Hans Boehm3666e632015-07-27 18:33:12 -0700262 new ThreadLocal<IdentityHashMap<CR,Integer>>();
Hans Boehm84614952014-11-25 18:46:17 -0800263 // Maps expressions to indices on output
264 private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
Hans Boehm3666e632015-07-27 18:33:12 -0700265 new ThreadLocal<HashMap<Integer,PreEval>>();
Hans Boehm84614952014-11-25 18:46:17 -0800266 // Maps expressions to indices on output
Hans Boehm3666e632015-07-27 18:33:12 -0700267 private static final ThreadLocal<Integer> exprIndex = new ThreadLocal<Integer>();
Hans Boehm84614952014-11-25 18:46:17 -0800268
Hans Boehm3666e632015-07-27 18:33:12 -0700269 /**
270 * Prepare for expression output.
271 * Initializes map that will lbe used to avoid duplicating shared subexpressions.
272 * This avoids a potential exponential blow-up in the expression size.
273 */
274 public static void initExprOutput() {
Hans Boehm84614952014-11-25 18:46:17 -0800275 outMap.set(new IdentityHashMap<CR,Integer>());
276 exprIndex.set(Integer.valueOf(0));
277 }
278
Hans Boehm3666e632015-07-27 18:33:12 -0700279 /**
280 * Prepare for expression input.
281 * Initializes map that will be used to reconstruct shared subexpressions.
282 */
283 public static void initExprInput() {
Hans Boehm84614952014-11-25 18:46:17 -0800284 inMap.set(new HashMap<Integer,PreEval>());
285 }
286
Hans Boehm3666e632015-07-27 18:33:12 -0700287 /**
288 * The "token" class for previously evaluated subexpressions.
289 * We treat previously evaluated subexpressions as tokens. These are inserted when we either
290 * continue an expression after evaluating some of it, or copy an expression and paste it back
291 * in.
292 * The representation includes both CR and possibly BoundedRational values. In order to
293 * support saving and restoring, we also include the underlying expression itself, and the
294 * context (currently just degree mode) used to evaluate it. The short string representation
295 * is also stored in order to avoid potentially expensive recomputation in the UI thread.
296 */
Hans Boehm84614952014-11-25 18:46:17 -0800297 private static class PreEval extends Token {
Hans Boehm3666e632015-07-27 18:33:12 -0700298 public final CR value;
299 public final BoundedRational ratValue;
Hans Boehm84614952014-11-25 18:46:17 -0800300 private final CalculatorExpr mExpr;
301 private final EvalContext mContext;
Hans Boehm50ed3202015-06-09 14:35:49 -0700302 private final String mShortRep; // Not internationalized.
Hans Boehm013969e2015-04-13 20:29:47 -0700303 PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr,
304 EvalContext ec, String shortRep) {
Hans Boehm3666e632015-07-27 18:33:12 -0700305 value = val;
306 ratValue = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800307 mExpr = expr;
308 mContext = ec;
309 mShortRep = shortRep;
310 }
311 // In writing out PreEvals, we are careful to avoid writing
312 // out duplicates. We assume that two expressions are
313 // duplicates if they have the same mVal. This avoids a
314 // potential exponential blow up in certain off cases and
315 // redundant evaluation after reading them back in.
316 // The parameter hash map maps expressions we've seen
317 // before to their index.
318 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700319 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800320 out.writeByte(TokenKind.PRE_EVAL.ordinal());
Hans Boehm3666e632015-07-27 18:33:12 -0700321 Integer index = outMap.get().get(value);
Hans Boehm84614952014-11-25 18:46:17 -0800322 if (index == null) {
323 int nextIndex = exprIndex.get() + 1;
324 exprIndex.set(nextIndex);
Hans Boehm3666e632015-07-27 18:33:12 -0700325 outMap.get().put(value, nextIndex);
Hans Boehm84614952014-11-25 18:46:17 -0800326 out.writeInt(nextIndex);
327 mExpr.write(out);
328 mContext.write(out);
329 out.writeUTF(mShortRep);
330 } else {
331 // Just write out the index
332 out.writeInt(index);
333 }
334 }
335 PreEval(DataInput in) throws IOException {
336 int index = in.readInt();
337 PreEval prev = inMap.get().get(index);
338 if (prev == null) {
339 mExpr = new CalculatorExpr(in);
Hans Boehmc023b732015-04-29 11:30:47 -0700340 mContext = new EvalContext(in, mExpr.mExpr.size());
Hans Boehm3666e632015-07-27 18:33:12 -0700341 // Recompute other fields We currently do this in the UI thread, but we only
342 // create PreEval expressions that were previously successfully evaluated, and
343 // thus don't diverge. We also only evaluate to a constructive real, which
344 // involves substantial work only in fairly contrived circumstances.
Hans Boehm84614952014-11-25 18:46:17 -0800345 // TODO: Deal better with slow evaluations.
Hans Boehmc023b732015-04-29 11:30:47 -0700346 EvalRet res = null;
347 try {
348 res = mExpr.evalExpr(0, mContext);
349 } catch (SyntaxException e) {
350 // Should be impossible, since we only write out
351 // expressions that can be evaluated.
352 Log.e("Calculator", "Unexpected syntax exception" + e);
353 }
Hans Boehm3666e632015-07-27 18:33:12 -0700354 value = res.val;
355 ratValue = res.ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800356 mShortRep = in.readUTF();
357 inMap.get().put(index, this);
358 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700359 value = prev.value;
360 ratValue = prev.ratValue;
Hans Boehm84614952014-11-25 18:46:17 -0800361 mExpr = prev.mExpr;
362 mContext = prev.mContext;
363 mShortRep = prev.mShortRep;
364 }
365 }
366 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700367 public CharSequence toCharSequence(Context context) {
Hans Boehm50ed3202015-06-09 14:35:49 -0700368 return KeyMaps.translateResult(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800369 }
370 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700371 public TokenKind kind() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700372 return TokenKind.PRE_EVAL;
373 }
Hans Boehm3666e632015-07-27 18:33:12 -0700374 public boolean hasEllipsis() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700375 return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
376 }
Hans Boehm84614952014-11-25 18:46:17 -0800377 }
378
Hans Boehm3666e632015-07-27 18:33:12 -0700379 /**
380 * Read token from in.
381 */
382 public static Token newToken(DataInput in) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800383 TokenKind kind = tokenKindValues[in.readByte()];
384 switch(kind) {
385 case CONSTANT:
386 return new Constant(in);
387 case OPERATOR:
388 return new Operator(in);
389 case PRE_EVAL:
390 return new PreEval(in);
391 default: throw new IOException("Bad save file format");
392 }
393 }
394
395 CalculatorExpr() {
396 mExpr = new ArrayList<Token>();
397 }
398
399 private CalculatorExpr(ArrayList<Token> expr) {
400 mExpr = expr;
401 }
402
Hans Boehm3666e632015-07-27 18:33:12 -0700403 /**
404 * Construct CalculatorExpr, by reading it from in.
405 */
Hans Boehm84614952014-11-25 18:46:17 -0800406 CalculatorExpr(DataInput in) throws IOException {
407 mExpr = new ArrayList<Token>();
408 int size = in.readInt();
409 for (int i = 0; i < size; ++i) {
410 mExpr.add(newToken(in));
411 }
412 }
413
Hans Boehm3666e632015-07-27 18:33:12 -0700414 /**
415 * Write this expression to out.
416 */
417 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800418 int size = mExpr.size();
419 out.writeInt(size);
420 for (int i = 0; i < size; ++i) {
421 mExpr.get(i).write(out);
422 }
423 }
424
Hans Boehm3666e632015-07-27 18:33:12 -0700425 /**
426 * Does this expression end with a numeric constant?
427 * As opposed to an operator or preevaluated expression.
428 */
Hans Boehm0b9806f2015-06-29 16:07:15 -0700429 boolean hasTrailingConstant() {
430 int s = mExpr.size();
431 if (s == 0) {
432 return false;
433 }
434 Token t = mExpr.get(s-1);
435 return t instanceof Constant;
436 }
437
Hans Boehm3666e632015-07-27 18:33:12 -0700438 /**
439 * Does this expression end with a binary operator?
440 */
Hans Boehm84614952014-11-25 18:46:17 -0800441 private boolean hasTrailingBinary() {
442 int s = mExpr.size();
443 if (s == 0) return false;
444 Token t = mExpr.get(s-1);
445 if (!(t instanceof Operator)) return false;
446 Operator o = (Operator)t;
Hans Boehm3666e632015-07-27 18:33:12 -0700447 return (KeyMaps.isBinary(o.id));
Hans Boehm84614952014-11-25 18:46:17 -0800448 }
449
Hans Boehm017de982015-06-10 17:46:03 -0700450 /**
451 * Append press of button with given id to expression.
452 * If the insertion would clearly result in a syntax error, either just return false
453 * and do nothing, or make an adjustment to avoid the problem. We do the latter only
454 * for unambiguous consecutive binary operators, in which case we delete the first
455 * operator.
456 */
Hans Boehm84614952014-11-25 18:46:17 -0800457 boolean add(int id) {
458 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700459 final int d = KeyMaps.digVal(id);
460 final boolean binary = KeyMaps.isBinary(id);
Hans Boehm017de982015-06-10 17:46:03 -0700461 Token lastTok = s == 0 ? null : mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700462 int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
Hans Boehm017de982015-06-10 17:46:03 -0700463 // Quietly replace a trailing binary operator with another one, unless the second
464 // operator is minus, in which case we just allow it as a unary minus.
465 if (binary && !KeyMaps.isPrefix(id)) {
466 if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
467 || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
468 return false;
469 }
470 while (hasTrailingBinary()) {
471 delete();
472 }
473 // s invalid and not used below.
Hans Boehm84614952014-11-25 18:46:17 -0800474 }
Hans Boehm3666e632015-07-27 18:33:12 -0700475 final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
Hans Boehm84614952014-11-25 18:46:17 -0800476 if (isConstPiece) {
Hans Boehm017de982015-06-10 17:46:03 -0700477 // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
Hans Boehm84614952014-11-25 18:46:17 -0800478 if (s == 0) {
479 mExpr.add(new Constant());
480 s++;
481 } else {
482 Token last = mExpr.get(s-1);
483 if(!(last instanceof Constant)) {
Hans Boehm017de982015-06-10 17:46:03 -0700484 if (last instanceof PreEval) {
485 // Add explicit multiplication to avoid confusing display.
486 mExpr.add(new Operator(R.id.op_mul));
487 s++;
Hans Boehm84614952014-11-25 18:46:17 -0800488 }
489 mExpr.add(new Constant());
490 s++;
491 }
492 }
493 return ((Constant)(mExpr.get(s-1))).add(id);
494 } else {
495 mExpr.add(new Operator(id));
496 return true;
497 }
498 }
499
Hans Boehm017de982015-06-10 17:46:03 -0700500 /**
Hans Boehm0b9806f2015-06-29 16:07:15 -0700501 * Add exponent to the constant at the end of the expression.
502 * Assumes there is a constant at the end of the expression.
503 */
504 void addExponent(int exp) {
505 Token lastTok = mExpr.get(mExpr.size() - 1);
506 ((Constant) lastTok).addExponent(exp);
507 }
508
509 /**
Hans Boehm017de982015-06-10 17:46:03 -0700510 * Remove trailing op_add and op_sub operators.
511 */
512 void removeTrailingAdditiveOperators() {
513 while (true) {
514 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700515 if (s == 0) {
516 break;
517 }
Hans Boehm017de982015-06-10 17:46:03 -0700518 Token lastTok = mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700519 if (!(lastTok instanceof Operator)) {
520 break;
521 }
522 int lastOp = ((Operator) lastTok).id;
523 if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
524 break;
525 }
Hans Boehm017de982015-06-10 17:46:03 -0700526 delete();
527 }
528 }
529
Hans Boehm3666e632015-07-27 18:33:12 -0700530 /**
531 * Append the contents of the argument expression.
532 * It is assumed that the argument expression will not change, and thus its pieces can be
533 * reused directly.
534 */
535 public void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700536 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800537 int s2 = expr2.mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700538 // Check that we're not concatenating Constant or PreEval tokens, since the result would
539 // look like a single constant, with very mysterious results for the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700540 if (s != 0 && s2 != 0) {
541 Token last = mExpr.get(s-1);
542 Token first = expr2.mExpr.get(0);
543 if (!(first instanceof Operator) && !(last instanceof Operator)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700544 // Fudge it by adding an explicit multiplication. We would have interpreted it as
545 // such anyway, and this makes it recognizable to the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700546 mExpr.add(new Operator(R.id.op_mul));
547 }
548 }
Hans Boehm84614952014-11-25 18:46:17 -0800549 for (int i = 0; i < s2; ++i) {
550 mExpr.add(expr2.mExpr.get(i));
551 }
552 }
553
Hans Boehm3666e632015-07-27 18:33:12 -0700554 /**
555 * Undo the last key addition, if any.
556 * Or possibly remove a trailing exponent digit.
557 */
558 public void delete() {
559 final int s = mExpr.size();
560 if (s == 0) {
561 return;
562 }
Hans Boehm84614952014-11-25 18:46:17 -0800563 Token last = mExpr.get(s-1);
564 if (last instanceof Constant) {
565 Constant c = (Constant)last;
566 c.delete();
Hans Boehm3666e632015-07-27 18:33:12 -0700567 if (!c.isEmpty()) {
568 return;
569 }
Hans Boehm84614952014-11-25 18:46:17 -0800570 }
571 mExpr.remove(s-1);
572 }
573
Hans Boehm3666e632015-07-27 18:33:12 -0700574 /**
575 * Remove all tokens from the expression.
576 */
577 public void clear() {
Hans Boehm84614952014-11-25 18:46:17 -0800578 mExpr.clear();
579 }
580
Hans Boehm3666e632015-07-27 18:33:12 -0700581 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800582 return mExpr.isEmpty();
583 }
584
Hans Boehm3666e632015-07-27 18:33:12 -0700585 /**
586 * Returns a logical deep copy of the CalculatorExpr.
587 * Operator and PreEval tokens are immutable, and thus aren't really copied.
588 */
Hans Boehm84614952014-11-25 18:46:17 -0800589 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700590 CalculatorExpr result = new CalculatorExpr();
Hans Boehm84614952014-11-25 18:46:17 -0800591 for (Token t: mExpr) {
592 if (t instanceof Constant) {
Hans Boehm3666e632015-07-27 18:33:12 -0700593 result.mExpr.add((Token)(((Constant)t).clone()));
Hans Boehm84614952014-11-25 18:46:17 -0800594 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700595 result.mExpr.add(t);
Hans Boehm84614952014-11-25 18:46:17 -0800596 }
597 }
Hans Boehm3666e632015-07-27 18:33:12 -0700598 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800599 }
600
601 // Am I just a constant?
Hans Boehm3666e632015-07-27 18:33:12 -0700602 public boolean isConstant() {
603 if (mExpr.size() != 1) {
604 return false;
605 }
Hans Boehm84614952014-11-25 18:46:17 -0800606 return mExpr.get(0) instanceof Constant;
607 }
608
Hans Boehm3666e632015-07-27 18:33:12 -0700609 /**
610 * Return a new expression consisting of a single token representing the current pre-evaluated
611 * expression.
612 * The caller supplies the value, degree mode, and short string representation, which must
613 * have been previously computed. Thus this is guaranteed to terminate reasonably quickly.
614 */
615 public CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800616 boolean dm, String sr) {
617 CalculatorExpr result = new CalculatorExpr();
Hans Boehm3666e632015-07-27 18:33:12 -0700618 Token t = new PreEval(val, ratVal, new CalculatorExpr((ArrayList<Token>) mExpr.clone()),
619 new EvalContext(dm, mExpr.size()), sr);
Hans Boehm84614952014-11-25 18:46:17 -0800620 result.mExpr.add(t);
621 return result;
622 }
623
Hans Boehm3666e632015-07-27 18:33:12 -0700624 /**
625 * Internal evaluation functions return an EvalRet triple.
626 * We compute rational (BoundedRational) results when possible, both as a performance
627 * optimization, and to detect errors exactly when we can.
628 */
629 private static class EvalRet {
630 public int pos; // Next position (expression index) to be parsed.
631 public final CR val; // Constructive Real result of evaluating subexpression.
632 public final BoundedRational ratVal; // Exact Rational value or null.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700633 EvalRet(int p, CR v, BoundedRational r) {
Hans Boehm3666e632015-07-27 18:33:12 -0700634 pos = p;
635 val = v;
636 ratVal = r;
Hans Boehm84614952014-11-25 18:46:17 -0800637 }
638 }
639
Hans Boehm3666e632015-07-27 18:33:12 -0700640 /**
641 * Internal evaluation functions take an EvalContext argument.
642 */
Hans Boehm84614952014-11-25 18:46:17 -0800643 private static class EvalContext {
Hans Boehm3666e632015-07-27 18:33:12 -0700644 public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
Hans Boehmc023b732015-04-29 11:30:47 -0700645 public final boolean mDegreeMode;
Hans Boehm84614952014-11-25 18:46:17 -0800646 // If we add any other kinds of evaluation modes, they go here.
Hans Boehmc023b732015-04-29 11:30:47 -0700647 EvalContext(boolean degreeMode, int len) {
Hans Boehm84614952014-11-25 18:46:17 -0800648 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700649 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800650 }
Hans Boehmc023b732015-04-29 11:30:47 -0700651 EvalContext(DataInput in, int len) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800652 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700653 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800654 }
655 void write(DataOutput out) throws IOException {
656 out.writeBoolean(mDegreeMode);
657 }
658 }
659
660 private final CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
661
662 private final CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
663
664 private CR toRadians(CR x, EvalContext ec) {
665 if (ec.mDegreeMode) {
666 return x.multiply(RADIANS_PER_DEGREE);
667 } else {
668 return x;
669 }
670 }
671
672 private CR fromRadians(CR x, EvalContext ec) {
673 if (ec.mDegreeMode) {
674 return x.multiply(DEGREES_PER_RADIAN);
675 } else {
676 return x;
677 }
678 }
679
Hans Boehm3666e632015-07-27 18:33:12 -0700680 // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
681 // error. We expect that to be caught in eval below.
Hans Boehm84614952014-11-25 18:46:17 -0800682
Hans Boehmc023b732015-04-29 11:30:47 -0700683 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800684 Token t = mExpr.get(i);
Hans Boehm3666e632015-07-27 18:33:12 -0700685 if (!(t instanceof Operator)) {
686 return false;
687 }
688 return ((Operator)(t)).id == op;
Hans Boehm84614952014-11-25 18:46:17 -0800689 }
690
Hans Boehmc023b732015-04-29 11:30:47 -0700691 private boolean isOperator(int i, int op, EvalContext ec) {
Hans Boehm3666e632015-07-27 18:33:12 -0700692 if (i >= ec.mPrefixLength) {
693 return false;
694 }
Hans Boehmc023b732015-04-29 11:30:47 -0700695 return isOperatorUnchecked(i, op);
696 }
697
Hans Boehm3666e632015-07-27 18:33:12 -0700698 public static class SyntaxException extends Exception {
Hans Boehmc023b732015-04-29 11:30:47 -0700699 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800700 super();
701 }
Hans Boehmc023b732015-04-29 11:30:47 -0700702 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800703 super(s);
704 }
705 }
706
Hans Boehm3666e632015-07-27 18:33:12 -0700707 // The following functions all evaluate some kind of expression starting at position i in
708 // mExpr in a specified evaluation context. They return both the expression value (as
709 // constructive real and, if applicable, as BoundedRational) and the position of the next token
Hans Boehm84614952014-11-25 18:46:17 -0800710 // that was not used as part of the evaluation.
Hans Boehm3666e632015-07-27 18:33:12 -0700711 // This is essentially a simple recursive descent parser combined with expression evaluation.
712
Hans Boehmc023b732015-04-29 11:30:47 -0700713 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700714 final Token t = mExpr.get(i);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700715 BoundedRational ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800716 if (t instanceof Constant) {
717 Constant c = (Constant)t;
Hans Boehm0b9806f2015-06-29 16:07:15 -0700718 ratVal = c.toRational();
Hans Boehm3666e632015-07-27 18:33:12 -0700719 return new EvalRet(i+1, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800720 }
721 if (t instanceof PreEval) {
Hans Boehm3666e632015-07-27 18:33:12 -0700722 final PreEval p = (PreEval)t;
723 return new EvalRet(i+1, p.value, p.ratValue);
Hans Boehm84614952014-11-25 18:46:17 -0800724 }
725 EvalRet argVal;
Hans Boehm3666e632015-07-27 18:33:12 -0700726 switch(((Operator)(t)).id) {
Hans Boehm84614952014-11-25 18:46:17 -0800727 case R.id.const_pi:
728 return new EvalRet(i+1, CR.PI, null);
729 case R.id.const_e:
Hans Boehm4db31b42015-05-31 12:19:05 -0700730 return new EvalRet(i+1, REAL_E, null);
Hans Boehm84614952014-11-25 18:46:17 -0800731 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700732 // Seems to have highest precedence.
733 // Does not add implicit paren.
734 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700735 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700736 argVal = evalUnary(i+2, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700737 ratVal = BoundedRational.sqrt(BoundedRational.negate(argVal.ratVal));
738 if (ratVal != null) {
739 break;
740 }
741 return new EvalRet(argVal.pos,
742 argVal.val.negate().sqrt(), null);
Hans Boehmfbcef702015-04-27 18:07:47 -0700743 } else {
744 argVal = evalUnary(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700745 ratVal = BoundedRational.sqrt(argVal.ratVal);
746 if (ratVal != null) {
747 break;
748 }
749 return new EvalRet(argVal.pos, argVal.val.sqrt(), null);
Hans Boehmfbcef702015-04-27 18:07:47 -0700750 }
Hans Boehm84614952014-11-25 18:46:17 -0800751 case R.id.lparen:
752 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700753 if (isOperator(argVal.pos, R.id.rparen, ec)) {
754 argVal.pos++;
755 }
756 return new EvalRet(argVal.pos, argVal.val, argVal.ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800757 case R.id.fun_sin:
758 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700759 if (isOperator(argVal.pos, R.id.rparen, ec)) {
760 argVal.pos++;
761 }
762 ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.ratVal)
763 : BoundedRational.sin(argVal.ratVal);
764 if (ratVal != null) {
765 break;
766 }
767 return new EvalRet(argVal.pos, toRadians(argVal.val,ec).sin(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800768 case R.id.fun_cos:
769 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700770 if (isOperator(argVal.pos, R.id.rparen, ec)) {
771 argVal.pos++;
772 }
773 ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.ratVal)
774 : BoundedRational.cos(argVal.ratVal);
775 if (ratVal != null) {
776 break;
777 }
778 return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800779 case R.id.fun_tan:
780 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700781 if (isOperator(argVal.pos, R.id.rparen, ec)) {
782 argVal.pos++;
783 }
784 ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.ratVal)
785 : BoundedRational.tan(argVal.ratVal);
786 if (ratVal != null) {
787 break;
788 }
789 CR argCR = toRadians(argVal.val, ec);
790 return new EvalRet(argVal.pos, argCR.sin().divide(argCR.cos()), null);
Hans Boehm84614952014-11-25 18:46:17 -0800791 case R.id.fun_ln:
792 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700793 if (isOperator(argVal.pos, R.id.rparen, ec)) {
794 argVal.pos++;
795 }
796 ratVal = BoundedRational.ln(argVal.ratVal);
797 if (ratVal != null) {
798 break;
799 }
800 return new EvalRet(argVal.pos, argVal.val.ln(), null);
Hans Boehm4db31b42015-05-31 12:19:05 -0700801 case R.id.fun_exp:
802 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700803 if (isOperator(argVal.pos, R.id.rparen, ec)) {
804 argVal.pos++;
805 }
806 ratVal = BoundedRational.exp(argVal.ratVal);
807 if (ratVal != null) {
808 break;
809 }
810 return new EvalRet(argVal.pos, argVal.val.exp(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800811 case R.id.fun_log:
812 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700813 if (isOperator(argVal.pos, R.id.rparen, ec)) {
814 argVal.pos++;
815 }
816 ratVal = BoundedRational.log(argVal.ratVal);
817 if (ratVal != null) {
818 break;
819 }
820 return new EvalRet(argVal.pos, argVal.val.ln().divide(CR.valueOf(10).ln()), null);
Hans Boehm84614952014-11-25 18:46:17 -0800821 case R.id.fun_arcsin:
822 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700823 if (isOperator(argVal.pos, R.id.rparen, ec)) {
824 argVal.pos++;
825 }
826 ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.ratVal)
827 : BoundedRational.asin(argVal.ratVal);
828 if (ratVal != null) {
829 break;
830 }
831 return new EvalRet(argVal.pos,
832 fromRadians(UnaryCRFunction.asinFunction.execute(argVal.val),ec), null);
Hans Boehm84614952014-11-25 18:46:17 -0800833 case R.id.fun_arccos:
834 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700835 if (isOperator(argVal.pos, R.id.rparen, ec)) {
836 argVal.pos++;
837 }
838 ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.ratVal)
839 : BoundedRational.acos(argVal.ratVal);
840 if (ratVal != null) {
841 break;
842 }
843 return new EvalRet(argVal.pos,
844 fromRadians(UnaryCRFunction.acosFunction.execute(argVal.val),ec), null);
Hans Boehm84614952014-11-25 18:46:17 -0800845 case R.id.fun_arctan:
846 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700847 if (isOperator(argVal.pos, R.id.rparen, ec)) {
848 argVal.pos++;
849 }
850 ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.ratVal)
851 : BoundedRational.atan(argVal.ratVal);
852 if (ratVal != null) {
853 break;
854 }
855 return new EvalRet(argVal.pos,
856 fromRadians(UnaryCRFunction.atanFunction.execute(argVal.val),ec), null);
Hans Boehm84614952014-11-25 18:46:17 -0800857 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700858 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800859 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700860 // We have a rational value.
Hans Boehm3666e632015-07-27 18:33:12 -0700861 return new EvalRet(argVal.pos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800862 }
863
Hans Boehm3666e632015-07-27 18:33:12 -0700864 /**
865 * Compute an integral power of a constructive real.
866 * Unlike the "general" case using logarithms, this handles a negative base.
867 */
Hans Boehm84614952014-11-25 18:46:17 -0800868 private static CR pow(CR base, BigInteger exp) {
869 if (exp.compareTo(BigInteger.ZERO) < 0) {
870 return pow(base, exp.negate()).inverse();
871 }
Hans Boehm3666e632015-07-27 18:33:12 -0700872 if (exp.equals(BigInteger.ONE)) {
873 return base;
874 }
Hans Boehm84614952014-11-25 18:46:17 -0800875 if (exp.and(BigInteger.ONE).intValue() == 1) {
876 return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
877 }
878 if (exp.equals(BigInteger.ZERO)) {
879 return CR.valueOf(1);
880 }
881 CR tmp = pow(base, exp.shiftRight(1));
882 return tmp.multiply(tmp);
883 }
884
Hans Boehm3666e632015-07-27 18:33:12 -0700885 // Number of bits past binary point to test for integer-ness.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700886 private static final int TEST_PREC = -100;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700887 private static final BigInteger MASK =
888 BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
Hans Boehm4db31b42015-05-31 12:19:05 -0700889 private static final CR REAL_E = CR.valueOf(1).exp();
890 private static final CR REAL_ONE_HUNDREDTH = CR.valueOf(100).inverse();
Hans Boehm3666e632015-07-27 18:33:12 -0700891 private static final BoundedRational RATIONAL_ONE_HUNDREDTH = new BoundedRational(1,100);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700892 private static boolean isApprInt(CR x) {
893 BigInteger appr = x.get_appr(TEST_PREC);
894 return appr.and(MASK).signum() == 0;
895 }
896
Hans Boehm4db31b42015-05-31 12:19:05 -0700897 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700898 final EvalRet tmp = evalUnary(i, ec);
899 int cpos = tmp.pos;
900 CR crVal = tmp.val;
901 BoundedRational ratVal = tmp.ratVal;
Hans Boehm4db31b42015-05-31 12:19:05 -0700902 boolean isFact;
903 boolean isSquared = false;
904 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
905 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
906 isOperator(cpos, R.id.op_pct, ec)) {
907 if (isFact) {
908 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700909 // Assume it was an integer, but we didn't figure it out.
Hans Boehm4db31b42015-05-31 12:19:05 -0700910 // KitKat may have used the Gamma function.
Hans Boehm3666e632015-07-27 18:33:12 -0700911 if (!isApprInt(crVal)) {
Hans Boehm4db31b42015-05-31 12:19:05 -0700912 throw new ArithmeticException("factorial(non-integer)");
913 }
Hans Boehm3666e632015-07-27 18:33:12 -0700914 ratVal = new BoundedRational(crVal.BigIntegerValue());
Hans Boehm682ff5e2015-03-09 14:40:25 -0700915 }
Hans Boehm4db31b42015-05-31 12:19:05 -0700916 ratVal = BoundedRational.fact(ratVal);
Hans Boehm3666e632015-07-27 18:33:12 -0700917 crVal = ratVal.CRValue();
Hans Boehm4db31b42015-05-31 12:19:05 -0700918 } else if (isSquared) {
919 ratVal = BoundedRational.multiply(ratVal, ratVal);
920 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700921 crVal = crVal.multiply(crVal);
Hans Boehm4db31b42015-05-31 12:19:05 -0700922 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700923 crVal = ratVal.CRValue();
Hans Boehm4db31b42015-05-31 12:19:05 -0700924 }
925 } else /* percent */ {
926 ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH);
927 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700928 crVal = crVal.multiply(REAL_ONE_HUNDREDTH);
Hans Boehm4db31b42015-05-31 12:19:05 -0700929 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700930 crVal = ratVal.CRValue();
Hans Boehm4db31b42015-05-31 12:19:05 -0700931 }
Hans Boehm84614952014-11-25 18:46:17 -0800932 }
Hans Boehm84614952014-11-25 18:46:17 -0800933 ++cpos;
934 }
Hans Boehm3666e632015-07-27 18:33:12 -0700935 return new EvalRet(cpos, crVal, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800936 }
937
Hans Boehmc023b732015-04-29 11:30:47 -0700938 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700939 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700940 int cpos = result1.pos; // current position
941 CR crVal = result1.val; // value so far
942 BoundedRational ratVal = result1.ratVal; // int value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700943 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700944 final EvalRet exp = evalSignedFactor(cpos + 1, ec);
945 cpos = exp.pos;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700946 // Try completely rational evaluation first.
Hans Boehm3666e632015-07-27 18:33:12 -0700947 ratVal = BoundedRational.pow(ratVal, exp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700948 if (ratVal != null) {
949 return new EvalRet(cpos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800950 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700951 // Power with integer exponent is defined for negative base.
952 // Thus we handle that case separately.
953 // We punt if the exponent is an integer computed from irrational
954 // values. That wouldn't work reliably with floating point either.
Hans Boehm3666e632015-07-27 18:33:12 -0700955 BigInteger int_exp = BoundedRational.asBigInteger(exp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700956 if (int_exp != null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700957 crVal = pow(crVal, int_exp);
Hans Boehm84614952014-11-25 18:46:17 -0800958 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700959 crVal = crVal.ln().multiply(exp.val).exp();
Hans Boehm84614952014-11-25 18:46:17 -0800960 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700961 ratVal = null;
Hans Boehm84614952014-11-25 18:46:17 -0800962 }
Hans Boehm3666e632015-07-27 18:33:12 -0700963 return new EvalRet(cpos, crVal, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800964 }
965
Hans Boehmc023b732015-04-29 11:30:47 -0700966 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
967 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700968 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800969 EvalRet tmp = evalFactor(cpos, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700970 cpos = tmp.pos;
971 CR crVal = negative ? tmp.val.negate() : tmp.val;
972 BoundedRational ratVal = negative ? BoundedRational.negate(tmp.ratVal)
973 : tmp.ratVal;
974 return new EvalRet(cpos, crVal, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800975 }
976
977 private boolean canStartFactor(int i) {
978 if (i >= mExpr.size()) return false;
979 Token t = mExpr.get(i);
980 if (!(t instanceof Operator)) return true;
Hans Boehm3666e632015-07-27 18:33:12 -0700981 int id = ((Operator)(t)).id;
Hans Boehm84614952014-11-25 18:46:17 -0800982 if (KeyMaps.isBinary(id)) return false;
983 switch (id) {
984 case R.id.op_fact:
985 case R.id.rparen:
986 return false;
987 default:
988 return true;
989 }
990 }
991
Hans Boehmc023b732015-04-29 11:30:47 -0700992 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800993 EvalRet tmp = evalSignedFactor(i, ec);
994 boolean is_mul = false;
995 boolean is_div = false;
Hans Boehm3666e632015-07-27 18:33:12 -0700996 int cpos = tmp.pos; // Current position in expression.
997 CR crVal = tmp.val; // Current value.
998 BoundedRational ratVal = tmp.ratVal; // Current rational value.
Hans Boehmc023b732015-04-29 11:30:47 -0700999 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
1000 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -08001001 || canStartFactor(cpos)) {
1002 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001003 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -08001004 if (is_div) {
Hans Boehm3666e632015-07-27 18:33:12 -07001005 ratVal = BoundedRational.divide(ratVal, tmp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001006 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -07001007 crVal = crVal.divide(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001008 } else {
Hans Boehm3666e632015-07-27 18:33:12 -07001009 crVal = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -08001010 }
1011 } else {
Hans Boehm3666e632015-07-27 18:33:12 -07001012 ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001013 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -07001014 crVal = crVal.multiply(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001015 } else {
Hans Boehm3666e632015-07-27 18:33:12 -07001016 crVal = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -08001017 }
1018 }
Hans Boehm3666e632015-07-27 18:33:12 -07001019 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -08001020 is_mul = is_div = false;
1021 }
Hans Boehm3666e632015-07-27 18:33:12 -07001022 return new EvalRet(cpos, crVal, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -08001023 }
1024
Hans Boehmc023b732015-04-29 11:30:47 -07001025 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -08001026 EvalRet tmp = evalTerm(i, ec);
1027 boolean is_plus;
Hans Boehm3666e632015-07-27 18:33:12 -07001028 int cpos = tmp.pos;
1029 CR crVal = tmp.val;
1030 BoundedRational ratVal = tmp.ratVal;
Hans Boehmc023b732015-04-29 11:30:47 -07001031 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
1032 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm84614952014-11-25 18:46:17 -08001033 tmp = evalTerm(cpos+1, ec);
1034 if (is_plus) {
Hans Boehm3666e632015-07-27 18:33:12 -07001035 ratVal = BoundedRational.add(ratVal, tmp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001036 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -07001037 crVal = crVal.add(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001038 } else {
Hans Boehm3666e632015-07-27 18:33:12 -07001039 crVal = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -08001040 }
1041 } else {
Hans Boehm3666e632015-07-27 18:33:12 -07001042 ratVal = BoundedRational.subtract(ratVal, tmp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001043 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -07001044 crVal = crVal.subtract(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001045 } else {
Hans Boehm3666e632015-07-27 18:33:12 -07001046 crVal = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -08001047 }
1048 }
Hans Boehm3666e632015-07-27 18:33:12 -07001049 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -08001050 }
Hans Boehm3666e632015-07-27 18:33:12 -07001051 return new EvalRet(cpos, crVal, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -08001052 }
1053
Hans Boehm3666e632015-07-27 18:33:12 -07001054 /**
1055 * Externally visible evaluation result.
1056 */
1057 public static class EvalResult {
1058 public final CR val;
1059 public final BoundedRational ratVal;
1060 EvalResult (CR v, BoundedRational rv) {
1061 val = v;
1062 ratVal = rv;
Hans Boehm84614952014-11-25 18:46:17 -08001063 }
Hans Boehm84614952014-11-25 18:46:17 -08001064 }
1065
Hans Boehme8553762015-06-26 17:56:52 -07001066 /**
1067 * Return the starting position of the sequence of trailing binary operators.
1068 */
1069 private int trailingBinaryOpsStart() {
Hans Boehmc023b732015-04-29 11:30:47 -07001070 int result = mExpr.size();
1071 while (result > 0) {
1072 Token last = mExpr.get(result - 1);
1073 if (!(last instanceof Operator)) break;
1074 Operator o = (Operator)last;
Hans Boehm3666e632015-07-27 18:33:12 -07001075 if (!KeyMaps.isBinary(o.id)) break;
Hans Boehmc023b732015-04-29 11:30:47 -07001076 --result;
1077 }
1078 return result;
1079 }
1080
Hans Boehm3666e632015-07-27 18:33:12 -07001081 /**
1082 * Is the current expression worth evaluating?
1083 */
Hans Boehmc023b732015-04-29 11:30:47 -07001084 public boolean hasInterestingOps() {
Hans Boehme8553762015-06-26 17:56:52 -07001085 int last = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -07001086 int first = 0;
1087 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
1088 // Leading minus is not by itself interesting.
1089 first++;
1090 }
1091 for (int i = first; i < last; ++i) {
1092 Token t1 = mExpr.get(i);
Hans Boehm187d3e92015-06-09 18:04:26 -07001093 if (t1 instanceof Operator
1094 || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
1095 return true;
1096 }
Hans Boehmc023b732015-04-29 11:30:47 -07001097 }
1098 return false;
1099 }
1100
Hans Boehme8553762015-06-26 17:56:52 -07001101 /**
1102 * Evaluate the expression excluding trailing binary operators.
Hans Boehm3666e632015-07-27 18:33:12 -07001103 * Errors result in exceptions, most of which are unchecked. Should not be called
1104 * concurrently with modification of the expression. May take a very long time; avoid calling
1105 * from UI thread.
Hans Boehme8553762015-06-26 17:56:52 -07001106 *
1107 * @param degreeMode use degrees rather than radians
1108 */
1109 EvalResult eval(boolean degreeMode) throws SyntaxException
Hans Boehmc023b732015-04-29 11:30:47 -07001110 // And unchecked exceptions thrown by CR
1111 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -08001112 {
1113 try {
Hans Boehm3666e632015-07-27 18:33:12 -07001114 // We currently never include trailing binary operators, but include other trailing
1115 // operators. Thus we usually, but not always, display results for prefixes of valid
1116 // expressions, and don't generate an error where we previously displayed an instant
1117 // result. This reflects the Android L design.
Hans Boehme8553762015-06-26 17:56:52 -07001118 int prefixLen = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -07001119 EvalContext ec = new EvalContext(degreeMode, prefixLen);
Hans Boehm84614952014-11-25 18:46:17 -08001120 EvalRet res = evalExpr(0, ec);
Hans Boehm3666e632015-07-27 18:33:12 -07001121 if (res.pos != prefixLen) {
Hans Boehmc023b732015-04-29 11:30:47 -07001122 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -07001123 }
Hans Boehm3666e632015-07-27 18:33:12 -07001124 return new EvalResult(res.val, res.ratVal);
Hans Boehm84614952014-11-25 18:46:17 -08001125 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -07001126 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -08001127 }
1128 }
1129
1130 // Produce a string representation of the expression itself
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001131 SpannableStringBuilder toSpannableStringBuilder(Context context) {
1132 SpannableStringBuilder ssb = new SpannableStringBuilder();
Hans Boehm84614952014-11-25 18:46:17 -08001133 for (Token t: mExpr) {
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001134 ssb.append(t.toCharSequence(context));
Hans Boehm84614952014-11-25 18:46:17 -08001135 }
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001136 return ssb;
Hans Boehm84614952014-11-25 18:46:17 -08001137 }
1138}