1   /* Copyright 2002-2022 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.utils.units;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.hipparchus.fraction.Fraction;
24  import org.hipparchus.util.FastMath;
25  
26  /** Parser for units.
27   * <p>
28   * This fairly basic parser uses recursive descent with the following grammar,
29   * where '*' can in fact be either '*', '×', '.', or '·', '/' can be either
30   * '/' or '⁄' and '^' can be either '^', "**" or implicit with switch to superscripts,
31   * and fraction are either unicode fractions like ½ or ⅞ or the decimal value 0.5.
32   * The special cases "n/a" returns a null list. It is intended to manage the
33   * special unit {@link Unit#NONE}.
34   * </p>
35   * <pre>
36   *   unit         ::=  "n/a" | chain
37   *   chain        ::=  operand { ('*' | '/') operand }
38   *   operand      ::=  integer | integer term | term
39   *   term         ::=  '√' base | base power
40   *   power        ::=  '^' exponent | ε
41   *   exponent     ::=  'fraction'   | integer | '(' integer denominator ')'
42   *   denominator  ::=  '/' integer  | ε
43   *   base         ::=  identifier | '(' chain ')'
44   * </pre>
45   * <p>
46   * This parses correctly units like MHz, km/√d, kg.m.s⁻¹, µas^⅖/(h**(2)×m)³, km/√(kg.s),
47   * √kg*km** (3/2) /(µs^2*Ω⁻⁷), km**0.5/s, #/y, 2rev/d², 1/s.
48   * </p>
49   * <p>
50   * Note that we don't accept combining square roots and power on the same operand; km/√d³
51   * is refused (but km/√(d³) is accepted). We also accept a single integer prefix and
52   * only at the start of the specification.
53   * </p>
54   * @author Luc Maisonobe
55   * @since 11.0
56   */
57  public class Parser {
58  
59      /** Private constructor for a utility class.
60       */
61      private Parser() {
62      }
63  
64      /** Build the list of terms corresponding to a units specification.
65       * @param unitsSpecification units specification to parse
66       * @return parse tree
67       */
68      public static List<PowerTerm> buildTermsList(final String unitsSpecification) {
69          if (Unit.NONE.getName().equals(unitsSpecification)) {
70              // special case for no units
71              return null;
72          } else {
73              final Lexer lexer = new Lexer(unitsSpecification);
74              final List<PowerTerm> chain = chain(lexer);
75              if (lexer.next() != null) {
76                  throw lexer.generateException();
77              }
78              return chain;
79          }
80      }
81  
82      /** Parse a units chain.
83       * @param lexer lexer providing tokens
84       * @return parsed units chain
85       */
86      private static List<PowerTerm> chain(final Lexer lexer) {
87          final List<PowerTerm> chain = new ArrayList<>();
88          chain.addAll(operand(lexer));
89          for (Token token = lexer.next(); token != null; token = lexer.next()) {
90              if (checkType(token, TokenType.MULTIPLICATION)) {
91                  chain.addAll(operand(lexer));
92              } else if (checkType(token, TokenType.DIVISION)) {
93                  chain.addAll(reciprocate(operand(lexer)));
94              } else {
95                  lexer.pushBack();
96                  break;
97              }
98          }
99          return chain;
100     }
101 
102     /** Parse an operand.
103      * @param lexer lexer providing tokens
104      * @return parsed operand
105      */
106     private static List<PowerTerm> operand(final Lexer lexer) {
107         final Token token1 = lexer.next();
108         if (token1 == null) {
109             throw lexer.generateException();
110         }
111         if (checkType(token1, TokenType.INTEGER)) {
112             final int scale = token1.getInt();
113             final Token token2 = lexer.next();
114             lexer.pushBack();
115             if (token2 == null ||
116                 checkType(token2, TokenType.MULTIPLICATION) ||
117                 checkType(token2, TokenType.DIVISION)) {
118                 return Collections.singletonList(new PowerTerm(scale, "1", Fraction.ONE));
119             } else {
120                 return applyScale(term(lexer), scale);
121             }
122         } else {
123             lexer.pushBack();
124             return term(lexer);
125         }
126     }
127 
128     /** Parse a term.
129      * @param lexer lexer providing tokens
130      * @return parsed term
131      */
132     private static List<PowerTerm> term(final Lexer lexer) {
133         final Token token = lexer.next();
134         if (token.getType() == TokenType.SQUARE_ROOT) {
135             return applyExponent(base(lexer), Fraction.ONE_HALF);
136         } else {
137             lexer.pushBack();
138             return applyExponent(base(lexer), power(lexer));
139         }
140     }
141 
142     /** Parse a power operation.
143      * @param lexer lexer providing tokens
144      * @return exponent, or null if no exponent
145      */
146     private static Fraction power(final Lexer lexer) {
147         final Token token = lexer.next();
148         if (checkType(token, TokenType.POWER)) {
149             return exponent(lexer);
150         } else {
151             lexer.pushBack();
152             return null;
153         }
154     }
155 
156     /** Parse an exponent.
157      * @param lexer lexer providing tokens
158      * @return exponent
159      */
160     private static Fraction exponent(final Lexer lexer) {
161         final Token token = lexer.next();
162         if (checkType(token, TokenType.FRACTION)) {
163             return token.getFraction();
164         } else if (checkType(token, TokenType.INTEGER)) {
165             return new Fraction(token.getInt());
166         } else {
167             lexer.pushBack();
168             accept(lexer, TokenType.OPEN);
169             final int num = accept(lexer, TokenType.INTEGER).getInt();
170             final int den = denominator(lexer);
171             accept(lexer, TokenType.CLOSE);
172             return new Fraction(num, den);
173         }
174     }
175 
176     /** Parse a denominator.
177      * @param lexer lexer providing tokens
178      * @return denominatior
179      */
180     private static int denominator(final Lexer lexer) {
181         final Token token = lexer.next();
182         if (checkType(token, TokenType.DIVISION)) {
183             return accept(lexer, TokenType.INTEGER).getInt();
184         } else  {
185             lexer.pushBack();
186             return 1;
187         }
188     }
189 
190     /** Parse a base term.
191      * @param lexer lexer providing tokens
192      * @return base term
193      */
194     private static List<PowerTerm> base(final Lexer lexer) {
195         final Token token = lexer.next();
196         if (checkType(token, TokenType.IDENTIFIER)) {
197             return Collections.singletonList(new PowerTerm(1.0, token.getSubString(), Fraction.ONE));
198         } else {
199             lexer.pushBack();
200             accept(lexer, TokenType.OPEN);
201             final List<PowerTerm> chain = chain(lexer);
202             accept(lexer, TokenType.CLOSE);
203             return chain;
204         }
205     }
206 
207     /** Compute the reciprocal a base term.
208      * @param base base term
209      * @return reciprocal of base term
210      */
211     private static List<PowerTerm> reciprocate(final List<PowerTerm> base) {
212 
213         // reciprocate individual terms
214         final List<PowerTerm> reciprocal = new ArrayList<>(base.size());
215         for (final PowerTerm term : base) {
216             reciprocal.add(new PowerTerm(1.0 / term.getScale(), term.getBase(), term.getExponent().negate()));
217         }
218 
219         return reciprocal;
220 
221     }
222 
223     /** Apply a scaling factor to a base term.
224      * @param base base term
225      * @param scale scaling factor
226      * @return term with scaling factor applied (same as {@code base} if {@code scale} is 1)
227      */
228     private static List<PowerTerm> applyScale(final List<PowerTerm> base, final int scale) {
229 
230         if (scale == 1) {
231             // no scaling at all, return the base term itself
232             return base;
233         }
234 
235         // combine scaling factor with first term
236         final List<PowerTerm> powered = new ArrayList<>(base.size());
237         boolean first = true;
238         for (final PowerTerm term : base) {
239             if (first) {
240                 powered.add(new PowerTerm(scale * term.getScale(), term.getBase(), term.getExponent()));
241                 first = false;
242             } else {
243                 powered.add(term);
244             }
245         }
246 
247         return powered;
248 
249     }
250 
251     /** Apply an exponent to a base term.
252      * @param base base term
253      * @param exponent exponent (may be null)
254      * @return term with exponent applied (same as {@code base} if exponent is null)
255      */
256     private static List<PowerTerm> applyExponent(final List<PowerTerm> base, final Fraction exponent) {
257 
258         if (exponent == null || exponent.equals(Fraction.ONE)) {
259             // return the base term itself
260             return base;
261         }
262 
263         // combine exponent with existing ones, for example to handles compounds units like m/(kg.s²)³
264         final List<PowerTerm> powered = new ArrayList<>(base.size());
265         for (final PowerTerm term : base) {
266             final double poweredScale;
267             if (exponent.isInteger()) {
268                 poweredScale = FastMath.pow(term.getScale(), exponent.getNumerator());
269             } else if (Fraction.ONE_HALF.equals(exponent)) {
270                 poweredScale = FastMath.sqrt(term.getScale());
271             } else {
272                 poweredScale = FastMath.pow(term.getScale(), exponent.doubleValue());
273             }
274             powered.add(new PowerTerm(poweredScale, term.getBase(), exponent.multiply(term.getExponent())));
275         }
276 
277         return powered;
278 
279     }
280 
281     /** Accept a token.
282      * @param lexer lexer providing tokens
283      * @param expected expected token type
284      * @return accepted token
285      */
286     private static Token accept(final Lexer lexer, final TokenType expected) {
287         final Token token = lexer.next();
288         if (!checkType(token, expected)) {
289             throw lexer.generateException();
290         }
291         return token;
292     }
293 
294     /** Check a token exists and has proper type.
295      * @param token token to check
296      * @param expected expected token type
297      * @return true if token exists and has proper type
298      */
299     private static boolean checkType(final Token token, final TokenType expected) {
300         return token != null && token.getType() == expected;
301     }
302 
303 }