blob: 18b1b353685037e53e3cdf6f335e9ccc6f94b640 [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 -080019import android.content.Context;
Hans Boehm8a4f81c2015-07-09 10:41:25 -070020import android.text.SpannableString;
21import android.text.SpannableStringBuilder;
22import android.text.Spanned;
23import android.text.style.TtsSpan;
Hans Boehm84614952014-11-25 18:46:17 -080024
Hans Boehmf4424772016-12-05 10:35:16 -080025import java.io.ByteArrayOutputStream;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070026import java.io.DataInput;
27import java.io.DataOutput;
Hans Boehmf4424772016-12-05 10:35:16 -080028import java.io.DataOutputStream;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070029import java.io.IOException;
Annie Chin06fd3cf2016-11-07 16:04:33 -080030import java.math.BigInteger;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070031import java.util.ArrayList;
Hans Boehm9c160b42016-12-02 11:55:12 -080032import java.util.Collections;
33import java.util.HashSet;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070034
Hans Boehm3666e632015-07-27 18:33:12 -070035/**
36 * A mathematical expression represented as a sequence of "tokens".
37 * Many tokens are represented by button ids for the corresponding operator.
38 * A token may also represent the result of a previously evaluated expression.
39 * The add() method adds a token to the end of the expression. The delete method() removes one.
40 * Clear() deletes the entire expression contents. Eval() evaluates the expression,
Hans Boehm995e5eb2016-02-08 11:03:01 -080041 * producing a UnifiedReal result.
Hans Boehm3666e632015-07-27 18:33:12 -070042 * Expressions are parsed only during evaluation; no explicit parse tree is maintained.
43 *
Hans Boehm995e5eb2016-02-08 11:03:01 -080044 * The write() method is used to save the current expression. Note that neither UnifiedReal
45 * nor the underlying CR provide a serialization facility. Thus we save all previously
46 * computed values by writing out the expression that was used to compute them, and reevaluate
47 * when reading it back in.
Hans Boehm3666e632015-07-27 18:33:12 -070048 */
Hans Boehm84614952014-11-25 18:46:17 -080049class CalculatorExpr {
Hans Boehm8f051c32016-10-03 16:53:58 -070050 /**
51 * An interface for resolving expression indices in embedded subexpressions to
52 * the associated CalculatorExpr, and associating a UnifiedReal result with it.
53 * All methods are thread-safe in the strong sense; they may be called asynchronously
54 * at any time from any thread.
55 */
56 public interface ExprResolver {
57 /*
58 * Retrieve the expression corresponding to index.
59 */
60 CalculatorExpr getExpr(long index);
61 /*
Hans Boehm9c160b42016-12-02 11:55:12 -080062 * Retrieve the degree mode associated with the expression at index i.
Hans Boehm8f051c32016-10-03 16:53:58 -070063 */
64 boolean getDegreeMode(long index);
65 /*
66 * Retrieve the stored result for the expression at index, or return null.
67 */
68 UnifiedReal getResult(long index);
69 /*
70 * Atomically test for an existing result, and set it if there was none.
71 * Return the prior result if there was one, or the new one if there was not.
72 * May only be called after getExpr.
73 */
74 UnifiedReal putResultIfAbsent(long index, UnifiedReal result);
75 // FIXME: Check that long timeouts for embedded expressions are propagated
76 // correctly.
77 }
78
Hans Boehm84614952014-11-25 18:46:17 -080079 private ArrayList<Token> mExpr; // The actual representation
80 // as a list of tokens. Constant
81 // tokens are always nonempty.
82
83 private static enum TokenKind { CONSTANT, OPERATOR, PRE_EVAL };
84 private static TokenKind[] tokenKindValues = TokenKind.values();
85 private final static BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
86 private final static BigInteger BIG_BILLION = BigInteger.valueOf(1000000000);
87
88 private static abstract class Token {
89 abstract TokenKind kind();
Hans Boehm8a4f81c2015-07-09 10:41:25 -070090
91 /**
Hans Boehm8f051c32016-10-03 16:53:58 -070092 * Write token as either a very small Byte containing the TokenKind,
93 * followed by data needed by subclass constructor,
94 * or as a byte >= 0x20 directly describing the OPERATOR token.
Hans Boehm8a4f81c2015-07-09 10:41:25 -070095 */
Hans Boehm84614952014-11-25 18:46:17 -080096 abstract void write(DataOutput out) throws IOException;
Hans Boehm8a4f81c2015-07-09 10:41:25 -070097
98 /**
99 * Return a textual representation of the token.
100 * The result is suitable for either display as part od the formula or TalkBack use.
101 * It may be a SpannableString that includes added TalkBack information.
102 * @param context context used for converting button ids to strings
103 */
104 abstract CharSequence toCharSequence(Context context);
Hans Boehm84614952014-11-25 18:46:17 -0800105 }
106
Hans Boehm3666e632015-07-27 18:33:12 -0700107 /**
108 * Representation of an operator token
109 */
Hans Boehm84614952014-11-25 18:46:17 -0800110 private static class Operator extends Token {
Hans Boehm8f051c32016-10-03 16:53:58 -0700111 // TODO: rename id.
Hans Boehm3666e632015-07-27 18:33:12 -0700112 public final int id; // We use the button resource id
Hans Boehm84614952014-11-25 18:46:17 -0800113 Operator(int resId) {
Hans Boehm3666e632015-07-27 18:33:12 -0700114 id = resId;
Hans Boehm84614952014-11-25 18:46:17 -0800115 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700116 Operator(byte op) throws IOException {
117 id = KeyMaps.fromByte(op);
Hans Boehm84614952014-11-25 18:46:17 -0800118 }
119 @Override
120 void write(DataOutput out) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700121 out.writeByte(KeyMaps.toByte(id));
Hans Boehm84614952014-11-25 18:46:17 -0800122 }
123 @Override
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700124 public CharSequence toCharSequence(Context context) {
Hans Boehm3666e632015-07-27 18:33:12 -0700125 String desc = KeyMaps.toDescriptiveString(context, id);
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700126 if (desc != null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700127 SpannableString result = new SpannableString(KeyMaps.toString(context, id));
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700128 Object descSpan = new TtsSpan.TextBuilder(desc).build();
129 result.setSpan(descSpan, 0, result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
130 return result;
131 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700132 return KeyMaps.toString(context, id);
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700133 }
Hans Boehm84614952014-11-25 18:46:17 -0800134 }
135 @Override
136 TokenKind kind() { return TokenKind.OPERATOR; }
137 }
138
Hans Boehm3666e632015-07-27 18:33:12 -0700139 /**
140 * Representation of a (possibly incomplete) numerical constant.
141 * Supports addition and removal of trailing characters; hence mutable.
142 */
Hans Boehm84614952014-11-25 18:46:17 -0800143 private static class Constant extends Token implements Cloneable {
144 private boolean mSawDecimal;
Hans Boehm3666e632015-07-27 18:33:12 -0700145 private String mWhole; // String preceding decimal point.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700146 private String mFraction; // String after decimal point.
147 private int mExponent; // Explicit exponent, only generated through addExponent.
Hans Boehm8f051c32016-10-03 16:53:58 -0700148 private static int SAW_DECIMAL = 0x1;
149 private static int HAS_EXPONENT = 0x2;
Hans Boehm84614952014-11-25 18:46:17 -0800150
151 Constant() {
152 mWhole = "";
153 mFraction = "";
Hans Boehm8f051c32016-10-03 16:53:58 -0700154 // mSawDecimal = false;
155 // mExponent = 0;
Hans Boehm84614952014-11-25 18:46:17 -0800156 };
157
158 Constant(DataInput in) throws IOException {
159 mWhole = in.readUTF();
Hans Boehm8f051c32016-10-03 16:53:58 -0700160 byte flags = in.readByte();
161 if ((flags & SAW_DECIMAL) != 0) {
162 mSawDecimal = true;
163 mFraction = in.readUTF();
164 } else {
165 // mSawDecimal = false;
166 mFraction = "";
167 }
168 if ((flags & HAS_EXPONENT) != 0) {
169 mExponent = in.readInt();
170 }
Hans Boehm84614952014-11-25 18:46:17 -0800171 }
172
173 @Override
174 void write(DataOutput out) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700175 byte flags = (byte)((mSawDecimal ? SAW_DECIMAL : 0)
176 | (mExponent != 0 ? HAS_EXPONENT : 0));
Hans Boehm84614952014-11-25 18:46:17 -0800177 out.writeByte(TokenKind.CONSTANT.ordinal());
178 out.writeUTF(mWhole);
Hans Boehm8f051c32016-10-03 16:53:58 -0700179 out.writeByte(flags);
180 if (mSawDecimal) {
181 out.writeUTF(mFraction);
182 }
183 if (mExponent != 0) {
184 out.writeInt(mExponent);
185 }
Hans Boehm84614952014-11-25 18:46:17 -0800186 }
187
188 // Given a button press, append corresponding digit.
189 // We assume id is a digit or decimal point.
190 // Just return false if this was the second (or later) decimal point
191 // in this constant.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700192 // Assumes that this constant does not have an exponent.
Hans Boehm3666e632015-07-27 18:33:12 -0700193 public boolean add(int id) {
Hans Boehm84614952014-11-25 18:46:17 -0800194 if (id == R.id.dec_point) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700195 if (mSawDecimal || mExponent != 0) return false;
Hans Boehm84614952014-11-25 18:46:17 -0800196 mSawDecimal = true;
197 return true;
198 }
199 int val = KeyMaps.digVal(id);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700200 if (mExponent != 0) {
201 if (Math.abs(mExponent) <= 10000) {
202 if (mExponent > 0) {
203 mExponent = 10 * mExponent + val;
204 } else {
205 mExponent = 10 * mExponent - val;
206 }
207 return true;
208 } else { // Too large; refuse
209 return false;
210 }
211 }
Hans Boehm84614952014-11-25 18:46:17 -0800212 if (mSawDecimal) {
213 mFraction += val;
214 } else {
215 mWhole += val;
216 }
217 return true;
218 }
219
Hans Boehm3666e632015-07-27 18:33:12 -0700220 public void addExponent(int exp) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700221 // Note that adding a 0 exponent is a no-op. That's OK.
222 mExponent = exp;
223 }
224
Hans Boehm3666e632015-07-27 18:33:12 -0700225 /**
226 * Undo the last add or remove last exponent digit.
227 * Assumes the constant is nonempty.
228 */
229 public void delete() {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700230 if (mExponent != 0) {
231 mExponent /= 10;
232 // Once zero, it can only be added back with addExponent.
233 } else if (!mFraction.isEmpty()) {
Hans Boehm84614952014-11-25 18:46:17 -0800234 mFraction = mFraction.substring(0, mFraction.length() - 1);
235 } else if (mSawDecimal) {
236 mSawDecimal = false;
237 } else {
238 mWhole = mWhole.substring(0, mWhole.length() - 1);
239 }
240 }
241
Hans Boehm3666e632015-07-27 18:33:12 -0700242 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800243 return (mSawDecimal == false && mWhole.isEmpty());
244 }
245
Hans Boehm3666e632015-07-27 18:33:12 -0700246 /**
247 * Produce human-readable string representation of constant, as typed.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700248 * We do add digit grouping separators to the whole number, even if not typed.
Hans Boehm3666e632015-07-27 18:33:12 -0700249 * Result is internationalized.
250 */
Hans Boehm84614952014-11-25 18:46:17 -0800251 @Override
252 public String toString() {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700253 String result;
254 if (mExponent != 0) {
255 result = mWhole;
256 } else {
257 result = StringUtils.addCommas(mWhole, 0, mWhole.length());
258 }
Hans Boehm84614952014-11-25 18:46:17 -0800259 if (mSawDecimal) {
Hans Boehm013969e2015-04-13 20:29:47 -0700260 result += '.';
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700261 result += mFraction;
262 }
Hans Boehm0b9806f2015-06-29 16:07:15 -0700263 if (mExponent != 0) {
264 result += "E" + mExponent;
265 }
Hans Boehm013969e2015-04-13 20:29:47 -0700266 return KeyMaps.translateResult(result);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700267 }
268
Hans Boehm3666e632015-07-27 18:33:12 -0700269 /**
Hans Boehmfa5203c2015-08-17 16:14:52 -0700270 * Return BoundedRational representation of constant, if well-formed.
271 * Result is never null.
Hans Boehm3666e632015-07-27 18:33:12 -0700272 */
Hans Boehmfa5203c2015-08-17 16:14:52 -0700273 public BoundedRational toRational() throws SyntaxException {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700274 String whole = mWhole;
Hans Boehmfa5203c2015-08-17 16:14:52 -0700275 if (whole.isEmpty()) {
276 if (mFraction.isEmpty()) {
277 // Decimal point without digits.
278 throw new SyntaxException();
279 } else {
280 whole = "0";
281 }
282 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700283 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700284 BigInteger den = BigInteger.TEN.pow(mFraction.length());
Hans Boehm0b9806f2015-06-29 16:07:15 -0700285 if (mExponent > 0) {
286 num = num.multiply(BigInteger.TEN.pow(mExponent));
287 }
288 if (mExponent < 0) {
289 den = den.multiply(BigInteger.TEN.pow(-mExponent));
290 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700291 return new BoundedRational(num, den);
292 }
293
Hans Boehm84614952014-11-25 18:46:17 -0800294 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700295 public CharSequence toCharSequence(Context context) {
Hans Boehm84614952014-11-25 18:46:17 -0800296 return toString();
297 }
298
299 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700300 public TokenKind kind() {
301 return TokenKind.CONSTANT;
302 }
Hans Boehm84614952014-11-25 18:46:17 -0800303
304 // Override clone to make it public
305 @Override
306 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700307 Constant result = new Constant();
308 result.mWhole = mWhole;
309 result.mFraction = mFraction;
310 result.mSawDecimal = mSawDecimal;
311 result.mExponent = mExponent;
312 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800313 }
314 }
315
Hans Boehm3666e632015-07-27 18:33:12 -0700316 /**
317 * The "token" class for previously evaluated subexpressions.
318 * We treat previously evaluated subexpressions as tokens. These are inserted when we either
319 * continue an expression after evaluating some of it, or copy an expression and paste it back
320 * in.
Hans Boehm8f051c32016-10-03 16:53:58 -0700321 * This only contains enough information to allow us to display the expression in a
322 * formula, or reevaluate the expression with the aid of an ExprResolver; we no longer
323 * cache the result. The expression corresponding to the index can be obtained through
324 * the ExprResolver, which looks it up in a subexpression database.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800325 * The representation includes a UnifiedReal value. In order to
Hans Boehm3666e632015-07-27 18:33:12 -0700326 * support saving and restoring, we also include the underlying expression itself, and the
327 * context (currently just degree mode) used to evaluate it. The short string representation
328 * is also stored in order to avoid potentially expensive recomputation in the UI thread.
329 */
Hans Boehm84614952014-11-25 18:46:17 -0800330 private static class PreEval extends Token {
Hans Boehm8f051c32016-10-03 16:53:58 -0700331 public final long mIndex;
Hans Boehm50ed3202015-06-09 14:35:49 -0700332 private final String mShortRep; // Not internationalized.
Hans Boehm8f051c32016-10-03 16:53:58 -0700333 PreEval(long index, String shortRep) {
334 mIndex = index;
Hans Boehm84614952014-11-25 18:46:17 -0800335 mShortRep = shortRep;
336 }
Hans Boehm84614952014-11-25 18:46:17 -0800337 @Override
Hans Boehm8f051c32016-10-03 16:53:58 -0700338 // This writes out only a shallow representation of the result, without
339 // information about subexpressions. To write out a deep representation, we
340 // find referenced subexpressions, and iteratively write those as well.
Hans Boehm3666e632015-07-27 18:33:12 -0700341 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800342 out.writeByte(TokenKind.PRE_EVAL.ordinal());
Hans Boehm8f051c32016-10-03 16:53:58 -0700343 if (mIndex > Integer.MAX_VALUE || mIndex < Integer.MIN_VALUE) {
344 // This would be millions of expressions per day for the life of the device.
345 throw new AssertionError("Expression index too big");
Hans Boehm84614952014-11-25 18:46:17 -0800346 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700347 out.writeInt((int)mIndex);
348 out.writeUTF(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800349 }
350 PreEval(DataInput in) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700351 mIndex = in.readInt();
352 mShortRep = in.readUTF();
Hans Boehm84614952014-11-25 18:46:17 -0800353 }
354 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700355 public CharSequence toCharSequence(Context context) {
Hans Boehm50ed3202015-06-09 14:35:49 -0700356 return KeyMaps.translateResult(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800357 }
358 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700359 public TokenKind kind() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700360 return TokenKind.PRE_EVAL;
361 }
Hans Boehm3666e632015-07-27 18:33:12 -0700362 public boolean hasEllipsis() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700363 return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
364 }
Hans Boehm84614952014-11-25 18:46:17 -0800365 }
366
Hans Boehm3666e632015-07-27 18:33:12 -0700367 /**
368 * Read token from in.
369 */
370 public static Token newToken(DataInput in) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700371 byte kindByte = in.readByte();
372 if (kindByte < 0x20) {
373 TokenKind kind = tokenKindValues[kindByte];
374 switch(kind) {
375 case CONSTANT:
376 return new Constant(in);
377 case PRE_EVAL:
378 return new PreEval(in);
379 default: throw new IOException("Bad save file format");
380 }
381 } else {
382 return new Operator(kindByte);
Hans Boehm84614952014-11-25 18:46:17 -0800383 }
384 }
385
386 CalculatorExpr() {
387 mExpr = new ArrayList<Token>();
388 }
389
390 private CalculatorExpr(ArrayList<Token> expr) {
391 mExpr = expr;
392 }
393
Hans Boehm3666e632015-07-27 18:33:12 -0700394 /**
395 * Construct CalculatorExpr, by reading it from in.
396 */
Hans Boehm84614952014-11-25 18:46:17 -0800397 CalculatorExpr(DataInput in) throws IOException {
398 mExpr = new ArrayList<Token>();
399 int size = in.readInt();
400 for (int i = 0; i < size; ++i) {
401 mExpr.add(newToken(in));
402 }
403 }
404
Hans Boehm3666e632015-07-27 18:33:12 -0700405 /**
406 * Write this expression to out.
407 */
408 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800409 int size = mExpr.size();
410 out.writeInt(size);
411 for (int i = 0; i < size; ++i) {
412 mExpr.get(i).write(out);
413 }
414 }
415
Hans Boehm3666e632015-07-27 18:33:12 -0700416 /**
Hans Boehmf4424772016-12-05 10:35:16 -0800417 * Use write() above to generate a byte array containing a serialized representation of
418 * this expression.
419 */
420 public byte[] toBytes() {
421 ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
422 try (DataOutputStream out = new DataOutputStream(byteArrayStream)) {
423 write(out);
424 } catch (IOException e) {
425 // Impossible; No IO involved.
426 throw new AssertionError("Impossible IO exception", e);
427 }
428 return byteArrayStream.toByteArray();
429 }
430
431 /**
Hans Boehm3666e632015-07-27 18:33:12 -0700432 * Does this expression end with a numeric constant?
433 * As opposed to an operator or preevaluated expression.
434 */
Hans Boehm0b9806f2015-06-29 16:07:15 -0700435 boolean hasTrailingConstant() {
436 int s = mExpr.size();
437 if (s == 0) {
438 return false;
439 }
440 Token t = mExpr.get(s-1);
441 return t instanceof Constant;
442 }
443
Hans Boehm3666e632015-07-27 18:33:12 -0700444 /**
445 * Does this expression end with a binary operator?
446 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700447 boolean hasTrailingBinary() {
Hans Boehm84614952014-11-25 18:46:17 -0800448 int s = mExpr.size();
449 if (s == 0) return false;
450 Token t = mExpr.get(s-1);
451 if (!(t instanceof Operator)) return false;
452 Operator o = (Operator)t;
Hans Boehm3666e632015-07-27 18:33:12 -0700453 return (KeyMaps.isBinary(o.id));
Hans Boehm84614952014-11-25 18:46:17 -0800454 }
455
Hans Boehm017de982015-06-10 17:46:03 -0700456 /**
457 * Append press of button with given id to expression.
458 * If the insertion would clearly result in a syntax error, either just return false
459 * and do nothing, or make an adjustment to avoid the problem. We do the latter only
460 * for unambiguous consecutive binary operators, in which case we delete the first
461 * operator.
462 */
Hans Boehm84614952014-11-25 18:46:17 -0800463 boolean add(int id) {
464 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700465 final int d = KeyMaps.digVal(id);
466 final boolean binary = KeyMaps.isBinary(id);
Hans Boehm017de982015-06-10 17:46:03 -0700467 Token lastTok = s == 0 ? null : mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700468 int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
Hans Boehm017de982015-06-10 17:46:03 -0700469 // Quietly replace a trailing binary operator with another one, unless the second
470 // operator is minus, in which case we just allow it as a unary minus.
471 if (binary && !KeyMaps.isPrefix(id)) {
472 if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
473 || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
474 return false;
475 }
476 while (hasTrailingBinary()) {
477 delete();
478 }
479 // s invalid and not used below.
Hans Boehm84614952014-11-25 18:46:17 -0800480 }
Hans Boehm3666e632015-07-27 18:33:12 -0700481 final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
Hans Boehm84614952014-11-25 18:46:17 -0800482 if (isConstPiece) {
Hans Boehm017de982015-06-10 17:46:03 -0700483 // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
Hans Boehm84614952014-11-25 18:46:17 -0800484 if (s == 0) {
485 mExpr.add(new Constant());
486 s++;
487 } else {
488 Token last = mExpr.get(s-1);
489 if(!(last instanceof Constant)) {
Hans Boehm017de982015-06-10 17:46:03 -0700490 if (last instanceof PreEval) {
491 // Add explicit multiplication to avoid confusing display.
492 mExpr.add(new Operator(R.id.op_mul));
493 s++;
Hans Boehm84614952014-11-25 18:46:17 -0800494 }
495 mExpr.add(new Constant());
496 s++;
497 }
498 }
499 return ((Constant)(mExpr.get(s-1))).add(id);
500 } else {
501 mExpr.add(new Operator(id));
502 return true;
503 }
504 }
505
Hans Boehm017de982015-06-10 17:46:03 -0700506 /**
Hans Boehm0b9806f2015-06-29 16:07:15 -0700507 * Add exponent to the constant at the end of the expression.
508 * Assumes there is a constant at the end of the expression.
509 */
510 void addExponent(int exp) {
511 Token lastTok = mExpr.get(mExpr.size() - 1);
512 ((Constant) lastTok).addExponent(exp);
513 }
514
515 /**
Hans Boehm017de982015-06-10 17:46:03 -0700516 * Remove trailing op_add and op_sub operators.
517 */
518 void removeTrailingAdditiveOperators() {
519 while (true) {
520 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700521 if (s == 0) {
522 break;
523 }
Hans Boehm017de982015-06-10 17:46:03 -0700524 Token lastTok = mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700525 if (!(lastTok instanceof Operator)) {
526 break;
527 }
528 int lastOp = ((Operator) lastTok).id;
529 if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
530 break;
531 }
Hans Boehm017de982015-06-10 17:46:03 -0700532 delete();
533 }
534 }
535
Hans Boehm3666e632015-07-27 18:33:12 -0700536 /**
537 * Append the contents of the argument expression.
538 * It is assumed that the argument expression will not change, and thus its pieces can be
539 * reused directly.
540 */
541 public void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700542 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800543 int s2 = expr2.mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700544 // Check that we're not concatenating Constant or PreEval tokens, since the result would
545 // look like a single constant, with very mysterious results for the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700546 if (s != 0 && s2 != 0) {
547 Token last = mExpr.get(s-1);
548 Token first = expr2.mExpr.get(0);
549 if (!(first instanceof Operator) && !(last instanceof Operator)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700550 // Fudge it by adding an explicit multiplication. We would have interpreted it as
551 // such anyway, and this makes it recognizable to the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700552 mExpr.add(new Operator(R.id.op_mul));
553 }
554 }
Hans Boehm84614952014-11-25 18:46:17 -0800555 for (int i = 0; i < s2; ++i) {
556 mExpr.add(expr2.mExpr.get(i));
557 }
558 }
559
Hans Boehm3666e632015-07-27 18:33:12 -0700560 /**
561 * Undo the last key addition, if any.
562 * Or possibly remove a trailing exponent digit.
563 */
564 public void delete() {
565 final int s = mExpr.size();
566 if (s == 0) {
567 return;
568 }
Hans Boehm84614952014-11-25 18:46:17 -0800569 Token last = mExpr.get(s-1);
570 if (last instanceof Constant) {
571 Constant c = (Constant)last;
572 c.delete();
Hans Boehm3666e632015-07-27 18:33:12 -0700573 if (!c.isEmpty()) {
574 return;
575 }
Hans Boehm84614952014-11-25 18:46:17 -0800576 }
577 mExpr.remove(s-1);
578 }
579
Hans Boehm3666e632015-07-27 18:33:12 -0700580 /**
581 * Remove all tokens from the expression.
582 */
583 public void clear() {
Hans Boehm84614952014-11-25 18:46:17 -0800584 mExpr.clear();
585 }
586
Hans Boehm3666e632015-07-27 18:33:12 -0700587 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800588 return mExpr.isEmpty();
589 }
590
Hans Boehm3666e632015-07-27 18:33:12 -0700591 /**
592 * Returns a logical deep copy of the CalculatorExpr.
593 * Operator and PreEval tokens are immutable, and thus aren't really copied.
594 */
Hans Boehm84614952014-11-25 18:46:17 -0800595 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700596 CalculatorExpr result = new CalculatorExpr();
Hans Boehm9c160b42016-12-02 11:55:12 -0800597 for (Token t : mExpr) {
Hans Boehm84614952014-11-25 18:46:17 -0800598 if (t instanceof Constant) {
Hans Boehm3666e632015-07-27 18:33:12 -0700599 result.mExpr.add((Token)(((Constant)t).clone()));
Hans Boehm84614952014-11-25 18:46:17 -0800600 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700601 result.mExpr.add(t);
Hans Boehm84614952014-11-25 18:46:17 -0800602 }
603 }
Hans Boehm3666e632015-07-27 18:33:12 -0700604 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800605 }
606
607 // Am I just a constant?
Hans Boehm3666e632015-07-27 18:33:12 -0700608 public boolean isConstant() {
609 if (mExpr.size() != 1) {
610 return false;
611 }
Hans Boehm84614952014-11-25 18:46:17 -0800612 return mExpr.get(0) instanceof Constant;
613 }
614
Hans Boehm3666e632015-07-27 18:33:12 -0700615 /**
616 * Return a new expression consisting of a single token representing the current pre-evaluated
617 * expression.
Hans Boehm8f051c32016-10-03 16:53:58 -0700618 * The caller supplies the expression index and short string representation.
619 * The expression must have been previously evaluated.
Hans Boehm3666e632015-07-27 18:33:12 -0700620 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700621 public CalculatorExpr abbreviate(long index, String sr) {
Hans Boehm84614952014-11-25 18:46:17 -0800622 CalculatorExpr result = new CalculatorExpr();
Justin Klaassen0ace4eb2016-02-05 11:38:12 -0800623 @SuppressWarnings("unchecked")
Hans Boehm8f051c32016-10-03 16:53:58 -0700624 Token t = new PreEval(index, sr);
Hans Boehm84614952014-11-25 18:46:17 -0800625 result.mExpr.add(t);
626 return result;
627 }
628
Hans Boehm3666e632015-07-27 18:33:12 -0700629 /**
Hans Boehm995e5eb2016-02-08 11:03:01 -0800630 * Internal evaluation functions return an EvalRet pair.
Hans Boehm3666e632015-07-27 18:33:12 -0700631 * We compute rational (BoundedRational) results when possible, both as a performance
632 * optimization, and to detect errors exactly when we can.
633 */
634 private static class EvalRet {
635 public int pos; // Next position (expression index) to be parsed.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800636 public final UnifiedReal val; // Constructive Real result of evaluating subexpression.
637 EvalRet(int p, UnifiedReal v) {
Hans Boehm3666e632015-07-27 18:33:12 -0700638 pos = p;
639 val = v;
Hans Boehm84614952014-11-25 18:46:17 -0800640 }
641 }
642
Hans Boehm3666e632015-07-27 18:33:12 -0700643 /**
644 * Internal evaluation functions take an EvalContext argument.
645 */
Hans Boehm84614952014-11-25 18:46:17 -0800646 private static class EvalContext {
Hans Boehm3666e632015-07-27 18:33:12 -0700647 public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
Hans Boehmc023b732015-04-29 11:30:47 -0700648 public final boolean mDegreeMode;
Hans Boehm8f051c32016-10-03 16:53:58 -0700649 public final ExprResolver mExprResolver; // Reconstructed, not saved.
Hans Boehm84614952014-11-25 18:46:17 -0800650 // If we add any other kinds of evaluation modes, they go here.
Hans Boehm8f051c32016-10-03 16:53:58 -0700651 EvalContext(boolean degreeMode, int len, ExprResolver er) {
Hans Boehm84614952014-11-25 18:46:17 -0800652 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700653 mPrefixLength = len;
Hans Boehm8f051c32016-10-03 16:53:58 -0700654 mExprResolver = er;
Hans Boehm84614952014-11-25 18:46:17 -0800655 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700656 EvalContext(DataInput in, int len, ExprResolver er) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800657 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700658 mPrefixLength = len;
Hans Boehm8f051c32016-10-03 16:53:58 -0700659 mExprResolver = er;
Hans Boehm84614952014-11-25 18:46:17 -0800660 }
661 void write(DataOutput out) throws IOException {
662 out.writeBoolean(mDegreeMode);
663 }
664 }
665
Hans Boehm995e5eb2016-02-08 11:03:01 -0800666 private UnifiedReal toRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800667 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800668 return x.multiply(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800669 } else {
670 return x;
671 }
672 }
673
Hans Boehm995e5eb2016-02-08 11:03:01 -0800674 private UnifiedReal fromRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800675 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800676 return x.divide(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800677 } else {
678 return x;
679 }
680 }
681
Hans Boehm3666e632015-07-27 18:33:12 -0700682 // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
683 // error. We expect that to be caught in eval below.
Hans Boehm84614952014-11-25 18:46:17 -0800684
Hans Boehmc023b732015-04-29 11:30:47 -0700685 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800686 Token t = mExpr.get(i);
Hans Boehm3666e632015-07-27 18:33:12 -0700687 if (!(t instanceof Operator)) {
688 return false;
689 }
690 return ((Operator)(t)).id == op;
Hans Boehm84614952014-11-25 18:46:17 -0800691 }
692
Hans Boehmc023b732015-04-29 11:30:47 -0700693 private boolean isOperator(int i, int op, EvalContext ec) {
Hans Boehm3666e632015-07-27 18:33:12 -0700694 if (i >= ec.mPrefixLength) {
695 return false;
696 }
Hans Boehmc023b732015-04-29 11:30:47 -0700697 return isOperatorUnchecked(i, op);
698 }
699
Hans Boehm3666e632015-07-27 18:33:12 -0700700 public static class SyntaxException extends Exception {
Hans Boehmc023b732015-04-29 11:30:47 -0700701 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800702 super();
703 }
Hans Boehmc023b732015-04-29 11:30:47 -0700704 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800705 super(s);
706 }
707 }
708
Hans Boehm3666e632015-07-27 18:33:12 -0700709 // The following functions all evaluate some kind of expression starting at position i in
710 // mExpr in a specified evaluation context. They return both the expression value (as
711 // constructive real and, if applicable, as BoundedRational) and the position of the next token
Hans Boehm84614952014-11-25 18:46:17 -0800712 // that was not used as part of the evaluation.
Hans Boehm3666e632015-07-27 18:33:12 -0700713 // This is essentially a simple recursive descent parser combined with expression evaluation.
714
Hans Boehmc023b732015-04-29 11:30:47 -0700715 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700716 final Token t = mExpr.get(i);
Hans Boehm84614952014-11-25 18:46:17 -0800717 if (t instanceof Constant) {
718 Constant c = (Constant)t;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800719 return new EvalRet(i+1,new UnifiedReal(c.toRational()));
Hans Boehm84614952014-11-25 18:46:17 -0800720 }
721 if (t instanceof PreEval) {
Hans Boehm8f051c32016-10-03 16:53:58 -0700722 final long index = ((PreEval)t).mIndex;
723 UnifiedReal res = ec.mExprResolver.getResult(index);
724 if (res == null) {
Hans Boehm9c160b42016-12-02 11:55:12 -0800725 // We try to minimize this recursive evaluation case, but currently don't
726 // completely avoid it.
727 res = nestedEval(index, ec.mExprResolver);
Hans Boehm8f051c32016-10-03 16:53:58 -0700728 }
729 return new EvalRet(i+1, res);
Hans Boehm84614952014-11-25 18:46:17 -0800730 }
731 EvalRet argVal;
Hans Boehm3666e632015-07-27 18:33:12 -0700732 switch(((Operator)(t)).id) {
Hans Boehm84614952014-11-25 18:46:17 -0800733 case R.id.const_pi:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800734 return new EvalRet(i+1, UnifiedReal.PI);
Hans Boehm84614952014-11-25 18:46:17 -0800735 case R.id.const_e:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800736 return new EvalRet(i+1, UnifiedReal.E);
Hans Boehm84614952014-11-25 18:46:17 -0800737 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700738 // Seems to have highest precedence.
739 // Does not add implicit paren.
740 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700741 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700742 argVal = evalUnary(i+2, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800743 return new EvalRet(argVal.pos, argVal.val.negate().sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700744 } else {
745 argVal = evalUnary(i+1, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800746 return new EvalRet(argVal.pos, argVal.val.sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700747 }
Hans Boehm84614952014-11-25 18:46:17 -0800748 case R.id.lparen:
749 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700750 if (isOperator(argVal.pos, R.id.rparen, ec)) {
751 argVal.pos++;
752 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800753 return new EvalRet(argVal.pos, argVal.val);
Hans Boehm84614952014-11-25 18:46:17 -0800754 case R.id.fun_sin:
755 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700756 if (isOperator(argVal.pos, R.id.rparen, ec)) {
757 argVal.pos++;
758 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800759 return new EvalRet(argVal.pos, toRadians(argVal.val, ec).sin());
Hans Boehm84614952014-11-25 18:46:17 -0800760 case R.id.fun_cos:
761 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700762 if (isOperator(argVal.pos, R.id.rparen, ec)) {
763 argVal.pos++;
764 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800765 return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos());
Hans Boehm84614952014-11-25 18:46:17 -0800766 case R.id.fun_tan:
767 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700768 if (isOperator(argVal.pos, R.id.rparen, ec)) {
769 argVal.pos++;
770 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800771 UnifiedReal arg = toRadians(argVal.val, ec);
772 return new EvalRet(argVal.pos, arg.sin().divide(arg.cos()));
Hans Boehm84614952014-11-25 18:46:17 -0800773 case R.id.fun_ln:
774 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700775 if (isOperator(argVal.pos, R.id.rparen, ec)) {
776 argVal.pos++;
777 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800778 return new EvalRet(argVal.pos, argVal.val.ln());
Hans Boehm4db31b42015-05-31 12:19:05 -0700779 case R.id.fun_exp:
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 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800784 return new EvalRet(argVal.pos, argVal.val.exp());
Hans Boehm84614952014-11-25 18:46:17 -0800785 case R.id.fun_log:
786 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700787 if (isOperator(argVal.pos, R.id.rparen, ec)) {
788 argVal.pos++;
789 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800790 return new EvalRet(argVal.pos, argVal.val.ln().divide(UnifiedReal.TEN.ln()));
Hans Boehm84614952014-11-25 18:46:17 -0800791 case R.id.fun_arcsin:
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 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800796 return new EvalRet(argVal.pos, fromRadians(argVal.val.asin(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800797 case R.id.fun_arccos:
798 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700799 if (isOperator(argVal.pos, R.id.rparen, ec)) {
800 argVal.pos++;
801 }
Hans Boehm79f273c2016-06-09 11:00:27 -0700802 return new EvalRet(argVal.pos, fromRadians(argVal.val.acos(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800803 case R.id.fun_arctan:
804 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700805 if (isOperator(argVal.pos, R.id.rparen, ec)) {
806 argVal.pos++;
807 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800808 return new EvalRet(argVal.pos, fromRadians(argVal.val.atan(),ec));
Hans Boehm84614952014-11-25 18:46:17 -0800809 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700810 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800811 }
Hans Boehm84614952014-11-25 18:46:17 -0800812 }
813
Hans Boehm995e5eb2016-02-08 11:03:01 -0800814 private static final UnifiedReal ONE_HUNDREDTH = new UnifiedReal(100).inverse();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700815
Hans Boehm4db31b42015-05-31 12:19:05 -0700816 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700817 final EvalRet tmp = evalUnary(i, ec);
818 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800819 UnifiedReal val = tmp.val;
820
Hans Boehm4db31b42015-05-31 12:19:05 -0700821 boolean isFact;
822 boolean isSquared = false;
823 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
824 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
825 isOperator(cpos, R.id.op_pct, ec)) {
826 if (isFact) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800827 val = val.fact();
Hans Boehm4db31b42015-05-31 12:19:05 -0700828 } else if (isSquared) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800829 val = val.multiply(val);
Hans Boehm4db31b42015-05-31 12:19:05 -0700830 } else /* percent */ {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800831 val = val.multiply(ONE_HUNDREDTH);
Hans Boehm84614952014-11-25 18:46:17 -0800832 }
Hans Boehm84614952014-11-25 18:46:17 -0800833 ++cpos;
834 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800835 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800836 }
837
Hans Boehmc023b732015-04-29 11:30:47 -0700838 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700839 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700840 int cpos = result1.pos; // current position
Hans Boehm995e5eb2016-02-08 11:03:01 -0800841 UnifiedReal val = result1.val; // value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700842 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700843 final EvalRet exp = evalSignedFactor(cpos + 1, ec);
844 cpos = exp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800845 val = val.pow(exp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800846 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800847 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800848 }
849
Hans Boehmc023b732015-04-29 11:30:47 -0700850 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
851 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700852 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800853 EvalRet tmp = evalFactor(cpos, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700854 cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800855 final UnifiedReal result = negative ? tmp.val.negate() : tmp.val;
856 return new EvalRet(cpos, result);
Hans Boehm84614952014-11-25 18:46:17 -0800857 }
858
859 private boolean canStartFactor(int i) {
860 if (i >= mExpr.size()) return false;
861 Token t = mExpr.get(i);
862 if (!(t instanceof Operator)) return true;
Hans Boehm3666e632015-07-27 18:33:12 -0700863 int id = ((Operator)(t)).id;
Hans Boehm84614952014-11-25 18:46:17 -0800864 if (KeyMaps.isBinary(id)) return false;
865 switch (id) {
866 case R.id.op_fact:
867 case R.id.rparen:
868 return false;
869 default:
870 return true;
871 }
872 }
873
Hans Boehmc023b732015-04-29 11:30:47 -0700874 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800875 EvalRet tmp = evalSignedFactor(i, ec);
876 boolean is_mul = false;
877 boolean is_div = false;
Hans Boehm3666e632015-07-27 18:33:12 -0700878 int cpos = tmp.pos; // Current position in expression.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800879 UnifiedReal val = tmp.val; // Current value.
Hans Boehmc023b732015-04-29 11:30:47 -0700880 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
881 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -0800882 || canStartFactor(cpos)) {
883 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700884 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800885 if (is_div) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800886 val = val.divide(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800887 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800888 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800889 }
Hans Boehm3666e632015-07-27 18:33:12 -0700890 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800891 is_mul = is_div = false;
892 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800893 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800894 }
895
Hans Boehm8afd0f82015-07-30 17:00:33 -0700896 /**
897 * Is the subexpression starting at pos a simple percent constant?
898 * This is used to recognize exppressions like 200+10%, which we handle specially.
899 * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
900 * by either nothing or an additive operator.
901 * Note that we are intentionally far more restrictive in recognizing such expressions than
902 * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
903 * When in doubt, we fall back to the the naive interpretation of % as 1/100.
904 * Note that 100+(10)% yields 100.1 while 100+10% yields 110. This may be controversial,
905 * but is consistent with Google web search.
906 */
907 private boolean isPercent(int pos) {
908 if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
909 return false;
910 }
911 Token number = mExpr.get(pos);
912 if (number instanceof Operator) {
913 return false;
914 }
915 if (mExpr.size() == pos + 2) {
916 return true;
917 }
918 if (!(mExpr.get(pos + 2) instanceof Operator)) {
919 return false;
920 }
921 Operator op = (Operator) mExpr.get(pos + 2);
Hans Boehm64d1cdb2016-03-08 19:14:07 -0800922 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 -0700923 }
924
925 /**
926 * Compute the multiplicative factor corresponding to an N% addition or subtraction.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800927 * @param pos position of Constant or PreEval expression token corresponding to N.
928 * @param isSubtraction this is a subtraction, as opposed to addition.
929 * @param ec usable evaluation contex; only length matters.
930 * @return UnifiedReal value and position, which is pos + 2, i.e. after percent sign
Hans Boehm8afd0f82015-07-30 17:00:33 -0700931 */
932 private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
933 throws SyntaxException {
934 EvalRet tmp = evalUnary(pos, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800935 UnifiedReal val = isSubtraction ? tmp.val.negate() : tmp.val;
936 val = UnifiedReal.ONE.add(val.multiply(ONE_HUNDREDTH));
937 return new EvalRet(pos + 2 /* after percent sign */, val);
Hans Boehm8afd0f82015-07-30 17:00:33 -0700938 }
939
Hans Boehmc023b732015-04-29 11:30:47 -0700940 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800941 EvalRet tmp = evalTerm(i, ec);
942 boolean is_plus;
Hans Boehm3666e632015-07-27 18:33:12 -0700943 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800944 UnifiedReal val = tmp.val;
Hans Boehmc023b732015-04-29 11:30:47 -0700945 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
946 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700947 if (isPercent(cpos + 1)) {
948 tmp = getPercentFactor(cpos + 1, !is_plus, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800949 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800950 } else {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700951 tmp = evalTerm(cpos + 1, ec);
952 if (is_plus) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800953 val = val.add(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700954 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800955 val = val.subtract(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800956 }
957 }
Hans Boehm3666e632015-07-27 18:33:12 -0700958 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800959 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800960 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800961 }
962
Hans Boehme8553762015-06-26 17:56:52 -0700963 /**
964 * Return the starting position of the sequence of trailing binary operators.
965 */
966 private int trailingBinaryOpsStart() {
Hans Boehmc023b732015-04-29 11:30:47 -0700967 int result = mExpr.size();
968 while (result > 0) {
969 Token last = mExpr.get(result - 1);
970 if (!(last instanceof Operator)) break;
971 Operator o = (Operator)last;
Hans Boehm3666e632015-07-27 18:33:12 -0700972 if (!KeyMaps.isBinary(o.id)) break;
Hans Boehmc023b732015-04-29 11:30:47 -0700973 --result;
974 }
975 return result;
976 }
977
Hans Boehm3666e632015-07-27 18:33:12 -0700978 /**
979 * Is the current expression worth evaluating?
980 */
Hans Boehmc023b732015-04-29 11:30:47 -0700981 public boolean hasInterestingOps() {
Hans Boehme8553762015-06-26 17:56:52 -0700982 int last = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -0700983 int first = 0;
984 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
985 // Leading minus is not by itself interesting.
986 first++;
987 }
988 for (int i = first; i < last; ++i) {
989 Token t1 = mExpr.get(i);
Hans Boehm187d3e92015-06-09 18:04:26 -0700990 if (t1 instanceof Operator
991 || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
992 return true;
993 }
Hans Boehmc023b732015-04-29 11:30:47 -0700994 }
995 return false;
996 }
997
Hans Boehme8553762015-06-26 17:56:52 -0700998 /**
Hans Boehm52d477a2016-04-01 17:42:50 -0700999 * Does the expression contain trig operations?
1000 */
1001 public boolean hasTrigFuncs() {
Hans Boehm9c160b42016-12-02 11:55:12 -08001002 for (Token t : mExpr) {
Hans Boehm52d477a2016-04-01 17:42:50 -07001003 if (t instanceof Operator) {
1004 Operator o = (Operator)t;
1005 if (KeyMaps.isTrigFunc(o.id)) {
1006 return true;
1007 }
1008 }
1009 }
1010 return false;
1011 }
1012
1013 /**
Hans Boehm9c160b42016-12-02 11:55:12 -08001014 * Add the indices of unevaluated PreEval expressions embedded in the current expression to
1015 * argument. This includes only directly referenced expressions e, not those indirectly
1016 * referenced by e. If the index was already present, it is not added. If the argument
1017 * contained no duplicates, the result will not either. New indices are added to the end of
1018 * the list.
1019 */
1020 private void addReferencedExprs(ArrayList<Long> list, ExprResolver er) {
1021 for (Token t : mExpr) {
1022 if (t instanceof PreEval) {
1023 Long index = ((PreEval) t).mIndex;
1024 if (er.getResult(index) == null && !list.contains(index)) {
1025 list.add(index);
1026 }
1027 }
1028 }
1029 }
1030
1031 /**
1032 * Return a list of unevaluated expressions transitively referenced by the current one.
1033 * All expressions in the resulting list will have had er.getExpr() called on them.
1034 * The resulting list is ordered such that evaluating expressions in list order
1035 * should trigger few recursive evaluations.
1036 */
1037 public ArrayList<Long> getTransitivelyReferencedExprs(ExprResolver er) {
1038 // We could avoid triggering any recursive evaluations by actually building the
1039 // dependency graph and topologically sorting it. Note that sorting by index works
1040 // for positive and negative indices separately, but not their union. Currently we
1041 // just settle for reverse breadth-first-search order, which handles the common case
1042 // of simple dependency chains well.
1043 ArrayList<Long> list = new ArrayList<Long>();
1044 int scanned = 0; // We've added expressions referenced by [0, scanned) to the list
1045 addReferencedExprs(list, er);
1046 while (scanned != list.size()) {
1047 er.getExpr(list.get(scanned++)).addReferencedExprs(list, er);
1048 }
1049 Collections.reverse(list);
1050 return list;
1051 }
1052
1053 /**
1054 * Evaluate the expression at the given index to a UnifiedReal.
1055 * Both saves and returns the result.
1056 */
1057 UnifiedReal nestedEval(long index, ExprResolver er) throws SyntaxException {
1058 CalculatorExpr nestedExpr = er.getExpr(index);
1059 EvalContext newEc = new EvalContext(er.getDegreeMode(index),
1060 nestedExpr.trailingBinaryOpsStart(), er);
1061 EvalRet new_res = nestedExpr.evalExpr(0, newEc);
1062 return er.putResultIfAbsent(index, new_res.val);
1063 }
1064
1065 /**
Hans Boehme8553762015-06-26 17:56:52 -07001066 * Evaluate the expression excluding trailing binary operators.
Hans Boehm3666e632015-07-27 18:33:12 -07001067 * Errors result in exceptions, most of which are unchecked. Should not be called
1068 * concurrently with modification of the expression. May take a very long time; avoid calling
1069 * from UI thread.
Hans Boehme8553762015-06-26 17:56:52 -07001070 *
1071 * @param degreeMode use degrees rather than radians
1072 */
Hans Boehm8f051c32016-10-03 16:53:58 -07001073 UnifiedReal eval(boolean degreeMode, ExprResolver er) throws SyntaxException
Hans Boehm995e5eb2016-02-08 11:03:01 -08001074 // And unchecked exceptions thrown by UnifiedReal, CR,
Hans Boehmc023b732015-04-29 11:30:47 -07001075 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -08001076 {
Hans Boehm9c160b42016-12-02 11:55:12 -08001077 // First evaluate all indirectly referenced expressions in increasing index order.
1078 // This ensures that subsequent evaluation never encounters an embedded PreEval
1079 // expression that has not been previously evaluated.
1080 // We could do the embedded evaluations recursively, but that risks running out of
1081 // stack space.
1082 ArrayList<Long> referenced = getTransitivelyReferencedExprs(er);
1083 for (long index : referenced) {
1084 nestedEval(index, er);
1085 }
Hans Boehm84614952014-11-25 18:46:17 -08001086 try {
Hans Boehm3666e632015-07-27 18:33:12 -07001087 // We currently never include trailing binary operators, but include other trailing
1088 // operators. Thus we usually, but not always, display results for prefixes of valid
1089 // expressions, and don't generate an error where we previously displayed an instant
1090 // result. This reflects the Android L design.
Hans Boehme8553762015-06-26 17:56:52 -07001091 int prefixLen = trailingBinaryOpsStart();
Hans Boehm8f051c32016-10-03 16:53:58 -07001092 EvalContext ec = new EvalContext(degreeMode, prefixLen, er);
Hans Boehm84614952014-11-25 18:46:17 -08001093 EvalRet res = evalExpr(0, ec);
Hans Boehm3666e632015-07-27 18:33:12 -07001094 if (res.pos != prefixLen) {
Hans Boehmc023b732015-04-29 11:30:47 -07001095 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -07001096 }
Hans Boehm995e5eb2016-02-08 11:03:01 -08001097 return res.val;
Hans Boehm84614952014-11-25 18:46:17 -08001098 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -07001099 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -08001100 }
1101 }
1102
1103 // Produce a string representation of the expression itself
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001104 SpannableStringBuilder toSpannableStringBuilder(Context context) {
1105 SpannableStringBuilder ssb = new SpannableStringBuilder();
Hans Boehm9c160b42016-12-02 11:55:12 -08001106 for (Token t : mExpr) {
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001107 ssb.append(t.toCharSequence(context));
Hans Boehm84614952014-11-25 18:46:17 -08001108 }
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001109 return ssb;
Hans Boehm84614952014-11-25 18:46:17 -08001110 }
1111}