/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.ejs.control.value;
/*----------------------------------------------------------------------------------------*
* Parser.java version 1.0 Jun 16 1996 *
* Parser.java version 2.0 Aug 25 1996 *
* Parser.java version 2.1 Oct 14 1996 *
* Parser.java version 2.11 Oct 25 1996 *
* Parser.java version 2.2 Nov 8 1996 *
* Parser.java version 3.0 May 17 1997 *
* Parser.java version 3.01 Oct 18 2001 *
* *
* Parser.java version 4.0 Oct 25 2001 *
* *
* Copyright (c) 1996 Yanto Suryono. All Rights Reserved. *
* Version 4 Modifications by Wolfgang Christian *
* Adapted by Francisco Esquembre for his own use *
* *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation; either version 2 of the License, or (at your option) *
* any later version. *
* *
* This program is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
* *
*----------------------------------------------------------------------------------------*/
import java.util.Hashtable;
import java.util.Vector;
/**
* The class <code>Parser</code> is a mathematical expression parser.<p>
* Example of code that uses this class:<p>
*
* <pre>
* Parser parser = new Parser(1); // creates parser with one variable
* parser.defineVariable(0,"x"); // lets the variable be 'x'
* parser.define("Math.sin(x)/x"); // defines function: sin(x)/x
* parser.parse(); // parses the function
IMPORTANT: Notice that my variables start at 0 in this version
*
* // calculates: sin(x)/x with x = -5.0 .. +5.0 in 20 steps
* // and prints the result to standard output.
*
* double result;
* for (i=-10; i <= 10; i++) {
* parser.setVariable(0,(double)i/2.0f);
* result = parser.evaluate();
* System.out.println(result);
* }
* </pre>
*/
public final class ParserSuryono {
// global variables
private int var_count; // number of variables
private String var_name[]; // variables' name
private double var_value[]; // value of variables
private double number[]; // numeric constants in defined function
private String function = ""; // function definition //$NON-NLS-1$
private String postfix_code = ""; // the postfix code //$NON-NLS-1$
private boolean valid = false; // postfix code status
private int error; // error code of last process
private boolean ISBOOLEAN = false; // boolean flag
private boolean INRELATION = false; // relation flag
// variables used during parsing
private int position; // parsing pointer
private int start; // starting position of identifier
private int num; // number of numeric constants
private char character; // current character
// variables used during evaluating
private int numberindex; // pointer to numbers/constants bank
private double[] refvalue = null; // value of references
// built in constants and functions
static private final String constname[] = {"Math.E", "Math.PI"}; // Added by Paco //$NON-NLS-1$ //$NON-NLS-2$
static private final double constvalue[] = {Math.E, Math.PI};
static private final String funcnameNoParam[] = // Added by Paco
{"Math.random"}; //$NON-NLS-1$
static private final String funcname[] = // Changed by Paco
{
"Math.abs", "Math.acos", "Math.asin", "Math.atan", "Math.ceil", "Math.cos", "Math.exp", "Math.floor", "Math.log", "Math.rint", "Math.round", "Math.sin", "Math.sqrt", "Math.tan", "Math.toDegrees", "Math.toRadians" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ //$NON-NLS-13$ //$NON-NLS-14$ //$NON-NLS-15$ //$NON-NLS-16$
};
static private final String extfunc[] = // Changed by Paco
{"Math.atan2", "Math.IEEEremainder", "Math.max", "Math.min", "Math.pow"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
private static final int MAX_NUM = 200; // max numeric constants// Changed to 200 by W. Christian
private static final int NO_CONST = constname.length; // no. of built-in Constants // Paco
private static final int NO_FUNCSNOPARAM = funcnameNoParam.length; // no. of built-in functions with no parameters // Paco
private static final int NO_FUNCS = funcname.length; // no. of built-in functions // Paco
private static final int NO_EXT_FUNCS = extfunc.length; // no. of extended functions
private static final int STACK_SIZE = 50; // evaluation stack size
private double[] stack = new double[STACK_SIZE]; // moved by W. Christian from evaluate to global variables for speed
// references - version 3.0
private Hashtable<String, String> references = null;
private Vector<String> refnames = null;
// error codes
/**
* No error.
*/
public static final int NO_ERROR = 0;
/**
* Syntax error.
*/
public static final int SYNTAX_ERROR = 1;
/**
* Parentheses expected.
*/
public static final int PAREN_EXPECTED = 2;
/**
* Attempt to evaluate an uncompiled function.
*/
public static final int UNCOMPILED_FUNCTION = 3;
/**
* Expression expected.
*/
public static final int EXPRESSION_EXPECTED = 4;
/**
* Unknown identifier.
*/
public static final int UNKNOWN_IDENTIFIER = 5;
/**
* Operator expected.
*/
public static final int OPERATOR_EXPECTED = 6;
/**
* Parenthesis mismatch.
*/
public static final int PAREN_NOT_MATCH = 7;
/**
* Code damaged.
*/
public static final int CODE_DAMAGED = 8;
/**
* Stack overflow.
*/
public static final int STACK_OVERFLOW = 9;
/**
* Too many constants.
*/
public static final int TOO_MANY_CONSTS = 10;
/**
* Comma expected.
*/
public static final int COMMA_EXPECTED = 11;
/**
* Invalid operand.
*/
public static final int INVALID_OPERAND = 12;
/**
* Invalid operator.
*/
public static final int INVALID_OPERATOR = 13;
/**
* No function definition to parse.
*/
public static final int NO_FUNC_DEFINITION = 14;
/**
* Referenced name could not be found.
*/
public static final int REF_NAME_EXPECTED = 15;
// postfix codes
private static final int FUNC_OFFSET = 1000;
private static final int EXT_FUNC_OFFSET = FUNC_OFFSET+NO_FUNCS;
private static final int FUNCNOPARAM_OFFSET = EXT_FUNC_OFFSET+NO_EXT_FUNCS;
private static final int VAR_OFFSET = 2000;
private static final int REF_OFFSET = 3000;
private static final char CONST_OFFSET = (char) 253; // Paco
// private static final char PI_CODE = (char)253;
// private static final char E_CODE = (char)254;
private static final char NUMERIC = (char) 300; // Paco changed from 255 to 300 to leave space for constants
// Jump, followed by n : Displacement
private static final char JUMP_CODE = (char) 1;
// Relation less than (<)
private static final char LESS_THAN = (char) 2;
// Relation greater than (>)
private static final char GREATER_THAN = (char) 3;
// Relation less than or equal (<=)
private static final char LESS_EQUAL = (char) 4;
// Relation greater than or equal (>=)
private static final char GREATER_EQUAL = (char) 5;
// Relation not equal (<>)
private static final char NOT_EQUAL = (char) 6;
// Relation equal (=)
private static final char EQUAL = (char) 7;
// Conditional statement IF, followed by a conditional block :
// * Displacement (Used to jump to condition FALSE code)
// * Condition TRUE code
// * Jump to next code outside conditional block
// * Condition FALSE code
// * ENDIF
private static final char IF_CODE = (char) 8;
private static final char ENDIF = (char) 9;
private static final char AND_CODE = (char) 10; // Boolean AND
private static final char OR_CODE = (char) 11; // Boolean OR
private static final char NOT_CODE = (char) 12; // Boolean NOT
/**
* Wether a given token is a reserved word
* @param token
* @return
*/
static public boolean isKeyword(String token) { // Added by Paco
try {
Double.parseDouble(token);
return true;
} // It's a valid constant
catch(Exception _exc) {}
// Do nothing
for(int i = 0; i<NO_CONST; i++) {
if(token.equals(constname[i])) {
return true;
}
}
for(int i = 0; i<NO_FUNCS; i++) {
if(token.equals(funcname[i])) {
return true;
}
}
for(int i = 0; i<NO_EXT_FUNCS; i++) {
if(token.equals(extfunc[i])) {
return true;
}
}
for(int i = 0; i<NO_FUNCSNOPARAM; i++) {
if(token.equals(funcnameNoParam[i])) {
return true;
}
}
return false;
}
/**
* Gets an expression and returns a String[] of the variables the expression will need.
* This can be used to decide beforehand how many variables a new parser should have.
*/
static public String[] getVariableList(String _expression) { // Added by Paco
Vector<String> varlist = getVariableList(_expression, new Vector<String>());
if(varlist.size()<1) {
return new String[0];
}
return varlist.toArray(new String[1]);
}
/**
* Gets an expression and a Vector of declared variables and updates the Vector with
* extra variables the expression will need.
* This can be used to decide beforehand how many variables a new parser should have.
* @return the same Vector updated
*/
static public Vector<String> getVariableList(String _expression, Vector<String> varlist) { // Added by Paco
java.util.StringTokenizer tkn = new java.util.StringTokenizer(_expression, "() \t+-*/,<>=&|"); //$NON-NLS-1$
while(tkn.hasMoreTokens()) {
String token = tkn.nextToken();
if(isKeyword(token)) {
continue;
}
if(!varlist.contains(token)) {
varlist.add(token);
}
}
return varlist;
}
/**
* The constructor of <code>Parser</code>.
*
* @param variablecount the number of variables
*/
public ParserSuryono(int variablecount) {
var_count = variablecount;
references = new Hashtable<String, String>();
refnames = new Vector<String>();
// arrays are much faster than vectors (IMHO)
var_name = new String[variablecount];
var_value = new double[variablecount];
number = new double[MAX_NUM];
}
/**
* Sets the variable names.
*
* @param index the variable index (one based)
* @param name the variable name
*/
public void defineVariable(int index, String name) {
// if (index >= var_count) return; // Paco suppressed this check
var_name[index] = name; // Paco changed from index-1
}
/**
* Sets the variable value.
* The variable is accessed by index.
*
* @param index the variable index (one based)
* @param value the variable value
*/
public void setVariable(int index, double value) {
// if (index >= var_count) return; // Paco suppressed this check
var_value[index] = value; // Paco changed from index-1
}
/**
* Defines a function. Current postfix code becomes invalid.
*
* @param definition the function definition
*/
public void define(String definition) {
function = definition;
// function.toLowerCase(); // removed by Paco
// function=removeEscapeCharacter(function); // added by W. Christian
valid = false;
}
/**
* Parses defined function.
*/
public void parse() {
String allFunction = new String(function);
String orgFunction = new String(function);
int index;
if(valid) {
return;
}
num = 0;
error = NO_ERROR;
references.clear();
refnames.removeAllElements();
while((index = allFunction.lastIndexOf(";"))!=-1) { //$NON-NLS-1$
function = allFunction.substring(index+1)+')';
allFunction = allFunction.substring(0, index++);
// references are of form: refname1:reffunc1;refname2:reffunc2;...
String refname = null;
int separator = function.indexOf(":"); //$NON-NLS-1$
if(separator==-1) {
error = NO_FUNC_DEFINITION;
for(position = 0; position<function.length(); position++) {
if(function.charAt(position)!=' ') {
break;
}
}
position++;
} else {
refname = function.substring(0, separator);
function = function.substring(separator+1);
refname = refname.trim();
if(refname.equals("")) { //$NON-NLS-1$
error = REF_NAME_EXPECTED;
position = 1;
} else {
index += ++separator;
parseSubFunction();
}
}
if(error!=NO_ERROR) {
position += index;
break;
}
references.put(refname, postfix_code);
refnames.addElement(refname);
}
if(error==NO_ERROR) {
function = allFunction+')';
parseSubFunction();
}
function = orgFunction;
valid = (error==NO_ERROR);
}
/**
* Evaluates compiled function.
*
* @return the result of the function
*/
public double evaluate() {
int size = refnames.size();
double result;
if(!valid) {
error = UNCOMPILED_FUNCTION;
return 0;
}
error = NO_ERROR;
numberindex = 0;
if(size!=0) {
String orgPFC = postfix_code;
refvalue = new double[size];
for(int i = 0; i<refnames.size(); i++) {
String name = refnames.elementAt(i);
postfix_code = references.get(name);
result = evaluateSubFunction();
if(error!=NO_ERROR) {
postfix_code = orgPFC;
refvalue = null;
return result;
}
refvalue[i] = result;
}
postfix_code = orgPFC;
}
result = evaluateSubFunction();
refvalue = null;
// added by W. Christian to trap for NaN
if(Double.isNaN(result)) {
result = 0.0;
}
return result;
}
/**
* Gets error code of last operation.
*
* @return the error code
*/
public int getErrorCode() {
return error;
}
/**
* Gets error string/message of last operation.
*
* @return the error string
*/
public String getErrorString() {
return toErrorString(error);
}
/**
* Gets error position. Valid only if error code != NO_ERROR
*
* @return error position (one based)
*/
public int getErrorPosition() {
return position;
}
/**
* Converts error code to error string.
*
* @return the error string
*/
public static String toErrorString(int errorcode) {
String s = ""; //$NON-NLS-1$
switch(errorcode) {
case NO_ERROR :
s = "no error"; //$NON-NLS-1$
break;
case SYNTAX_ERROR :
s = "syntax error"; //$NON-NLS-1$
break;
case PAREN_EXPECTED :
s = "parenthesis expected"; //$NON-NLS-1$
break;
case UNCOMPILED_FUNCTION :
s = "uncompiled function"; //$NON-NLS-1$
break;
case EXPRESSION_EXPECTED :
s = "expression expected"; //$NON-NLS-1$
break;
case UNKNOWN_IDENTIFIER :
s = "unknown identifier"; //$NON-NLS-1$
break;
case OPERATOR_EXPECTED :
s = "operator expected"; //$NON-NLS-1$
break;
case PAREN_NOT_MATCH :
s = "parentheses not match"; //$NON-NLS-1$
break;
case CODE_DAMAGED :
s = "internal code damaged"; //$NON-NLS-1$
break;
case STACK_OVERFLOW :
s = "execution stack overflow"; //$NON-NLS-1$
break;
case TOO_MANY_CONSTS :
s = "too many constants"; //$NON-NLS-1$
break;
case COMMA_EXPECTED :
s = "comma expected"; //$NON-NLS-1$
break;
case INVALID_OPERAND :
s = "invalid operand type"; //$NON-NLS-1$
break;
case INVALID_OPERATOR :
s = "invalid operator"; //$NON-NLS-1$
break;
case NO_FUNC_DEFINITION :
s = "bad reference definition (: expected)"; //$NON-NLS-1$
break;
case REF_NAME_EXPECTED :
s = "reference name expected"; //$NON-NLS-1$
break;
}
return s;
}
/*----------------------------------------------------------------------------------------*
* Private methods begin here *
*----------------------------------------------------------------------------------------*/
/**
* Advances parsing pointer, skips pass all white spaces.
*
* @exception ParserException
*/
private void skipSpaces() throws ParserException {
try {
while(function.charAt(position-1)==' ') {
position++;
}
character = function.charAt(position-1);
} catch(StringIndexOutOfBoundsException e) {
throw new ParserException(PAREN_NOT_MATCH);
}
}
/**
* Advances parsing pointer, gets next character.
*
* @exception ParserException
*/
private void getNextCharacter() throws ParserException {
position++;
try {
character = function.charAt(position-1);
} catch(StringIndexOutOfBoundsException e) {
throw new ParserException(PAREN_NOT_MATCH);
}
}
/**
* Appends postfix code to compiled code.
*
* @param code the postfix code to append
*/
private void addCode(char code) {
postfix_code += code;
}
/**
* Scans a number. Valid format: xxx[.xxx[e[+|-]xxx]]
*
* @exception ParserException
*/
private void scanNumber() throws ParserException {
// changed by W. Christian to parse numbers with leading zeros.
String numstr = ""; //$NON-NLS-1$
double value;
if(num==MAX_NUM) {
throw new ParserException(TOO_MANY_CONSTS);
}
if(character!='.') { // added by W. Christian
do {
numstr += character;
getNextCharacter();
} while((character>='0')&&(character<='9'));
} else {
numstr += '0';
} // added by W. Christian
if(character=='.') {
do {
numstr += character;
getNextCharacter();
} while((character>='0')&&(character<='9'));
}
if((character=='e')||(character=='E')) { // Paco added 'E'
numstr += 'e';
getNextCharacter(); // Paco changes character to 'e'
if((character=='+')||(character=='-')) {
numstr += character;
getNextCharacter();
}
while((character>='0')&&(character<='9')) {
numstr += character;
getNextCharacter();
}
}
try {
value = Double.valueOf(numstr).doubleValue();
} catch(NumberFormatException e) {
position = start;
throw new ParserException(SYNTAX_ERROR);
}
number[num++] = value;
addCode(NUMERIC);
}
/**
* Scans a non-numerical identifier. Can be function call,
* variable, reference, etc.
*
* @exception ParserException
*/
private void scanNonNumeric() throws ParserException {
String stream = ""; //$NON-NLS-1$
if((character=='*')||(character=='/')||
// (character == '^') || Paco suppressed '^'
(character==')')||(character==',')||(character=='<')||(character=='>')||(character=='=')||(character=='&')||(character=='|')) {
throw new ParserException(SYNTAX_ERROR);
}
do {
stream += character;
getNextCharacter();
} while(!((character==' ')||(character=='+')||(character=='-')||(character=='*')||(character=='/')||
// (character == '^') || Paco suppressed '^'
(character=='(')||(character==')')||(character==',')||(character=='<')||(character=='>')||(character=='=')||(character=='&')||(character=='|')));
for(int i = 0; i<NO_CONST; i++) {
if(stream.equals(constname[i])) {
addCode((char) (i+CONST_OFFSET));
return;
}
}
/* Paco
if (stream.equals("pi")) {
addCode(PI_CODE); return;
}
else
if (stream.equals("e")) {
addCode(E_CODE); return;
}
// if
if (stream.equals("if")) {
skipSpaces();
if (character != '(')
throw new ParserException(PAREN_EXPECTED);
scanAndParse();
if (character != ',')
throw new ParserException(COMMA_EXPECTED);
addCode(IF_CODE);
String savecode = new String(postfix_code);
postfix_code = "";
scanAndParse();
if (character != ',')
throw new ParserException(COMMA_EXPECTED);
addCode(JUMP_CODE);
savecode += (char)(postfix_code.length()+2);
savecode += postfix_code;
postfix_code = "";
scanAndParse();
if (character != ')')
throw new ParserException(PAREN_EXPECTED);
savecode += (char)(postfix_code.length()+1);
savecode += postfix_code;
postfix_code = new String(savecode);
getNextCharacter();
return;
}
*/
// built-in function
for(int i = 0; i<NO_FUNCS; i++) {
if(stream.equals(funcname[i])) {
skipSpaces();
if(character!='(') {
throw new ParserException(PAREN_EXPECTED);
}
scanAndParse();
if(character!=')') {
throw new ParserException(PAREN_EXPECTED);
}
getNextCharacter();
addCode((char) (i+FUNC_OFFSET));
return;
}
}
// extended functions
for(int i = 0; i<NO_EXT_FUNCS; i++) {
if(stream.equals(extfunc[i])) {
skipSpaces();
if(character!='(') {
throw new ParserException(PAREN_EXPECTED);
}
scanAndParse();
if(character!=',') {
throw new ParserException(COMMA_EXPECTED);
}
String savecode = new String(postfix_code);
postfix_code = ""; //$NON-NLS-1$
scanAndParse();
if(character!=')') {
throw new ParserException(PAREN_EXPECTED);
}
getNextCharacter();
savecode += postfix_code;
postfix_code = new String(savecode);
addCode((char) (i+EXT_FUNC_OFFSET));
return;
}
}
// built-in function with no parameters // Added by Paco
for(int i = 0; i<NO_FUNCSNOPARAM; i++) {
if(stream.equals(funcnameNoParam[i])) {
skipSpaces();
if(character!='(') {
throw new ParserException(PAREN_EXPECTED);
}
skipSpaces(); // These two lines is one of the differences for no parameters. Paco
getNextCharacter();
if(character!=')') {
throw new ParserException(PAREN_EXPECTED);
}
getNextCharacter();
addCode((char) (i+FUNCNOPARAM_OFFSET));
return;
}
}
// registered variables
for(int i = 0; i<var_count; i++) {
if(stream.equals(var_name[i])) {
addCode((char) (i+VAR_OFFSET));
return;
}
}
// references
int index = refnames.indexOf(stream);
if(index!=-1) {
addCode((char) (index+REF_OFFSET));
return;
}
position = start;
throw new ParserException(UNKNOWN_IDENTIFIER);
}
/**
* Gets an identifier starting from current parsing pointer.
*
* @return whether the identifier should be negated
* @exception ParserException
*/
private boolean getIdentifier() throws ParserException {
boolean negate = false;
getNextCharacter();
skipSpaces();
if(character=='!') {
getNextCharacter();
skipSpaces();
if(character!='(') {
throw new ParserException(PAREN_EXPECTED);
}
scanAndParse();
if(character!=')') {
throw new ParserException(PAREN_EXPECTED);
}
if(!ISBOOLEAN) {
throw new ParserException(INVALID_OPERAND);
}
addCode(NOT_CODE);
getNextCharacter();
return false;
}
ISBOOLEAN = false;
while((character=='+')||(character=='-')) {
if(character=='-') {
negate = !negate;
}
getNextCharacter();
skipSpaces();
}
start = position;
// if ((character >= '0') && (character <= '9')) changed by W. Christian to handle leanding zeros.
if(((character>='0')&&(character<='9'))||(character=='.')) {
scanNumber();
} else if(character=='(') {
scanAndParse();
getNextCharacter();
} else {
scanNonNumeric();
}
skipSpaces();
return(negate);
}
/**
* Scans arithmetic level 3 (highest). Power arithmetics.
*
* @exception ParserException
*/
/*
private void arithmeticLevel3() throws ParserException {
boolean negate;
if (ISBOOLEAN)
throw new ParserException(INVALID_OPERAND);
negate = getIdentifier();
if (ISBOOLEAN)
throw new ParserException(INVALID_OPERAND);
if (character == '^') arithmeticLevel3();
addCode('^');
if (negate) addCode('_');
}
*/
/**
* Scans arithmetic level 2. Multiplications and divisions.
*
* @exception ParserException
*/
private void arithmeticLevel2() throws ParserException {
boolean negate;
if(ISBOOLEAN) {
throw new ParserException(INVALID_OPERAND);
}
do {
char operator = character;
negate = getIdentifier();
if(ISBOOLEAN) {
throw new ParserException(INVALID_OPERAND);
}
// if (character == '^') arithmeticLevel3(); Paco
if(negate) {
addCode('_');
}
addCode(operator);
} while((character=='*')||(character=='/'));
}
/**
* Scans arithmetic level 1 (lowest).
* Additions and substractions.
*
* @exception ParserException
*/
private void arithmeticLevel1() throws ParserException {
boolean negate;
if(ISBOOLEAN) {
throw new ParserException(INVALID_OPERAND);
}
do {
char operator = character;
negate = getIdentifier();
if(ISBOOLEAN) {
throw new ParserException(INVALID_OPERAND);
}
/* Paco
if (character == '^') {
arithmeticLevel3();
if (negate) addCode('_');
}
else
*/
if((character=='*')||(character=='/')) {
if(negate) {
addCode('_');
}
arithmeticLevel2();
}
addCode(operator);
} while((character=='+')||(character=='-'));
}
/**
* Scans relation level.
*
* @exception ParserException
*/
private void relationLevel() throws ParserException {
char code = (char) 0;
if(INRELATION) {
throw new ParserException(INVALID_OPERATOR);
}
INRELATION = true;
if(ISBOOLEAN) {
throw new ParserException(INVALID_OPERAND);
}
switch(character) {
case '=' :
code = EQUAL;
break;
case '<' :
code = LESS_THAN;
getNextCharacter();
if(character=='>') {
code = NOT_EQUAL;
} else if(character=='=') {
code = LESS_EQUAL;
} else {
position--;
}
break;
case '>' :
code = GREATER_THAN;
getNextCharacter();
if(character=='=') {
code = GREATER_EQUAL;
} else {
position--;
}
break;
}
scanAndParse();
INRELATION = false;
if(ISBOOLEAN) {
throw new ParserException(INVALID_OPERAND);
}
addCode(code);
ISBOOLEAN = true;
}
/**
* Scans boolean level.
*
* @exception ParserException
*/
private void booleanLevel() throws ParserException {
if(!ISBOOLEAN) {
throw new ParserException(INVALID_OPERAND);
}
char operator = character;
scanAndParse();
if(!ISBOOLEAN) {
throw new ParserException(INVALID_OPERAND);
}
switch(operator) {
case '&' :
addCode(AND_CODE);
break;
case '|' :
addCode(OR_CODE);
break;
}
}
/**
* Main method of scanning and parsing process.
*
* @exception ParserException
*/
private void scanAndParse() throws ParserException {
boolean negate;
negate = getIdentifier();
// if ((character != '^') && (negate)) addCode('_'); Paco
if(negate) {
addCode('_');
}
do {
switch(character) {
case '+' :
case '-' :
arithmeticLevel1();
break;
case '*' :
case '/' :
arithmeticLevel2();
break;
/* Paco
case '^':
arithmeticLevel3();
if (negate) addCode('_');
break;
*/
case ',' :
case ')' :
return;
case '=' :
case '<' :
case '>' :
relationLevel();
break;
case '&' :
case '|' :
booleanLevel();
break;
default :
throw new ParserException(OPERATOR_EXPECTED);
}
} while(true);
}
/**
* Parses subfunction.
*/
private void parseSubFunction() {
position = 0;
postfix_code = ""; //$NON-NLS-1$
INRELATION = false;
ISBOOLEAN = false;
try {
scanAndParse();
} catch(ParserException e) {
error = e.getErrorCode();
if((error==SYNTAX_ERROR)&&(postfix_code=="")) { //$NON-NLS-1$
error = EXPRESSION_EXPECTED;
}
}
if((error==NO_ERROR)&&(position!=function.length())) {
error = PAREN_NOT_MATCH;
}
}
/**
* Built-in one parameter function call.
*
* @return the function result
* @param function the function index
* @param parameter the parameter to the function
*/
/*
{ "Math.abs", "Math.acos" , "Math.asin" , "Math.atan", "Math.ceil" ,
"Math.cos", "Math.exp" , "Math.floor", "Math.log" , "Math.rint",
"Math.round", "Math.sin" , "Math.sqrt" , "Math.tan" , "Math.toDegrees",
"Math.toRadians" };
*/
// Changed completely by Paco. Use of if (radian) suppressed
private double builtInFunction(int function, double parameter) {
switch(function) {
case 0 :
return Math.abs(parameter);
case 1 :
return Math.acos(parameter);
case 2 :
return Math.asin(parameter);
case 3 :
return Math.atan(parameter);
case 4 :
return Math.ceil(parameter);
case 5 :
return Math.cos(parameter);
case 6 :
return Math.exp(parameter);
case 7 :
return Math.floor(parameter);
case 8 :
return Math.log(parameter);
case 9 :
return Math.rint(parameter);
case 10 :
return Math.round(parameter);
case 11 :
return Math.sin(parameter);
case 12 :
return Math.sqrt(parameter);
case 13 :
return Math.tan(parameter);
case 14 :
return Math.toDegrees(parameter);
case 15 :
return Math.toRadians(parameter);
default :
error = CODE_DAMAGED;
return Double.NaN;
}
}
private double builtInFunctionNoParam(int function) { // Paco
switch(function) {
case 0 :
return Math.random();
default :
error = CODE_DAMAGED;
return Double.NaN;
}
}
/**
* Built-in two parameters extended function call.
*
* @return the function result
* @param function the function index
* @param param1 the first parameter to the function
* @param param2 the second parameter to the function
*/
// { "Math.atan2", "Math.IEEEremainder", "Math.max", "Math.min", "Math.pow"};
private double builtInExtFunction(int function, double param1, double param2) {
switch(function) {
case 0 :
return Math.atan2(param1, param2);
case 1 :
return Math.IEEEremainder(param1, param2);
case 2 :
return Math.max(param1, param2);
case 3 :
return Math.min(param1, param2);
case 4 :
return Math.pow(param1, param2);
default :
error = CODE_DAMAGED;
return Double.NaN;
}
}
/**
* Evaluates subfunction.
*
* @return the result of the subfunction
*/
private double evaluateSubFunction() {
// double stack[]; moved by W. Christian
int stack_pointer = -1;
int code_pointer = 0;
int destination;
char code;
// stack = new double[STACK_SIZE]; moved by W. Christian
int codeLength = postfix_code.length(); // added bt W. Christian to check the length.
while(true) {
try {
if(code_pointer==codeLength) {
return stack[0]; // added by W. Christian. Do not use doing an Exception!
}
code = postfix_code.charAt(code_pointer++);
} catch(StringIndexOutOfBoundsException e) {
return stack[0];
}
try {
switch(code) {
case '+' :
stack[stack_pointer-1] += stack[stack_pointer];
stack_pointer--;
break;
case '-' :
stack[stack_pointer-1] -= stack[stack_pointer];
stack_pointer--;
break;
case '*' :
stack[stack_pointer-1] *= stack[stack_pointer];
stack_pointer--;
break;
case '/' :
if(stack[stack_pointer]!=0) {
stack[stack_pointer-1] /= stack[stack_pointer];
} else {
stack[stack_pointer-1] /= 1.0e-128; // added by W.Christian to trap for divide by zero.
}
stack_pointer--;
break;
/*
case '^': stack[stack_pointer-1] = Math.pow(stack[stack_pointer-1],
stack[stack_pointer]);
stack_pointer--; break;
*/
case '_' :
stack[stack_pointer] = -stack[stack_pointer];
break;
case JUMP_CODE :
destination = code_pointer+postfix_code.charAt(code_pointer++);
while(code_pointer<destination) {
if(postfix_code.charAt(code_pointer++)==NUMERIC) {
numberindex++;
}
}
break;
case LESS_THAN :
stack_pointer--;
stack[stack_pointer] = (stack[stack_pointer]<stack[stack_pointer+1]) ? 1.0 : 0.0;
break;
case GREATER_THAN :
stack_pointer--;
stack[stack_pointer] = (stack[stack_pointer]>stack[stack_pointer+1]) ? 1.0 : 0.0;
break;
case LESS_EQUAL :
stack_pointer--;
stack[stack_pointer] = (stack[stack_pointer]<=stack[stack_pointer+1]) ? 1.0 : 0.0;
break;
case GREATER_EQUAL :
stack_pointer--;
stack[stack_pointer] = (stack[stack_pointer]>=stack[stack_pointer+1]) ? 1.0 : 0.0;
break;
case EQUAL :
stack_pointer--;
stack[stack_pointer] = (stack[stack_pointer]==stack[stack_pointer+1]) ? 1.0 : 0.0;
break;
case NOT_EQUAL :
stack_pointer--;
stack[stack_pointer] = (stack[stack_pointer]!=stack[stack_pointer+1]) ? 1.0 : 0.0;
break;
case IF_CODE :
if(stack[stack_pointer--]==0.0) {
destination = code_pointer+postfix_code.charAt(code_pointer++);
while(code_pointer<destination) {
if(postfix_code.charAt(code_pointer++)==NUMERIC) {
numberindex++;
}
}
} else {
code_pointer++;
}
break;
case ENDIF :
break; // same as NOP
case AND_CODE :
stack_pointer--;
if((stack[stack_pointer]!=0.0)&&(stack[stack_pointer+1]!=0.0)) {
stack[stack_pointer] = 1.0;
} else {
stack[stack_pointer] = 0.0;
}
break;
case OR_CODE :
stack_pointer--;
if((stack[stack_pointer]!=0.0)||(stack[stack_pointer+1]!=0.0)) {
stack[stack_pointer] = 1.0;
} else {
stack[stack_pointer] = 0.0;
}
break;
case NOT_CODE :
stack[stack_pointer] = (stack[stack_pointer]==0.0) ? 1.0 : 0.0;
break;
case NUMERIC :
stack[++stack_pointer] = number[numberindex++];
break;
// case PI_CODE : stack[++stack_pointer] = Math.PI; break; Paco
// case E_CODE : stack[++stack_pointer] = Math.E; break; Paco
default :
if(code>=REF_OFFSET) {
stack[++stack_pointer] = refvalue[code-REF_OFFSET];
} else if(code>=VAR_OFFSET) {
stack[++stack_pointer] = var_value[code-VAR_OFFSET];
} else if(code>=FUNCNOPARAM_OFFSET) { // Paco
stack[++stack_pointer] = // This is the other difference for no parameters ! Paco
builtInFunctionNoParam(code-FUNCNOPARAM_OFFSET);
} else if(code>=EXT_FUNC_OFFSET) {
stack[stack_pointer-1] = builtInExtFunction(code-EXT_FUNC_OFFSET, stack[stack_pointer-1], stack[stack_pointer]);
stack_pointer--;
} else if(code>=FUNC_OFFSET) {
stack[stack_pointer] = builtInFunction(code-FUNC_OFFSET, stack[stack_pointer]);
} else if(code>=CONST_OFFSET) { // Added by Paco for constants
stack[++stack_pointer] = constvalue[code-CONST_OFFSET];
} else {
error = CODE_DAMAGED;
return Double.NaN;
}
}
} catch(ArrayIndexOutOfBoundsException oe) {
error = STACK_OVERFLOW;
return Double.NaN;
} catch(NullPointerException ne) {
error = CODE_DAMAGED;
return Double.NaN;
}
}
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/