blob: f20d2bf2548c4f6a5b7e8548b0a4087c3a7b4b77 [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;
24import android.text.style.TtsSpan.TextBuilder;
Hans Boehm84614952014-11-25 18:46:17 -080025
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070026import java.math.BigInteger;
27import java.io.DataInput;
28import java.io.DataOutput;
29import java.io.IOException;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070030import java.util.ArrayList;
31import java.util.HashMap;
32import java.util.IdentityHashMap;
33
Hans Boehm3666e632015-07-27 18:33:12 -070034/**
35 * A mathematical expression represented as a sequence of "tokens".
36 * Many tokens are represented by button ids for the corresponding operator.
37 * A token may also represent the result of a previously evaluated expression.
38 * The add() method adds a token to the end of the expression. The delete method() removes one.
39 * Clear() deletes the entire expression contents. Eval() evaluates the expression,
Hans Boehm995e5eb2016-02-08 11:03:01 -080040 * producing a UnifiedReal result.
Hans Boehm3666e632015-07-27 18:33:12 -070041 * Expressions are parsed only during evaluation; no explicit parse tree is maintained.
42 *
Hans Boehm995e5eb2016-02-08 11:03:01 -080043 * The write() method is used to save the current expression. Note that neither UnifiedReal
44 * nor the underlying CR provide a serialization facility. Thus we save all previously
45 * computed values by writing out the expression that was used to compute them, and reevaluate
46 * when reading it back in.
Hans Boehm3666e632015-07-27 18:33:12 -070047 */
Hans Boehm84614952014-11-25 18:46:17 -080048class CalculatorExpr {
Hans Boehm8f051c32016-10-03 16:53:58 -070049 /**
50 * An interface for resolving expression indices in embedded subexpressions to
51 * the associated CalculatorExpr, and associating a UnifiedReal result with it.
52 * All methods are thread-safe in the strong sense; they may be called asynchronously
53 * at any time from any thread.
54 */
55 public interface ExprResolver {
56 /*
57 * Retrieve the expression corresponding to index.
58 */
59 CalculatorExpr getExpr(long index);
60 /*
61 * Retrive the degree mode associated with the expression at index i.
62 */
63 boolean getDegreeMode(long index);
64 /*
65 * Retrieve the stored result for the expression at index, or return null.
66 */
67 UnifiedReal getResult(long index);
68 /*
69 * Atomically test for an existing result, and set it if there was none.
70 * Return the prior result if there was one, or the new one if there was not.
71 * May only be called after getExpr.
72 */
73 UnifiedReal putResultIfAbsent(long index, UnifiedReal result);
74 // FIXME: Check that long timeouts for embedded expressions are propagated
75 // correctly.
76 }
77
Hans Boehm84614952014-11-25 18:46:17 -080078 private ArrayList<Token> mExpr; // The actual representation
79 // as a list of tokens. Constant
80 // tokens are always nonempty.
81
82 private static enum TokenKind { CONSTANT, OPERATOR, PRE_EVAL };
83 private static TokenKind[] tokenKindValues = TokenKind.values();
84 private final static BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
85 private final static BigInteger BIG_BILLION = BigInteger.valueOf(1000000000);
86
87 private static abstract class Token {
88 abstract TokenKind kind();
Hans Boehm8a4f81c2015-07-09 10:41:25 -070089
90 /**
Hans Boehm8f051c32016-10-03 16:53:58 -070091 * Write token as either a very small Byte containing the TokenKind,
92 * followed by data needed by subclass constructor,
93 * or as a byte >= 0x20 directly describing the OPERATOR token.
Hans Boehm8a4f81c2015-07-09 10:41:25 -070094 */
Hans Boehm84614952014-11-25 18:46:17 -080095 abstract void write(DataOutput out) throws IOException;
Hans Boehm8a4f81c2015-07-09 10:41:25 -070096
97 /**
98 * Return a textual representation of the token.
99 * The result is suitable for either display as part od the formula or TalkBack use.
100 * It may be a SpannableString that includes added TalkBack information.
101 * @param context context used for converting button ids to strings
102 */
103 abstract CharSequence toCharSequence(Context context);
Hans Boehm84614952014-11-25 18:46:17 -0800104 }
105
Hans Boehm3666e632015-07-27 18:33:12 -0700106 /**
107 * Representation of an operator token
108 */
Hans Boehm84614952014-11-25 18:46:17 -0800109 private static class Operator extends Token {
Hans Boehm8f051c32016-10-03 16:53:58 -0700110 // TODO: rename id.
Hans Boehm3666e632015-07-27 18:33:12 -0700111 public final int id; // We use the button resource id
Hans Boehm84614952014-11-25 18:46:17 -0800112 Operator(int resId) {
Hans Boehm3666e632015-07-27 18:33:12 -0700113 id = resId;
Hans Boehm84614952014-11-25 18:46:17 -0800114 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700115 Operator(byte op) throws IOException {
116 id = KeyMaps.fromByte(op);
Hans Boehm84614952014-11-25 18:46:17 -0800117 }
118 @Override
119 void write(DataOutput out) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700120 out.writeByte(KeyMaps.toByte(id));
Hans Boehm84614952014-11-25 18:46:17 -0800121 }
122 @Override
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700123 public CharSequence toCharSequence(Context context) {
Hans Boehm3666e632015-07-27 18:33:12 -0700124 String desc = KeyMaps.toDescriptiveString(context, id);
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700125 if (desc != null) {
Hans Boehm3666e632015-07-27 18:33:12 -0700126 SpannableString result = new SpannableString(KeyMaps.toString(context, id));
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700127 Object descSpan = new TtsSpan.TextBuilder(desc).build();
128 result.setSpan(descSpan, 0, result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
129 return result;
130 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700131 return KeyMaps.toString(context, id);
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700132 }
Hans Boehm84614952014-11-25 18:46:17 -0800133 }
134 @Override
135 TokenKind kind() { return TokenKind.OPERATOR; }
136 }
137
Hans Boehm3666e632015-07-27 18:33:12 -0700138 /**
139 * Representation of a (possibly incomplete) numerical constant.
140 * Supports addition and removal of trailing characters; hence mutable.
141 */
Hans Boehm84614952014-11-25 18:46:17 -0800142 private static class Constant extends Token implements Cloneable {
143 private boolean mSawDecimal;
Hans Boehm3666e632015-07-27 18:33:12 -0700144 private String mWhole; // String preceding decimal point.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700145 private String mFraction; // String after decimal point.
146 private int mExponent; // Explicit exponent, only generated through addExponent.
Hans Boehm8f051c32016-10-03 16:53:58 -0700147 private static int SAW_DECIMAL = 0x1;
148 private static int HAS_EXPONENT = 0x2;
Hans Boehm84614952014-11-25 18:46:17 -0800149
150 Constant() {
151 mWhole = "";
152 mFraction = "";
Hans Boehm8f051c32016-10-03 16:53:58 -0700153 // mSawDecimal = false;
154 // mExponent = 0;
Hans Boehm84614952014-11-25 18:46:17 -0800155 };
156
157 Constant(DataInput in) throws IOException {
158 mWhole = in.readUTF();
Hans Boehm8f051c32016-10-03 16:53:58 -0700159 byte flags = in.readByte();
160 if ((flags & SAW_DECIMAL) != 0) {
161 mSawDecimal = true;
162 mFraction = in.readUTF();
163 } else {
164 // mSawDecimal = false;
165 mFraction = "";
166 }
167 if ((flags & HAS_EXPONENT) != 0) {
168 mExponent = in.readInt();
169 }
Hans Boehm84614952014-11-25 18:46:17 -0800170 }
171
172 @Override
173 void write(DataOutput out) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700174 byte flags = (byte)((mSawDecimal ? SAW_DECIMAL : 0)
175 | (mExponent != 0 ? HAS_EXPONENT : 0));
Hans Boehm84614952014-11-25 18:46:17 -0800176 out.writeByte(TokenKind.CONSTANT.ordinal());
177 out.writeUTF(mWhole);
Hans Boehm8f051c32016-10-03 16:53:58 -0700178 out.writeByte(flags);
179 if (mSawDecimal) {
180 out.writeUTF(mFraction);
181 }
182 if (mExponent != 0) {
183 out.writeInt(mExponent);
184 }
Hans Boehm84614952014-11-25 18:46:17 -0800185 }
186
187 // Given a button press, append corresponding digit.
188 // We assume id is a digit or decimal point.
189 // Just return false if this was the second (or later) decimal point
190 // in this constant.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700191 // Assumes that this constant does not have an exponent.
Hans Boehm3666e632015-07-27 18:33:12 -0700192 public boolean add(int id) {
Hans Boehm84614952014-11-25 18:46:17 -0800193 if (id == R.id.dec_point) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700194 if (mSawDecimal || mExponent != 0) return false;
Hans Boehm84614952014-11-25 18:46:17 -0800195 mSawDecimal = true;
196 return true;
197 }
198 int val = KeyMaps.digVal(id);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700199 if (mExponent != 0) {
200 if (Math.abs(mExponent) <= 10000) {
201 if (mExponent > 0) {
202 mExponent = 10 * mExponent + val;
203 } else {
204 mExponent = 10 * mExponent - val;
205 }
206 return true;
207 } else { // Too large; refuse
208 return false;
209 }
210 }
Hans Boehm84614952014-11-25 18:46:17 -0800211 if (mSawDecimal) {
212 mFraction += val;
213 } else {
214 mWhole += val;
215 }
216 return true;
217 }
218
Hans Boehm3666e632015-07-27 18:33:12 -0700219 public void addExponent(int exp) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700220 // Note that adding a 0 exponent is a no-op. That's OK.
221 mExponent = exp;
222 }
223
Hans Boehm3666e632015-07-27 18:33:12 -0700224 /**
225 * Undo the last add or remove last exponent digit.
226 * Assumes the constant is nonempty.
227 */
228 public void delete() {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700229 if (mExponent != 0) {
230 mExponent /= 10;
231 // Once zero, it can only be added back with addExponent.
232 } else if (!mFraction.isEmpty()) {
Hans Boehm84614952014-11-25 18:46:17 -0800233 mFraction = mFraction.substring(0, mFraction.length() - 1);
234 } else if (mSawDecimal) {
235 mSawDecimal = false;
236 } else {
237 mWhole = mWhole.substring(0, mWhole.length() - 1);
238 }
239 }
240
Hans Boehm3666e632015-07-27 18:33:12 -0700241 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800242 return (mSawDecimal == false && mWhole.isEmpty());
243 }
244
Hans Boehm3666e632015-07-27 18:33:12 -0700245 /**
246 * Produce human-readable string representation of constant, as typed.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700247 * We do add digit grouping separators to the whole number, even if not typed.
Hans Boehm3666e632015-07-27 18:33:12 -0700248 * Result is internationalized.
249 */
Hans Boehm84614952014-11-25 18:46:17 -0800250 @Override
251 public String toString() {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700252 String result;
253 if (mExponent != 0) {
254 result = mWhole;
255 } else {
256 result = StringUtils.addCommas(mWhole, 0, mWhole.length());
257 }
Hans Boehm84614952014-11-25 18:46:17 -0800258 if (mSawDecimal) {
Hans Boehm013969e2015-04-13 20:29:47 -0700259 result += '.';
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700260 result += mFraction;
261 }
Hans Boehm0b9806f2015-06-29 16:07:15 -0700262 if (mExponent != 0) {
263 result += "E" + mExponent;
264 }
Hans Boehm013969e2015-04-13 20:29:47 -0700265 return KeyMaps.translateResult(result);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700266 }
267
Hans Boehm3666e632015-07-27 18:33:12 -0700268 /**
Hans Boehmfa5203c2015-08-17 16:14:52 -0700269 * Return BoundedRational representation of constant, if well-formed.
270 * Result is never null.
Hans Boehm3666e632015-07-27 18:33:12 -0700271 */
Hans Boehmfa5203c2015-08-17 16:14:52 -0700272 public BoundedRational toRational() throws SyntaxException {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700273 String whole = mWhole;
Hans Boehmfa5203c2015-08-17 16:14:52 -0700274 if (whole.isEmpty()) {
275 if (mFraction.isEmpty()) {
276 // Decimal point without digits.
277 throw new SyntaxException();
278 } else {
279 whole = "0";
280 }
281 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700282 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700283 BigInteger den = BigInteger.TEN.pow(mFraction.length());
Hans Boehm0b9806f2015-06-29 16:07:15 -0700284 if (mExponent > 0) {
285 num = num.multiply(BigInteger.TEN.pow(mExponent));
286 }
287 if (mExponent < 0) {
288 den = den.multiply(BigInteger.TEN.pow(-mExponent));
289 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700290 return new BoundedRational(num, den);
291 }
292
Hans Boehm84614952014-11-25 18:46:17 -0800293 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700294 public CharSequence toCharSequence(Context context) {
Hans Boehm84614952014-11-25 18:46:17 -0800295 return toString();
296 }
297
298 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700299 public TokenKind kind() {
300 return TokenKind.CONSTANT;
301 }
Hans Boehm84614952014-11-25 18:46:17 -0800302
303 // Override clone to make it public
304 @Override
305 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700306 Constant result = new Constant();
307 result.mWhole = mWhole;
308 result.mFraction = mFraction;
309 result.mSawDecimal = mSawDecimal;
310 result.mExponent = mExponent;
311 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800312 }
313 }
314
Hans Boehm3666e632015-07-27 18:33:12 -0700315 /**
316 * The "token" class for previously evaluated subexpressions.
317 * We treat previously evaluated subexpressions as tokens. These are inserted when we either
318 * continue an expression after evaluating some of it, or copy an expression and paste it back
319 * in.
Hans Boehm8f051c32016-10-03 16:53:58 -0700320 * This only contains enough information to allow us to display the expression in a
321 * formula, or reevaluate the expression with the aid of an ExprResolver; we no longer
322 * cache the result. The expression corresponding to the index can be obtained through
323 * the ExprResolver, which looks it up in a subexpression database.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800324 * The representation includes a UnifiedReal value. In order to
Hans Boehm3666e632015-07-27 18:33:12 -0700325 * support saving and restoring, we also include the underlying expression itself, and the
326 * context (currently just degree mode) used to evaluate it. The short string representation
327 * is also stored in order to avoid potentially expensive recomputation in the UI thread.
328 */
Hans Boehm84614952014-11-25 18:46:17 -0800329 private static class PreEval extends Token {
Hans Boehm8f051c32016-10-03 16:53:58 -0700330 public final long mIndex;
Hans Boehm50ed3202015-06-09 14:35:49 -0700331 private final String mShortRep; // Not internationalized.
Hans Boehm8f051c32016-10-03 16:53:58 -0700332 PreEval(long index, String shortRep) {
333 mIndex = index;
Hans Boehm84614952014-11-25 18:46:17 -0800334 mShortRep = shortRep;
335 }
Hans Boehm84614952014-11-25 18:46:17 -0800336 @Override
Hans Boehm8f051c32016-10-03 16:53:58 -0700337 // This writes out only a shallow representation of the result, without
338 // information about subexpressions. To write out a deep representation, we
339 // find referenced subexpressions, and iteratively write those as well.
Hans Boehm3666e632015-07-27 18:33:12 -0700340 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800341 out.writeByte(TokenKind.PRE_EVAL.ordinal());
Hans Boehm8f051c32016-10-03 16:53:58 -0700342 if (mIndex > Integer.MAX_VALUE || mIndex < Integer.MIN_VALUE) {
343 // This would be millions of expressions per day for the life of the device.
344 throw new AssertionError("Expression index too big");
Hans Boehm84614952014-11-25 18:46:17 -0800345 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700346 out.writeInt((int)mIndex);
347 out.writeUTF(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800348 }
349 PreEval(DataInput in) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700350 mIndex = in.readInt();
351 mShortRep = in.readUTF();
Hans Boehm84614952014-11-25 18:46:17 -0800352 }
353 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700354 public CharSequence toCharSequence(Context context) {
Hans Boehm50ed3202015-06-09 14:35:49 -0700355 return KeyMaps.translateResult(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800356 }
357 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700358 public TokenKind kind() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700359 return TokenKind.PRE_EVAL;
360 }
Hans Boehm3666e632015-07-27 18:33:12 -0700361 public boolean hasEllipsis() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700362 return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
363 }
Hans Boehm84614952014-11-25 18:46:17 -0800364 }
365
Hans Boehm3666e632015-07-27 18:33:12 -0700366 /**
367 * Read token from in.
368 */
369 public static Token newToken(DataInput in) throws IOException {
Hans Boehm8f051c32016-10-03 16:53:58 -0700370 byte kindByte = in.readByte();
371 if (kindByte < 0x20) {
372 TokenKind kind = tokenKindValues[kindByte];
373 switch(kind) {
374 case CONSTANT:
375 return new Constant(in);
376 case PRE_EVAL:
377 return new PreEval(in);
378 default: throw new IOException("Bad save file format");
379 }
380 } else {
381 return new Operator(kindByte);
Hans Boehm84614952014-11-25 18:46:17 -0800382 }
383 }
384
385 CalculatorExpr() {
386 mExpr = new ArrayList<Token>();
387 }
388
389 private CalculatorExpr(ArrayList<Token> expr) {
390 mExpr = expr;
391 }
392
Hans Boehm3666e632015-07-27 18:33:12 -0700393 /**
394 * Construct CalculatorExpr, by reading it from in.
395 */
Hans Boehm84614952014-11-25 18:46:17 -0800396 CalculatorExpr(DataInput in) throws IOException {
397 mExpr = new ArrayList<Token>();
398 int size = in.readInt();
399 for (int i = 0; i < size; ++i) {
400 mExpr.add(newToken(in));
401 }
402 }
403
Hans Boehm3666e632015-07-27 18:33:12 -0700404 /**
405 * Write this expression to out.
406 */
407 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800408 int size = mExpr.size();
409 out.writeInt(size);
410 for (int i = 0; i < size; ++i) {
411 mExpr.get(i).write(out);
412 }
413 }
414
Hans Boehm3666e632015-07-27 18:33:12 -0700415 /**
416 * Does this expression end with a numeric constant?
417 * As opposed to an operator or preevaluated expression.
418 */
Hans Boehm0b9806f2015-06-29 16:07:15 -0700419 boolean hasTrailingConstant() {
420 int s = mExpr.size();
421 if (s == 0) {
422 return false;
423 }
424 Token t = mExpr.get(s-1);
425 return t instanceof Constant;
426 }
427
Hans Boehm3666e632015-07-27 18:33:12 -0700428 /**
429 * Does this expression end with a binary operator?
430 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700431 boolean hasTrailingBinary() {
Hans Boehm84614952014-11-25 18:46:17 -0800432 int s = mExpr.size();
433 if (s == 0) return false;
434 Token t = mExpr.get(s-1);
435 if (!(t instanceof Operator)) return false;
436 Operator o = (Operator)t;
Hans Boehm3666e632015-07-27 18:33:12 -0700437 return (KeyMaps.isBinary(o.id));
Hans Boehm84614952014-11-25 18:46:17 -0800438 }
439
Hans Boehm017de982015-06-10 17:46:03 -0700440 /**
441 * Append press of button with given id to expression.
442 * If the insertion would clearly result in a syntax error, either just return false
443 * and do nothing, or make an adjustment to avoid the problem. We do the latter only
444 * for unambiguous consecutive binary operators, in which case we delete the first
445 * operator.
446 */
Hans Boehm84614952014-11-25 18:46:17 -0800447 boolean add(int id) {
448 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700449 final int d = KeyMaps.digVal(id);
450 final boolean binary = KeyMaps.isBinary(id);
Hans Boehm017de982015-06-10 17:46:03 -0700451 Token lastTok = s == 0 ? null : mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700452 int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
Hans Boehm017de982015-06-10 17:46:03 -0700453 // Quietly replace a trailing binary operator with another one, unless the second
454 // operator is minus, in which case we just allow it as a unary minus.
455 if (binary && !KeyMaps.isPrefix(id)) {
456 if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
457 || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
458 return false;
459 }
460 while (hasTrailingBinary()) {
461 delete();
462 }
463 // s invalid and not used below.
Hans Boehm84614952014-11-25 18:46:17 -0800464 }
Hans Boehm3666e632015-07-27 18:33:12 -0700465 final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
Hans Boehm84614952014-11-25 18:46:17 -0800466 if (isConstPiece) {
Hans Boehm017de982015-06-10 17:46:03 -0700467 // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
Hans Boehm84614952014-11-25 18:46:17 -0800468 if (s == 0) {
469 mExpr.add(new Constant());
470 s++;
471 } else {
472 Token last = mExpr.get(s-1);
473 if(!(last instanceof Constant)) {
Hans Boehm017de982015-06-10 17:46:03 -0700474 if (last instanceof PreEval) {
475 // Add explicit multiplication to avoid confusing display.
476 mExpr.add(new Operator(R.id.op_mul));
477 s++;
Hans Boehm84614952014-11-25 18:46:17 -0800478 }
479 mExpr.add(new Constant());
480 s++;
481 }
482 }
483 return ((Constant)(mExpr.get(s-1))).add(id);
484 } else {
485 mExpr.add(new Operator(id));
486 return true;
487 }
488 }
489
Hans Boehm017de982015-06-10 17:46:03 -0700490 /**
Hans Boehm0b9806f2015-06-29 16:07:15 -0700491 * Add exponent to the constant at the end of the expression.
492 * Assumes there is a constant at the end of the expression.
493 */
494 void addExponent(int exp) {
495 Token lastTok = mExpr.get(mExpr.size() - 1);
496 ((Constant) lastTok).addExponent(exp);
497 }
498
499 /**
Hans Boehm017de982015-06-10 17:46:03 -0700500 * Remove trailing op_add and op_sub operators.
501 */
502 void removeTrailingAdditiveOperators() {
503 while (true) {
504 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700505 if (s == 0) {
506 break;
507 }
Hans Boehm017de982015-06-10 17:46:03 -0700508 Token lastTok = mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700509 if (!(lastTok instanceof Operator)) {
510 break;
511 }
512 int lastOp = ((Operator) lastTok).id;
513 if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
514 break;
515 }
Hans Boehm017de982015-06-10 17:46:03 -0700516 delete();
517 }
518 }
519
Hans Boehm3666e632015-07-27 18:33:12 -0700520 /**
521 * Append the contents of the argument expression.
522 * It is assumed that the argument expression will not change, and thus its pieces can be
523 * reused directly.
524 */
525 public void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700526 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800527 int s2 = expr2.mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700528 // Check that we're not concatenating Constant or PreEval tokens, since the result would
529 // look like a single constant, with very mysterious results for the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700530 if (s != 0 && s2 != 0) {
531 Token last = mExpr.get(s-1);
532 Token first = expr2.mExpr.get(0);
533 if (!(first instanceof Operator) && !(last instanceof Operator)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700534 // Fudge it by adding an explicit multiplication. We would have interpreted it as
535 // such anyway, and this makes it recognizable to the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700536 mExpr.add(new Operator(R.id.op_mul));
537 }
538 }
Hans Boehm84614952014-11-25 18:46:17 -0800539 for (int i = 0; i < s2; ++i) {
540 mExpr.add(expr2.mExpr.get(i));
541 }
542 }
543
Hans Boehm3666e632015-07-27 18:33:12 -0700544 /**
545 * Undo the last key addition, if any.
546 * Or possibly remove a trailing exponent digit.
547 */
548 public void delete() {
549 final int s = mExpr.size();
550 if (s == 0) {
551 return;
552 }
Hans Boehm84614952014-11-25 18:46:17 -0800553 Token last = mExpr.get(s-1);
554 if (last instanceof Constant) {
555 Constant c = (Constant)last;
556 c.delete();
Hans Boehm3666e632015-07-27 18:33:12 -0700557 if (!c.isEmpty()) {
558 return;
559 }
Hans Boehm84614952014-11-25 18:46:17 -0800560 }
561 mExpr.remove(s-1);
562 }
563
Hans Boehm3666e632015-07-27 18:33:12 -0700564 /**
565 * Remove all tokens from the expression.
566 */
567 public void clear() {
Hans Boehm84614952014-11-25 18:46:17 -0800568 mExpr.clear();
569 }
570
Hans Boehm3666e632015-07-27 18:33:12 -0700571 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800572 return mExpr.isEmpty();
573 }
574
Hans Boehm3666e632015-07-27 18:33:12 -0700575 /**
576 * Returns a logical deep copy of the CalculatorExpr.
577 * Operator and PreEval tokens are immutable, and thus aren't really copied.
578 */
Hans Boehm84614952014-11-25 18:46:17 -0800579 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700580 CalculatorExpr result = new CalculatorExpr();
Hans Boehm84614952014-11-25 18:46:17 -0800581 for (Token t: mExpr) {
582 if (t instanceof Constant) {
Hans Boehm3666e632015-07-27 18:33:12 -0700583 result.mExpr.add((Token)(((Constant)t).clone()));
Hans Boehm84614952014-11-25 18:46:17 -0800584 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700585 result.mExpr.add(t);
Hans Boehm84614952014-11-25 18:46:17 -0800586 }
587 }
Hans Boehm3666e632015-07-27 18:33:12 -0700588 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800589 }
590
591 // Am I just a constant?
Hans Boehm3666e632015-07-27 18:33:12 -0700592 public boolean isConstant() {
593 if (mExpr.size() != 1) {
594 return false;
595 }
Hans Boehm84614952014-11-25 18:46:17 -0800596 return mExpr.get(0) instanceof Constant;
597 }
598
Hans Boehm3666e632015-07-27 18:33:12 -0700599 /**
600 * Return a new expression consisting of a single token representing the current pre-evaluated
601 * expression.
Hans Boehm8f051c32016-10-03 16:53:58 -0700602 * The caller supplies the expression index and short string representation.
603 * The expression must have been previously evaluated.
Hans Boehm3666e632015-07-27 18:33:12 -0700604 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700605 public CalculatorExpr abbreviate(long index, String sr) {
Hans Boehm84614952014-11-25 18:46:17 -0800606 CalculatorExpr result = new CalculatorExpr();
Justin Klaassen0ace4eb2016-02-05 11:38:12 -0800607 @SuppressWarnings("unchecked")
Hans Boehm8f051c32016-10-03 16:53:58 -0700608 Token t = new PreEval(index, sr);
Hans Boehm84614952014-11-25 18:46:17 -0800609 result.mExpr.add(t);
610 return result;
611 }
612
Hans Boehm3666e632015-07-27 18:33:12 -0700613 /**
Hans Boehm995e5eb2016-02-08 11:03:01 -0800614 * Internal evaluation functions return an EvalRet pair.
Hans Boehm3666e632015-07-27 18:33:12 -0700615 * We compute rational (BoundedRational) results when possible, both as a performance
616 * optimization, and to detect errors exactly when we can.
617 */
618 private static class EvalRet {
619 public int pos; // Next position (expression index) to be parsed.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800620 public final UnifiedReal val; // Constructive Real result of evaluating subexpression.
621 EvalRet(int p, UnifiedReal v) {
Hans Boehm3666e632015-07-27 18:33:12 -0700622 pos = p;
623 val = v;
Hans Boehm84614952014-11-25 18:46:17 -0800624 }
625 }
626
Hans Boehm3666e632015-07-27 18:33:12 -0700627 /**
628 * Internal evaluation functions take an EvalContext argument.
629 */
Hans Boehm84614952014-11-25 18:46:17 -0800630 private static class EvalContext {
Hans Boehm3666e632015-07-27 18:33:12 -0700631 public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
Hans Boehmc023b732015-04-29 11:30:47 -0700632 public final boolean mDegreeMode;
Hans Boehm8f051c32016-10-03 16:53:58 -0700633 public final ExprResolver mExprResolver; // Reconstructed, not saved.
Hans Boehm84614952014-11-25 18:46:17 -0800634 // If we add any other kinds of evaluation modes, they go here.
Hans Boehm8f051c32016-10-03 16:53:58 -0700635 EvalContext(boolean degreeMode, int len, ExprResolver er) {
Hans Boehm84614952014-11-25 18:46:17 -0800636 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700637 mPrefixLength = len;
Hans Boehm8f051c32016-10-03 16:53:58 -0700638 mExprResolver = er;
Hans Boehm84614952014-11-25 18:46:17 -0800639 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700640 EvalContext(DataInput in, int len, ExprResolver er) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800641 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700642 mPrefixLength = len;
Hans Boehm8f051c32016-10-03 16:53:58 -0700643 mExprResolver = er;
Hans Boehm84614952014-11-25 18:46:17 -0800644 }
645 void write(DataOutput out) throws IOException {
646 out.writeBoolean(mDegreeMode);
647 }
648 }
649
Hans Boehm995e5eb2016-02-08 11:03:01 -0800650 private UnifiedReal toRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800651 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800652 return x.multiply(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800653 } else {
654 return x;
655 }
656 }
657
Hans Boehm995e5eb2016-02-08 11:03:01 -0800658 private UnifiedReal fromRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800659 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800660 return x.divide(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800661 } else {
662 return x;
663 }
664 }
665
Hans Boehm3666e632015-07-27 18:33:12 -0700666 // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
667 // error. We expect that to be caught in eval below.
Hans Boehm84614952014-11-25 18:46:17 -0800668
Hans Boehmc023b732015-04-29 11:30:47 -0700669 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800670 Token t = mExpr.get(i);
Hans Boehm3666e632015-07-27 18:33:12 -0700671 if (!(t instanceof Operator)) {
672 return false;
673 }
674 return ((Operator)(t)).id == op;
Hans Boehm84614952014-11-25 18:46:17 -0800675 }
676
Hans Boehmc023b732015-04-29 11:30:47 -0700677 private boolean isOperator(int i, int op, EvalContext ec) {
Hans Boehm3666e632015-07-27 18:33:12 -0700678 if (i >= ec.mPrefixLength) {
679 return false;
680 }
Hans Boehmc023b732015-04-29 11:30:47 -0700681 return isOperatorUnchecked(i, op);
682 }
683
Hans Boehm3666e632015-07-27 18:33:12 -0700684 public static class SyntaxException extends Exception {
Hans Boehmc023b732015-04-29 11:30:47 -0700685 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800686 super();
687 }
Hans Boehmc023b732015-04-29 11:30:47 -0700688 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800689 super(s);
690 }
691 }
692
Hans Boehm3666e632015-07-27 18:33:12 -0700693 // The following functions all evaluate some kind of expression starting at position i in
694 // mExpr in a specified evaluation context. They return both the expression value (as
695 // constructive real and, if applicable, as BoundedRational) and the position of the next token
Hans Boehm84614952014-11-25 18:46:17 -0800696 // that was not used as part of the evaluation.
Hans Boehm3666e632015-07-27 18:33:12 -0700697 // This is essentially a simple recursive descent parser combined with expression evaluation.
698
Hans Boehmc023b732015-04-29 11:30:47 -0700699 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700700 final Token t = mExpr.get(i);
Hans Boehm84614952014-11-25 18:46:17 -0800701 if (t instanceof Constant) {
702 Constant c = (Constant)t;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800703 return new EvalRet(i+1,new UnifiedReal(c.toRational()));
Hans Boehm84614952014-11-25 18:46:17 -0800704 }
705 if (t instanceof PreEval) {
Hans Boehm8f051c32016-10-03 16:53:58 -0700706 final long index = ((PreEval)t).mIndex;
707 UnifiedReal res = ec.mExprResolver.getResult(index);
708 if (res == null) {
709 CalculatorExpr nestedExpr = ec.mExprResolver.getExpr(index);
710 EvalContext newEc = new EvalContext(ec.mExprResolver.getDegreeMode(index),
711 nestedExpr.trailingBinaryOpsStart(), ec.mExprResolver);
712 EvalRet new_res = nestedExpr.evalExpr(0, newEc);
713 res = ec.mExprResolver.putResultIfAbsent(index, new_res.val);
714 }
715 return new EvalRet(i+1, res);
Hans Boehm84614952014-11-25 18:46:17 -0800716 }
717 EvalRet argVal;
Hans Boehm3666e632015-07-27 18:33:12 -0700718 switch(((Operator)(t)).id) {
Hans Boehm84614952014-11-25 18:46:17 -0800719 case R.id.const_pi:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800720 return new EvalRet(i+1, UnifiedReal.PI);
Hans Boehm84614952014-11-25 18:46:17 -0800721 case R.id.const_e:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800722 return new EvalRet(i+1, UnifiedReal.E);
Hans Boehm84614952014-11-25 18:46:17 -0800723 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700724 // Seems to have highest precedence.
725 // Does not add implicit paren.
726 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700727 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700728 argVal = evalUnary(i+2, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800729 return new EvalRet(argVal.pos, argVal.val.negate().sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700730 } else {
731 argVal = evalUnary(i+1, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800732 return new EvalRet(argVal.pos, argVal.val.sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700733 }
Hans Boehm84614952014-11-25 18:46:17 -0800734 case R.id.lparen:
735 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700736 if (isOperator(argVal.pos, R.id.rparen, ec)) {
737 argVal.pos++;
738 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800739 return new EvalRet(argVal.pos, argVal.val);
Hans Boehm84614952014-11-25 18:46:17 -0800740 case R.id.fun_sin:
741 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700742 if (isOperator(argVal.pos, R.id.rparen, ec)) {
743 argVal.pos++;
744 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800745 return new EvalRet(argVal.pos, toRadians(argVal.val, ec).sin());
Hans Boehm84614952014-11-25 18:46:17 -0800746 case R.id.fun_cos:
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, toRadians(argVal.val,ec).cos());
Hans Boehm84614952014-11-25 18:46:17 -0800752 case R.id.fun_tan:
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 UnifiedReal arg = toRadians(argVal.val, ec);
758 return new EvalRet(argVal.pos, arg.sin().divide(arg.cos()));
Hans Boehm84614952014-11-25 18:46:17 -0800759 case R.id.fun_ln:
760 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700761 if (isOperator(argVal.pos, R.id.rparen, ec)) {
762 argVal.pos++;
763 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800764 return new EvalRet(argVal.pos, argVal.val.ln());
Hans Boehm4db31b42015-05-31 12:19:05 -0700765 case R.id.fun_exp:
766 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700767 if (isOperator(argVal.pos, R.id.rparen, ec)) {
768 argVal.pos++;
769 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800770 return new EvalRet(argVal.pos, argVal.val.exp());
Hans Boehm84614952014-11-25 18:46:17 -0800771 case R.id.fun_log:
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().divide(UnifiedReal.TEN.ln()));
Hans Boehm84614952014-11-25 18:46:17 -0800777 case R.id.fun_arcsin:
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, fromRadians(argVal.val.asin(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800783 case R.id.fun_arccos:
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 Boehm79f273c2016-06-09 11:00:27 -0700788 return new EvalRet(argVal.pos, fromRadians(argVal.val.acos(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800789 case R.id.fun_arctan:
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.atan(),ec));
Hans Boehm84614952014-11-25 18:46:17 -0800795 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700796 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800797 }
Hans Boehm84614952014-11-25 18:46:17 -0800798 }
799
Hans Boehm995e5eb2016-02-08 11:03:01 -0800800 private static final UnifiedReal ONE_HUNDREDTH = new UnifiedReal(100).inverse();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700801
Hans Boehm4db31b42015-05-31 12:19:05 -0700802 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700803 final EvalRet tmp = evalUnary(i, ec);
804 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800805 UnifiedReal val = tmp.val;
806
Hans Boehm4db31b42015-05-31 12:19:05 -0700807 boolean isFact;
808 boolean isSquared = false;
809 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
810 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
811 isOperator(cpos, R.id.op_pct, ec)) {
812 if (isFact) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800813 val = val.fact();
Hans Boehm4db31b42015-05-31 12:19:05 -0700814 } else if (isSquared) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800815 val = val.multiply(val);
Hans Boehm4db31b42015-05-31 12:19:05 -0700816 } else /* percent */ {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800817 val = val.multiply(ONE_HUNDREDTH);
Hans Boehm84614952014-11-25 18:46:17 -0800818 }
Hans Boehm84614952014-11-25 18:46:17 -0800819 ++cpos;
820 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800821 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800822 }
823
Hans Boehmc023b732015-04-29 11:30:47 -0700824 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700825 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700826 int cpos = result1.pos; // current position
Hans Boehm995e5eb2016-02-08 11:03:01 -0800827 UnifiedReal val = result1.val; // value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700828 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700829 final EvalRet exp = evalSignedFactor(cpos + 1, ec);
830 cpos = exp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800831 val = val.pow(exp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800832 }
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 evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
837 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700838 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800839 EvalRet tmp = evalFactor(cpos, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700840 cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800841 final UnifiedReal result = negative ? tmp.val.negate() : tmp.val;
842 return new EvalRet(cpos, result);
Hans Boehm84614952014-11-25 18:46:17 -0800843 }
844
845 private boolean canStartFactor(int i) {
846 if (i >= mExpr.size()) return false;
847 Token t = mExpr.get(i);
848 if (!(t instanceof Operator)) return true;
Hans Boehm3666e632015-07-27 18:33:12 -0700849 int id = ((Operator)(t)).id;
Hans Boehm84614952014-11-25 18:46:17 -0800850 if (KeyMaps.isBinary(id)) return false;
851 switch (id) {
852 case R.id.op_fact:
853 case R.id.rparen:
854 return false;
855 default:
856 return true;
857 }
858 }
859
Hans Boehmc023b732015-04-29 11:30:47 -0700860 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800861 EvalRet tmp = evalSignedFactor(i, ec);
862 boolean is_mul = false;
863 boolean is_div = false;
Hans Boehm3666e632015-07-27 18:33:12 -0700864 int cpos = tmp.pos; // Current position in expression.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800865 UnifiedReal val = tmp.val; // Current value.
Hans Boehmc023b732015-04-29 11:30:47 -0700866 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
867 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -0800868 || canStartFactor(cpos)) {
869 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700870 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800871 if (is_div) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800872 val = val.divide(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800873 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800874 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800875 }
Hans Boehm3666e632015-07-27 18:33:12 -0700876 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800877 is_mul = is_div = false;
878 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800879 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800880 }
881
Hans Boehm8afd0f82015-07-30 17:00:33 -0700882 /**
883 * Is the subexpression starting at pos a simple percent constant?
884 * This is used to recognize exppressions like 200+10%, which we handle specially.
885 * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
886 * by either nothing or an additive operator.
887 * Note that we are intentionally far more restrictive in recognizing such expressions than
888 * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
889 * When in doubt, we fall back to the the naive interpretation of % as 1/100.
890 * Note that 100+(10)% yields 100.1 while 100+10% yields 110. This may be controversial,
891 * but is consistent with Google web search.
892 */
893 private boolean isPercent(int pos) {
894 if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
895 return false;
896 }
897 Token number = mExpr.get(pos);
898 if (number instanceof Operator) {
899 return false;
900 }
901 if (mExpr.size() == pos + 2) {
902 return true;
903 }
904 if (!(mExpr.get(pos + 2) instanceof Operator)) {
905 return false;
906 }
907 Operator op = (Operator) mExpr.get(pos + 2);
Hans Boehm64d1cdb2016-03-08 19:14:07 -0800908 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 -0700909 }
910
911 /**
912 * Compute the multiplicative factor corresponding to an N% addition or subtraction.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800913 * @param pos position of Constant or PreEval expression token corresponding to N.
914 * @param isSubtraction this is a subtraction, as opposed to addition.
915 * @param ec usable evaluation contex; only length matters.
916 * @return UnifiedReal value and position, which is pos + 2, i.e. after percent sign
Hans Boehm8afd0f82015-07-30 17:00:33 -0700917 */
918 private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
919 throws SyntaxException {
920 EvalRet tmp = evalUnary(pos, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800921 UnifiedReal val = isSubtraction ? tmp.val.negate() : tmp.val;
922 val = UnifiedReal.ONE.add(val.multiply(ONE_HUNDREDTH));
923 return new EvalRet(pos + 2 /* after percent sign */, val);
Hans Boehm8afd0f82015-07-30 17:00:33 -0700924 }
925
Hans Boehmc023b732015-04-29 11:30:47 -0700926 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800927 EvalRet tmp = evalTerm(i, ec);
928 boolean is_plus;
Hans Boehm3666e632015-07-27 18:33:12 -0700929 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800930 UnifiedReal val = tmp.val;
Hans Boehmc023b732015-04-29 11:30:47 -0700931 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
932 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700933 if (isPercent(cpos + 1)) {
934 tmp = getPercentFactor(cpos + 1, !is_plus, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800935 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800936 } else {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700937 tmp = evalTerm(cpos + 1, ec);
938 if (is_plus) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800939 val = val.add(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700940 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800941 val = val.subtract(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800942 }
943 }
Hans Boehm3666e632015-07-27 18:33:12 -0700944 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800945 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800946 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800947 }
948
Hans Boehme8553762015-06-26 17:56:52 -0700949 /**
950 * Return the starting position of the sequence of trailing binary operators.
951 */
952 private int trailingBinaryOpsStart() {
Hans Boehmc023b732015-04-29 11:30:47 -0700953 int result = mExpr.size();
954 while (result > 0) {
955 Token last = mExpr.get(result - 1);
956 if (!(last instanceof Operator)) break;
957 Operator o = (Operator)last;
Hans Boehm3666e632015-07-27 18:33:12 -0700958 if (!KeyMaps.isBinary(o.id)) break;
Hans Boehmc023b732015-04-29 11:30:47 -0700959 --result;
960 }
961 return result;
962 }
963
Hans Boehm3666e632015-07-27 18:33:12 -0700964 /**
965 * Is the current expression worth evaluating?
966 */
Hans Boehmc023b732015-04-29 11:30:47 -0700967 public boolean hasInterestingOps() {
Hans Boehme8553762015-06-26 17:56:52 -0700968 int last = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -0700969 int first = 0;
970 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
971 // Leading minus is not by itself interesting.
972 first++;
973 }
974 for (int i = first; i < last; ++i) {
975 Token t1 = mExpr.get(i);
Hans Boehm187d3e92015-06-09 18:04:26 -0700976 if (t1 instanceof Operator
977 || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
978 return true;
979 }
Hans Boehmc023b732015-04-29 11:30:47 -0700980 }
981 return false;
982 }
983
Hans Boehme8553762015-06-26 17:56:52 -0700984 /**
Hans Boehm52d477a2016-04-01 17:42:50 -0700985 * Does the expression contain trig operations?
986 */
987 public boolean hasTrigFuncs() {
988 for (Token t: mExpr) {
989 if (t instanceof Operator) {
990 Operator o = (Operator)t;
991 if (KeyMaps.isTrigFunc(o.id)) {
992 return true;
993 }
994 }
995 }
996 return false;
997 }
998
999 /**
Hans Boehme8553762015-06-26 17:56:52 -07001000 * Evaluate the expression excluding trailing binary operators.
Hans Boehm3666e632015-07-27 18:33:12 -07001001 * Errors result in exceptions, most of which are unchecked. Should not be called
1002 * concurrently with modification of the expression. May take a very long time; avoid calling
1003 * from UI thread.
Hans Boehme8553762015-06-26 17:56:52 -07001004 *
1005 * @param degreeMode use degrees rather than radians
1006 */
Hans Boehm8f051c32016-10-03 16:53:58 -07001007 UnifiedReal eval(boolean degreeMode, ExprResolver er) throws SyntaxException
Hans Boehm995e5eb2016-02-08 11:03:01 -08001008 // And unchecked exceptions thrown by UnifiedReal, CR,
Hans Boehmc023b732015-04-29 11:30:47 -07001009 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -08001010 {
1011 try {
Hans Boehm3666e632015-07-27 18:33:12 -07001012 // We currently never include trailing binary operators, but include other trailing
1013 // operators. Thus we usually, but not always, display results for prefixes of valid
1014 // expressions, and don't generate an error where we previously displayed an instant
1015 // result. This reflects the Android L design.
Hans Boehme8553762015-06-26 17:56:52 -07001016 int prefixLen = trailingBinaryOpsStart();
Hans Boehm8f051c32016-10-03 16:53:58 -07001017 EvalContext ec = new EvalContext(degreeMode, prefixLen, er);
Hans Boehm84614952014-11-25 18:46:17 -08001018 EvalRet res = evalExpr(0, ec);
Hans Boehm3666e632015-07-27 18:33:12 -07001019 if (res.pos != prefixLen) {
Hans Boehmc023b732015-04-29 11:30:47 -07001020 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -07001021 }
Hans Boehm995e5eb2016-02-08 11:03:01 -08001022 return res.val;
Hans Boehm84614952014-11-25 18:46:17 -08001023 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -07001024 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -08001025 }
1026 }
1027
1028 // Produce a string representation of the expression itself
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001029 SpannableStringBuilder toSpannableStringBuilder(Context context) {
1030 SpannableStringBuilder ssb = new SpannableStringBuilder();
Hans Boehm84614952014-11-25 18:46:17 -08001031 for (Token t: mExpr) {
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001032 ssb.append(t.toCharSequence(context));
Hans Boehm84614952014-11-25 18:46:17 -08001033 }
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001034 return ssb;
Hans Boehm84614952014-11-25 18:46:17 -08001035 }
1036}