/* * 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 */