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 }