blob: 41dfe13b83f1d8f85db12ac56c1a1274514654dc [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.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700201 * We do add digit grouping separators to the whole number, even if not typed.
Hans Boehm3666e632015-07-27 18:33:12 -0700202 * Result is internationalized.
203 */
Hans Boehm84614952014-11-25 18:46:17 -0800204 @Override
205 public String toString() {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700206 String result;
207 if (mExponent != 0) {
208 result = mWhole;
209 } else {
210 result = StringUtils.addCommas(mWhole, 0, mWhole.length());
211 }
Hans Boehm84614952014-11-25 18:46:17 -0800212 if (mSawDecimal) {
Hans Boehm013969e2015-04-13 20:29:47 -0700213 result += '.';
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700214 result += mFraction;
215 }
Hans Boehm0b9806f2015-06-29 16:07:15 -0700216 if (mExponent != 0) {
217 result += "E" + mExponent;
218 }
Hans Boehm013969e2015-04-13 20:29:47 -0700219 return KeyMaps.translateResult(result);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700220 }
221
Hans Boehm3666e632015-07-27 18:33:12 -0700222 /**
Hans Boehmfa5203c2015-08-17 16:14:52 -0700223 * Return BoundedRational representation of constant, if well-formed.
224 * Result is never null.
Hans Boehm3666e632015-07-27 18:33:12 -0700225 */
Hans Boehmfa5203c2015-08-17 16:14:52 -0700226 public BoundedRational toRational() throws SyntaxException {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700227 String whole = mWhole;
Hans Boehmfa5203c2015-08-17 16:14:52 -0700228 if (whole.isEmpty()) {
229 if (mFraction.isEmpty()) {
230 // Decimal point without digits.
231 throw new SyntaxException();
232 } else {
233 whole = "0";
234 }
235 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700236 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700237 BigInteger den = BigInteger.TEN.pow(mFraction.length());
Hans Boehm0b9806f2015-06-29 16:07:15 -0700238 if (mExponent > 0) {
239 num = num.multiply(BigInteger.TEN.pow(mExponent));
240 }
241 if (mExponent < 0) {
242 den = den.multiply(BigInteger.TEN.pow(-mExponent));
243 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700244 return new BoundedRational(num, den);
245 }
246
Hans Boehm84614952014-11-25 18:46:17 -0800247 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700248 public CharSequence toCharSequence(Context context) {
Hans Boehm84614952014-11-25 18:46:17 -0800249 return toString();
250 }
251
252 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700253 public TokenKind kind() {
254 return TokenKind.CONSTANT;
255 }
Hans Boehm84614952014-11-25 18:46:17 -0800256
257 // Override clone to make it public
258 @Override
259 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700260 Constant result = new Constant();
261 result.mWhole = mWhole;
262 result.mFraction = mFraction;
263 result.mSawDecimal = mSawDecimal;
264 result.mExponent = mExponent;
265 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800266 }
267 }
268
Hans Boehm3666e632015-07-27 18:33:12 -0700269 // Hash maps used to detect duplicate subexpressions when we write out CalculatorExprs and
270 // read them back in.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800271 private static final ThreadLocal<IdentityHashMap<UnifiedReal, Integer>>outMap =
272 new ThreadLocal<IdentityHashMap<UnifiedReal, Integer>>();
Hans Boehm84614952014-11-25 18:46:17 -0800273 // Maps expressions to indices on output
Hans Boehm995e5eb2016-02-08 11:03:01 -0800274 private static final ThreadLocal<HashMap<Integer, PreEval>>inMap =
275 new ThreadLocal<HashMap<Integer, PreEval>>();
Hans Boehm84614952014-11-25 18:46:17 -0800276 // Maps expressions to indices on output
Hans Boehm3666e632015-07-27 18:33:12 -0700277 private static final ThreadLocal<Integer> exprIndex = new ThreadLocal<Integer>();
Hans Boehm84614952014-11-25 18:46:17 -0800278
Hans Boehm3666e632015-07-27 18:33:12 -0700279 /**
280 * Prepare for expression output.
281 * Initializes map that will lbe used to avoid duplicating shared subexpressions.
282 * This avoids a potential exponential blow-up in the expression size.
283 */
284 public static void initExprOutput() {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800285 outMap.set(new IdentityHashMap<UnifiedReal, Integer>());
Hans Boehm84614952014-11-25 18:46:17 -0800286 exprIndex.set(Integer.valueOf(0));
287 }
288
Hans Boehm3666e632015-07-27 18:33:12 -0700289 /**
290 * Prepare for expression input.
291 * Initializes map that will be used to reconstruct shared subexpressions.
292 */
293 public static void initExprInput() {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800294 inMap.set(new HashMap<Integer, PreEval>());
Hans Boehm84614952014-11-25 18:46:17 -0800295 }
296
Hans Boehm3666e632015-07-27 18:33:12 -0700297 /**
298 * The "token" class for previously evaluated subexpressions.
299 * We treat previously evaluated subexpressions as tokens. These are inserted when we either
300 * continue an expression after evaluating some of it, or copy an expression and paste it back
301 * in.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800302 * The representation includes a UnifiedReal value. In order to
Hans Boehm3666e632015-07-27 18:33:12 -0700303 * support saving and restoring, we also include the underlying expression itself, and the
304 * context (currently just degree mode) used to evaluate it. The short string representation
305 * is also stored in order to avoid potentially expensive recomputation in the UI thread.
306 */
Hans Boehm84614952014-11-25 18:46:17 -0800307 private static class PreEval extends Token {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800308 public final UnifiedReal value;
Hans Boehm84614952014-11-25 18:46:17 -0800309 private final CalculatorExpr mExpr;
310 private final EvalContext mContext;
Hans Boehm50ed3202015-06-09 14:35:49 -0700311 private final String mShortRep; // Not internationalized.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800312 PreEval(UnifiedReal val, CalculatorExpr expr, EvalContext ec, String shortRep) {
Hans Boehm3666e632015-07-27 18:33:12 -0700313 value = val;
Hans Boehm84614952014-11-25 18:46:17 -0800314 mExpr = expr;
315 mContext = ec;
316 mShortRep = shortRep;
317 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800318 // In writing out PreEvals, we are careful to avoid writing out duplicates. We conclude
319 // that two expressions are duplicates if they have the same UnifiedReal value. This
320 // avoids a potential exponential blow up in certain off cases and redundant evaluation
321 // after reading them back in. The parameter hash map maps expressions we've seen
Hans Boehm84614952014-11-25 18:46:17 -0800322 // before to their index.
323 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700324 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800325 out.writeByte(TokenKind.PRE_EVAL.ordinal());
Hans Boehm3666e632015-07-27 18:33:12 -0700326 Integer index = outMap.get().get(value);
Hans Boehm84614952014-11-25 18:46:17 -0800327 if (index == null) {
328 int nextIndex = exprIndex.get() + 1;
329 exprIndex.set(nextIndex);
Hans Boehm3666e632015-07-27 18:33:12 -0700330 outMap.get().put(value, nextIndex);
Hans Boehm84614952014-11-25 18:46:17 -0800331 out.writeInt(nextIndex);
332 mExpr.write(out);
333 mContext.write(out);
334 out.writeUTF(mShortRep);
335 } else {
336 // Just write out the index
337 out.writeInt(index);
338 }
339 }
340 PreEval(DataInput in) throws IOException {
341 int index = in.readInt();
342 PreEval prev = inMap.get().get(index);
343 if (prev == null) {
344 mExpr = new CalculatorExpr(in);
Hans Boehmc023b732015-04-29 11:30:47 -0700345 mContext = new EvalContext(in, mExpr.mExpr.size());
Hans Boehm3666e632015-07-27 18:33:12 -0700346 // Recompute other fields We currently do this in the UI thread, but we only
347 // create PreEval expressions that were previously successfully evaluated, and
348 // thus don't diverge. We also only evaluate to a constructive real, which
349 // involves substantial work only in fairly contrived circumstances.
Hans Boehm84614952014-11-25 18:46:17 -0800350 // TODO: Deal better with slow evaluations.
Hans Boehmc023b732015-04-29 11:30:47 -0700351 EvalRet res = null;
352 try {
353 res = mExpr.evalExpr(0, mContext);
354 } catch (SyntaxException e) {
355 // Should be impossible, since we only write out
356 // expressions that can be evaluated.
357 Log.e("Calculator", "Unexpected syntax exception" + e);
358 }
Hans Boehm3666e632015-07-27 18:33:12 -0700359 value = res.val;
Hans Boehm84614952014-11-25 18:46:17 -0800360 mShortRep = in.readUTF();
361 inMap.get().put(index, this);
362 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700363 value = prev.value;
Hans Boehm84614952014-11-25 18:46:17 -0800364 mExpr = prev.mExpr;
365 mContext = prev.mContext;
366 mShortRep = prev.mShortRep;
367 }
368 }
369 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700370 public CharSequence toCharSequence(Context context) {
Hans Boehm50ed3202015-06-09 14:35:49 -0700371 return KeyMaps.translateResult(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800372 }
373 @Override
Hans Boehm3666e632015-07-27 18:33:12 -0700374 public TokenKind kind() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700375 return TokenKind.PRE_EVAL;
376 }
Hans Boehm3666e632015-07-27 18:33:12 -0700377 public boolean hasEllipsis() {
Hans Boehm187d3e92015-06-09 18:04:26 -0700378 return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
379 }
Hans Boehm84614952014-11-25 18:46:17 -0800380 }
381
Hans Boehm3666e632015-07-27 18:33:12 -0700382 /**
383 * Read token from in.
384 */
385 public static Token newToken(DataInput in) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800386 TokenKind kind = tokenKindValues[in.readByte()];
387 switch(kind) {
388 case CONSTANT:
389 return new Constant(in);
390 case OPERATOR:
391 return new Operator(in);
392 case PRE_EVAL:
393 return new PreEval(in);
394 default: throw new IOException("Bad save file format");
395 }
396 }
397
398 CalculatorExpr() {
399 mExpr = new ArrayList<Token>();
400 }
401
402 private CalculatorExpr(ArrayList<Token> expr) {
403 mExpr = expr;
404 }
405
Hans Boehm3666e632015-07-27 18:33:12 -0700406 /**
407 * Construct CalculatorExpr, by reading it from in.
408 */
Hans Boehm84614952014-11-25 18:46:17 -0800409 CalculatorExpr(DataInput in) throws IOException {
410 mExpr = new ArrayList<Token>();
411 int size = in.readInt();
412 for (int i = 0; i < size; ++i) {
413 mExpr.add(newToken(in));
414 }
415 }
416
Hans Boehm3666e632015-07-27 18:33:12 -0700417 /**
418 * Write this expression to out.
419 */
420 public void write(DataOutput out) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800421 int size = mExpr.size();
422 out.writeInt(size);
423 for (int i = 0; i < size; ++i) {
424 mExpr.get(i).write(out);
425 }
426 }
427
Hans Boehm3666e632015-07-27 18:33:12 -0700428 /**
429 * Does this expression end with a numeric constant?
430 * As opposed to an operator or preevaluated expression.
431 */
Hans Boehm0b9806f2015-06-29 16:07:15 -0700432 boolean hasTrailingConstant() {
433 int s = mExpr.size();
434 if (s == 0) {
435 return false;
436 }
437 Token t = mExpr.get(s-1);
438 return t instanceof Constant;
439 }
440
Hans Boehm3666e632015-07-27 18:33:12 -0700441 /**
442 * Does this expression end with a binary operator?
443 */
Hans Boehm84614952014-11-25 18:46:17 -0800444 private boolean hasTrailingBinary() {
445 int s = mExpr.size();
446 if (s == 0) return false;
447 Token t = mExpr.get(s-1);
448 if (!(t instanceof Operator)) return false;
449 Operator o = (Operator)t;
Hans Boehm3666e632015-07-27 18:33:12 -0700450 return (KeyMaps.isBinary(o.id));
Hans Boehm84614952014-11-25 18:46:17 -0800451 }
452
Hans Boehm017de982015-06-10 17:46:03 -0700453 /**
454 * Append press of button with given id to expression.
455 * If the insertion would clearly result in a syntax error, either just return false
456 * and do nothing, or make an adjustment to avoid the problem. We do the latter only
457 * for unambiguous consecutive binary operators, in which case we delete the first
458 * operator.
459 */
Hans Boehm84614952014-11-25 18:46:17 -0800460 boolean add(int id) {
461 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700462 final int d = KeyMaps.digVal(id);
463 final boolean binary = KeyMaps.isBinary(id);
Hans Boehm017de982015-06-10 17:46:03 -0700464 Token lastTok = s == 0 ? null : mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700465 int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
Hans Boehm017de982015-06-10 17:46:03 -0700466 // Quietly replace a trailing binary operator with another one, unless the second
467 // operator is minus, in which case we just allow it as a unary minus.
468 if (binary && !KeyMaps.isPrefix(id)) {
469 if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
470 || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
471 return false;
472 }
473 while (hasTrailingBinary()) {
474 delete();
475 }
476 // s invalid and not used below.
Hans Boehm84614952014-11-25 18:46:17 -0800477 }
Hans Boehm3666e632015-07-27 18:33:12 -0700478 final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
Hans Boehm84614952014-11-25 18:46:17 -0800479 if (isConstPiece) {
Hans Boehm017de982015-06-10 17:46:03 -0700480 // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
Hans Boehm84614952014-11-25 18:46:17 -0800481 if (s == 0) {
482 mExpr.add(new Constant());
483 s++;
484 } else {
485 Token last = mExpr.get(s-1);
486 if(!(last instanceof Constant)) {
Hans Boehm017de982015-06-10 17:46:03 -0700487 if (last instanceof PreEval) {
488 // Add explicit multiplication to avoid confusing display.
489 mExpr.add(new Operator(R.id.op_mul));
490 s++;
Hans Boehm84614952014-11-25 18:46:17 -0800491 }
492 mExpr.add(new Constant());
493 s++;
494 }
495 }
496 return ((Constant)(mExpr.get(s-1))).add(id);
497 } else {
498 mExpr.add(new Operator(id));
499 return true;
500 }
501 }
502
Hans Boehm017de982015-06-10 17:46:03 -0700503 /**
Hans Boehm0b9806f2015-06-29 16:07:15 -0700504 * Add exponent to the constant at the end of the expression.
505 * Assumes there is a constant at the end of the expression.
506 */
507 void addExponent(int exp) {
508 Token lastTok = mExpr.get(mExpr.size() - 1);
509 ((Constant) lastTok).addExponent(exp);
510 }
511
512 /**
Hans Boehm017de982015-06-10 17:46:03 -0700513 * Remove trailing op_add and op_sub operators.
514 */
515 void removeTrailingAdditiveOperators() {
516 while (true) {
517 int s = mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700518 if (s == 0) {
519 break;
520 }
Hans Boehm017de982015-06-10 17:46:03 -0700521 Token lastTok = mExpr.get(s-1);
Hans Boehm3666e632015-07-27 18:33:12 -0700522 if (!(lastTok instanceof Operator)) {
523 break;
524 }
525 int lastOp = ((Operator) lastTok).id;
526 if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
527 break;
528 }
Hans Boehm017de982015-06-10 17:46:03 -0700529 delete();
530 }
531 }
532
Hans Boehm3666e632015-07-27 18:33:12 -0700533 /**
534 * Append the contents of the argument expression.
535 * It is assumed that the argument expression will not change, and thus its pieces can be
536 * reused directly.
537 */
538 public void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700539 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800540 int s2 = expr2.mExpr.size();
Hans Boehm3666e632015-07-27 18:33:12 -0700541 // Check that we're not concatenating Constant or PreEval tokens, since the result would
542 // look like a single constant, with very mysterious results for the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700543 if (s != 0 && s2 != 0) {
544 Token last = mExpr.get(s-1);
545 Token first = expr2.mExpr.get(0);
546 if (!(first instanceof Operator) && !(last instanceof Operator)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700547 // Fudge it by adding an explicit multiplication. We would have interpreted it as
548 // such anyway, and this makes it recognizable to the user.
Hans Boehmfbcef702015-04-27 18:07:47 -0700549 mExpr.add(new Operator(R.id.op_mul));
550 }
551 }
Hans Boehm84614952014-11-25 18:46:17 -0800552 for (int i = 0; i < s2; ++i) {
553 mExpr.add(expr2.mExpr.get(i));
554 }
555 }
556
Hans Boehm3666e632015-07-27 18:33:12 -0700557 /**
558 * Undo the last key addition, if any.
559 * Or possibly remove a trailing exponent digit.
560 */
561 public void delete() {
562 final int s = mExpr.size();
563 if (s == 0) {
564 return;
565 }
Hans Boehm84614952014-11-25 18:46:17 -0800566 Token last = mExpr.get(s-1);
567 if (last instanceof Constant) {
568 Constant c = (Constant)last;
569 c.delete();
Hans Boehm3666e632015-07-27 18:33:12 -0700570 if (!c.isEmpty()) {
571 return;
572 }
Hans Boehm84614952014-11-25 18:46:17 -0800573 }
574 mExpr.remove(s-1);
575 }
576
Hans Boehm3666e632015-07-27 18:33:12 -0700577 /**
578 * Remove all tokens from the expression.
579 */
580 public void clear() {
Hans Boehm84614952014-11-25 18:46:17 -0800581 mExpr.clear();
582 }
583
Hans Boehm3666e632015-07-27 18:33:12 -0700584 public boolean isEmpty() {
Hans Boehm84614952014-11-25 18:46:17 -0800585 return mExpr.isEmpty();
586 }
587
Hans Boehm3666e632015-07-27 18:33:12 -0700588 /**
589 * Returns a logical deep copy of the CalculatorExpr.
590 * Operator and PreEval tokens are immutable, and thus aren't really copied.
591 */
Hans Boehm84614952014-11-25 18:46:17 -0800592 public Object clone() {
Hans Boehm3666e632015-07-27 18:33:12 -0700593 CalculatorExpr result = new CalculatorExpr();
Hans Boehm84614952014-11-25 18:46:17 -0800594 for (Token t: mExpr) {
595 if (t instanceof Constant) {
Hans Boehm3666e632015-07-27 18:33:12 -0700596 result.mExpr.add((Token)(((Constant)t).clone()));
Hans Boehm84614952014-11-25 18:46:17 -0800597 } else {
Hans Boehm3666e632015-07-27 18:33:12 -0700598 result.mExpr.add(t);
Hans Boehm84614952014-11-25 18:46:17 -0800599 }
600 }
Hans Boehm3666e632015-07-27 18:33:12 -0700601 return result;
Hans Boehm84614952014-11-25 18:46:17 -0800602 }
603
604 // Am I just a constant?
Hans Boehm3666e632015-07-27 18:33:12 -0700605 public boolean isConstant() {
606 if (mExpr.size() != 1) {
607 return false;
608 }
Hans Boehm84614952014-11-25 18:46:17 -0800609 return mExpr.get(0) instanceof Constant;
610 }
611
Hans Boehm3666e632015-07-27 18:33:12 -0700612 /**
613 * Return a new expression consisting of a single token representing the current pre-evaluated
614 * expression.
615 * The caller supplies the value, degree mode, and short string representation, which must
616 * have been previously computed. Thus this is guaranteed to terminate reasonably quickly.
617 */
Hans Boehm995e5eb2016-02-08 11:03:01 -0800618 public CalculatorExpr abbreviate(UnifiedReal val, boolean dm, String sr) {
Hans Boehm84614952014-11-25 18:46:17 -0800619 CalculatorExpr result = new CalculatorExpr();
Justin Klaassen0ace4eb2016-02-05 11:38:12 -0800620 @SuppressWarnings("unchecked")
Hans Boehm995e5eb2016-02-08 11:03:01 -0800621 Token t = new PreEval(val, new CalculatorExpr((ArrayList<Token>) mExpr.clone()),
Hans Boehm3666e632015-07-27 18:33:12 -0700622 new EvalContext(dm, mExpr.size()), sr);
Hans Boehm84614952014-11-25 18:46:17 -0800623 result.mExpr.add(t);
624 return result;
625 }
626
Hans Boehm3666e632015-07-27 18:33:12 -0700627 /**
Hans Boehm995e5eb2016-02-08 11:03:01 -0800628 * Internal evaluation functions return an EvalRet pair.
Hans Boehm3666e632015-07-27 18:33:12 -0700629 * We compute rational (BoundedRational) results when possible, both as a performance
630 * optimization, and to detect errors exactly when we can.
631 */
632 private static class EvalRet {
633 public int pos; // Next position (expression index) to be parsed.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800634 public final UnifiedReal val; // Constructive Real result of evaluating subexpression.
635 EvalRet(int p, UnifiedReal v) {
Hans Boehm3666e632015-07-27 18:33:12 -0700636 pos = p;
637 val = v;
Hans Boehm84614952014-11-25 18:46:17 -0800638 }
639 }
640
Hans Boehm3666e632015-07-27 18:33:12 -0700641 /**
642 * Internal evaluation functions take an EvalContext argument.
643 */
Hans Boehm84614952014-11-25 18:46:17 -0800644 private static class EvalContext {
Hans Boehm3666e632015-07-27 18:33:12 -0700645 public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
Hans Boehmc023b732015-04-29 11:30:47 -0700646 public final boolean mDegreeMode;
Hans Boehm84614952014-11-25 18:46:17 -0800647 // If we add any other kinds of evaluation modes, they go here.
Hans Boehmc023b732015-04-29 11:30:47 -0700648 EvalContext(boolean degreeMode, int len) {
Hans Boehm84614952014-11-25 18:46:17 -0800649 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700650 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800651 }
Hans Boehmc023b732015-04-29 11:30:47 -0700652 EvalContext(DataInput in, int len) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800653 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700654 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800655 }
656 void write(DataOutput out) throws IOException {
657 out.writeBoolean(mDegreeMode);
658 }
659 }
660
Hans Boehm995e5eb2016-02-08 11:03:01 -0800661 private UnifiedReal toRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800662 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800663 return x.multiply(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800664 } else {
665 return x;
666 }
667 }
668
Hans Boehm995e5eb2016-02-08 11:03:01 -0800669 private UnifiedReal fromRadians(UnifiedReal x, EvalContext ec) {
Hans Boehm84614952014-11-25 18:46:17 -0800670 if (ec.mDegreeMode) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800671 return x.divide(UnifiedReal.RADIANS_PER_DEGREE);
Hans Boehm84614952014-11-25 18:46:17 -0800672 } else {
673 return x;
674 }
675 }
676
Hans Boehm3666e632015-07-27 18:33:12 -0700677 // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
678 // error. We expect that to be caught in eval below.
Hans Boehm84614952014-11-25 18:46:17 -0800679
Hans Boehmc023b732015-04-29 11:30:47 -0700680 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800681 Token t = mExpr.get(i);
Hans Boehm3666e632015-07-27 18:33:12 -0700682 if (!(t instanceof Operator)) {
683 return false;
684 }
685 return ((Operator)(t)).id == op;
Hans Boehm84614952014-11-25 18:46:17 -0800686 }
687
Hans Boehmc023b732015-04-29 11:30:47 -0700688 private boolean isOperator(int i, int op, EvalContext ec) {
Hans Boehm3666e632015-07-27 18:33:12 -0700689 if (i >= ec.mPrefixLength) {
690 return false;
691 }
Hans Boehmc023b732015-04-29 11:30:47 -0700692 return isOperatorUnchecked(i, op);
693 }
694
Hans Boehm3666e632015-07-27 18:33:12 -0700695 public static class SyntaxException extends Exception {
Hans Boehmc023b732015-04-29 11:30:47 -0700696 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800697 super();
698 }
Hans Boehmc023b732015-04-29 11:30:47 -0700699 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800700 super(s);
701 }
702 }
703
Hans Boehm3666e632015-07-27 18:33:12 -0700704 // The following functions all evaluate some kind of expression starting at position i in
705 // mExpr in a specified evaluation context. They return both the expression value (as
706 // constructive real and, if applicable, as BoundedRational) and the position of the next token
Hans Boehm84614952014-11-25 18:46:17 -0800707 // that was not used as part of the evaluation.
Hans Boehm3666e632015-07-27 18:33:12 -0700708 // This is essentially a simple recursive descent parser combined with expression evaluation.
709
Hans Boehmc023b732015-04-29 11:30:47 -0700710 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700711 final Token t = mExpr.get(i);
Hans Boehm84614952014-11-25 18:46:17 -0800712 if (t instanceof Constant) {
713 Constant c = (Constant)t;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800714 return new EvalRet(i+1,new UnifiedReal(c.toRational()));
Hans Boehm84614952014-11-25 18:46:17 -0800715 }
716 if (t instanceof PreEval) {
Hans Boehm3666e632015-07-27 18:33:12 -0700717 final PreEval p = (PreEval)t;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800718 return new EvalRet(i+1, p.value);
Hans Boehm84614952014-11-25 18:46:17 -0800719 }
720 EvalRet argVal;
Hans Boehm3666e632015-07-27 18:33:12 -0700721 switch(((Operator)(t)).id) {
Hans Boehm84614952014-11-25 18:46:17 -0800722 case R.id.const_pi:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800723 return new EvalRet(i+1, UnifiedReal.PI);
Hans Boehm84614952014-11-25 18:46:17 -0800724 case R.id.const_e:
Hans Boehm995e5eb2016-02-08 11:03:01 -0800725 return new EvalRet(i+1, UnifiedReal.E);
Hans Boehm84614952014-11-25 18:46:17 -0800726 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700727 // Seems to have highest precedence.
728 // Does not add implicit paren.
729 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700730 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700731 argVal = evalUnary(i+2, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800732 return new EvalRet(argVal.pos, argVal.val.negate().sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700733 } else {
734 argVal = evalUnary(i+1, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800735 return new EvalRet(argVal.pos, argVal.val.sqrt());
Hans Boehmfbcef702015-04-27 18:07:47 -0700736 }
Hans Boehm84614952014-11-25 18:46:17 -0800737 case R.id.lparen:
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, argVal.val);
Hans Boehm84614952014-11-25 18:46:17 -0800743 case R.id.fun_sin:
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).sin());
Hans Boehm84614952014-11-25 18:46:17 -0800749 case R.id.fun_cos:
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 return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos());
Hans Boehm84614952014-11-25 18:46:17 -0800755 case R.id.fun_tan:
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 UnifiedReal arg = toRadians(argVal.val, ec);
761 return new EvalRet(argVal.pos, arg.sin().divide(arg.cos()));
Hans Boehm84614952014-11-25 18:46:17 -0800762 case R.id.fun_ln:
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.ln());
Hans Boehm4db31b42015-05-31 12:19:05 -0700768 case R.id.fun_exp:
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.exp());
Hans Boehm84614952014-11-25 18:46:17 -0800774 case R.id.fun_log:
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, argVal.val.ln().divide(UnifiedReal.TEN.ln()));
Hans Boehm84614952014-11-25 18:46:17 -0800780 case R.id.fun_arcsin:
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.asin(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800786 case R.id.fun_arccos:
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 Boehm79f273c2016-06-09 11:00:27 -0700791 return new EvalRet(argVal.pos, fromRadians(argVal.val.acos(), ec));
Hans Boehm84614952014-11-25 18:46:17 -0800792 case R.id.fun_arctan:
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, fromRadians(argVal.val.atan(),ec));
Hans Boehm84614952014-11-25 18:46:17 -0800798 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700799 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800800 }
Hans Boehm84614952014-11-25 18:46:17 -0800801 }
802
Hans Boehm995e5eb2016-02-08 11:03:01 -0800803 private static final UnifiedReal ONE_HUNDREDTH = new UnifiedReal(100).inverse();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700804
Hans Boehm4db31b42015-05-31 12:19:05 -0700805 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm3666e632015-07-27 18:33:12 -0700806 final EvalRet tmp = evalUnary(i, ec);
807 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800808 UnifiedReal val = tmp.val;
809
Hans Boehm4db31b42015-05-31 12:19:05 -0700810 boolean isFact;
811 boolean isSquared = false;
812 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
813 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
814 isOperator(cpos, R.id.op_pct, ec)) {
815 if (isFact) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800816 val = val.fact();
Hans Boehm4db31b42015-05-31 12:19:05 -0700817 } else if (isSquared) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800818 val = val.multiply(val);
Hans Boehm4db31b42015-05-31 12:19:05 -0700819 } else /* percent */ {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800820 val = val.multiply(ONE_HUNDREDTH);
Hans Boehm84614952014-11-25 18:46:17 -0800821 }
Hans Boehm84614952014-11-25 18:46:17 -0800822 ++cpos;
823 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800824 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800825 }
826
Hans Boehmc023b732015-04-29 11:30:47 -0700827 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700828 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700829 int cpos = result1.pos; // current position
Hans Boehm995e5eb2016-02-08 11:03:01 -0800830 UnifiedReal val = result1.val; // value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700831 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm3666e632015-07-27 18:33:12 -0700832 final EvalRet exp = evalSignedFactor(cpos + 1, ec);
833 cpos = exp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800834 val = val.pow(exp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800835 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800836 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800837 }
838
Hans Boehmc023b732015-04-29 11:30:47 -0700839 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
840 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700841 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800842 EvalRet tmp = evalFactor(cpos, ec);
Hans Boehm3666e632015-07-27 18:33:12 -0700843 cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800844 final UnifiedReal result = negative ? tmp.val.negate() : tmp.val;
845 return new EvalRet(cpos, result);
Hans Boehm84614952014-11-25 18:46:17 -0800846 }
847
848 private boolean canStartFactor(int i) {
849 if (i >= mExpr.size()) return false;
850 Token t = mExpr.get(i);
851 if (!(t instanceof Operator)) return true;
Hans Boehm3666e632015-07-27 18:33:12 -0700852 int id = ((Operator)(t)).id;
Hans Boehm84614952014-11-25 18:46:17 -0800853 if (KeyMaps.isBinary(id)) return false;
854 switch (id) {
855 case R.id.op_fact:
856 case R.id.rparen:
857 return false;
858 default:
859 return true;
860 }
861 }
862
Hans Boehmc023b732015-04-29 11:30:47 -0700863 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800864 EvalRet tmp = evalSignedFactor(i, ec);
865 boolean is_mul = false;
866 boolean is_div = false;
Hans Boehm3666e632015-07-27 18:33:12 -0700867 int cpos = tmp.pos; // Current position in expression.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800868 UnifiedReal val = tmp.val; // Current value.
Hans Boehmc023b732015-04-29 11:30:47 -0700869 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
870 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -0800871 || canStartFactor(cpos)) {
872 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700873 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800874 if (is_div) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800875 val = val.divide(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800876 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800877 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800878 }
Hans Boehm3666e632015-07-27 18:33:12 -0700879 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800880 is_mul = is_div = false;
881 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800882 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800883 }
884
Hans Boehm8afd0f82015-07-30 17:00:33 -0700885 /**
886 * Is the subexpression starting at pos a simple percent constant?
887 * This is used to recognize exppressions like 200+10%, which we handle specially.
888 * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
889 * by either nothing or an additive operator.
890 * Note that we are intentionally far more restrictive in recognizing such expressions than
891 * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
892 * When in doubt, we fall back to the the naive interpretation of % as 1/100.
893 * Note that 100+(10)% yields 100.1 while 100+10% yields 110. This may be controversial,
894 * but is consistent with Google web search.
895 */
896 private boolean isPercent(int pos) {
897 if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
898 return false;
899 }
900 Token number = mExpr.get(pos);
901 if (number instanceof Operator) {
902 return false;
903 }
904 if (mExpr.size() == pos + 2) {
905 return true;
906 }
907 if (!(mExpr.get(pos + 2) instanceof Operator)) {
908 return false;
909 }
910 Operator op = (Operator) mExpr.get(pos + 2);
Hans Boehm64d1cdb2016-03-08 19:14:07 -0800911 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 -0700912 }
913
914 /**
915 * Compute the multiplicative factor corresponding to an N% addition or subtraction.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800916 * @param pos position of Constant or PreEval expression token corresponding to N.
917 * @param isSubtraction this is a subtraction, as opposed to addition.
918 * @param ec usable evaluation contex; only length matters.
919 * @return UnifiedReal value and position, which is pos + 2, i.e. after percent sign
Hans Boehm8afd0f82015-07-30 17:00:33 -0700920 */
921 private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
922 throws SyntaxException {
923 EvalRet tmp = evalUnary(pos, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800924 UnifiedReal val = isSubtraction ? tmp.val.negate() : tmp.val;
925 val = UnifiedReal.ONE.add(val.multiply(ONE_HUNDREDTH));
926 return new EvalRet(pos + 2 /* after percent sign */, val);
Hans Boehm8afd0f82015-07-30 17:00:33 -0700927 }
928
Hans Boehmc023b732015-04-29 11:30:47 -0700929 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800930 EvalRet tmp = evalTerm(i, ec);
931 boolean is_plus;
Hans Boehm3666e632015-07-27 18:33:12 -0700932 int cpos = tmp.pos;
Hans Boehm995e5eb2016-02-08 11:03:01 -0800933 UnifiedReal val = tmp.val;
Hans Boehmc023b732015-04-29 11:30:47 -0700934 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
935 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700936 if (isPercent(cpos + 1)) {
937 tmp = getPercentFactor(cpos + 1, !is_plus, ec);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800938 val = val.multiply(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800939 } else {
Hans Boehm8afd0f82015-07-30 17:00:33 -0700940 tmp = evalTerm(cpos + 1, ec);
941 if (is_plus) {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800942 val = val.add(tmp.val);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700943 } else {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800944 val = val.subtract(tmp.val);
Hans Boehm84614952014-11-25 18:46:17 -0800945 }
946 }
Hans Boehm3666e632015-07-27 18:33:12 -0700947 cpos = tmp.pos;
Hans Boehm84614952014-11-25 18:46:17 -0800948 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800949 return new EvalRet(cpos, val);
Hans Boehm84614952014-11-25 18:46:17 -0800950 }
951
Hans Boehme8553762015-06-26 17:56:52 -0700952 /**
953 * Return the starting position of the sequence of trailing binary operators.
954 */
955 private int trailingBinaryOpsStart() {
Hans Boehmc023b732015-04-29 11:30:47 -0700956 int result = mExpr.size();
957 while (result > 0) {
958 Token last = mExpr.get(result - 1);
959 if (!(last instanceof Operator)) break;
960 Operator o = (Operator)last;
Hans Boehm3666e632015-07-27 18:33:12 -0700961 if (!KeyMaps.isBinary(o.id)) break;
Hans Boehmc023b732015-04-29 11:30:47 -0700962 --result;
963 }
964 return result;
965 }
966
Hans Boehm3666e632015-07-27 18:33:12 -0700967 /**
968 * Is the current expression worth evaluating?
969 */
Hans Boehmc023b732015-04-29 11:30:47 -0700970 public boolean hasInterestingOps() {
Hans Boehme8553762015-06-26 17:56:52 -0700971 int last = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -0700972 int first = 0;
973 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
974 // Leading minus is not by itself interesting.
975 first++;
976 }
977 for (int i = first; i < last; ++i) {
978 Token t1 = mExpr.get(i);
Hans Boehm187d3e92015-06-09 18:04:26 -0700979 if (t1 instanceof Operator
980 || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
981 return true;
982 }
Hans Boehmc023b732015-04-29 11:30:47 -0700983 }
984 return false;
985 }
986
Hans Boehme8553762015-06-26 17:56:52 -0700987 /**
Hans Boehm52d477a2016-04-01 17:42:50 -0700988 * Does the expression contain trig operations?
989 */
990 public boolean hasTrigFuncs() {
991 for (Token t: mExpr) {
992 if (t instanceof Operator) {
993 Operator o = (Operator)t;
994 if (KeyMaps.isTrigFunc(o.id)) {
995 return true;
996 }
997 }
998 }
999 return false;
1000 }
1001
1002 /**
Hans Boehme8553762015-06-26 17:56:52 -07001003 * Evaluate the expression excluding trailing binary operators.
Hans Boehm3666e632015-07-27 18:33:12 -07001004 * Errors result in exceptions, most of which are unchecked. Should not be called
1005 * concurrently with modification of the expression. May take a very long time; avoid calling
1006 * from UI thread.
Hans Boehme8553762015-06-26 17:56:52 -07001007 *
1008 * @param degreeMode use degrees rather than radians
1009 */
Hans Boehm995e5eb2016-02-08 11:03:01 -08001010 UnifiedReal eval(boolean degreeMode) throws SyntaxException
1011 // And unchecked exceptions thrown by UnifiedReal, CR,
Hans Boehmc023b732015-04-29 11:30:47 -07001012 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -08001013 {
1014 try {
Hans Boehm3666e632015-07-27 18:33:12 -07001015 // We currently never include trailing binary operators, but include other trailing
1016 // operators. Thus we usually, but not always, display results for prefixes of valid
1017 // expressions, and don't generate an error where we previously displayed an instant
1018 // result. This reflects the Android L design.
Hans Boehme8553762015-06-26 17:56:52 -07001019 int prefixLen = trailingBinaryOpsStart();
Hans Boehmc023b732015-04-29 11:30:47 -07001020 EvalContext ec = new EvalContext(degreeMode, prefixLen);
Hans Boehm84614952014-11-25 18:46:17 -08001021 EvalRet res = evalExpr(0, ec);
Hans Boehm3666e632015-07-27 18:33:12 -07001022 if (res.pos != prefixLen) {
Hans Boehmc023b732015-04-29 11:30:47 -07001023 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -07001024 }
Hans Boehm995e5eb2016-02-08 11:03:01 -08001025 return res.val;
Hans Boehm84614952014-11-25 18:46:17 -08001026 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -07001027 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -08001028 }
1029 }
1030
1031 // Produce a string representation of the expression itself
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001032 SpannableStringBuilder toSpannableStringBuilder(Context context) {
1033 SpannableStringBuilder ssb = new SpannableStringBuilder();
Hans Boehm84614952014-11-25 18:46:17 -08001034 for (Token t: mExpr) {
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001035 ssb.append(t.toCharSequence(context));
Hans Boehm84614952014-11-25 18:46:17 -08001036 }
Hans Boehm8a4f81c2015-07-09 10:41:25 -07001037 return ssb;
Hans Boehm84614952014-11-25 18:46:17 -08001038 }
1039}