/*
* Copyright 2008 Reg Whitton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.dev.eval;
import java.math.BigDecimal;
final class Tokeniser {
static final Character START_NEW_EXPRESSION = new Character('(');
private final String string;
private int position;
private Operator pushedBackOperator = null;
Tokeniser(String string) {
this.string = string;
this.position = 0;
}
int getPosition() {
return this.position;
}
void setPosition(int position) {
this.position = position;
}
void pushBack(Operator operator) {
this.pushedBackOperator = operator;
}
Operator getOperator(char endOfExpressionChar) {
/* Use any pushed back operator. */
if (this.pushedBackOperator != null) {
Operator operator = this.pushedBackOperator;
this.pushedBackOperator = null;
return operator;
}
/* Skip whitespace */
final int len = this.string.length();
char ch = 0;
while (this.position < len && Character.isWhitespace(ch = this.string.charAt(this.position))) {
this.position++;
}
if (this.position == len) {
if (endOfExpressionChar == 0) {
return Operator.END;
} else {
throw new RuntimeException("missing " + endOfExpressionChar);
}
}
this.position++;
if (ch == endOfExpressionChar) {
return Operator.END;
}
switch (ch) {
case '+': {
return Operator.ADD;
}
case '-': {
return Operator.SUB;
}
case '/': {
return Operator.DIV;
}
case '%': {
return Operator.REMAINDER;
}
case '*': {
return Operator.MUL;
}
case '?': {
return Operator.TERNARY;
}
case '>': {
if (this.position < len && this.string.charAt(this.position) == '=') {
this.position++;
return Operator.GE;
}
return Operator.GT;
}
case '<': {
if (this.position < len) {
switch (this.string.charAt(this.position)) {
case '=':
this.position++;
return Operator.LE;
case '>':
this.position++;
return Operator.NE;
}
}
return Operator.LT;
}
case '=': {
if (this.position < len && this.string.charAt(this.position) == '=') {
this.position++;
return Operator.EQ;
}
throw new RuntimeException("use == for equality at position " + this.position);
}
case '!': {
if (this.position < len && this.string.charAt(this.position) == '=') {
this.position++;
return Operator.NE;
}
throw new RuntimeException("use != or <> for inequality at position " + this.position);
}
case '&': {
if (this.position < len && this.string.charAt(this.position) == '&') {
this.position++;
return Operator.AND;
}
throw new RuntimeException("use && for AND at position " + this.position);
}
case '|': {
if (this.position < len && this.string.charAt(this.position) == '|') {
this.position++;
return Operator.OR;
}
throw new RuntimeException("use || for OR at position " + this.position);
}
default: {
/* Is this an identifier name for an operator function? */
if (Character.isUnicodeIdentifierStart(ch)) {
int start = this.position - 1;
while (this.position < len && Character.isUnicodeIdentifierPart(this.string.charAt(this.position))) {
this.position++;
}
String name = this.string.substring(start, this.position);
if (name.equals("pow")) {
return Operator.POW;
}
}
throw new RuntimeException("operator expected at position " + this.position + " instead of '" + ch + "'");
}
}
}
/**
* Called when an operand is expected next.
*
* @return one of:
* <UL>
* <LI>a {@link BigDecimal} value;</LI>
* <LI>the {@link String} name of a variable;</LI>
* <LI>{@link Tokeniser#START_NEW_EXPRESSION} when an opening parenthesis is found:</LI>
* <LI>or {@link Operator} when a unary operator is found in front of an operand</LI>
* </UL>
* @throws RuntimeException
* if the end of the string is reached unexpectedly.
*/
Object getOperand() {
/* Skip whitespace */
final int len = this.string.length();
char ch = 0;
while (this.position < len && Character.isWhitespace(ch = this.string.charAt(this.position))) {
this.position++;
}
if (this.position == len) {
throw new RuntimeException("operand expected but end of string found");
}
if (ch == '(') {
this.position++;
return START_NEW_EXPRESSION;
} else if (ch == '-') {
this.position++;
return Operator.NEG;
} else if (ch == '+') {
this.position++;
return Operator.PLUS;
} else if (ch == '.' || Character.isDigit(ch)) {
return getBigDecimal();
} else if (Character.isUnicodeIdentifierStart(ch) || ch == '$') {
int start = this.position++;
while (this.position < len && Character.isUnicodeIdentifierPart(this.string.charAt(this.position))) {
this.position++;
}
String name = this.string.substring(start, this.position);
/* Is variable name actually a keyword unary operator? */
if (name.equals("abs")) {
return Operator.ABS;
} else if (name.equals("int")) {
return Operator.INT;
}
/* Return variable name */
return name;
}
throw new RuntimeException("operand expected but '" + ch + "' found");
}
private BigDecimal getBigDecimal() {
final int len = this.string.length();
final int start = this.position;
char ch;
while (this.position < len && (Character.isDigit(ch = this.string.charAt(this.position)) || ch == '.')) {
this.position++;
}
/* Optional exponent part including another sign character. */
if (this.position < len && ((ch = this.string.charAt(this.position)) == 'E' || ch == 'e')) {
this.position++;
if (this.position < len && ((ch = this.string.charAt(this.position)) == '+' || ch == '-')) {
this.position++;
}
while (this.position < len && Character.isDigit(ch = this.string.charAt(this.position))
) {
this.position++;
}
}
return new BigDecimal(this.string.substring(start, this.position));
}
@Override
public String toString() {
return this.string.substring(0, this.position) + ">>>" + this.string.substring(this.position);
}
}