blob: c5ad301715b790f0b0124b51d10abd639df03138 [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
308 TokenKind kind() { return TokenKind.PRE_EVAL; }
309 }
310
311 static Token newToken(DataInput in) throws IOException {
312 TokenKind kind = tokenKindValues[in.readByte()];
313 switch(kind) {
314 case CONSTANT:
315 return new Constant(in);
316 case OPERATOR:
317 return new Operator(in);
318 case PRE_EVAL:
319 return new PreEval(in);
320 default: throw new IOException("Bad save file format");
321 }
322 }
323
324 CalculatorExpr() {
325 mExpr = new ArrayList<Token>();
326 }
327
328 private CalculatorExpr(ArrayList<Token> expr) {
329 mExpr = expr;
330 }
331
332 CalculatorExpr(DataInput in) throws IOException {
333 mExpr = new ArrayList<Token>();
334 int size = in.readInt();
335 for (int i = 0; i < size; ++i) {
336 mExpr.add(newToken(in));
337 }
338 }
339
340 void write(DataOutput out) throws IOException {
341 int size = mExpr.size();
342 out.writeInt(size);
343 for (int i = 0; i < size; ++i) {
344 mExpr.get(i).write(out);
345 }
346 }
347
348 private boolean hasTrailingBinary() {
349 int s = mExpr.size();
350 if (s == 0) return false;
351 Token t = mExpr.get(s-1);
352 if (!(t instanceof Operator)) return false;
353 Operator o = (Operator)t;
354 return (KeyMaps.isBinary(o.mId));
355 }
356
357 // Append press of button with given id to expression.
358 // Returns false and does nothing if this would clearly
359 // result in a syntax error.
360 boolean add(int id) {
361 int s = mExpr.size();
362 int d = KeyMaps.digVal(id);
363 boolean binary = KeyMaps.isBinary(id);
364 if (s == 0 && binary && id != R.id.op_sub) return false;
365 if (binary && hasTrailingBinary()
Hans Boehmc023b732015-04-29 11:30:47 -0700366 && (id != R.id.op_sub || isOperatorUnchecked(s-1, R.id.op_sub))) {
Hans Boehm84614952014-11-25 18:46:17 -0800367 return false;
368 }
369 boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
370 if (isConstPiece) {
371 if (s == 0) {
372 mExpr.add(new Constant());
373 s++;
374 } else {
375 Token last = mExpr.get(s-1);
376 if(!(last instanceof Constant)) {
377 if (!(last instanceof Operator)) {
378 return false;
379 }
380 int lastOp = ((Operator)last).mId;
381 if (lastOp == R.id.const_e || lastOp == R.id.const_pi
382 || lastOp == R.id.op_fact
383 || lastOp == R.id.rparen) {
384 // Constant cannot possibly follow; reject immediately
385 return false;
386 }
387 mExpr.add(new Constant());
388 s++;
389 }
390 }
391 return ((Constant)(mExpr.get(s-1))).add(id);
392 } else {
393 mExpr.add(new Operator(id));
394 return true;
395 }
396 }
397
398 // Append the contents of the argument expression.
399 // It is assumed that the argument expression will not change,
400 // and thus its pieces can be reused directly.
401 // TODO: We probably only need this for expressions consisting of
402 // a single PreEval "token", and may want to check that.
403 void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700404 // Check that we're not concatenating Constant or PreEval
405 // tokens, since the result would look like a single constant
406 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800407 int s2 = expr2.mExpr.size();
Hans Boehmfbcef702015-04-27 18:07:47 -0700408 // Check that we're not concatenating Constant or PreEval
409 // tokens, since the result would look like a single constant,
410 // with very mysterious results for the user.
411 if (s != 0 && s2 != 0) {
412 Token last = mExpr.get(s-1);
413 Token first = expr2.mExpr.get(0);
414 if (!(first instanceof Operator) && !(last instanceof Operator)) {
415 // Fudge it by adding an explicit multiplication.
416 // We would have interpreted it as such anyway, and this
417 // makes it recognizable to the user.
418 mExpr.add(new Operator(R.id.op_mul));
419 }
420 }
Hans Boehm84614952014-11-25 18:46:17 -0800421 for (int i = 0; i < s2; ++i) {
422 mExpr.add(expr2.mExpr.get(i));
423 }
424 }
425
426 // Undo the last key addition, if any.
427 void delete() {
428 int s = mExpr.size();
429 if (s == 0) return;
430 Token last = mExpr.get(s-1);
431 if (last instanceof Constant) {
432 Constant c = (Constant)last;
433 c.delete();
434 if (!c.isEmpty()) return;
435 }
436 mExpr.remove(s-1);
437 }
438
439 void clear() {
440 mExpr.clear();
441 }
442
443 boolean isEmpty() {
444 return mExpr.isEmpty();
445 }
446
447 // Returns a logical deep copy of the CalculatorExpr.
448 // Operator and PreEval tokens are immutable, and thus
449 // aren't really copied.
450 public Object clone() {
451 CalculatorExpr res = new CalculatorExpr();
452 for (Token t: mExpr) {
453 if (t instanceof Constant) {
454 res.mExpr.add((Token)(((Constant)t).clone()));
455 } else {
456 res.mExpr.add(t);
457 }
458 }
459 return res;
460 }
461
462 // Am I just a constant?
463 boolean isConstant() {
464 if (mExpr.size() != 1) return false;
465 return mExpr.get(0) instanceof Constant;
466 }
467
468 // Return a new expression consisting of a single PreEval token
469 // representing the current expression.
470 // The caller supplies the value, degree mode, and short
471 // string representation, which must have been previously computed.
472 // Thus this is guaranteed to terminate reasonably quickly.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700473 CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800474 boolean dm, String sr) {
475 CalculatorExpr result = new CalculatorExpr();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700476 Token t = new PreEval(val, ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800477 new CalculatorExpr(
478 (ArrayList<Token>)mExpr.clone()),
Hans Boehmc023b732015-04-29 11:30:47 -0700479 new EvalContext(dm, mExpr.size()), sr);
Hans Boehm84614952014-11-25 18:46:17 -0800480 result.mExpr.add(t);
481 return result;
482 }
483
484 // Internal evaluation functions return an EvalRet triple.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700485 // We compute rational (BoundedRational) results when possible, both as
Hans Boehm84614952014-11-25 18:46:17 -0800486 // a performance optimization, and to detect errors exactly when we can.
487 private class EvalRet {
488 int mPos; // Next position (expression index) to be parsed
489 final CR mVal; // Constructive Real result of evaluating subexpression
Hans Boehm682ff5e2015-03-09 14:40:25 -0700490 final BoundedRational mRatVal; // Exact Rational value or null if
491 // irrational or hard to compute.
492 EvalRet(int p, CR v, BoundedRational r) {
Hans Boehm84614952014-11-25 18:46:17 -0800493 mPos = p;
494 mVal = v;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700495 mRatVal = r;
Hans Boehm84614952014-11-25 18:46:17 -0800496 }
497 }
498
499 // And take a context argument:
500 private static class EvalContext {
Hans Boehmc023b732015-04-29 11:30:47 -0700501 public final int mPrefixLength; // Length of prefix to evaluate.
502 // Not explicitly saved.
503 public final boolean mDegreeMode;
Hans Boehm84614952014-11-25 18:46:17 -0800504 // If we add any other kinds of evaluation modes, they go here.
Hans Boehmc023b732015-04-29 11:30:47 -0700505 EvalContext(boolean degreeMode, int len) {
Hans Boehm84614952014-11-25 18:46:17 -0800506 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700507 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800508 }
Hans Boehmc023b732015-04-29 11:30:47 -0700509 EvalContext(DataInput in, int len) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800510 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700511 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800512 }
513 void write(DataOutput out) throws IOException {
514 out.writeBoolean(mDegreeMode);
515 }
516 }
517
518 private final CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
519
520 private final CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
521
522 private CR toRadians(CR x, EvalContext ec) {
523 if (ec.mDegreeMode) {
524 return x.multiply(RADIANS_PER_DEGREE);
525 } else {
526 return x;
527 }
528 }
529
530 private CR fromRadians(CR x, EvalContext ec) {
531 if (ec.mDegreeMode) {
532 return x.multiply(DEGREES_PER_RADIAN);
533 } else {
534 return x;
535 }
536 }
537
538 // The following methods can all throw IndexOutOfBoundsException
539 // in the event of a syntax error. We expect that to be caught in
540 // eval below.
541
Hans Boehmc023b732015-04-29 11:30:47 -0700542 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800543 Token t = mExpr.get(i);
544 if (!(t instanceof Operator)) return false;
545 return ((Operator)(t)).mId == op;
546 }
547
Hans Boehmc023b732015-04-29 11:30:47 -0700548 private boolean isOperator(int i, int op, EvalContext ec) {
549 if (i >= ec.mPrefixLength) return false;
550 return isOperatorUnchecked(i, op);
551 }
552
553 static class SyntaxException extends Exception {
554 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800555 super();
556 }
Hans Boehmc023b732015-04-29 11:30:47 -0700557 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800558 super(s);
559 }
560 }
561
562 // The following functions all evaluate some kind of expression
563 // starting at position i in mExpr in a specified evaluation context.
564 // They return both the expression value (as constructive real and,
565 // if applicable, as BigInteger) and the position of the next token
566 // that was not used as part of the evaluation.
Hans Boehmc023b732015-04-29 11:30:47 -0700567 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800568 Token t = mExpr.get(i);
569 CR value;
570 if (t instanceof Constant) {
571 Constant c = (Constant)t;
Hans Boehm013969e2015-04-13 20:29:47 -0700572 value = CR.valueOf(c.toEasyString(),10);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700573 return new EvalRet(i+1, value, c.toRational());
Hans Boehm84614952014-11-25 18:46:17 -0800574 }
575 if (t instanceof PreEval) {
576 PreEval p = (PreEval)t;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700577 return new EvalRet(i+1, p.mValue, p.mRatValue);
Hans Boehm84614952014-11-25 18:46:17 -0800578 }
579 EvalRet argVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700580 BoundedRational ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800581 switch(((Operator)(t)).mId) {
582 case R.id.const_pi:
583 return new EvalRet(i+1, CR.PI, null);
584 case R.id.const_e:
Hans Boehm4db31b42015-05-31 12:19:05 -0700585 return new EvalRet(i+1, REAL_E, null);
Hans Boehm84614952014-11-25 18:46:17 -0800586 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700587 // Seems to have highest precedence.
588 // Does not add implicit paren.
589 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700590 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700591 argVal = evalUnary(i+2, ec);
592 ratVal = BoundedRational.sqrt(
593 BoundedRational.negate(argVal.mRatVal));
594 if (ratVal != null) break;
595 return new EvalRet(argVal.mPos,
596 argVal.mVal.negate().sqrt(), null);
597 } else {
598 argVal = evalUnary(i+1, ec);
599 ratVal = BoundedRational.sqrt(argVal.mRatVal);
600 if (ratVal != null) break;
601 return new EvalRet(argVal.mPos, argVal.mVal.sqrt(), null);
602 }
Hans Boehm84614952014-11-25 18:46:17 -0800603 case R.id.lparen:
604 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700605 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700606 return new EvalRet(argVal.mPos, argVal.mVal, argVal.mRatVal);
Hans Boehm84614952014-11-25 18:46:17 -0800607 case R.id.fun_sin:
608 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700609 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700610 ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700611 : BoundedRational.sin(argVal.mRatVal);
612 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800613 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700614 toRadians(argVal.mVal,ec).sin(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800615 case R.id.fun_cos:
616 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700617 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700618 ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700619 : BoundedRational.cos(argVal.mRatVal);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700620 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800621 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700622 toRadians(argVal.mVal,ec).cos(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800623 case R.id.fun_tan:
624 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700625 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700626 ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700627 : BoundedRational.tan(argVal.mRatVal);
628 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800629 CR argCR = toRadians(argVal.mVal, ec);
630 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700631 argCR.sin().divide(argCR.cos()), null);
Hans Boehm84614952014-11-25 18:46:17 -0800632 case R.id.fun_ln:
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 ratVal = BoundedRational.ln(argVal.mRatVal);
636 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800637 return new EvalRet(argVal.mPos, argVal.mVal.ln(), null);
Hans Boehm4db31b42015-05-31 12:19:05 -0700638 case R.id.fun_exp:
639 argVal = evalExpr(i+1, ec);
640 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
641 ratVal = BoundedRational.exp(argVal.mRatVal);
642 if (ratVal != null) break;
643 return new EvalRet(argVal.mPos, argVal.mVal.exp(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800644 case R.id.fun_log:
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 Boehm682ff5e2015-03-09 14:40:25 -0700647 ratVal = BoundedRational.log(argVal.mRatVal);
648 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800649 return new EvalRet(argVal.mPos,
650 argVal.mVal.ln().divide(CR.valueOf(10).ln()),
651 null);
652 case R.id.fun_arcsin:
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.degreeAsin(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700656 : BoundedRational.asin(argVal.mRatVal);
657 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800658 return new EvalRet(argVal.mPos,
659 fromRadians(UnaryCRFunction
660 .asinFunction.execute(argVal.mVal),ec),
661 null);
662 case R.id.fun_arccos:
663 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700664 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700665 ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700666 : BoundedRational.acos(argVal.mRatVal);
667 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800668 return new EvalRet(argVal.mPos,
669 fromRadians(UnaryCRFunction
670 .acosFunction.execute(argVal.mVal),ec),
671 null);
672 case R.id.fun_arctan:
673 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700674 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700675 ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700676 : BoundedRational.atan(argVal.mRatVal);
677 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800678 return new EvalRet(argVal.mPos,
679 fromRadians(UnaryCRFunction
680 .atanFunction.execute(argVal.mVal),ec),
681 null);
682 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700683 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800684 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700685 // We have a rational value.
686 return new EvalRet(argVal.mPos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800687 }
688
689 // Compute an integral power of a constructive real.
690 // Unlike the "general" case using logarithms, this handles a negative
691 // base.
692 private static CR pow(CR base, BigInteger exp) {
693 if (exp.compareTo(BigInteger.ZERO) < 0) {
694 return pow(base, exp.negate()).inverse();
695 }
696 if (exp.equals(BigInteger.ONE)) return base;
697 if (exp.and(BigInteger.ONE).intValue() == 1) {
698 return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
699 }
700 if (exp.equals(BigInteger.ZERO)) {
701 return CR.valueOf(1);
702 }
703 CR tmp = pow(base, exp.shiftRight(1));
704 return tmp.multiply(tmp);
705 }
706
Hans Boehm682ff5e2015-03-09 14:40:25 -0700707 private static final int TEST_PREC = -100;
708 // Test for integer-ness to 100 bits past binary point.
709 private static final BigInteger MASK =
710 BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
Hans Boehm4db31b42015-05-31 12:19:05 -0700711 private static final CR REAL_E = CR.valueOf(1).exp();
712 private static final CR REAL_ONE_HUNDREDTH = CR.valueOf(100).inverse();
713 private static final BoundedRational RATIONAL_ONE_HUNDREDTH =
714 new BoundedRational(1,100);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700715 private static boolean isApprInt(CR x) {
716 BigInteger appr = x.get_appr(TEST_PREC);
717 return appr.and(MASK).signum() == 0;
718 }
719
Hans Boehm4db31b42015-05-31 12:19:05 -0700720 private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800721 EvalRet tmp = evalUnary(i, ec);
722 int cpos = tmp.mPos;
723 CR cval = tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700724 BoundedRational ratVal = tmp.mRatVal;
Hans Boehm4db31b42015-05-31 12:19:05 -0700725 boolean isFact;
726 boolean isSquared = false;
727 while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
728 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
729 isOperator(cpos, R.id.op_pct, ec)) {
730 if (isFact) {
731 if (ratVal == null) {
732 // Assume it was an integer, but we
733 // didn't figure it out.
734 // KitKat may have used the Gamma function.
735 if (!isApprInt(cval)) {
736 throw new ArithmeticException("factorial(non-integer)");
737 }
738 ratVal = new BoundedRational(cval.BigIntegerValue());
Hans Boehm682ff5e2015-03-09 14:40:25 -0700739 }
Hans Boehm4db31b42015-05-31 12:19:05 -0700740 ratVal = BoundedRational.fact(ratVal);
741 cval = ratVal.CRValue();
742 } else if (isSquared) {
743 ratVal = BoundedRational.multiply(ratVal, ratVal);
744 if (ratVal == null) {
745 cval = cval.multiply(cval);
746 } else {
747 cval = ratVal.CRValue();
748 }
749 } else /* percent */ {
750 ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH);
751 if (ratVal == null) {
752 cval = cval.multiply(REAL_ONE_HUNDREDTH);
753 } else {
754 cval = ratVal.CRValue();
755 }
Hans Boehm84614952014-11-25 18:46:17 -0800756 }
Hans Boehm84614952014-11-25 18:46:17 -0800757 ++cpos;
758 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700759 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800760 }
761
Hans Boehmc023b732015-04-29 11:30:47 -0700762 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm4db31b42015-05-31 12:19:05 -0700763 final EvalRet result1 = evalSuffix(i, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800764 int cpos = result1.mPos; // current position
765 CR cval = result1.mVal; // value so far
Hans Boehm682ff5e2015-03-09 14:40:25 -0700766 BoundedRational ratVal = result1.mRatVal; // int value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700767 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm84614952014-11-25 18:46:17 -0800768 final EvalRet exp = evalSignedFactor(cpos+1, ec);
769 cpos = exp.mPos;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700770 // Try completely rational evaluation first.
771 ratVal = BoundedRational.pow(ratVal, exp.mRatVal);
772 if (ratVal != null) {
773 return new EvalRet(cpos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800774 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700775 // Power with integer exponent is defined for negative base.
776 // Thus we handle that case separately.
777 // We punt if the exponent is an integer computed from irrational
778 // values. That wouldn't work reliably with floating point either.
779 BigInteger int_exp = BoundedRational.asBigInteger(exp.mRatVal);
780 if (int_exp != null) {
781 cval = pow(cval, int_exp);
Hans Boehm84614952014-11-25 18:46:17 -0800782 } else {
Hans Boehm84614952014-11-25 18:46:17 -0800783 cval = cval.ln().multiply(exp.mVal).exp();
784 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700785 ratVal = null;
Hans Boehm84614952014-11-25 18:46:17 -0800786 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700787 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800788 }
789
Hans Boehmc023b732015-04-29 11:30:47 -0700790 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
791 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700792 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800793 EvalRet tmp = evalFactor(cpos, ec);
794 cpos = tmp.mPos;
Hans Boehm08e8f322015-04-21 13:18:38 -0700795 CR cval = negative ? tmp.mVal.negate() : tmp.mVal;
796 BoundedRational ratVal = negative ? BoundedRational.negate(tmp.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700797 : tmp.mRatVal;
798 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800799 }
800
801 private boolean canStartFactor(int i) {
802 if (i >= mExpr.size()) return false;
803 Token t = mExpr.get(i);
804 if (!(t instanceof Operator)) return true;
805 int id = ((Operator)(t)).mId;
806 if (KeyMaps.isBinary(id)) return false;
807 switch (id) {
808 case R.id.op_fact:
809 case R.id.rparen:
810 return false;
811 default:
812 return true;
813 }
814 }
815
Hans Boehmc023b732015-04-29 11:30:47 -0700816 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800817 EvalRet tmp = evalSignedFactor(i, ec);
818 boolean is_mul = false;
819 boolean is_div = false;
820 int cpos = tmp.mPos; // Current position in expression.
821 CR cval = tmp.mVal; // Current value.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700822 BoundedRational ratVal = tmp.mRatVal; // Current rational value.
Hans Boehmc023b732015-04-29 11:30:47 -0700823 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
824 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -0800825 || canStartFactor(cpos)) {
826 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700827 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800828 if (is_div) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700829 ratVal = BoundedRational.divide(ratVal, tmp.mRatVal);
830 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800831 cval = cval.divide(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700832 } else {
833 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800834 }
835 } else {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700836 ratVal = BoundedRational.multiply(ratVal, tmp.mRatVal);
837 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800838 cval = cval.multiply(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700839 } else {
840 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800841 }
842 }
843 cpos = tmp.mPos;
844 is_mul = is_div = false;
845 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700846 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800847 }
848
Hans Boehmc023b732015-04-29 11:30:47 -0700849 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800850 EvalRet tmp = evalTerm(i, ec);
851 boolean is_plus;
852 int cpos = tmp.mPos;
853 CR cval = tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700854 BoundedRational ratVal = tmp.mRatVal;
Hans Boehmc023b732015-04-29 11:30:47 -0700855 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
856 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm84614952014-11-25 18:46:17 -0800857 tmp = evalTerm(cpos+1, ec);
858 if (is_plus) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700859 ratVal = BoundedRational.add(ratVal, tmp.mRatVal);
860 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800861 cval = cval.add(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700862 } else {
863 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800864 }
865 } else {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700866 ratVal = BoundedRational.subtract(ratVal, tmp.mRatVal);
867 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800868 cval = cval.subtract(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700869 } else {
870 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800871 }
872 }
873 cpos = tmp.mPos;
874 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700875 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800876 }
877
878 // Externally visible evaluation result.
879 public class EvalResult {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700880 EvalResult (CR val, BoundedRational ratVal) {
Hans Boehm84614952014-11-25 18:46:17 -0800881 mVal = val;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700882 mRatVal = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800883 }
884 final CR mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700885 final BoundedRational mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800886 }
887
Hans Boehmc023b732015-04-29 11:30:47 -0700888 // Return the starting position of the sequence of trailing operators
889 // that cannot be meaningfully evaluated.
890 private int trailingOpsStart() {
891 int result = mExpr.size();
892 while (result > 0) {
893 Token last = mExpr.get(result - 1);
894 if (!(last instanceof Operator)) break;
895 Operator o = (Operator)last;
896 if (KeyMaps.isSuffix(o.mId) || o.mId == R.id.const_pi
897 || o.mId == R.id.const_e) {
898 break;
899 }
900 --result;
901 }
902 return result;
903 }
904
905 public boolean hasTrailingOperators() {
906 return trailingOpsStart() != mExpr.size();
907 }
908
909 // Is the current expression worth evaluating?
910 public boolean hasInterestingOps() {
911 int last = trailingOpsStart();
912 int first = 0;
913 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
914 // Leading minus is not by itself interesting.
915 first++;
916 }
917 for (int i = first; i < last; ++i) {
918 Token t1 = mExpr.get(i);
919 if (!(t1 instanceof Constant)) return true;
920 // We consider preevaluated expressions "interesting",
921 // since the evaluation will usually result in more precision
922 // than the "short representation".
923 }
924 return false;
925 }
926
Hans Boehm84614952014-11-25 18:46:17 -0800927 // Evaluate the entire expression, returning null in the event
928 // of an error.
929 // Not called from the UI thread, but should not be called
930 // concurrently with modifications to the expression.
Hans Boehmc023b732015-04-29 11:30:47 -0700931 EvalResult eval(boolean degreeMode, boolean required) throws SyntaxException
932 // And unchecked exceptions thrown by CR
933 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -0800934 {
935 try {
Hans Boehmc023b732015-04-29 11:30:47 -0700936 int prefixLen = required ? mExpr.size() : trailingOpsStart();
937 EvalContext ec = new EvalContext(degreeMode, prefixLen);
Hans Boehm84614952014-11-25 18:46:17 -0800938 EvalRet res = evalExpr(0, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700939 if (res.mPos != prefixLen) {
940 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -0700941 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700942 return new EvalResult(res.mVal, res.mRatVal);
Hans Boehm84614952014-11-25 18:46:17 -0800943 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -0700944 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -0800945 }
946 }
947
948 // Produce a string representation of the expression itself
949 String toString(Context context) {
950 StringBuilder sb = new StringBuilder();
951 for (Token t: mExpr) {
952 sb.append(t.toString(context));
953 }
954 return sb.toString();
955 }
956}