1 package org.djutils.eval;
2
3 import java.lang.reflect.Field;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.HashMap;
7 import java.util.List;
8 import java.util.Map;
9
10 import org.djunits.unit.AbsoluteLinearUnit;
11 import org.djunits.unit.DimensionlessUnit;
12 import org.djunits.unit.TimeUnit;
13 import org.djunits.unit.Unit;
14 import org.djunits.unit.si.SIDimensions;
15 import org.djunits.value.vdouble.scalar.Dimensionless;
16 import org.djunits.value.vdouble.scalar.SIScalar;
17 import org.djunits.value.vdouble.scalar.Time;
18 import org.djunits.value.vdouble.scalar.base.Constants;
19 import org.djunits.value.vdouble.scalar.base.DoubleScalar;
20 import org.djunits.value.vdouble.scalar.base.DoubleScalarAbs;
21 import org.djunits.value.vdouble.scalar.base.DoubleScalarRel;
22 import org.djutils.exceptions.Throw;
23 import org.djutils.metadata.MetaData;
24 import org.djutils.metadata.ObjectDescriptor;
25
26
27
28
29
30
31
32
33
34
35
36
37 public class Eval
38 {
39
40 private List<Object> stack = new ArrayList<>();
41
42
43 private String expression;
44
45
46 private int position = 0;
47
48
49 private RetrieveValue retrieveValue = null;
50
51
52 private static final int BIND_CONDITIONAL_EXPRESSION = 1;
53
54
55 private static final int BIND_OR = 2;
56
57
58 private static final int BIND_AND = 3;
59
60
61 private static final int BIND_EQUAL = 4;
62
63
64 private static final int BIND_RELATIONAL = 5;
65
66
67 private static final int BIND_ADD = 6;
68
69
70 private static final int BIND_MUL = 7;
71
72
73 private static final int BIND_POW = 8;
74
75
76 private static final int BIND_UMINUS = 9;
77
78
79 private static final ObjectDescriptor[] noArguments = new ObjectDescriptor[] {};
80
81
82
83 private final Function[] builtinFunctions = new Function[] {
84 new F0("AVOGADRO", Constants.AVOGADRO, new MetaData("Avogadro constant", "Avogadro constant in 1/mol", noArguments)),
85 new F0("BOLTZMANN", Constants.BOLTZMANN, new MetaData("Boltzmann constant",
86 "The exact value of the Boltzmann constant in Joule per Kelvin", noArguments)),
87 new F0("CESIUM133_FREQUENCY", Constants.CESIUM133_FREQUENCY, new MetaData("Cesium 133 frequency",
88 "The exact value of the Cesium 133 ground state hyperfine structure transition frequency", noArguments)),
89 new F0("CURRENTTIME", Time.ZERO.getClass(),
90 new MetaData("The current time in seconds since 1970 UTC",
91 "The current time in seconds since 1970 UTC to the nearest ms as reported by the operating system", noArguments),
92 (f) -> new Time(System.currentTimeMillis() / 1000d, TimeUnit.BASE_SECOND)),
93 new F0("E", Constants.E, new MetaData("Euler\'s constant e", "Euler\'s constant e; the base of the natural logarithm")),
94 new F0("ELECTRONCHARGE", Constants.ELECTRONCHARGE, new MetaData("Electrical charge of one electron",
95 "The exact electrical charge of one electron", noArguments)),
96 new F0("ELECTRONMASS", Constants.ELECTRONMASS, new MetaData("Mass of an electron",
97 "Mass of an electron, the value of this physical constant has an uncertainty of 2.8e-40 kg", noArguments)),
98 new F0("G", Constants.G, new MetaData("Gravitational constant",
99 "Gravitational constant, a.k.a. Newtonian constant of gravitation. This is the 2018 best known approximation, which has an "
100 + "uncertainty 1.5e-15 m^3/kgs^2", noArguments)),
101 new F0("LIGHTSPEED", Constants.LIGHTSPEED, new MetaData("Speed of light in vacuum", "The exact speed of light in vacuum", noArguments)),
102 new F0("LUMINOUS_EFFICACY_540THZ", Constants.LUMINOUS_EFFICACY_540THZ, new MetaData(
103 "The luminous efficacy Kcd of monochromatic radiation of frequency 540×10^12 Hz (540 THz)",
104 "The exact luminous efficacy Kcd of monochromatic radiation of frequency 540×10^12 Hz (540 THz)", noArguments)),
105 new F0("NEUTRONMASS", Constants.NEUTRONMASS, new MetaData("Mass of a neutron",
106 "Mass of a neutron. The value of this physical constant has an uncertainty of 9.5e-37 kg.", noArguments)),
107 new F0("PI", Constants.PI, new MetaData("Ratio of a half circumference of a circle and its radius",
108 "Ratio of a half circumference of a circle and its radius", noArguments)),
109 new F0("PHI", Constants.PHI, new MetaData("Golden ratio", "Golden ratio", noArguments)),
110 new F0("PLANCK", Constants.PLANCK, new MetaData("Planck constant; ratio of a photon's energy and its frequency",
111 "Planck constant; the exact ratio of a photon's energy and its frequency", noArguments)),
112 new F0("PLANCKREDUCED", Constants.PLANCKREDUCED, new MetaData("Reduced Planck constant",
113 "Reduced Planck constant, a.k.a. angular Planck constant; Planck constant divided by 2 pi" ,noArguments)),
114 new F0("PROTONCHARGE", Constants.PROTONCHARGE, new MetaData("ElectricalCharge of one proton", "ElectricalCharge of one proton", noArguments)),
115 new F0("PROTONMASS", Constants.PROTONMASS, new MetaData("Mass of a proton",
116 "Mass of a proton. The value of this physical constant has an uncertainty of 5.1e-37", noArguments)),
117 new F0("TAU", Constants.TAU, new MetaData("Ratio of circumference of circle and its radius",
118 "Ratio of circumference of circle and its radius", noArguments)),
119 new F0("VACUUMIMPEDANCE", Constants.VACUUMIMPEDANCE, new MetaData("Impedance of vacuum", "Impedance of vacuum", noArguments)),
120 new F0("VACUUMPERMEABILITY", Constants.VACUUMPERMEABILITY, new MetaData("Permeability of vacuum",
121 "Permeability of vacuum. The uncertainty of this value is 1.9e-16N/A^2", noArguments)),
122 new F0("VACUUMPERMITTIVITY", Constants.VACUUMPERMITTIVITY, new MetaData("Permittivity of vacuum.",
123 "Permittivity of vacuum. The uncertainty of this value is 1.3e-21 F/m.", noArguments)),
124 new F0("TRUE", Boolean.TRUE, new MetaData("The logical value TRUE", "The logical value TRUE", noArguments)),
125 new F0("FALSE", Boolean.FALSE, new MetaData("The logical value FALSE", "The logical value FALSE", noArguments)),
126 new F1("acos", Dimensionless.class, new MetaData("acos", "returns the angle of which the cosine equals the value of the argument",
127 new ObjectDescriptor("angle", "angle", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).acos()),
128 new F1("asin", Dimensionless.class, new MetaData("asin", "returns the angle of which the sine equals the value of the argument",
129 new ObjectDescriptor("angle", "angle", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).asin()),
130 new F1("atan", Dimensionless.class, new MetaData("atan", "returns the angle of which the tangent equals the value of the argument",
131 new ObjectDescriptor("angle", "angle", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).atan()),
132 new F1("cbrt", Dimensionless.class, new MetaData("cbrt", "returns the cubic root of the value of the argument",
133 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).cbrt()),
134 new F1("cos", Dimensionless.class, new MetaData("cos", "returns the cosine of the value of the argument",
135 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).cos()),
136 new F1("cosh", Dimensionless.class, new MetaData("cosh", "returns the hyperbolic cosine of the value of the argument",
137 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).cosh()),
138 new F1("exp", Dimensionless.class, new MetaData("exp", "returns e to the power of the argument",
139 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).exp()),
140 new F1("expm1", Dimensionless.class, new MetaData("expm1", "returns e to the power of the argument minus 1",
141 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).expm1()),
142 new F1("log", Dimensionless.class, new MetaData("log", "returns natural logarithm (logarithm base e) of the argument",
143 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).log()),
144 new F1("log10", Dimensionless.class, new MetaData("log10", "returns logarithm base 10 of the argument",
145 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).log10()),
146 new F1("log1p", Dimensionless.class, new MetaData("log1p", "returns natural logarithm (logarithm base e) of the argument plus 1",
147 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).log1p()),
148 new F1("signum", Dimensionless.class, new MetaData("signum", "returns sign of the argument (1 if positive, -1 if negative, 0 if zero)",
149 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).signum()),
150 new F1("sin", Dimensionless.class, new MetaData("cos", "returns the sine of the value of the argument",
151 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).sin()),
152 new F1("sinh", Dimensionless.class, new MetaData("cosh", "returns the hyperbolic sine of the value of the argument",
153 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).sinh()),
154 new F1("sqrt", Dimensionless.class, new MetaData("cos", "returns the square root of the value of the argument",
155 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).sqrt()),
156 new F1("tan", Dimensionless.class, new MetaData("cos", "returns the tangent of the value of the argument",
157 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).tan()),
158 new F1("tanh", Dimensionless.class, new MetaData("cosh", "returns the hyperbolic tangent of the value of the argument",
159 new ObjectDescriptor("value", "value", Dimensionless.class)), (i, a) -> checkDimensionless(i, a).tanh()),
160 new F2("pow", new MetaData("pow", "raises the first argument to the power of the second argument",
161 new ObjectDescriptor("base", "base", Dimensionless.class),
162 new ObjectDescriptor("exponent", "exponent", Dimensionless.class)),
163 (i, b, p)-> performPower(b, p) ),
164 new F2("atan2", new MetaData("atan2",
165 "atan2 function (needs two DoubleScalarRel parameters that have the same SI dimensions)",
166 new ObjectDescriptor("y", "y", DoubleScalarRel.class),
167 new ObjectDescriptor("x", "x", DoubleScalarRel.class)), (i, y, x) -> performAtan2(y, x)),
168 };
169
170
171
172 private Map<String, Function> functionData;
173
174 {
175 this.functionData = new HashMap<>();
176 for (Function function : this.builtinFunctions)
177 {
178 this.functionData.put(function.getId(), function);
179 }
180 }
181
182
183 private Map<String, Function> userDefinedFunctions = null;
184
185
186 private UnitParser userSuppliedUnitParser = null;
187
188
189 private Map<Class<?>, SIDimensions> siDimensionsMap = new HashMap<>();
190
191
192
193
194 public Eval()
195 {
196
197 }
198
199
200
201
202
203
204 public Eval setRetrieveValue(final RetrieveValue retrieveValue)
205 {
206 this.retrieveValue = retrieveValue;
207 return this;
208 }
209
210
211
212
213
214
215
216 public Eval setUnitParser(final UnitParser unitParser)
217 {
218 this.userSuppliedUnitParser = unitParser;
219 return this;
220 }
221
222
223
224
225
226
227
228 public Object evaluate(final String expression) throws RuntimeException
229 {
230 return evaluateExpression(expression);
231 }
232
233
234
235
236
237
238
239 public Boolean evaluateAsBoolean(final String expression) throws RuntimeException
240 {
241 Object result = evaluateExpression(expression);
242 if (!(result instanceof Boolean))
243 {
244 throwException("Result " + result + " can not be cast to a Boolean");
245 }
246 return ((Boolean) result);
247 }
248
249
250
251
252
253
254
255
256 public double evaluateAsDouble(final String expression) throws RuntimeException
257 {
258 Object result = evaluateExpression(expression);
259 if (!(result instanceof DoubleScalar<?, ?>))
260 {
261 throwException("Result " + result + " can not be cast to a double");
262 }
263 return ((DoubleScalar<?, ?>) result).si;
264 }
265
266
267
268
269
270 public Collection<Function> builtInFunctions()
271 {
272 return this.functionData.values();
273 }
274
275
276
277
278
279
280
281 public Eval setUserDefinedFunctions(final Map<String, Function> userDefinedFunctionMap)
282 {
283 this.userDefinedFunctions = userDefinedFunctionMap;
284 return this;
285 }
286
287
288
289
290
291
292
293 public Object evaluateExpression(final String expression) throws RuntimeException
294 {
295 Throw.whenNull(expression, "expression may not be null");
296 Throw.when(expression.length() == 0, IllegalArgumentException.class, "Expression may not be the empty string");
297 String savedExpression = this.expression;
298 int savedPosition = this.position;
299 List<Object> savedStack = new ArrayList<>(this.stack);
300 try
301 {
302 this.expression = expression;
303 this.position = 0;
304 this.stack.clear();
305 eatSpace();
306 evalLhs(0);
307 if (this.position < this.expression.length())
308 {
309 this.throwException("Trailing garbage: \"" + this.expression.substring(this.position) + "\"");
310 }
311 if (this.stack.size() > 1)
312 {
313 this.throwException("Unfinished operations");
314 }
315 if (this.stack.size() <= 0)
316 {
317 this.throwException("No result after evaluation");
318 }
319 return pop();
320 }
321 finally
322 {
323 this.expression = savedExpression;
324 this.position = savedPosition;
325 this.stack = savedStack;
326 }
327 }
328
329
330
331
332 private void eatSpace()
333 {
334 while (this.position < this.expression.length() && Character.isWhitespace(this.expression.charAt(this.position)))
335 {
336 this.position++;
337 }
338 }
339
340
341
342
343
344
345 private void evalLhs(final int bindingStrength) throws RuntimeException
346 {
347 eatSpace();
348 if (this.position >= this.expression.length())
349 {
350 throwException("Missing operand");
351 }
352 char token = this.expression.charAt(this.position);
353 switch (token)
354 {
355 case '-':
356 {
357 this.position++;
358 evalLhs(BIND_UMINUS);
359 Object value = pop();
360 if (value instanceof DoubleScalar<?, ?>)
361 {
362 push(((DoubleScalar<?, ?>) value).neg());
363 break;
364 }
365 throwException("Cannot apply unary minus on " + value);
366 }
367
368 case '!':
369 if (this.position >= this.expression.length() - 1 || '=' != this.expression.charAt(this.position + 1))
370 {
371
372 this.position++;
373 evalLhs(BIND_UMINUS);
374 Object value = pop();
375 if (value instanceof Boolean)
376 {
377 push(!((Boolean) value));
378 break;
379 }
380 throwException("Cannot apply unary not operator on " + value);
381 }
382 break;
383
384 case '(':
385 this.position++;
386 evalLhs(0);
387 if (this.position >= this.expression.length() || ')' != this.expression.charAt(this.position))
388 {
389 throwException("Missing closing parenthesis");
390 }
391 this.position++;
392 break;
393
394 default:
395 {
396 if (Character.isLetter(token) || '#' == token || '@' == token || '_' == token)
397 {
398 push(handleFunctionOrVariableOrNamedConstant());
399 }
400 else if (Character.isDigit(token) || '.' == token)
401 {
402 push(handleNumber());
403 }
404 }
405 }
406 evalRhs(bindingStrength);
407 }
408
409
410
411
412
413
414
415
416 private void evalRhs(final int bindingStrength) throws RuntimeException
417 {
418 eatSpace();
419 while (this.position < this.expression.length())
420 {
421 char token = this.expression.charAt(this.position);
422 switch (token)
423 {
424 case ')':
425 return;
426
427 case '^':
428 if (this.stack.isEmpty())
429 {
430 throwException("Missing left operand");
431 }
432 if (bindingStrength > BIND_POW)
433 {
434 return;
435 }
436 this.position++;
437 evalLhs(BIND_POW);
438 power();
439 break;
440
441 case '*':
442 if (this.stack.isEmpty())
443 {
444 throwException("Missing left operand");
445 }
446 if (bindingStrength >= BIND_MUL)
447 {
448 return;
449 }
450 this.position++;
451 evalLhs(BIND_MUL);
452 multiply();
453 break;
454
455 case '/':
456 if (this.stack.isEmpty())
457 {
458 throwException("Missing left operand");
459 }
460 if (bindingStrength >= BIND_MUL)
461 {
462 return;
463 }
464 this.position++;
465 evalLhs(BIND_MUL);
466 divide();
467 break;
468
469 case '+':
470 if (this.stack.isEmpty())
471 {
472 throwException("Missing left operand");
473 }
474 if (bindingStrength >= BIND_ADD)
475 {
476 return;
477 }
478 this.position++;
479 evalLhs(BIND_ADD);
480 add();
481 break;
482
483 case '-':
484 if (this.stack.isEmpty())
485 {
486 throwException("Missing left operand");
487 }
488 if (bindingStrength >= BIND_ADD)
489 {
490 return;
491 }
492 this.position++;
493 evalLhs(BIND_ADD);
494 subtract();
495 break;
496
497 case '&':
498 if (this.position >= this.expression.length() - 1 || '&' != this.expression.charAt(this.position + 1))
499 {
500 throwException("Single \'&\' is not a valid operator");
501 }
502 if (this.stack.isEmpty())
503 {
504 throwException("Missing left operand");
505 }
506 if (bindingStrength >= BIND_AND)
507 {
508 return;
509 }
510 this.position += 2;
511 evalLhs(BIND_AND);
512 and();
513 break;
514
515 case '|':
516 if (this.position >= this.expression.length() - 1 || '|' != this.expression.charAt(this.position + 1))
517 {
518 throwException("Single \'|\' is not a valid operator");
519 }
520 if (this.stack.isEmpty())
521 {
522 throwException("Missing left operand");
523 }
524 if (bindingStrength >= BIND_OR)
525 {
526 return;
527 }
528 this.position += 2;
529 evalLhs(BIND_OR);
530 or();
531 break;
532
533 case '<':
534 if (this.stack.isEmpty())
535 {
536 throwException("Missing left operand");
537 }
538 this.position++;
539 if (this.position < this.expression.length() && '=' == this.expression.charAt(this.position))
540 {
541 this.position++;
542 evalLhs(BIND_RELATIONAL);
543 compareDoubleScalars((a, b) -> (a <= b));
544 }
545 else
546 {
547 evalLhs(BIND_RELATIONAL);
548 compareDoubleScalars((a, b) -> (a < b));
549 }
550 break;
551
552 case '>':
553 if (this.stack.isEmpty())
554 {
555 throwException("Missing left operand");
556 }
557 this.position++;
558 if (this.position < this.expression.length() && '=' == this.expression.charAt(this.position))
559 {
560 this.position++;
561 evalLhs(BIND_RELATIONAL);
562 compareDoubleScalars((a, b) -> (a >= b));
563 }
564 else
565 {
566 evalLhs(BIND_RELATIONAL);
567 compareDoubleScalars((a, b) -> (a > b));
568 }
569 break;
570
571 case '=':
572 {
573 if (this.position >= this.expression.length() - 1 || '=' != this.expression.charAt(this.position + 1))
574 {
575 throwException("Single \'=\' is not a valid operator");
576 }
577 if (this.stack.isEmpty())
578 {
579 throwException("Missing left operand");
580 }
581 this.position += 2;
582 evalLhs(BIND_EQUAL);
583 Object right = pop();
584 Object left = pop();
585 push(left.equals(right));
586 break;
587 }
588
589 case '!':
590 {
591 if (this.position >= this.expression.length() - 1 || '=' != this.expression.charAt(this.position + 1))
592 {
593 throwException("Single \'!\' is not a valid operator");
594 }
595 if (this.stack.isEmpty())
596 {
597 throwException("Missing left operand");
598 }
599 this.position += 2;
600 evalLhs(BIND_EQUAL);
601 Object right = pop();
602 Object left = pop();
603 push(!left.equals(right));
604 break;
605 }
606
607 case '?':
608 {
609 if (bindingStrength >= BIND_CONDITIONAL_EXPRESSION)
610 {
611 return;
612 }
613 this.position++;
614
615 Object choice = pop();
616 if (!(choice instanceof Boolean))
617 {
618 throwException("Condition does not evaluate to a logical value");
619 }
620 if (!((Boolean) choice))
621 {
622
623 skip(true);
624 }
625 else
626 {
627 evalLhs(0);
628 }
629 if (this.position >= this.expression.length() || ':' != this.expression.charAt(this.position))
630 {
631 throwException("Missing \':\' of conditional expression");
632 }
633 this.position++;
634 if ((Boolean) choice)
635 {
636
637 skip(false);
638 }
639 else
640 {
641 evalLhs(BIND_CONDITIONAL_EXPRESSION);
642 }
643 break;
644 }
645
646 case ':':
647 return;
648
649 case ',':
650 return;
651
652 default:
653 throwException("Operator expected (got \"" + token + "\")");
654
655 }
656 }
657 }
658
659
660
661
662 interface CompareValues
663 {
664
665
666
667
668
669
670 Object execute(double argument1, double argument2);
671
672 }
673
674
675
676
677
678 private void compareDoubleScalars(final CompareValues comparator)
679 {
680 Object right = pop();
681 Object left = pop();
682 if ((left instanceof DoubleScalar) && (right instanceof DoubleScalar)
683 && getDimensions((DoubleScalar<?, ?>) left).equals(getDimensions((DoubleScalar<?, ?>) right)))
684 {
685 push(comparator.execute(((DoubleScalar<?, ?>) left).si, ((DoubleScalar<?, ?>) right).si));
686 return;
687 }
688 throwException("Cannot compare " + left + " to " + right);
689 }
690
691
692
693
694
695
696 private void throwException(final String description) throws RuntimeException
697 {
698 throw new RuntimeException(description + " at position " + this.position);
699 }
700
701
702
703
704
705
706 private void skip(final boolean thenPart)
707 {
708 while (this.position < this.expression.length())
709 {
710 switch (this.expression.charAt(this.position))
711 {
712 case '(':
713 {
714 int level = 1;
715 this.position++;
716 while (this.position < this.expression.length())
717 {
718 char c = this.expression.charAt(this.position);
719 if ('(' == c)
720 {
721 level++;
722 }
723 else if (')' == c && --level == 0)
724 {
725 break;
726 }
727 this.position++;
728 }
729 if (level > 0)
730 {
731 throwException("Missing closing parenthesis");
732 }
733 this.position++;
734 break;
735 }
736
737 case '?':
738 this.position++;
739 skip(true);
740 if (this.position >= this.expression.length() || ':' != this.expression.charAt(this.position))
741 {
742 throwException("Conditional expression missing \':\'");
743 }
744 this.position++;
745 skip(false);
746 break;
747
748 case ':':
749 return;
750
751 case '^':
752 case '*':
753 case '/':
754 case '+':
755 case '-':
756 case '<':
757 case '=':
758 case '>':
759 case '!':
760 if (!thenPart)
761 {
762 return;
763 }
764 this.position++;
765 break;
766
767 default:
768 this.position++;
769 break;
770 }
771 }
772 if (thenPart)
773 {
774 throwException("Nonterminated conditional expression");
775 }
776
777 }
778
779
780
781
782 private void and()
783 {
784 Object right = pop();
785 Object left = pop();
786 if ((left instanceof Boolean) && (right instanceof Boolean))
787 {
788 push(((Boolean) left) && ((Boolean) right));
789 return;
790 }
791 throwException("Cannot compute logical AND of " + left + " and " + right);
792 }
793
794
795
796
797 private void or()
798 {
799 Object right = pop();
800 Object left = pop();
801 if ((left instanceof Boolean) && (right instanceof Boolean))
802 {
803 push(((Boolean) left) || ((Boolean) right));
804 return;
805 }
806 throwException("Cannot compute logical AND of " + left + " and " + right);
807 }
808
809
810
811
812 private void power()
813 {
814 Object exponent = pop();
815 Object base = pop();
816 push(performPower(base, exponent));
817 }
818
819
820
821
822
823
824
825 private Object performPower(final Object base, final Object exponent)
826 {
827 if ((base instanceof DoubleScalarRel) && (exponent instanceof DoubleScalarRel)
828 && getDimensions((DoubleScalarRel<?, ?>) base).equals(getDimensions(DimensionlessUnit.SI))
829 && getDimensions((DoubleScalarRel<?, ?>) exponent).equals(getDimensions(DimensionlessUnit.SI)))
830 {
831 var result = Dimensionless.ofSI(Math.pow(((DoubleScalarRel<?, ?>) base).si, ((DoubleScalarRel<?, ?>) exponent).si));
832
833 return result;
834 }
835 throwException("Cannot raise " + base + " to power " + exponent);
836 return null;
837 }
838
839
840
841
842
843
844
845 private Object performAtan2(final Object y, final Object x)
846 {
847 if ((y instanceof DoubleScalarRel) && (x instanceof DoubleScalarRel)
848 && getDimensions((DoubleScalarRel<?, ?>) y).equals(getDimensions((DoubleScalarRel<?, ?>) x)))
849 {
850 var result = Dimensionless.ofSI(Math.atan2(((DoubleScalarRel<?, ?>) y).si, ((DoubleScalarRel<?, ?>) x).si));
851
852 return result;
853 }
854 throwException("Cannot compute atan2 of " + y + ", " + x + ")");
855 return null;
856
857 }
858
859
860
861
862 private void multiply()
863 {
864 Object right = pop();
865 Object left = pop();
866 if ((right instanceof DoubleScalarRel) && (left instanceof DoubleScalarRel))
867 {
868 push(((DoubleScalarRel<?, ?>) left).times((DoubleScalarRel<?, ?>) right));
869 return;
870 }
871 throwException("Cannot multiply with " + right + " as right hand operand");
872 }
873
874
875
876
877 private void divide()
878 {
879 Object right = pop();
880 Object left = pop();
881 if ((left instanceof DoubleScalarRel) && (right instanceof DoubleScalarRel))
882 {
883 if (0.0 == ((DoubleScalarRel<?, ?>) right).si)
884 {
885 throwException("Division by 0");
886 }
887 push(((DoubleScalarRel<?, ?>) left).divide((DoubleScalarRel<?, ?>) right));
888 return;
889 }
890 throwException("Cannot divide " + left + " by " + right);
891 }
892
893
894
895
896 @SuppressWarnings({"rawtypes", "unchecked"})
897 private void add()
898 {
899 Object right = pop();
900 Object left = pop();
901 if (!(left instanceof DoubleScalar))
902 {
903 throwException("Left operand of addition must be a scalar (got \"" + left + "\")");
904 }
905 if (!(right instanceof DoubleScalar))
906 {
907 throwException("Right operand of addition must be a scalar (got \"" + right + "\")");
908 }
909
910 if (!((DoubleScalar<?, ?>) left).getDisplayUnit()
911 .getQuantity()
912 .getSiDimensions()
913 .equals(getDimensions((DoubleScalar<?, ?>) right)))
914 {
915
916
917 throwException("Cannot add " + left + " to " + right + " because the types are incompatible");
918 }
919
920 if ((left instanceof DoubleScalarRel) && (right instanceof DoubleScalarRel))
921 {
922
923 var dsl = (DoubleScalarRel) left;
924 var dsr = (DoubleScalarRel) right;
925 var sum = dsl.plus(dsr);
926
927
928 push(sum);
929 return;
930 }
931 if (right instanceof DoubleScalarAbs)
932 {
933 throwException("Cannot add an absolute value to some other value");
934 }
935
936 var dsl = (DoubleScalarAbs) left;
937 var dsr = (DoubleScalarRel) right;
938 var sum = dsl.instantiateAbs(dsl.si + dsr.si, (AbsoluteLinearUnit) dsl.getDisplayUnit().getStandardUnit());
939
940
941 push(sum);
942 }
943
944
945
946
947 @SuppressWarnings({"rawtypes", "unchecked"})
948 private void subtract()
949 {
950 Object right = pop();
951 Object left = pop();
952 if (!(left instanceof DoubleScalar))
953 {
954 throwException("Left operand of subtraction must be a scalar (got \"" + left + "\")");
955 }
956 if (!(right instanceof DoubleScalar))
957 {
958 throwException("Right operand of subtraction must be a scalar (got \"" + right + "\")");
959 }
960
961 if (!getDimensions((DoubleScalar<?, ?>) left).equals(getDimensions((DoubleScalar<?, ?>) right)))
962 {
963 throwException("Cannot subtract " + right + " from " + left + " because the types are incompatible");
964 }
965 if ((left instanceof DoubleScalarAbs) && (right instanceof DoubleScalarAbs))
966 {
967
968 var dsl = (DoubleScalarAbs) left;
969 var dsr = (DoubleScalarAbs) right;
970 var difference = dsl.minus(dsr);
971
972 push(difference);
973 return;
974 }
975 if ((left instanceof DoubleScalarAbs) && (right instanceof DoubleScalarRel))
976 {
977
978 var dsl = (DoubleScalarAbs) left;
979 var dsr = (DoubleScalarRel) right;
980 var difference = dsl.instantiateAbs(dsl.si - dsr.si, (AbsoluteLinearUnit) dsl.getDisplayUnit().getStandardUnit());
981
982 push(difference);
983 return;
984 }
985 if ((left instanceof DoubleScalarRel) && (right instanceof DoubleScalarAbs))
986 {
987
988 throwException("Cannot subtract " + right + " from " + left + " because the right operand is absolute");
989 }
990
991 var dsl = (DoubleScalarRel) left;
992 var dsr = (DoubleScalarRel) right;
993 var difference = dsl.minus(dsr);
994
995 push(difference);
996 }
997
998
999
1000
1001
1002
1003 private DoubleScalar<?, ?> handleNumber()
1004 {
1005
1006 int startPosition = this.position;
1007 boolean seenExp = false;
1008 boolean seenExpSign = false;
1009 boolean seenExpDigit = false;
1010 boolean seenRadix = false;
1011 while (this.position < this.expression.length())
1012 {
1013 char c = this.expression.charAt(this.position);
1014
1015
1016 if (seenExp && (!seenExpDigit) && ('-' == c || '+' == c))
1017 {
1018 if (seenExpSign)
1019 {
1020 throwException("Too many signs in exponent");
1021 }
1022 seenExpSign = true;
1023 }
1024 else if (seenExp && seenExpSign && (!Character.isDigit(c)))
1025 {
1026 break;
1027 }
1028 else if ('e' == c || 'E' == c)
1029 {
1030 if (seenExp)
1031 {
1032 throwException("Too many 'e' or 'E' indicators");
1033 }
1034 seenExp = true;
1035 }
1036 else if ('.' == c)
1037 {
1038 if (seenRadix)
1039 {
1040 throwException("Too many '.'");
1041 }
1042 if (seenExp)
1043 {
1044 throwException("A \'.\' is not allowed after \'e\' or \'E\'");
1045 }
1046 seenRadix = true;
1047 }
1048 else if (Character.isDigit(c))
1049 {
1050 if (seenExp)
1051 {
1052 seenExpDigit = true;
1053 }
1054 }
1055 else
1056 {
1057 break;
1058 }
1059 this.position++;
1060 }
1061 eatSpace();
1062 if (seenExp && !seenExpDigit)
1063 {
1064 throwException("Exponent value missing");
1065 }
1066 String number = this.expression.substring(startPosition, this.position);
1067 if (this.position < this.expression.length() && '[' == this.expression.charAt(this.position))
1068 {
1069 this.position++;
1070 startPosition = this.position;
1071 while (this.position < this.expression.length())
1072 {
1073 char c = this.expression.charAt(this.position);
1074 if (']' == c)
1075 {
1076 String unit = this.expression.substring(startPosition, this.position++);
1077 DoubleScalar<?, ?> result = null;
1078 if (null != this.userSuppliedUnitParser)
1079 {
1080 result = this.userSuppliedUnitParser.parseUnit(Double.parseDouble(number), unit);
1081 }
1082 if (null == result)
1083 {
1084 result = SIScalar.valueOf(number + " " + unit);
1085 }
1086 return result;
1087 }
1088 if ((!Character.isLetterOrDigit(c)) && '-' != c && '^' != c && '/' != c && '.' != c)
1089 {
1090 throwException("Bad symbol in SI unit string");
1091 }
1092 this.position++;
1093 }
1094 if (this.position >= this.expression.length())
1095 {
1096 throwException("Missing closing bracket (\']\')");
1097 }
1098 }
1099 return SIScalar.valueOf(number);
1100 }
1101
1102
1103
1104
1105
1106
1107 private Object handleFunctionOrVariableOrNamedConstant()
1108 {
1109 int startPosition = this.position;
1110 while (this.position < this.expression.length())
1111 {
1112 char c = this.expression.charAt(this.position);
1113 if (Character.isLetterOrDigit(c) || '.' == c || '_' == c || '@' == c || '#' == c)
1114 {
1115 this.position++;
1116 }
1117 else
1118 {
1119 break;
1120 }
1121 }
1122 String name = this.expression.substring(startPosition, this.position);
1123 eatSpace();
1124 if (this.position >= this.expression.length() || this.expression.charAt(this.position) != '(')
1125 {
1126
1127 Object result = null == this.retrieveValue ? null : this.retrieveValue.lookup(name);
1128 if (null == result)
1129 {
1130 throwException("Cannot resolve variable " + name);
1131 }
1132
1133 return result;
1134 }
1135
1136
1137 this.position++;
1138 eatSpace();
1139 int argCount = 0;
1140 while (this.position < this.expression.length() && ')' != this.expression.charAt(this.position))
1141 {
1142 evalLhs(0);
1143 argCount++;
1144 eatSpace();
1145 if (this.position < this.expression.length() && ',' == this.expression.charAt(this.position))
1146 {
1147 this.position++;
1148 }
1149 }
1150 if (this.position >= this.expression.length() || ')' != this.expression.charAt(this.position))
1151 {
1152 throwException("Missing closing parenthesis");
1153 }
1154 this.position++;
1155 Function f = null;
1156 if (null != this.userDefinedFunctions)
1157 {
1158 f = this.userDefinedFunctions.get(name);
1159 }
1160 if (null == f)
1161 {
1162 f = this.functionData.get(name);
1163 }
1164 if (null == f)
1165 {
1166 throwException("Unknown function " + name);
1167 }
1168 Object[] args = new Object[argCount];
1169 for (int i = 0; i < argCount; i++)
1170 {
1171 args[argCount - i - 1] = pop();
1172 }
1173 if (f.getMetaData() != MetaData.NO_META_DATA)
1174 {
1175
1176 int argsNeeded = f.getMetaData().getObjectDescriptors().length;
1177 if (argsNeeded != argCount)
1178 {
1179 throwException(name + " needs " + argsNeeded + " parameter" + (argsNeeded == 1 ? "" : "s") + " (got " + argCount
1180 + ")");
1181 }
1182 for (int i = 0; i < argCount; i++)
1183 {
1184 if ((args[i] instanceof Boolean) && (!Boolean.class.isAssignableFrom(f.getMetaData().getObjectClass(i))))
1185 {
1186 throwException(name + " does not take " + args[i] + " as parameter " + i);
1187 }
1188 else if ((args[i] instanceof DoubleScalar)
1189 && (DoubleScalar.class.isAssignableFrom(f.getMetaData().getObjectClass(i))))
1190 {
1191 DoubleScalar<?, ?> ds = (DoubleScalar<?, ?>) args[i];
1192 Class<?> clazz = f.getMetaData().getObjectClass(i);
1193 if (!clazz.equals(DoubleScalarRel.class))
1194 {
1195 SIDimensions siDimensions = this.siDimensionsMap.get(clazz);
1196 if (null == siDimensions)
1197 {
1198
1199 try
1200 {
1201 Field field = clazz.getDeclaredField("ZERO");
1202 DoubleScalar<?, ?> zero = (DoubleScalar<?, ?>) field.get(clazz);
1203 siDimensions = zero.getDisplayUnit().getQuantity().getSiDimensions();
1204 this.siDimensionsMap.put(clazz, siDimensions);
1205 }
1206 catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException nsfe)
1207 {
1208 throwException("ERROR: Cannot determine quantity for " + clazz.getCanonicalName());
1209 }
1210 }
1211 if (!siDimensions.equals(getDimensions(ds)))
1212 {
1213 throwException("parameter " + i + " of " + name + " has incompatible quantity");
1214 }
1215 }
1216 }
1217 else
1218 {
1219 throwException("Argument " + i + " of function " + name + " is of an unhandled type (" + args[i] + ")");
1220 }
1221 }
1222 }
1223
1224 return (f.function(args));
1225 }
1226
1227
1228
1229
1230
1231 private void push(final Object object)
1232 {
1233 this.stack.add(object);
1234 }
1235
1236
1237
1238
1239
1240
1241 private Object pop() throws RuntimeException
1242 {
1243 if (this.stack.isEmpty())
1244 {
1245 throwException("Stack empty");
1246 }
1247 return (this.stack.remove(this.stack.size() - 1));
1248 }
1249
1250
1251
1252
1253
1254
1255
1256 private Dimensionless checkDimensionless(final Function functionData, final Object object)
1257 {
1258 if (!(object instanceof DoubleScalar))
1259 {
1260 throwException("Function " + functionData.getId() + " cannot be applied to " + object);
1261 }
1262 DoubleScalar<?, ?> ds = (DoubleScalar<?, ?>) object;
1263 if (!getDimensions(ds).equals(getDimensions(DimensionlessUnit.SI)))
1264 {
1265 throwException("Function " + functionData.getId() + " cannot be applied to " + ds);
1266 }
1267 return new Dimensionless(ds.si, DimensionlessUnit.SI);
1268 }
1269
1270
1271
1272
1273
1274
1275 private static SIDimensions getDimensions(final DoubleScalar<?, ?> doubleScalar)
1276 {
1277 return doubleScalar.getDisplayUnit().getQuantity().getSiDimensions();
1278 }
1279
1280
1281
1282
1283
1284
1285 private static SIDimensions getDimensions(final Unit<?> unit)
1286 {
1287 return unit.getQuantity().getSiDimensions();
1288 }
1289
1290 }