/**
   * Return a set of the variables in the supplied expression. Note: Substitutions which are in the
   * constant table are not included.
   */
  public Set<String> getVariablesWithin(String exp) {
    Set<String> all = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
    String add = null;

    if (separators == null) {
      StringBuilder sep = new StringBuilder(10);
      for (char chr = 0; chr < operators.length; chr++) {
        if (operators[chr] != null && !operators[chr].internal) {
          sep.append(chr);
        }
      }
      sep.append("()");
      separators = sep.toString();
    }

    for (StringTokenizer tkz = new StringTokenizer(exp, separators, true); tkz.hasMoreTokens(); ) {
      String tkn = tkz.nextToken().trim();

      if (tkn.length() != 0 && Character.isLetter(tkn.charAt(0))) {
        add = tkn;
      } else if (tkn.length() == 1 && tkn.charAt(0) == '(') {
        add = null;
      } else if (add != null && !constants.containsKey(add)) {
        all.add(add);
      }
    }
    if (add != null && !constants.containsKey(add)) {
      all.add(add);
    }
    return all;
  }
 /**
  * Set a named constant (constants names are not case-sensitive). Constants are like variables but
  * are not cleared by clear(). Variables of the same name have precedence over constants.
  */
 public MathEval setConstant(String nam, Double val) {
   if (constants.get(nam) != null) {
     throw new IllegalArgumentException("Constants may not be redefined");
   }
   validateName(nam);
   constants.put(nam, val);
   return this;
 }
 /**
  * Set a named variable (variables names are not case-sensitive). If the value is null, the
  * variable is removed.
  */
 public MathEval setVariable(String nam, Double val) {
   validateName(nam);
   if (val == null) {
     variables.remove(nam);
   } else {
     variables.put(nam, val);
   }
   return this;
 }
  private double doNamedVal(int beg, int end) {
    while (beg < end && Character.isWhitespace(expression.charAt(end))) {
      end--;
    } // since a letter triggers a named value, this can never reduce to beg==end

    String nam = expression.substring(beg, (end + 1));
    Double val;

    if ((val = constants.get(nam)) != null) {
      return val.doubleValue();
    } else if ((val = variables.get(nam)) != null) {
      isConstant = false;
      return val.doubleValue();
    } else if (relaxed) {
      isConstant = false;
      return 0.0;
    }

    throw exception(beg, "Unrecognized constant or variable \"" + nam + "\"");
  }
  private double doFunction(int beg, int end) {
    int argbeg;

    for (argbeg = beg; argbeg <= end && expression.charAt(argbeg) != '('; argbeg++) {;
    }

    String fncnam = expression.substring(beg, argbeg).trim();
    ArgParser fncargs = new ArgParser(argbeg, end);
    FunctionHandler fnchdl = null;

    try {
      if ((fnchdl = pureFunctions.get(fncnam)) != null) {
        return fnchdl.evaluateFunction(fncnam, fncargs);
      } else if ((fnchdl = impureFunctions.get(fncnam)) != null) {
        isConstant = false; // impure functions cannot be guaranteed to be constant
        return fnchdl.evaluateFunction(fncnam, fncargs);
      }
      fncargs = null; // suppress check for too many fncargs
    } catch (ArithmeticException thr) {
      fncargs = null;
      throw thr;
    } catch (NoSuchMethodError thr) {
      fncargs = null;
      throw exception(beg, "Function not supported in this JVM: \"" + fncnam + "\"");
    } catch (UnsupportedOperationException thr) {
      fncargs = null;
      throw exception(beg, thr.getMessage());
    } catch (Throwable thr) {
      fncargs = null;
      throw exception(beg, "Unexpected exception parsing function arguments", thr);
    } finally {
      if (fncargs != null) {
        if (fncargs.hasNext()) {
          throw exception(fncargs.getIndex(), "Function has too many arguments");
        }
        offset = fncargs.getIndex();
      }
    }
    throw exception(beg, "Function \"" + fncnam + "\" not recognized");
  }
  /**
   * Create a math evaluator with the same constants, variables, function handlers and relaxation
   * setting as the supplied evaluator.
   */
  public MathEval(MathEval oth) {
    super();

    operators = oth.operators;

    constants = new TreeMap<String, Double>(String.CASE_INSENSITIVE_ORDER);
    constants.putAll(oth.constants);

    variables = new TreeMap<String, Double>(String.CASE_INSENSITIVE_ORDER);
    variables.putAll(oth.variables);

    pureFunctions = new TreeMap<String, FunctionHandler>(String.CASE_INSENSITIVE_ORDER);
    impureFunctions = new TreeMap<String, FunctionHandler>(String.CASE_INSENSITIVE_ORDER);
    pureFunctions.putAll(oth.pureFunctions);
    impureFunctions.putAll(oth.impureFunctions);

    relaxed = oth.relaxed;
    separators = oth.separators;

    offset = 0;
    isConstant = false;
  }
 /**
  * Set a function handler for the specific named function optionally tagging the function as
  * impure, replacing any existing handler for the given name; if the handler is null the function
  * handler is removed.
  *
  * <p>Pure functions have results which depend purely on their arguments; given constant arguments
  * they will have a constant result. Impure functions are rare.
  */
 public MathEval setFunctionHandler(String nam, FunctionHandler hdl, boolean impure) {
   validateName(nam);
   if (hdl == null) {
     pureFunctions.remove(nam);
     impureFunctions.remove(nam);
   } else if (impure) {
     pureFunctions.remove(nam);
     impureFunctions.put(nam, hdl);
   } else {
     pureFunctions.put(nam, hdl);
     impureFunctions.remove(nam);
   }
   return this;
 }
 /**
  * Clear all variables prefixed by the supplied string followed by a dot, such that they match
  * "Prefix.xxx".
  */
 public MathEval clear(String pfx) {
   variables.subMap((pfx + "."), (pfx + "." + Character.MAX_VALUE)).clear();
   return this;
 }
 /** Clear all variables (constants are not affected). */
 public MathEval clear() {
   variables.clear();
   return this;
 }
  /** Set a named variable (variables names are not case-sensitive). */
  public double getVariable(String nam) {
    Double val = variables.get(nam);

    return (val == null ? 0 : val.doubleValue());
  }
  /**
   * Set a named constant (constant names are not case-sensitive). Constants are like variables but
   * are not cleared by clear(). Variables of the same name have precedence over constants.
   */
  public double getConstant(String nam) {
    Double val = constants.get(nam);

    return (val == null ? 0 : val.doubleValue());
  }