MathStringTokenizer.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-7.2.0).
003  * Copyright (c) 2007-2023 Franz Wilhelmstötter
004  *
005  * Licensed under the Apache License, Version 2.0 (the "License");
006  * you may not use this file except in compliance with the License.
007  * You may obtain a copy of the License at
008  *
009  *      http://www.apache.org/licenses/LICENSE-2.0
010  *
011  * Unless required by applicable law or agreed to in writing, software
012  * distributed under the License is distributed on an "AS IS" BASIS,
013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014  * See the License for the specific language governing permissions and
015  * limitations under the License.
016  *
017  * Author:
018  *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019  */
020 package io.jenetics.prog.op;
021 
022 import static java.lang.Character.isDigit;
023 import static java.lang.Character.isJavaIdentifierPart;
024 import static java.lang.Character.isJavaIdentifierStart;
025 import static java.lang.String.format;
026 import static io.jenetics.prog.op.MathTokenType.COMMA;
027 import static io.jenetics.prog.op.MathTokenType.DIV;
028 import static io.jenetics.prog.op.MathTokenType.IDENTIFIER;
029 import static io.jenetics.prog.op.MathTokenType.LPAREN;
030 import static io.jenetics.prog.op.MathTokenType.MINUS;
031 import static io.jenetics.prog.op.MathTokenType.MOD;
032 import static io.jenetics.prog.op.MathTokenType.NUMBER;
033 import static io.jenetics.prog.op.MathTokenType.PLUS;
034 import static io.jenetics.prog.op.MathTokenType.POW;
035 import static io.jenetics.prog.op.MathTokenType.RPAREN;
036 import static io.jenetics.prog.op.MathTokenType.TIMES;
037 
038 import io.jenetics.ext.internal.parser.CharSequenceTokenizer;
039 import io.jenetics.ext.internal.parser.ParsingException;
040 import io.jenetics.ext.internal.parser.Token;
041 
042 /**
043  * Tokenizer for simple arithmetic expressions.
044  *
045  <pre>{@code
046  * LPAREN: '(';
047  * RPAREN: ')';
048  * COMMA: ',';
049  * PLUS: '+';
050  * MINUS: '-';
051  * TIMES: '*';
052  * DIV: '/';
053  * POW: '^';
054  * NUMBER: ('0'..'9')+ ('.' ('0'..'9')+)? ((e|E) (+|-)? ('0'..'9'))?
055  * ID: ('a'..'z'|'A'..'Z') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')+;
056  * WS: [ \r\n\t] + -> skip;
057  * }</pre>
058  *
059  @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
060  @since 7.1
061  @version 7.1
062  */
063 final class MathStringTokenizer extends CharSequenceTokenizer {
064 
065     public MathStringTokenizer(final CharSequence input) {
066         super(input);
067     }
068 
069     @Override
070     public Token<String> next() {
071         while (isNonEof(c)) {
072             final char value = c;
073 
074             switch (value) {
075                 case ' ''\r''\n''\t' -> {
076                     WS();
077                     continue;
078                 }
079                 case '(' -> {
080                     consume();
081                     return LPAREN.token(value);
082                 }
083                 case ')' -> {
084                     consume();
085                     return RPAREN.token(value);
086                 }
087                 case ',' -> {
088                     consume();
089                     return COMMA.token(value);
090                 }
091                 case '+' -> {
092                     consume();
093                     return PLUS.token(value);
094                 }
095                 case '-' -> {
096                     consume();
097                     return MINUS.token(value);
098                 }
099                 case '*' -> {
100                     if (LA(2== '*') {
101                         consume();
102                         consume();
103                         return POW.token("**");
104                     else {
105                         consume();
106                         return TIMES.token(value);
107                     }
108                 }
109                 case '/' -> {
110                     consume();
111                     return DIV.token(value);
112                 }
113                 case '%' -> {
114                     consume();
115                     return MOD.token(value);
116                 }
117                 case '^' -> {
118                     consume();
119                     return POW.token(value);
120                 }
121                 case '0''1''2''3''4''5''6''7''8''9' -> {
122                     return NUMBER();
123                 }
124                 default -> {
125                     if (isJavaIdentifierStart(c)) {
126                         return ID();
127                     else {
128                         throw new ParsingException(format("Got invalid character '%s' at position '%d'.", c, pos));
129                     }
130                 }
131             }
132         }
133 
134         return null;
135     }
136 
137     // NUMBER (E SIGN? UNSIGNED_INTEGER)?
138     private Token<String> NUMBER() {
139         final var value = new StringBuilder();
140 
141         REAL_NUMBER(value);
142         if ('e' == c || 'E' == c) {
143             value.append(c);
144             consume();
145 
146             if ('+' == c || '-' == c) {
147                 value.append(c);
148                 consume();
149             }
150             if (isDigit(c)) {
151                 UNSIGNED_NUMBER(value);
152             }
153         }
154 
155         return NUMBER.token(value.toString());
156     }
157 
158     // ('0' .. '9') + ('.' ('0' .. '9') +)?
159     private void REAL_NUMBER(final StringBuilder value) {
160         UNSIGNED_NUMBER(value);
161         if ('.' == c) {
162             value.append(c);
163             consume();
164             UNSIGNED_NUMBER(value);
165         }
166     }
167 
168     // ('0' .. '9')+
169     private void UNSIGNED_NUMBER(final StringBuilder value) {
170         while (isDigit(c)) {
171             value.append(c);
172             consume();
173         }
174     }
175 
176     private Token<String> ID() {
177         final var value = new StringBuilder();
178 
179         do {
180             value.append(c);
181             consume();
182         while (isJavaIdentifierPart(c));
183 
184         return IDENTIFIER.token(value.toString());
185     }
186 
187 }