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