MathStringTokenizer.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-7.1.0).
003  * Copyright (c) 2007-2022 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                 case '(':
079                     consume();
080                     return LPAREN.token(value);
081                 case ')':
082                     consume();
083                     return RPAREN.token(value);
084                 case ',':
085                     consume();
086                     return COMMA.token(value);
087                 case '+':
088                     consume();
089                     return PLUS.token(value);
090                 case '-':
091                     consume();
092                     return MINUS.token(value);
093                 case '*':
094                     if (LA(2== '*') {
095                         consume();
096                         consume();
097                         return POW.token("**");
098                     else {
099                         consume();
100                         return TIMES.token(value);
101                     }
102                 case '/':
103                     consume();
104                     return DIV.token(value);
105                 case '%':
106                     consume();
107                     return MOD.token(value);
108                 case '^':
109                     consume();
110                     return POW.token(value);
111                 case '0''1''2''3''4''5''6''7''8''9':
112                     return NUMBER();
113                 default:
114                     if (isJavaIdentifierStart(c)) {
115                         return ID();
116                     else {
117                         throw new ParsingException(format(
118                             "Got invalid character '%s' at position '%d'.",
119                             c, pos
120                         ));
121                     }
122             }
123         }
124 
125         return null;
126     }
127 
128     // NUMBER (E SIGN? UNSIGNED_INTEGER)?
129     private Token<String> NUMBER() {
130         final var value = new StringBuilder();
131 
132         REAL_NUMBER(value);
133         if ('e' == c || 'E' == c) {
134             value.append(c);
135             consume();
136 
137             if ('+' == c || '-' == c) {
138                 value.append(c);
139                 consume();
140             }
141             if (isDigit(c)) {
142                 UNSIGNED_NUMBER(value);
143             }
144         }
145 
146         return NUMBER.token(value.toString());
147     }
148 
149     // ('0' .. '9') + ('.' ('0' .. '9') +)?
150     private void REAL_NUMBER(final StringBuilder value) {
151         UNSIGNED_NUMBER(value);
152         if ('.' == c) {
153             value.append(c);
154             consume();
155             UNSIGNED_NUMBER(value);
156         }
157     }
158 
159     // ('0' .. '9')+
160     private void UNSIGNED_NUMBER(final StringBuilder value) {
161         while (isDigit(c)) {
162             value.append(c);
163             consume();
164         }
165     }
166 
167     private Token<String> ID() {
168         final var value = new StringBuilder();
169 
170         do {
171             value.append(c);
172             consume();
173         while (isJavaIdentifierPart(c));
174 
175         return IDENTIFIER.token(value.toString());
176     }
177 
178 }