Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
7
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Open sidebar
Orekit
Orekit
Commits
3380d717
Commit
3380d717
authored
Apr 06, 2021
by
Luc Maisonobe
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Improved handling of fractional exponent in units parsing.
We also parse correctly the CCSDS notation km**0.5 for √km.
parent
228cf092
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
266 additions
and
140 deletions
+266
-140
src/main/java/org/orekit/utils/units/Lexer.java
src/main/java/org/orekit/utils/units/Lexer.java
+69
-18
src/main/java/org/orekit/utils/units/Parser.java
src/main/java/org/orekit/utils/units/Parser.java
+11
-7
src/main/java/org/orekit/utils/units/Token.java
src/main/java/org/orekit/utils/units/Token.java
+21
-7
src/main/java/org/orekit/utils/units/TokenType.java
src/main/java/org/orekit/utils/units/TokenType.java
+4
-1
src/main/java/org/orekit/utils/units/Unit.java
src/main/java/org/orekit/utils/units/Unit.java
+17
-7
src/test/java/org/orekit/utils/units/LexerTest.java
src/test/java/org/orekit/utils/units/LexerTest.java
+131
-97
src/test/java/org/orekit/utils/units/ParserTest.java
src/test/java/org/orekit/utils/units/ParserTest.java
+13
-3
No files found.
src/main/java/org/orekit/utils/units/Lexer.java
View file @
3380d717
...
...
@@ -16,6 +16,7 @@
*/
package
org.orekit.utils.units
;
import
org.hipparchus.fraction.Fraction
;
import
org.orekit.errors.OrekitException
;
import
org.orekit.errors.OrekitMessages
;
...
...
@@ -110,42 +111,90 @@ class Lexer {
}
if
(
current
>
start
)
{
final
String
identifier
=
unitSpecification
.
subSequence
(
start
,
current
).
toString
();
return
emit
(
current
,
TokenType
.
PREFIXED_UNIT
,
PrefixedUnit
.
valueOf
(
identifier
),
0
);
return
emit
(
current
,
TokenType
.
PREFIXED_UNIT
,
PrefixedUnit
.
valueOf
(
identifier
),
0
,
1
);
}
// look for power
if
((
start
<
end
-
1
)
&&
unitSpecification
.
charAt
(
start
)
==
'*'
&&
unitSpecification
.
charAt
(
start
)
==
'*'
&&
unitSpecification
.
charAt
(
start
+
1
)
==
'*'
)
{
// power indicator as **
return
emit
(
start
+
2
,
TokenType
.
POWER
,
null
,
0
);
return
emit
(
start
+
2
,
TokenType
.
POWER
,
null
,
0
,
1
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'^'
)
{
// power indicator as ^
return
emit
(
start
+
1
,
TokenType
.
POWER
,
null
,
0
);
return
emit
(
start
+
1
,
TokenType
.
POWER
,
null
,
0
,
1
);
}
else
if
(
convertSuperscript
(
start
)
!=
' '
&&
last
!=
null
&&
last
.
getType
()
!=
TokenType
.
POWER
)
{
// virtual power indicator as we switch to superscript characters
return
emit
(
start
,
TokenType
.
POWER
,
null
,
0
);
return
emit
(
start
,
TokenType
.
POWER
,
null
,
0
,
1
);
}
// look for one character tokens
if
(
unitSpecification
.
charAt
(
start
)
==
'*'
)
{
return
emit
(
start
+
1
,
TokenType
.
MULTIPLICATION
,
null
,
0
);
return
emit
(
start
+
1
,
TokenType
.
MULTIPLICATION
,
null
,
0
,
1
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'×'
)
{
return
emit
(
start
+
1
,
TokenType
.
MULTIPLICATION
,
null
,
0
);
return
emit
(
start
+
1
,
TokenType
.
MULTIPLICATION
,
null
,
0
,
1
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'.'
)
{
return
emit
(
start
+
1
,
TokenType
.
MULTIPLICATION
,
null
,
0
);
return
emit
(
start
+
1
,
TokenType
.
MULTIPLICATION
,
null
,
0
,
1
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'/'
)
{
return
emit
(
start
+
1
,
TokenType
.
DIVISION
,
null
,
0
);
return
emit
(
start
+
1
,
TokenType
.
DIVISION
,
null
,
0
,
1
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⁄'
)
{
return
emit
(
start
+
1
,
TokenType
.
DIVISION
,
null
,
0
);
return
emit
(
start
+
1
,
TokenType
.
DIVISION
,
null
,
0
,
1
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'('
)
{
return
emit
(
start
+
1
,
TokenType
.
OPEN
,
null
,
0
);
return
emit
(
start
+
1
,
TokenType
.
OPEN
,
null
,
0
,
1
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
')'
)
{
return
emit
(
start
+
1
,
TokenType
.
CLOSE
,
null
,
0
);
return
emit
(
start
+
1
,
TokenType
.
CLOSE
,
null
,
0
,
1
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'√'
)
{
return
emit
(
start
+
1
,
TokenType
.
SQUARE_ROOT
,
null
,
0
);
return
emit
(
start
+
1
,
TokenType
.
SQUARE_ROOT
,
null
,
0
,
1
);
}
// look for special case "0.5" (used by CCSDS for square roots)
if
((
start
<
end
-
2
)
&&
unitSpecification
.
charAt
(
start
)
==
'0'
&&
unitSpecification
.
charAt
(
start
+
1
)
==
'.'
&&
unitSpecification
.
charAt
(
start
+
2
)
==
'5'
)
{
// ½ written as decimal number
return
emit
(
start
+
3
,
TokenType
.
FRACTION
,
null
,
1
,
2
);
}
// look for unicode fractions
if
(
unitSpecification
.
charAt
(
start
)
==
'¼'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
1
,
4
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'½'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
1
,
2
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'¾'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
3
,
4
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅐'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
1
,
7
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅑'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
1
,
9
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅒'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
1
,
10
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅓'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
1
,
3
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅔'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
2
,
3
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅕'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
1
,
5
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅖'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
2
,
5
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅗'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
3
,
5
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅘'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
4
,
5
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅙'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
1
,
6
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅚'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
5
,
6
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅛'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
1
,
8
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅜'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
3
,
8
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅝'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
5
,
8
);
}
else
if
(
unitSpecification
.
charAt
(
start
)
==
'⅞'
)
{
return
emit
(
start
+
1
,
TokenType
.
FRACTION
,
null
,
7
,
8
);
}
// it must be an integer, either as regular character or as superscript
...
...
@@ -180,7 +229,7 @@ class Lexer {
}
if
(
current
>
numberStart
)
{
// there were some digits
return
emit
(
current
,
TokenType
.
INTEGER
,
null
,
sign
*
value
);
return
emit
(
current
,
TokenType
.
INTEGER
,
null
,
sign
*
value
,
1
);
}
throw
generateException
();
...
...
@@ -198,15 +247,17 @@ class Lexer {
* @param after index after token
* @param type token type
* @param unit prefixed unit value
* @param value integer value
* @param numerator value of the token numerator
* @param denominator value of the token denominator
* @return new token
*/
private
Token
emit
(
final
int
after
,
final
TokenType
type
,
final
PrefixedUnit
unit
,
final
int
value
)
{
private
Token
emit
(
final
int
after
,
final
TokenType
type
,
final
PrefixedUnit
unit
,
final
int
numerator
,
final
int
denominator
)
{
final
CharSequence
subString
=
unitSpecification
.
subSequence
(
start
,
after
);
start
=
after
;
nextToLast
=
last
;
last
=
new
Token
(
subString
,
type
,
unit
,
value
);
last
=
new
Token
(
subString
,
type
,
unit
,
numerator
,
denominator
==
1
?
null
:
new
Fraction
(
numerator
,
denominator
));
return
last
;
}
...
...
src/main/java/org/orekit/utils/units/Parser.java
View file @
3380d717
...
...
@@ -22,7 +22,8 @@ import org.hipparchus.fraction.Fraction;
* <p>
* This fairly basic parser uses recursive descent with the following grammar,
* where '*' can in fact be either '*', '×' or '.', '/' can be either '/' or '⁄'
* and '^' can be either '^', "**" or implicit with switch to superscripts.
* and '^' can be either '^', "**" or implicit with switch to superscripts,
* and fraction are either unicode fractions like ½ or ⅞ or the decimal value 0.5.
* The special case "n/a" corresponds to {@link PredefinedUnit#NONE}.
* </p>
* <pre>
...
...
@@ -30,12 +31,13 @@ import org.hipparchus.fraction.Fraction;
* chain ::= operand { ('*' | '/') operand }
* operand ::= '√' simple | simple power
* power ::= '^' exponent | ε
* exponent ::= integer
| '(' integer denominator ')'
* exponent ::=
'fraction' |
integer | '(' integer denominator ')'
* denominator ::= '/' integer | ε
* simple ::= predefined | '(' chain ')'
* </pre>
* <p>
* This parses correctly units like MHz, km/√d, kg.m.s⁻¹, µas^(2/5)/(h**(2)×m)³, km/√(kg.s), √kg*km** (3/2) /(µs^2*Ω⁻⁷).
* This parses correctly units like MHz, km/√d, kg.m.s⁻¹, µas^⅖/(h**(2)×m)³, km/√(kg.s),
* √kg*km** (3/2) /(µs^2*Ω⁻⁷), km**0.5/s
* Note that we don't accept both square root and power on the same operand, so km/√d³ is refused (but km/√(d³) is accepted).
* Note that "nd" does not stands for "not-defined" but for "nano-day"…
* </p>
...
...
@@ -125,12 +127,14 @@ class Parser {
*/
private
static
Fraction
exponent
(
final
Lexer
lexer
)
{
final
Token
token
=
lexer
.
next
();
if
(
checkType
(
token
,
TokenType
.
INTEGER
))
{
return
new
Fraction
(
token
.
getValue
());
if
(
checkType
(
token
,
TokenType
.
FRACTION
))
{
return
token
.
getFraction
();
}
else
if
(
checkType
(
token
,
TokenType
.
INTEGER
))
{
return
new
Fraction
(
token
.
getInt
());
}
else
{
lexer
.
pushBack
();
accept
(
lexer
,
TokenType
.
OPEN
);
final
int
num
=
accept
(
lexer
,
TokenType
.
INTEGER
).
get
Value
();
final
int
num
=
accept
(
lexer
,
TokenType
.
INTEGER
).
get
Int
();
final
int
den
=
denominator
(
lexer
);
accept
(
lexer
,
TokenType
.
CLOSE
);
return
new
Fraction
(
num
,
den
);
...
...
@@ -144,7 +148,7 @@ class Parser {
private
static
int
denominator
(
final
Lexer
lexer
)
{
final
Token
token
=
lexer
.
next
();
if
(
checkType
(
token
,
TokenType
.
DIVISION
))
{
return
accept
(
lexer
,
TokenType
.
INTEGER
).
get
Value
();
return
accept
(
lexer
,
TokenType
.
INTEGER
).
get
Int
();
}
else
{
lexer
.
pushBack
();
return
1
;
...
...
src/main/java/org/orekit/utils/units/Token.java
View file @
3380d717
...
...
@@ -16,6 +16,8 @@
*/
package
org.orekit.utils.units
;
import
org.hipparchus.fraction.Fraction
;
/** Unit token.
* @author Luc Maisonobe
* @since 11.0
...
...
@@ -32,20 +34,25 @@ class Token {
private
final
PrefixedUnit
unit
;
/** Integer value. */
private
final
int
value
;
private
final
int
integer
;
/** Fraction value. */
private
final
Fraction
fraction
;
/** Build a token.
* @param subString substring corresponding to the token
* @param type token type
* @param unit prefixed unit value
* @param value integer value of the token
* @param integer integer value
* @param fraction fraction value
*/
Token
(
final
CharSequence
subString
,
final
TokenType
type
,
final
PrefixedUnit
unit
,
final
int
value
)
{
final
PrefixedUnit
unit
,
final
int
integer
,
final
Fraction
fraction
)
{
this
.
subString
=
subString
;
this
.
type
=
type
;
this
.
unit
=
unit
;
this
.
value
=
value
;
this
.
integer
=
integer
;
this
.
fraction
=
fraction
;
}
/** Get the substring corresponding to the token.
...
...
@@ -69,11 +76,18 @@ class Token {
return
unit
;
}
/** Get the integer value.
/** Get the integer value
(numerator in case of fraction)
.
* @return integer value
*/
public
int
getValue
()
{
return
value
;
public
int
getInt
()
{
return
integer
;
}
/** Get the fraction value.
* @return fraction value
*/
public
Fraction
getFraction
()
{
return
fraction
;
}
}
src/main/java/org/orekit/utils/units/TokenType.java
View file @
3380d717
...
...
@@ -44,6 +44,9 @@ enum TokenType {
SQUARE_ROOT
,
/** Integer. */
INTEGER
;
INTEGER
,
/** Fraction. */
FRACTION
;
}
src/main/java/org/orekit/utils/units/Unit.java
View file @
3380d717
...
...
@@ -414,10 +414,10 @@ public class Unit implements Serializable {
* <p>
* All the SI prefix (from "y", yocto, to "Y", Yotta) are accepted. Beware
* that some combinations are forbidden, for example "Pa" is Pascal, not
* peta-years, and "as" is arcsecond for
us
, not atto-seconds, because
many people
* in the space field use mas for milli-arcseconds and µas for
micro-arcseconds.
* Beware that prefixes are case-sensitive! Prefix and units
must be joined
* without blanks.
* peta-years, and "as" is arcsecond for
this parser
, not atto-seconds, because
*
many people
in the space field use mas for milli-arcseconds and µas for
*
micro-arcseconds.
Beware that prefixes are case-sensitive! Prefix and units
*
must be joined
without blanks.
* </p>
* <ul>
* <li>multiplication can specified with either "*", "×" or "." as the operator</li>
...
...
@@ -430,12 +430,22 @@ public class Unit implements Serializable {
* </li>
* </ul>
* <p>
* Fractional exponents are allowed, but only with regular characters (because unicode
* does not provide a superscript '/'). Negative exponents can be used too.
* Exponents can be specified in different ways:
* <ul>
* <li>as an integer, as in "m^-2" or "m⁻²"</li>
* <li>directly as unicode characters for the few fractions that unicode supports, as in "Ω^⅞"</li>
* <li>as the special decimal value 0.5 which is used by CCSDS, as in "km**0.5"</li>
* <li>as a pair of parentheses surrounding two integers separated by a '/', as in "Pa^(11/12)"</li>
* </ul>
* For integer exponents, the digits must be ASCII digits from the Basic Latin block from
* unicode if explicit exponent maker "**" or "^" was used, or using unicode superscript
* digits if implicit exponentiation (i.e. no markers at all) is used. Unicode superscripts
* are not allowed fractional exponents because unicode does not provide a superscript '/'.
* Negative exponents can be used too.
* </p>
* <p>
* These rules mean all the following (silly) examples are parsed properly:
* MHz, km/√d, kg.m.s⁻¹, µas^
(2/5)
/(h**(2)×m)³, km/√(kg.s)
* MHz, km/√d, kg.m.s⁻¹, µas^
⅖
/(h**(2)×m)³, km/√(kg.s)
, km**0.5
* </p>
* @param unitSpecification unit specification to parse
* @return parsed unit
...
...
src/test/java/org/orekit/utils/units/LexerTest.java
View file @
3380d717
...
...
@@ -16,6 +16,7 @@
*/
package
org.orekit.utils.units
;
import
org.hipparchus.fraction.Fraction
;
import
org.junit.Assert
;
import
org.junit.Test
;
import
org.orekit.errors.OrekitException
;
...
...
@@ -31,84 +32,84 @@ public class LexerTest {
@Test
public
void
testAllTypes
()
{
final
Lexer
lexer
=
new
Lexer
(
"√kg*km** (3/2) /(µs^2*Ω⁻⁷)"
);
expect
(
lexer
,
"√"
,
TokenType
.
SQUARE_ROOT
,
null
,
null
,
0
);
expect
(
lexer
,
"kg"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
KILOGRAM
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"km"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
KILO
,
Unit
.
METRE
,
0
);
expect
(
lexer
,
"**"
,
TokenType
.
POWER
,
null
,
null
,
0
);
expect
(
lexer
,
"("
,
TokenType
.
OPEN
,
null
,
null
,
0
);
expect
(
lexer
,
"3"
,
TokenType
.
INTEGER
,
null
,
null
,
3
);
expect
(
lexer
,
"/"
,
TokenType
.
DIVISION
,
null
,
null
,
0
);
expect
(
lexer
,
"2"
,
TokenType
.
INTEGER
,
null
,
null
,
2
);
expect
(
lexer
,
")"
,
TokenType
.
CLOSE
,
null
,
null
,
0
);
expect
(
lexer
,
"/"
,
TokenType
.
DIVISION
,
null
,
null
,
0
);
expect
(
lexer
,
"("
,
TokenType
.
OPEN
,
null
,
null
,
0
);
expect
(
lexer
,
"µs"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
MICRO
,
Unit
.
SECOND
,
0
);
expect
(
lexer
,
"^"
,
TokenType
.
POWER
,
null
,
null
,
0
);
expect
(
lexer
,
"2"
,
TokenType
.
INTEGER
,
null
,
null
,
2
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"Ω"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
OHM
,
0
);
expect
(
lexer
,
""
,
TokenType
.
POWER
,
null
,
null
,
0
);
expect
(
lexer
,
"⁻⁷"
,
TokenType
.
INTEGER
,
null
,
null
,
-
7
);
expect
(
lexer
,
")"
,
TokenType
.
CLOSE
,
null
,
null
,
0
);
expect
(
lexer
,
"√"
,
TokenType
.
SQUARE_ROOT
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"kg"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
KILOGRAM
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"km"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
KILO
,
Unit
.
METRE
,
0
,
1
);
expect
(
lexer
,
"**"
,
TokenType
.
POWER
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"("
,
TokenType
.
OPEN
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"3"
,
TokenType
.
INTEGER
,
null
,
null
,
3
,
1
);
expect
(
lexer
,
"/"
,
TokenType
.
DIVISION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"2"
,
TokenType
.
INTEGER
,
null
,
null
,
2
,
1
);
expect
(
lexer
,
")"
,
TokenType
.
CLOSE
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"/"
,
TokenType
.
DIVISION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"("
,
TokenType
.
OPEN
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"µs"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
MICRO
,
Unit
.
SECOND
,
0
,
1
);
expect
(
lexer
,
"^"
,
TokenType
.
POWER
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"2"
,
TokenType
.
INTEGER
,
null
,
null
,
2
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"Ω"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
OHM
,
0
,
1
);
expect
(
lexer
,
""
,
TokenType
.
POWER
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"⁻⁷"
,
TokenType
.
INTEGER
,
null
,
null
,
-
7
,
1
);
expect
(
lexer
,
")"
,
TokenType
.
CLOSE
,
null
,
null
,
0
,
1
);
Assert
.
assertNull
(
lexer
.
next
());
}
@Test
public
void
testRegularExponent
()
{
final
Lexer
lexer
=
new
Lexer
(
"N^123450 × MHz^-98765"
);
expect
(
lexer
,
"N"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
NEWTON
,
0
);
expect
(
lexer
,
"^"
,
TokenType
.
POWER
,
null
,
null
,
0
);
expect
(
lexer
,
"123450"
,
TokenType
.
INTEGER
,
null
,
null
,
123450
);
expect
(
lexer
,
"×"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"MHz"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
MEGA
,
Unit
.
HERTZ
,
0
);
expect
(
lexer
,
"^"
,
TokenType
.
POWER
,
null
,
null
,
0
);
expect
(
lexer
,
"-98765"
,
TokenType
.
INTEGER
,
null
,
null
,
-
98765
);
expect
(
lexer
,
"N"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
NEWTON
,
0
,
1
);
expect
(
lexer
,
"^"
,
TokenType
.
POWER
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"123450"
,
TokenType
.
INTEGER
,
null
,
null
,
123450
,
1
);
expect
(
lexer
,
"×"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"MHz"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
MEGA
,
Unit
.
HERTZ
,
0
,
1
);
expect
(
lexer
,
"^"
,
TokenType
.
POWER
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"-98765"
,
TokenType
.
INTEGER
,
null
,
null
,
-
98765
,
1
);
Assert
.
assertNull
(
lexer
.
next
());
}
@Test
public
void
testSuperscriptExponent
()
{
final
Lexer
lexer
=
new
Lexer
(
"SFU⁺¹²³⁴⁵⁰ ⁄ mas⁻⁹⁸⁷⁶⁵"
);
expect
(
lexer
,
"SFU"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
SOLAR_FLUX_UNIT
,
0
);
expect
(
lexer
,
""
,
TokenType
.
POWER
,
null
,
null
,
0
);
expect
(
lexer
,
"⁺¹²³⁴⁵⁰"
,
TokenType
.
INTEGER
,
null
,
null
,
123450
);
expect
(
lexer
,
"⁄"
,
TokenType
.
DIVISION
,
null
,
null
,
0
);
expect
(
lexer
,
"mas"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
MILLI
,
Unit
.
ARC_SECOND
,
0
);
expect
(
lexer
,
""
,
TokenType
.
POWER
,
null
,
null
,
0
);
expect
(
lexer
,
"⁻⁹⁸⁷⁶⁵"
,
TokenType
.
INTEGER
,
null
,
null
,
-
98765
);
expect
(
lexer
,
"SFU"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
SOLAR_FLUX_UNIT
,
0
,
1
);
expect
(
lexer
,
""
,
TokenType
.
POWER
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"⁺¹²³⁴⁵⁰"
,
TokenType
.
INTEGER
,
null
,
null
,
123450
,
1
);
expect
(
lexer
,
"⁄"
,
TokenType
.
DIVISION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"mas"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
MILLI
,
Unit
.
ARC_SECOND
,
0
,
1
);
expect
(
lexer
,
""
,
TokenType
.
POWER
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"⁻⁹⁸⁷⁶⁵"
,
TokenType
.
INTEGER
,
null
,
null
,
-
98765
,
1
);
Assert
.
assertNull
(
lexer
.
next
());
}
@Test
public
void
testSignWithoutDigits
()
{
final
Lexer
lexer
=
new
Lexer
(
"Pa⁻"
);
expect
(
lexer
,
"Pa"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
PASCAL
,
0
);
expect
(
lexer
,
""
,
TokenType
.
POWER
,
null
,
null
,
0
);
expect
(
lexer
,
"Pa"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
PASCAL
,
0
,
1
);
expect
(
lexer
,
""
,
TokenType
.
POWER
,
null
,
null
,
0
,
1
);
expectFailure
(
lexer
);
}
@Test
public
void
testMultipleSigns
()
{
final
Lexer
lexer
=
new
Lexer
(
"MJ⁻⁺²"
);
expect
(
lexer
,
"MJ"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
MEGA
,
Unit
.
JOULE
,
0
);
expect
(
lexer
,
""
,
TokenType
.
POWER
,
null
,
null
,
0
);
expect
(
lexer
,
"MJ"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
MEGA
,
Unit
.
JOULE
,
0
,
1
);
expect
(
lexer
,
""
,
TokenType
.
POWER
,
null
,
null
,
0
,
1
);
expectFailure
(
lexer
);
}
@Test
public
void
testUnknownCharacter
()
{
final
Lexer
lexer
=
new
Lexer
(
"pW^2#"
);
expect
(
lexer
,
"pW"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
PICO
,
Unit
.
WATT
,
0
);
expect
(
lexer
,
"^"
,
TokenType
.
POWER
,
null
,
null
,
0
);
expect
(
lexer
,
"2"
,
TokenType
.
INTEGER
,
null
,
null
,
2
);
expect
(
lexer
,
"pW"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
PICO
,
Unit
.
WATT
,
0
,
1
);
expect
(
lexer
,
"^"
,
TokenType
.
POWER
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"2"
,
TokenType
.
INTEGER
,
null
,
null
,
2
,
1
);
expectFailure
(
lexer
);
}
@Test
public
void
testPercentageCharacter
()
{
final
Lexer
lexer
=
new
Lexer
(
"%"
);
expect
(
lexer
,
"%"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
PERCENT
,
0
);
expect
(
lexer
,
"%"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
PERCENT
,
0
,
1
);
Assert
.
assertNull
(
lexer
.
next
());
}
...
...
@@ -127,102 +128,133 @@ public class LexerTest {
@Test
public
void
testStartWithSuperscript
()
{
final
Lexer
lexer
=
new
Lexer
(
"³"
);
expect
(
lexer
,
"³"
,
TokenType
.
INTEGER
,
null
,
null
,
3
);
expect
(
lexer
,
"³"
,
TokenType
.
INTEGER
,
null
,
null
,
3
,
1
);
Assert
.
assertNull
(
lexer
.
next
());
}
@Test
public
void
testCharacters
()
{
final
Lexer
lexer
=
new
Lexer
(
"d*°*min*◦*deg*′*hh*'*ad*″*a*\"*µ''"
);
expect
(
lexer
,
"d"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
DAY
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"°"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
DEGREE
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"min"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
MINUTE
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"◦"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
DEGREE
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"deg"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
DEGREE
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"′"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
ARC_MINUTE
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"hh"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
HECTO
,
Unit
.
HOUR
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"'"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
ARC_MINUTE
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"ad"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
ATTO
,
Unit
.
DAY
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"″"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
ARC_SECOND
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"a"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
YEAR
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"\""
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
ARC_SECOND
,
0
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
);
expect
(
lexer
,
"µ''"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
MICRO
,
Unit
.
ARC_SECOND
,
0
);
expect
(
lexer
,
"d"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
DAY
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"°"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
DEGREE
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"min"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
MINUTE
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"◦"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
DEGREE
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"deg"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
DEGREE
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"′"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
ARC_MINUTE
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"hh"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
HECTO
,
Unit
.
HOUR
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"'"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
ARC_MINUTE
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"ad"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
ATTO
,
Unit
.
DAY
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"″"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
ARC_SECOND
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"a"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
YEAR
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"\""
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
ARC_SECOND
,
0
,
1
);
expect
(
lexer
,
"*"
,
TokenType
.
MULTIPLICATION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"µ''"
,
TokenType
.
PREFIXED_UNIT
,
Prefix
.
MICRO
,
Unit
.
ARC_SECOND
,
0
,
1
);
Assert
.
assertNull
(
lexer
.
next
());
}
@Test
public
void
testOneHalfAsDecimal
()
{
final
Lexer
lexer
=
new
Lexer
(
"0.5"
);
expect
(
lexer
,
"0.5"
,
TokenType
.
FRACTION
,
null
,
null
,
1
,
2
);
Assert
.
assertNull
(
lexer
.
next
());
}
@Test
public
void
testUnicodeFractions
()
{
final
Lexer
lexer
=
new
Lexer
(
"¼½¾⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞"
);
expect
(
lexer
,
"¼"
,
TokenType
.
FRACTION
,
null
,
null
,
1
,
4
);
expect
(
lexer
,
"½"
,
TokenType
.
FRACTION
,
null
,
null
,
1
,
2
);
expect
(
lexer
,
"¾"
,
TokenType
.
FRACTION
,
null
,
null
,
3
,
4
);
expect
(
lexer
,
"⅐"
,
TokenType
.
FRACTION
,
null
,
null
,
1
,
7
);
expect
(
lexer
,
"⅑"
,
TokenType
.
FRACTION
,
null
,
null
,
1
,
9
);
expect
(
lexer
,
"⅒"
,
TokenType
.
FRACTION
,
null
,
null
,
1
,
10
);
expect
(
lexer
,
"⅓"
,
TokenType
.
FRACTION
,
null
,
null
,
1
,
3
);
expect
(
lexer
,
"⅔"
,
TokenType
.
FRACTION
,
null
,
null
,
2
,
3
);
expect
(
lexer
,
"⅕"
,
TokenType
.
FRACTION
,
null
,
null
,
1
,
5
);
expect
(
lexer
,
"⅖"
,
TokenType
.
FRACTION
,
null
,
null
,
2
,
5
);
expect
(
lexer
,
"⅗"
,
TokenType
.
FRACTION
,
null
,
null
,
3
,
5
);
expect
(
lexer
,
"⅘"
,
TokenType
.
FRACTION
,
null
,
null
,
4
,
5
);
expect
(
lexer
,
"⅙"
,
TokenType
.
FRACTION
,
null
,
null
,
1
,
6
);
expect
(
lexer
,
"⅚"
,
TokenType
.
FRACTION
,
null
,
null
,
5
,
6
);
expect
(
lexer
,
"⅛"
,
TokenType
.
FRACTION
,
null
,
null
,
1
,
8
);
expect
(
lexer
,
"⅜"
,
TokenType
.
FRACTION
,
null
,
null
,
3
,
8
);
expect
(
lexer
,
"⅝"
,
TokenType
.
FRACTION
,
null
,
null
,
5
,
8
);
expect
(
lexer
,
"⅞"
,
TokenType
.
FRACTION
,
null
,
null
,
7
,
8
);
Assert
.
assertNull
(
lexer
.
next
());
}
@Test
public
void
testPushBack
()
{
final
Lexer
lexer
=
new
Lexer
(
"m/s"
);
expect
(
lexer
,
"m"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
METRE
,
0
);
expect
(
lexer
,
"/"
,
TokenType
.
DIVISION
,
null
,
null
,
0
);
expect
(
lexer
,
"m"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
METRE
,
0
,
1
);
expect
(
lexer
,
"/"
,
TokenType
.
DIVISION
,
null
,
null
,
0
,
1
);
lexer
.
pushBack
();
expect
(
lexer
,
"/"
,
TokenType
.
DIVISION
,
null
,
null
,
0
);
expect
(
lexer
,
"s"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
SECOND
,
0
);
expect
(
lexer
,
"/"
,
TokenType
.
DIVISION
,
null
,
null
,
0
,
1
);
expect
(
lexer
,
"s"
,
TokenType
.
PREFIXED_UNIT
,
null
,
Unit
.
SECOND
,
0
,
1
);
}