blob: 73e09bfd233be5a419660b54fb48dcc070874022 [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 -080019
20import com.hp.creals.CR;
21import com.hp.creals.UnaryCRFunction;
Hans Boehm84614952014-11-25 18:46:17 -080022
23import android.content.Context;
Hans Boehmc023b732015-04-29 11:30:47 -070024import android.util.Log;
Hans Boehm84614952014-11-25 18:46:17 -080025
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070026import java.math.BigInteger;
27import java.io.DataInput;
28import java.io.DataOutput;
29import java.io.IOException;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070030import java.util.ArrayList;
31import java.util.HashMap;
32import java.util.IdentityHashMap;
33
Hans Boehm84614952014-11-25 18:46:17 -080034// A mathematical expression represented as a sequence of "tokens".
35// Many tokes are represented by button ids for the corresponding operator.
36// Parsed only when we evaluate the expression using the "eval" method.
37class CalculatorExpr {
38 private ArrayList<Token> mExpr; // The actual representation
39 // as a list of tokens. Constant
40 // tokens are always nonempty.
41
42 private static enum TokenKind { CONSTANT, OPERATOR, PRE_EVAL };
43 private static TokenKind[] tokenKindValues = TokenKind.values();
44 private final static BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
45 private final static BigInteger BIG_BILLION = BigInteger.valueOf(1000000000);
46
47 private static abstract class Token {
48 abstract TokenKind kind();
49 abstract void write(DataOutput out) throws IOException;
50 // Implementation writes kind as Byte followed by
51 // data read by constructor.
52 abstract String toString(Context context);
53 // We need the context to convert button ids to strings.
54 }
55
56 // An operator token
57 private static class Operator extends Token {
58 final int mId; // We use the button resource id
59 Operator(int resId) {
60 mId = resId;
61 }
62 Operator(DataInput in) throws IOException {
63 mId = in.readInt();
64 }
65 @Override
66 void write(DataOutput out) throws IOException {
67 out.writeByte(TokenKind.OPERATOR.ordinal());
68 out.writeInt(mId);
69 }
70 @Override
71 public String toString(Context context) {
Justin Klaassene2711cb2015-05-28 11:13:17 -070072 return KeyMaps.toString(context, mId);
Hans Boehm84614952014-11-25 18:46:17 -080073 }
74 @Override
75 TokenKind kind() { return TokenKind.OPERATOR; }
76 }
77
Hans Boehm682ff5e2015-03-09 14:40:25 -070078 // A (possibly incomplete) numerical constant.
79 // Supports addition and removal of trailing characters; hence mutable.
Hans Boehm84614952014-11-25 18:46:17 -080080 private static class Constant extends Token implements Cloneable {
81 private boolean mSawDecimal;
82 String mWhole; // part before decimal point
83 private String mFraction; // part after decimal point
84
85 Constant() {
86 mWhole = "";
87 mFraction = "";
88 mSawDecimal = false;
89 };
90
91 Constant(DataInput in) throws IOException {
92 mWhole = in.readUTF();
93 mSawDecimal = in.readBoolean();
94 mFraction = in.readUTF();
95 }
96
97 @Override
98 void write(DataOutput out) throws IOException {
99 out.writeByte(TokenKind.CONSTANT.ordinal());
100 out.writeUTF(mWhole);
101 out.writeBoolean(mSawDecimal);
102 out.writeUTF(mFraction);
103 }
104
105 // Given a button press, append corresponding digit.
106 // We assume id is a digit or decimal point.
107 // Just return false if this was the second (or later) decimal point
108 // in this constant.
109 boolean add(int id) {
110 if (id == R.id.dec_point) {
111 if (mSawDecimal) return false;
112 mSawDecimal = true;
113 return true;
114 }
115 int val = KeyMaps.digVal(id);
116 if (mSawDecimal) {
117 mFraction += val;
118 } else {
119 mWhole += val;
120 }
121 return true;
122 }
123
124 // Undo the last add.
125 // Assumes the constant is nonempty.
126 void delete() {
127 if (!mFraction.isEmpty()) {
128 mFraction = mFraction.substring(0, mFraction.length() - 1);
129 } else if (mSawDecimal) {
130 mSawDecimal = false;
131 } else {
132 mWhole = mWhole.substring(0, mWhole.length() - 1);
133 }
134 }
135
136 boolean isEmpty() {
137 return (mSawDecimal == false && mWhole.isEmpty());
138 }
139
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700140 // Produces human-readable string, as typed.
Hans Boehm013969e2015-04-13 20:29:47 -0700141 // Result is internationalized.
Hans Boehm84614952014-11-25 18:46:17 -0800142 @Override
143 public String toString() {
144 String result = mWhole;
145 if (mSawDecimal) {
Hans Boehm013969e2015-04-13 20:29:47 -0700146 result += '.';
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700147 result += mFraction;
148 }
Hans Boehm013969e2015-04-13 20:29:47 -0700149 return KeyMaps.translateResult(result);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700150 }
151
152 // Eliminates leading decimal, which some of our
153 // other packages don't like.
Hans Boehm013969e2015-04-13 20:29:47 -0700154 // Meant for machine consumption:
155 // Doesn't internationalize decimal point or digits.
156 public String toEasyString() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700157 String result = mWhole;
158 if (result.isEmpty()) result = "0";
159 if (mSawDecimal) {
Hans Boehm84614952014-11-25 18:46:17 -0800160 result += '.';
161 result += mFraction;
162 }
163 return result;
164 }
165
Hans Boehm682ff5e2015-03-09 14:40:25 -0700166 public BoundedRational toRational() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700167 String whole = mWhole;
168 if (whole.isEmpty()) whole = "0";
169 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700170 BigInteger den = BigInteger.TEN.pow(mFraction.length());
171 return new BoundedRational(num, den);
172 }
173
Hans Boehm84614952014-11-25 18:46:17 -0800174 @Override
175 String toString(Context context) {
176 return toString();
177 }
178
179 @Override
180 TokenKind kind() { return TokenKind.CONSTANT; }
181
182 // Override clone to make it public
183 @Override
184 public Object clone() {
185 Constant res = new Constant();
186 res.mWhole = mWhole;
187 res.mFraction = mFraction;
188 res.mSawDecimal = mSawDecimal;
189 return res;
190 }
191 }
192
193 // Hash maps used to detect duplicate subexpressions when
194 // we write out CalculatorExprs and read them back in.
195 private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
196 new ThreadLocal<IdentityHashMap<CR,Integer>>();
197 // Maps expressions to indices on output
198 private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
199 new ThreadLocal<HashMap<Integer,PreEval>>();
200 // Maps expressions to indices on output
201 private static final ThreadLocal<Integer> exprIndex =
202 new ThreadLocal<Integer>();
203
204 static void initExprOutput() {
205 outMap.set(new IdentityHashMap<CR,Integer>());
206 exprIndex.set(Integer.valueOf(0));
207 }
208
209 static void initExprInput() {
210 inMap.set(new HashMap<Integer,PreEval>());
211 }
212
213 // We treat previously evaluated subexpressions as tokens
214 // These are inserted when either:
215 // - We continue an expression after evaluating some of it.
216 // - TODO: When we copy/paste expressions.
217 // The representation includes three different representations
218 // of the expression:
219 // 1) The CR value for use in computation.
220 // 2) The integer value for use in the computations,
221 // if the expression evaluates to an integer.
222 // 3a) The corresponding CalculatorExpr, together with
223 // 3b) The context (currently just deg/rad mode) used to evaluate
224 // the expression.
225 // 4) A short string representation that is used to
226 // Display the expression.
227 //
228 // (3) is present only so that we can persist the object.
229 // (4) is stored explicitly to avoid waiting for recomputation in the UI
230 // thread.
231 private static class PreEval extends Token {
232 final CR mValue;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700233 final BoundedRational mRatValue;
Hans Boehm84614952014-11-25 18:46:17 -0800234 private final CalculatorExpr mExpr;
235 private final EvalContext mContext;
Hans Boehm50ed3202015-06-09 14:35:49 -0700236 private final String mShortRep; // Not internationalized.
Hans Boehm013969e2015-04-13 20:29:47 -0700237 PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr,
238 EvalContext ec, String shortRep) {
Hans Boehm84614952014-11-25 18:46:17 -0800239 mValue = val;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700240 mRatValue = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800241 mExpr = expr;
242 mContext = ec;
243 mShortRep = shortRep;
244 }
245 // In writing out PreEvals, we are careful to avoid writing
246 // out duplicates. We assume that two expressions are
247 // duplicates if they have the same mVal. This avoids a
248 // potential exponential blow up in certain off cases and
249 // redundant evaluation after reading them back in.
250 // The parameter hash map maps expressions we've seen
251 // before to their index.
252 @Override
253 void write(DataOutput out) throws IOException {
254 out.writeByte(TokenKind.PRE_EVAL.ordinal());
255 Integer index = outMap.get().get(mValue);
256 if (index == null) {
257 int nextIndex = exprIndex.get() + 1;
258 exprIndex.set(nextIndex);
259 outMap.get().put(mValue, nextIndex);
260 out.writeInt(nextIndex);
261 mExpr.write(out);
262 mContext.write(out);
263 out.writeUTF(mShortRep);
264 } else {
265 // Just write out the index
266 out.writeInt(index);
267 }
268 }
269 PreEval(DataInput in) throws IOException {
270 int index = in.readInt();
271 PreEval prev = inMap.get().get(index);
272 if (prev == null) {
273 mExpr = new CalculatorExpr(in);
Hans Boehmc023b732015-04-29 11:30:47 -0700274 mContext = new EvalContext(in, mExpr.mExpr.size());
Hans Boehm84614952014-11-25 18:46:17 -0800275 // Recompute other fields
276 // We currently do this in the UI thread, but we
277 // only create PreEval expressions that were
278 // previously successfully evaluated, and thus
279 // don't diverge. We also only evaluate to a
280 // constructive real, which involves substantial
281 // work only in fairly contrived circumstances.
282 // TODO: Deal better with slow evaluations.
Hans Boehmc023b732015-04-29 11:30:47 -0700283 EvalRet res = null;
284 try {
285 res = mExpr.evalExpr(0, mContext);
286 } catch (SyntaxException e) {
287 // Should be impossible, since we only write out
288 // expressions that can be evaluated.
289 Log.e("Calculator", "Unexpected syntax exception" + e);
290 }
Hans Boehm84614952014-11-25 18:46:17 -0800291 mValue = res.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700292 mRatValue = res.mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800293 mShortRep = in.readUTF();
294 inMap.get().put(index, this);
295 } else {
296 mValue = prev.mValue;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700297 mRatValue = prev.mRatValue;
Hans Boehm84614952014-11-25 18:46:17 -0800298 mExpr = prev.mExpr;
299 mContext = prev.mContext;
300 mShortRep = prev.mShortRep;
301 }
302 }
303 @Override
304 String toString(Context context) {
Hans Boehm50ed3202015-06-09 14:35:49 -0700305 return KeyMaps.translateResult(mShortRep);
Hans Boehm84614952014-11-25 18:46:17 -0800306 }
307 @Override
Hans Boehm187d3e92015-06-09 18:04:26 -0700308 TokenKind kind() {
309 return TokenKind.PRE_EVAL;
310 }
311 boolean hasEllipsis() {
312 return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
313 }
Hans Boehm84614952014-11-25 18:46:17 -0800314 }
315
316 static Token newToken(DataInput in) throws IOException {
317 TokenKind kind = tokenKindValues[in.readByte()];
318 switch(kind) {
319 case CONSTANT:
320 return new Constant(in);
321 case OPERATOR:
322 return new Operator(in);
323 case PRE_EVAL:
324 return new PreEval(in);
325 default: throw new IOException("Bad save file format");
326 }
327 }
328
329 CalculatorExpr() {
330 mExpr = new ArrayList<Token>();
331 }
332
333 private CalculatorExpr(ArrayList<Token> expr) {
334 mExpr = expr;
335 }
336
337 CalculatorExpr(DataInput in) throws IOException {
338 mExpr = new ArrayList<Token>();
339 int size = in.readInt();
340 for (int i = 0; i < size; ++i) {
341 mExpr.add(newToken(in));
342 }
343 }
344
345 void write(DataOutput out) throws IOException {
346 int size = mExpr.size();
347 out.writeInt(size);
348 for (int i = 0; i < size; ++i) {
349 mExpr.get(i).write(out);
350 }
351 }
352
353 private boolean hasTrailingBinary() {
354 int s = mExpr.size();
355 if (s == 0) return false;
356 Token t = mExpr.get(s-1);
357 if (!(t instanceof Operator)) return false;
358 Operator o = (Operator)t;
359 return (KeyMaps.isBinary(o.mId));
360 }
361
Hans Boehm017de982015-06-10 17:46:03 -0700362 /**
363 * Append press of button with given id to expression.
364 * If the insertion would clearly result in a syntax error, either just return false
365 * and do nothing, or make an adjustment to avoid the problem. We do the latter only
366 * for unambiguous consecutive binary operators, in which case we delete the first
367 * operator.
368 */
Hans Boehm84614952014-11-25 18:46:17 -0800369 boolean add(int id) {
370 int s = mExpr.size();
371 int d = KeyMaps.digVal(id);
372 boolean binary = KeyMaps.isBinary(id);
Hans Boehm017de982015-06-10 17:46:03 -0700373 Token lastTok = s == 0 ? null : mExpr.get(s-1);
374 int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).mId : 0;
375 // Quietly replace a trailing binary operator with another one, unless the second
376 // operator is minus, in which case we just allow it as a unary minus.
377 if (binary && !KeyMaps.isPrefix(id)) {
378 if (s == 0 || lastOp == R.id.lparen || KeyMaps.isFunc(lastOp)
379 || KeyMaps.isPrefix(lastOp) && lastOp != R.id.op_sub) {
380 return false;
381 }
382 while (hasTrailingBinary()) {
383 delete();
384 }
385 // s invalid and not used below.
Hans Boehm84614952014-11-25 18:46:17 -0800386 }
387 boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
388 if (isConstPiece) {
Hans Boehm017de982015-06-10 17:46:03 -0700389 // Since we treat juxtaposition as multiplication, a constant can appear anywhere.
Hans Boehm84614952014-11-25 18:46:17 -0800390 if (s == 0) {
391 mExpr.add(new Constant());
392 s++;
393 } else {
394 Token last = mExpr.get(s-1);
395 if(!(last instanceof Constant)) {
Hans Boehm017de982015-06-10 17:46:03 -0700396 if (last instanceof PreEval) {
397 // Add explicit multiplication to avoid confusing display.
398 mExpr.add(new Operator(R.id.op_mul));
399 s++;
Hans Boehm84614952014-11-25 18:46:17 -0800400 }
401 mExpr.add(new Constant());
402 s++;
403 }
404 }
405 return ((Constant)(mExpr.get(s-1))).add(id);
406 } else {
407 mExpr.add(new Operator(id));
408 return true;
409 }
410 }
411
Hans Boehm017de982015-06-10 17:46:03 -0700412 /**
413 * Remove trailing op_add and op_sub operators.
414 */
415 void removeTrailingAdditiveOperators() {
416 while (true) {
417 int s = mExpr.size();
418 if (s == 0) break;
419 Token lastTok = mExpr.get(s-1);
420 if (!(lastTok instanceof Operator)) break;
421 int lastOp = ((Operator) lastTok).mId;
422 if (lastOp != R.id.op_add && lastOp != R.id.op_sub) break;
423 delete();
424 }
425 }
426
Hans Boehm84614952014-11-25 18:46:17 -0800427 // Append the contents of the argument expression.
428 // It is assumed that the argument expression will not change,
429 // and thus its pieces can be reused directly.
430 // TODO: We probably only need this for expressions consisting of
431 // a single PreEval "token", and may want to check that.
432 void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700433 // Check that we're not concatenating Constant or PreEval
434 // tokens, since the result would look like a single constant
435 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800436 int s2 = expr2.mExpr.size();
Hans Boehmfbcef702015-04-27 18:07:47 -0700437 // Check that we're not concatenating Constant or PreEval
438 // tokens, since the result would look like a single constant,
439 // with very mysterious results for the user.
440 if (s != 0 && s2 != 0) {
441 Token last = mExpr.get(s-1);
442 Token first = expr2.mExpr.get(0);
443 if (!(first instanceof Operator) && !(last instanceof Operator)) {
444 // Fudge it by adding an explicit multiplication.
445 // We would have interpreted it as such anyway, and this
446 // makes it recognizable to the user.
447 mExpr.add(new Operator(R.id.op_mul));
448 }
449 }
Hans Boehm84614952014-11-25 18:46:17 -0800450 for (int i = 0; i < s2; ++i) {
451 mExpr.add(expr2.mExpr.get(i));
452 }
453 }
454
455 // Undo the last key addition, if any.
456 void delete() {
457 int s = mExpr.size();
458 if (s == 0) return;
459 Token last = mExpr.get(s-1);
460 if (last instanceof Constant) {
461 Constant c = (Constant)last;
462 c.delete();
463 if (!c.isEmpty()) return;
464 }
465 mExpr.remove(s-1);
466 }
467
468 void clear() {
469 mExpr.clear();
470 }
471
472 boolean isEmpty() {
473 return mExpr.isEmpty();
474 }
475
476 // Returns a logical deep copy of the CalculatorExpr.
477 // Operator and PreEval tokens are immutable, and thus
478 // aren't really copied.
479 public Object clone() {
480 CalculatorExpr res = new CalculatorExpr();
481 for (Token t: mExpr) {
482 if (t instanceof Constant) {
483 res.mExpr.add((Token)(((Constant)t).clone()));
484 } else {
485 res.mExpr.add(t);
486 }
487 }
488 return res;
489 }
490
491 // Am I just a constant?
492 boolean isConstant() {
493 if (mExpr.size() != 1) return false;
494 return mExpr.get(0) instanceof Constant;
495 }
496
497 // Return a new expression consisting of a single PreEval token
498 // representing the current expression.
499 // The caller supplies the value, degree mode, and short
500 // string representation, which must have been previously computed.
501 // Thus this is guaranteed to terminate reasonably quickly.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700502 CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800503 boolean dm, String sr) {
504 CalculatorExpr result = new CalculatorExpr();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700505 Token t = new PreEval(val, ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800506 new CalculatorExpr(
507 (ArrayList<Token>)mExpr.clone()),
Hans Boehmc023b732015-04-29 11:30:47 -0700508 new EvalContext(dm, mExpr.size()), sr);
Hans Boehm84614952014-11-25 18:46:17 -0800509 result.mExpr.add(t);
510 return result;
511 }
512
513 // Internal evaluation functions return an EvalRet triple.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700514 // We compute rational (BoundedRational) results when possible, both as
Hans Boehm84614952014-11-25 18:46:17 -0800515 // a performance optimization, and to detect errors exactly when we can.
516 private class EvalRet {
517 int mPos; // Next position (expression index) to be parsed
518 final CR mVal; // Constructive Real result of evaluating subexpression
Hans Boehm682ff5e2015-03-09 14:40:25 -0700519 final BoundedRational mRatVal; // Exact Rational value or null if
520 // irrational or hard to compute.
521 EvalRet(int p, CR v, BoundedRational r) {
Hans Boehm84614952014-11-25 18:46:17 -0800522 mPos = p;
523 mVal = v;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700524 mRatVal = r;
Hans Boehm84614952014-11-25 18:46:17 -0800525 }
526 }
527
528 // And take a context argument:
529 private static class EvalContext {
Hans Boehmc023b732015-04-29 11:30:47 -0700530 public final int mPrefixLength; // Length of prefix to evaluate.
531 // Not explicitly saved.
532 public final boolean mDegreeMode;
Hans Boehm84614952014-11-25 18:46:17 -0800533 // If we add any other kinds of evaluation modes, they go here.
Hans Boehmc023b732015-04-29 11:30:47 -0700534 EvalContext(boolean degreeMode, int len) {
Hans Boehm84614952014-11-25 18:46:17 -0800535 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700536 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800537 }
Hans Boehmc023b732015-04-29 11:30:47 -0700538 EvalContext(DataInput in, int len) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800539 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700540 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800541 }
542 void write(DataOutput out) throws IOException {
543 out.writeBoolean(mDegreeMode);
544 }
545 }
546
547 private final CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
548
549 private final CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
550
551 private CR toRadians(CR x, EvalContext ec) {
552 if (ec.mDegreeMode) {
553 return x.multiply(RADIANS_PER_DEGREE);
554 } else {
555 return x;
556 }
557 }
558
559 private CR fromRadians(CR x, EvalContext ec) {
560 if (ec.mDegreeMode) {
561 return x.multiply(DEGREES_PER_RADIAN);
562 } else {
563 return x;
564 }
565 }
566
567 // The following methods can all throw IndexOutOfBoundsException
568 // in the event of a syntax error. We expect that to be caught in
569 // eval below.
570
Hans Boehmc023b732015-04-29 11:30:47 -0700571 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800572 Token t = mExpr.get(i);
573 if (!(t instanceof Operator)) return false;
574 return ((Operator)(t)).mId == op;
575 }
576
Hans Boehmc023b732015-04-29 11:30:47 -0700577 private boolean isOperator(int i, int op, EvalContext ec) {
578 if (i >= ec.mPrefixLength) return false;
579 return isOperatorUnchecked(i, op);
580 }
581
582 static class SyntaxException extends Exception {
583 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800584 super();
585 }
Hans Boehmc023b732015-04-29 11:30:47 -0700586 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800587 super(s);
588 }
589 }
590
591 // The following functions all evaluate some kind of expression
592 // starting at position i in mExpr in a specified evaluation context.
593 // They return both the expression value (as constructive real and,
594 // if applicable, as BigInteger) and the position of the next token
595 // that was not used as part of the evaluation.
Hans Boehmc023b732015-04-29 11:30:47 -0700596 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800597 Token t = mExpr.get(i);
598 CR value;
599 if (t instanceof Constant) {
600 Constant c = (Constant)t;
Hans Boehm013969e2015-04-13 20:29:47 -0700601 value = CR.valueOf(c.toEasyString(),10);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700602 return new EvalRet(i+1, value, c.toRational());
Hans Boehm84614952014-11-25 18:46:17 -0800603 }
604 if (t instanceof PreEval) {
605 PreEval p = (PreEval)t;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700606 return new EvalRet(i+1, p.mValue, p.mRatValue);
Hans Boehm84614952014-11-25 18:46:17 -0800607 }
608 EvalRet argVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700609 BoundedRational ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800610 switch(((Operator)(t)).mId) {
611 case R.id.const_pi:
612 return new EvalRet(i+1, CR.PI, null);
613 case R.id.const_e:
Hans Boehm4db31b42015-05-31 12:19:05 -0700614 return new EvalRet(i+1, REAL_E, null);
Hans Boehm84614952014-11-25 18:46:17 -0800615 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700616 // Seems to have highest precedence.
617 // Does not add implicit paren.
618 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700619 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700620 argVal = evalUnary(i+2, ec);
621 ratVal = BoundedRational.sqrt(
622 BoundedRational.negate(argVal.mRatVal));
623 if (ratVal != null) break;
624 return new EvalRet(argVal.mPos,
625 argVal.mVal.negate().sqrt(), null);
626 } else {
627 argVal = evalUnary(i+1, ec);
628 ratVal = BoundedRational.sqrt(argVal.mRatVal);
629 if (ratVal != null) break;
630 return new EvalRet(argVal.mPos, argVal.mVal.sqrt(), null);
631 }
Hans Boehm84614952014-11-25 18:46:17 -0800632 case R.id.lparen:
633 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700634 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700635 return new EvalRet(argVal.mPos, argVal.mVal, argVal.mRatVal);
Hans Boehm84614952014-11-25 18:46:17 -0800636 case R.id.fun_sin:
637 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700638 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700639 ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700640 : BoundedRational.sin(argVal.mRatVal);
641 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800642 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700643 toRadians(argVal.mVal,ec).sin(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800644 case R.id.fun_cos:
645 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700646 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700647 ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700648 : BoundedRational.cos(argVal.mRatVal);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700649 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800650 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700651 toRadians(argVal.mVal,ec).cos(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800652 case R.id.fun_tan:
653 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700654 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700655 ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700656 : BoundedRational.tan(argVal.mRatVal);
657 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800658 CR argCR = toRadians(argVal.mVal, ec);
659 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700660 argCR.sin().divide(argCR.cos()), null);
Hans Boehm84614952014-11-25 18:46:17 -0800661 case R.id.fun_ln:
662 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700663 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700664 ratVal = BoundedRational.ln(argVal.mRatVal);
665 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800666 return new EvalRet(argVal.mPos, argVal.mVal.ln(), null);
Hans Boehm4db31b42015-05-31 12:19:05 -0700667 case R.id.fun_exp:
668 argVal = evalExpr(i+1, ec);
669 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
670 ratVal = BoundedRational.exp(argVal.mRatVal);
671 if (ratVal != null) break;
672 return new EvalRet(argVal.mPos, argVal.mVal.exp(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800673 case R.id.fun_log:
674 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700675 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700676 ratVal = BoundedRational.log(argVal.mRatVal);
677 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800678 return new EvalRet(argVal.mPos,
679 argVal.mVal.ln().divide(CR.valueOf(10).ln()),
680 null);
681 case R.id.fun_arcsin:
682 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700683 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700684 ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700685 : BoundedRational.asin(argVal.mRatVal);
686 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800687 return new EvalRet(argVal.mPos,
688 fromRadians(UnaryCRFunction
689 .asinFunction.execute(argVal.mVal),ec),
690 null);
691 case R.id.fun_arccos:
692 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700693 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700694 ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700695 : BoundedRational.acos(argVal.mRatVal);
696 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800697 return new EvalRet(argVal.mPos,
698 fromRadians(UnaryCRFunction
699 .acosFunction.execute(argVal.mVal),ec),
700 null);
701 case R.id.fun_arctan:
702 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700703 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700704 ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700705 : BoundedRational.atan(argVal.mRatVal);
706 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800707 return new EvalRet(argVal.mPos,
708 fromRadians(UnaryCRFunction
709 .atanFunction.execute(argVal.mVal),ec),
710 null);
711 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700712 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800713 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700714 // We have a rational value.
715 return new EvalRet(argVal.mPos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800716 }
717
718 // Compute an integral power of a constructive real.
719 // Unlike the "general" case using logarithms, this handles a negative
720 // base.
721 private static CR pow(CR base, BigInteger exp) {
722 if (exp.compareTo(BigInteger.ZERO) < 0) {
723 return pow(base, exp.negate()).inverse();
724 }
725 if (exp.equals(BigInteger.ONE)) return base;
726 if (exp.and(BigInteger.ONE).intValue() == 1) {
727 return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
728 }
729 if (exp.equals(BigInteger.ZERO)) {
730 return CR.valueOf(1);
731 }
732 CR tmp = pow(base, exp.shiftRight(1));
733 return tmp.multiply(tmp);
734 }
735
Hans Boehm682ff5e2015-03-09 14:40:25 -0700736 private static final int TEST_PREC = -100;
737 // Test for integer-ness to 100 bits past binary point.
738 private static final BigInteger MASK =
739 BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
Hans Boehm4db31b42015-05-31 12:19:05 -0700740 private static final CR REAL_E = CR.valueOf(1).exp();
741 private static final CR REAL_ONE_HUNDREDTH = CR.valueOf(100).inverse();
742 private static final BoundedRational RATIONAL_ONE_HUNDREDTH =
743 new BoundedRational(1,100);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700744 private static boolean isApprInt(CR x) {
745 BigInteger appr = x.get_appr(TEST_PREC);
746 return appr.and(MASK).signum() == 0;
747 }
748
Hans Boehm4db31b42015-05-31 12:19:05 -0700749 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800750 EvalRet tmp = evalUnary(i, ec);
751 int cpos = tmp.mPos;
752 CR cval = tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700753 BoundedRational ratVal = tmp.mRatVal;
Hans Boehm4db31b42015-05-31 12:19:05 -0700754 boolean isFact;
755 boolean isSquared = false;
756 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
757 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
758 isOperator(cpos, R.id.op_pct, ec)) {
759 if (isFact) {
760 if (ratVal == null) {
761 // Assume it was an integer, but we
762 // didn't figure it out.
763 // KitKat may have used the Gamma function.
764 if (!isApprInt(cval)) {
765 throw new ArithmeticException("factorial(non-integer)");
766 }
767 ratVal = new BoundedRational(cval.BigIntegerValue());
Hans Boehm682ff5e2015-03-09 14:40:25 -0700768 }
Hans Boehm4db31b42015-05-31 12:19:05 -0700769 ratVal = BoundedRational.fact(ratVal);
770 cval = ratVal.CRValue();
771 } else if (isSquared) {
772 ratVal = BoundedRational.multiply(ratVal, ratVal);
773 if (ratVal == null) {
774 cval = cval.multiply(cval);
775 } else {
776 cval = ratVal.CRValue();
777 }
778 } else /* percent */ {
779 ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH);
780 if (ratVal == null) {
781 cval = cval.multiply(REAL_ONE_HUNDREDTH);
782 } else {
783 cval = ratVal.CRValue();
784 }
Hans Boehm84614952014-11-25 18:46:17 -0800785 }
Hans Boehm84614952014-11-25 18:46:17 -0800786 ++cpos;
787 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700788 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800789 }
790
Hans Boehmc023b732015-04-29 11:30:47 -0700791 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700792 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800793 int cpos = result1.mPos; // current position
794 CR cval = result1.mVal; // value so far
Hans Boehm682ff5e2015-03-09 14:40:25 -0700795 BoundedRational ratVal = result1.mRatVal; // int value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700796 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm84614952014-11-25 18:46:17 -0800797 final EvalRet exp = evalSignedFactor(cpos+1, ec);
798 cpos = exp.mPos;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700799 // Try completely rational evaluation first.
800 ratVal = BoundedRational.pow(ratVal, exp.mRatVal);
801 if (ratVal != null) {
802 return new EvalRet(cpos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800803 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700804 // Power with integer exponent is defined for negative base.
805 // Thus we handle that case separately.
806 // We punt if the exponent is an integer computed from irrational
807 // values. That wouldn't work reliably with floating point either.
808 BigInteger int_exp = BoundedRational.asBigInteger(exp.mRatVal);
809 if (int_exp != null) {
810 cval = pow(cval, int_exp);
Hans Boehm84614952014-11-25 18:46:17 -0800811 } else {
Hans Boehm84614952014-11-25 18:46:17 -0800812 cval = cval.ln().multiply(exp.mVal).exp();
813 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700814 ratVal = null;
Hans Boehm84614952014-11-25 18:46:17 -0800815 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700816 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800817 }
818
Hans Boehmc023b732015-04-29 11:30:47 -0700819 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
820 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700821 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800822 EvalRet tmp = evalFactor(cpos, ec);
823 cpos = tmp.mPos;
Hans Boehm08e8f322015-04-21 13:18:38 -0700824 CR cval = negative ? tmp.mVal.negate() : tmp.mVal;
825 BoundedRational ratVal = negative ? BoundedRational.negate(tmp.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700826 : tmp.mRatVal;
827 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800828 }
829
830 private boolean canStartFactor(int i) {
831 if (i >= mExpr.size()) return false;
832 Token t = mExpr.get(i);
833 if (!(t instanceof Operator)) return true;
834 int id = ((Operator)(t)).mId;
835 if (KeyMaps.isBinary(id)) return false;
836 switch (id) {
837 case R.id.op_fact:
838 case R.id.rparen:
839 return false;
840 default:
841 return true;
842 }
843 }
844
Hans Boehmc023b732015-04-29 11:30:47 -0700845 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800846 EvalRet tmp = evalSignedFactor(i, ec);
847 boolean is_mul = false;
848 boolean is_div = false;
849 int cpos = tmp.mPos; // Current position in expression.
850 CR cval = tmp.mVal; // Current value.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700851 BoundedRational ratVal = tmp.mRatVal; // Current rational value.
Hans Boehmc023b732015-04-29 11:30:47 -0700852 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
853 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -0800854 || canStartFactor(cpos)) {
855 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700856 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800857 if (is_div) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700858 ratVal = BoundedRational.divide(ratVal, tmp.mRatVal);
859 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800860 cval = cval.divide(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700861 } else {
862 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800863 }
864 } else {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700865 ratVal = BoundedRational.multiply(ratVal, tmp.mRatVal);
866 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800867 cval = cval.multiply(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700868 } else {
869 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800870 }
871 }
872 cpos = tmp.mPos;
873 is_mul = is_div = false;
874 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700875 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800876 }
877
Hans Boehmc023b732015-04-29 11:30:47 -0700878 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800879 EvalRet tmp = evalTerm(i, ec);
880 boolean is_plus;
881 int cpos = tmp.mPos;
882 CR cval = tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700883 BoundedRational ratVal = tmp.mRatVal;
Hans Boehmc023b732015-04-29 11:30:47 -0700884 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
885 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm84614952014-11-25 18:46:17 -0800886 tmp = evalTerm(cpos+1, ec);
887 if (is_plus) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700888 ratVal = BoundedRational.add(ratVal, tmp.mRatVal);
889 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800890 cval = cval.add(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700891 } else {
892 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800893 }
894 } else {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700895 ratVal = BoundedRational.subtract(ratVal, tmp.mRatVal);
896 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800897 cval = cval.subtract(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700898 } else {
899 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800900 }
901 }
902 cpos = tmp.mPos;
903 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700904 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800905 }
906
907 // Externally visible evaluation result.
908 public class EvalResult {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700909 EvalResult (CR val, BoundedRational ratVal) {
Hans Boehm84614952014-11-25 18:46:17 -0800910 mVal = val;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700911 mRatVal = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800912 }
913 final CR mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700914 final BoundedRational mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800915 }
916
Hans Boehmc023b732015-04-29 11:30:47 -0700917 // Return the starting position of the sequence of trailing operators
918 // that cannot be meaningfully evaluated.
919 private int trailingOpsStart() {
920 int result = mExpr.size();
921 while (result > 0) {
922 Token last = mExpr.get(result - 1);
923 if (!(last instanceof Operator)) break;
924 Operator o = (Operator)last;
925 if (KeyMaps.isSuffix(o.mId) || o.mId == R.id.const_pi
926 || o.mId == R.id.const_e) {
927 break;
928 }
929 --result;
930 }
931 return result;
932 }
933
934 public boolean hasTrailingOperators() {
935 return trailingOpsStart() != mExpr.size();
936 }
937
938 // Is the current expression worth evaluating?
939 public boolean hasInterestingOps() {
940 int last = trailingOpsStart();
941 int first = 0;
942 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
943 // Leading minus is not by itself interesting.
944 first++;
945 }
946 for (int i = first; i < last; ++i) {
947 Token t1 = mExpr.get(i);
Hans Boehm187d3e92015-06-09 18:04:26 -0700948 if (t1 instanceof Operator
949 || t1 instanceof PreEval && ((PreEval)t1).hasEllipsis()) {
950 return true;
951 }
Hans Boehmc023b732015-04-29 11:30:47 -0700952 }
953 return false;
954 }
955
Hans Boehm84614952014-11-25 18:46:17 -0800956 // Evaluate the entire expression, returning null in the event
957 // of an error.
958 // Not called from the UI thread, but should not be called
959 // concurrently with modifications to the expression.
Hans Boehmc023b732015-04-29 11:30:47 -0700960 EvalResult eval(boolean degreeMode, boolean required) throws SyntaxException
961 // And unchecked exceptions thrown by CR
962 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -0800963 {
964 try {
Hans Boehmc023b732015-04-29 11:30:47 -0700965 int prefixLen = required ? mExpr.size() : trailingOpsStart();
966 EvalContext ec = new EvalContext(degreeMode, prefixLen);
Hans Boehm84614952014-11-25 18:46:17 -0800967 EvalRet res = evalExpr(0, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700968 if (res.mPos != prefixLen) {
969 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -0700970 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700971 return new EvalResult(res.mVal, res.mRatVal);
Hans Boehm84614952014-11-25 18:46:17 -0800972 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -0700973 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -0800974 }
975 }
976
977 // Produce a string representation of the expression itself
978 String toString(Context context) {
979 StringBuilder sb = new StringBuilder();
980 for (Token t: mExpr) {
981 sb.append(t.toString(context));
982 }
983 return sb.toString();
984 }
985}