Parser.java

  1. /* Copyright 2002-2024 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. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;

  21. import org.hipparchus.fraction.Fraction;
  22. import org.hipparchus.util.FastMath;

  23. /** Parser for units.
  24.  * <p>
  25.  * This fairly basic parser uses recursive descent with the following grammar,
  26.  * where '*' can in fact be either '*', '×', '.', or '·', '/' can be either
  27.  * '/' or '⁄' and '^' can be either '^', "**" or implicit with switch to superscripts,
  28.  * and fraction are either unicode fractions like ½ or ⅞ or the decimal value 0.5.
  29.  * The special cases "n/a" returns a null list. It is intended to manage the
  30.  * special unit {@link Unit#NONE}.
  31.  * </p>
  32.  * <pre>
  33.  *   unit         ::=  "n/a" | chain
  34.  *   chain        ::=  operand { ('*' | '/') operand }
  35.  *   operand      ::=  integer | integer term | term
  36.  *   term         ::=  '√' base | base power
  37.  *   power        ::=  '^' exponent | ε
  38.  *   exponent     ::=  'fraction'   | integer | '(' integer denominator ')'
  39.  *   denominator  ::=  '/' integer  | ε
  40.  *   base         ::=  identifier | '(' chain ')'
  41.  * </pre>
  42.  * <p>
  43.  * This parses correctly units like MHz, km/√d, kg.m.s⁻¹, µas^⅖/(h**(2)×m)³, km/√(kg.s),
  44.  * √kg*km** (3/2) /(µs^2*Ω⁻⁷), km**0.5/s, #/y, 2rev/d², 1/s.
  45.  * </p>
  46.  * <p>
  47.  * Note that we don't accept combining square roots and power on the same operand; km/√d³
  48.  * is refused (but km/√(d³) is accepted). We also accept a single integer prefix and
  49.  * only at the start of the specification.
  50.  * </p>
  51.  * @author Luc Maisonobe
  52.  * @since 11.0
  53.  */
  54. public class Parser {

  55.     /** Private constructor for a utility class.
  56.      */
  57.     private Parser() {
  58.     }

  59.     /** Build the list of terms corresponding to a units specification.
  60.      * @param unitsSpecification units specification to parse
  61.      * @return parse tree
  62.      */
  63.     public static List<PowerTerm> buildTermsList(final String unitsSpecification) {
  64.         if (Unit.NONE.getName().equals(unitsSpecification)) {
  65.             // special case for no units
  66.             return null;
  67.         } else {
  68.             final Lexer lexer = new Lexer(unitsSpecification);
  69.             final List<PowerTerm> chain = chain(lexer);
  70.             if (lexer.next() != null) {
  71.                 throw lexer.generateException();
  72.             }
  73.             return chain;
  74.         }
  75.     }

  76.     /** Parse a units chain.
  77.      * @param lexer lexer providing tokens
  78.      * @return parsed units chain
  79.      */
  80.     private static List<PowerTerm> chain(final Lexer lexer) {
  81.         final List<PowerTerm> chain = new ArrayList<>();
  82.         chain.addAll(operand(lexer));
  83.         for (Token token = lexer.next(); token != null; token = lexer.next()) {
  84.             if (checkType(token, TokenType.MULTIPLICATION)) {
  85.                 chain.addAll(operand(lexer));
  86.             } else if (checkType(token, TokenType.DIVISION)) {
  87.                 chain.addAll(reciprocate(operand(lexer)));
  88.             } else {
  89.                 lexer.pushBack();
  90.                 break;
  91.             }
  92.         }
  93.         return chain;
  94.     }

  95.     /** Parse an operand.
  96.      * @param lexer lexer providing tokens
  97.      * @return parsed operand
  98.      */
  99.     private static List<PowerTerm> operand(final Lexer lexer) {
  100.         final Token token1 = lexer.next();
  101.         if (token1 == null) {
  102.             throw lexer.generateException();
  103.         }
  104.         if (checkType(token1, TokenType.INTEGER)) {
  105.             final int scale = token1.getInt();
  106.             final Token token2 = lexer.next();
  107.             lexer.pushBack();
  108.             if (token2 == null ||
  109.                 checkType(token2, TokenType.MULTIPLICATION) ||
  110.                 checkType(token2, TokenType.DIVISION)) {
  111.                 return Collections.singletonList(new PowerTerm(scale, "1", Fraction.ONE));
  112.             } else {
  113.                 return applyScale(term(lexer), scale);
  114.             }
  115.         } else {
  116.             lexer.pushBack();
  117.             return term(lexer);
  118.         }
  119.     }

  120.     /** Parse a term.
  121.      * @param lexer lexer providing tokens
  122.      * @return parsed term
  123.      */
  124.     private static List<PowerTerm> term(final Lexer lexer) {
  125.         final Token token = lexer.next();
  126.         if (token.getType() == TokenType.SQUARE_ROOT) {
  127.             return applyExponent(base(lexer), Fraction.ONE_HALF);
  128.         } else {
  129.             lexer.pushBack();
  130.             return applyExponent(base(lexer), power(lexer));
  131.         }
  132.     }

  133.     /** Parse a power operation.
  134.      * @param lexer lexer providing tokens
  135.      * @return exponent, or null if no exponent
  136.      */
  137.     private static Fraction power(final Lexer lexer) {
  138.         final Token token = lexer.next();
  139.         if (checkType(token, TokenType.POWER)) {
  140.             return exponent(lexer);
  141.         } else {
  142.             lexer.pushBack();
  143.             return null;
  144.         }
  145.     }

  146.     /** Parse an exponent.
  147.      * @param lexer lexer providing tokens
  148.      * @return exponent
  149.      */
  150.     private static Fraction exponent(final Lexer lexer) {
  151.         final Token token = lexer.next();
  152.         if (checkType(token, TokenType.FRACTION)) {
  153.             return token.getFraction();
  154.         } else if (checkType(token, TokenType.INTEGER)) {
  155.             return new Fraction(token.getInt());
  156.         } else {
  157.             lexer.pushBack();
  158.             accept(lexer, TokenType.OPEN);
  159.             final int num = accept(lexer, TokenType.INTEGER).getInt();
  160.             final int den = denominator(lexer);
  161.             accept(lexer, TokenType.CLOSE);
  162.             return new Fraction(num, den);
  163.         }
  164.     }

  165.     /** Parse a denominator.
  166.      * @param lexer lexer providing tokens
  167.      * @return denominatior
  168.      */
  169.     private static int denominator(final Lexer lexer) {
  170.         final Token token = lexer.next();
  171.         if (checkType(token, TokenType.DIVISION)) {
  172.             return accept(lexer, TokenType.INTEGER).getInt();
  173.         } else  {
  174.             lexer.pushBack();
  175.             return 1;
  176.         }
  177.     }

  178.     /** Parse a base term.
  179.      * @param lexer lexer providing tokens
  180.      * @return base term
  181.      */
  182.     private static List<PowerTerm> base(final Lexer lexer) {
  183.         final Token token = lexer.next();
  184.         if (checkType(token, TokenType.IDENTIFIER)) {
  185.             return Collections.singletonList(new PowerTerm(1.0, token.getSubString(), Fraction.ONE));
  186.         } else {
  187.             lexer.pushBack();
  188.             accept(lexer, TokenType.OPEN);
  189.             final List<PowerTerm> chain = chain(lexer);
  190.             accept(lexer, TokenType.CLOSE);
  191.             return chain;
  192.         }
  193.     }

  194.     /** Compute the reciprocal a base term.
  195.      * @param base base term
  196.      * @return reciprocal of base term
  197.      */
  198.     private static List<PowerTerm> reciprocate(final List<PowerTerm> base) {

  199.         // reciprocate individual terms
  200.         final List<PowerTerm> reciprocal = new ArrayList<>(base.size());
  201.         for (final PowerTerm term : base) {
  202.             reciprocal.add(new PowerTerm(1.0 / term.getScale(), term.getBase(), term.getExponent().negate()));
  203.         }

  204.         return reciprocal;

  205.     }

  206.     /** Apply a scaling factor to a base term.
  207.      * @param base base term
  208.      * @param scale scaling factor
  209.      * @return term with scaling factor applied (same as {@code base} if {@code scale} is 1)
  210.      */
  211.     private static List<PowerTerm> applyScale(final List<PowerTerm> base, final int scale) {

  212.         if (scale == 1) {
  213.             // no scaling at all, return the base term itself
  214.             return base;
  215.         }

  216.         // combine scaling factor with first term
  217.         final List<PowerTerm> powered = new ArrayList<>(base.size());
  218.         boolean first = true;
  219.         for (final PowerTerm term : base) {
  220.             if (first) {
  221.                 powered.add(new PowerTerm(scale * term.getScale(), term.getBase(), term.getExponent()));
  222.                 first = false;
  223.             } else {
  224.                 powered.add(term);
  225.             }
  226.         }

  227.         return powered;

  228.     }

  229.     /** Apply an exponent to a base term.
  230.      * @param base base term
  231.      * @param exponent exponent (may be null)
  232.      * @return term with exponent applied (same as {@code base} if exponent is null)
  233.      */
  234.     private static List<PowerTerm> applyExponent(final List<PowerTerm> base, final Fraction exponent) {

  235.         if (exponent == null || exponent.equals(Fraction.ONE)) {
  236.             // return the base term itself
  237.             return base;
  238.         }

  239.         // combine exponent with existing ones, for example to handles compounds units like m/(kg.s²)³
  240.         final List<PowerTerm> powered = new ArrayList<>(base.size());
  241.         for (final PowerTerm term : base) {
  242.             final double poweredScale;
  243.             if (exponent.isInteger()) {
  244.                 poweredScale = FastMath.pow(term.getScale(), exponent.getNumerator());
  245.             } else if (Fraction.ONE_HALF.equals(exponent)) {
  246.                 poweredScale = FastMath.sqrt(term.getScale());
  247.             } else {
  248.                 poweredScale = FastMath.pow(term.getScale(), exponent.doubleValue());
  249.             }
  250.             powered.add(new PowerTerm(poweredScale, term.getBase(), exponent.multiply(term.getExponent())));
  251.         }

  252.         return powered;

  253.     }

  254.     /** Accept a token.
  255.      * @param lexer lexer providing tokens
  256.      * @param expected expected token type
  257.      * @return accepted token
  258.      */
  259.     private static Token accept(final Lexer lexer, final TokenType expected) {
  260.         final Token token = lexer.next();
  261.         if (!checkType(token, expected)) {
  262.             throw lexer.generateException();
  263.         }
  264.         return token;
  265.     }

  266.     /** Check a token exists and has proper type.
  267.      * @param token token to check
  268.      * @param expected expected token type
  269.      * @return true if token exists and has proper type
  270.      */
  271.     private static boolean checkType(final Token token, final TokenType expected) {
  272.         return token != null && token.getType() == expected;
  273.     }

  274. }