blob: 33227a0b90811e7b896bca290f3878259ee93598 [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 Boehmc023b732015-04-29 11:30:47 -070025import android.util.Log;
Hans Boehm84614952014-11-25 18:46:17 -080026
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070027import java.math.BigInteger;
28import java.io.DataInput;
29import java.io.DataOutput;
30import java.io.IOException;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070031import java.util.ArrayList;
32import java.util.HashMap;
33import java.util.IdentityHashMap;
34
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 {
50 private ArrayList<Token> mExpr; // The actual representation
51 // as a list of tokens. Constant
52 // tokens are always nonempty.
53
54 private static enum TokenKind { CONSTANT, OPERATOR, PRE_EVAL };
55 private static TokenKind[] tokenKindValues = TokenKind.values();
56 private final static BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
57 private final static BigInteger BIG_BILLION = BigInteger.valueOf(1000000000);
58
59 private static abstract class Token {
60 abstract TokenKind kind();
Hans Boehm8a4f81c2015-07-09 10:41:25 -070061
62 /**
63 * Write kind as Byte followed by data needed by subclass constructor.
64 */
Hans Boehm84614952014-11-25 18:46:17 -080065 abstract void write(DataOutput out) throws IOException;
Hans Boehm8a4f81c2015-07-09 10:41:25 -070066
67 /**
68 * Return a textual representation of the token.
69 * The result is suitable for either display as part od the formula or TalkBack use.
70 * It may be a SpannableString that includes added TalkBack information.
71 * @param context context used for converting button ids to strings
72 */
73 abstract CharSequence toCharSequence(Context context);
Hans Boehm84614952014-11-25 18:46:17 -080074 }
75
Hans Boehm3666e632015-07-27 18:33:12 -070076 /**
77 * Representation of an operator token
78 */
Hans Boehm84614952014-11-25 18:46:17 -080079 private static class Operator extends Token {
Hans Boehm3666e632015-07-27 18:33:12 -070080 public final int id; // We use the button resource id
Hans Boehm84614952014-11-25 18:46:17 -080081 Operator(int resId) {
Hans Boehm3666e632015-07-27 18:33:12 -070082 id = resId;
Hans Boehm84614952014-11-25 18:46:17 -080083 }
84 Operator(DataInput in) throws IOException {
Hans Boehm3666e632015-07-27 18:33:12 -070085 id = in.readInt();
Hans Boehm84614952014-11-25 18:46:17 -080086 }
87 @Override
88 void write(DataOutput out) throws IOException {
89 out.writeByte(TokenKind.OPERATOR.ordinal());
Hans Boehm3666e632015-07-27 18:33:12 -070090 out.writeInt(id);
Hans Boehm84614952014-11-25 18:46:17 -080091 }
92 @Override
Hans Boehm8a4f81c2015-07-09 10:41:25 -070093 public CharSequence toCharSequence(Context context) {
Hans Boehm3666e632015-07-27 18:33:12 -070094 String desc = KeyMaps.toDescriptiveString(context, id);
Hans Boehm8a4f81c2015-07-09 10:41:25 -070095 if (desc != null) {
Hans Boehm3666e632015-07-27 18:33:12 -070096 SpannableString result = new SpannableString(KeyMaps.toString(context, id));
Hans Boehm8a4f81c2015-07-09 10:41:25 -070097 Object descSpan = new TtsSpan.TextBuilder(desc).build();
98 result.setSpan(descSpan, 0, result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
99 return result;
100 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700101 return KeyMaps.toString(context, id);
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700102 }
Hans Boehm84614952014-11-25 18:46:17 -0800103 }
104 @Override
105 TokenKind kind() { return TokenKind.OPERATOR; }
106 }
107
Hans Boehm3666e632015-07-27 18:33:12 -0700108 /**
109 * Representation of a (possibly incomplete) numerical constant.
110 * Supports addition and removal of trailing characters; hence mutable.
111 */
Hans Boehm84614952014-11-25 18:46:17 -0800112 private static class Constant extends Token implements Cloneable {
113 private boolean mSawDecimal;
Hans Boehm3666e632015-07-27 18:33:12 -0700114 private String mWhole; // String preceding decimal point.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700115 private String mFraction; // String after decimal point.
116 private int mExponent; // Explicit exponent, only generated through addExponent.
Hans Boehm84614952014-11-25 18:46:17 -0800117
118 Constant() {
119 mWhole = "";
120 mFraction = "";
Hans Boehm0b9806f2015-06-29 16:07:15 -0700121 mSawDecimal = false;
122 mExponent = 0;
Hans Boehm84614952014-11-25 18:46:17 -0800123 };
124
125 Constant(DataInput in) throws IOException {
126 mWhole = in.readUTF();
127 mSawDecimal = in.readBoolean();
128 mFraction = in.readUTF();
Hans Boehm0b9806f2015-06-29 16:07:15 -0700129 mExponent = in.readInt();
Hans Boehm84614952014-11-25 18:46:17 -0800130 }
131
132 @Override
133 void write(DataOutput out) throws IOException {
134 out.writeByte(TokenKind.CONSTANT.ordinal());
135 out.writeUTF(mWhole);
136 out.writeBoolean(mSawDecimal);
137 out.writeUTF(mFraction);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700138 out.writeInt(mExponent);
Hans Boehm84614952014-11-25 18:46:17 -0800139 }
140
141 // Given a button press, append corresponding digit.
142 // We assume id is a digit or decimal point.
143 // Just return false if this was the second (or later) decimal point
144 // in this constant.
Hans Boehm0b9806f2015-06-29 16:07:15 -0700145 // Assumes that this constant does not have an exponent.
Hans Boehm3666e632015-07-27 18:33:12 -0700146 public boolean add(int id) {
Hans Boehm84614952014-11-25 18:46:17 -0800147 if (id == R.id.dec_point) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700148 if (mSawDecimal || mExponent != 0) return false;
Hans Boehm84614952014-11-25 18:46:17 -0800149 mSawDecimal = true;
150 return true;
151 }
152 int val = KeyMaps.digVal(id);
Hans Boehm0b9806f2015-06-29 16:07:15 -0700153 if (mExponent != 0) {
154 if (Math.abs(mExponent) <= 10000) {
155 if (mExponent > 0) {
156 mExponent = 10 * mExponent + val;
157 } else {
158 mExponent = 10 * mExponent - val;
159 }
160 return true;
161 } else { // Too large; refuse
162 return false;
163 }
164 }
Hans Boehm84614952014-11-25 18:46:17 -0800165 if (mSawDecimal) {
166 mFraction += val;
167 } else {
168 mWhole += val;
169 }
170 return true;
171 }
172
Hans Boehm3666e632015-07-27 18:33:12 -0700173 public void addExponent(int exp) {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700174 // Note that adding a 0 exponent is a no-op. That's OK.
175 mExponent = exp;
176 }
177
Hans Boehm3666e632015-07-27 18:33:12 -0700178 /**
179 * Undo the last add or remove last exponent digit.
180 * Assumes the constant is nonempty.
181 */
182 public void delete() {
Hans Boehm0b9806f2015-06-29 16:07:15 -0700183 if (mExponent != 0) {
184 mExponent /= 10;
185 // Once zero, it can only be added back with addExponent.
186 } else if (!mFraction.isEmpty()) {
Hans Boehm84614952014-11-25 18:46:17 -0800187 mFraction = mFraction.substring(0, mFraction.length() - 1);
188 } else if (mSawDecimal) {
189 mSawDecimal = false;
190 } else {
191 mWhole = mWhole.substring(0, mWhole.length() - 1);
192 }
193 }
194
Hans Boehm3666e632015-07-27 18:33:12 -0700195 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800196 return (mSawDecimal == false && mWhole.isEmpty());
197 }
198
Hans Boehm3666e632015-07-27 18:33:12 -0700199 /**
200 * Produce human-readable string representation of constant, as typed.
201 * Result is internationalized.
202 */
Hans Boehm84614952014-11-25 18:46:17 -0800203 @Override
204 public String toString() {
205 String result = mWhole;
206 if (mSawDecimal) {
Hans Boehm013969e2015-04-13 20:29:47 -0700207 result += '.';
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700208 result += mFraction;
209 }
Hans Boehm0b9806f2015-06-29 16:07:15 -0700210 if (mExponent != 0) {
211 result += "E" + mExponent;
212 }
Hans Boehm013969e2015-04-13 20:29:47 -0700213 return KeyMaps.translateResult(result);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700214 }
215
Hans Boehm3666e632015-07-27 18:33:12 -0700216 /**
Hans Boehmfa5203c2015-08-17 16:14:52 -0700217 * Return BoundedRational representation of constant, if well-formed.
218 * Result is never null.
Hans Boehm3666e632015-07-27 18:33:12 -0700219 */
Hans Boehmfa5203c2015-08-17 16:14:52 -0700220 public BoundedRational toRational() throws SyntaxException {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700221 String whole = mWhole;
Hans Boehmfa5203c2015-08-17 16:14:52 -0700222 if (whole.isEmpty()) {
223 if (mFraction.isEmpty()) {
224 // Decimal point without digits.
225 throw new SyntaxException();
226 } else {
227 whole = "0";
228 }
229 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700230 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700231 BigInteger den = BigInteger.TEN.pow(mFraction.length());
Hans Boehm0b9806f2015-06-29 16:07:15 -0700232 if (mExponent > 0) {
233 num = num.multiply(BigInteger.TEN.pow(mExponent));
234 }
235 if (mExponent < 0) {
236 den = den.multiply(BigInteger.TEN.pow(-mExponent));
237 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700238 return new BoundedRational(num, den);
239 }
240
Hans Boehm84614952014-11-25 18:46:17 -0800241 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700242 public CharSequence toCharSequence(Context context) {
Hans Boehm84614952014-11-25 18:46:17 -0800243 return toString();
244 }
245
246 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700247 public TokenKind kind() {
248 return TokenKind.CONSTANT;
249 }
Hans Boehm84614952014-11-25 18:46:17 -0800250
251 // Override clone to make it public
252 @Override
253 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700254 Constant result = new Constant();
255 result.mWhole = mWhole;
256 result.mFraction = mFraction;
257 result.mSawDecimal = mSawDecimal;
258 result.mExponent = mExponent;
259 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800260 }
261 }
262
Hans Boehm3666e632015-07-27 18:33:12 -0700263 // Hash maps used to detect duplicate subexpressions when we write out CalculatorExprs and
264 // read them back in.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800265 private static final ThreadLocal<IdentityHashMap<UnifiedReal, Integer>>outMap =
266 new ThreadLocal<IdentityHashMap<UnifiedReal, Integer>>();
Hans Boehm84614952014-11-25 18:46:17 -0800267 // Maps expressions to indices on output
Hans Boehm995e5eb2016-02-08 11:03:01 -0800268 private static final ThreadLocal<HashMap<Integer, PreEval>>inMap =
269 new ThreadLocal<HashMap<Integer, PreEval>>();
Hans Boehm84614952014-11-25 18:46:17 -0800270 // Maps expressions to indices on output
Hans Boehm3666e632015-07-27 18:33:12 -0700271 private static final ThreadLocal<Integer> exprIndex = new ThreadLocal<Integer>();
Hans Boehm84614952014-11-25 18:46:17 -0800272
Hans Boehm3666e632015-07-27 18:33:12 -0700273 /**
274 * Prepare for expression output.
275 * Initializes map that will lbe used to avoid duplicating shared subexpressions.
276 * This avoids a potential exponential blow-up in the expression size.
277 */
278 public static void initExprOutput() {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800279 outMap.set(new IdentityHashMap<UnifiedReal, Integer>());
Hans Boehm84614952014-11-25 18:46:17 -0800280 exprIndex.set(Integer.valueOf(0));
281 }
282
Hans Boehm3666e632015-07-27 18:33:12 -0700283 /**
284 * Prepare for expression input.
285 * Initializes map that will be used to reconstruct shared subexpressions.
286 */
287 public static void initExprInput() {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800288 inMap.set(new HashMap<Integer, PreEval>());
Hans Boehm84614952014-11-25 18:46:17 -0800289 }
290
Hans Boehm3666e632015-07-27 18:33:12 -0700291 /**
292 * The "token" class for previously evaluated subexpressions.
293 * We treat previously evaluated subexpressions as tokens. These are inserted when we either
294 * continue an expression after evaluating some of it, or copy an expression and paste it back
295 * in.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800296 * The representation includes a UnifiedReal value. In order to
Hans Boehm3666e632015-07-27 18:33:12 -0700297 * support saving and restoring, we also include the underlying expression itself, and the
298 * context (currently just degree mode) used to evaluate it. The short string representation
299 * is also stored in order to avoid potentially expensive recomputation in the UI thread.
300 */
Hans Boehm84614952014-11-25 18:46:17 -0800301 private static class PreEval extends Token {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800302 public final UnifiedReal value;
Hans Boehm84614952014-11-25 18:46:17 -0800303 private final CalculatorExpr mExpr;
304 private final EvalContext mContext;
Hans Boehm50ed3202015-06-09 14:35:49 -0700305 private final String mShortRep; // Not internationalized.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800306 PreEval(UnifiedReal val, CalculatorExpr expr, EvalContext ec, String shortRep) {
Hans Boehm3666e632015-07-27 18:33:12 -0700307 value = val;
Hans Boehm84614952014-11-25 18:46:17 -0800308 mExpr = expr;
309 mContext = ec;
310 mShortRep = shortRep;
311 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800312 // In writing out PreEvals, we are careful to avoid writing out duplicates. We conclude
313 // that two expressions are duplicates if they have the same UnifiedReal value. This
314 // avoids a potential exponential blow up in certain off cases and redundant evaluation
315 // after reading them back in. The parameter hash map maps expressions we've seen
Hans Boehm84614952014-11-25 18:46:17 -0800316 // before to their index.
317 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700318 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800319 out.writeByte(TokenKind.PRE_EVAL.ordinal());
Hans Boehm3666e632015-07-27 18:33:12 -0700320 Integer index = outMap.get().get(value);
Hans Boehm84614952014-11-25 18:46:17 -0800321 if (index == null) {
322 int nextIndex = exprIndex.get() + 1;
323 exprIndex.set(nextIndex);
Hans Boehm3666e632015-07-27 18:33:12 -0700324 outMap.get().put(value, nextIndex);
Hans Boehm84614952014-11-25 18:46:17 -0800325 out.writeInt(nextIndex);
326 mExpr.write(out);
327 mContext.write(out);
328 out.writeUTF(mShortRep);
329 } else {
330 // Just write out the index
331 out.writeInt(index);
332 }
333 }
334 PreEval(DataInput in) throws IOException {
335 int index = in.readInt();
336 PreEval prev = inMap.get().get(index);
337 if (prev == null) {
338 mExpr = new CalculatorExpr(in);
Hans Boehmc023b732015-04-29 11:30:47 -0700339 mContext = new EvalContext(in, mExpr.mExpr.size());
Hans Boehm3666e632015-07-27 18:33:12 -0700340 // Recompute other fields We currently do this in the UI thread, but we only
341 // create PreEval expressions that were previously successfully evaluated, and
342 // thus don't diverge. We also only evaluate to a constructive real, which
343 // involves substantial work only in fairly contrived circumstances.
Hans Boehm84614952014-11-25 18:46:17 -0800344 // TODO: Deal better with slow evaluations.
Hans Boehmc023b732015-04-29 11:30:47 -0700345 EvalRet res = null;
346 try {
347 res = mExpr.evalExpr(0, mContext);
348 } catch (SyntaxException e) {
349 // Should be impossible, since we only write out
350 // expressions that can be evaluated.
351 Log.e("Calculator", "Unexpected syntax exception" + e);
352 }
Hans Boehm3666e632015-07-27 18:33:12 -0700353 value = res.val;
Hans Boehm84614952014-11-25 18:46:17 -0800354 mShortRep = in.readUTF();
355 inMap.get().put(index, this);
356 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700357 value = prev.value;
Hans Boehm84614952014-11-25 18:46:17 -0800358 mExpr = prev.mExpr;
359 mContext = prev.mContext;
360 mShortRep = prev.mShortRep;
361 }
362 }
363 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700364 public CharSequence toCharSequence(Context context) {
Hans Boehm50ed3202015-06-09 14:35:49 -0700365 return KeyMaps.translateResult(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800366 }
367 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700368 public TokenKind kind() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700369 return TokenKind.PRE_EVAL;
370 }
Hans Boehm3666e632015-07-27 18:33:12 -0700371 public boolean hasEllipsis() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700372 return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
373 }
Hans Boehm84614952014-11-25 18:46:17 -0800374 }
375
Hans Boehm3666e632015-07-27 18:33:12 -0700376 /**
377 * Read token from in.
378 */
379 public static Token newToken(DataInput in) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800380 TokenKind kind = tokenKindValues[in.readByte()];
381 switch(kind) {
382 case CONSTANT:
383 return new Constant(in);
384 case OPERATOR:
385 return new Operator(in);
386 case PRE_EVAL:
387 return new PreEval(in);
388 default: throw new IOException("Bad save file format");
389 }
390 }
391
392 CalculatorExpr() {
393 mExpr = new ArrayList<Token>();
394 }
395
396 private CalculatorExpr(ArrayList<Token> expr) {
397 mExpr = expr;
398 }
399
Hans Boehm3666e632015-07-27 18:33:12 -0700400 /**
401 * Construct CalculatorExpr, by reading it from in.
402 */
Hans Boehm84614952014-11-25 18:46:17 -0800403 CalculatorExpr(DataInput in) throws IOException {
404 mExpr = new ArrayList<Token>();
405 int size = in.readInt();
406 for (int i = 0; i < size; ++i) {
407 mExpr.add(newToken(in));
408 }
409 }
410
Hans Boehm3666e632015-07-27 18:33:12 -0700411 /**
412 * Write this expression to out.
413 */
414 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800415 int size = mExpr.size();
416 out.writeInt(size);
417 for (int i = 0; i < size; ++i) {
418 mExpr.get(i).write(out);
419 }
420 }
421
Hans Boehm3666e632015-07-27 18:33:12 -0700422 /**
423 * Does this expression end with a numeric constant?
424 * As opposed to an operator or preevaluated expression.
425 */
Hans Boehm0b9806f2015-06-29 16:07:15 -0700426 boolean hasTrailingConstant() {
427 int s = mExpr.size();
428 if (s == 0) {
429 return false;
430 }
431 Token t = mExpr.get(s-1);
432 return t instanceof Constant;
433 }
434
Hans Boehm3666e632015-07-27 18:33:12 -0700435 /**
436 * Does this expression end with a binary operator?
437 */
Hans Boehm84614952014-11-25 18:46:17 -0800438 private boolean hasTrailingBinary() {
439 int s = mExpr.size();
440 if (s == 0) return false;
441 Token t = mExpr.get(s-1);
442 if (!(t instanceof Operator)) return false;
443 Operator o = (Operator)t;
Hans Boehm3666e632015-07-27 18:33:12 -0700444 return (KeyMaps.isBinary(o.id));
Hans Boehm84614952014-11-25 18:46:17 -0800445 }
446
Hans Boehm017de982015-06-10 17:46:03 -0700447 /**
448 * Append press of button with given id to expression.
449 * If the insertion would clearly result in a syntax error, either just return false
450 * and do nothing, or make an adjustment to avoid the problem. We do the latter only
451 * for unambiguous consecutive binary operators, in which case we delete the first
452 * operator.
453 */
Hans Boehm84614952014-11-25 18:46:17 -0800454 boolean add(int id) {
455 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700456 final int d = KeyMaps.digVal(id);
457 final boolean binary = KeyMaps.isBinary(id);
Hans Boehm017de982015-06-10 17:46:03 -0700458 Token lastTok = s == 0 ? null : mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700459 int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
Hans Boehm017de982015-06-10 17:46:03 -0700460 // Quietly replace a trailing binary operator with another one, unless the second
461 // operator is minus, in which case we just allow it as a unary minus.
462 if (binary && !KeyMaps.isPrefix(id)) {
463 if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
464 || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
465 return false;
466 }
467 while (hasTrailingBinary()) {
468 delete();
469 }
470 // s invalid and not used below.
Hans Boehm84614952014-11-25 18:46:17 -0800471 }
Hans Boehm3666e632015-07-27 18:33:12 -0700472 final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
Hans Boehm84614952014-11-25 18:46:17 -0800473 if (isConstPiece) {
Hans Boehm017de982015-06-10 17:46:03 -0700474 // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
Hans Boehm84614952014-11-25 18:46:17 -0800475 if (s == 0) {
476 mExpr.add(new Constant());
477 s++;
478 } else {
479 Token last = mExpr.get(s-1);
480 if(!(last instanceof Constant)) {
Hans Boehm017de982015-06-10 17:46:03 -0700481 if (last instanceof PreEval) {
482 // Add explicit multiplication to avoid confusing display.
483 mExpr.add(new Operator(R.id.op_mul));
484 s++;
Hans Boehm84614952014-11-25 18:46:17 -0800485 }
486 mExpr.add(new Constant());
487 s++;
488 }
489 }
490 return ((Constant)(mExpr.get(s-1))).add(id);
491 } else {
492 mExpr.add(new Operator(id));
493 return true;
494 }
495 }
496
Hans Boehm017de982015-06-10 17:46:03 -0700497 /**
Hans Boehm0b9806f2015-06-29 16:07:15 -0700498 * Add exponent to the constant at the end of the expression.
499 * Assumes there is a constant at the end of the expression.
500 */
501 void addExponent(int exp) {
502 Token lastTok = mExpr.get(mExpr.size() - 1);
503 ((Constant) lastTok).addExponent(exp);
504 }
505
506 /**
Hans Boehm017de982015-06-10 17:46:03 -0700507 * Remove trailing op_add and op_sub operators.
508 */
509 void removeTrailingAdditiveOperators() {
510 while (true) {
511 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700512 if (s == 0) {
513 break;
514 }
Hans Boehm017de982015-06-10 17:46:03 -0700515 Token lastTok = mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700516 if (!(lastTok instanceof Operator)) {
517 break;
518 }
519 int lastOp = ((Operator) lastTok).id;
520 if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
521 break;
522 }
Hans Boehm017de982015-06-10 17:46:03 -0700523 delete();
524 }
525 }
526
Hans Boehm3666e632015-07-27 18:33:12 -0700527 /**
528 * Append the contents of the argument expression.
529 * It is assumed that the argument expression will not change, and thus its pieces can be
530 * reused directly.
531 */
532 public void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700533 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800534 int s2 = expr2.mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700535 // Check that we're not concatenating Constant or PreEval tokens, since the result would
536 // look like a single constant, with very mysterious results for the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700537 if (s != 0 && s2 != 0) {
538 Token last = mExpr.get(s-1);
539 Token first = expr2.mExpr.get(0);
540 if (!(first instanceof Operator) && !(last instanceof Operator)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700541 // Fudge it by adding an explicit multiplication. We would have interpreted it as
542 // such anyway, and this makes it recognizable to the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700543 mExpr.add(new Operator(R.id.op_mul));
544 }
545 }
Hans Boehm84614952014-11-25 18:46:17 -0800546 for (int i = 0; i < s2; ++i) {
547 mExpr.add(expr2.mExpr.get(i));
548 }
549 }
550
Hans Boehm3666e632015-07-27 18:33:12 -0700551 /**
552 * Undo the last key addition, if any.
553 * Or possibly remove a trailing exponent digit.
554 */
555 public void delete() {
556 final int s = mExpr.size();
557 if (s == 0) {
558 return;
559 }
Hans Boehm84614952014-11-25 18:46:17 -0800560 Token last = mExpr.get(s-1);
561 if (last instanceof Constant) {
562 Constant c = (Constant)last;
563 c.delete();
Hans Boehm3666e632015-07-27 18:33:12 -0700564 if (!c.isEmpty()) {
565 return;
566 }
Hans Boehm84614952014-11-25 18:46:17 -0800567 }
568 mExpr.remove(s-1);
569 }
570
Hans Boehm3666e632015-07-27 18:33:12 -0700571 /**
572 * Remove all tokens from the expression.
573 */
574 public void clear() {
Hans Boehm84614952014-11-25 18:46:17 -0800575 mExpr.clear();
576 }
577
Hans Boehm3666e632015-07-27 18:33:12 -0700578 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800579 return mExpr.isEmpty();
580 }
581
Hans Boehm3666e632015-07-27 18:33:12 -0700582 /**
583 * Returns a logical deep copy of the CalculatorExpr.
584 * Operator and PreEval tokens are immutable, and thus aren't really copied.
585 */
Hans Boehm84614952014-11-25 18:46:17 -0800586 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700587 CalculatorExpr result = new CalculatorExpr();
Hans Boehm84614952014-11-25 18:46:17 -0800588 for (Token t: mExpr) {
589 if (t instanceof Constant) {
Hans Boehm3666e632015-07-27 18:33:12 -0700590 result.mExpr.add((Token)(((Constant)t).clone()));
Hans Boehm84614952014-11-25 18:46:17 -0800591 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700592 result.mExpr.add(t);
Hans Boehm84614952014-11-25 18:46:17 -0800593 }
594 }
Hans Boehm3666e632015-07-27 18:33:12 -0700595 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800596 }
597
598 // Am I just a constant?
Hans Boehm3666e632015-07-27 18:33:12 -0700599 public boolean isConstant() {
600 if (mExpr.size() != 1) {
601 return false;
602 }
Hans Boehm84614952014-11-25 18:46:17 -0800603 return mExpr.get(0) instanceof Constant;
604 }
605
Hans Boehm3666e632015-07-27 18:33:12 -0700606 /**
607 * Return a new expression consisting of a single token representing the current pre-evaluated
608 * expression.
609 * The caller supplies the value, degree mode, and short string representation, which must
610 * have been previously computed. Thus this is guaranteed to terminate reasonably quickly.
611 */
Hans Boehm995e5eb2016-02-08 11:03:01 -0800612 public CalculatorExpr abbreviate(UnifiedReal val, boolean dm, String sr) {
Hans Boehm84614952014-11-25 18:46:17 -0800613 CalculatorExpr result = new CalculatorExpr();
Justin Klaassen0ace4eb2016-02-05 11:38:12 -0800614 @SuppressWarnings("unchecked")
Hans Boehm995e5eb2016-02-08 11:03:01 -0800615 Token t = new PreEval(val, new CalculatorExpr((ArrayList<Token>) mExpr.clone()),
Hans Boehm3666e632015-07-27 18:33:12 -0700616 new EvalContext(dm, mExpr.size()), sr);
Hans Boehm84614952014-11-25 18:46:17 -0800617 result.mExpr.add(t);
618 return result;
619 }
620
Hans Boehm3666e632015-07-27 18:33:12 -0700621 /**
Hans Boehm995e5eb2016-02-08 11:03:01 -0800622 * Internal evaluation functions return an EvalRet pair.
Hans Boehm3666e632015-07-27 18:33:12 -0700623 * We compute rational (BoundedRational) results when possible, both as a performance
624 * optimization, and to detect errors exactly when we can.
625 */
626 private static class EvalRet {
627 public int pos; // Next position (expression index) to be parsed.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800628 public final UnifiedReal val; // Constructive Real result of evaluating subexpression.
629 EvalRet(int p, UnifiedReal v) {
Hans Boehm3666e632015-07-27 18:33:12 -0700630 pos = p;
631 val = v;
Hans Boehm84614952014-11-25 18:46:17 -0800632 }
633 }
634
Hans Boehm3666e632015-07-27 18:33:12 -0700635 /**
636 * Internal evaluation functions take an EvalContext argument.
637 */
Hans Boehm84614952014-11-25 18:46:17 -0800638 private static class EvalContext {
Hans Boehm3666e632015-07-27 18:33:12 -0700639 public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
Hans Boehmc023b732015-04-29 11:30:47 -0700640 public final boolean mDegreeMode;
Hans Boehm84614952014-11-25 18:46:17 -0800641 // If we add any other kinds of evaluation modes, they go here.
Hans Boehmc023b732015-04-29 11:30:47 -0700642 EvalContext(boolean degreeMode, int len) {
Hans Boehm84614952014-11-25 18:46:17 -0800643 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700644 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800645 }
Hans Boehmc023b732015-04-29 11:30:47 -0700646 EvalContext(DataInput in, int len) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800647 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700648 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800649 }
650 void write(DataOutput out) throws IOException {
651 out.writeBoolean(mDegreeMode);
652 }
653 }
654
Hans Boehm995e5eb2016-02-08 11:03:01 -0800655 private UnifiedReal toRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800656 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800657 return x.multiply(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800658 } else {
659 return x;
660 }
661 }
662
Hans Boehm995e5eb2016-02-08 11:03:01 -0800663 private UnifiedReal fromRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800664 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800665 return x.divide(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800666 } else {
667 return x;
668 }
669 }
670
Hans Boehm3666e632015-07-27 18:33:12 -0700671 // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
672 // error. We expect that to be caught in eval below.
Hans Boehm84614952014-11-25 18:46:17 -0800673
Hans Boehmc023b732015-04-29 11:30:47 -0700674 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800675 Token t = mExpr.get(i);
Hans Boehm3666e632015-07-27 18:33:12 -0700676 if (!(t instanceof Operator)) {
677 return false;
678 }
679 return ((Operator)(t)).id == op;
Hans Boehm84614952014-11-25 18:46:17 -0800680 }
681
Hans Boehmc023b732015-04-29 11:30:47 -0700682 private boolean isOperator(int i, int op, EvalContext ec) {
Hans Boehm3666e632015-07-27 18:33:12 -0700683 if (i >= ec.mPrefixLength) {
684 return false;
685 }
Hans Boehmc023b732015-04-29 11:30:47 -0700686 return isOperatorUnchecked(i, op);
687 }
688
Hans Boehm3666e632015-07-27 18:33:12 -0700689 public static class SyntaxException extends Exception {
Hans Boehmc023b732015-04-29 11:30:47 -0700690 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800691 super();
692 }
Hans Boehmc023b732015-04-29 11:30:47 -0700693 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800694 super(s);
695 }
696 }
697
Hans Boehm3666e632015-07-27 18:33:12 -0700698 // The following functions all evaluate some kind of expression starting at position i in
699 // mExpr in a specified evaluation context. They return both the expression value (as
700 // constructive real and, if applicable, as BoundedRational) and the position of the next token
Hans Boehm84614952014-11-25 18:46:17 -0800701 // that was not used as part of the evaluation.
Hans Boehm3666e632015-07-27 18:33:12 -0700702 // This is essentially a simple recursive descent parser combined with expression evaluation.
703
Hans Boehmc023b732015-04-29 11:30:47 -0700704 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700705 final Token t = mExpr.get(i);
Hans Boehm84614952014-11-25 18:46:17 -0800706 if (t instanceof Constant) {
707 Constant c = (Constant)t;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800708 return new EvalRet(i+1,new UnifiedReal(c.toRational()));
Hans Boehm84614952014-11-25 18:46:17 -0800709 }
710 if (t instanceof PreEval) {
Hans Boehm3666e632015-07-27 18:33:12 -0700711 final PreEval p = (PreEval)t;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800712 return new EvalRet(i+1, p.value);
Hans Boehm84614952014-11-25 18:46:17 -0800713 }
714 EvalRet argVal;
Hans Boehm3666e632015-07-27 18:33:12 -0700715 switch(((Operator)(t)).id) {
Hans Boehm84614952014-11-25 18:46:17 -0800716 case R.id.const_pi:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800717 return new EvalRet(i+1, UnifiedReal.PI);
Hans Boehm84614952014-11-25 18:46:17 -0800718 case R.id.const_e:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800719 return new EvalRet(i+1, UnifiedReal.E);
Hans Boehm84614952014-11-25 18:46:17 -0800720 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700721 // Seems to have highest precedence.
722 // Does not add implicit paren.
723 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700724 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700725 argVal = evalUnary(i+2, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800726 return new EvalRet(argVal.pos, argVal.val.negate().sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700727 } else {
728 argVal = evalUnary(i+1, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800729 return new EvalRet(argVal.pos, argVal.val.sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700730 }
Hans Boehm84614952014-11-25 18:46:17 -0800731 case R.id.lparen:
732 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700733 if (isOperator(argVal.pos, R.id.rparen, ec)) {
734 argVal.pos++;
735 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800736 return new EvalRet(argVal.pos, argVal.val);
Hans Boehm84614952014-11-25 18:46:17 -0800737 case R.id.fun_sin:
738 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700739 if (isOperator(argVal.pos, R.id.rparen, ec)) {
740 argVal.pos++;
741 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800742 return new EvalRet(argVal.pos, toRadians(argVal.val, ec).sin());
Hans Boehm84614952014-11-25 18:46:17 -0800743 case R.id.fun_cos:
744 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700745 if (isOperator(argVal.pos, R.id.rparen, ec)) {
746 argVal.pos++;
747 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800748 return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos());
Hans Boehm84614952014-11-25 18:46:17 -0800749 case R.id.fun_tan:
750 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700751 if (isOperator(argVal.pos, R.id.rparen, ec)) {
752 argVal.pos++;
753 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800754 UnifiedReal arg = toRadians(argVal.val, ec);
755 return new EvalRet(argVal.pos, arg.sin().divide(arg.cos()));
Hans Boehm84614952014-11-25 18:46:17 -0800756 case R.id.fun_ln:
757 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700758 if (isOperator(argVal.pos, R.id.rparen, ec)) {
759 argVal.pos++;
760 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800761 return new EvalRet(argVal.pos, argVal.val.ln());
Hans Boehm4db31b42015-05-31 12:19:05 -0700762 case R.id.fun_exp:
763 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700764 if (isOperator(argVal.pos, R.id.rparen, ec)) {
765 argVal.pos++;
766 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800767 return new EvalRet(argVal.pos, argVal.val.exp());
Hans Boehm84614952014-11-25 18:46:17 -0800768 case R.id.fun_log:
769 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700770 if (isOperator(argVal.pos, R.id.rparen, ec)) {
771 argVal.pos++;
772 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800773 return new EvalRet(argVal.pos, argVal.val.ln().divide(UnifiedReal.TEN.ln()));
Hans Boehm84614952014-11-25 18:46:17 -0800774 case R.id.fun_arcsin:
775 argVal = evalExpr(i+1, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700776 if (isOperator(argVal.pos, R.id.rparen, ec)) {
777 argVal.pos++;
778 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800779 return new EvalRet(argVal.pos, fromRadians(argVal.val.asin(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800780 case R.id.fun_arccos:
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, fromRadians(argVal.val.cos(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800786 case R.id.fun_arctan:
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, fromRadians(argVal.val.atan(),ec));
Hans Boehm84614952014-11-25 18:46:17 -0800792 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700793 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800794 }
Hans Boehm84614952014-11-25 18:46:17 -0800795 }
796
Hans Boehm995e5eb2016-02-08 11:03:01 -0800797 private static final UnifiedReal ONE_HUNDREDTH = new UnifiedReal(100).inverse();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700798
Hans Boehm4db31b42015-05-31 12:19:05 -0700799 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700800 final EvalRet tmp = evalUnary(i, ec);
801 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800802 UnifiedReal val = tmp.val;
803
Hans Boehm4db31b42015-05-31 12:19:05 -0700804 boolean isFact;
805 boolean isSquared = false;
806 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
807 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
808 isOperator(cpos, R.id.op_pct, ec)) {
809 if (isFact) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800810 val = val.fact();
Hans Boehm4db31b42015-05-31 12:19:05 -0700811 } else if (isSquared) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800812 val = val.multiply(val);
Hans Boehm4db31b42015-05-31 12:19:05 -0700813 } else /* percent */ {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800814 val = val.multiply(ONE_HUNDREDTH);
Hans Boehm84614952014-11-25 18:46:17 -0800815 }
Hans Boehm84614952014-11-25 18:46:17 -0800816 ++cpos;
817 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800818 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800819 }
820
Hans Boehmc023b732015-04-29 11:30:47 -0700821 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700822 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700823 int cpos = result1.pos; // current position
Hans Boehm995e5eb2016-02-08 11:03:01 -0800824 UnifiedReal val = result1.val; // value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700825 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700826 final EvalRet exp = evalSignedFactor(cpos + 1, ec);
827 cpos = exp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800828 val = val.pow(exp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800829 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800830 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800831 }
832
Hans Boehmc023b732015-04-29 11:30:47 -0700833 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
834 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700835 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800836 EvalRet tmp = evalFactor(cpos, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700837 cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800838 final UnifiedReal result = negative ? tmp.val.negate() : tmp.val;
839 return new EvalRet(cpos, result);
Hans Boehm84614952014-11-25 18:46:17 -0800840 }
841
842 private boolean canStartFactor(int i) {
843 if (i >= mExpr.size()) return false;
844 Token t = mExpr.get(i);
845 if (!(t instanceof Operator)) return true;
Hans Boehm3666e632015-07-27 18:33:12 -0700846 int id = ((Operator)(t)).id;
Hans Boehm84614952014-11-25 18:46:17 -0800847 if (KeyMaps.isBinary(id)) return false;
848 switch (id) {
849 case R.id.op_fact:
850 case R.id.rparen:
851 return false;
852 default:
853 return true;
854 }
855 }
856
Hans Boehmc023b732015-04-29 11:30:47 -0700857 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800858 EvalRet tmp = evalSignedFactor(i, ec);
859 boolean is_mul = false;
860 boolean is_div = false;
Hans Boehm3666e632015-07-27 18:33:12 -0700861 int cpos = tmp.pos; // Current position in expression.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800862 UnifiedReal val = tmp.val; // Current value.
Hans Boehmc023b732015-04-29 11:30:47 -0700863 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
864 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -0800865 || canStartFactor(cpos)) {
866 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700867 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800868 if (is_div) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800869 val = val.divide(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800870 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800871 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800872 }
Hans Boehm3666e632015-07-27 18:33:12 -0700873 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800874 is_mul = is_div = false;
875 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800876 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800877 }
878
Hans Boehm8afd0f82015-07-30 17:00:33 -0700879 /**
880 * Is the subexpression starting at pos a simple percent constant?
881 * This is used to recognize exppressions like 200+10%, which we handle specially.
882 * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
883 * by either nothing or an additive operator.
884 * Note that we are intentionally far more restrictive in recognizing such expressions than
885 * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
886 * When in doubt, we fall back to the the naive interpretation of % as 1/100.
887 * Note that 100+(10)% yields 100.1 while 100+10% yields 110. This may be controversial,
888 * but is consistent with Google web search.
889 */
890 private boolean isPercent(int pos) {
891 if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
892 return false;
893 }
894 Token number = mExpr.get(pos);
895 if (number instanceof Operator) {
896 return false;
897 }
898 if (mExpr.size() == pos + 2) {
899 return true;
900 }
901 if (!(mExpr.get(pos + 2) instanceof Operator)) {
902 return false;
903 }
904 Operator op = (Operator) mExpr.get(pos + 2);
905 return op.id == R.id.op_add || op.id == R.id.op_sub;
906 }
907
908 /**
909 * Compute the multiplicative factor corresponding to an N% addition or subtraction.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800910 * @param pos position of Constant or PreEval expression token corresponding to N.
911 * @param isSubtraction this is a subtraction, as opposed to addition.
912 * @param ec usable evaluation contex; only length matters.
913 * @return UnifiedReal value and position, which is pos + 2, i.e. after percent sign
Hans Boehm8afd0f82015-07-30 17:00:33 -0700914 */
915 private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
916 throws SyntaxException {
917 EvalRet tmp = evalUnary(pos, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800918 UnifiedReal val = isSubtraction ? tmp.val.negate() : tmp.val;
919 val = UnifiedReal.ONE.add(val.multiply(ONE_HUNDREDTH));
920 return new EvalRet(pos + 2 /* after percent sign */, val);
Hans Boehm8afd0f82015-07-30 17:00:33 -0700921 }
922
Hans Boehmc023b732015-04-29 11:30:47 -0700923 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800924 EvalRet tmp = evalTerm(i, ec);
925 boolean is_plus;
Hans Boehm3666e632015-07-27 18:33:12 -0700926 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800927 UnifiedReal val = tmp.val;
Hans Boehmc023b732015-04-29 11:30:47 -0700928 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
929 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700930 if (isPercent(cpos + 1)) {
931 tmp = getPercentFactor(cpos + 1, !is_plus, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800932 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800933 } else {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700934 tmp = evalTerm(cpos + 1, ec);
935 if (is_plus) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800936 val = val.add(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700937 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800938 val = val.subtract(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800939 }
940 }
Hans Boehm3666e632015-07-27 18:33:12 -0700941 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800942 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800943 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800944 }
945
Hans Boehme8553762015-06-26 17:56:52 -0700946 /**
947 * Return the starting position of the sequence of trailing binary operators.
948 */
949 private int trailingBinaryOpsStart() {
Hans Boehmc023b732015-04-29 11:30:47 -0700950 int result = mExpr.size();
951 while (result > 0) {
952 Token last = mExpr.get(result - 1);
953 if (!(last instanceof Operator)) break;
954 Operator o = (Operator)last;
Hans Boehm3666e632015-07-27 18:33:12 -0700955 if (!KeyMaps.isBinary(o.id)) break;
Hans Boehmc023b732015-04-29 11:30:47 -0700956 --result;
957 }
958 return result;
959 }
960
Hans Boehm3666e632015-07-27 18:33:12 -0700961 /**
962 * Is the current expression worth evaluating?
963 */
Hans Boehmc023b732015-04-29 11:30:47 -0700964 public boolean hasInterestingOps() {
Hans Boehme8553762015-06-26 17:56:52 -0700965 int last = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -0700966 int first = 0;
967 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
968 // Leading minus is not by itself interesting.
969 first++;
970 }
971 for (int i = first; i < last; ++i) {
972 Token t1 = mExpr.get(i);
Hans Boehm187d3e92015-06-09 18:04:26 -0700973 if (t1 instanceof Operator
974 || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
975 return true;
976 }
Hans Boehmc023b732015-04-29 11:30:47 -0700977 }
978 return false;
979 }
980
Hans Boehme8553762015-06-26 17:56:52 -0700981 /**
982 * Evaluate the expression excluding trailing binary operators.
Hans Boehm3666e632015-07-27 18:33:12 -0700983 * Errors result in exceptions, most of which are unchecked. Should not be called
984 * concurrently with modification of the expression. May take a very long time; avoid calling
985 * from UI thread.
Hans Boehme8553762015-06-26 17:56:52 -0700986 *
987 * @param degreeMode use degrees rather than radians
988 */
Hans Boehm995e5eb2016-02-08 11:03:01 -0800989 UnifiedReal eval(boolean degreeMode) throws SyntaxException
990 // And unchecked exceptions thrown by UnifiedReal, CR,
Hans Boehmc023b732015-04-29 11:30:47 -0700991 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -0800992 {
993 try {
Hans Boehm3666e632015-07-27 18:33:12 -0700994 // We currently never include trailing binary operators, but include other trailing
995 // operators. Thus we usually, but not always, display results for prefixes of valid
996 // expressions, and don't generate an error where we previously displayed an instant
997 // result. This reflects the Android L design.
Hans Boehme8553762015-06-26 17:56:52 -0700998 int prefixLen = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -0700999 EvalContext ec = new EvalContext(degreeMode, prefixLen);
Hans Boehm84614952014-11-25 18:46:17 -08001000 EvalRet res = evalExpr(0, ec);
Hans Boehm3666e632015-07-27 18:33:12 -07001001 if (res.pos != prefixLen) {
Hans Boehmc023b732015-04-29 11:30:47 -07001002 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -07001003 }
Hans Boehm995e5eb2016-02-08 11:03:01 -08001004 return res.val;
Hans Boehm84614952014-11-25 18:46:17 -08001005 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -07001006 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -08001007 }
1008 }
1009
1010 // Produce a string representation of the expression itself
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001011 SpannableStringBuilder toSpannableStringBuilder(Context context) {
1012 SpannableStringBuilder ssb = new SpannableStringBuilder();
Hans Boehm84614952014-11-25 18:46:17 -08001013 for (Token t: mExpr) {
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001014 ssb.append(t.toCharSequence(context));
Hans Boehm84614952014-11-25 18:46:17 -08001015 }
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001016 return ssb;
Hans Boehm84614952014-11-25 18:46:17 -08001017 }
1018}