import javax.swing.JOptionPane; // allows GUI import java.util.*; // ExprEvaluator class //33 // Revised 11/24/05, 4/8/06, 3/8/08, 3/10/08, 3/15/08, 3/20/08, 5/12/08, // 5/19/08, 6/7/08, 6/10/08, 6/18/08, 6/23/08, 6/15/13 // 7/29/18 (Corrected subtype of random to 20) // added functions roundTo, toRadians, toDegrees. /** * Beta 6.0 *

* The ExprEvaluator class is designed to be used in applications and applets * when it is desired to allow the user to type in a mathematical expression * and have the program calculate its values.

* *

A two step process is used. Given a mathematical expression, a "build" * method creates a binary expression tree representing the expression. It may * be useful to think of the expression tree as the compiled version of the * expression. Then * an evaluate method is used to evaluate the binary expression tree. For * example, the following code could be used in a simple situation to evaluate * expression using the variables t, x, and y: * *
    ExprEvaluator evaluator = new ExprEvaluator( ); *
    double t = ... *
    double x = ... *
    double y = ... *
    String expression = ... *
    try { *
        evaluator.buildFromInfix(expression); *
        double value = evaluator.evaluate(t, x, y); *
}    catch (Exception e) { *
        ... *
} *
*

All calculations are carried out with double precision real numbers.

* *

If you are interested in handling multiple expressions, buildFromInfix * returns the expression tree (the "compiled" version of the expression). * This allows handling multiple expressions. See the "Evaluating multiple * infix expressions" section below. * *

The class provides "build" methods that can handle prefix, infix and * postfix expressions. Those expressions may contain the following:

* * *

There are separate build methods for prefix, infix, and postfix expressions. * (If you are not familiar with these terms, we normally write mathematical * expressions in infix. Postfix is sometimes called reverse Polish notation and * is used by HP calculators and is convenient for computer work. You don't * need to know how to use prefix and postfix to use this class. You will probably * only want to use infix.) The following table show the equivalent prefix, infix, * and postfix for some expressions emphasizing notation for functions and "=" * signs.

* * * * * * * * * * * * * * * * * * * * * * *
PrefixInfixPostFix
/ + x y - t 7(x+y)/(t-7)x y + t 7 - /
= x 4x = 4x 4 =
= a = b = c * x ya = b = c = x * ya b c x y * = = =
sin xsin(x)x sin
abs - x yabs(x - y)x y - abs
max x ymax(x, y)x y max
*

* The "=" operator is optional and can be used in multiple assignments. For * example: Both x + y and z = x + y (or + x y and * = z + x y in prefix and x y + and z x y + = in * postfix) return the same value. They differ in second expression also sets * the value of z. The expression a = b = c = 3*x (or * = a = b = c * 3 x in prefix and a b c 3 x * = = = in * postfix) sets the values of a, * b and c in addition to returning the value of 3*x * (* 3 x in prefix). *

* Order of evaluation for infix expressions (the same as standard algebra and * Visual Basic) * * * * * * * * * * * * * * * * *
( ) Parenthesized expressions (highest priority)
functions 
^exponentials (right associative: 2^3^4 = 2^(3^4) )
leading + - (as in -5)
* / 
binary + - (as in 4 - 3)
=assignment (right associative: value on right is assigned to * variable on left)) (lowest priority)
* * *

Processing one expression at a time

*

* To simplify using ExprEvaluator, it "remembers" the last expression tree * created by a build method. That expression tree is the default for * the evaluate methods. Depending on the variables used, there are three * general ways of evaluating expressions after the tree is built: *
Only the "common" variables t, x, and y are used: *
Use double evaluate(double t, double x, double y). *
Example: evaluator.evaluate(10, 20, 30);. *
See the above coding example or * TrivialExample.java. * *
Only a few variables are used but they include "uncommon" variables: *
Use setValueOf(char var, double value) as needed to set values * of variables * and then use evaluate( ). For example, if the expression uses variables * a and b: *
evaluator.setValueOf('a', 10); *
evaluator.setValueOf('b', 20); *
try { *
    result = evaluator.evaluate( ); *
} catch (Expression e) { *
... *
} *
See TrivialExample3.java. * *
Many variables with values stored in an array: *
Use evaluate(double[] values). *
Example: *
double [] variables = new double[26]; *
... *
result = root.evaluate(variables);     *
See TrivialExample2.java. * *

Notes:

*

* These techniques are not mutually exclusive. Internally, the same * array is used to store variable values and is independent of the * evaluate method is used. For example: Consider the sequence *
evaluator.evaluate(10, 20, 30); *
evaluator.evaluate( ); *
The second evaluate would use the values of t, x, and y resulting from * the first evaluation. Typically, they would be t = 10, x = 20, and y = 30 * but it is possible that those values were changed during the course of the * first call to evaluate. *

* As another example, suppose that the expression is function that must be * evaluated for many (x, y) pairs but depends on "constants" a and b. We could * proceed as follows: *
evaluator.setValueOf('a', 20); *
evaluator.setValueOf('b', 30); *
... inside a loop ... *
xval = ...; *
yval = ...; *
value = evaluator.evaluate(0, xVal, yVal);    // t is not used * *

Evaluating multiple infix expressions

* *

ExprEvaluator has special methods that are useful when processing multiple * infix expressions. *

* Evaluating functions for 2-D graph or table typically has one of the * following levels of difficulty: * * * * * * * * * * * * * * * * * * * * * *
Type of problemComment
Evaluate or plot f(x)The buildFromInfix and zero parameter evaluate method are completely * capable of handling this situation. * See TableExample1.java * Alternatively, one may choose to use the buildFromInfix and evaluate(ExprTree) * methods as suggested in the next item.
Evaluate or plot multiple functions of xAssuming exprStr is string with the expression and valX is a double, use *
  ExprTree expr = buildFromInfix(exprStr); *
  setX(valX); *
  double val = evaluate(expr);. *
See TableExample2.java
Use parametric functions for x and y.
* (There may be multiple sets of expressions.)
Use the technique from the previous example except use the buildFromInfix * to build a tree for each x and y expression, set the value of the parameter, * and then use evaluate for each of the expression trees to get the x and y values. * See TableExample3.java
Use polar coordinates.
* (There may be multiple sets of expressions.)
ExprEvaluator provides a special method, convertToRect(r, theta), to * convert polar * coordinates to rectangular coordinates that may be needed to graph polar * functions. If there is only 1 expression to be evaluated, then the * buildFromInfix, setValueOf, and the * zero parameter evaluate methods along with the convertToRect * method are completely capable of handling this situation. * However, if there is more than * one expression to be evaluated, you should use buildFromInfix for each expression, * set the values of the parameter(s) and then use evaluate(ExprTree) * methods for each expression being evaluated. * See TableExample4.java
*

While these techniques are designed for producing 2-D graphs, they may also * be useful in other situations where it is desired to evaluate multiple * functions. In fact, all the above examples just produce tables that could * be used for plotting but there could be other uses as well. *

* The general code format when using multiple infix functions: * *
    ExprEvaluator evaluator = new ExprEvaluator(); *
    double x; *
    double firstX = ...; *
    double lastX = ...; *
    double incX = ...; *
    double y1, y2; *
    ExprTree tree1, tree2; *
    String expr1 = ...; *
    String expr2 = ...; *
    try { *
        tree1 = evaluator.buildFromInfix(expr1); *
        tree2 = evaluator.buildFromInfix(expr2); *
        for (x = firstX; x < lastX; x += incX) { *
            evaluator.setX(x); *
            y1 = evaluator.evaluate(tree1); *
            y2 = evaluator.evaluate(tree2); *
            ... *
        } *
    } catch (Exception e) { *
        ... *
    } *
* *

Functions

*

The ExprEvaluator can evaluate 34 functions, including 32 functions * defined in Java's Math class, one unique function inspired by Excel * (roundTo(val, num)) and * a user defined function. You can search Java documentation for definitions * of functions that you are unfamiliar with. * The functions that can be used include the following: *

* *

"user": The user defined function

* *

The buildFromInfix method allow the user to define * a function with 0 to 10 parameters. Because of notational difficulties, * the buildFromPrefix and buildFromPostfix methods only allow defining 1 * parameter functions. The formats for the string defining * the function are *
prefix: define = user parameter prefixExpression *
infix: define user(parameter list) = infixExpression *
postfix: define parameter user postfixExpression = * *

For example using infix: *

 * try {
 *     evaluator.buildFromInfix("define user(a, b, x) = x * max(a, b)");
 *     evaluator.setX(10);
 *     evaluator.setY(20);
 *     evaluator.setValueOf('z', 30);
 *     evaluator.buildFromInfix("100000 + user(x, y, z)");
 *     double value = evaluator.evaluate();
 * } catch (...) {
 *     ...
 * }
 * 
* The following examples compare defining a function in prefix, infix, * and postfix: *
prefix: define = user t * 10 t *
infix: define user(t) = 10 * t *
postfix: define t user 10 t * = * *

* When a build method is used to define a function, the resulting tree is * null and it returns null. The parameters follow the same notational rule * as variables: one lower case letter. * Once defined, the number of parameters is fixed (until user is redefined). * The expression used in defining a function can use parameters, * constants and any other variables and functions. * *

When the user function is defined using buildFromPrefix or * buildFromPostfix, it must have exactly 1 parameter. However, * once defined, the user function can be used in prefix, infix and postfix * expressions no matter which of the build functions was used to define it. * This is true independently of the number of parameters the function has. * (See TrivialExample4.java.) * *

roundTo(x, num)

* *

The roundTo(x, num) function was inspired by the fact that Java often * prints doubles with many decimal places. For example, we might see * something like 1.5999999999 or 1.600000003 as approximations for 1.6 after * a series of calculations. The function was modeled after the * ROUND(number, num_digits) function in Excel. 'num' is the number of decimal * places to which x is rounded. If num < 0, then the number is rounded to the * left of the decimal point. For example, roundTo(12345.6,-3) = 12000.

*

* The roundTo function is available both when running ExprEvaluator and as * a public static function in the EasyFormat class. * *

Converting between different "..fixes"

* *

The toPrefix(), toInfix(U) and toPostfix() methods allow converting from * one "..fix" to another after using any of the build... methods. toInfix() provides * fully parenthesized output. (I.e. typically there are many unneeded pairs * of parenthesis. For Example. x+y*z will be output as (x+(y*z)) .)

* *

Calculation errors

*

Illegal calculations such as 1/0, log(-1) or sqrt(-1) will typically * result in infinity, -infinity, or NaN as is normal in Java.

* *

Revisions

*

Beta 3: Modified for more efficient evaluations by adding fields to the * binary nodes to avoid having to repeatedly check for values, types, and * so on. Also ignores leading (unary) + signs. *

*

Beta 4.0: Modified to allow variables a, b, ..., z. *

Beta 4.1: Added the public static function roundTo *
Allow = sign as a right associative operator. *
Added the function roundTo(x, numDecPlaces) *
Changed the name of the class for ExpressionTree to ExprEvaluator *
Implemented buildFromPrefix. *

Beta 4.2: Added help messages for infix function and used * EasyFormat.roundTo. *

Beta 4.3: Added methods that were previously in the subclass * GraphEvaluator2D. *

Beta 4.4: Added many new functions. *

Beta 4.5: Added user defined function (and report error method), * added help for prefix and postfix. *

Beta 5.0: Implemented the toInfix() method and added TrivialExample5 * to test it. *

Beta 6.0: Added roundTo(x, num) as a public static method so it can * be used in ap or applet similar to the way Math.sqrt is used. * It is actually the same as EasyFormat.roundTo(x, num). *

*

Requires:

* EasyFormat.java *
DoubleScanner.java *
ArrayPureStack.java *
BinaryNode.java *
ExprTree.java *
PureStack.java * @author James Brink, brinkje@plu.edu * @version Beta 5.0: 6/17/2013 *

* In the documentation for individual Fields, Constructors, and Methods, "##" * is used to signify the item is intended primarily for use with aps. Some * of the other items are public because of various requirements or possible * extensions or debugging. */ public class ExprEvaluator { private final static int MAX_PARAMS = 10; /** ## The number PI = 3.14159... */ public static double PI = Math.PI; /** ## The number E = 2.718... */ protected static double E = Math.E; /** A String holding the 26 variables "a" to "z" allowed in Expression tree and * 10 parameters allowed in the user function. The parameters follow the * variables. Parameter names are either unprintable or have strange values. * they are (char)123, (char)124, ..., (char) 132. The parameters * are stored in reverse order so the parameter represented by (char)123 is always * the last parameter. */ public static final String variables = "abcdefghijklmnopqrstuvwxyz" + (char)123 + (char)124 + (char)125 + (char)126 + (char)127 + (char)128 + (char)129 + (char)130 + (char)131 + (char)132; /** The allowed delimiters (operators). */ public String DELIMITERS = "+-*/^()=,"; // The operators. Note: > is used only internally. The constant is not // used but included here for reference // private String OPERATORS = DELIMITERS + "<"; /** The root of the current expression tree */ protected BinaryNode root = null; /** Reference to top (root) node in the tree for the user function */ protected BinaryNode definedFunction = null; /** ## Values of the variables (and parameters). The first 26 values are * variables, the last 10 values are parameters for the function (in * reverse order. For example for user(x, y, z), values[26] is the * value of paramter z, values[27] is the value of y, and values[28] * is the value of x. values[0] is the value of variable 'a' */ protected double [] values = new double[36]; private String inputStr = ""; protected String userFunction = ""; private boolean debug = false; private boolean debugStacks = false; static final BinaryNode zero = new BinaryNode("0", BinaryNode.CONSTANT, 0.0); // The list of function names must be in alpha order. /** * The array of the names of recognized functions. It should be in * alpha order. It must correspond to the private numParams array * and the order list of function names. It was made protected only * to allow extensions to access the list of names. */ protected static final String [] functions = {"abs", "acos", "asin", "atan", "atan2", "cbrt", "ceil", "cos", "cosh", "exp", "expm1", "floor", "hypot", "IEEEremainder", "log", "log10", "log1p", "max", "min", "pow", "random", "rint", "round", "roundTo","signum", "sin", "sinh", "sqrt", "tan", "tanh", "toDegrees", "toRadians", "ulp","user"}; // <<<<<<<<<< static final int [] numParams // number of parameters for a function = {1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; // <<<<<<<<<< static final int ABS = 0; static final int ACOS = 1; static final int ASIN = 2; static final int ATAN = 3; static final int ATAN2 = 4; static final int CBRT = 5; static final int CEIL = 6; static final int COS = 7; static final int COSH = 8; static final int EXP = 9; static final int EXPM1 = 10; static final int FLOOR = 11; static final int HYPOT = 12; static final int IEEEREMAINDER = 13; static final int LOG = 14; static final int LOG10 = 15; static final int LOG1P = 16; static final int MAX = 17; static final int MIN = 18; static final int POW = 19; static final int RANDOM = 20; static final int RINT = 21; static final int ROUND = 22; static final int ROUNDTO = 23; static final int SIGNUM = 24; static final int SIN = 25; static final int SINH = 26; static final int SQRT = 27; static final int TAN = 28; static final int TANH = 29; static final int TODEGREES = 30; static final int TORADIANS = 31; static final int ULP = 32; static final int USER = 33; // <<<<<<<<<<<< static final String keyword = "define "; // <<<<<<<<<<<< /** * ## introHelp can be used as the first part of a help message in * applications using ExprEvaluator. introHelp should be followed by * varHelp (or equivalent modified for the application) and then * infixHelp. They can be used in something like: *

     *      ExprEvaluator.introHelp + ExprEvaluator.varHelp
     *          + ExprEvaluator.infixHelp
     * @see #varHelp
     * @see #prefixHelp
     * @see #infixHelp
     * @see #postfixHelp
     */
    public static String introHelp =
           "Expressions may include:";

    /**
     * ## varHelp can be used as the middle part of a help message in those
     * situations where all 26 variables can be used.  It would normally
     * be immediately preceded infixHelp.  In those situations where only
     * a restricted subset of variables are allowed, use an appropriate
     * substitute for this string listing those variables.
     * @see #introHelp
     * @see #prefixHelp
     * @see #infixHelp
     * @see #postfixHelp
     */
    public static String varHelp =
           "   variables:  a, b, c, ..., z\n";
   
    private static String common1Help =
           "   double numbers (like 2 or 3.1415927)\n"
         + "   constants:  PI, E\n"
         + "   operators:  +, -, *, /, ^ (exponent), = (assignment)\n";
    private static String common2Help =
           "Notes:\n"
         + "    Variable, constant, and function names are case sensitive.\n";
    private static String common3Help =
           "    Trig functions use radians.\n"
         + "    log uses base e while log10 uses base 10.\n"
         + "    floor  rounds down towards -infinity while  round  rounds"
         + " the nearest integer.\n";
    private static String common4Help =
           "    random  generates a random number >= 0 and <1.\n";
    private static String common5Help =
           "    toDegrees  and  toRadians  convert between degrees and"
         + " radians.\n"
         + "    In addition to those functions listed, all Java functions"
         + " are available.  (34 in total.)";

    /**
     * ## prefixHelp can be used in a help message that explains to the user
     * what is allowed as input to the ExprEvaluator when prefix expressions
     * are used.  It should be preceded by introHelp and varHelp or
     * equivalent customized for a particular applications.
     * 
     *      ExprEvaluator.introHelp + ExprEvaluator.varHelp
     *          + ExprEvaluator.prefixHelp
     * @see #introHelp
     * @see #varHelp
     */    public static String prefixHelp =
         common1Help
         + "   functions:  e.g. abs x, acos x, asin x, atan x, atan2 y x, cos x,"
         + " cbrt x, ceil x, \n"
         + "        cosh x, exp x, expm1 x, floor x, hypot x y, IEEEremaider x y,"
         + " log x, log10 x, \n"
         + "        log1p x, max x y, min x y, pow x y, random, rint x, round x,"
         + " roundTo x num, \n"
         + "        signum x, sin x, sinh x, sqrt x, tan x, tanh x, toDegrees x, toRadians x,\n"
         + "        ulp x, or user x.     [To define user use "
         + "\"define = user param expression\"].\n"
         + "\n"
         + common2Help
         + "    = = a b + * 2 x 3  assigns values to a and b as well as"
         + " returning a value.\n"
         + common3Help
         + "    roundTo x num  rounds x to num decimal places.  num<0 is OK.\n"
         + common4Help
         + "    pow x y  raises x to the y and is equivalent to  ^ x y.\n"
         + common5Help;
    /**
     * ## infixHelp can be used in a help message that explains to the user
     * what is allowed as input to the ExprEvaluator when infix expressions
     * are used.  It should be preceded by introHelp and varHelp or
     * equivalent customized for a particular applications.
     * 
     *      ExprEvaluator.introHelp + ExprEvaluator.varHelp
     *          + ExprEvaluator.infixHelp
     * @see #introHelp
     * @see #varHelp
     */
    public static String infixHelp =
         common1Help
         + "   functions:  e.g. abs(x), acos(x), asin(x), atan(x), atan2(y, x),"
         + " cbrt(x), ceil(x), cos(x),\n"
         + "        cosh(x), exp(x), expm1(x), floor(x), hypot(x, y), "
         + " IEEEremainder(x, y), log(x), log10(x), \n"
         + "        log1p(x), max(x, y), min(x, y), pow(x, y), random(),"
         + " rint(x), round(x), roundTo(x, num),\n"
         + "        signum(x), sin(x), sinh(x), sqrt(x), tan(x), tanh(x), toDegrees(x),"
         + " toRadians(x), ulp(x),\n"
         + "        or user(...).     [To define user use "
         + "\"define user(paramList) = expression)\"].\n"
         + "\n"
         + common2Help
         + "    \"^\" is right associative so 4^3^2 = 4^(3^2).\n"
         + "    Leading - signs are treated as binary operators."
         + " That is, -x is interpreted as 0 - x.\n"
         + "    Leading + signs are ignored.\n"
         + "    a = b = 2 * x + 3  assigns values to a and b as well as"
         + " returning a value.\n"
         + common3Help
         + "    roundTo(x, num)  rounds x to num decimal places. num<0 is OK."
         + "\n"
         + common4Help
         + "    pow(x, y)  raises x to the y and is equivalent to  x ^ y.\n"
         + common5Help;
    /**
     * ## postfixHelp can be used in a help message that explains to the user
     * what is allowed as input to the ExprEvaluator when postfix expressions
     * are used.  It should be preceded by introHelp and varHelp or
     * equivalent customized for a particular applications.
     * 
     *      ExprEvaluator.introHelp + ExprEvaluator.varHelp
     *          + ExprEvaluator.postHelp
     * @see #introHelp
     * @see #varHelp
     */    public static String postfixHelp =
         common1Help
         + "   functions:  e.g. x abs, x acos, x cosh, x asin, x atan,"
         + " y x atan2, x cbrt, x ceil,\n"
         + "        x cos, x cosh, x exp, x expm1, x floor, x y hypot,"
         + " x y IEEEremainder, x log, \n"
         + "       x log10, x loglp, x y max, x y min,"
         + " x y pow, random, x rint, x round, x num roundTo,\n"
         + "        x signum, x sin, x sinh, x sqrt, x tan,"
         + " x tanh, x toDegrees, x toRadians,\n"
         + "        x ulp or x user.     [To define user use "
         + "\"define param user expression = \"].\n"
         + "\n"
         + common2Help
         + "    a b 2 x * 3 + = =  assigns values to a and b as well as"
         + " returning a value.\n"
         + common3Help
         + "    x num  roundTo rounds x to num decimal places. num<0 is OK.\n"
         + common4Help
         + "    x y pow  raises x to the y and is equivalent to  x y ^.\n"
         + common5Help;


    // **************************** CONSTRUCTOR ****************************
    /**
     * ## Initializes an ExprEvaluator object with an empty expression tree.
     */
    public ExprEvaluator () {
       int i;
       root = null;
       for (i = 0; i < 26; i++)
          values[i] = 0;
    } // end ExprEvaluator

    // **************************** debugMode **************************
    /**
     * Sets the debug mode in buildFromInfix.  Debug output is produced
     * and sent to System.out if the private variable debugStacks is true.
     * The output is only useful for debugging build and evaluate methods.
     * @param debugging - sets the debug mode (true turns the mode on).
     */
    public void debugMode(boolean debugging) {
       debug = debugging;
    } // end debugMode

    // ************************** displayStacks ************************
    /**
     * Sets the display stack mode in buildFromInfix.  Output is produced
     * and sent to System.out
     * if the mode is true.  This output may be useful to demonstrate how
     * buildFromInfix uses stacks or to help while debugging.
     * @param debug - sets the display stack mode (true turns the mode on.
     */
    public void displayStacks(boolean debug) {
       debugStacks = debug;
    } // end debugMode

    // ***************** get and sets for variables ********************
    /**
     * ## Gets the value of x.
     * @return - the value of x used in the last call of evaluate.
     * @see #setX(double)
     */
    public double getX( ) {
       return values['x' - 'a'];
    }  // end getX

    /**
     * ## Sets the value of x.
     * @param value - the value of variable 'x'
     * @see #getX
     */
    public void setX(double value) {
       values['x' - 'a'] = value;
    }

    /**
     * ## Gets the value of y.
     * @return - the value of y used in the last call of evaluate.
     * @see #setY
     */
    public double getY( ) {
       return values['y' - 'a'];
    }  // end #getY

    /**
     * ## Sets the value of y.
     * @param value - the value of variable 'y'
     * @see #getY
     */
    public void setY(double value) {
       values['y' - 'a'] = value;
    }  // end of setY

    /**
     * ## Gets the value of t.
     * @return - the value of t used in the last call of evaluate.
     * @see #setT
     */
    public double getT( ) {
       return values['t' - 'a'];
    }  // end getT

    /**
     * ## Sets the value of t.
     * @param value - the value of variable 't'
     * @see #getT
     */
    public void setT(double value) {
       values['t' - 'a'] = value;
    } // end setT

    /**
     * ## Gets the value of the variable var where var is 'a', 'b', ..., or 'z'.
     * @param var - the variable name as a character.
     * @return - the value of the specified variable.  If the variable is outside
     * the range of 'a', 'b', ..., 'z', NaN (Not a Number) is returned instead.
     * @see #setValueOf
     * @see #getX
     * @see #getY
     * @see #getT
     */
    public double getValueOf(char var) {
       if (var >= 'a' && var <= 'z')
          return values[variables.indexOf(var)];
       else
          return Double.NaN;
    }  // getValueOf

    /**
     * ## Sets the value of the specified variable ('a', 'b', ..., or 'z') to
     * the specified value.  The value is ignored if the character var is
     * outside the specified range.
     * @param var - a character in the range 'a' to 'z'.  The name of one of
     * the 26 variables.
     * @param value - the value to which the variable is set.
     * @see #getValueOf
     * @see #setX
     * @see #setY
     * @see #setT
     */
    public void setValueOf(char var, double value) {
       if (var >= 'a' && var <= 'z')
          values[var - 'a'] = value;
    }
    /**
     * ## Returns the array of 26 doubles corresponding to the the variables
     * "a", "b", ..., or "z".  The value of "a" is stored in location 0,
     * the value of "b" is stored in location 1 and so on.
     * @return - an array containing the values of the 26 variables.
     * @see #setValues
     */
    public double [] getValues() {
       return values;
    }  // getValues


    /**
     * ## Sets the value of all 26 variables.
     * @param values - an array of doubles with the 26 variable values.  If the
     * array has less than 26 values, only corresponding values are set.  If
     * array has more than 26 values, only the first 26 are used.
     * @see #getValues
     */
    public void setValues(double [] values) {
       for (int i = 0; i < Math.min(values.length, 26); i++)
          this.values[i] = values[i];
    }

    // **************************** getInputStr **********************
    /**
     * ## Returns the last input string processed by buildFromPrefix, buildFromInfix,
     * or buildFromPostfix methods.
     *
     * @return = the last input string.
     */
    public String getInputStr() {
       return inputStr;
    }  // end getInputStr
    
    // *************************** getUserFunction ********************
    /**    
     * ## Returns the current user function as a string.
     * @return The user function.  It will be the empty string if the function
     * has not been defined properly.
     */
    public String getUserFunction() {
       return userFunction;
    }


    // ***************************** isDouble **************************
    /**
     * Determines if the string is an double.  Numbers begin with a digit
     * or a '.', "+", or "-".  Strings beginning with a nondigit must
     * be longer than one character.  Values passing these tests are
     * parsed to make sure they will convert to a double.
     * 

(Note: if the DoubleScanner is used, number tokens cannot begin * with + or -.) * @return true if the string is an operator, false otherwise. */ public static boolean isDouble(String s) { char ch; ch = s.charAt(0); if ( Character.isDigit(ch) || (ch == '.' || ch == '-' || ch == '+') && (s.length() > 1)) { try { // make sure that s can be converted to double Double.parseDouble(s); } catch ( Exception e ) { return false; } return true; } else return false; } // end isDouble // ***************************** isOperator ************************** /** * Determines if the string is an operator. Recognized operators are * '(', ')', '+', '-', '*', '/', '^', '=', and ','. "," is allowed * only in functions used in infix expressions. *

* Actually this routine should be called isDelimiter as it only recognizes * the operators that can be used in expressions. * * @return true if the string is an operator, false otherwise. */ public boolean isOperator(String s) { // Note: IsOperator would more correctly called isDelimitor. The "operators" // "<" and "[" are used internally but not recognized by this function. int len; char ch; ch = s.charAt(0); len = s.length(); return len == 1 && DELIMITERS.indexOf(ch) >= 0; } // end isOperator // **************************** isFunction ************************** /** * Returns a number >= 0 if the string s is a function name. Returns * -1 if it is not a function name. The value is the subscript of the * function name in the array of function names. * @param s - the string which will be tested. * @return - the subscript of the function in the array of functions * names or -1 if the string is not a function. */ public static int isFunction(String s) { for (int i = 0; i < functions.length; i++) { if (s.equals(functions[i])) return i; } return -1; } // end isFunction // **************************** numFuncParams ************************** /** * Returns the number of parameters if the string is a function name. * See functions for a list of function names that will be recognized. * Returns the number of parameters for the function or -1 if the string * is not a function name. * @param s - the string which will be tested. * @return - the number of parameters if the string is the name of a * function or -1 if the string is not a function. */ public static int numFuncParams(String s) { for (int i = 0; i < functions.length; i++) { if (s.equals(functions[i])) return numParams[i]; } return -1; } // end numFuncParams // **************************** precedence ************************** /** * Determines the precedence of an operator. * * @param operator - the operator. * * @return the precedence of the operator. */ public static int precedence(String operator) { if (operator.length() != 1) return -10; else if ("+-".indexOf(operator) >= 0) return 4; else if ("*/".indexOf(operator) >= 0) return 5; else if (operator.equals("^")) return 6; else if (operator.equals("(")) return -1; else if (operator.equals("=")) return 1; else if (operator.equals(")")) return 0; else if (operator.equals(",")) // parameter return 2; else if (operator.equals("<")) // function ( return 3; else return -10; } // method precedence // ************************** comparePrecedence ********************** /** * Compares precedence of two operators. * * @param op1 the first operator. * @param op2 the second operator. * * @return a negative number if op1 < op2, 0 if op1 = op2, or a * positive number if op1 > op2. Special cases: if op1 * = "op2" = "^" or op1 = op2 = "=" then it returns -1 to cause * "^" and "=" to be right associative. */ public static int comparePrecedence(String op1, String op2) { if ((op1.equals("^") && op2.equals("^")) || (op1.equals("=") && op2.equals("="))) return -1; // right associative so 2^3^2 = 2^(3^2)\ // and a = b = c is a = (b = c) return precedence(op1) - precedence(op2); } // method comparePrecedence // ************************** showStacks ***************************** /** * Optionally show the current token and the stacks. May be helpful * for debugging or demonstrating how the procedure works. * @param token - the token that was just processed. * @param nodeStack - the node stack * @param operatorStack - the operator stack */ private void showStacks(String token, ArrayPureStack> nodeStack, ArrayPureStack operatorStack) { if (debugStacks) { if (token.equals("(just initialized)")) System.out.println("\n"); System.out.println("*** token: " + token + "\n nodeStack: " + nodeStack + "\n operatorStack: " + operatorStack); } } // end showStacks // ************************** buildFromPrefix *********************** /** * ## Builds an expression tree given a prefix string of tokens. * It can be used to define the user function with 1 parameter. * Tokens may be nonnegative double numbers, constant, variable, function * or operator "+", "-", "*", "/", "^", or "=" * *

Examples of postfix functions: *
sqrt 16   : (square root of 16) *
* sqrt + ^4 2 ^ 3 2 100   : (square root(4^2 + 3^2) * 100) *
toDegrees atan2 1 sqrt 3   : (atan2(1, sqrt(3)) converted to * degrees) * *

The tree built from the prefix string is assigned to root. * * @param prefix the infix string to be processed. * @return the expression tree that was built. This may be considered the * "compiled" version of the expression. */ public ExprTree buildFromPrefix(String prefix) throws Exception { boolean definingFunction = false; // <<<<<<<<<<<<<<< BinaryNode userFunction = null; // <<<<<<<<<<<<<<< // check for function definition prefix = prefix.trim(); if (prefix.indexOf(keyword) == 0) { // <<<<<<<<<<<<<< definingFunction = true; numParams[USER] = 1; prefix = prefix.substring(keyword.length()); if(debug) System.out.println("Defining a function: the string: " + prefix); } inputStr = prefix; DoubleScanner prefixTok = new DoubleScanner(prefix, DELIMITERS); if (debug) System.out.println(prefix); root = buildFromPrefixSub(prefixTok); if (prefixTok.hasNext()) { reportError("Illegal expression: end of expression not processed"); } // check for user defined functions // <<<<<<<<<<<<<< if (definingFunction) { defineFunction(root); root = null; } return new ExprTree(root); } // end buildFromPrefix private BinaryNode buildFromPrefixSub(DoubleScanner prefixTok) throws Exception { String tok; BinaryNode node, commaNode, paramNode, lastNode, leftNode, rightNode; if (! prefixTok.hasNext()) { reportError("Illegal expression: incomplete - needs more tokens '"); } tok = prefixTok.next(); if (isDouble(tok)) { return new BinaryNode(tok, BinaryNode.DOUBLE, Double.parseDouble(tok)); } else if (tok.equals("PI")) { return new BinaryNode(tok, BinaryNode.CONSTANT, Math.PI); // nodeType = BinaryNode.CONSTANT; } else if (tok.equals("E")) { return new BinaryNode(tok, BinaryNode.CONSTANT, Math.E); // nodeType = BinaryNode.CONSTANT; } else if (tok.length() == 1 && variables.indexOf(tok) >= 0) { return new BinaryNode(tok, BinaryNode.VARIABLE, tok.charAt(0)); // nodeType = BinaryNode.VARIABLE; } else if (isFunction(tok) >= 0) { node = new BinaryNode(tok, BinaryNode.FUNCTION, isFunction(tok), numFuncParams(tok)); lastNode = null; for (int i = 0; i < numFuncParams(tok); i++) { paramNode = buildFromPrefixSub(prefixTok); commaNode = new BinaryNode(",", BinaryNode.OPERATOR, ',', null, paramNode); node.left = commaNode; commaNode.left = lastNode; lastNode = commaNode; } if (debug) { System.out.print("node: " + tok + ", Parameters: "); lastNode = node; while (lastNode.left != null) { lastNode = lastNode.left; System.out.print(lastNode.right.element + " "); } System.out.println(); } return node; } else if (tok.equals("(") || tok.equals(")") || tok.equals(",")) { reportError("Illegal parenthesis or comma. " + "The are not allowed in prefix expression"); } else if (isOperator(tok)) { leftNode = buildFromPrefixSub(prefixTok); rightNode = buildFromPrefixSub(prefixTok); if (debug) System.out.println("node: " + tok + ", left node: " + leftNode.toString() + ", right node: " + rightNode.toString()); return new BinaryNode(tok, BinaryNode.OPERATOR, tok.charAt(0), leftNode, rightNode); } else { // It is invalid reportError("Illegal token: '" +tok + "'"); } return null; // Should never happen but required to compile } // end buildFromPrefixSub // ************************** buildFromInfix ************************* /** * ## Builds an expression tree given an infix string of tokens. * I can be used to define the user function with 0 to 10 parameters. * Tokens may be double numbers (optionally with leading "-" or "+") or * an operator "+", "-", "*", "/", "^" (exponent) * or the names of the variables "a", "b",... or "z", the constants PI or * E, or a function name. Names are case sensitive.

*

The precedence of operators: *

    *
  • ( ) (highest priority)
  • *
  • functions
  • *
  • ^
  • *
  • leading + and - signs
  • *
  • * and /
  • *
  • + and - (binary operations)
  • *
  • = (assignment) (lowest priority) *
*

The tree built from the infix string is assigned to root. * * @param infix the infix string to be processed. * @return the expression tree that was built. This may be considered the * "compiled" version of the expression. * @throws Exception - if an identifier name is invalid or the expression * is invalid. */ public ExprTree buildFromInfix(String infix) throws Exception{ DoubleScanner scan; String token; BinaryNode node; BinaryNode commaNode; BinaryNode left; BinaryNode right; BinaryNode emptyForParamList = null; int nodeType; boolean lastOpLeftParen; boolean definingFunction = false; // <<<<<<<<<<<<<<< BinaryNode userFunction = null; // <<<<<<<<<<<<<<< // check for function definition infix = infix.trim(); if (infix.indexOf(keyword) == 0) { // <<<<<<<<<<<<<< definingFunction = true; infix = infix.substring(keyword.length()).trim(); if(debug) System.out.println("Defining a function: the string: " + infix); } inputStr = infix; // save a copy of infix // I. Initialize nodeStack to be an empty stack of // BinaryNodes and operatorStack to be an empty stack of // operators ArrayPureStack> nodeStack = new ArrayPureStack>(); ArrayPureStack operatorStack = new ArrayPureStack(); // II. Push a left type parenthesis '(' on the operator stack. operatorStack.push("("); lastOpLeftParen = true; if (debug) System.out.println("\npush (: operatorStack: " + operatorStack); // III. Append a right parenthesis ')' to the end of infix and create a // scanner for it. infix += " )"; // include blank for scanners that need whitespace scan = new DoubleScanner(infix, DELIMITERS); scan.setReservedWords(functions); if (debug) System.out.println(" add ): infix: " + infix); showStacks("(just initialized)", nodeStack, operatorStack); // IV. While the scanner has more tokens, process infix tokens // from left to right by doing the following: while (scan.hasNext()) { // A. Get the next token token = scan.next(); if (debug) System.out.println("Token: " + token); if (isDouble(token)) { node = new BinaryNode(token, BinaryNode.DOUBLE, Double.parseDouble(token)); nodeType = BinaryNode.DOUBLE; } else if (token.length() == 1 && variables.indexOf(token) >= 0) { node = new BinaryNode(token, BinaryNode.VARIABLE, token.charAt(0)); nodeType = BinaryNode.VARIABLE; } else if (token.equals("PI")) { node = new BinaryNode(token, BinaryNode.CONSTANT,PI); nodeType = BinaryNode.CONSTANT; } else if (token.equals("E")) { node = new BinaryNode(token, BinaryNode.CONSTANT, E); nodeType = BinaryNode.CONSTANT; } else if (isFunction(token) >= 0) { node = new BinaryNode(token, BinaryNode.FUNCTION, isFunction(token), numFuncParams(token)); nodeType = BinaryNode.FUNCTION; } else if (isOperator(token)) { node = null; // the node will be built later nodeType = BinaryNode.OPERATOR; } else { reportError("Illegal token: " + token); return null; // never happens but added to prevent compiler errors } if (debug) System.out.println ("token: " + token); // B. If the current token is a double, create a BinaryNode for // it and push it on the nodeStack. if (nodeType == BinaryNode.DOUBLE || nodeType == BinaryNode.VARIABLE || nodeType == BinaryNode.FUNCTION || nodeType == BinaryNode.CONSTANT) // variable, "PI", "E", double or function { nodeStack.push(node); lastOpLeftParen = false; if (debug) System.out.println("push " + token + " nodeStack: " + nodeStack); if (numFuncParams(token) >= 0) { operatorStack.push("["); // Expect to find a ( following function if (debug) System.out.println("push '[' on operator stack"); lastOpLeftParen = true; } // C. Else if current token is a left parenthesis, push it on the // operator stack. } else if (token.equals("(")) { // "(" if (operatorStack.peek().equals("[")) { operatorStack.pop(); // found "(" after function. change operatorStack.push("<"); // "[" to "<" if (debug) System.out.println("processing '('. Pushed '<' to replace '['"); } else { operatorStack.push(token); if (debug) System.out.println("processing '('. Pushed '('"); } lastOpLeftParen = true; // D. Else if last op is left paren and the token is "+", ignore it } else if (lastOpLeftParen && token.equals("+")) { // ignore unary "+" but set lastOpLeftParen false lastOpLeftParen = false; // E. Else if the current token is an operator (including ")" } else if (nodeType == BinaryNode.OPERATOR) { // operator // 0. if lastOpLeftParen is true push a zero on the node stack if (lastOpLeftParen && (token.equals("-"))) { // handling of leading + or - works but is inefficient // as it always creates a new 0 node. nodeStack.push(zero); if (debug) System.out.println("added 0: nodeStack: " + nodeStack); } // 1. if lastOpLeftParen and the token is ")" and the top of // the operator stack is "<", replace "<" by "(" to allow // parameterless functions. if (lastOpLeftParen && token.equals(")") && operatorStack.peek().equals("<")) { operatorStack.pop(); // This allows parameterless functions operatorStack.push("("); } // 2. Set lastOpLeft paren to false lastOpLeftParen = false; // 3. While the operator on the top of the operator stack has more // precedence than the current token while (comparePrecedence(operatorStack.peek(), token) >= 0) { if (debug) System.out.println("top stack: " + operatorStack.peek() + " prec " + precedence(operatorStack.peek()) + " token " + token + " prec " + precedence(token) + " compare " + comparePrecedence(operatorStack.peek(), token) + " lastOpLeftParen: " + lastOpLeftParen); // a. Pop an operator off the operator stack String op = operatorStack.pop(); // b. if the operator is a "<" if (op.equals("<")) { //first parameter after function right = nodeStack.pop(); // first parameter commaNode = new BinaryNode(",", BinaryNode.OPERATOR, ',', null, right); node = nodeStack.pop(); // the function node.left = commaNode; // attach the first parameter nodeStack.push(node); operatorStack.push("("); if (debug) { System.out.println("processing '<', popped " + right); System.out.println("processing '<', popped " + node); System.out.println("processing '<', nodeStack: " + nodeStack); System.out.println("processing '<', operatorStack: " + operatorStack); } // c. if the operator is a "," } else if (op.equals(",")) { // attach new parameter to the right // elements are in a linked list in reverse order. right = nodeStack.pop(); // the parameter commaNode = new BinaryNode(",", BinaryNode.OPERATOR, ',', null, right); node = nodeStack.pop(); // function commaNode.left = node.left; node.left = commaNode; nodeStack.push(node); if (debug) { System.out.println("processing ',', popped " + right); System.out.println("processing ',', popped " + node); System.out.println("processing ',', nodeStack: " + nodeStack); System.out.println("processing ',', operatorStack: " + operatorStack); } } // d. if it is another binary operator (+, -, *, /, or ^) else { // i. Pop the right and then the left nodes of the nodeStack right = nodeStack.pop(); left = nodeStack.pop(); // ii. Create a new BinaryNode using the left node, the // operator, and the right node. node = new BinaryNode(op, BinaryNode.OPERATOR, op.charAt(0), left, right); // iii. Push this new node on the nodeStack nodeStack.push(node); if (debug) { System.out.println("processing op: nodeStack: " + nodeStack); System.out.println("processing op: opeatorStack: " + operatorStack); } } } // 4. If the operator is a right parenthesis if (token.equals(")")) { // Pop the corresponding left parenthesis off the operator // stack if (debug) System.out.println("peeking for (. top operator stack: " + operatorStack.peek()); if (operatorStack.peek().equals("(")) operatorStack.pop(); else { reportError("Invalid expression (possibly a" + " missing parenthesis)"); } if (debug) System.out.println("processing ')': opeatorStack: " + operatorStack); } else { operatorStack.push(token); if (token.equals(",")) lastOpLeftParen = true; if (debug) System.out.println("processing op: operatorStack: " + operatorStack); } } else { root = null; reportError("Invalid token: \"" + token + "\""); } // V. Set root to the top of the node stack. showStacks(token, nodeStack, operatorStack); } if (nodeStack.size() > 1) { reportError("Invalid expression (possibly a missing" + " or misplaced operator)"); } if (nodeStack.size() == 0) { // this error is probably caught elsewhere reportError("Invalid expression (possibly an extra operator)"); } if (operatorStack.size() > 0) { reportError("Error: Invalid expression (possibly a" + " missing parenthesis)"); } // check for user defined functions // <<<<<<<<<<<<<< if (definingFunction) { defineFunction(nodeStack.peek()); root = null; } else { // get ready to return root = nodeStack.peek(); } if (debug) System.out.println("return: " + nodeStack.peek()); return new ExprTree(root); } // method buildFromInfix // ************************** defineFunction *********************** /** * ## Used to define the user function. * The defineFunction assumes that one of the build methods has created * a tree of the form
*

     *       subtree expression for function
     *    /
     * =
     *    \
     *       subtree function name and parameters
     * 
* The following actions take place:
* Some error checking is done
* The parameters are replaced by "unique variables"
* The subtree for the function is used as the function definition
* @param root - The root of a tree defining the user function having * the form specified above. * @throws - Exception if the root of the tree is not "=", the name of the * function in the left subtree is not "user", there is an illegal * parameter, a parameter name is repeated in the paramter list, * or there are two many parameters. */ protected void defineFunction(BinaryNode root) throws Exception { int paramCount; BinaryNode param; char [] params = new char[MAX_PARAMS]; if (! root.element.equals("=")) { definedFunction = null; reportError("Invalid function definition: " + "Missing or misplaced '=' sign'"); } if (! root.left.element.equals(functions[USER])) { definedFunction = null; reportError("Invalid function when defining a function"); } definedFunction = root.right; userFunction = ""; // temporarily set user function to null in case // there are errors // find number of supplied parameters paramCount = 0; param = root.left.left; while (param != null && paramCount < MAX_PARAMS) { params[paramCount] = param.right.element.charAt(0); if (debug) System.out.println("param[" + paramCount + "] = " + params[paramCount]); // make sure the parameter is a legal variable if (params[paramCount] < 'a' || params[paramCount] > 'z') { definedFunction = null; reportError("Illegal parameter when defining a function. " + "Namely: " + params[paramCount]); } // verify the param is unique (so far); for (int i = 0; i < paramCount; i++) if (params[i] == params[paramCount]) { definedFunction = null; reportError("Duplicate parameters when defining function"); } // replace original parameter by a special parameter variable replaceParam(definedFunction, param.right.element, ""+(char)('z'+paramCount+1)); if (debug) System.out.println("Finished replacements " + paramCount); paramCount++; param = param.left; } if (param != null) { definedFunction = null; reportError("Defined function has too many parameters:"); } numParams[USER] = paramCount; if (debug) { System.out.println("function:\n" + toStringSub(definedFunction,1)); System.out.println("tree:\n" + toStringSub(root, 1)); } userFunction = inputStr; // The user function seems to work } // end defineFunction /** * Does a recursive traversal of node's tree and replaces every oldParam by * newParam. Used by defineFunction. * @param node - The root node of the tree to be searched * @param oldParam - the parameter to be replaced * @param newParam - the new parameter */ protected void replaceParam(BinaryNode node, String oldParam, String newParam) { // traverse the tree if (node != null) { if (node.element.equals(oldParam)) { node.element = newParam; node.subType = newParam.charAt(0); } replaceParam(node.left, oldParam, newParam); replaceParam(node.right, oldParam, newParam); } } // end replaceParam // ************************** buildTree *********************** /** * ## Builds an ExprTree from the infix expression s and returns the tree. * This is a simple extension of ExprEvaluator.buildFromInfix(s) which * is used to build the tree but then extension returns the root of the * ExprTree generated. This is useful if a situation requires evaluating * more than 1 expression. * @param s - the infix string to be processed. * @return - the root of the tree generated. * @throws - Exception - if an identifier name, a value, or the expression * is invalid. * @deprecated Starting with beta 4.5, buildFromInfix returns the tree and * should be used instead of buildTree. */ public ExprTree buildTree(String s) throws Exception { return buildFromInfix(s); } // end buildTree // ************************** buildFromPostfix *********************** /** * ## Builds an expression tree given a postfix string of tokens. * It can be used to define the user function with 1 paramter. * Tokens may be double numbers (leading + or - are not allowed) or * an operator "+", "-", "*", "/", "^", or the name of a variable * ("a", "b",..., "z"), a constant (PI or E), or a function name. * *

Examples of postfix functions: *
16 sqrt   : (square root of 16) *
4 2 ^ 3 2 ^ + sqrt 100 *   : (sq root(4^2 + 3^2) * 100) *
1 3 sqrt atan2 toDegrees   : (atan2(1, sqrt(3)) converted to * degrees) * *

The tree built from the postfix string is assigned to root. * * @param postfix the postfix string to be processed. * @return the expression tree that was built. This may be considered the * "compiled" version of the expression. */ public ExprTree buildFromPostfix(String postfix) throws Exception // throws ArrayIndexOutOfBoundsException { DoubleScanner scan; String token; BinaryNode node, right, left, param, lastParam; int nodeType; boolean definingFunction = false; // <<<<<<<<<<<<<<< BinaryNode userFunction = null; // <<<<<<<<<<<<<<< // check for function definition postfix = postfix.trim(); if (postfix.indexOf(keyword) == 0) { // <<<<<<<<<<<<<< definingFunction = true; numParams[USER] = 1; postfix = postfix.substring(keyword.length()); if(debug) System.out.println("Defining a function: the string: " + postfix); } // I. Initialize nodeStack to be an empty stack of // BinaryNodes inputStr = postfix; ArrayPureStack> nodeStack = new ArrayPureStack>(); // II. Set up a scanner for postfix String scan = new DoubleScanner(postfix, DELIMITERS); scan.setReservedWords(functions); if (debug) System.out.println(scan); // III. While the scanner has more tokens while (scan.hasNext()) { // A. Get the next token token = scan.next(); // B. If the token is a double, create a node and push // it on the node stack if (isDouble(token)) { node = new BinaryNode(token, BinaryNode.DOUBLE, Double.parseDouble(token)); nodeType = BinaryNode.DOUBLE; } else if (token.length() == 1 && variables.indexOf(token) >= 0) { node = new BinaryNode(token, BinaryNode.VARIABLE, token.charAt(0)); nodeType = BinaryNode.VARIABLE; } else if (token.equals("PI")) { node = new BinaryNode(token, BinaryNode.CONSTANT, Math.PI); nodeType = BinaryNode.CONSTANT; } else if (token.equals("E")) { node = new BinaryNode(token, BinaryNode.CONSTANT, Math.E); nodeType = BinaryNode.CONSTANT; } else if (isFunction(token) >= 0) { node = new BinaryNode(token, BinaryNode.FUNCTION, isFunction(token), numFuncParams(token)); nodeType = BinaryNode.FUNCTION; } else if (isOperator(token)) { node = null; // the node will be built later nodeType = BinaryNode.OPERATOR; } else { reportError("Illegal token: " + token); return null; // never happens but prevents compiler errors } if (debug) System.out.println ("token: " + token); // B. If the current token is a double, create a BinaryNode for // it and push it on the nodeStack. if (nodeType == BinaryNode.DOUBLE || nodeType == BinaryNode.VARIABLE || nodeType == BinaryNode.CONSTANT) nodeStack.push(node); else if (nodeType == BinaryNode.FUNCTION) { lastParam = node; for (int i = 0; i < numFuncParams(token); i++) { right = nodeStack.pop(); param = new BinaryNode(",", BinaryNode.OPERATOR, ',', null, right); lastParam.left = param; lastParam = param; } nodeStack.push(node); } // C. Else (token is an operator) else { // i. pop the right and left expressions from the // node stack right = nodeStack.pop(); left = nodeStack.pop(); // ii. Create a node from token, left and right node = new BinaryNode(token, BinaryNode.OPERATOR, token.charAt(0), left, right); // iii. Push it on the node stack nodeStack.push(node); } } // IV. set root to the resulting tree on the node stack root = nodeStack.pop(); if (debug) System.out.println(toString()); // check for user defined functions // <<<<<<<<<<<<<< if (definingFunction) { defineFunction(root); root = null; } return new ExprTree(root); } // end method buildFromPostfix // ***************************** toPrefix ***************************** /** * ## Creates a prefix expression from the expression tree. * @return The string holding the expression written in postfix. */ public String toPrefix( ) { // if the expressiong tree is empty if (root == null) // return "empty" return "(empty)"; else // else recursive build and return the prefix expression return toPrefixSub(root); } // end toPostfix private String toPrefixSub(BinaryNode ref) { // it is assumed that preFixSub is never called with a null ref // visit the root of the subtree by putting into string s. String s = ""; if (ref.element != ",") s = ref.element + " "; // if the left subtree is not null, add it to the string s if (ref.left != null) s += toPrefixSub(ref.left) + " "; // if the right subtree is not null, add it to the string s if (ref.right != null) s += toPrefixSub(ref.right) + " "; // return s return s; } // end toPostfixSub // ****************************** toInfix ****************************** /** * ## Creates a fully parenthesized infix expression String. * @return The string holding the expression written in infix. */ public String toInfix() { if (root != null) return toInfixSub(root); else return "(empty)"; } // end toInfix private String toInfixSub(BinaryNode ref) { String s = ""; String suffix = ""; if (! (ref.left == null && ref.right == null) && !ref.element.equals(",")) { s = "("; suffix = ")"; } if (ref.type == BinaryNode.FUNCTION){ s = ref.element + "("; suffix = ""; if (ref.left != null) s += toInfixSub(ref.left); s += ")"; } else if (ref.element.equals(",")) { if (ref.left != null) { s += toInfixSub(ref.left); s += ","; } if (ref.right != null) { s += toInfixSub(ref.right); } } else { if (ref.left != null) { s += toInfixSub(ref.left); } s += ref.element; if (ref.right != null) { s += toInfixSub(ref.right); } } s += suffix; return s; } // toInfixSub // ***************************** toPostfix ***************************** /** * ## Creates a postfix expression from the expression tree. * @return The string holding the expression written in postfix. */ public String toPostfix( ) { if (root != null) return toPostfixSub(root); else return "(empty)"; } // end toPostfix private String toPostfixSub(BinaryNode ref) { // it is assumed that postFixSub is never called with a null ref String s = ""; if (ref.left != null) s += toPostfixSub(ref.left) + " "; if (ref.right != null) s += toPostfixSub(ref.right) + " "; if (ref.element != ",") s += ref.element; return s; } // end toPostfixSub // ************************* evaluate (no parameter) ********************** /** * ## Returns the value of a binary expression tree previously created by * one of the build methods using the current values of the variables. * Once a binary tree is created by a build * method, evaluate can be called repeatedly with different parameters. *

* In the case of illegal calculations such as 1/0, log(-1), or sqrt(-1), * the result will typically be infinity, -infinity, or NaN (Not a Number). * Note: This version assumes the values of the 26 values are unchanged * from the last evaluation or the time they were supplied. If they have * never been defined, their value will be 0. * * @return The value of the expression. * @throws Exception - if a function cannot be evaluated or has the wrong * number of parameters. * @see #setT(double) * @see #setX(double) * @see #setY(double) * @see #setValueOf(char, double) * @see #setValues(double[]) */ public double evaluate () throws Exception { if (debug) System.out.println("\nEvaluate"); if (root != null) return evaluateSub(root); else return 0; // There is no tree to evaluate } // end evaluate (3 parameter) // ************************* evaluate (3 parameter) ********************** /** * ## Returns the value of a binary expression tree previously created by * one of the build methods using the supplied values of t, x, and y and the * current values of the other variables. Once a binary tree is created by * a build method, evaluate can be called repeatedly with different * parameters. *

* In the case of illegal calculations such as 1/0, log(-1), or sqrt(-1), * the result will typically be infinity, -infinity, or NaN (Not a Number). *

* Note: This version allows setting the values of the variables "t", "x", * and "y". This may be completely adequate for many situations. * The values of the other 23 variables are unchanged from any * previous evaluations. If they have never been defined, there value will * be 0. * * @param t - value of variable t. * @param x - value of variable x. * @param y - value of variable y. * @return The value of the expression. * @throws Exception - if a function cannot be evaluated or has the wrong * number of parameters. */ public double evaluate (double t, double x, double y) throws Exception { values['x' - 'a'] = x; values['y' - 'a'] = y; values['t' - 'a'] = t; return evaluate(); } // end evaluate (3 parameter) // *********************** evaluate (1 parameter) ************************* /** * ## Returns the value of a binary expression tree previously created by * one of the build methods using the supplied parameters. Once a binary * tree is created by a build * method, evaluate can be called repeatedly with different parameters. *

* In the case of illegal calculations such as 1/0, log(-1), or sqrt(-1), * the result will typically be infinity, -infinity, or NaN (Not a Number). *

* In this version of evaluate, the values of all 26 variables are supplied * in an array. * @param params - an array of 26 double values corresponding to the * values of variables "a", "b", ..., "z" * @return The value of the expression. * @throws Exception - if a function cannot be evaluated or has the wrong * number of parameters. * @see #getValues */ public double evaluate (double [] params) throws Exception { setValues(params); // for (int i = 0; i < Math.min(26, params.length); i++) // values[i] = params[i]; return evaluate(); } // end evaluate (1 parameter) private double evaluateSub(BinaryNode ref) throws Exception { if (debug) System.out.println("EvaluateSub " + ref + ", type: " + ref.type); double val, leftVal, rightVal; // it is assumed that evaluateSub is never called with a null ref if (ref.type == BinaryNode.VARIABLE) { val = values[variables.indexOf(ref.subType)]; if (debug) System.out.println("Binary node: " + ref.toString() + " subtype: " + ref.subType); } else if (ref.type == BinaryNode.CONSTANT || ref.type == BinaryNode.DOUBLE) { val = ref.value; } else if (ref.type == BinaryNode.FUNCTION) { if (debug) System.out.println("Function"); val = calculateFunction(ref); } else { // the node is a operator - the left and right links // must exist leftVal = evaluateSub(ref.left); rightVal = evaluateSub(ref.right); val = calculate(ref, leftVal, rightVal); } return val; } // end evaluateSub private double calculate(BinaryNode opNode, double leftVal, double rightVal) throws Exception { if (debug) System.out.println("Calculate " + opNode.element + " " + leftVal + " " + rightVal); if (opNode.subType == '+') return leftVal + rightVal; else if (opNode.subType == '-') return leftVal - rightVal; else if (opNode.subType == '*') return leftVal * rightVal; else if (opNode.subType == '/') return leftVal / rightVal; else if (opNode.subType == '=') { if (opNode.left.type == BinaryNode.VARIABLE) { values[opNode.left.subType - 'a'] = rightVal; return rightVal; } else reportError( "Error: equal sign must assign value to a variable" + toString()); return 0; // never happens but prevents compiler errors } else if (opNode.subType == '^') { int intRightVal = (int)Math.floor(rightVal); if (intRightVal >= -50 && intRightVal <=50 && intRightVal == rightVal) { // if rightVal is an reasonable sized integer, use multiplication // (and division) double val = 1.0; for (int i = 0; i < Math.abs(intRightVal); i++) val = val * leftVal; if (intRightVal < 0) val = 1.0/val; return val; } else // otherwise use logs and exp. return Math.pow(leftVal, rightVal); } else return Double.NaN; } // end calculate private double calculateFunction(BinaryNode ref) throws Exception { // params are in reverse order if (debug) System.out.println("CalculateFunction - function: " + ref.element + " ref.subType: " + ref.subType); BinaryNode param; double [] params = new double[MAX_PARAMS]; double val; int numParams, neededParams; String functionName; functionName = ref.element; neededParams = ref.numParams; val = Double.NaN; try { // find number of supplied parameters numParams = 0; param = ref.left; while (param != null && numParams < MAX_PARAMS) { params[numParams] = evaluateSub(param.right); if (debug) System.out.println("param[" + numParams + "] = " + params[numParams]); numParams++; param = param.left; } // the following is a copy of the original definition that is intended to // to make it coding this method easier. // static String [] functions // = {"abs", "acos", "asin", "atan", "atan2", "cbrt", "ceil", // "cos", "cosh", "exp", "expm1", "floor", "hypot", "IEEEremainer", // "log", "log10", "log1p", "max", "min", "pow", "random", // "rint", "round", "roundTo","signum", "sin", "sinh", "sqrt", // // "tan", "tanh", "toDegrees", "toRadians", "ulp","user"}; // static int [] numParams // number of parameters for a function // = {1, 1, 1, 1, 2, 1, 1, // 1, 1, 1, 1, 1, 2, 2, // 1, 1, 1, 2, 2, 2, 0, // 1, 1, 2, 1, 1, 1, 1, // 1, 1, 1, 1, 1, 3 }; if (numParams != neededParams) { System.out.println("Error: Invalid parameter list for " + functionName + " which must have " + neededParams + " parameters"); throw new NegativeArraySizeException(); // Note: Using NegativeArraySizeException is just an hack // so the exception can be processed below in which the // exception is turned into a normal esception. } if (ref.subType == USER) return calculateUser(params); if (numParams == 0) { if (ref.subType == RANDOM) // corrected 6/29/18 { val = Math.random(); // System.out.println("random:" + Math.random()); } } else if (numParams == 1) { switch (ref.subType) { case ABS: val = Math.abs(params[0]); break; case ACOS: val = Math.acos(params[0]); break; case ASIN: val = Math.asin(params[0]); break; case ATAN: val = Math.atan(params[0]); break; case CBRT: val = Math.cbrt(params[0]); break; case CEIL: val = Math.ceil(params[0]); break; case COS: val = Math.cos(params[0]); break; case COSH: val = Math.cosh(params[0]); break; case EXP: val = Math.exp(params[0]); break; case EXPM1: val = Math.expm1(params[0]); break; case FLOOR: val = Math.floor(params[0]); break; case LOG: val = Math.log(params[0]); break; case LOG10: val = Math.log10(params[0]); break; case LOG1P: val = Math.log1p(params[0]); break; case RINT: val = Math.rint(params[0]); break; case ROUND: val = Math.round(params[0]); break; case SIGNUM: val = Math.signum(params[0]); break; case SIN: val = Math.sin(params[0]); break; case SINH: val = Math.sinh(params[0]); break; case SQRT: val = Math.sqrt(params[0]); break; case TAN: val = Math.tan(params[0]); break; case TANH: val = Math.tanh(params[0]); break; case TODEGREES: val = Math.toDegrees(params[0]); break; case TORADIANS: val = Math.toRadians(params[0]); break; case ULP: val = Math.ulp(params[0]); break; } } else if (numParams == 2) { // parameters are in reverse order!!! switch (ref.subType) { case ATAN2: val = Math.atan2(params[1], params[0]); break; case IEEEREMAINDER: val = Math.IEEEremainder(params[1], params[0]); break; case HYPOT: val = Math.hypot(params[1], params[0]); break; case MAX: val = Math.max(params[1], params[0]); break; case MIN: val = Math.min(params[1], params[0]); break; case POW: val = Math.pow(params[1], params[0]); break; case ROUNDTO: val = EasyFormat.roundTo(params[1], (int)params[0]); break; } } else if (numParams == 3) { switch (ref.subType) { // this function is for testing purposes only. case USER: val = 100 * params[2] + 10 * params[1] + params[0]; break; } } } catch (NegativeArraySizeException e) { reportError("Error: Invalid parameter list for '" + functionName + "' which must have " + neededParams + " parameter(s)"); } catch (Exception e) { reportError("Calculation error while evaluating: " + functionName); } return val; } // end calculateFunction /** * ## Calculates the user function. * @param params - an array containing the values of the parameters for user. * @return - the value of the user function for the given parameters. */ protected double calculateUser(double [] params) throws Exception{ for (int i = 0; i < numParams[USER]; i++) { if (debug) System.out.println("Params["+i+"] = " + params[i]); values[i+26] = params[i]; } return evaluateSub(definedFunction); } // ********************* evaluate(ExprTree) ****************** /** * ## Evaluates the specified tree using the current values for the * variables 'a', 'b', ..., 'z'. * (This is a simple extension of ExprEvaluator.evaluate(). *

* Use set... functions to assign values to variables before calling this * methods. * @param tree - the ExprTree to be evaluated. * @return - the value of the expression which generated the ExprTree. * @throws Exception - if a function cannot be evaluated or has the wrong * number of parameters. * @see #setX * @see #setY * @see #setT * @see #setValueOf * @see #setValues * @see #evaluate */ public double evaluate(ExprTree tree) throws Exception { root = tree.getTree(); return evaluate(); } // end 1 parameter evaluate // ************************** convertToRect *********************** /** * ## Converts polar coordinates into rectangular coordinates. The results are * stored in the variables "x" and "y". The other 24 variables remain * unchanged. The getX and getY methods can be used to retrieve the values. * @param r - the radius in polar coordinates. * @param theta - the angle in polar coordinates. It is measured in radians. * @see #getX * @see #getY * @see #getValueOf */ public void convertToRect(double r, double theta) { values['x' - 'a'] = r * Math.cos(theta); values['y' - 'a'] = r * Math.sin(theta); } // end convertToRect // ***************************** toString ***************************** /** * Output the tree structure -- used in testing/debugging. * Source: A modification of showStructure() in Data Structures * *** in C++ by Roberge. * @return String repression of the expression tree. */ public String toString () { // Outputs the keys in a binary search tree. The tree is output // rotated counterclockwise 90 degrees from its conventional // orientation using a "reverse" inorder traversal. This operation is // intended for testing and debugging purposes only. if ( root == null ) return("Empty tree\n"); else return "\n" + toStringSub(root,1) + "\n"; } // end toString private String toStringSub ( BinaryNode p, int level) { // Recursive partner of the toString() function. Outputs the // subtree whose root node is pointed to by p. Parameter level is the // level of this node within the tree. String s=""; int j; // Loop counter if ( p != null) { s = toStringSub(p.right,level+1); // Output right subtree for ( j = 0 ; j < level ; j++ ) // Tab over to level s += " "; s += " " + p.element; // Output element if ( ( p.left != null ) && // Output "connector" ( p.right != null ) ) s += "<"; else if ( p.right != null ) s += "/"; else if ( p.left != null ) s += "\\"; s += '\n' + toStringSub(p.left,level+1); // Output left subtree } return s; } // end toStringSub /** * Prints the error message to the standard output and then throws * an exception again using the message. * @param msg - the error message */ protected void reportError(String msg) throws Exception { System.out.println("Error: " + msg); throw new Exception("Error: " + msg); } // end reportError /** * ## roundTo(x, num) rounds x to num decimal places where num less * than 0 is permitted. For example, roundTo(12345.6,-2) = 12300. * It is the same as EasyFormat.roundTo. For output, consider * EasyFormat.format(double x, int num) instead which produces a * string instead of a double. */ public static double roundTo(double x, double num) { return EasyFormat.roundTo(x, (int)num); } // roundTo } // end of ExprEvaluator