blob: f8011e5663d2bd4c0537601777df6b0f3ecffcd4 [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 /**
Hans Boehmfa5203c2015-08-17 16:14:52 -0700220 * Return BoundedRational representation of constant, if well-formed.
221 * Result is never null.
Hans Boehm3666e632015-07-27 18:33:12 -0700222 */
Hans Boehmfa5203c2015-08-17 16:14:52 -0700223 public BoundedRational toRational() throws SyntaxException {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700224 String whole = mWhole;
Hans Boehmfa5203c2015-08-17 16:14:52 -0700225 if (whole.isEmpty()) {
226 if (mFraction.isEmpty()) {
227 // Decimal point without digits.
228 throw new SyntaxException();
229 } else {
230 whole = "0";
231 }
232 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700233 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700234 BigInteger den = BigInteger.TEN.pow(mFraction.length());
Hans Boehm0b9806f2015-06-29 16:07:15 -0700235 if (mExponent > 0) {
236 num = num.multiply(BigInteger.TEN.pow(mExponent));
237 }
238 if (mExponent < 0) {
239 den = den.multiply(BigInteger.TEN.pow(-mExponent));
240 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700241 return new BoundedRational(num, den);
242 }
243
Hans Boehm84614952014-11-25 18:46:17 -0800244 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700245 public CharSequence toCharSequence(Context context) {
Hans Boehm84614952014-11-25 18:46:17 -0800246 return toString();
247 }
248
249 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700250 public TokenKind kind() {
251 return TokenKind.CONSTANT;
252 }
Hans Boehm84614952014-11-25 18:46:17 -0800253
254 // Override clone to make it public
255 @Override
256 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700257 Constant result = new Constant();
258 result.mWhole = mWhole;
259 result.mFraction = mFraction;
260 result.mSawDecimal = mSawDecimal;
261 result.mExponent = mExponent;
262 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800263 }
264 }
265
Hans Boehm3666e632015-07-27 18:33:12 -0700266 // Hash maps used to detect duplicate subexpressions when we write out CalculatorExprs and
267 // read them back in.
Hans Boehm84614952014-11-25 18:46:17 -0800268 private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
Hans Boehm3666e632015-07-27 18:33:12 -0700269 new ThreadLocal<IdentityHashMap<CR,Integer>>();
Hans Boehm84614952014-11-25 18:46:17 -0800270 // Maps expressions to indices on output
271 private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
Hans Boehm3666e632015-07-27 18:33:12 -0700272 new ThreadLocal<HashMap<Integer,PreEval>>();
Hans Boehm84614952014-11-25 18:46:17 -0800273 // Maps expressions to indices on output
Hans Boehm3666e632015-07-27 18:33:12 -0700274 private static final ThreadLocal<Integer> exprIndex = new ThreadLocal<Integer>();
Hans Boehm84614952014-11-25 18:46:17 -0800275
Hans Boehm3666e632015-07-27 18:33:12 -0700276 /**
277 * Prepare for expression output.
278 * Initializes map that will lbe used to avoid duplicating shared subexpressions.
279 * This avoids a potential exponential blow-up in the expression size.
280 */
281 public static void initExprOutput() {
Hans Boehm84614952014-11-25 18:46:17 -0800282 outMap.set(new IdentityHashMap<CR,Integer>());
283 exprIndex.set(Integer.valueOf(0));
284 }
285
Hans Boehm3666e632015-07-27 18:33:12 -0700286 /**
287 * Prepare for expression input.
288 * Initializes map that will be used to reconstruct shared subexpressions.
289 */
290 public static void initExprInput() {
Hans Boehm84614952014-11-25 18:46:17 -0800291 inMap.set(new HashMap<Integer,PreEval>());
292 }
293
Hans Boehm3666e632015-07-27 18:33:12 -0700294 /**
295 * The "token" class for previously evaluated subexpressions.
296 * We treat previously evaluated subexpressions as tokens. These are inserted when we either
297 * continue an expression after evaluating some of it, or copy an expression and paste it back
298 * in.
299 * The representation includes both CR and possibly BoundedRational values. In order to
300 * support saving and restoring, we also include the underlying expression itself, and the
301 * context (currently just degree mode) used to evaluate it. The short string representation
302 * is also stored in order to avoid potentially expensive recomputation in the UI thread.
303 */
Hans Boehm84614952014-11-25 18:46:17 -0800304 private static class PreEval extends Token {
Hans Boehm3666e632015-07-27 18:33:12 -0700305 public final CR value;
306 public final BoundedRational ratValue;
Hans Boehm84614952014-11-25 18:46:17 -0800307 private final CalculatorExpr mExpr;
308 private final EvalContext mContext;
Hans Boehm50ed3202015-06-09 14:35:49 -0700309 private final String mShortRep; // Not internationalized.
Hans Boehm013969e2015-04-13 20:29:47 -0700310 PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr,
311 EvalContext ec, String shortRep) {
Hans Boehm3666e632015-07-27 18:33:12 -0700312 value = val;
313 ratValue = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800314 mExpr = expr;
315 mContext = ec;
316 mShortRep = shortRep;
317 }
318 // In writing out PreEvals, we are careful to avoid writing
319 // out duplicates. We assume that two expressions are
Hans Boehm8afd0f82015-07-30 17:00:33 -0700320 // duplicates if they have the same CR value. This avoids a
Hans Boehm84614952014-11-25 18:46:17 -0800321 // potential exponential blow up in certain off cases and
322 // redundant evaluation after reading them back in.
323 // The parameter hash map maps expressions we've seen
324 // before to their index.
325 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700326 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800327 out.writeByte(TokenKind.PRE_EVAL.ordinal());
Hans Boehm3666e632015-07-27 18:33:12 -0700328 Integer index = outMap.get().get(value);
Hans Boehm84614952014-11-25 18:46:17 -0800329 if (index == null) {
330 int nextIndex = exprIndex.get() + 1;
331 exprIndex.set(nextIndex);
Hans Boehm3666e632015-07-27 18:33:12 -0700332 outMap.get().put(value, nextIndex);
Hans Boehm84614952014-11-25 18:46:17 -0800333 out.writeInt(nextIndex);
334 mExpr.write(out);
335 mContext.write(out);
336 out.writeUTF(mShortRep);
337 } else {
338 // Just write out the index
339 out.writeInt(index);
340 }
341 }
342 PreEval(DataInput in) throws IOException {
343 int index = in.readInt();
344 PreEval prev = inMap.get().get(index);
345 if (prev == null) {
346 mExpr = new CalculatorExpr(in);
Hans Boehmc023b732015-04-29 11:30:47 -0700347 mContext = new EvalContext(in, mExpr.mExpr.size());
Hans Boehm3666e632015-07-27 18:33:12 -0700348 // Recompute other fields We currently do this in the UI thread, but we only
349 // create PreEval expressions that were previously successfully evaluated, and
350 // thus don't diverge. We also only evaluate to a constructive real, which
351 // involves substantial work only in fairly contrived circumstances.
Hans Boehm84614952014-11-25 18:46:17 -0800352 // TODO: Deal better with slow evaluations.
Hans Boehmc023b732015-04-29 11:30:47 -0700353 EvalRet res = null;
354 try {
355 res = mExpr.evalExpr(0, mContext);
356 } catch (SyntaxException e) {
357 // Should be impossible, since we only write out
358 // expressions that can be evaluated.
359 Log.e("Calculator", "Unexpected syntax exception" + e);
360 }
Hans Boehm3666e632015-07-27 18:33:12 -0700361 value = res.val;
362 ratValue = res.ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800363 mShortRep = in.readUTF();
364 inMap.get().put(index, this);
365 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700366 value = prev.value;
367 ratValue = prev.ratValue;
Hans Boehm84614952014-11-25 18:46:17 -0800368 mExpr = prev.mExpr;
369 mContext = prev.mContext;
370 mShortRep = prev.mShortRep;
371 }
372 }
373 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700374 public CharSequence toCharSequence(Context context) {
Hans Boehm50ed3202015-06-09 14:35:49 -0700375 return KeyMaps.translateResult(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800376 }
377 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700378 public TokenKind kind() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700379 return TokenKind.PRE_EVAL;
380 }
Hans Boehm3666e632015-07-27 18:33:12 -0700381 public boolean hasEllipsis() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700382 return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
383 }
Hans Boehm84614952014-11-25 18:46:17 -0800384 }
385
Hans Boehm3666e632015-07-27 18:33:12 -0700386 /**
387 * Read token from in.
388 */
389 public static Token newToken(DataInput in) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800390 TokenKind kind = tokenKindValues[in.readByte()];
391 switch(kind) {
392 case CONSTANT:
393 return new Constant(in);
394 case OPERATOR:
395 return new Operator(in);
396 case PRE_EVAL:
397 return new PreEval(in);
398 default: throw new IOException("Bad save file format");
399 }
400 }
401
402 CalculatorExpr() {
403 mExpr = new ArrayList<Token>();
404 }
405
406 private CalculatorExpr(ArrayList<Token> expr) {
407 mExpr = expr;
408 }
409
Hans Boehm3666e632015-07-27 18:33:12 -0700410 /**
411 * Construct CalculatorExpr, by reading it from in.
412 */
Hans Boehm84614952014-11-25 18:46:17 -0800413 CalculatorExpr(DataInput in) throws IOException {
414 mExpr = new ArrayList<Token>();
415 int size = in.readInt();
416 for (int i = 0; i < size; ++i) {
417 mExpr.add(newToken(in));
418 }
419 }
420
Hans Boehm3666e632015-07-27 18:33:12 -0700421 /**
422 * Write this expression to out.
423 */
424 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800425 int size = mExpr.size();
426 out.writeInt(size);
427 for (int i = 0; i < size; ++i) {
428 mExpr.get(i).write(out);
429 }
430 }
431
Hans Boehm3666e632015-07-27 18:33:12 -0700432 /**
433 * Does this expression end with a numeric constant?
434 * As opposed to an operator or preevaluated expression.
435 */
Hans Boehm0b9806f2015-06-29 16:07:15 -0700436 boolean hasTrailingConstant() {
437 int s = mExpr.size();
438 if (s == 0) {
439 return false;
440 }
441 Token t = mExpr.get(s-1);
442 return t instanceof Constant;
443 }
444
Hans Boehm3666e632015-07-27 18:33:12 -0700445 /**
446 * Does this expression end with a binary operator?
447 */
Hans Boehm84614952014-11-25 18:46:17 -0800448 private boolean hasTrailingBinary() {
449 int s = mExpr.size();
450 if (s == 0) return false;
451 Token t = mExpr.get(s-1);
452 if (!(t instanceof Operator)) return false;
453 Operator o = (Operator)t;
Hans Boehm3666e632015-07-27 18:33:12 -0700454 return (KeyMaps.isBinary(o.id));
Hans Boehm84614952014-11-25 18:46:17 -0800455 }
456
Hans Boehm017de982015-06-10 17:46:03 -0700457 /**
458 * Append press of button with given id to expression.
459 * If the insertion would clearly result in a syntax error, either just return false
460 * and do nothing, or make an adjustment to avoid the problem. We do the latter only
461 * for unambiguous consecutive binary operators, in which case we delete the first
462 * operator.
463 */
Hans Boehm84614952014-11-25 18:46:17 -0800464 boolean add(int id) {
465 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700466 final int d = KeyMaps.digVal(id);
467 final boolean binary = KeyMaps.isBinary(id);
Hans Boehm017de982015-06-10 17:46:03 -0700468 Token lastTok = s == 0 ? null : mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700469 int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
Hans Boehm017de982015-06-10 17:46:03 -0700470 // Quietly replace a trailing binary operator with another one, unless the second
471 // operator is minus, in which case we just allow it as a unary minus.
472 if (binary && !KeyMaps.isPrefix(id)) {
473 if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
474 || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
475 return false;
476 }
477 while (hasTrailingBinary()) {
478 delete();
479 }
480 // s invalid and not used below.
Hans Boehm84614952014-11-25 18:46:17 -0800481 }
Hans Boehm3666e632015-07-27 18:33:12 -0700482 final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
Hans Boehm84614952014-11-25 18:46:17 -0800483 if (isConstPiece) {
Hans Boehm017de982015-06-10 17:46:03 -0700484 // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
Hans Boehm84614952014-11-25 18:46:17 -0800485 if (s == 0) {
486 mExpr.add(new Constant());
487 s++;
488 } else {
489 Token last = mExpr.get(s-1);
490 if(!(last instanceof Constant)) {
Hans Boehm017de982015-06-10 17:46:03 -0700491 if (last instanceof PreEval) {
492 // Add explicit multiplication to avoid confusing display.
493 mExpr.add(new Operator(R.id.op_mul));
494 s++;
Hans Boehm84614952014-11-25 18:46:17 -0800495 }
496 mExpr.add(new Constant());
497 s++;
498 }
499 }
500 return ((Constant)(mExpr.get(s-1))).add(id);
501 } else {
502 mExpr.add(new Operator(id));
503 return true;
504 }
505 }
506
Hans Boehm017de982015-06-10 17:46:03 -0700507 /**
Hans Boehm0b9806f2015-06-29 16:07:15 -0700508 * Add exponent to the constant at the end of the expression.
509 * Assumes there is a constant at the end of the expression.
510 */
511 void addExponent(int exp) {
512 Token lastTok = mExpr.get(mExpr.size() - 1);
513 ((Constant) lastTok).addExponent(exp);
514 }
515
516 /**
Hans Boehm017de982015-06-10 17:46:03 -0700517 * Remove trailing op_add and op_sub operators.
518 */
519 void removeTrailingAdditiveOperators() {
520 while (true) {
521 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700522 if (s == 0) {
523 break;
524 }
Hans Boehm017de982015-06-10 17:46:03 -0700525 Token lastTok = mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700526 if (!(lastTok instanceof Operator)) {
527 break;
528 }
529 int lastOp = ((Operator) lastTok).id;
530 if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
531 break;
532 }
Hans Boehm017de982015-06-10 17:46:03 -0700533 delete();
534 }
535 }
536
Hans Boehm3666e632015-07-27 18:33:12 -0700537 /**
538 * Append the contents of the argument expression.
539 * It is assumed that the argument expression will not change, and thus its pieces can be
540 * reused directly.
541 */
542 public void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700543 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800544 int s2 = expr2.mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700545 // Check that we're not concatenating Constant or PreEval tokens, since the result would
546 // look like a single constant, with very mysterious results for the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700547 if (s != 0 && s2 != 0) {
548 Token last = mExpr.get(s-1);
549 Token first = expr2.mExpr.get(0);
550 if (!(first instanceof Operator) && !(last instanceof Operator)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700551 // Fudge it by adding an explicit multiplication. We would have interpreted it as
552 // such anyway, and this makes it recognizable to the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700553 mExpr.add(new Operator(R.id.op_mul));
554 }
555 }
Hans Boehm84614952014-11-25 18:46:17 -0800556 for (int i = 0; i < s2; ++i) {
557 mExpr.add(expr2.mExpr.get(i));
558 }
559 }
560
Hans Boehm3666e632015-07-27 18:33:12 -0700561 /**
562 * Undo the last key addition, if any.
563 * Or possibly remove a trailing exponent digit.
564 */
565 public void delete() {
566 final int s = mExpr.size();
567 if (s == 0) {
568 return;
569 }
Hans Boehm84614952014-11-25 18:46:17 -0800570 Token last = mExpr.get(s-1);
571 if (last instanceof Constant) {
572 Constant c = (Constant)last;
573 c.delete();
Hans Boehm3666e632015-07-27 18:33:12 -0700574 if (!c.isEmpty()) {
575 return;
576 }
Hans Boehm84614952014-11-25 18:46:17 -0800577 }
578 mExpr.remove(s-1);
579 }
580
Hans Boehm3666e632015-07-27 18:33:12 -0700581 /**
582 * Remove all tokens from the expression.
583 */
584 public void clear() {
Hans Boehm84614952014-11-25 18:46:17 -0800585 mExpr.clear();
586 }
587
Hans Boehm3666e632015-07-27 18:33:12 -0700588 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800589 return mExpr.isEmpty();
590 }
591
Hans Boehm3666e632015-07-27 18:33:12 -0700592 /**
593 * Returns a logical deep copy of the CalculatorExpr.
594 * Operator and PreEval tokens are immutable, and thus aren't really copied.
595 */
Hans Boehm84614952014-11-25 18:46:17 -0800596 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700597 CalculatorExpr result = new CalculatorExpr();
Hans Boehm84614952014-11-25 18:46:17 -0800598 for (Token t: mExpr) {
599 if (t instanceof Constant) {
Hans Boehm3666e632015-07-27 18:33:12 -0700600 result.mExpr.add((Token)(((Constant)t).clone()));
Hans Boehm84614952014-11-25 18:46:17 -0800601 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700602 result.mExpr.add(t);
Hans Boehm84614952014-11-25 18:46:17 -0800603 }
604 }
Hans Boehm3666e632015-07-27 18:33:12 -0700605 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800606 }
607
608 // Am I just a constant?
Hans Boehm3666e632015-07-27 18:33:12 -0700609 public boolean isConstant() {
610 if (mExpr.size() != 1) {
611 return false;
612 }
Hans Boehm84614952014-11-25 18:46:17 -0800613 return mExpr.get(0) instanceof Constant;
614 }
615
Hans Boehm3666e632015-07-27 18:33:12 -0700616 /**
617 * Return a new expression consisting of a single token representing the current pre-evaluated
618 * expression.
619 * The caller supplies the value, degree mode, and short string representation, which must
620 * have been previously computed. Thus this is guaranteed to terminate reasonably quickly.
621 */
Justin Klaassen0ace4eb2016-02-05 11:38:12 -0800622 public CalculatorExpr abbreviate(CR val, BoundedRational ratVal, boolean dm, String sr) {
Hans Boehm84614952014-11-25 18:46:17 -0800623 CalculatorExpr result = new CalculatorExpr();
Justin Klaassen0ace4eb2016-02-05 11:38:12 -0800624 @SuppressWarnings("unchecked")
Hans Boehm3666e632015-07-27 18:33:12 -0700625 Token t = new PreEval(val, ratVal, new CalculatorExpr((ArrayList<Token>) mExpr.clone()),
626 new EvalContext(dm, mExpr.size()), sr);
Hans Boehm84614952014-11-25 18:46:17 -0800627 result.mExpr.add(t);
628 return result;
629 }
630
Hans Boehm3666e632015-07-27 18:33:12 -0700631 /**
632 * Internal evaluation functions return an EvalRet triple.
633 * We compute rational (BoundedRational) results when possible, both as a performance
634 * optimization, and to detect errors exactly when we can.
635 */
636 private static class EvalRet {
637 public int pos; // Next position (expression index) to be parsed.
638 public final CR val; // Constructive Real result of evaluating subexpression.
639 public final BoundedRational ratVal; // Exact Rational value or null.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700640 EvalRet(int p, CR v, BoundedRational r) {
Hans Boehm3666e632015-07-27 18:33:12 -0700641 pos = p;
642 val = v;
643 ratVal = r;
Hans Boehm84614952014-11-25 18:46:17 -0800644 }
645 }
646
Hans Boehm3666e632015-07-27 18:33:12 -0700647 /**
648 * Internal evaluation functions take an EvalContext argument.
649 */
Hans Boehm84614952014-11-25 18:46:17 -0800650 private static class EvalContext {
Hans Boehm3666e632015-07-27 18:33:12 -0700651 public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
Hans Boehmc023b732015-04-29 11:30:47 -0700652 public final boolean mDegreeMode;
Hans Boehm84614952014-11-25 18:46:17 -0800653 // If we add any other kinds of evaluation modes, they go here.
Hans Boehmc023b732015-04-29 11:30:47 -0700654 EvalContext(boolean degreeMode, int len) {
Hans Boehm84614952014-11-25 18:46:17 -0800655 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700656 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800657 }
Hans Boehmc023b732015-04-29 11:30:47 -0700658 EvalContext(DataInput in, int len) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800659 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700660 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800661 }
662 void write(DataOutput out) throws IOException {
663 out.writeBoolean(mDegreeMode);
664 }
665 }
666
667 private final CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
668
669 private final CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
670
671 private CR toRadians(CR x, EvalContext ec) {
672 if (ec.mDegreeMode) {
673 return x.multiply(RADIANS_PER_DEGREE);
674 } else {
675 return x;
676 }
677 }
678
679 private CR fromRadians(CR x, EvalContext ec) {
680 if (ec.mDegreeMode) {
681 return x.multiply(DEGREES_PER_RADIAN);
682 } else {
683 return x;
684 }
685 }
686
Hans Boehm3666e632015-07-27 18:33:12 -0700687 // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
688 // error. We expect that to be caught in eval below.
Hans Boehm84614952014-11-25 18:46:17 -0800689
Hans Boehmc023b732015-04-29 11:30:47 -0700690 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800691 Token t = mExpr.get(i);
Hans Boehm3666e632015-07-27 18:33:12 -0700692 if (!(t instanceof Operator)) {
693 return false;
694 }
695 return ((Operator)(t)).id == op;
Hans Boehm84614952014-11-25 18:46:17 -0800696 }
697
Hans Boehmc023b732015-04-29 11:30:47 -0700698 private boolean isOperator(int i, int op, EvalContext ec) {
Hans Boehm3666e632015-07-27 18:33:12 -0700699 if (i >= ec.mPrefixLength) {
700 return false;
701 }
Hans Boehmc023b732015-04-29 11:30:47 -0700702 return isOperatorUnchecked(i, op);
703 }
704
Hans Boehm3666e632015-07-27 18:33:12 -0700705 public static class SyntaxException extends Exception {
Hans Boehmc023b732015-04-29 11:30:47 -0700706 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800707 super();
708 }
Hans Boehmc023b732015-04-29 11:30:47 -0700709 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800710 super(s);
711 }
712 }
713
Hans Boehm3666e632015-07-27 18:33:12 -0700714 // The following functions all evaluate some kind of expression starting at position i in
715 // mExpr in a specified evaluation context. They return both the expression value (as
716 // constructive real and, if applicable, as BoundedRational) and the position of the next token
Hans Boehm84614952014-11-25 18:46:17 -0800717 // that was not used as part of the evaluation.
Hans Boehm3666e632015-07-27 18:33:12 -0700718 // This is essentially a simple recursive descent parser combined with expression evaluation.
719
Hans Boehmc023b732015-04-29 11:30:47 -0700720 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700721 final Token t = mExpr.get(i);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700722 BoundedRational ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800723 if (t instanceof Constant) {
724 Constant c = (Constant)t;
Hans Boehm0b9806f2015-06-29 16:07:15 -0700725 ratVal = c.toRational();
Hans Boehm3666e632015-07-27 18:33:12 -0700726 return new EvalRet(i+1, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800727 }
728 if (t instanceof PreEval) {
Hans Boehm3666e632015-07-27 18:33:12 -0700729 final PreEval p = (PreEval)t;
730 return new EvalRet(i+1, p.value, p.ratValue);
Hans Boehm84614952014-11-25 18:46:17 -0800731 }
732 EvalRet argVal;
Hans Boehm3666e632015-07-27 18:33:12 -0700733 switch(((Operator)(t)).id) {
Hans Boehm84614952014-11-25 18:46:17 -0800734 case R.id.const_pi:
735 return new EvalRet(i+1, CR.PI, null);
736 case R.id.const_e:
Hans Boehm4db31b42015-05-31 12:19:05 -0700737 return new EvalRet(i+1, REAL_E, null);
Hans Boehm84614952014-11-25 18:46:17 -0800738 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700739 // Seems to have highest precedence.
740 // Does not add implicit paren.
741 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700742 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700743 argVal = evalUnary(i+2, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700744 ratVal = BoundedRational.sqrt(BoundedRational.negate(argVal.ratVal));
745 if (ratVal != null) {
746 break;
747 }
748 return new EvalRet(argVal.pos,
749 argVal.val.negate().sqrt(), null);
Hans Boehmfbcef702015-04-27 18:07:47 -0700750 } else {
751 argVal = evalUnary(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700752 ratVal = BoundedRational.sqrt(argVal.ratVal);
753 if (ratVal != null) {
754 break;
755 }
756 return new EvalRet(argVal.pos, argVal.val.sqrt(), null);
Hans Boehmfbcef702015-04-27 18:07:47 -0700757 }
Hans Boehm84614952014-11-25 18:46:17 -0800758 case R.id.lparen:
759 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700760 if (isOperator(argVal.pos, R.id.rparen, ec)) {
761 argVal.pos++;
762 }
763 return new EvalRet(argVal.pos, argVal.val, argVal.ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800764 case R.id.fun_sin:
765 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700766 if (isOperator(argVal.pos, R.id.rparen, ec)) {
767 argVal.pos++;
768 }
769 ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.ratVal)
770 : BoundedRational.sin(argVal.ratVal);
771 if (ratVal != null) {
772 break;
773 }
774 return new EvalRet(argVal.pos, toRadians(argVal.val,ec).sin(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800775 case R.id.fun_cos:
776 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700777 if (isOperator(argVal.pos, R.id.rparen, ec)) {
778 argVal.pos++;
779 }
780 ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.ratVal)
781 : BoundedRational.cos(argVal.ratVal);
782 if (ratVal != null) {
783 break;
784 }
785 return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800786 case R.id.fun_tan:
787 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700788 if (isOperator(argVal.pos, R.id.rparen, ec)) {
789 argVal.pos++;
790 }
791 ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.ratVal)
792 : BoundedRational.tan(argVal.ratVal);
793 if (ratVal != null) {
794 break;
795 }
796 CR argCR = toRadians(argVal.val, ec);
797 return new EvalRet(argVal.pos, argCR.sin().divide(argCR.cos()), null);
Hans Boehm84614952014-11-25 18:46:17 -0800798 case R.id.fun_ln:
799 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700800 if (isOperator(argVal.pos, R.id.rparen, ec)) {
801 argVal.pos++;
802 }
803 ratVal = BoundedRational.ln(argVal.ratVal);
804 if (ratVal != null) {
805 break;
806 }
807 return new EvalRet(argVal.pos, argVal.val.ln(), null);
Hans Boehm4db31b42015-05-31 12:19:05 -0700808 case R.id.fun_exp:
809 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700810 if (isOperator(argVal.pos, R.id.rparen, ec)) {
811 argVal.pos++;
812 }
813 ratVal = BoundedRational.exp(argVal.ratVal);
814 if (ratVal != null) {
815 break;
816 }
817 return new EvalRet(argVal.pos, argVal.val.exp(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800818 case R.id.fun_log:
819 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700820 if (isOperator(argVal.pos, R.id.rparen, ec)) {
821 argVal.pos++;
822 }
823 ratVal = BoundedRational.log(argVal.ratVal);
824 if (ratVal != null) {
825 break;
826 }
827 return new EvalRet(argVal.pos, argVal.val.ln().divide(CR.valueOf(10).ln()), null);
Hans Boehm84614952014-11-25 18:46:17 -0800828 case R.id.fun_arcsin:
829 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700830 if (isOperator(argVal.pos, R.id.rparen, ec)) {
831 argVal.pos++;
832 }
833 ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.ratVal)
834 : BoundedRational.asin(argVal.ratVal);
835 if (ratVal != null) {
836 break;
837 }
838 return new EvalRet(argVal.pos,
839 fromRadians(UnaryCRFunction.asinFunction.execute(argVal.val),ec), null);
Hans Boehm84614952014-11-25 18:46:17 -0800840 case R.id.fun_arccos:
841 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700842 if (isOperator(argVal.pos, R.id.rparen, ec)) {
843 argVal.pos++;
844 }
845 ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.ratVal)
846 : BoundedRational.acos(argVal.ratVal);
847 if (ratVal != null) {
848 break;
849 }
850 return new EvalRet(argVal.pos,
851 fromRadians(UnaryCRFunction.acosFunction.execute(argVal.val),ec), null);
Hans Boehm84614952014-11-25 18:46:17 -0800852 case R.id.fun_arctan:
853 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700854 if (isOperator(argVal.pos, R.id.rparen, ec)) {
855 argVal.pos++;
856 }
857 ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.ratVal)
858 : BoundedRational.atan(argVal.ratVal);
859 if (ratVal != null) {
860 break;
861 }
862 return new EvalRet(argVal.pos,
863 fromRadians(UnaryCRFunction.atanFunction.execute(argVal.val),ec), null);
Hans Boehm84614952014-11-25 18:46:17 -0800864 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700865 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800866 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700867 // We have a rational value.
Hans Boehm3666e632015-07-27 18:33:12 -0700868 return new EvalRet(argVal.pos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800869 }
870
Hans Boehm3666e632015-07-27 18:33:12 -0700871 /**
872 * Compute an integral power of a constructive real.
873 * Unlike the "general" case using logarithms, this handles a negative base.
874 */
Hans Boehm84614952014-11-25 18:46:17 -0800875 private static CR pow(CR base, BigInteger exp) {
876 if (exp.compareTo(BigInteger.ZERO) < 0) {
877 return pow(base, exp.negate()).inverse();
878 }
Hans Boehm3666e632015-07-27 18:33:12 -0700879 if (exp.equals(BigInteger.ONE)) {
880 return base;
881 }
Hans Boehm84614952014-11-25 18:46:17 -0800882 if (exp.and(BigInteger.ONE).intValue() == 1) {
883 return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
884 }
885 if (exp.equals(BigInteger.ZERO)) {
886 return CR.valueOf(1);
887 }
888 CR tmp = pow(base, exp.shiftRight(1));
889 return tmp.multiply(tmp);
890 }
891
Hans Boehm3666e632015-07-27 18:33:12 -0700892 // Number of bits past binary point to test for integer-ness.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700893 private static final int TEST_PREC = -100;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700894 private static final BigInteger MASK =
895 BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
Hans Boehm4db31b42015-05-31 12:19:05 -0700896 private static final CR REAL_E = CR.valueOf(1).exp();
897 private static final CR REAL_ONE_HUNDREDTH = CR.valueOf(100).inverse();
Hans Boehm3666e632015-07-27 18:33:12 -0700898 private static final BoundedRational RATIONAL_ONE_HUNDREDTH = new BoundedRational(1,100);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700899 private static boolean isApprInt(CR x) {
900 BigInteger appr = x.get_appr(TEST_PREC);
901 return appr.and(MASK).signum() == 0;
902 }
903
Hans Boehm4db31b42015-05-31 12:19:05 -0700904 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700905 final EvalRet tmp = evalUnary(i, ec);
906 int cpos = tmp.pos;
907 CR crVal = tmp.val;
908 BoundedRational ratVal = tmp.ratVal;
Hans Boehm4db31b42015-05-31 12:19:05 -0700909 boolean isFact;
910 boolean isSquared = false;
911 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
912 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
913 isOperator(cpos, R.id.op_pct, ec)) {
914 if (isFact) {
915 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700916 // Assume it was an integer, but we didn't figure it out.
Hans Boehm4db31b42015-05-31 12:19:05 -0700917 // KitKat may have used the Gamma function.
Hans Boehm3666e632015-07-27 18:33:12 -0700918 if (!isApprInt(crVal)) {
Hans Boehm4db31b42015-05-31 12:19:05 -0700919 throw new ArithmeticException("factorial(non-integer)");
920 }
Hans Boehm3666e632015-07-27 18:33:12 -0700921 ratVal = new BoundedRational(crVal.BigIntegerValue());
Hans Boehm682ff5e2015-03-09 14:40:25 -0700922 }
Hans Boehm4db31b42015-05-31 12:19:05 -0700923 ratVal = BoundedRational.fact(ratVal);
Hans Boehm3666e632015-07-27 18:33:12 -0700924 crVal = ratVal.CRValue();
Hans Boehm4db31b42015-05-31 12:19:05 -0700925 } else if (isSquared) {
926 ratVal = BoundedRational.multiply(ratVal, ratVal);
927 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700928 crVal = crVal.multiply(crVal);
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 }
932 } else /* percent */ {
933 ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH);
934 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700935 crVal = crVal.multiply(REAL_ONE_HUNDREDTH);
Hans Boehm4db31b42015-05-31 12:19:05 -0700936 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700937 crVal = ratVal.CRValue();
Hans Boehm4db31b42015-05-31 12:19:05 -0700938 }
Hans Boehm84614952014-11-25 18:46:17 -0800939 }
Hans Boehm84614952014-11-25 18:46:17 -0800940 ++cpos;
941 }
Hans Boehm3666e632015-07-27 18:33:12 -0700942 return new EvalRet(cpos, crVal, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800943 }
944
Hans Boehmc023b732015-04-29 11:30:47 -0700945 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700946 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700947 int cpos = result1.pos; // current position
948 CR crVal = result1.val; // value so far
949 BoundedRational ratVal = result1.ratVal; // int value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700950 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700951 final EvalRet exp = evalSignedFactor(cpos + 1, ec);
952 cpos = exp.pos;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700953 // Try completely rational evaluation first.
Hans Boehm3666e632015-07-27 18:33:12 -0700954 ratVal = BoundedRational.pow(ratVal, exp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700955 if (ratVal != null) {
956 return new EvalRet(cpos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800957 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700958 // Power with integer exponent is defined for negative base.
959 // Thus we handle that case separately.
960 // We punt if the exponent is an integer computed from irrational
961 // values. That wouldn't work reliably with floating point either.
Hans Boehm3666e632015-07-27 18:33:12 -0700962 BigInteger int_exp = BoundedRational.asBigInteger(exp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700963 if (int_exp != null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700964 crVal = pow(crVal, int_exp);
Hans Boehm84614952014-11-25 18:46:17 -0800965 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700966 crVal = crVal.ln().multiply(exp.val).exp();
Hans Boehm84614952014-11-25 18:46:17 -0800967 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700968 ratVal = null;
Hans Boehm84614952014-11-25 18:46:17 -0800969 }
Hans Boehm3666e632015-07-27 18:33:12 -0700970 return new EvalRet(cpos, crVal, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800971 }
972
Hans Boehmc023b732015-04-29 11:30:47 -0700973 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
974 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700975 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800976 EvalRet tmp = evalFactor(cpos, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700977 cpos = tmp.pos;
978 CR crVal = negative ? tmp.val.negate() : tmp.val;
979 BoundedRational ratVal = negative ? BoundedRational.negate(tmp.ratVal)
980 : tmp.ratVal;
981 return new EvalRet(cpos, crVal, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800982 }
983
984 private boolean canStartFactor(int i) {
985 if (i >= mExpr.size()) return false;
986 Token t = mExpr.get(i);
987 if (!(t instanceof Operator)) return true;
Hans Boehm3666e632015-07-27 18:33:12 -0700988 int id = ((Operator)(t)).id;
Hans Boehm84614952014-11-25 18:46:17 -0800989 if (KeyMaps.isBinary(id)) return false;
990 switch (id) {
991 case R.id.op_fact:
992 case R.id.rparen:
993 return false;
994 default:
995 return true;
996 }
997 }
998
Hans Boehmc023b732015-04-29 11:30:47 -0700999 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -08001000 EvalRet tmp = evalSignedFactor(i, ec);
1001 boolean is_mul = false;
1002 boolean is_div = false;
Hans Boehm3666e632015-07-27 18:33:12 -07001003 int cpos = tmp.pos; // Current position in expression.
1004 CR crVal = tmp.val; // Current value.
1005 BoundedRational ratVal = tmp.ratVal; // Current rational value.
Hans Boehmc023b732015-04-29 11:30:47 -07001006 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
1007 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -08001008 || canStartFactor(cpos)) {
1009 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001010 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -08001011 if (is_div) {
Hans Boehm3666e632015-07-27 18:33:12 -07001012 ratVal = BoundedRational.divide(ratVal, tmp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001013 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -07001014 crVal = crVal.divide(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 } else {
Hans Boehm3666e632015-07-27 18:33:12 -07001019 ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001020 if (ratVal == null) {
Hans Boehm3666e632015-07-27 18:33:12 -07001021 crVal = crVal.multiply(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001022 } else {
Hans Boehm3666e632015-07-27 18:33:12 -07001023 crVal = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -08001024 }
1025 }
Hans Boehm3666e632015-07-27 18:33:12 -07001026 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -08001027 is_mul = is_div = false;
1028 }
Hans Boehm3666e632015-07-27 18:33:12 -07001029 return new EvalRet(cpos, crVal, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -08001030 }
1031
Hans Boehm8afd0f82015-07-30 17:00:33 -07001032 /**
1033 * Is the subexpression starting at pos a simple percent constant?
1034 * This is used to recognize exppressions like 200+10%, which we handle specially.
1035 * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
1036 * by either nothing or an additive operator.
1037 * Note that we are intentionally far more restrictive in recognizing such expressions than
1038 * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
1039 * When in doubt, we fall back to the the naive interpretation of % as 1/100.
1040 * Note that 100+(10)% yields 100.1 while 100+10% yields 110. This may be controversial,
1041 * but is consistent with Google web search.
1042 */
1043 private boolean isPercent(int pos) {
1044 if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
1045 return false;
1046 }
1047 Token number = mExpr.get(pos);
1048 if (number instanceof Operator) {
1049 return false;
1050 }
1051 if (mExpr.size() == pos + 2) {
1052 return true;
1053 }
1054 if (!(mExpr.get(pos + 2) instanceof Operator)) {
1055 return false;
1056 }
1057 Operator op = (Operator) mExpr.get(pos + 2);
Hans Boehm64d1cdb2016-03-08 19:14:07 -08001058 return op.id == R.id.op_add || op.id == R.id.op_sub || op.id == R.id.rparen;
Hans Boehm8afd0f82015-07-30 17:00:33 -07001059 }
1060
1061 /**
1062 * Compute the multiplicative factor corresponding to an N% addition or subtraction.
1063 * @param pos position of Constant or PreEval expression token corresponding to N
1064 * @param isSubtraction this is a subtraction, as opposed to addition
1065 * @param ec usable evaluation contex; only length matters
1066 * @return Rational and CR values; position is pos + 2, i.e. after percent sign
1067 */
1068 private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
1069 throws SyntaxException {
1070 EvalRet tmp = evalUnary(pos, ec);
1071 BoundedRational ratVal = isSubtraction ? BoundedRational.negate(tmp.ratVal)
1072 : tmp.ratVal;
1073 CR crVal = isSubtraction ? tmp.val.negate() : tmp.val;
1074 ratVal = BoundedRational.add(BoundedRational.ONE,
1075 BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH));
1076 if (ratVal == null) {
1077 crVal = CR.ONE.add(crVal.multiply(REAL_ONE_HUNDREDTH));
1078 } else {
1079 crVal = ratVal.CRValue();
1080 }
1081 return new EvalRet(pos + 2 /* after percent sign */, crVal, ratVal);
1082 }
1083
Hans Boehmc023b732015-04-29 11:30:47 -07001084 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -08001085 EvalRet tmp = evalTerm(i, ec);
1086 boolean is_plus;
Hans Boehm3666e632015-07-27 18:33:12 -07001087 int cpos = tmp.pos;
1088 CR crVal = tmp.val;
1089 BoundedRational ratVal = tmp.ratVal;
Hans Boehmc023b732015-04-29 11:30:47 -07001090 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
1091 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm8afd0f82015-07-30 17:00:33 -07001092 if (isPercent(cpos + 1)) {
1093 tmp = getPercentFactor(cpos + 1, !is_plus, ec);
1094 ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001095 if (ratVal == null) {
Hans Boehm8afd0f82015-07-30 17:00:33 -07001096 crVal = crVal.multiply(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -07001097 } else {
Hans Boehm3666e632015-07-27 18:33:12 -07001098 crVal = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -08001099 }
1100 } else {
Hans Boehm8afd0f82015-07-30 17:00:33 -07001101 tmp = evalTerm(cpos + 1, ec);
1102 if (is_plus) {
1103 ratVal = BoundedRational.add(ratVal, tmp.ratVal);
1104 if (ratVal == null) {
1105 crVal = crVal.add(tmp.val);
1106 } else {
1107 crVal = ratVal.CRValue();
1108 }
Hans Boehm682ff5e2015-03-09 14:40:25 -07001109 } else {
Hans Boehm8afd0f82015-07-30 17:00:33 -07001110 ratVal = BoundedRational.subtract(ratVal, tmp.ratVal);
1111 if (ratVal == null) {
1112 crVal = crVal.subtract(tmp.val);
1113 } else {
1114 crVal = ratVal.CRValue();
1115 }
Hans Boehm84614952014-11-25 18:46:17 -08001116 }
1117 }
Hans Boehm3666e632015-07-27 18:33:12 -07001118 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -08001119 }
Hans Boehm3666e632015-07-27 18:33:12 -07001120 return new EvalRet(cpos, crVal, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -08001121 }
1122
Hans Boehm3666e632015-07-27 18:33:12 -07001123 /**
1124 * Externally visible evaluation result.
1125 */
1126 public static class EvalResult {
1127 public final CR val;
1128 public final BoundedRational ratVal;
1129 EvalResult (CR v, BoundedRational rv) {
1130 val = v;
1131 ratVal = rv;
Hans Boehm84614952014-11-25 18:46:17 -08001132 }
Hans Boehm65a99a42016-02-03 18:16:07 -08001133 /*
1134 * Return decimal String result to the indicated precision.
1135 * For rational values this is the exactly truncated result.
1136 * Otherwise the error is < 1 in the last included digit.
1137 * @param precOffset Always non-negative. 1 is accurate 1/10, 2 means 1/100, etc.
1138 */
1139 public String toString(int precOffset) {
1140 return ratVal == null ? val.toString(precOffset) : ratVal.toString(precOffset);
1141 }
Hans Boehm84614952014-11-25 18:46:17 -08001142 }
1143
Hans Boehme8553762015-06-26 17:56:52 -07001144 /**
1145 * Return the starting position of the sequence of trailing binary operators.
1146 */
1147 private int trailingBinaryOpsStart() {
Hans Boehmc023b732015-04-29 11:30:47 -07001148 int result = mExpr.size();
1149 while (result > 0) {
1150 Token last = mExpr.get(result - 1);
1151 if (!(last instanceof Operator)) break;
1152 Operator o = (Operator)last;
Hans Boehm3666e632015-07-27 18:33:12 -07001153 if (!KeyMaps.isBinary(o.id)) break;
Hans Boehmc023b732015-04-29 11:30:47 -07001154 --result;
1155 }
1156 return result;
1157 }
1158
Hans Boehm3666e632015-07-27 18:33:12 -07001159 /**
1160 * Is the current expression worth evaluating?
1161 */
Hans Boehmc023b732015-04-29 11:30:47 -07001162 public boolean hasInterestingOps() {
Hans Boehme8553762015-06-26 17:56:52 -07001163 int last = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -07001164 int first = 0;
1165 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
1166 // Leading minus is not by itself interesting.
1167 first++;
1168 }
1169 for (int i = first; i < last; ++i) {
1170 Token t1 = mExpr.get(i);
Hans Boehm187d3e92015-06-09 18:04:26 -07001171 if (t1 instanceof Operator
1172 || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
1173 return true;
1174 }
Hans Boehmc023b732015-04-29 11:30:47 -07001175 }
1176 return false;
1177 }
1178
Hans Boehme8553762015-06-26 17:56:52 -07001179 /**
1180 * Evaluate the expression excluding trailing binary operators.
Hans Boehm3666e632015-07-27 18:33:12 -07001181 * Errors result in exceptions, most of which are unchecked. Should not be called
1182 * concurrently with modification of the expression. May take a very long time; avoid calling
1183 * from UI thread.
Hans Boehme8553762015-06-26 17:56:52 -07001184 *
1185 * @param degreeMode use degrees rather than radians
1186 */
1187 EvalResult eval(boolean degreeMode) throws SyntaxException
Hans Boehmc023b732015-04-29 11:30:47 -07001188 // And unchecked exceptions thrown by CR
1189 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -08001190 {
1191 try {
Hans Boehm3666e632015-07-27 18:33:12 -07001192 // We currently never include trailing binary operators, but include other trailing
1193 // operators. Thus we usually, but not always, display results for prefixes of valid
1194 // expressions, and don't generate an error where we previously displayed an instant
1195 // result. This reflects the Android L design.
Hans Boehme8553762015-06-26 17:56:52 -07001196 int prefixLen = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -07001197 EvalContext ec = new EvalContext(degreeMode, prefixLen);
Hans Boehm84614952014-11-25 18:46:17 -08001198 EvalRet res = evalExpr(0, ec);
Hans Boehm3666e632015-07-27 18:33:12 -07001199 if (res.pos != prefixLen) {
Hans Boehmc023b732015-04-29 11:30:47 -07001200 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -07001201 }
Hans Boehm3666e632015-07-27 18:33:12 -07001202 return new EvalResult(res.val, res.ratVal);
Hans Boehm84614952014-11-25 18:46:17 -08001203 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -07001204 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -08001205 }
1206 }
1207
1208 // Produce a string representation of the expression itself
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001209 SpannableStringBuilder toSpannableStringBuilder(Context context) {
1210 SpannableStringBuilder ssb = new SpannableStringBuilder();
Hans Boehm84614952014-11-25 18:46:17 -08001211 for (Token t: mExpr) {
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001212 ssb.append(t.toCharSequence(context));
Hans Boehm84614952014-11-25 18:46:17 -08001213 }
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001214 return ssb;
Hans Boehm84614952014-11-25 18:46:17 -08001215 }
1216}