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 }
|