blob: 75ab1c98919362ae71edb7ca58490668a94e3843 [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:
Hans Boehm8bf0dca2017-01-25 17:10:39 -0800376 PreEval pe = new PreEval(in);
377 if (pe.mIndex == -1) {
378 // Database corrupted by earlier bug.
379 // Return a conspicuously wrong placeholder that won't lead to a crash.
380 Constant result = new Constant();
381 result.add(R.id.dec_point);
382 return result;
383 } else {
384 return pe;
385 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700386 default: throw new IOException("Bad save file format");
387 }
388 } else {
389 return new Operator(kindByte);
Hans Boehm84614952014-11-25 18:46:17 -0800390 }
391 }
392
393 CalculatorExpr() {
394 mExpr = new ArrayList<Token>();
395 }
396
397 private CalculatorExpr(ArrayList<Token> expr) {
398 mExpr = expr;
399 }
400
Hans Boehm3666e632015-07-27 18:33:12 -0700401 /**
402 * Construct CalculatorExpr, by reading it from in.
403 */
Hans Boehm84614952014-11-25 18:46:17 -0800404 CalculatorExpr(DataInput in) throws IOException {
405 mExpr = new ArrayList<Token>();
406 int size = in.readInt();
407 for (int i = 0; i < size; ++i) {
408 mExpr.add(newToken(in));
409 }
410 }
411
Hans Boehm3666e632015-07-27 18:33:12 -0700412 /**
413 * Write this expression to out.
414 */
415 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800416 int size = mExpr.size();
417 out.writeInt(size);
418 for (int i = 0; i < size; ++i) {
419 mExpr.get(i).write(out);
420 }
421 }
422
Hans Boehm3666e632015-07-27 18:33:12 -0700423 /**
Hans Boehmf4424772016-12-05 10:35:16 -0800424 * Use write() above to generate a byte array containing a serialized representation of
425 * this expression.
426 */
427 public byte[] toBytes() {
428 ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
429 try (DataOutputStream out = new DataOutputStream(byteArrayStream)) {
430 write(out);
431 } catch (IOException e) {
432 // Impossible; No IO involved.
433 throw new AssertionError("Impossible IO exception", e);
434 }
435 return byteArrayStream.toByteArray();
436 }
437
438 /**
Hans Boehm3666e632015-07-27 18:33:12 -0700439 * Does this expression end with a numeric constant?
440 * As opposed to an operator or preevaluated expression.
441 */
Hans Boehm0b9806f2015-06-29 16:07:15 -0700442 boolean hasTrailingConstant() {
443 int s = mExpr.size();
444 if (s == 0) {
445 return false;
446 }
447 Token t = mExpr.get(s-1);
448 return t instanceof Constant;
449 }
450
Hans Boehm3666e632015-07-27 18:33:12 -0700451 /**
452 * Does this expression end with a binary operator?
453 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700454 boolean hasTrailingBinary() {
Hans Boehm84614952014-11-25 18:46:17 -0800455 int s = mExpr.size();
456 if (s == 0) return false;
457 Token t = mExpr.get(s-1);
458 if (!(t instanceof Operator)) return false;
459 Operator o = (Operator)t;
Hans Boehm3666e632015-07-27 18:33:12 -0700460 return (KeyMaps.isBinary(o.id));
Hans Boehm84614952014-11-25 18:46:17 -0800461 }
462
Hans Boehm017de982015-06-10 17:46:03 -0700463 /**
464 * Append press of button with given id to expression.
465 * If the insertion would clearly result in a syntax error, either just return false
466 * and do nothing, or make an adjustment to avoid the problem. We do the latter only
467 * for unambiguous consecutive binary operators, in which case we delete the first
468 * operator.
469 */
Hans Boehm84614952014-11-25 18:46:17 -0800470 boolean add(int id) {
471 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700472 final int d = KeyMaps.digVal(id);
473 final boolean binary = KeyMaps.isBinary(id);
Hans Boehm017de982015-06-10 17:46:03 -0700474 Token lastTok = s == 0 ? null : mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700475 int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
Hans Boehm017de982015-06-10 17:46:03 -0700476 // Quietly replace a trailing binary operator with another one, unless the second
477 // operator is minus, in which case we just allow it as a unary minus.
478 if (binary && !KeyMaps.isPrefix(id)) {
479 if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
480 || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
481 return false;
482 }
483 while (hasTrailingBinary()) {
484 delete();
485 }
486 // s invalid and not used below.
Hans Boehm84614952014-11-25 18:46:17 -0800487 }
Hans Boehm3666e632015-07-27 18:33:12 -0700488 final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
Hans Boehm84614952014-11-25 18:46:17 -0800489 if (isConstPiece) {
Hans Boehm017de982015-06-10 17:46:03 -0700490 // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
Hans Boehm84614952014-11-25 18:46:17 -0800491 if (s == 0) {
492 mExpr.add(new Constant());
493 s++;
494 } else {
495 Token last = mExpr.get(s-1);
496 if(!(last instanceof Constant)) {
Hans Boehm017de982015-06-10 17:46:03 -0700497 if (last instanceof PreEval) {
498 // Add explicit multiplication to avoid confusing display.
499 mExpr.add(new Operator(R.id.op_mul));
500 s++;
Hans Boehm84614952014-11-25 18:46:17 -0800501 }
502 mExpr.add(new Constant());
503 s++;
504 }
505 }
506 return ((Constant)(mExpr.get(s-1))).add(id);
507 } else {
508 mExpr.add(new Operator(id));
509 return true;
510 }
511 }
512
Hans Boehm017de982015-06-10 17:46:03 -0700513 /**
Hans Boehm0b9806f2015-06-29 16:07:15 -0700514 * Add exponent to the constant at the end of the expression.
515 * Assumes there is a constant at the end of the expression.
516 */
517 void addExponent(int exp) {
518 Token lastTok = mExpr.get(mExpr.size() - 1);
519 ((Constant) lastTok).addExponent(exp);
520 }
521
522 /**
Hans Boehm017de982015-06-10 17:46:03 -0700523 * Remove trailing op_add and op_sub operators.
524 */
525 void removeTrailingAdditiveOperators() {
526 while (true) {
527 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700528 if (s == 0) {
529 break;
530 }
Hans Boehm017de982015-06-10 17:46:03 -0700531 Token lastTok = mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700532 if (!(lastTok instanceof Operator)) {
533 break;
534 }
535 int lastOp = ((Operator) lastTok).id;
536 if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
537 break;
538 }
Hans Boehm017de982015-06-10 17:46:03 -0700539 delete();
540 }
541 }
542
Hans Boehm3666e632015-07-27 18:33:12 -0700543 /**
544 * Append the contents of the argument expression.
545 * It is assumed that the argument expression will not change, and thus its pieces can be
546 * reused directly.
547 */
548 public void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700549 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800550 int s2 = expr2.mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700551 // Check that we're not concatenating Constant or PreEval tokens, since the result would
552 // look like a single constant, with very mysterious results for the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700553 if (s != 0 && s2 != 0) {
554 Token last = mExpr.get(s-1);
555 Token first = expr2.mExpr.get(0);
556 if (!(first instanceof Operator) && !(last instanceof Operator)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700557 // Fudge it by adding an explicit multiplication. We would have interpreted it as
558 // such anyway, and this makes it recognizable to the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700559 mExpr.add(new Operator(R.id.op_mul));
560 }
561 }
Hans Boehm84614952014-11-25 18:46:17 -0800562 for (int i = 0; i < s2; ++i) {
563 mExpr.add(expr2.mExpr.get(i));
564 }
565 }
566
Hans Boehm3666e632015-07-27 18:33:12 -0700567 /**
568 * Undo the last key addition, if any.
569 * Or possibly remove a trailing exponent digit.
570 */
571 public void delete() {
572 final int s = mExpr.size();
573 if (s == 0) {
574 return;
575 }
Hans Boehm84614952014-11-25 18:46:17 -0800576 Token last = mExpr.get(s-1);
577 if (last instanceof Constant) {
578 Constant c = (Constant)last;
579 c.delete();
Hans Boehm3666e632015-07-27 18:33:12 -0700580 if (!c.isEmpty()) {
581 return;
582 }
Hans Boehm84614952014-11-25 18:46:17 -0800583 }
584 mExpr.remove(s-1);
585 }
586
Hans Boehm3666e632015-07-27 18:33:12 -0700587 /**
588 * Remove all tokens from the expression.
589 */
590 public void clear() {
Hans Boehm84614952014-11-25 18:46:17 -0800591 mExpr.clear();
592 }
593
Hans Boehm3666e632015-07-27 18:33:12 -0700594 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800595 return mExpr.isEmpty();
596 }
597
Hans Boehm3666e632015-07-27 18:33:12 -0700598 /**
599 * Returns a logical deep copy of the CalculatorExpr.
600 * Operator and PreEval tokens are immutable, and thus aren't really copied.
601 */
Hans Boehm84614952014-11-25 18:46:17 -0800602 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700603 CalculatorExpr result = new CalculatorExpr();
Hans Boehm9c160b42016-12-02 11:55:12 -0800604 for (Token t : mExpr) {
Hans Boehm84614952014-11-25 18:46:17 -0800605 if (t instanceof Constant) {
Hans Boehm3666e632015-07-27 18:33:12 -0700606 result.mExpr.add((Token)(((Constant)t).clone()));
Hans Boehm84614952014-11-25 18:46:17 -0800607 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700608 result.mExpr.add(t);
Hans Boehm84614952014-11-25 18:46:17 -0800609 }
610 }
Hans Boehm3666e632015-07-27 18:33:12 -0700611 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800612 }
613
614 // Am I just a constant?
Hans Boehm3666e632015-07-27 18:33:12 -0700615 public boolean isConstant() {
616 if (mExpr.size() != 1) {
617 return false;
618 }
Hans Boehm84614952014-11-25 18:46:17 -0800619 return mExpr.get(0) instanceof Constant;
620 }
621
Hans Boehm3666e632015-07-27 18:33:12 -0700622 /**
623 * Return a new expression consisting of a single token representing the current pre-evaluated
624 * expression.
Hans Boehm8f051c32016-10-03 16:53:58 -0700625 * The caller supplies the expression index and short string representation.
626 * The expression must have been previously evaluated.
Hans Boehm3666e632015-07-27 18:33:12 -0700627 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700628 public CalculatorExpr abbreviate(long index, String sr) {
Hans Boehm84614952014-11-25 18:46:17 -0800629 CalculatorExpr result = new CalculatorExpr();
Justin Klaassen0ace4eb2016-02-05 11:38:12 -0800630 @SuppressWarnings("unchecked")
Hans Boehm8f051c32016-10-03 16:53:58 -0700631 Token t = new PreEval(index, sr);
Hans Boehm84614952014-11-25 18:46:17 -0800632 result.mExpr.add(t);
633 return result;
634 }
635
Hans Boehm3666e632015-07-27 18:33:12 -0700636 /**
Hans Boehm995e5eb2016-02-08 11:03:01 -0800637 * Internal evaluation functions return an EvalRet pair.
Hans Boehm3666e632015-07-27 18:33:12 -0700638 * We compute rational (BoundedRational) results when possible, both as a performance
639 * optimization, and to detect errors exactly when we can.
640 */
641 private static class EvalRet {
642 public int pos; // Next position (expression index) to be parsed.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800643 public final UnifiedReal val; // Constructive Real result of evaluating subexpression.
644 EvalRet(int p, UnifiedReal v) {
Hans Boehm3666e632015-07-27 18:33:12 -0700645 pos = p;
646 val = v;
Hans Boehm84614952014-11-25 18:46:17 -0800647 }
648 }
649
Hans Boehm3666e632015-07-27 18:33:12 -0700650 /**
651 * Internal evaluation functions take an EvalContext argument.
652 */
Hans Boehm84614952014-11-25 18:46:17 -0800653 private static class EvalContext {
Hans Boehm3666e632015-07-27 18:33:12 -0700654 public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
Hans Boehmc023b732015-04-29 11:30:47 -0700655 public final boolean mDegreeMode;
Hans Boehm8f051c32016-10-03 16:53:58 -0700656 public final ExprResolver mExprResolver; // Reconstructed, not saved.
Hans Boehm84614952014-11-25 18:46:17 -0800657 // If we add any other kinds of evaluation modes, they go here.
Hans Boehm8f051c32016-10-03 16:53:58 -0700658 EvalContext(boolean degreeMode, int len, ExprResolver er) {
Hans Boehm84614952014-11-25 18:46:17 -0800659 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700660 mPrefixLength = len;
Hans Boehm8f051c32016-10-03 16:53:58 -0700661 mExprResolver = er;
Hans Boehm84614952014-11-25 18:46:17 -0800662 }
Hans Boehm8f051c32016-10-03 16:53:58 -0700663 EvalContext(DataInput in, int len, ExprResolver er) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800664 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700665 mPrefixLength = len;
Hans Boehm8f051c32016-10-03 16:53:58 -0700666 mExprResolver = er;
Hans Boehm84614952014-11-25 18:46:17 -0800667 }
668 void write(DataOutput out) throws IOException {
669 out.writeBoolean(mDegreeMode);
670 }
671 }
672
Hans Boehm995e5eb2016-02-08 11:03:01 -0800673 private UnifiedReal toRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800674 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800675 return x.multiply(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800676 } else {
677 return x;
678 }
679 }
680
Hans Boehm995e5eb2016-02-08 11:03:01 -0800681 private UnifiedReal fromRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800682 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800683 return x.divide(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800684 } else {
685 return x;
686 }
687 }
688
Hans Boehm3666e632015-07-27 18:33:12 -0700689 // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
690 // error. We expect that to be caught in eval below.
Hans Boehm84614952014-11-25 18:46:17 -0800691
Hans Boehmc023b732015-04-29 11:30:47 -0700692 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800693 Token t = mExpr.get(i);
Hans Boehm3666e632015-07-27 18:33:12 -0700694 if (!(t instanceof Operator)) {
695 return false;
696 }
697 return ((Operator)(t)).id == op;
Hans Boehm84614952014-11-25 18:46:17 -0800698 }
699
Hans Boehmc023b732015-04-29 11:30:47 -0700700 private boolean isOperator(int i, int op, EvalContext ec) {
Hans Boehm3666e632015-07-27 18:33:12 -0700701 if (i >= ec.mPrefixLength) {
702 return false;
703 }
Hans Boehmc023b732015-04-29 11:30:47 -0700704 return isOperatorUnchecked(i, op);
705 }
706
Hans Boehm3666e632015-07-27 18:33:12 -0700707 public static class SyntaxException extends Exception {
Hans Boehmc023b732015-04-29 11:30:47 -0700708 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800709 super();
710 }
Hans Boehmc023b732015-04-29 11:30:47 -0700711 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800712 super(s);
713 }
714 }
715
Hans Boehm3666e632015-07-27 18:33:12 -0700716 // The following functions all evaluate some kind of expression starting at position i in
717 // mExpr in a specified evaluation context. They return both the expression value (as
718 // constructive real and, if applicable, as BoundedRational) and the position of the next token
Hans Boehm84614952014-11-25 18:46:17 -0800719 // that was not used as part of the evaluation.
Hans Boehm3666e632015-07-27 18:33:12 -0700720 // This is essentially a simple recursive descent parser combined with expression evaluation.
721
Hans Boehmc023b732015-04-29 11:30:47 -0700722 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700723 final Token t = mExpr.get(i);
Hans Boehm84614952014-11-25 18:46:17 -0800724 if (t instanceof Constant) {
725 Constant c = (Constant)t;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800726 return new EvalRet(i+1,new UnifiedReal(c.toRational()));
Hans Boehm84614952014-11-25 18:46:17 -0800727 }
728 if (t instanceof PreEval) {
Hans Boehm8f051c32016-10-03 16:53:58 -0700729 final long index = ((PreEval)t).mIndex;
730 UnifiedReal res = ec.mExprResolver.getResult(index);
731 if (res == null) {
Hans Boehm9c160b42016-12-02 11:55:12 -0800732 // We try to minimize this recursive evaluation case, but currently don't
733 // completely avoid it.
734 res = nestedEval(index, ec.mExprResolver);
Hans Boehm8f051c32016-10-03 16:53:58 -0700735 }
736 return new EvalRet(i+1, res);
Hans Boehm84614952014-11-25 18:46:17 -0800737 }
738 EvalRet argVal;
Hans Boehm3666e632015-07-27 18:33:12 -0700739 switch(((Operator)(t)).id) {
Hans Boehm84614952014-11-25 18:46:17 -0800740 case R.id.const_pi:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800741 return new EvalRet(i+1, UnifiedReal.PI);
Hans Boehm84614952014-11-25 18:46:17 -0800742 case R.id.const_e:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800743 return new EvalRet(i+1, UnifiedReal.E);
Hans Boehm84614952014-11-25 18:46:17 -0800744 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700745 // Seems to have highest precedence.
746 // Does not add implicit paren.
747 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700748 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700749 argVal = evalUnary(i+2, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800750 return new EvalRet(argVal.pos, argVal.val.negate().sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700751 } else {
752 argVal = evalUnary(i+1, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800753 return new EvalRet(argVal.pos, argVal.val.sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700754 }
Hans Boehm84614952014-11-25 18:46:17 -0800755 case R.id.lparen:
756 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700757 if (isOperator(argVal.pos, R.id.rparen, ec)) {
758 argVal.pos++;
759 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800760 return new EvalRet(argVal.pos, argVal.val);
Hans Boehm84614952014-11-25 18:46:17 -0800761 case R.id.fun_sin:
762 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700763 if (isOperator(argVal.pos, R.id.rparen, ec)) {
764 argVal.pos++;
765 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800766 return new EvalRet(argVal.pos, toRadians(argVal.val, ec).sin());
Hans Boehm84614952014-11-25 18:46:17 -0800767 case R.id.fun_cos:
768 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700769 if (isOperator(argVal.pos, R.id.rparen, ec)) {
770 argVal.pos++;
771 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800772 return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos());
Hans Boehm84614952014-11-25 18:46:17 -0800773 case R.id.fun_tan:
774 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700775 if (isOperator(argVal.pos, R.id.rparen, ec)) {
776 argVal.pos++;
777 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800778 UnifiedReal arg = toRadians(argVal.val, ec);
779 return new EvalRet(argVal.pos, arg.sin().divide(arg.cos()));
Hans Boehm84614952014-11-25 18:46:17 -0800780 case R.id.fun_ln:
781 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700782 if (isOperator(argVal.pos, R.id.rparen, ec)) {
783 argVal.pos++;
784 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800785 return new EvalRet(argVal.pos, argVal.val.ln());
Hans Boehm4db31b42015-05-31 12:19:05 -0700786 case R.id.fun_exp:
787 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700788 if (isOperator(argVal.pos, R.id.rparen, ec)) {
789 argVal.pos++;
790 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800791 return new EvalRet(argVal.pos, argVal.val.exp());
Hans Boehm84614952014-11-25 18:46:17 -0800792 case R.id.fun_log:
793 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700794 if (isOperator(argVal.pos, R.id.rparen, ec)) {
795 argVal.pos++;
796 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800797 return new EvalRet(argVal.pos, argVal.val.ln().divide(UnifiedReal.TEN.ln()));
Hans Boehm84614952014-11-25 18:46:17 -0800798 case R.id.fun_arcsin:
799 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700800 if (isOperator(argVal.pos, R.id.rparen, ec)) {
801 argVal.pos++;
802 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800803 return new EvalRet(argVal.pos, fromRadians(argVal.val.asin(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800804 case R.id.fun_arccos:
805 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700806 if (isOperator(argVal.pos, R.id.rparen, ec)) {
807 argVal.pos++;
808 }
Hans Boehm79f273c2016-06-09 11:00:27 -0700809 return new EvalRet(argVal.pos, fromRadians(argVal.val.acos(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800810 case R.id.fun_arctan:
811 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700812 if (isOperator(argVal.pos, R.id.rparen, ec)) {
813 argVal.pos++;
814 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800815 return new EvalRet(argVal.pos, fromRadians(argVal.val.atan(),ec));
Hans Boehm84614952014-11-25 18:46:17 -0800816 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700817 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800818 }
Hans Boehm84614952014-11-25 18:46:17 -0800819 }
820
Hans Boehm995e5eb2016-02-08 11:03:01 -0800821 private static final UnifiedReal ONE_HUNDREDTH = new UnifiedReal(100).inverse();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700822
Hans Boehm4db31b42015-05-31 12:19:05 -0700823 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700824 final EvalRet tmp = evalUnary(i, ec);
825 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800826 UnifiedReal val = tmp.val;
827
Hans Boehm4db31b42015-05-31 12:19:05 -0700828 boolean isFact;
829 boolean isSquared = false;
830 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
831 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
832 isOperator(cpos, R.id.op_pct, ec)) {
833 if (isFact) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800834 val = val.fact();
Hans Boehm4db31b42015-05-31 12:19:05 -0700835 } else if (isSquared) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800836 val = val.multiply(val);
Hans Boehm4db31b42015-05-31 12:19:05 -0700837 } else /* percent */ {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800838 val = val.multiply(ONE_HUNDREDTH);
Hans Boehm84614952014-11-25 18:46:17 -0800839 }
Hans Boehm84614952014-11-25 18:46:17 -0800840 ++cpos;
841 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800842 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800843 }
844
Hans Boehmc023b732015-04-29 11:30:47 -0700845 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700846 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700847 int cpos = result1.pos; // current position
Hans Boehm995e5eb2016-02-08 11:03:01 -0800848 UnifiedReal val = result1.val; // value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700849 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700850 final EvalRet exp = evalSignedFactor(cpos + 1, ec);
851 cpos = exp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800852 val = val.pow(exp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800853 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800854 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800855 }
856
Hans Boehmc023b732015-04-29 11:30:47 -0700857 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
858 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700859 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800860 EvalRet tmp = evalFactor(cpos, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700861 cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800862 final UnifiedReal result = negative ? tmp.val.negate() : tmp.val;
863 return new EvalRet(cpos, result);
Hans Boehm84614952014-11-25 18:46:17 -0800864 }
865
866 private boolean canStartFactor(int i) {
867 if (i >= mExpr.size()) return false;
868 Token t = mExpr.get(i);
869 if (!(t instanceof Operator)) return true;
Hans Boehm3666e632015-07-27 18:33:12 -0700870 int id = ((Operator)(t)).id;
Hans Boehm84614952014-11-25 18:46:17 -0800871 if (KeyMaps.isBinary(id)) return false;
872 switch (id) {
873 case R.id.op_fact:
874 case R.id.rparen:
875 return false;
876 default:
877 return true;
878 }
879 }
880
Hans Boehmc023b732015-04-29 11:30:47 -0700881 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800882 EvalRet tmp = evalSignedFactor(i, ec);
883 boolean is_mul = false;
884 boolean is_div = false;
Hans Boehm3666e632015-07-27 18:33:12 -0700885 int cpos = tmp.pos; // Current position in expression.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800886 UnifiedReal val = tmp.val; // Current value.
Hans Boehmc023b732015-04-29 11:30:47 -0700887 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
888 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -0800889 || canStartFactor(cpos)) {
890 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700891 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800892 if (is_div) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800893 val = val.divide(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800894 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800895 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800896 }
Hans Boehm3666e632015-07-27 18:33:12 -0700897 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800898 is_mul = is_div = false;
899 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800900 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800901 }
902
Hans Boehm8afd0f82015-07-30 17:00:33 -0700903 /**
904 * Is the subexpression starting at pos a simple percent constant?
905 * This is used to recognize exppressions like 200+10%, which we handle specially.
906 * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
907 * by either nothing or an additive operator.
908 * Note that we are intentionally far more restrictive in recognizing such expressions than
909 * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
910 * When in doubt, we fall back to the the naive interpretation of % as 1/100.
911 * Note that 100+(10)% yields 100.1 while 100+10% yields 110. This may be controversial,
912 * but is consistent with Google web search.
913 */
914 private boolean isPercent(int pos) {
915 if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
916 return false;
917 }
918 Token number = mExpr.get(pos);
919 if (number instanceof Operator) {
920 return false;
921 }
922 if (mExpr.size() == pos + 2) {
923 return true;
924 }
925 if (!(mExpr.get(pos + 2) instanceof Operator)) {
926 return false;
927 }
928 Operator op = (Operator) mExpr.get(pos + 2);
Hans Boehm64d1cdb2016-03-08 19:14:07 -0800929 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 -0700930 }
931
932 /**
933 * Compute the multiplicative factor corresponding to an N% addition or subtraction.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800934 * @param pos position of Constant or PreEval expression token corresponding to N.
935 * @param isSubtraction this is a subtraction, as opposed to addition.
936 * @param ec usable evaluation contex; only length matters.
937 * @return UnifiedReal value and position, which is pos + 2, i.e. after percent sign
Hans Boehm8afd0f82015-07-30 17:00:33 -0700938 */
939 private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
940 throws SyntaxException {
941 EvalRet tmp = evalUnary(pos, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800942 UnifiedReal val = isSubtraction ? tmp.val.negate() : tmp.val;
943 val = UnifiedReal.ONE.add(val.multiply(ONE_HUNDREDTH));
944 return new EvalRet(pos + 2 /* after percent sign */, val);
Hans Boehm8afd0f82015-07-30 17:00:33 -0700945 }
946
Hans Boehmc023b732015-04-29 11:30:47 -0700947 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800948 EvalRet tmp = evalTerm(i, ec);
949 boolean is_plus;
Hans Boehm3666e632015-07-27 18:33:12 -0700950 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800951 UnifiedReal val = tmp.val;
Hans Boehmc023b732015-04-29 11:30:47 -0700952 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
953 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700954 if (isPercent(cpos + 1)) {
955 tmp = getPercentFactor(cpos + 1, !is_plus, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800956 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800957 } else {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700958 tmp = evalTerm(cpos + 1, ec);
959 if (is_plus) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800960 val = val.add(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700961 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800962 val = val.subtract(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800963 }
964 }
Hans Boehm3666e632015-07-27 18:33:12 -0700965 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800966 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800967 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800968 }
969
Hans Boehme8553762015-06-26 17:56:52 -0700970 /**
971 * Return the starting position of the sequence of trailing binary operators.
972 */
973 private int trailingBinaryOpsStart() {
Hans Boehmc023b732015-04-29 11:30:47 -0700974 int result = mExpr.size();
975 while (result > 0) {
976 Token last = mExpr.get(result - 1);
977 if (!(last instanceof Operator)) break;
978 Operator o = (Operator)last;
Hans Boehm3666e632015-07-27 18:33:12 -0700979 if (!KeyMaps.isBinary(o.id)) break;
Hans Boehmc023b732015-04-29 11:30:47 -0700980 --result;
981 }
982 return result;
983 }
984
Hans Boehm3666e632015-07-27 18:33:12 -0700985 /**
986 * Is the current expression worth evaluating?
987 */
Hans Boehmc023b732015-04-29 11:30:47 -0700988 public boolean hasInterestingOps() {
Hans Boehm83f278e2016-12-19 16:20:03 -0800989 final int last = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -0700990 int first = 0;
991 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
992 // Leading minus is not by itself interesting.
993 first++;
994 }
995 for (int i = first; i < last; ++i) {
996 Token t1 = mExpr.get(i);
Hans Boehm187d3e92015-06-09 18:04:26 -0700997 if (t1 instanceof Operator
998 || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
999 return true;
1000 }
Hans Boehmc023b732015-04-29 11:30:47 -07001001 }
1002 return false;
1003 }
1004
Hans Boehme8553762015-06-26 17:56:52 -07001005 /**
Hans Boehm52d477a2016-04-01 17:42:50 -07001006 * Does the expression contain trig operations?
1007 */
1008 public boolean hasTrigFuncs() {
Hans Boehm9c160b42016-12-02 11:55:12 -08001009 for (Token t : mExpr) {
Hans Boehm52d477a2016-04-01 17:42:50 -07001010 if (t instanceof Operator) {
1011 Operator o = (Operator)t;
1012 if (KeyMaps.isTrigFunc(o.id)) {
1013 return true;
1014 }
1015 }
1016 }
1017 return false;
1018 }
1019
1020 /**
Hans Boehm9c160b42016-12-02 11:55:12 -08001021 * Add the indices of unevaluated PreEval expressions embedded in the current expression to
1022 * argument. This includes only directly referenced expressions e, not those indirectly
1023 * referenced by e. If the index was already present, it is not added. If the argument
1024 * contained no duplicates, the result will not either. New indices are added to the end of
1025 * the list.
1026 */
1027 private void addReferencedExprs(ArrayList<Long> list, ExprResolver er) {
1028 for (Token t : mExpr) {
1029 if (t instanceof PreEval) {
1030 Long index = ((PreEval) t).mIndex;
1031 if (er.getResult(index) == null && !list.contains(index)) {
1032 list.add(index);
1033 }
1034 }
1035 }
1036 }
1037
1038 /**
1039 * Return a list of unevaluated expressions transitively referenced by the current one.
1040 * All expressions in the resulting list will have had er.getExpr() called on them.
1041 * The resulting list is ordered such that evaluating expressions in list order
1042 * should trigger few recursive evaluations.
1043 */
1044 public ArrayList<Long> getTransitivelyReferencedExprs(ExprResolver er) {
1045 // We could avoid triggering any recursive evaluations by actually building the
1046 // dependency graph and topologically sorting it. Note that sorting by index works
1047 // for positive and negative indices separately, but not their union. Currently we
1048 // just settle for reverse breadth-first-search order, which handles the common case
1049 // of simple dependency chains well.
1050 ArrayList<Long> list = new ArrayList<Long>();
1051 int scanned = 0; // We've added expressions referenced by [0, scanned) to the list
1052 addReferencedExprs(list, er);
1053 while (scanned != list.size()) {
1054 er.getExpr(list.get(scanned++)).addReferencedExprs(list, er);
1055 }
1056 Collections.reverse(list);
1057 return list;
1058 }
1059
1060 /**
1061 * Evaluate the expression at the given index to a UnifiedReal.
1062 * Both saves and returns the result.
1063 */
1064 UnifiedReal nestedEval(long index, ExprResolver er) throws SyntaxException {
1065 CalculatorExpr nestedExpr = er.getExpr(index);
1066 EvalContext newEc = new EvalContext(er.getDegreeMode(index),
1067 nestedExpr.trailingBinaryOpsStart(), er);
1068 EvalRet new_res = nestedExpr.evalExpr(0, newEc);
1069 return er.putResultIfAbsent(index, new_res.val);
1070 }
1071
1072 /**
Hans Boehme8553762015-06-26 17:56:52 -07001073 * Evaluate the expression excluding trailing binary operators.
Hans Boehm3666e632015-07-27 18:33:12 -07001074 * Errors result in exceptions, most of which are unchecked. Should not be called
1075 * concurrently with modification of the expression. May take a very long time; avoid calling
1076 * from UI thread.
Hans Boehme8553762015-06-26 17:56:52 -07001077 *
1078 * @param degreeMode use degrees rather than radians
1079 */
Hans Boehm8f051c32016-10-03 16:53:58 -07001080 UnifiedReal eval(boolean degreeMode, ExprResolver er) throws SyntaxException
Hans Boehm995e5eb2016-02-08 11:03:01 -08001081 // And unchecked exceptions thrown by UnifiedReal, CR,
Hans Boehmc023b732015-04-29 11:30:47 -07001082 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -08001083 {
Hans Boehm9c160b42016-12-02 11:55:12 -08001084 // First evaluate all indirectly referenced expressions in increasing index order.
1085 // This ensures that subsequent evaluation never encounters an embedded PreEval
1086 // expression that has not been previously evaluated.
1087 // We could do the embedded evaluations recursively, but that risks running out of
1088 // stack space.
1089 ArrayList<Long> referenced = getTransitivelyReferencedExprs(er);
1090 for (long index : referenced) {
1091 nestedEval(index, er);
1092 }
Hans Boehm84614952014-11-25 18:46:17 -08001093 try {
Hans Boehm3666e632015-07-27 18:33:12 -07001094 // We currently never include trailing binary operators, but include other trailing
1095 // operators. Thus we usually, but not always, display results for prefixes of valid
1096 // expressions, and don't generate an error where we previously displayed an instant
1097 // result. This reflects the Android L design.
Hans Boehme8553762015-06-26 17:56:52 -07001098 int prefixLen = trailingBinaryOpsStart();
Hans Boehm8f051c32016-10-03 16:53:58 -07001099 EvalContext ec = new EvalContext(degreeMode, prefixLen, er);
Hans Boehm84614952014-11-25 18:46:17 -08001100 EvalRet res = evalExpr(0, ec);
Hans Boehm3666e632015-07-27 18:33:12 -07001101 if (res.pos != prefixLen) {
Hans Boehmc023b732015-04-29 11:30:47 -07001102 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -07001103 }
Hans Boehm995e5eb2016-02-08 11:03:01 -08001104 return res.val;
Hans Boehm84614952014-11-25 18:46:17 -08001105 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -07001106 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -08001107 }
1108 }
1109
1110 // Produce a string representation of the expression itself
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001111 SpannableStringBuilder toSpannableStringBuilder(Context context) {
1112 SpannableStringBuilder ssb = new SpannableStringBuilder();
Hans Boehm9c160b42016-12-02 11:55:12 -08001113 for (Token t : mExpr) {
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001114 ssb.append(t.toCharSequence(context));
Hans Boehm84614952014-11-25 18:46:17 -08001115 }
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001116 return ssb;
Hans Boehm84614952014-11-25 18:46:17 -08001117 }
1118}