How evaluations works

General steps

The method Scanner.start() parses the input string and returns an object:

Scanner scan = new Scanner("100");
HObject eval_integer = scan.start();

In the above case eval_integer is an instance of HInteger.

Scanner scan = new Scanner("1+a+b+f(x)+Sin(0)");
HObject eval_function = scan.start();

In this case eval_function is an instance of HFunction.

Now the system must evaluate the given object. (in the following we will take eval_function).

HObject result = C.EV(eval_function);

With the toString() method we can turn back the result in a user readable String:

result.toString();

Evaluation of objects

In the method com.hartmath.lib.C.EV() the method com.hartmath.lib.C.ELoop() will be called. In general all following (recursive) evaluation steps can return the special value null, to indicate that the input-expression is the same as the output-expression. So you always have to test if an evaluation function returns null. null means "no result" e.g. if you evaluate Sin(x) you get the result null

return ((result = ELoop(eval_function)) == null) ? eval_function : result;

In the evaluation loop the object will evaluate through the method HObject.evaluate() until HObject.evaluate() returns null.

    static public HObject ELoop(HObject obj)
    {
      ...
      HObject   result = obj.evaluate();
      if (result == null) {
         .....
         return null;
      }
      ...
      HObject   temp = result.evaluate();
      iterationCounter = 0;
      while (temp != null) {
         ...
         result = temp;
         temp = result.evaluate();
      }
      ...
      return result;
    }
    

As an example for an atomic object (e.g. HDouble, HDoubleComplex, HInteger, HFraction, HComplex, HString, HSymbol or HPattern object) take a look at the com.hartmath.expression.HInteger.evaluate() method:

    public HObject evaluate() {
      if (C.numericFlag) {
        return new HDouble(doubleValue());
      }
      return null;
    }
    

Only in numeric mode (C.numericFlag==true) the method returns a result (e.g. converts the integer to a double value). (You can start a numeric evaluation with the method com.hartmath.lib.C.NEV() )


Step into an evaluation of a HFunction

Now we take a look at the com.hartmath.expression.HFunction.evaluate() method:

    public HObject evaluate()
    {
      return symbol.evaluateFunction(this);
    }
    

Every HFunction has a variable which is called the "header symbol".
Example: if you have a function Sin(x) the "head" of the function is the HSymbol com.hartmath.lib.C.Sin.
The above call jumps into the HSymbol.evaluateFunction() method:

    public HObject evaluateFunction(HFunction f)
    {
      HObject   result = null;
      if ((attr & HSymbol.FASTCALL) == HSymbol.FASTCALL)
      {
         return functionEval.evaluate(f);
      }
      ...
    

In the int variable attr all attributes (e.g. FASTCALL, ONEIDENTITY, FLAT, ORDERLESS, LISTABLE,...) of this "header symbol" are stored as single bitflags.
A lot of system functions, like Do, While, For ... set the flag "FASTCALL", to shorten the evaluation procedure.

      ...
      if ((result = C.ELoop(this)) != null)
      {
         return f.apply(result);
      }
      ...
    

If the header can be evaluated it will be "applied" on the given funtion.
Example: suppose you have defined f as a pure function
f=(#+1)&
and now you evaluate the following input:
f(x)
you get the result
x+1

So the call to the HFunction.apply() method will look like this in pseudo code:

"f(x)".apply("(#+1)&");

In the following source code of the HSymbol.evaluateFunction() method, the program gets the number of arguments of the function (int variable sz). For functions, which have only 1 or 2 arguments special evaluation functions are called for a better performance of the system. The HFunction.evaluateAttributtes() method evaluates all arguments of the function recursively (with method com.hartmath.lib.C.EV()) and takes into account the attributes: ONEIDENTITY, FLAT, LISTABLE, ORDERLESS, HOLDFIRST, HOLDREST, HOLDALL.

      ...
      int       sz = f.size();
      if (sz >= 1)
      {
         if (sz == 1)
         {
            result = f.evaluateAttributes1();
         }
         else
         {
            if (sz == 2)
            {
               result = f.evaluateAttributes2();
            }
            else
            {
               result = f.evaluateAttributes();
            }
         }
      }

      if (result != null)
      {
         return result;
      }
      ...
    

Some "header symbols" have the attribute BUITLINFIRST so their built in functions will be called before the evaluation of the user defined rules:

      ...
      if ((attr & HSymbol.BUILTINFIRST) == HSymbol.BUILTINFIRST)
      {
         if ((result = functionEval.evaluate(f)) != null)
         {
            return result;
         }
      }
      if (sz > 0)
      {
         HObject        elem;
      ...
    

Now all user defined "UpRules" will be evaluated. These rules are all defined with the operators ^= or ^:= which assiciate a rule with the headers of the arguments
Example:
Infinity*Infinity^(-1)^:=Throw(InfinityError,Multiply);

      ...
         if ((HSymbol.FLAT & attr) != HSymbol.FLAT)
         {
            elem = f.get(0);

            if (elem instanceof HSymbol)
            {
               result = ((HSymbol) elem).getUpRule(f);
            }
            else
            {
               result = elem.head().getUpRule(f);
            }

            if (result != null)
            {
               return result;
            }
         }
         else
         {      // associative
            HArrayList       arr = new HArrayList();

            elem = f.get(0);

            HObject      last;

            if (elem instanceof HSymbol)
            {
               last = elem;

               if (!((HSymbol) last).isEmptyUpRule())
               {
                  arr.add(last);
               }
            }
            else
            {
               last = elem.head();

               if (!((HSymbol) last).isEmptyUpRule())
               {
                  arr.add(last);
               }
            }
            for (int i = 1; i < sz; i++)
            {
               elem = f.get(i);

               if (elem instanceof HSymbol)
               {
                  if (elem == last)
                  {
                     continue;
                  }

                  last = elem;

                  if (((HSymbol) last).isEmptyUpRule())
                  {
                     continue;
                  }

                  arr.add(last);
               }
               else
               {
                  if (elem.head() == last)
                  {
                     continue;
                  }

                  last = elem.head();

                  if (((HSymbol) last).isEmptyUpRule())
                  {
                     continue;
                  }

                  arr.add(last);
               }
            }
            arr.sort();
            if (arr.size() > 0)
            {
               last = arr.get(0);
               if ((result = ((HSymbol) last).getUpRule(f)) != null)
               {
                  return result;
               }

               for (int i = 1; i < arr.size(); i++)
               {
                  if (!arr.get(i).equals(last))
                  {
                     last = arr.get(i);
                     if ((result = ((HSymbol) last).getUpRule(f)) != null)
                     {
                        return result;
                     }
                  }
               }
            }
         }
      }
      ...
    

Now all user defined "DownRules" will be evaluated.
These Rules are all defined with the operators = or :=
Examples:
f(x_+y_):=(x*y)/(x+y) or
g(2,3)=100

      ...
      if ((result = getDownRule(f)) != null)
      {
         return result;
      }
      ...
    

Now all "hard coded java functions" will be evaluated:
Normally these functiions are evaluated in the packages:
com.hartmath.initial or
com.hartmath.loadable

A simple "builtin function" is defined for example in the class:
com.hartmath.loadable.EExec
for the "function symbol"
com.hartmath.lib.C.Exec

      ...
      if ((attr & HSymbol.BUILTINFIRST) != HSymbol.BUILTINFIRST)
      {
         return functionEval.evaluate(f);
      }
      return null;
    }