/*
* 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.numerics;
/*----------------------------------------------------------------------------------------*
* 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 *
* *
* 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(1,"x"); // lets the variable be 'x'
* parser.define("sin(x)/x"); // defines function: sin(x)/x
* parser.parse(); // parses the function
*
* // calculates: sin(x)/x with x = -5.0 .. +5.0 in 20 steps
* // and prints the result to standard output.
*
* float result;
* for (i=-10; i <= 10; i++) {
* parser.setVariable(1,(float)i/2.0f);
* result = parser.evaluate();
* System.out.println(result);
* }
* </pre>
*/
public final class SuryonoParser extends MathExpParser {
// 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 boolean radian; // radian unit flag
private int numberindex; // pointer to numbers/constants bank
private double[] refvalue = null; // value of references
// private static final int MAX_NUM = 100; // max numeric constants // changed by W. Christian
private static final int MAX_NUM = 200; // max numeric constants
// private static final int NO_FUNCS = 24; // no. of built-in functions
// changed from 24 function by W. Christian to add step and random function
private static final int NO_FUNCS = 26; // no. of built-in functions
private static final int NO_EXT_FUNCS = 4; // 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
// constants
private static final double DEGTORAD = Math.PI/180;
private static final double LOG10 = Math.log(10);
// references - version 3.0
private Hashtable<String, String> references = null;
private Vector<String> refnames = null;
// error codes
/**
* No error.
*
* Moved to superclass by W. Christian
*/
// public static final int NO_ERROR = 0;
/**
* Syntax error.
*
* Moved to superclass by W. Christian
*/
// 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 VAR_OFFSET = 2000;
private static final int REF_OFFSET = 3000;
private static final char PI_CODE = (char) 253;
private static final char E_CODE = (char) 254;
private static final char NUMERIC = (char) 255;
// 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
// built in functions
private String funcname[] = {
"sin", "cos", "tan", "ln", "log", "abs", "int", "frac", "asin", "acos", "atan", "sinh", "cosh", "tanh", "asinh", "acosh", "atanh", "ceil", "floor", "round", "exp", "sqr", "sqrt", "sign", "step","random" //$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$ //$NON-NLS-17$ //$NON-NLS-18$ //$NON-NLS-19$ //$NON-NLS-20$ //$NON-NLS-21$ //$NON-NLS-22$ //$NON-NLS-23$ //$NON-NLS-24$ //$NON-NLS-25$ //$NON-NLS-26$
};
// extended functions
private String extfunc[] = {"min", "max", "mod", "atan2"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
// set when evaluate() method converts NaN to zero--added by D Brown 15 Sep 2010
private boolean isNaN;
/**
* The constructor of <code>Parser</code>.
*
* Added by W. Christian to make it easy to construct a parser for with one variable.
*
* @param f function
* @param v variable
*
* @throws ParserException
*/
public SuryonoParser(String f, String v) throws ParserException {
this(1);
defineVariable(1, v); // lets the variable be v
define(f); // defines function: f
parse(); // parses the function
if(getErrorCode()!=NO_ERROR) {
String msg = "Error in function string: "+f; //$NON-NLS-1$
msg = msg+'\n'+"Error: "+getErrorString(); //$NON-NLS-1$
msg = msg+'\n'+"Position: "+getErrorPosition(); //$NON-NLS-1$
throw new ParserException(msg);
}
}
/**
* The constructor of <code>Parser</code>.
*
* Added by W. Christian to make it easy to construct a parser for with two variables.
*
* @param f the function
* @param v1 variable 1
* @param v2 variable 2
* @throws ParserException
*/
public SuryonoParser(String f, String v1, String v2) throws ParserException {
this(2);
defineVariable(1, v1);
defineVariable(2, v2);
define(f); // defines function: f
parse(); // parses the function
if(getErrorCode()!=NO_ERROR) {
String msg = "Error in function string: "+f; //$NON-NLS-1$
msg = msg+'\n'+"Error: "+getErrorString(); //$NON-NLS-1$
msg = msg+'\n'+"Position: "+getErrorPosition(); //$NON-NLS-1$
throw new ParserException(msg);
}
}
/**
* The constructor of <code>Parser</code>.
*
* Added by W. Christian to make it easy to construct a parser for with multiple variables.
*
* @param f the function
* @param v variables
* @throws ParserException
*/
public SuryonoParser(String f, String[] v) throws ParserException {
this(v.length);
for(int i = 0; i<v.length; i++) {
defineVariable(i+1, v[i]);
}
define(f); // defines function: f
parse(); // parses the function
if(getErrorCode()!=NO_ERROR) {
String msg = "Error in function string: "+f; //$NON-NLS-1$
msg = msg+'\n'+"Error: "+getErrorString(); //$NON-NLS-1$
msg = msg+'\n'+"Position: "+getErrorPosition(); //$NON-NLS-1$
throw new ParserException(msg);
}
}
/**
* The constructor of <code>Parser</code>.
*
* @param variablecount the number of variables
*/
public SuryonoParser(int variablecount) {
var_count = variablecount;
references = new Hashtable<String, String>();
refnames = new Vector<String>();
radian = true;
// arrays are much faster than vectors (IMHO)
var_name = new String[variablecount];
var_value = new double[variablecount];
number = new double[MAX_NUM];
}
/**
* Sets the funtion to zero.
*/
public void setToZero() {
try {
setFunction("0"); //$NON-NLS-1$
} catch(ParserException ex) {}
}
boolean appendVariables = false;
/**
* Sets the angle unit to radian. Default upon construction.
*/
public void useRadian() {
radian = true;
}
/**
* Sets the angle unit to degree.
*/
public void useDegree() {
radian = false;
}
/**
* Remove any escape sequences such as \, and replace with the character.
* by W. Christian.
*
* @param function the function
*
* @return the new function
*/
private String removeEscapeCharacter(String str) {
if((str==null)||(str.length()<1)) {
return str;
}
StringBuffer sb = new StringBuffer(str.length());
for(int i = 0; i<str.length(); i++) {
if(str.charAt(i)!='\\') {
sb.append(str.charAt(i));
}
}
return sb.toString();
}
/**
* Sets the variable names.
* Nothing happens if variable index > number of variables.
*
* @param index the variable index (one based)
* @param name the variable name
*/
public void defineVariable(int index, String name) {
if(index>var_count) {
return;
}
var_name[index-1] = name;
}
/**
* Sets the variable value.
* The variable is accessed by index.
* Nothing happens if variable index > number of variables.
*
* @param index the variable index (one based)
* @param value the variable value
*/
public void setVariable(int index, double value) {
if(index>var_count) {
return;
}
var_value[index-1] = value;
}
/**
* Sets the variable value.
* The variable is accessed by name.
* Nothing happens if variable could not be found.
*
* @param name the variable name
* @param value the variable value
*/
public void setVariable(String name, double value) {
for(int i = 0; i<var_count; i++) {
if(var_name[i].equals(name)) {
var_value[i] = value;
break;
}
}
}
/**
* Defines a function. Current postfix code becomes invalid.
*
* @param definition the function definition
*/
public void define(String definition) {
function = definition;
function.toLowerCase();
function = removeEscapeCharacter(function); // added by W. Christian
valid = false;
}
/**
* Parses defined function.
*/
public void parse(String function) throws ParserException {
define(function);
parse();
if(getErrorCode()!=NO_ERROR) {
String msg = "Error in function string: "+function; //$NON-NLS-1$
msg = msg+'\n'+"Error: "+getErrorString(); //$NON-NLS-1$
msg = msg+'\n'+"Position: "+getErrorPosition(); //$NON-NLS-1$
throw new ParserException(msg);
}
}
/**
* Parses a function looking for unknown variables. Unknown tokens are used to create the variable list in the order
* that they are found.
*/
public String[] parseUnknown(String function) throws ParserException {
var_name = new String[0];
var_value = new double[0];
var_count = 0;
appendVariables = true;
define(function);
parse();
if(getErrorCode()!=NO_ERROR) {
String msg = "Error in function string: "+function; //$NON-NLS-1$
msg = msg+'\n'+"Error: "+getErrorString(); //$NON-NLS-1$
msg = msg+'\n'+"Position: "+getErrorPosition(); //$NON-NLS-1$
appendVariables = false;
throw new ParserException(msg);
}
appendVariables = false;
return var_name;
}
public String[] getVariableNames() {
return var_name;
}
/**
* Returns all built-in and extended function names.
* Added by D. Brown 06 Jul 2008
*
* @return array of function names
*/
public String[] getFunctionNames() {
int len = funcname.length;
String[] names = new String[len+extfunc.length];
System.arraycopy(funcname, 0, names, 0, len);
System.arraycopy(extfunc, 0, names, len, extfunc.length);
return names;
}
/**
* 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);
}
public double evaluate(double x, double y)
// added by Wolfgang Christian to make it easier to call parser.
{
if(var_count!=2) {
return 0;
}
var_value[0] = x;
var_value[1] = y;
return evaluate();
}
public double evaluate(double x, double y, double z)
// added by Wolfgang Christian to make it easier to call parser.
{
if(var_count!=3) {
return 0;
}
var_value[0] = x;
var_value[1] = y;
var_value[2] = z;
return evaluate();
}
public double evaluate(double x)
// added by Wolfgang Christian to make it easier to call parser.
{
if(var_count!=1) {
return 0;
}
var_value[0] = x;
return evaluate();
}
public double evaluate(double[] v)
// added by Wolfgang Christian to make it easier to call parser with an array.
{
if(var_value.length!=v.length) {
System.out.println("JEParser Error: incorrect number of variables."); //$NON-NLS-1$
return 0;
}
System.arraycopy(v, 0, var_value, 0, v.length);
return evaluate();
}
/**
* 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 D Brown to flag NaN results
isNaN = Double.isNaN(result);
// added by W. Christian to trap for NaN
if(isNaN) {
result = 0.0;
}
return result;
}
/**
* Determines if last evaluation resulted in NaN. Added by D Brown 15 Sep 2010.
*
* @return true if result was converted from NaN to zero
*/
public boolean evaluatedToNaN() {
return isNaN;
}
/**
* 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;
}
/**
* Gets function string of last operation.
*
* Added by W. Christian to implement the MathExpParser interface.
*
* @return the function string
*/
public String getFunction() {
return function;
}
/**
* Parse the function string using the existing variables.
*
* Added by W. Christian to implement the MathExpParser interface.
*/
public void setFunction(String funcStr) throws ParserException {
function = funcStr;
define(function);
parse();
if(error!=NO_ERROR) {
String msg = "Error in function string: "+funcStr; //$NON-NLS-1$
msg = msg+'\n'+"Error: "+toErrorString(error); //$NON-NLS-1$
msg = msg+'\n'+"Position: "+getErrorPosition(); //$NON-NLS-1$
throw new ParserException(msg);
}
}
/**
* Parse the function string using new variable names.
*
* Added by W. Christian to implement the MathExpParser interface.
*/
public void setFunction(String funcStr, String[] vars) throws ParserException {
function = funcStr;
if(vars.length!=var_count) {
var_count = vars.length;
references.clear();
refnames.clear();
var_name = new String[var_count];
var_value = new double[var_count];
}
for(int i = 0; i<vars.length; i++) {
defineVariable(i+1, vars[i]);
}
define(function);
parse();
if(error!=NO_ERROR) {
String msg = "Error in function string: "+funcStr; //$NON-NLS-1$
msg = msg+'\n'+"Error: "+toErrorString(error); //$NON-NLS-1$
msg = msg+'\n'+"Position: "+getErrorPosition(); //$NON-NLS-1$
throw new ParserException(msg);
}
}
/*----------------------------------------------------------------------------------------*
* 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') {
if((character=='e')||(character=='E')) { // changed by Doug Brown May 2007
numstr += character;
getNextCharacter();
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=='^')||(character==')')||(character==',')||(character=='<')||(character=='>')||(character=='=')||(character=='&')||(character=='|')) {
throw new ParserException(SYNTAX_ERROR);
}
do {
stream += character;
getNextCharacter();
} while(!((character==' ')||(character=='+')||(character=='-')||(character=='*')||(character=='/')||(character=='^')||(character=='(')||(character==')')||(character==',')||(character=='<')||(character=='>')||(character=='=')||(character=='&')||(character=='|')));
if(stream.equals("pi")) { //$NON-NLS-1$
addCode(PI_CODE);
return;
} else if(stream.equals("e")) { //$NON-NLS-1$
addCode(E_CODE);
return;
}
// if
if(stream.equals("if")) { //$NON-NLS-1$
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 = ""; //$NON-NLS-1$
scanAndParse();
if(character!=',') {
throw new ParserException(COMMA_EXPECTED);
}
addCode(JUMP_CODE);
savecode += (char) (postfix_code.length()+2);
savecode += postfix_code;
postfix_code = ""; //$NON-NLS-1$
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;
}
}
// 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;
}
// appendVariables option added by W. Christian
if(appendVariables&&append(stream)) {
return;
}
position = start;
throw new ParserException(UNKNOWN_IDENTIFIER);
}
// W. Christian addition to automatically add variables
private boolean append(String stream) {
String[] var_name2 = new String[var_count+1];
double[] var_value2 = new double[var_count+1];
System.arraycopy(var_name, 0, var_name2, 0, var_count);
System.arraycopy(var_value, 0, var_value2, 0, var_count);
var_name2[var_count] = stream;
var_name = var_name2;
var_value = var_value2;
var_count++;
// System.out.println("appended=" + stream);
for(int i = 0; i<var_count; i++) {
if(stream.equals(var_name[i])) {
addCode((char) (i+VAR_OFFSET));
return true;
}
}
return false;
}
/**
* 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 be 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();
}
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);
}
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('_');
}
do {
switch(character) {
case '+' :
case '-' :
arithmeticLevel1();
break;
case '*' :
case '/' :
arithmeticLevel2();
break;
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
*/
private double builtInFunction(int function, double parameter) {
switch(function) {
case 0 :
if(radian) {
return Math.sin(parameter);
}
return Math.sin(parameter*DEGTORAD);
case 1 :
if(radian) {
return Math.cos(parameter);
}
return Math.cos(parameter*DEGTORAD);
case 2 :
if(radian) {
return Math.tan(parameter);
}
return Math.tan(parameter*DEGTORAD);
case 3 :
return Math.log(parameter);
case 4 :
return Math.log(parameter)/LOG10;
case 5 :
return Math.abs(parameter);
case 6 :
return Math.rint(parameter);
case 7 :
return parameter-Math.rint(parameter);
case 8 :
if(radian) {
return Math.asin(parameter);
}
return Math.asin(parameter)/DEGTORAD;
case 9 :
if(radian) {
return Math.acos(parameter);
}
return Math.acos(parameter)/DEGTORAD;
case 10 :
if(radian) {
return Math.atan(parameter);
}
return Math.atan(parameter)/DEGTORAD;
case 11 :
return(Math.exp(parameter)-Math.exp(-parameter))/2;
case 12 :
return(Math.exp(parameter)+Math.exp(-parameter))/2;
case 13 :
double a = Math.exp(parameter);
double b = Math.exp(-parameter);
return(a-b)/(a+b);
case 14 :
return Math.log(parameter+Math.sqrt(parameter*parameter+1));
case 15 :
return Math.log(parameter+Math.sqrt(parameter*parameter-1));
case 16 :
return Math.log((1+parameter)/(1-parameter))/2;
case 17 :
return Math.ceil(parameter);
case 18 :
return Math.floor(parameter);
case 19 :
return Math.round(parameter);
case 20 :
return Math.exp(parameter);
case 21 :
return parameter*parameter;
case 22 :
return Math.sqrt(parameter);
case 23 :
if(parameter==0.0d) {
return 0;
} else if(parameter>0.0d) {
return 1;
} else {
return -1;
}
case 24 :
if(parameter<0) {
return 0;
}
return 1; // added by W. Christian for step function
case 25 :
return parameter*Math.random(); // added by W. Christian for random function
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
*/
private double builtInExtFunction(int function, double param1, double param2) {
switch(function) {
case 0 :
return Math.min(param1, param2);
case 1 :
return Math.max(param1, param2);
case 2 :
return Math.IEEEremainder(param1, param2);
case 3 :
return Math.atan2(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;
case E_CODE :
stack[++stack_pointer] = Math.E;
break;
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>=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 {
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
*/