/**
* Copyright 2006 Bertoli Marco
*
* 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 com.gamingmesh.jobs.resources.jfep;
import java.util.HashSet;
/**
*
Name: Parser
* Description:
* Simple Java arithmetic expression parser and ELL(1) grammar syntactical analizer and evaluator.
* Parser supports detection of implicit multiplication when a constant is followed by a variable or function.
*
* Grammar:
* S -> E (('+' | '-') E)*
* E -> G ('*' G)*
* G -> H ('/' H)*
* H -> T ('%' T)*
* T -> F | '-' F
* F -> U ('^' U)*
* U -> function '(' S ')' | number | '(' S ')' | variable
*
* Date: 08/dic/06
* Time: 13:58:28
* @author Bertoli Marco
* @version 1.0
*/
public class Parser {
// Special tokens
private static final char TERM = '@'; // Input end character
private static final char NUM = 'n'; // number
private static final char FUNC = 'f'; // function
private static final char VAR = 'x'; // variable
private static final char OP_BRACKET = '('; // open bracket
private static final char CL_BRACKET = ')'; // closed bracket
private char[] input;
private boolean error;
private int inp_cur; // Current input position
private Element cc; // Current character
private ExpressionNode root; // Root of expression tree
private HashSet functions; // Used functions
private HashSet variables; // Used variables
/**
* Class constructor
* @param str Input string to be analyzed
*/
public Parser(String str) {
this(str, false);
}
/**
* Class constructor
* @param str Input string to be analyzed
* @param error if true, an exception is raised when value is get
* from a variable not initialized. If false, that variable is
* considered as zero.
*/
public Parser(String str, boolean error) {
input = convertInput(str);
root = null;
inp_cur = 0;
this.error = error;
functions = new HashSet();
variables = new HashSet();
}
/**
* Converts input string in a character array and removes unsupported characters
* @param str input string
* @return converted string, terminated by TERM character
*/
protected char[] convertInput(String str) {
char[] input = str.toCharArray();
char[] output = new char[input.length+1]; // One more space for terminator
int pos = 0; // Position on output
for (int i=0; i= '0' && c <= '9') || c == '.')
return true;
return false;
}
/**
* Returns if imput character character is an operator
* @param c input character
* @return truth value
*/
private boolean isOperator(char c) {
for (int i=0;i= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
c == '_');
}
/**
* Returns if input character is a bracket
* @param c character
* @return truth value
*/
private boolean isBracket(char c) {
return (c == OP_BRACKET) || (c == CL_BRACKET);
}
/**
* Returns if imput character is the last one (TERM)
* @param c input character
* @return truth value
*/
private boolean isEndOfExpression (char c) {
return c == TERM;
}
/**
* Perform parsing of input string
* @return Element
*/
private Element parse() {
// Detects implicit multiplication
if (detectImplicitMult())
return new Element('*');
// Matches operators, brackets and termination character
if (isOperator(input[inp_cur]) || isBracket(input[inp_cur]) || isEndOfExpression(input[inp_cur]))
return new Element(input[inp_cur++]);
// Matches numbers
if (isNumber(input[inp_cur])) {
int tmp = inp_cur++;
// Finds the end of number
while (isNumber(input[inp_cur]))
inp_cur++;
// Eventual exponential
if (Character.toLowerCase(input[inp_cur]) == 'e') {
if (input[inp_cur + 1] == '-' || isNumber(input[inp_cur + 1]))
inp_cur += 2;
while (isNumber(input[inp_cur]))
inp_cur++;
}
String s = new String(input, tmp, inp_cur - tmp);
double d;
try {
d = Double.valueOf(s).doubleValue();
}
catch (NumberFormatException ex) {
throw new ParseError("Invalid number: "+s, inp_cur);
}
return new Element(NUM, d);
}
// Matches text (functions or variables or built-in constants)
if (isText(input[inp_cur])) {
int tmp = inp_cur++;
// Finds the end of text
while (isText(input[inp_cur]) || isNumber(input[inp_cur]))
inp_cur++;
String s = new String(input, tmp, inp_cur - tmp);
String lower = s.toLowerCase();
// Now searches if this string is a function
for (int i=0; i
* S -> E (('+' | '-') E)*
* @return Node
*/
private ExpressionNode S() {
ExpressionNode current = E();
while (cc.getToken() == '+' || cc.getToken() == '-') {
char operator = cc.getToken();
cc = parse();
current = new OperatorNode(current, E(), operator);
}
return current;
}
/**
* Non-terminal E
* E -> G ('*' G)*
* @return Node
*/
private ExpressionNode E() {
ExpressionNode current = G();
while (cc.getToken() == '*') {
cc = parse();
current = new OperatorNode(current, G(), '*');
}
return current;
}
/**
* Non-terminal G
* G -> H ('/' H)*
* @return Node
*/
private ExpressionNode G() {
ExpressionNode current = H();
while (cc.getToken() == '/') {
cc = parse();
current = new OperatorNode(current, H(), '/');
}
return current;
}
/**
* Non-terminal H
* H -> T ('%' T)*
* @return Node
*/
private ExpressionNode H() {
ExpressionNode current = T();
while (cc.getToken() == '%') {
cc = parse();
current = new OperatorNode(current, T(), '%');
}
return current;
}
/**
* Non-terminal T
* T -> F | '-' F
* @return Node
*/
private ExpressionNode T() {
if (cc.getToken() == '-') {
cc = parse();
return new FunctionNode(F(), "-");
}
return F();
}
/**
* Non-terminal F
* F -> U ('^' U)*
* @return Node
*/
private ExpressionNode F() {
ExpressionNode left;
left = U();
if (cc.getToken() == '^') {
cc = parse();
return new OperatorNode(left, F(), '^');
}
return left;
}
/**
* Non-terminal U
* U -> function '(' S ')' | number | '(' S ')' | variable
* @return Node
*/
private ExpressionNode U() {
switch(cc.getToken()) {
case NUM:
double n = cc.number;
int constPosition = cc.position; // Position if this is a built-in constant
cc = parse();
if (constPosition >= 0)
return new ConstantNode(constPosition);
else
return new ConstantNode(n);
case VAR :
String name = cc.name;
cc = parse();
return new VariableNode(name, error);
case OP_BRACKET :
cc = parse();
ExpressionNode tmp = S();
if (cc.getToken() == CL_BRACKET) {
cc = parse();
return tmp;
}
throw new ParseError("Semantic Error, expected '" + CL_BRACKET + "'", inp_cur);
case FUNC :
int function = cc.position;
cc = parse();
if (cc.getToken() == OP_BRACKET)
cc = parse();
else
throw new ParseError("Semantic Error, expected '"+OP_BRACKET+"'", inp_cur);
ExpressionNode tmp2 = S();
if (cc.getToken() == CL_BRACKET)
cc = parse();
else
throw new ParseError("Semantic Error, expected '" + CL_BRACKET + "'", inp_cur);
return new FunctionNode(tmp2, function);
}
throw new ParseError("Semantic Error, expected function or variable or constant or '('", inp_cur);
}
/**
* Data structure used internally to return parsed elements.
*/
private class Element {
private char token;
public double number = Double.NaN;
public int position = -1;
public String name = null;
public Element (char token) {
this.token = token;
}
public Element (char token, double number) {
this.token = token;
this.number = number;
}
public Element (char token, String name) {
this.token = token;
this.name = name;
}
public Element (char token, int position) {
this.token = token;
this.position = position;
}
/**
* Returns the token
* @return char
*/
public char getToken() {
return token;
}
}
/**
* Sets the value for a given variable
* @param name name of the variable to be set (case sensitive)
* @param value value for the variable
* @throws ParseError if parsed input string was malformed
*/
public void setVariable(String name, double value) throws ParseError {
getTree();
root.setVariable(name, value);
}
/**
* Returns the value for evaluated expression
* @return value of expression
* @throws ParseError if parsed input string was malformed
* @throws EvaluationException if one variable was not initialized and parser was
* created with error = true
* @see #Parser(String, boolean)
*/
public double getValue() throws ParseError, EvaluationException {
getTree();
return root.getValue();
}
/**
* Returns a string rappresentation of parsed expression with the right parentesis
* @return a string rappresentation of parsed expression with the right parentesis
* @throws ParseError if parsed input string was malformed
*/
public String getExpression() throws ParseError {
getTree();
return root.toString();
}
/**
* Returns a Set of all functions parsed in input string
* @return a set with all parsed functions
* @throws ParseError if parsed input string was malformed
*/
public HashSet getParsedFunctions() throws ParseError {
getTree();
return functions;
}
/**
* Returns a Set of all variables parsed in input string
* @return a set with all parsed variables
* @throws ParseError if parsed input string was malformed
*/
public HashSet getParsedVariables() throws ParseError {
getTree();
return variables;
}
/**
* Returns input string, without invalid characters
* @return input string without invalid characters
*/
public String getInputString() {
StringBuffer output = new StringBuffer();
for (int i=0; i