blob: 18bb6cf3cd1e36edf7fb2326c187296d9029c6ce [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);
Hans Boehm8f051c32016-10-03 16:53:58 -070075 }
76
Hans Boehm84614952014-11-25 18:46:17 -080077 private ArrayList<Token> mExpr; // The actual representation
78 // as a list of tokens. Constant
79 // tokens are always nonempty.
80
81 private static enum TokenKind { CONSTANT, OPERATOR, PRE_EVAL };
82 private static TokenKind[] tokenKindValues = TokenKind.values();
83 private final static BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
84 private final static BigInteger BIG_BILLION = BigInteger.valueOf(1000000000);
85
86 private static abstract class Token {
87 abstract TokenKind kind();
Hans Boehm8a4f81c2015-07-09 10:41:25 -070088
89 /**
Hans Boehm8f051c32016-10-03 16:53:58 -070090 * Write token as either a very small Byte containing the TokenKind,
91 * followed by data needed by subclass constructor,
92 * or as a byte >= 0x20 directly describing the OPERATOR token.
Hans Boehm8a4f81c2015-07-09 10:41:25 -070093 */
Hans Boehm84614952014-11-25 18:46:17 -080094 abstract void write(DataOutput out) throws IOException;
Hans Boehm8a4f81c2015-07-09 10:41:25 -070095
96 /**
97 * Return a textual representation of the token.
98 * The result is suitable for either display as part od the formula or TalkBack use.
99 * It may be a SpannableString that includes added TalkBack information.
100 * @param context context used for converting button ids to strings
101 */
102 abstract CharSequence toCharSequence(Context context);
Hans Boehm84614952014-11-25 18:46:17 -0800103 }
104
Hans Boehm3666e632015-07-27 18:33:12 -0700105 /**
106 * Representation of an operator token
107 */
Hans Boehm84614952014-11-25 18:46:17 -0800108 private static class Operator extends Token {
Hans Boehm8f051c32016-10-03 16:53:58 -0700109 // TODO: rename id.
Hans Boehm3666e632015-07-27 18:33:12 -0700110 public final int id; // We use the button resource id
Hans Boehm84614952014-11-25 18:46:17 -0800111 Operator(int resId) {
Hans Boehm3666e632015-07-27 18:33:12 -0700112 id = resId;
Hans Boehm84614952014-11-25 18:46:17 -0800113 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700114 Operator(byte op) throws IOException {
115 id = KeyMaps.fromByte(op);
Hans Boehm84614952014-11-25 18:46:17 -0800116 }
117 @Override
118 void write(DataOutput out) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700119 out.writeByte(KeyMaps.toByte(id));
Hans Boehm84614952014-11-25 18:46:17 -0800120 }
121 @Override
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700122 public CharSequence toCharSequence(Context context) {
Hans Boehm3666e632015-07-27 18:33:12 -0700123 String desc = KeyMaps.toDescriptiveString(context, id);
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700124 if (desc != null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700125 SpannableString result = new SpannableString(KeyMaps.toString(context, id));
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700126 Object descSpan = new TtsSpan.TextBuilder(desc).build();
127 result.setSpan(descSpan, 0, result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
128 return result;
129 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700130 return KeyMaps.toString(context, id);
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700131 }
Hans Boehm84614952014-11-25 18:46:17 -0800132 }
133 @Override
134 TokenKind kind() { return TokenKind.OPERATOR; }
135 }
136
Hans Boehm3666e632015-07-27 18:33:12 -0700137 /**
138 * Representation of a (possibly incomplete) numerical constant.
139 * Supports addition and removal of trailing characters; hence mutable.
140 */
Hans Boehm84614952014-11-25 18:46:17 -0800141 private static class Constant extends Token implements Cloneable {
142 private boolean mSawDecimal;
Hans Boehm3666e632015-07-27 18:33:12 -0700143 private String mWhole; // String preceding decimal point.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700144 private String mFraction; // String after decimal point.
145 private int mExponent; // Explicit exponent, only generated through addExponent.
Hans Boehm8f051c32016-10-03 16:53:58 -0700146 private static int SAW_DECIMAL = 0x1;
147 private static int HAS_EXPONENT = 0x2;
Hans Boehm84614952014-11-25 18:46:17 -0800148
149 Constant() {
150 mWhole = "";
151 mFraction = "";
Hans Boehm8f051c32016-10-03 16:53:58 -0700152 // mSawDecimal = false;
153 // mExponent = 0;
Hans Boehm84614952014-11-25 18:46:17 -0800154 };
155
156 Constant(DataInput in) throws IOException {
157 mWhole = in.readUTF();
Hans Boehm8f051c32016-10-03 16:53:58 -0700158 byte flags = in.readByte();
159 if ((flags & SAW_DECIMAL) != 0) {
160 mSawDecimal = true;
161 mFraction = in.readUTF();
162 } else {
163 // mSawDecimal = false;
164 mFraction = "";
165 }
166 if ((flags & HAS_EXPONENT) != 0) {
167 mExponent = in.readInt();
168 }
Hans Boehm84614952014-11-25 18:46:17 -0800169 }
170
171 @Override
172 void write(DataOutput out) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700173 byte flags = (byte)((mSawDecimal ? SAW_DECIMAL : 0)
174 | (mExponent != 0 ? HAS_EXPONENT : 0));
Hans Boehm84614952014-11-25 18:46:17 -0800175 out.writeByte(TokenKind.CONSTANT.ordinal());
176 out.writeUTF(mWhole);
Hans Boehm8f051c32016-10-03 16:53:58 -0700177 out.writeByte(flags);
178 if (mSawDecimal) {
179 out.writeUTF(mFraction);
180 }
181 if (mExponent != 0) {
182 out.writeInt(mExponent);
183 }
Hans Boehm84614952014-11-25 18:46:17 -0800184 }
185
186 // Given a button press, append corresponding digit.
187 // We assume id is a digit or decimal point.
188 // Just return false if this was the second (or later) decimal point
189 // in this constant.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700190 // Assumes that this constant does not have an exponent.
Hans Boehm3666e632015-07-27 18:33:12 -0700191 public boolean add(int id) {
Hans Boehm84614952014-11-25 18:46:17 -0800192 if (id == R.id.dec_point) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700193 if (mSawDecimal || mExponent != 0) return false;
Hans Boehm84614952014-11-25 18:46:17 -0800194 mSawDecimal = true;
195 return true;
196 }
197 int val = KeyMaps.digVal(id);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700198 if (mExponent != 0) {
199 if (Math.abs(mExponent) <= 10000) {
200 if (mExponent > 0) {
201 mExponent = 10 * mExponent + val;
202 } else {
203 mExponent = 10 * mExponent - val;
204 }
205 return true;
206 } else { // Too large; refuse
207 return false;
208 }
209 }
Hans Boehm84614952014-11-25 18:46:17 -0800210 if (mSawDecimal) {
211 mFraction += val;
212 } else {
213 mWhole += val;
214 }
215 return true;
216 }
217
Hans Boehm3666e632015-07-27 18:33:12 -0700218 public void addExponent(int exp) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700219 // Note that adding a 0 exponent is a no-op. That's OK.
220 mExponent = exp;
221 }
222
Hans Boehm3666e632015-07-27 18:33:12 -0700223 /**
224 * Undo the last add or remove last exponent digit.
225 * Assumes the constant is nonempty.
226 */
227 public void delete() {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700228 if (mExponent != 0) {
229 mExponent /= 10;
230 // Once zero, it can only be added back with addExponent.
231 } else if (!mFraction.isEmpty()) {
Hans Boehm84614952014-11-25 18:46:17 -0800232 mFraction = mFraction.substring(0, mFraction.length() - 1);
233 } else if (mSawDecimal) {
234 mSawDecimal = false;
235 } else {
236 mWhole = mWhole.substring(0, mWhole.length() - 1);
237 }
238 }
239
Hans Boehm3666e632015-07-27 18:33:12 -0700240 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800241 return (mSawDecimal == false && mWhole.isEmpty());
242 }
243
Hans Boehm3666e632015-07-27 18:33:12 -0700244 /**
245 * Produce human-readable string representation of constant, as typed.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700246 * We do add digit grouping separators to the whole number, even if not typed.
Hans Boehm3666e632015-07-27 18:33:12 -0700247 * Result is internationalized.
248 */
Hans Boehm84614952014-11-25 18:46:17 -0800249 @Override
250 public String toString() {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700251 String result;
252 if (mExponent != 0) {
253 result = mWhole;
254 } else {
255 result = StringUtils.addCommas(mWhole, 0, mWhole.length());
256 }
Hans Boehm84614952014-11-25 18:46:17 -0800257 if (mSawDecimal) {
Hans Boehm013969e2015-04-13 20:29:47 -0700258 result += '.';
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700259 result += mFraction;
260 }
Hans Boehm0b9806f2015-06-29 16:07:15 -0700261 if (mExponent != 0) {
262 result += "E" + mExponent;
263 }
Hans Boehm013969e2015-04-13 20:29:47 -0700264 return KeyMaps.translateResult(result);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700265 }
266
Hans Boehm3666e632015-07-27 18:33:12 -0700267 /**
Hans Boehmfa5203c2015-08-17 16:14:52 -0700268 * Return BoundedRational representation of constant, if well-formed.
269 * Result is never null.
Hans Boehm3666e632015-07-27 18:33:12 -0700270 */
Hans Boehmfa5203c2015-08-17 16:14:52 -0700271 public BoundedRational toRational() throws SyntaxException {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700272 String whole = mWhole;
Hans Boehmfa5203c2015-08-17 16:14:52 -0700273 if (whole.isEmpty()) {
274 if (mFraction.isEmpty()) {
275 // Decimal point without digits.
276 throw new SyntaxException();
277 } else {
278 whole = "0";
279 }
280 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700281 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700282 BigInteger den = BigInteger.TEN.pow(mFraction.length());
Hans Boehm0b9806f2015-06-29 16:07:15 -0700283 if (mExponent > 0) {
284 num = num.multiply(BigInteger.TEN.pow(mExponent));
285 }
286 if (mExponent < 0) {
287 den = den.multiply(BigInteger.TEN.pow(-mExponent));
288 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700289 return new BoundedRational(num, den);
290 }
291
Hans Boehm84614952014-11-25 18:46:17 -0800292 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700293 public CharSequence toCharSequence(Context context) {
Hans Boehm84614952014-11-25 18:46:17 -0800294 return toString();
295 }
296
297 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700298 public TokenKind kind() {
299 return TokenKind.CONSTANT;
300 }
Hans Boehm84614952014-11-25 18:46:17 -0800301
302 // Override clone to make it public
303 @Override
304 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700305 Constant result = new Constant();
306 result.mWhole = mWhole;
307 result.mFraction = mFraction;
308 result.mSawDecimal = mSawDecimal;
309 result.mExponent = mExponent;
310 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800311 }
312 }
313
Hans Boehm3666e632015-07-27 18:33:12 -0700314 /**
315 * The "token" class for previously evaluated subexpressions.
316 * We treat previously evaluated subexpressions as tokens. These are inserted when we either
317 * continue an expression after evaluating some of it, or copy an expression and paste it back
318 * in.
Hans Boehm8f051c32016-10-03 16:53:58 -0700319 * This only contains enough information to allow us to display the expression in a
320 * formula, or reevaluate the expression with the aid of an ExprResolver; we no longer
321 * cache the result. The expression corresponding to the index can be obtained through
322 * the ExprResolver, which looks it up in a subexpression database.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800323 * The representation includes a UnifiedReal value. In order to
Hans Boehm3666e632015-07-27 18:33:12 -0700324 * support saving and restoring, we also include the underlying expression itself, and the
325 * context (currently just degree mode) used to evaluate it. The short string representation
326 * is also stored in order to avoid potentially expensive recomputation in the UI thread.
327 */
Hans Boehm84614952014-11-25 18:46:17 -0800328 private static class PreEval extends Token {
Hans Boehm8f051c32016-10-03 16:53:58 -0700329 public final long mIndex;
Hans Boehm50ed3202015-06-09 14:35:49 -0700330 private final String mShortRep; // Not internationalized.
Hans Boehm8f051c32016-10-03 16:53:58 -0700331 PreEval(long index, String shortRep) {
332 mIndex = index;
Hans Boehm84614952014-11-25 18:46:17 -0800333 mShortRep = shortRep;
334 }
Hans Boehm84614952014-11-25 18:46:17 -0800335 @Override
Hans Boehm8f051c32016-10-03 16:53:58 -0700336 // This writes out only a shallow representation of the result, without
337 // information about subexpressions. To write out a deep representation, we
338 // find referenced subexpressions, and iteratively write those as well.
Hans Boehm3666e632015-07-27 18:33:12 -0700339 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800340 out.writeByte(TokenKind.PRE_EVAL.ordinal());
Hans Boehm8f051c32016-10-03 16:53:58 -0700341 if (mIndex > Integer.MAX_VALUE || mIndex < Integer.MIN_VALUE) {
342 // This would be millions of expressions per day for the life of the device.
343 throw new AssertionError("Expression index too big");
Hans Boehm84614952014-11-25 18:46:17 -0800344 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700345 out.writeInt((int)mIndex);
346 out.writeUTF(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800347 }
348 PreEval(DataInput in) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700349 mIndex = in.readInt();
350 mShortRep = in.readUTF();
Hans Boehm84614952014-11-25 18:46:17 -0800351 }
352 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700353 public CharSequence toCharSequence(Context context) {
Hans Boehm50ed3202015-06-09 14:35:49 -0700354 return KeyMaps.translateResult(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800355 }
356 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700357 public TokenKind kind() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700358 return TokenKind.PRE_EVAL;
359 }
Hans Boehm3666e632015-07-27 18:33:12 -0700360 public boolean hasEllipsis() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700361 return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
362 }
Hans Boehm84614952014-11-25 18:46:17 -0800363 }
364
Hans Boehm3666e632015-07-27 18:33:12 -0700365 /**
366 * Read token from in.
367 */
368 public static Token newToken(DataInput in) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700369 byte kindByte = in.readByte();
370 if (kindByte < 0x20) {
371 TokenKind kind = tokenKindValues[kindByte];
372 switch(kind) {
373 case CONSTANT:
374 return new Constant(in);
375 case PRE_EVAL:
376 return new PreEval(in);
377 default: throw new IOException("Bad save file format");
378 }
379 } else {
380 return new Operator(kindByte);
Hans Boehm84614952014-11-25 18:46:17 -0800381 }
382 }
383
384 CalculatorExpr() {
385 mExpr = new ArrayList<Token>();
386 }
387
388 private CalculatorExpr(ArrayList<Token> expr) {
389 mExpr = expr;
390 }
391
Hans Boehm3666e632015-07-27 18:33:12 -0700392 /**
393 * Construct CalculatorExpr, by reading it from in.
394 */
Hans Boehm84614952014-11-25 18:46:17 -0800395 CalculatorExpr(DataInput in) throws IOException {
396 mExpr = new ArrayList<Token>();
397 int size = in.readInt();
398 for (int i = 0; i < size; ++i) {
399 mExpr.add(newToken(in));
400 }
401 }
402
Hans Boehm3666e632015-07-27 18:33:12 -0700403 /**
404 * Write this expression to out.
405 */
406 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800407 int size = mExpr.size();
408 out.writeInt(size);
409 for (int i = 0; i < size; ++i) {
410 mExpr.get(i).write(out);
411 }
412 }
413
Hans Boehm3666e632015-07-27 18:33:12 -0700414 /**
Hans Boehmf4424772016-12-05 10:35:16 -0800415 * Use write() above to generate a byte array containing a serialized representation of
416 * this expression.
417 */
418 public byte[] toBytes() {
419 ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
420 try (DataOutputStream out = new DataOutputStream(byteArrayStream)) {
421 write(out);
422 } catch (IOException e) {
423 // Impossible; No IO involved.
424 throw new AssertionError("Impossible IO exception", e);
425 }
426 return byteArrayStream.toByteArray();
427 }
428
429 /**
Hans Boehm3666e632015-07-27 18:33:12 -0700430 * Does this expression end with a numeric constant?
431 * As opposed to an operator or preevaluated expression.
432 */
Hans Boehm0b9806f2015-06-29 16:07:15 -0700433 boolean hasTrailingConstant() {
434 int s = mExpr.size();
435 if (s == 0) {
436 return false;
437 }
438 Token t = mExpr.get(s-1);
439 return t instanceof Constant;
440 }
441
Hans Boehm3666e632015-07-27 18:33:12 -0700442 /**
443 * Does this expression end with a binary operator?
444 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700445 boolean hasTrailingBinary() {
Hans Boehm84614952014-11-25 18:46:17 -0800446 int s = mExpr.size();
447 if (s == 0) return false;
448 Token t = mExpr.get(s-1);
449 if (!(t instanceof Operator)) return false;
450 Operator o = (Operator)t;
Hans Boehm3666e632015-07-27 18:33:12 -0700451 return (KeyMaps.isBinary(o.id));
Hans Boehm84614952014-11-25 18:46:17 -0800452 }
453
Hans Boehm017de982015-06-10 17:46:03 -0700454 /**
455 * Append press of button with given id to expression.
456 * If the insertion would clearly result in a syntax error, either just return false
457 * and do nothing, or make an adjustment to avoid the problem. We do the latter only
458 * for unambiguous consecutive binary operators, in which case we delete the first
459 * operator.
460 */
Hans Boehm84614952014-11-25 18:46:17 -0800461 boolean add(int id) {
462 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700463 final int d = KeyMaps.digVal(id);
464 final boolean binary = KeyMaps.isBinary(id);
Hans Boehm017de982015-06-10 17:46:03 -0700465 Token lastTok = s == 0 ? null : mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700466 int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
Hans Boehm017de982015-06-10 17:46:03 -0700467 // Quietly replace a trailing binary operator with another one, unless the second
468 // operator is minus, in which case we just allow it as a unary minus.
469 if (binary && !KeyMaps.isPrefix(id)) {
470 if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
471 || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
472 return false;
473 }
474 while (hasTrailingBinary()) {
475 delete();
476 }
477 // s invalid and not used below.
Hans Boehm84614952014-11-25 18:46:17 -0800478 }
Hans Boehm3666e632015-07-27 18:33:12 -0700479 final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
Hans Boehm84614952014-11-25 18:46:17 -0800480 if (isConstPiece) {
Hans Boehm017de982015-06-10 17:46:03 -0700481 // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
Hans Boehm84614952014-11-25 18:46:17 -0800482 if (s == 0) {
483 mExpr.add(new Constant());
484 s++;
485 } else {
486 Token last = mExpr.get(s-1);
487 if(!(last instanceof Constant)) {
Hans Boehm017de982015-06-10 17:46:03 -0700488 if (last instanceof PreEval) {
489 // Add explicit multiplication to avoid confusing display.
490 mExpr.add(new Operator(R.id.op_mul));
491 s++;
Hans Boehm84614952014-11-25 18:46:17 -0800492 }
493 mExpr.add(new Constant());
494 s++;
495 }
496 }
497 return ((Constant)(mExpr.get(s-1))).add(id);
498 } else {
499 mExpr.add(new Operator(id));
500 return true;
501 }
502 }
503
Hans Boehm017de982015-06-10 17:46:03 -0700504 /**
Hans Boehm0b9806f2015-06-29 16:07:15 -0700505 * Add exponent to the constant at the end of the expression.
506 * Assumes there is a constant at the end of the expression.
507 */
508 void addExponent(int exp) {
509 Token lastTok = mExpr.get(mExpr.size() - 1);
510 ((Constant) lastTok).addExponent(exp);
511 }
512
513 /**
Hans Boehm017de982015-06-10 17:46:03 -0700514 * Remove trailing op_add and op_sub operators.
515 */
516 void removeTrailingAdditiveOperators() {
517 while (true) {
518 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700519 if (s == 0) {
520 break;
521 }
Hans Boehm017de982015-06-10 17:46:03 -0700522 Token lastTok = mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700523 if (!(lastTok instanceof Operator)) {
524 break;
525 }
526 int lastOp = ((Operator) lastTok).id;
527 if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
528 break;
529 }
Hans Boehm017de982015-06-10 17:46:03 -0700530 delete();
531 }
532 }
533
Hans Boehm3666e632015-07-27 18:33:12 -0700534 /**
535 * Append the contents of the argument expression.
536 * It is assumed that the argument expression will not change, and thus its pieces can be
537 * reused directly.
538 */
539 public void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700540 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800541 int s2 = expr2.mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700542 // Check that we're not concatenating Constant or PreEval tokens, since the result would
543 // look like a single constant, with very mysterious results for the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700544 if (s != 0 && s2 != 0) {
545 Token last = mExpr.get(s-1);
546 Token first = expr2.mExpr.get(0);
547 if (!(first instanceof Operator) && !(last instanceof Operator)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700548 // Fudge it by adding an explicit multiplication. We would have interpreted it as
549 // such anyway, and this makes it recognizable to the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700550 mExpr.add(new Operator(R.id.op_mul));
551 }
552 }
Hans Boehm84614952014-11-25 18:46:17 -0800553 for (int i = 0; i < s2; ++i) {
554 mExpr.add(expr2.mExpr.get(i));
555 }
556 }
557
Hans Boehm3666e632015-07-27 18:33:12 -0700558 /**
559 * Undo the last key addition, if any.
560 * Or possibly remove a trailing exponent digit.
561 */
562 public void delete() {
563 final int s = mExpr.size();
564 if (s == 0) {
565 return;
566 }
Hans Boehm84614952014-11-25 18:46:17 -0800567 Token last = mExpr.get(s-1);
568 if (last instanceof Constant) {
569 Constant c = (Constant)last;
570 c.delete();
Hans Boehm3666e632015-07-27 18:33:12 -0700571 if (!c.isEmpty()) {
572 return;
573 }
Hans Boehm84614952014-11-25 18:46:17 -0800574 }
575 mExpr.remove(s-1);
576 }
577
Hans Boehm3666e632015-07-27 18:33:12 -0700578 /**
579 * Remove all tokens from the expression.
580 */
581 public void clear() {
Hans Boehm84614952014-11-25 18:46:17 -0800582 mExpr.clear();
583 }
584
Hans Boehm3666e632015-07-27 18:33:12 -0700585 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800586 return mExpr.isEmpty();
587 }
588
Hans Boehm3666e632015-07-27 18:33:12 -0700589 /**
590 * Returns a logical deep copy of the CalculatorExpr.
591 * Operator and PreEval tokens are immutable, and thus aren't really copied.
592 */
Hans Boehm84614952014-11-25 18:46:17 -0800593 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700594 CalculatorExpr result = new CalculatorExpr();
Hans Boehm9c160b42016-12-02 11:55:12 -0800595 for (Token t : mExpr) {
Hans Boehm84614952014-11-25 18:46:17 -0800596 if (t instanceof Constant) {
Hans Boehm3666e632015-07-27 18:33:12 -0700597 result.mExpr.add((Token)(((Constant)t).clone()));
Hans Boehm84614952014-11-25 18:46:17 -0800598 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700599 result.mExpr.add(t);
Hans Boehm84614952014-11-25 18:46:17 -0800600 }
601 }
Hans Boehm3666e632015-07-27 18:33:12 -0700602 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800603 }
604
605 // Am I just a constant?
Hans Boehm3666e632015-07-27 18:33:12 -0700606 public boolean isConstant() {
607 if (mExpr.size() != 1) {
608 return false;
609 }
Hans Boehm84614952014-11-25 18:46:17 -0800610 return mExpr.get(0) instanceof Constant;
611 }
612
Hans Boehm3666e632015-07-27 18:33:12 -0700613 /**
614 * Return a new expression consisting of a single token representing the current pre-evaluated
615 * expression.
Hans Boehm8f051c32016-10-03 16:53:58 -0700616 * The caller supplies the expression index and short string representation.
617 * The expression must have been previously evaluated.
Hans Boehm3666e632015-07-27 18:33:12 -0700618 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700619 public CalculatorExpr abbreviate(long index, String sr) {
Hans Boehm84614952014-11-25 18:46:17 -0800620 CalculatorExpr result = new CalculatorExpr();
Justin Klaassen0ace4eb2016-02-05 11:38:12 -0800621 @SuppressWarnings("unchecked")
Hans Boehm8f051c32016-10-03 16:53:58 -0700622 Token t = new PreEval(index, sr);
Hans Boehm84614952014-11-25 18:46:17 -0800623 result.mExpr.add(t);
624 return result;
625 }
626
Hans Boehm3666e632015-07-27 18:33:12 -0700627 /**
Hans Boehm995e5eb2016-02-08 11:03:01 -0800628 * Internal evaluation functions return an EvalRet pair.
Hans Boehm3666e632015-07-27 18:33:12 -0700629 * We compute rational (BoundedRational) results when possible, both as a performance
630 * optimization, and to detect errors exactly when we can.
631 */
632 private static class EvalRet {
633 public int pos; // Next position (expression index) to be parsed.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800634 public final UnifiedReal val; // Constructive Real result of evaluating subexpression.
635 EvalRet(int p, UnifiedReal v) {
Hans Boehm3666e632015-07-27 18:33:12 -0700636 pos = p;
637 val = v;
Hans Boehm84614952014-11-25 18:46:17 -0800638 }
639 }
640
Hans Boehm3666e632015-07-27 18:33:12 -0700641 /**
642 * Internal evaluation functions take an EvalContext argument.
643 */
Hans Boehm84614952014-11-25 18:46:17 -0800644 private static class EvalContext {
Hans Boehm3666e632015-07-27 18:33:12 -0700645 public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
Hans Boehmc023b732015-04-29 11:30:47 -0700646 public final boolean mDegreeMode;
Hans Boehm8f051c32016-10-03 16:53:58 -0700647 public final ExprResolver mExprResolver; // Reconstructed, not saved.
Hans Boehm84614952014-11-25 18:46:17 -0800648 // If we add any other kinds of evaluation modes, they go here.
Hans Boehm8f051c32016-10-03 16:53:58 -0700649 EvalContext(boolean degreeMode, int len, ExprResolver er) {
Hans Boehm84614952014-11-25 18:46:17 -0800650 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700651 mPrefixLength = len;
Hans Boehm8f051c32016-10-03 16:53:58 -0700652 mExprResolver = er;
Hans Boehm84614952014-11-25 18:46:17 -0800653 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700654 EvalContext(DataInput in, int len, ExprResolver er) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800655 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700656 mPrefixLength = len;
Hans Boehm8f051c32016-10-03 16:53:58 -0700657 mExprResolver = er;
Hans Boehm84614952014-11-25 18:46:17 -0800658 }
659 void write(DataOutput out) throws IOException {
660 out.writeBoolean(mDegreeMode);
661 }
662 }
663
Hans Boehm995e5eb2016-02-08 11:03:01 -0800664 private UnifiedReal toRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800665 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800666 return x.multiply(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800667 } else {
668 return x;
669 }
670 }
671
Hans Boehm995e5eb2016-02-08 11:03:01 -0800672 private UnifiedReal fromRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800673 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800674 return x.divide(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800675 } else {
676 return x;
677 }
678 }
679
Hans Boehm3666e632015-07-27 18:33:12 -0700680 // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
681 // error. We expect that to be caught in eval below.
Hans Boehm84614952014-11-25 18:46:17 -0800682
Hans Boehmc023b732015-04-29 11:30:47 -0700683 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800684 Token t = mExpr.get(i);
Hans Boehm3666e632015-07-27 18:33:12 -0700685 if (!(t instanceof Operator)) {
686 return false;
687 }
688 return ((Operator)(t)).id == op;
Hans Boehm84614952014-11-25 18:46:17 -0800689 }
690
Hans Boehmc023b732015-04-29 11:30:47 -0700691 private boolean isOperator(int i, int op, EvalContext ec) {
Hans Boehm3666e632015-07-27 18:33:12 -0700692 if (i >= ec.mPrefixLength) {
693 return false;
694 }
Hans Boehmc023b732015-04-29 11:30:47 -0700695 return isOperatorUnchecked(i, op);
696 }
697
Hans Boehm3666e632015-07-27 18:33:12 -0700698 public static class SyntaxException extends Exception {
Hans Boehmc023b732015-04-29 11:30:47 -0700699 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800700 super();
701 }
Hans Boehmc023b732015-04-29 11:30:47 -0700702 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800703 super(s);
704 }
705 }
706
Hans Boehm3666e632015-07-27 18:33:12 -0700707 // The following functions all evaluate some kind of expression starting at position i in
708 // mExpr in a specified evaluation context. They return both the expression value (as
709 // constructive real and, if applicable, as BoundedRational) and the position of the next token
Hans Boehm84614952014-11-25 18:46:17 -0800710 // that was not used as part of the evaluation.
Hans Boehm3666e632015-07-27 18:33:12 -0700711 // This is essentially a simple recursive descent parser combined with expression evaluation.
712
Hans Boehmc023b732015-04-29 11:30:47 -0700713 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700714 final Token t = mExpr.get(i);
Hans Boehm84614952014-11-25 18:46:17 -0800715 if (t instanceof Constant) {
716 Constant c = (Constant)t;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800717 return new EvalRet(i+1,new UnifiedReal(c.toRational()));
Hans Boehm84614952014-11-25 18:46:17 -0800718 }
719 if (t instanceof PreEval) {
Hans Boehm8f051c32016-10-03 16:53:58 -0700720 final long index = ((PreEval)t).mIndex;
721 UnifiedReal res = ec.mExprResolver.getResult(index);
722 if (res == null) {
Hans Boehm9c160b42016-12-02 11:55:12 -0800723 // We try to minimize this recursive evaluation case, but currently don't
724 // completely avoid it.
725 res = nestedEval(index, ec.mExprResolver);
Hans Boehm8f051c32016-10-03 16:53:58 -0700726 }
727 return new EvalRet(i+1, res);
Hans Boehm84614952014-11-25 18:46:17 -0800728 }
729 EvalRet argVal;
Hans Boehm3666e632015-07-27 18:33:12 -0700730 switch(((Operator)(t)).id) {
Hans Boehm84614952014-11-25 18:46:17 -0800731 case R.id.const_pi:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800732 return new EvalRet(i+1, UnifiedReal.PI);
Hans Boehm84614952014-11-25 18:46:17 -0800733 case R.id.const_e:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800734 return new EvalRet(i+1, UnifiedReal.E);
Hans Boehm84614952014-11-25 18:46:17 -0800735 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700736 // Seems to have highest precedence.
737 // Does not add implicit paren.
738 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700739 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700740 argVal = evalUnary(i+2, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800741 return new EvalRet(argVal.pos, argVal.val.negate().sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700742 } else {
743 argVal = evalUnary(i+1, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800744 return new EvalRet(argVal.pos, argVal.val.sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700745 }
Hans Boehm84614952014-11-25 18:46:17 -0800746 case R.id.lparen:
747 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700748 if (isOperator(argVal.pos, R.id.rparen, ec)) {
749 argVal.pos++;
750 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800751 return new EvalRet(argVal.pos, argVal.val);
Hans Boehm84614952014-11-25 18:46:17 -0800752 case R.id.fun_sin:
753 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700754 if (isOperator(argVal.pos, R.id.rparen, ec)) {
755 argVal.pos++;
756 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800757 return new EvalRet(argVal.pos, toRadians(argVal.val, ec).sin());
Hans Boehm84614952014-11-25 18:46:17 -0800758 case R.id.fun_cos:
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 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800763 return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos());
Hans Boehm84614952014-11-25 18:46:17 -0800764 case R.id.fun_tan:
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 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800769 UnifiedReal arg = toRadians(argVal.val, ec);
770 return new EvalRet(argVal.pos, arg.sin().divide(arg.cos()));
Hans Boehm84614952014-11-25 18:46:17 -0800771 case R.id.fun_ln:
772 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700773 if (isOperator(argVal.pos, R.id.rparen, ec)) {
774 argVal.pos++;
775 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800776 return new EvalRet(argVal.pos, argVal.val.ln());
Hans Boehm4db31b42015-05-31 12:19:05 -0700777 case R.id.fun_exp:
778 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700779 if (isOperator(argVal.pos, R.id.rparen, ec)) {
780 argVal.pos++;
781 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800782 return new EvalRet(argVal.pos, argVal.val.exp());
Hans Boehm84614952014-11-25 18:46:17 -0800783 case R.id.fun_log:
784 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700785 if (isOperator(argVal.pos, R.id.rparen, ec)) {
786 argVal.pos++;
787 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800788 return new EvalRet(argVal.pos, argVal.val.ln().divide(UnifiedReal.TEN.ln()));
Hans Boehm84614952014-11-25 18:46:17 -0800789 case R.id.fun_arcsin:
790 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700791 if (isOperator(argVal.pos, R.id.rparen, ec)) {
792 argVal.pos++;
793 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800794 return new EvalRet(argVal.pos, fromRadians(argVal.val.asin(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800795 case R.id.fun_arccos:
796 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700797 if (isOperator(argVal.pos, R.id.rparen, ec)) {
798 argVal.pos++;
799 }
Hans Boehm79f273c2016-06-09 11:00:27 -0700800 return new EvalRet(argVal.pos, fromRadians(argVal.val.acos(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800801 case R.id.fun_arctan:
802 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700803 if (isOperator(argVal.pos, R.id.rparen, ec)) {
804 argVal.pos++;
805 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800806 return new EvalRet(argVal.pos, fromRadians(argVal.val.atan(),ec));
Hans Boehm84614952014-11-25 18:46:17 -0800807 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700808 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800809 }
Hans Boehm84614952014-11-25 18:46:17 -0800810 }
811
Hans Boehm995e5eb2016-02-08 11:03:01 -0800812 private static final UnifiedReal ONE_HUNDREDTH = new UnifiedReal(100).inverse();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700813
Hans Boehm4db31b42015-05-31 12:19:05 -0700814 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700815 final EvalRet tmp = evalUnary(i, ec);
816 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800817 UnifiedReal val = tmp.val;
818
Hans Boehm4db31b42015-05-31 12:19:05 -0700819 boolean isFact;
820 boolean isSquared = false;
821 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
822 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
823 isOperator(cpos, R.id.op_pct, ec)) {
824 if (isFact) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800825 val = val.fact();
Hans Boehm4db31b42015-05-31 12:19:05 -0700826 } else if (isSquared) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800827 val = val.multiply(val);
Hans Boehm4db31b42015-05-31 12:19:05 -0700828 } else /* percent */ {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800829 val = val.multiply(ONE_HUNDREDTH);
Hans Boehm84614952014-11-25 18:46:17 -0800830 }
Hans Boehm84614952014-11-25 18:46:17 -0800831 ++cpos;
832 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800833 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800834 }
835
Hans Boehmc023b732015-04-29 11:30:47 -0700836 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700837 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700838 int cpos = result1.pos; // current position
Hans Boehm995e5eb2016-02-08 11:03:01 -0800839 UnifiedReal val = result1.val; // value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700840 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700841 final EvalRet exp = evalSignedFactor(cpos + 1, ec);
842 cpos = exp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800843 val = val.pow(exp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800844 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800845 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800846 }
847
Hans Boehmc023b732015-04-29 11:30:47 -0700848 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
849 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700850 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800851 EvalRet tmp = evalFactor(cpos, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700852 cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800853 final UnifiedReal result = negative ? tmp.val.negate() : tmp.val;
854 return new EvalRet(cpos, result);
Hans Boehm84614952014-11-25 18:46:17 -0800855 }
856
857 private boolean canStartFactor(int i) {
858 if (i >= mExpr.size()) return false;
859 Token t = mExpr.get(i);
860 if (!(t instanceof Operator)) return true;
Hans Boehm3666e632015-07-27 18:33:12 -0700861 int id = ((Operator)(t)).id;
Hans Boehm84614952014-11-25 18:46:17 -0800862 if (KeyMaps.isBinary(id)) return false;
863 switch (id) {
864 case R.id.op_fact:
865 case R.id.rparen:
866 return false;
867 default:
868 return true;
869 }
870 }
871
Hans Boehmc023b732015-04-29 11:30:47 -0700872 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800873 EvalRet tmp = evalSignedFactor(i, ec);
874 boolean is_mul = false;
875 boolean is_div = false;
Hans Boehm3666e632015-07-27 18:33:12 -0700876 int cpos = tmp.pos; // Current position in expression.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800877 UnifiedReal val = tmp.val; // Current value.
Hans Boehmc023b732015-04-29 11:30:47 -0700878 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
879 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -0800880 || canStartFactor(cpos)) {
881 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700882 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800883 if (is_div) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800884 val = val.divide(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800885 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800886 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800887 }
Hans Boehm3666e632015-07-27 18:33:12 -0700888 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800889 is_mul = is_div = false;
890 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800891 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800892 }
893
Hans Boehm8afd0f82015-07-30 17:00:33 -0700894 /**
895 * Is the subexpression starting at pos a simple percent constant?
896 * This is used to recognize exppressions like 200+10%, which we handle specially.
897 * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
898 * by either nothing or an additive operator.
899 * Note that we are intentionally far more restrictive in recognizing such expressions than
900 * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
901 * When in doubt, we fall back to the the naive interpretation of % as 1/100.
902 * Note that 100+(10)% yields 100.1 while 100+10% yields 110. This may be controversial,
903 * but is consistent with Google web search.
904 */
905 private boolean isPercent(int pos) {
906 if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
907 return false;
908 }
909 Token number = mExpr.get(pos);
910 if (number instanceof Operator) {
911 return false;
912 }
913 if (mExpr.size() == pos + 2) {
914 return true;
915 }
916 if (!(mExpr.get(pos + 2) instanceof Operator)) {
917 return false;
918 }
919 Operator op = (Operator) mExpr.get(pos + 2);
Hans Boehm64d1cdb2016-03-08 19:14:07 -0800920 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 -0700921 }
922
923 /**
924 * Compute the multiplicative factor corresponding to an N% addition or subtraction.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800925 * @param pos position of Constant or PreEval expression token corresponding to N.
926 * @param isSubtraction this is a subtraction, as opposed to addition.
927 * @param ec usable evaluation contex; only length matters.
928 * @return UnifiedReal value and position, which is pos + 2, i.e. after percent sign
Hans Boehm8afd0f82015-07-30 17:00:33 -0700929 */
930 private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
931 throws SyntaxException {
932 EvalRet tmp = evalUnary(pos, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800933 UnifiedReal val = isSubtraction ? tmp.val.negate() : tmp.val;
934 val = UnifiedReal.ONE.add(val.multiply(ONE_HUNDREDTH));
935 return new EvalRet(pos + 2 /* after percent sign */, val);
Hans Boehm8afd0f82015-07-30 17:00:33 -0700936 }
937
Hans Boehmc023b732015-04-29 11:30:47 -0700938 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800939 EvalRet tmp = evalTerm(i, ec);
940 boolean is_plus;
Hans Boehm3666e632015-07-27 18:33:12 -0700941 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800942 UnifiedReal val = tmp.val;
Hans Boehmc023b732015-04-29 11:30:47 -0700943 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
944 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700945 if (isPercent(cpos + 1)) {
946 tmp = getPercentFactor(cpos + 1, !is_plus, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800947 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800948 } else {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700949 tmp = evalTerm(cpos + 1, ec);
950 if (is_plus) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800951 val = val.add(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700952 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800953 val = val.subtract(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800954 }
955 }
Hans Boehm3666e632015-07-27 18:33:12 -0700956 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800957 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800958 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800959 }
960
Hans Boehme8553762015-06-26 17:56:52 -0700961 /**
962 * Return the starting position of the sequence of trailing binary operators.
963 */
964 private int trailingBinaryOpsStart() {
Hans Boehmc023b732015-04-29 11:30:47 -0700965 int result = mExpr.size();
966 while (result > 0) {
967 Token last = mExpr.get(result - 1);
968 if (!(last instanceof Operator)) break;
969 Operator o = (Operator)last;
Hans Boehm3666e632015-07-27 18:33:12 -0700970 if (!KeyMaps.isBinary(o.id)) break;
Hans Boehmc023b732015-04-29 11:30:47 -0700971 --result;
972 }
973 return result;
974 }
975
Hans Boehm3666e632015-07-27 18:33:12 -0700976 /**
977 * Is the current expression worth evaluating?
978 */
Hans Boehmc023b732015-04-29 11:30:47 -0700979 public boolean hasInterestingOps() {
Hans Boehm83f278e2016-12-19 16:20:03 -0800980 final int last = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -0700981 int first = 0;
982 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
983 // Leading minus is not by itself interesting.
984 first++;
985 }
986 for (int i = first; i < last; ++i) {
987 Token t1 = mExpr.get(i);
Hans Boehm187d3e92015-06-09 18:04:26 -0700988 if (t1 instanceof Operator
989 || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
990 return true;
991 }
Hans Boehmc023b732015-04-29 11:30:47 -0700992 }
993 return false;
994 }
995
Hans Boehme8553762015-06-26 17:56:52 -0700996 /**
Hans Boehm52d477a2016-04-01 17:42:50 -0700997 * Does the expression contain trig operations?
998 */
999 public boolean hasTrigFuncs() {
Hans Boehm9c160b42016-12-02 11:55:12 -08001000 for (Token t : mExpr) {
Hans Boehm52d477a2016-04-01 17:42:50 -07001001 if (t instanceof Operator) {
1002 Operator o = (Operator)t;
1003 if (KeyMaps.isTrigFunc(o.id)) {
1004 return true;
1005 }
1006 }
1007 }
1008 return false;
1009 }
1010
1011 /**
Hans Boehm9c160b42016-12-02 11:55:12 -08001012 * Add the indices of unevaluated PreEval expressions embedded in the current expression to
1013 * argument. This includes only directly referenced expressions e, not those indirectly
1014 * referenced by e. If the index was already present, it is not added. If the argument
1015 * contained no duplicates, the result will not either. New indices are added to the end of
1016 * the list.
1017 */
1018 private void addReferencedExprs(ArrayList<Long> list, ExprResolver er) {
1019 for (Token t : mExpr) {
1020 if (t instanceof PreEval) {
1021 Long index = ((PreEval) t).mIndex;
1022 if (er.getResult(index) == null && !list.contains(index)) {
1023 list.add(index);
1024 }
1025 }
1026 }
1027 }
1028
1029 /**
1030 * Return a list of unevaluated expressions transitively referenced by the current one.
1031 * All expressions in the resulting list will have had er.getExpr() called on them.
1032 * The resulting list is ordered such that evaluating expressions in list order
1033 * should trigger few recursive evaluations.
1034 */
1035 public ArrayList<Long> getTransitivelyReferencedExprs(ExprResolver er) {
1036 // We could avoid triggering any recursive evaluations by actually building the
1037 // dependency graph and topologically sorting it. Note that sorting by index works
1038 // for positive and negative indices separately, but not their union. Currently we
1039 // just settle for reverse breadth-first-search order, which handles the common case
1040 // of simple dependency chains well.
1041 ArrayList<Long> list = new ArrayList<Long>();
1042 int scanned = 0; // We've added expressions referenced by [0, scanned) to the list
1043 addReferencedExprs(list, er);
1044 while (scanned != list.size()) {
1045 er.getExpr(list.get(scanned++)).addReferencedExprs(list, er);
1046 }
1047 Collections.reverse(list);
1048 return list;
1049 }
1050
1051 /**
1052 * Evaluate the expression at the given index to a UnifiedReal.
1053 * Both saves and returns the result.
1054 */
1055 UnifiedReal nestedEval(long index, ExprResolver er) throws SyntaxException {
1056 CalculatorExpr nestedExpr = er.getExpr(index);
1057 EvalContext newEc = new EvalContext(er.getDegreeMode(index),
1058 nestedExpr.trailingBinaryOpsStart(), er);
1059 EvalRet new_res = nestedExpr.evalExpr(0, newEc);
1060 return er.putResultIfAbsent(index, new_res.val);
1061 }
1062
1063 /**
Hans Boehme8553762015-06-26 17:56:52 -07001064 * Evaluate the expression excluding trailing binary operators.
Hans Boehm3666e632015-07-27 18:33:12 -07001065 * Errors result in exceptions, most of which are unchecked. Should not be called
1066 * concurrently with modification of the expression. May take a very long time; avoid calling
1067 * from UI thread.
Hans Boehme8553762015-06-26 17:56:52 -07001068 *
1069 * @param degreeMode use degrees rather than radians
1070 */
Hans Boehm8f051c32016-10-03 16:53:58 -07001071 UnifiedReal eval(boolean degreeMode, ExprResolver er) throws SyntaxException
Hans Boehm995e5eb2016-02-08 11:03:01 -08001072 // And unchecked exceptions thrown by UnifiedReal, CR,
Hans Boehmc023b732015-04-29 11:30:47 -07001073 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -08001074 {
Hans Boehm9c160b42016-12-02 11:55:12 -08001075 // First evaluate all indirectly referenced expressions in increasing index order.
1076 // This ensures that subsequent evaluation never encounters an embedded PreEval
1077 // expression that has not been previously evaluated.
1078 // We could do the embedded evaluations recursively, but that risks running out of
1079 // stack space.
1080 ArrayList<Long> referenced = getTransitivelyReferencedExprs(er);
1081 for (long index : referenced) {
1082 nestedEval(index, er);
1083 }
Hans Boehm84614952014-11-25 18:46:17 -08001084 try {
Hans Boehm3666e632015-07-27 18:33:12 -07001085 // We currently never include trailing binary operators, but include other trailing
1086 // operators. Thus we usually, but not always, display results for prefixes of valid
1087 // expressions, and don't generate an error where we previously displayed an instant
1088 // result. This reflects the Android L design.
Hans Boehme8553762015-06-26 17:56:52 -07001089 int prefixLen = trailingBinaryOpsStart();
Hans Boehm8f051c32016-10-03 16:53:58 -07001090 EvalContext ec = new EvalContext(degreeMode, prefixLen, er);
Hans Boehm84614952014-11-25 18:46:17 -08001091 EvalRet res = evalExpr(0, ec);
Hans Boehm3666e632015-07-27 18:33:12 -07001092 if (res.pos != prefixLen) {
Hans Boehmc023b732015-04-29 11:30:47 -07001093 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -07001094 }
Hans Boehm995e5eb2016-02-08 11:03:01 -08001095 return res.val;
Hans Boehm84614952014-11-25 18:46:17 -08001096 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -07001097 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -08001098 }
1099 }
1100
1101 // Produce a string representation of the expression itself
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001102 SpannableStringBuilder toSpannableStringBuilder(Context context) {
1103 SpannableStringBuilder ssb = new SpannableStringBuilder();
Hans Boehm9c160b42016-12-02 11:55:12 -08001104 for (Token t : mExpr) {
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001105 ssb.append(t.toCharSequence(context));
Hans Boehm84614952014-11-25 18:46:17 -08001106 }
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001107 return ssb;
Hans Boehm84614952014-11-25 18:46:17 -08001108 }
1109}