001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.util;
011:
012: import java.util.*;
013: import org.mmbase.util.logging.*;
014:
015: /**
016: * Class to calculate expressions. It implements a simple LL(1)
017: * grammar to calculate simple expressions with the basic
018: * operators +,-,*,/ and brackets.
019: * <br />
020: * The grammar in EBNF notation:
021: * <br />
022: * <expr> -> <term> { '+' <term> } | <term> { '-' <term> } <br />
023: * <term> -> <fact> { '*' <fact> } | <fact> { '/' <fact> } <br />
024: * <fact> -> <nmeral> | '(' <expr> ')' <br />
025: *
026: * @author Arnold Beck
027: * @version $Id: ExprCalc.java,v 1.12 2004/09/29 14:29:24 pierre Exp $
028: */
029: public class ExprCalc {
030: private static final int MC_SYMB = 1;
031: private static final int MC_NUM = 2;
032: private static final int MC_NONE = 0;
033: private static final int MC_EOT = -1;
034:
035: private static final Logger log = Logging
036: .getLoggerInstance(ExprCalc.class);
037:
038: // a token is represented by an tokencode (MCode)
039: // and a tokenvalue (MSym or MNum) depending on
040: // the tokencode
041:
042: private StringTokenizer tokenizer;
043: private String input;
044:
045: private int mCode;
046: private char mSymb;
047: private double mNum;
048:
049: private double result;
050:
051: /**
052: * Constructor of ExrpCalc
053: * @param input a <code>String</code> representing the expression
054: */
055: public ExprCalc(String input) {
056: this .input = input;
057: tokenizer = new StringTokenizer(input, "+-*/()% \t", true);
058: mCode = MC_NONE;
059: result = expr();
060: if (mCode != MC_EOT) {
061: log.error("Could not evaluate expression: '" + input + "'");
062: }
063: }
064:
065: /**
066: * Returns the calculated value of the expression
067: */
068: public double getResult() {
069: return result;
070: }
071:
072: /**
073: * The lexer to produce a token when mCode is MC_NONE
074: */
075: private boolean lex() {
076: String token;
077: if (mCode == MC_NONE) {
078: mCode = MC_EOT;
079: mSymb = '\0';
080: mNum = 0.0;
081: try {
082: do {
083: token = tokenizer.nextToken();
084: } while (token.equals(" ") || token.equals("\t"));
085: } catch (NoSuchElementException e) {
086: return false;
087: }
088: // numeral
089: if (Character.isDigit(token.charAt(0))) {
090: int i;
091: for (i = 0; i < token.length()
092: && (Character.isDigit(token.charAt(i)) || token
093: .charAt(i) == '.'); i++) {
094: }
095: ;
096: if (i != token.length()) {
097: log.error("Could not evaluate expression '" + token
098: + "' of '" + input + "'");
099: }
100: try {
101: mNum = (Double.valueOf(token)).doubleValue();
102: } catch (NumberFormatException e) {
103: log.error("Could not evaluate expression ('"
104: + token + "' not a number) of '" + input
105: + "'");
106: }
107: mCode = MC_NUM;
108: } else { // symbol
109: mSymb = token.charAt(0);
110: mCode = MC_SYMB;
111: }
112: }
113: return true;
114: }
115:
116: /**
117: * expr implements the rule: <br />
118: * <expr> -< <term> { '+' <term> } | <term> { '-' <term> } .
119: */
120: private double expr() {
121: double tmp = term();
122: while (lex() && mCode == MC_SYMB
123: && (mSymb == '+' || mSymb == '-')) {
124: mCode = MC_NONE;
125: if (mSymb == '+') {
126: tmp += term();
127: } else {
128: tmp -= term();
129: }
130: }
131: if (mCode == MC_SYMB && mSymb == '(' || mCode == MC_SYMB
132: && mSymb == ')' || mCode == MC_EOT) {
133:
134: } else {
135: log.error("expr: Could not evaluate expression '" + input
136: + "'");
137: }
138: return tmp;
139: }
140:
141: /**
142: * term implements the rule: <br />
143: * <term> -< <fact> { '*' <fact> } | <fact> { '/' <fact> } .
144: */
145: private double term() {
146: double tmp = fac();
147: while (lex() && mCode == MC_SYMB
148: && (mSymb == '*' || mSymb == '/' || mSymb == '%')) {
149: mCode = MC_NONE;
150: if (mSymb == '*') {
151: tmp *= fac();
152: } else if (mSymb == '/') {
153: tmp /= fac();
154: } else {
155: tmp %= fac();
156: }
157: }
158: return tmp;
159: }
160:
161: /**
162: * fac implements the rule <br />
163: * <fact> -< <nmeral> | '(' <expr> ')' .
164: */
165: private double fac() {
166: double tmp = -1;
167: boolean minus = false;
168:
169: if (lex() && mCode == MC_SYMB && mSymb == '-') {
170: mCode = MC_NONE;
171: minus = true;
172: }
173: if (lex() && mCode == MC_SYMB && mSymb == '(') {
174: mCode = MC_NONE;
175: tmp = expr();
176: if (lex() && mCode != MC_SYMB || mSymb != ')') {
177: log.error("fac1: Could not evaluate expression '"
178: + input + "'");
179: }
180: mCode = MC_NONE;
181: } else if (mCode == MC_NUM) {
182: mCode = MC_NONE;
183: tmp = mNum;
184: } else {
185: log.error("fac2: Could not evaluate expression '" + input
186: + "'");
187: }
188: if (minus)
189: tmp = -tmp;
190: return tmp;
191: }
192: }
|