/* {{{ License.
 * 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 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.
 */ //}}}
// :indentSize=4:lineSeparator=\n:noTabs=false:tabSize=4:folding=explicit:collapseFolds=0:
package org.mathpiper.lisp;

import org.mathpiper.lisp.collections.OperatorMap;
import org.mathpiper.lisp.cons.ConsTraverser;
import org.mathpiper.lisp.cons.SublistCons;
import org.mathpiper.lisp.cons.AtomCons;
import org.mathpiper.lisp.cons.ConsPointer;
import org.mathpiper.lisp.cons.Cons;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.mathpiper.io.MathPiperInputStream;
import org.mathpiper.io.MathPiperOutputStream;
import org.mathpiper.exceptions.EvaluationException;
import org.mathpiper.io.InputStatus;
import org.mathpiper.builtin.BigNumber;
import org.mathpiper.builtin.BuiltinFunction;
import org.mathpiper.builtin.JavaObject;
import org.mathpiper.io.InputDirectories;
import org.mathpiper.lisp.behaviours.Substitute;
import org.mathpiper.lisp.tokenizers.MathPiperTokenizer;
import org.mathpiper.lisp.rulebases.MultipleArityRulebase;
import org.mathpiper.lisp.printers.MathPiperPrinter;
import org.mathpiper.lisp.parsers.MathPiperParser;
import org.mathpiper.io.JarFileInputStream;
import org.mathpiper.io.StandardFileInputStream;
import org.mathpiper.io.StringInputStream;
import org.mathpiper.io.StringOutput;
import org.mathpiper.io.StringOutputStream;
import org.mathpiper.lisp.behaviours.BackQuoteSubstitute;
import org.mathpiper.lisp.cons.BuiltinObjectCons;
import org.mathpiper.lisp.cons.NumberCons;
import org.mathpiper.lisp.parametermatchers.ParametersPatternMatcher;
import org.mathpiper.lisp.parametermatchers.PatternParameterMatcher;
import org.mathpiper.lisp.parsers.Parser;
import org.mathpiper.lisp.printers.LispPrinter;
import org.mathpiper.lisp.rulebases.Rule;
import org.mathpiper.lisp.rulebases.ParameterName;
import org.mathpiper.lisp.rulebases.MacroRulebase;
import org.mathpiper.lisp.rulebases.PatternRule;
import org.mathpiper.lisp.rulebases.SingleArityRulebase;

public class Utility {

    public static final int ATOM = 1;
    public static final int NUMBER = 2;
    public static final int SUBLIST = 3;
    public static final int OBJECT = 4;
    static int log2_table_size = 32;    // A lookup table of Ln(n)/Ln(2) for n = 1 .. 32.
    // With this we don't have to use math.h if all we need is to convert the number of digits from one base to another. This is also faster.
    // Generated by: PrintList(N(Ln(1 .. 32)/Ln(2)), ",") at getPrecision 40.
    static double log2_table[] = {
        0.,
        1.,
        1.5849625007211561814537389439478165087598,
        2.,
        2.3219280948873623478703194294893901758648,
        2.5849625007211561814537389439478165087598,
        2.807354922057604107441969317231830808641,
        3.,
        3.1699250014423123629074778878956330175196,
        3.3219280948873623478703194294893901758648,
        3.4594316186372972561993630467257929587032,
        3.5849625007211561814537389439478165087598,
        3.7004397181410921603968126542566947336284,
        3.807354922057604107441969317231830808641,
        3.9068905956085185293240583734372066846246,
        4.,
        4.0874628412503394082540660108104043540112,
        4.1699250014423123629074778878956330175196,
        4.2479275134435854937935194229068344226935,
        4.3219280948873623478703194294893901758648,
        4.3923174227787602888957082611796473174008,
        4.4594316186372972561993630467257929587032,
        4.5235619560570128722941482441626688444988,
        4.5849625007211561814537389439478165087598,
        4.6438561897747246957406388589787803517296,
        4.7004397181410921603968126542566947336284,
        4.7548875021634685443612168318434495262794,
        4.807354922057604107441969317231830808641,
        4.8579809951275721207197733246279847624768,
        4.9068905956085185293240583734372066846246,
        4.9541963103868752088061235991755544235489,
        5.
    };
    public static java.util.zip.ZipFile zipFile = null;
    public static String scriptsPath = null;

    public static boolean isNumber(String ptr, boolean aAllowFloat) {

        if (ptr.length() == 0) {
            return false;
        }//end if.

        int pos = 0;
        if (ptr.charAt(pos) == '-' || ptr.charAt(pos) == '+') {
            pos++;
        }
        int nrDigits = 0;
        int index = 0;
        if (pos + index == ptr.length()) {
            return false;
        }
        while (ptr.charAt(pos + index) >= '0' && ptr.charAt(pos + index) <= '9') {
            nrDigits++;
            index++;
            if (pos + index == ptr.length()) {
                return true;
            }
        }
        if (ptr.charAt(pos + index) == '.') {
            if (!aAllowFloat) {
                return false;
            }
            index++;
            if (pos + index == ptr.length()) {
                return true;
            }
            while (ptr.charAt(pos + index) >= '0' && ptr.charAt(pos + index) <= '9') {
                nrDigits++;
                index++;
                if (pos + index == ptr.length()) {
                    return true;
                }
            }
        }
        if (nrDigits == 0) {
            return false;
        }
        if (ptr.charAt(pos + index) == 'e' || ptr.charAt(pos + index) == 'E') {
            if (!aAllowFloat) {
                return false;
            }
            if (!BigNumber.numericSupportForMantissa()) {
                return false;
            }
            index++;
            if (pos + index == ptr.length()) {
                return true;
            }
            if (ptr.charAt(pos + index) == '-' || ptr.charAt(pos + index) == '+') {
                index++;
            }
            while (ptr.charAt(pos + index) >= '0' && ptr.charAt(pos + index) <= '9') {
                index++;
                if (pos + index == ptr.length()) {
                    return true;
                }
            }
        }
        if (ptr.length() != (pos + index)) {
            return false;
        }
        return true;
    }

    public static int listLength(Environment aEnvironment, int aStackTop, ConsPointer aOriginal) throws Exception {
        ConsPointer consTraverser = new ConsPointer( aOriginal.getCons());
        int length = 0;
        while (consTraverser.getCons() != null) {
            consTraverser.goNext(aStackTop, aEnvironment);
            length++;
        }
        return length;
    }

    public static void reverseList(Environment aEnvironment, ConsPointer aResult, ConsPointer aOriginal) {
        //ConsPointer iter = new ConsPointer(aOriginal);
        ConsPointer iter = new ConsPointer();
        iter.setCons(aOriginal.getCons());
        ConsPointer previous = new ConsPointer();
        ConsPointer tail = new ConsPointer();
        tail.setCons(aOriginal.getCons());

        while (iter.getCons() != null) {
            tail.setCons(iter.cdr().getCons());
            iter.cdr().setCons(previous.getCons());
            previous.setCons(iter.getCons());
            iter.setCons(tail.getCons());
        }
        aResult.setCons(previous.getCons());
    }

    public static void returnUnEvaluated(int aStackTop, ConsPointer aResult, ConsPointer aArguments, Environment aEnvironment) throws Exception {
        ConsPointer full = new ConsPointer();
        full.setCons(aArguments.getCons().copy(aEnvironment, false));
        aResult.setCons(SublistCons.getInstance(aEnvironment, full.getCons()));

        ConsTraverser consTraverser = new ConsTraverser(aEnvironment, aArguments);
        consTraverser.goNext(aStackTop);

        while (consTraverser.getCons() != null) {
            ConsPointer next = new ConsPointer();
            aEnvironment.iLispExpressionEvaluator.evaluate(aEnvironment, aStackTop, next, consTraverser.getPointer());
            full.cdr().setCons(next.getCons());
            full.setCons(next.getCons());
            consTraverser.goNext(aStackTop);
        }
        full.cdr().setCons(null);
    }

    //Evaluate a function which is in string form.
    public static void applyString(Environment aEnvironment, int aStackTop, ConsPointer aResult, String aOperator, ConsPointer aArgs) throws Exception {
        LispError.check(aEnvironment, aStackTop, isString(aOperator), LispError.NOT_A_STRING, "INTERNAL");

        Cons head = AtomCons.getInstance(aEnvironment, aStackTop, getSymbolName(aEnvironment, aOperator));
        head.cdr().setCons(aArgs.getCons());
        ConsPointer body = new ConsPointer();
        body.setCons(SublistCons.getInstance(aEnvironment, head));
        aEnvironment.iLispExpressionEvaluator.evaluate(aEnvironment, aStackTop, aResult, body);
    }

    public static void applyPure(int aStackTop, ConsPointer oper, ConsPointer args2, ConsPointer aResult, Environment aEnvironment) throws Exception {
        LispError.check(aEnvironment, aStackTop, oper.car() instanceof ConsPointer, LispError.INVALID_ARGUMENT, "INTERNAL");
        LispError.check(aEnvironment, aStackTop, ((ConsPointer) oper.car()).getCons() != null, LispError.INVALID_ARGUMENT, "INTERNAL");
        ConsPointer oper2 = new ConsPointer();
        oper2.setCons(((ConsPointer) oper.car()).cdr().getCons());
        LispError.check(aEnvironment, aStackTop, oper2.getCons() != null, LispError.INVALID_ARGUMENT, "INTERNAL");

        ConsPointer body = new ConsPointer();
        body.setCons(oper2.cdr().getCons());
        LispError.check(aEnvironment, aStackTop, body.getCons() != null, LispError.INVALID_ARGUMENT, "INTERNAL");

        LispError.check(aEnvironment, aStackTop, oper2.car() instanceof ConsPointer, LispError.INVALID_ARGUMENT, "INTERNAL");
        LispError.check(aEnvironment, aStackTop, ((ConsPointer) oper2.car()).getCons() != null, LispError.INVALID_ARGUMENT, "INTERNAL");
        oper2.setCons(((ConsPointer) oper2.car()).cdr().getCons());

        aEnvironment.pushLocalFrame(false, "Pure");
        try {
            while (oper2.getCons() != null) {
                LispError.check(aEnvironment, aStackTop, args2.getCons() != null, LispError.INVALID_ARGUMENT, "INTERNAL");

                String var = (String) oper2.car();
                LispError.check(aEnvironment, aStackTop, var != null, LispError.INVALID_ARGUMENT, "INTERNAL");
                ConsPointer newly = new ConsPointer();
                newly.setCons(args2.getCons().copy(aEnvironment, false));
                aEnvironment.newLocalVariable(var, newly.getCons(), aStackTop);
                oper2.setCons(oper2.cdr().getCons());
                args2.setCons(args2.cdr().getCons());
            }
            LispError.check(aEnvironment, aStackTop, args2.getCons() == null, LispError.INVALID_ARGUMENT, "INTERNAL");
            aEnvironment.iLispExpressionEvaluator.evaluate(aEnvironment, aStackTop, aResult, body);
        } catch (EvaluationException e) {
            throw e;
        } finally {
            aEnvironment.popLocalFrame(aStackTop);
        }

    }

    public static void putTrueInPointer(Environment aEnvironment, ConsPointer aResult) throws Exception {
        aResult.setCons(aEnvironment.iTrueAtom.copy(aEnvironment, false));
    }

    public static void putFalseInPointer(Environment aEnvironment, ConsPointer aResult) throws Exception {
        aResult.setCons(aEnvironment.iFalseAtom.copy(aEnvironment, false));
    }

    public static void putBooleanInPointer(Environment aEnvironment, ConsPointer aResult, boolean aValue) throws Exception {
        if (aValue) {
            putTrueInPointer(aEnvironment, aResult);
        } else {
            putFalseInPointer(aEnvironment, aResult);
        }
    }

    public static void nth(Environment aEnvironment, int aStackTop, ConsPointer aResult, ConsPointer aArg, int n) throws Exception {
        LispError.check(aEnvironment, aStackTop, aArg.getCons() != null, LispError.INVALID_ARGUMENT, "INTERNAL");
        LispError.check(aEnvironment, aStackTop, aArg.car() instanceof ConsPointer, LispError.INVALID_ARGUMENT, "INTERNAL");
        LispError.check(aEnvironment, aStackTop, n >= 0, LispError.INVALID_ARGUMENT, "INTERNAL");
        ConsTraverser consTraverser = new ConsTraverser(aEnvironment, (ConsPointer) aArg.car());

        while (n > 0) {
            LispError.check(aEnvironment, aStackTop, consTraverser.getCons() != null, LispError.INVALID_ARGUMENT, "INTERNAL");
            consTraverser.goNext(aStackTop);
            n--;
        }
        LispError.check(aEnvironment, aStackTop, consTraverser.getCons() != null, LispError.INVALID_ARGUMENT, "INTERNAL");
        aResult.setCons(consTraverser.getCons().copy(aEnvironment, false));
    }

    public static void tail(Environment aEnvironment, int aStackTop, ConsPointer aResult, ConsPointer aArg) throws Exception {
        LispError.check(aEnvironment, aStackTop, aArg.getCons() != null, LispError.INVALID_ARGUMENT, "INTERNAL");
        LispError.check(aEnvironment, aStackTop, aArg.car() instanceof ConsPointer, LispError.INVALID_ARGUMENT, "INTERNAL");

        ConsPointer iter = (ConsPointer) aArg.car();

        LispError.check(aEnvironment, aStackTop, iter.getCons() != null, LispError.INVALID_ARGUMENT, "INTERNAL");
        aResult.setCons(SublistCons.getInstance(aEnvironment, iter.cdr().getCons()));
    }


    public static boolean isTrue(Environment aEnvironment, ConsPointer aExpression, int aStackTop) throws Exception {
        LispError.lispAssert(aExpression.getCons() != null, aEnvironment, aStackTop);

        //return aExpression.car() == aEnvironment.iTrueAtom.car();
        return aExpression.car() instanceof String && ((String) aExpression.car()).equals(aEnvironment.iTrueString);

        /* Code which returns True for everything except False and {};
        String expressionString = aExpression.car();

        //return expressionString == aEnvironment.iTrueString;

        if (expressionString == aEnvironment.iTrueString) {
        return true;
        } else if (isSublist(aExpression)) {
        if (listLength(aExpression.car()) == 1) {
        //Empty list.
        return false;
        } else {
        //Non-empty list.
        return true;
        }
        } else {
        //Anything other than False returns true.
        return expressionString != null && expressionString != aEnvironment.iFalseString;
        }*/

    }//end method.
    public static boolean isFalse(Environment aEnvironment, ConsPointer aExpression, int aStackTop) throws Exception {
        LispError.lispAssert(aExpression.getCons() != null, aEnvironment, aStackTop);
        return aExpression.car() instanceof String && ((String) aExpression.car()).equals(aEnvironment.iFalseString);

        /* Code which returns True for everything except False and {};
        return aExpression.car() == aEnvironment.iFalseString || (isSublist(aExpression) && (listLength(aExpression.car()) == 1));
         */
    }

    public static String getSymbolName(Environment aEnvironment, String aSymbol) {
        if (aSymbol.charAt(0) == '\"') {
            return aEnvironment.getTokenHash().lookUpUnStringify(aSymbol);
        } else {
            return (String) aEnvironment.getTokenHash().lookUp(aSymbol);
        }
    }

    public static boolean isSublist(ConsPointer aPtr) throws Exception {
        /**
         * todo:tk: I am currently not sure why non nested lists are not supported in Yacas.
         */
        if (aPtr.getCons() == null) {
            return false;
        }
        if (!(aPtr.car() instanceof ConsPointer)) {
            return false;
        }
        if (((ConsPointer) aPtr.car()).getCons() == null) {
            return false;
            //TODO this StrEqual is far from perfect. We could pass in a Environment object...
        }
        if (!((ConsPointer) aPtr.car()).car().equals("List")) {
            return false;
        }
        return true;

    }//end method.

    public static boolean isList(ConsPointer aPtr) throws Exception {
        /**
         * todo:tk: I am currently not sure why non nested lists are not supported in Yacas.
         */
        if (aPtr.getCons() == null) {
            return false;
        }

        if (aPtr.type() == Utility.ATOM) {
            if (((String) aPtr.car()).equalsIgnoreCase("List")) {
                return true;
            }//end if.
        }//end if.

        if (isSublist(aPtr)) {
            return true;
        }//end if.
        return false;

    }//end method.

    public static boolean isNestedList(Environment aEnvironment, int aStackTop, ConsPointer clientListPointer) throws Exception {

        ConsPointer listPointer = new ConsPointer( clientListPointer.getCons());

        listPointer.goNext(aStackTop, aEnvironment); //Strip List tag.

        while (listPointer.getCons() != null) {
            if (listPointer.car() instanceof ConsPointer && isList((ConsPointer) listPointer.car())) {
                listPointer.goNext(aStackTop, aEnvironment);
            } else {
                return false;
            }
        }//end while.
        return true;
    }//end method.

    public static Map optionsListToJavaMap(Environment aEnvironment, int aStackTop, ConsPointer argumentsPointer, Map defaultOptions) throws Exception {

        Map userOptions = (Map) ((HashMap) defaultOptions).clone();

        while (argumentsPointer.getCons() != null) {
            //Obtain -> operator.
            ConsPointer optionPointer = (ConsPointer) argumentsPointer.car();
            LispError.check(aEnvironment, aStackTop, optionPointer.type() == Utility.ATOM, LispError.INVALID_ARGUMENT, "INTERNAL");
            String operator = (String) optionPointer.car();
            LispError.check(aEnvironment, aStackTop, operator.equals("->"), LispError.INVALID_ARGUMENT, "INTERNAL");

            //Obtain key.
            optionPointer.goNext(aStackTop, aEnvironment);
            LispError.check(aEnvironment, aStackTop, optionPointer.type() == Utility.ATOM, LispError.INVALID_ARGUMENT, "INTERNAL");
            String key = (String) optionPointer.car();
            key = Utility.stripEndQuotesIfPresent(aEnvironment, aStackTop, key);

            //Obtain value.
            optionPointer.goNext(aStackTop, aEnvironment);
            LispError.check(aEnvironment, aStackTop, optionPointer.type() == Utility.ATOM || optionPointer.type() == Utility.NUMBER, LispError.INVALID_ARGUMENT, "INTERNAL");
            if (optionPointer.type() == Utility.ATOM) {
                String value = (String) optionPointer.car();
                value = Utility.stripEndQuotesIfPresent(aEnvironment, aStackTop, value);
                if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
                    userOptions.put(key, Boolean.parseBoolean(value));
                } else {
                    userOptions.put(key, value);
                }//ende else.
            } else //Number
            {
                NumberCons numberCons = (NumberCons) optionPointer.getCons();
                BigNumber bigNumber = (BigNumber) numberCons.getNumber(10, aEnvironment);
                Double value = bigNumber.toDouble();
                userOptions.put(key, value);
            }//end if/else.



            argumentsPointer.goNext(aStackTop, aEnvironment);

        }//end while

        return userOptions;
    }//end method.

    public static boolean isString(Object aOriginal) {

        if (!(aOriginal instanceof String)) {
            return false;
        }//end if.

        String stringVersion = (String) aOriginal;

        if (stringVersion != null) {
            if (stringVersion.charAt(0) == '\"') {
                if (stringVersion.charAt(stringVersion.length() - 1) == '\"') {
                    return true;
                }
            }
        }
        return false;
    }//end method


    public static String stripEndDollarSigns(String aOriginal) throws Exception {
        //If there are not dollar signs on both ends of the string then return without any changes.
        aOriginal = aOriginal.trim();
        if (aOriginal.startsWith("$") && aOriginal.endsWith("$")) {
            aOriginal = aOriginal.substring(1, aOriginal.length());
            aOriginal = aOriginal.substring(0, aOriginal.length() - 1);
        }//end if.

        return aOriginal;
    }//end method.

    public static void not(int aStackTop, ConsPointer aResult, Environment aEnvironment, ConsPointer aExpression) throws Exception {
        if (isTrue(aEnvironment, aExpression, aStackTop)) {
            putFalseInPointer(aEnvironment, aResult);
        } else {
            LispError.check(aEnvironment, aStackTop, isFalse(aEnvironment, aExpression, aStackTop), LispError.INVALID_ARGUMENT, "INTERNAL");
            putTrueInPointer(aEnvironment, aResult);
        }
    }

    public static void flatCopy(Environment aEnvironment, int aStackTop, ConsPointer aResult, ConsPointer aOriginal) throws Exception {
        ConsTraverser orig = new ConsTraverser(aEnvironment, aOriginal);
        ConsTraverser res = new ConsTraverser(aEnvironment, aResult);

        while (orig.getCons() != null) {
            res.getPointer().setCons(orig.getCons().copy(aEnvironment, false));
            orig.goNext(aStackTop);
            res.goNext(aStackTop);
        }
    }

    public static boolean equals(Environment aEnvironment, int aStackTop, ConsPointer aExpression1, ConsPointer aExpression2) throws Exception {
        // Handle pointers to same, or null
        if (aExpression1.getCons() == aExpression2.getCons()) {
            return true;
        }
        //LispError.check(aExpression1.type().equals("Number"), LispError.INVALID_ARGUMENT);
        //LispError.check(aExpression2.type().equals("Number"), LispError.INVALID_ARGUMENT);
        BigNumber n1 = (BigNumber) aExpression1.getCons().getNumber(aEnvironment.getPrecision(), aEnvironment);
        BigNumber n2 = (BigNumber) aExpression2.getCons().getNumber(aEnvironment.getPrecision(), aEnvironment);
        if (!(n1 == null && n2 == null)) {
            if (n1 == n2) {
                return true;
            }
            if (n1 == null) {
                return false;
            }
            if (n2 == null) {
                return false;
            }
            if (n1.equals(n2)) {
                return true;
            }
            return false;
        }

        //Pointers to strings should be the same
        if ((aExpression1.car() instanceof String) && (aExpression2.car() instanceof String)) {
            if (aExpression1.car() != aExpression2.car()) {
                return false;
            }
        }


        // Handle same sublists, or null
        if (aExpression1.car() == aExpression2.car()) {
            return true;
        }

        // Now check the sublists
        if (aExpression1.car() instanceof ConsPointer) {
            if (!(aExpression2.car() instanceof ConsPointer)) {
                return false;
            }
            ConsTraverser consTraverser1 = new ConsTraverser(aEnvironment, (ConsPointer) aExpression1.car());
            ConsTraverser consTraverser2 = new ConsTraverser(aEnvironment, (ConsPointer) aExpression2.car());

            while (consTraverser1.getCons() != null && consTraverser2.getCons() != null) {
                // compare two list elements
                if (!equals(aEnvironment, aStackTop, consTraverser1.getPointer(), consTraverser2.getPointer())) {
                    return false;
                }

                // Step to rest
                consTraverser1.goNext(aStackTop);
                consTraverser2.goNext(aStackTop);
            }
            // Lists don't have the same length
            if (consTraverser1.getCons() != consTraverser2.getCons()) {
                return false;            // Same!
            }
            return true;
        }

        // expressions sublists are not the same!
        return false;
    }

    public static void substitute(Environment aEnvironment, int aStackTop, ConsPointer aTarget, ConsPointer aSource, Substitute aBehaviour) throws Exception {
        Cons object = aSource.getCons();
        LispError.lispAssert(object != null, aEnvironment, aStackTop);
        if (!aBehaviour.matches(aEnvironment, aStackTop, aTarget, aSource)) {
            Object oldList = object.car();

            ConsPointer oldListPointer = null;
            if (oldList instanceof ConsPointer) {
                oldListPointer = (ConsPointer) oldList;
                oldList = null;
            }
            if (oldListPointer != null) {

                ConsPointer newList = new ConsPointer();
                ConsPointer next = newList;
                while (oldListPointer.getCons() != null) {
                    substitute(aEnvironment, aStackTop, next, oldListPointer, aBehaviour);
                    oldListPointer = oldListPointer.cdr();
                    next = next.cdr();
                }
                aTarget.setCons(SublistCons.getInstance(aEnvironment, newList.getCons()));
            } else {
                aTarget.setCons(object.copy(aEnvironment, false));
            }
        }//end matches if.
    }


    public static String stripEndQuotesIfPresent(Environment aEnvironment, int aStackTop, String aOriginal) throws Exception {
        //If there are not quotes on both ends of the string then return without any changes.
        if (aOriginal.startsWith("\"") && aOriginal.endsWith("\"")) {
            aOriginal = aOriginal.substring(1, aOriginal.length());
            aOriginal = aOriginal.substring(0, aOriginal.length() - 1);
        }//end if.

        return aOriginal;
    }//end method.

    

    public static String toNormalString(Environment aEnvironment, int aStackTop, String aOriginal) throws Exception {
        LispError.check(aEnvironment, aStackTop, aOriginal != null, LispError.INVALID_ARGUMENT, "INTERNAL");
        LispError.check(aEnvironment, aStackTop, aOriginal.charAt(0) == '\"', LispError.INVALID_ARGUMENT, "INTERNAL");
        int nrc = aOriginal.length() - 1;
        LispError.check(aEnvironment, aStackTop, aOriginal.charAt(nrc) == '\"', LispError.INVALID_ARGUMENT, "INTERNAL");
        return aOriginal.substring(1, nrc);
    }

    public static String toMathPiperString(Environment aEnvironment, int aStackTop, String aOriginal) throws Exception {
        LispError.check(aEnvironment, aStackTop, aOriginal != null, LispError.INVALID_ARGUMENT, "INTERNAL");

        return "\"" + aOriginal + "\"";
    }

    private static void doInternalLoad(Environment aEnvironment, int aStackTop, MathPiperInputStream aInput) throws Exception {
        MathPiperInputStream previous = aEnvironment.iCurrentInput;
        try {
            aEnvironment.iCurrentInput = aInput;
            // TODO make "EndOfFile" a global thing
            // read-parse-evaluate to the end of file
            String eof = (String) aEnvironment.getTokenHash().lookUp("EndOfFile");
            boolean endoffile = false;
            MathPiperParser parser = new MathPiperParser(new MathPiperTokenizer(),
                    aEnvironment.iCurrentInput, aEnvironment,
                    aEnvironment.iPrefixOperators, aEnvironment.iInfixOperators,
                    aEnvironment.iPostfixOperators, aEnvironment.iBodiedOperators);
            ConsPointer readIn = new ConsPointer();
            while (!endoffile) {
                // Read expression
                parser.parse(aStackTop, readIn);

                LispError.check(aEnvironment, aStackTop, readIn.getCons() != null, LispError.READING_FILE, "INTERNAL");
                // check for end of file
                if (readIn.car() instanceof String && ((String) readIn.car()).equals(eof)) {
                    endoffile = true;
                } // Else evaluate
                else {
                    ConsPointer result = new ConsPointer();
                    aEnvironment.iLispExpressionEvaluator.evaluate(aEnvironment, aStackTop, result, readIn);
                    aEnvironment.setGlobalVariable(aStackTop, "$LoadResult", result, false);//Note:tk:added to make the result of executing Loaded code available.
                }
            }//end while.



        } catch (Exception e) {
            //e.printStackTrace(); //todo:tk:uncomment for debugging.

            EvaluationException ee = new EvaluationException(e.getMessage(), aEnvironment.iInputStatus.fileName(), aEnvironment.iCurrentInput.iStatus.lineNumber());
            throw ee;
        } finally {
            aEnvironment.iCurrentInput = previous;
        }
    }

    /**
     * Searches for a file on the classpath then in the default directories.  If the file is found, it is loaded.
     * @param aEnvironment
     * @param aFileName
     * @throws java.lang.Exception
     */
    public static void loadScript(Environment aEnvironment, int aStackTop, String aFileName) throws Exception {
        String oper = toNormalString(aEnvironment, aStackTop, aFileName);

        String hashedname = (String) aEnvironment.getTokenHash().lookUp(oper);

        InputStatus oldstatus = new InputStatus(aEnvironment.iInputStatus);
        aEnvironment.iInputStatus.setTo(hashedname);

        MathPiperInputStream newInput = null;

        String path = Utility.scriptsPath + oper;

        //Try to find script on classpath + scriptspath.
        java.io.InputStream inputStream = Utility.class.getResourceAsStream(path);

        //Try to find script on classpath.
        if(inputStream == null)
        {
            inputStream = Utility.class.getResourceAsStream(oper);
        }


        if (inputStream != null) //File is on the classpath.
        {
            newInput = new StandardFileInputStream(new InputStreamReader(inputStream), aEnvironment.iInputStatus);
            LispError.check(aEnvironment, aStackTop, newInput != null, LispError.FILE_NOT_FOUND, "INTERNAL");
            doInternalLoad(aEnvironment, aStackTop, newInput);

        } else { //File may be in the filesystem.
            try {
                // Open file
                newInput = // new StandardFileInputStream(hashedname, aEnvironment.iInputStatus);
                        openInputFile(aEnvironment, aEnvironment.iInputDirectories, hashedname, aEnvironment.iInputStatus);

                LispError.check(aEnvironment, aStackTop, newInput != null, LispError.FILE_NOT_FOUND, "INTERNAL");
                doInternalLoad(aEnvironment, aStackTop, newInput);
            } catch (Exception e) {
                throw e;
            } finally {
                aEnvironment.iInputStatus.restoreFrom(oldstatus);
            }
        }//end else.*/


        aEnvironment.iInputStatus.restoreFrom(oldstatus);


    }

    public static void loadScriptOnce(Environment aEnvironment, int aStackTop, String aFileName) throws Exception {
        DefFile def = aEnvironment.iDefFiles.getFile(aFileName);
        if (!def.isLoaded()) {
            def.setLoaded();
            loadScript(aEnvironment, aStackTop, aFileName);
        }
    }

    public static void doPatchString(String unpatchedString, MathPiperOutputStream aOutput, Environment aEnvironment, int aStackTop) throws Exception
    {
        String[] tags = unpatchedString.split("\\?\\>");
        if (tags.length > 1) {
            for (int x = 0; x < tags.length; x++) {
                String[] tag = tags[x].split("\\<\\?");
                if (tag.length > 1) {
                    aOutput.write(tag[0]);
                    String scriptCode = tag[1].trim();
                    StringBuffer scriptCodeBuffer = 
                        new StringBuffer(scriptCode);
                    StringInputStream scriptStream = 
                        new StringInputStream(scriptCodeBuffer, aEnvironment.iInputStatus);
                    MathPiperOutputStream previous = 
                        aEnvironment.iCurrentOutput;
                    try {
                        aEnvironment.iCurrentOutput = aOutput;
                        Utility.doInternalLoad(aEnvironment, aStackTop, scriptStream);
                    } catch(Exception e) {
                        throw e;
                    } finally {
                        aEnvironment.iCurrentOutput = previous;
                    }
                }
            } // end for
            aOutput.write(tags[tags.length - 1]);
        } else {
            aOutput.write(unpatchedString);
        }
    }

    public static String printMathPiperExpression(int aStackTop, ConsPointer aExpression, Environment aEnvironment, int aMaxChars) throws Exception {
        if(aExpression.getCons() == null)
        {
            return "NULL";
        }

        StringBuffer result = new StringBuffer();
        StringOutputStream newOutput = new StringOutputStream(result);
        MathPiperPrinter infixprinter = new MathPiperPrinter(aEnvironment.iPrefixOperators,
                aEnvironment.iInfixOperators,
                aEnvironment.iPostfixOperators,
                aEnvironment.iBodiedOperators);
        infixprinter.print(aStackTop, aExpression, newOutput, aEnvironment);
        if (aMaxChars > 0 && result.length() > aMaxChars) {
            result.delete(aMaxChars, result.length());
            result.append((char) '.');
            result.append((char) '.');
            result.append((char) '.');
        }
        return result.toString();
    }//end method.


    public static String printLispExpression( int aStackTop, ConsPointer aExpression, Environment aEnvironment, int aMaxChars) throws Exception {
        if(aExpression.getCons() == null)
        {
            return "NULL";
        }

        StringOutput out = new StringOutput();

        LispPrinter printer = new LispPrinter();

        printer.print(aStackTop, aExpression, out, aEnvironment);

        //todo:tk:add the ability to truncate the result.

        return out.toString();
    }

    public static MathPiperInputStream openInputFile(String aFileName, InputStatus aInputStatus) throws Exception {//Note:tk:primary method for file opening.

        try {
            if (zipFile != null) {
                java.util.zip.ZipEntry e = zipFile.getEntry(aFileName);
                if (e != null) {
                    java.io.InputStream s = zipFile.getInputStream(e);
                    return new StandardFileInputStream(new InputStreamReader(s), aInputStatus);
                }
            }

            if (aFileName.substring(0, 4).equals("jar:")) {
                return new JarFileInputStream(aFileName, aInputStatus);
            } else {
                return new StandardFileInputStream(aFileName, aInputStatus);
            }
        } catch (Exception e) {
            //MathPiper eats this exception because returning null indicates to higher level code that the file was not found.
        }
        return null;

        //return new StandardFileInputStream(aFileName, aInputStatus);
    }

    public static MathPiperInputStream openInputFile(Environment aEnvironment,
            InputDirectories aInputDirectories, String aFileName,
            InputStatus aInputStatus) throws Exception {
        String othername = aFileName;
        int i = 0;
        MathPiperInputStream f = openInputFile(othername, aInputStatus);
        while (f == null && i < aInputDirectories.size()) {
            othername = ((String) aInputDirectories.get(i)) + aFileName;
            f = openInputFile(othername, aInputStatus);
            i++;
        }
        return f;
    }

    public static String findFile(String aFileName, InputDirectories aInputDirectories) throws Exception {
        InputStatus inputStatus = new InputStatus();
        String othername = aFileName;
        int i = 0;
        MathPiperInputStream f = openInputFile(othername, inputStatus);
        if (f != null) {
            return othername;
        }
        while (i < aInputDirectories.size()) {
            othername = ((String) aInputDirectories.get(i)) + aFileName;
            f = openInputFile(othername, inputStatus);
            if (f != null) {
                return othername;
            }
            i++;
        }
        return "";
    }

    private static void doLoadDefFile(Environment aEnvironment, int aStackTop, MathPiperInputStream aInput, DefFile def) throws Exception {
        MathPiperInputStream previous = aEnvironment.iCurrentInput;
        try {
            aEnvironment.iCurrentInput = aInput;
            String eof = (String) aEnvironment.getTokenHash().lookUp("EndOfFile");
            String end = (String) aEnvironment.getTokenHash().lookUp("}");
            boolean endoffile = false;

            MathPiperTokenizer tok = new MathPiperTokenizer();

            while (!endoffile) {
                // Read expression
                String token = tok.nextToken(aEnvironment, aStackTop, aEnvironment.iCurrentInput, aEnvironment.getTokenHash());

                // check for end of file
                if (token.equals(eof) || token.equals(end)) {
                    endoffile = true;
                } // Else evaluate
                else {
                    String str = token;
                    MultipleArityRulebase multiUser = aEnvironment.getMultipleArityRulebase(aStackTop, str, true);
                    if (multiUser.iFileToOpen != null) {
                        throw new EvaluationException("[" + str + "]" + "] : def file already chosen: " + multiUser.iFileToOpen.iFileName, aEnvironment.iInputStatus.fileName(), aEnvironment.iCurrentInput.iStatus.lineNumber());
                    }
                    multiUser.iFileToOpen = def;
                    multiUser.iFileLocation = def.fileName();
                }
            }
        } catch (Exception e) {
            throw e;
        } finally {
            aEnvironment.iCurrentInput = previous;
        }
    }

    public static void loadDefFile(Environment aEnvironment, int aStackTop, String aFileName) throws Exception {
        LispError.lispAssert(aFileName != null, aEnvironment, aStackTop);

        String flatfile = toNormalString(aEnvironment, aStackTop, aFileName) + ".def";
        DefFile def = aEnvironment.iDefFiles.getFile(aFileName);

        String hashedname = (String) aEnvironment.getTokenHash().lookUp(flatfile);

        InputStatus oldstatus = aEnvironment.iInputStatus;
        aEnvironment.iInputStatus.setTo(hashedname);


        MathPiperInputStream newInput = null;

        String path = Utility.scriptsPath + flatfile;

        java.io.InputStream inputStream = Utility.class.getResourceAsStream(path);


        if (inputStream != null) //File is on the classpath.
        {
            newInput = new StandardFileInputStream(new InputStreamReader(inputStream), aEnvironment.iInputStatus);
            LispError.check(aEnvironment, aStackTop, newInput != null, LispError.FILE_NOT_FOUND, "INTERNAL");
            doLoadDefFile(aEnvironment, aStackTop, newInput, def);

        } else //File may be in the filesystem.
        {
            newInput = // new StandardFileInputStream(hashedname, aEnvironment.iInputStatus);
                    openInputFile(aEnvironment, aEnvironment.iInputDirectories, hashedname, aEnvironment.iInputStatus);
            LispError.check(aEnvironment, aStackTop, newInput != null, LispError.FILE_NOT_FOUND, "INTERNAL");
            doLoadDefFile(aEnvironment, aStackTop, newInput, def);
        }

        aEnvironment.iInputStatus.restoreFrom(oldstatus);
    }
    //////////////////////////////////////////////////
    ///// bits_to_digits and digits_to_bits implementation
    //////////////////////////////////////////////////

    // lookup table for transforming the number of digits
    // report the table size
    int log2TableRange() {
        return log2_table_size;
    }
    // table look-up of small integer logarithms, for converting the number of digits to binary and back

    static double log2TableLookup(int n) throws Exception {
        if (n <= log2_table_size && n >= 2) {
            return log2_table[n - 1];
        } else {
            throw new EvaluationException("log2_table_lookup: error: invalid argument " + n, "none", -1);
        }
    }

    /**
     * Convert the number of digits in given base to the number of bits.  To make sure there is no hysteresis, the returned
     * value is rounded up.
     *
     * @param digits
     * @param base
     * @return the number of bits
     * @throws java.lang.Exception
     */
    public static long digitsToBits(long digits, int base) throws Exception {
        return (long) Math.ceil(((double) digits) * log2TableLookup(base));
    }

    /**
     * Convert the  number of bits in a given base to the number of digits.  To make sure there is no hysteresis, the returned
     * value is rounded down.
     *
     * @param bits
     * @param base
     * @return the number of digits
     * @throws java.lang.Exception
     */
    public static long bitsToDigits(long bits, int base) throws Exception {
        return (long) Math.floor(((double) bits) / log2TableLookup(base));
    }

    //************************* The following methods were taken from the Functions class.
    /**
     * Construct a {@link BigNumber}.
     * @param aEnvironment the current {@link Environment}.
     * @param aStackTop points to the the top of the argument stack.
     * @param aArgNr the index of the argument to be converted.
     * @return a BigNumber.
     * @throws java.lang.Exception
     */
    public static BigNumber getNumber(Environment aEnvironment, int aStackTop, int aArgNr) throws Exception {
        //LispError.check(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, aArgNr).type().equals("Number"), LispError.INVALID_ARGUMENT);
        BigNumber x = (BigNumber) BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, aArgNr).getCons().getNumber(aEnvironment.getPrecision(), aEnvironment);
        LispError.checkArgument(aEnvironment, aStackTop, x != null, aArgNr, "INTERNAL");
        return x;
    }

    public static void multiFix(Environment aEnvironment, int aStackTop, OperatorMap aOps) throws Exception {
        // Get operator
        LispError.checkArgument(aEnvironment, aStackTop, BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).getCons() != null, 1, "INTERNAL");
        String orig = (String) BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).car();
        LispError.checkArgument(aEnvironment, aStackTop, orig != null, 1, "INTERNAL");

        ConsPointer precedence = new ConsPointer();
        aEnvironment.iLispExpressionEvaluator.evaluate(aEnvironment, aStackTop, precedence, BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 2));
        LispError.checkArgument(aEnvironment, aStackTop, precedence.car() instanceof String, 2, "INTERNAL");
        int prec = Integer.parseInt((String) precedence.car(), 10);
        LispError.checkArgument(aEnvironment, aStackTop, prec <= MathPiperPrinter.KMaxPrecedence, 2, "INTERNAL");
        aOps.setOperator(prec, Utility.getSymbolName(aEnvironment, orig));
        Utility.putTrueInPointer(aEnvironment, BuiltinFunction.getTopOfStackPointer(aEnvironment, aStackTop));
    }

    public static void singleFix(int aPrecedence, Environment aEnvironment, int aStackTop, OperatorMap aOps) throws Exception {
        // Get operator
        LispError.checkArgument(aEnvironment, aStackTop, BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).getCons() != null, 1, "INTERNAL");
        String orig = (String) BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).car();
        LispError.checkArgument(aEnvironment, aStackTop, orig != null, 1, "INTERNAL");
        aOps.setOperator(aPrecedence, Utility.getSymbolName(aEnvironment, orig));
        Utility.putTrueInPointer(aEnvironment, BuiltinFunction.getTopOfStackPointer(aEnvironment, aStackTop));
    }

    public static Operator operatorInfo(Environment aEnvironment, int aStackTop, OperatorMap aOperators) throws Exception {
        // Get operator
        LispError.checkArgument(aEnvironment, aStackTop, BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).getCons() != null, 1, "INTERNAL");

        ConsPointer evaluated = new ConsPointer();
        evaluated.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).getCons());

        String orig = (String) evaluated.car();
        LispError.checkArgument(aEnvironment, aStackTop, orig != null, 1, "INTERNAL");
        //
        Operator op = (Operator) aOperators.lookUp(Utility.getSymbolName(aEnvironment, orig));
        return op;
    }

    /**
     * Sets a variable in the current {@link Environment}.
     * @param aEnvironment holds the execution environment of the program.
     * @param aStackTop
     * @param aMacroMode boolean which determines whether the getFirstPointer argument should be evaluated.
     * @param aGlobalLazyVariable
     * @throws java.lang.Exception
     */
    public static void setVar(Environment aEnvironment, int aStackTop, boolean aMacroMode, boolean aGlobalLazyVariable) throws Exception {
        String variableString = null;
        if (aMacroMode) {
            ConsPointer result = new ConsPointer();
            aEnvironment.iLispExpressionEvaluator.evaluate(aEnvironment, aStackTop, result, BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1));
            variableString = (String) result.car();
        } else {
            variableString = (String) BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).car();
        }
        LispError.checkArgument(aEnvironment, aStackTop, variableString != null, 1, "INTERNAL");
        LispError.checkArgument(aEnvironment, aStackTop, !Utility.isNumber(variableString, true), 1, "INTERNAL");

        ConsPointer result = new ConsPointer();
        aEnvironment.iLispExpressionEvaluator.evaluate(aEnvironment, aStackTop, result, BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 2));
        aEnvironment.setGlobalVariable(aStackTop, variableString, result, aGlobalLazyVariable); //Variable setting is deligated to Environment.
        Utility.putTrueInPointer(aEnvironment, BuiltinFunction.getTopOfStackPointer(aEnvironment, aStackTop));
    }

    public static void delete(Environment aEnvironment, int aStackTop, boolean aDestructive) throws Exception {
        ConsPointer evaluated = new ConsPointer();
        evaluated.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).getCons());
        LispError.checkIsList(aEnvironment, aStackTop, evaluated, 1, "INTERNAL");

        ConsPointer copied = new ConsPointer();
        if (aDestructive) {
            copied.setCons(((ConsPointer) evaluated.car()).getCons());
        } else {
            Utility.flatCopy(aEnvironment, aStackTop, copied, (ConsPointer) evaluated.car());
        }

        ConsPointer index = new ConsPointer();
        index.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 2).getCons());
        LispError.checkArgument(aEnvironment, aStackTop, index.getCons() != null, 2, "INTERNAL");
        LispError.checkArgument(aEnvironment, aStackTop, index.car() instanceof String, 2, "INTERNAL");
        int ind = Integer.parseInt((String) index.car(), 10);
        LispError.checkArgument(aEnvironment, aStackTop, ind > 0, 2, "INTERNAL");

        ConsTraverser consTraverser = new ConsTraverser(aEnvironment, copied);
        while (ind > 0) {
            consTraverser.goNext(aStackTop);
            ind--;
        }
        LispError.check(aEnvironment, aStackTop, consTraverser.getCons() != null, LispError.NOT_LONG_ENOUGH);
        ConsPointer next = new ConsPointer();
        next.setCons(consTraverser.cdr().getCons());
        consTraverser.getPointer().setCons(next.getCons());
        BuiltinFunction.getTopOfStackPointer(aEnvironment, aStackTop).setCons(SublistCons.getInstance(aEnvironment, copied.getCons()));
    }

    public static void insert(Environment aEnvironment, int aStackTop, boolean aDestructive) throws Exception {
        ConsPointer evaluated = new ConsPointer();
        evaluated.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).getCons());
        LispError.checkIsList(aEnvironment, aStackTop, evaluated, 1, "INTERNAL");

        ConsPointer copied = new ConsPointer();
        if (aDestructive) {
            copied.setCons(((ConsPointer) evaluated.car()).getCons());
        } else {
            Utility.flatCopy(aEnvironment, aStackTop, copied, (ConsPointer) evaluated.car());
        }

        ConsPointer index = new ConsPointer();
        index.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 2).getCons());
        LispError.checkArgument(aEnvironment, aStackTop, index.getCons() != null, 2, "INTERNAL");
        LispError.checkArgument(aEnvironment, aStackTop, index.car() instanceof String, 2, "INTERNAL");
        int ind = Integer.parseInt((String) index.car(), 10);
        LispError.checkArgument(aEnvironment, aStackTop, ind > 0, 2, "INTERNAL");

        ConsTraverser consTraverser = new ConsTraverser(aEnvironment, copied);
        while (ind > 0) {
            consTraverser.goNext(aStackTop);
            ind--;
        }

        ConsPointer toInsert = new ConsPointer();
        toInsert.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 3).getCons());
        toInsert.cdr().setCons(consTraverser.getCons());
        consTraverser.getPointer().setCons(toInsert.getCons());
        BuiltinFunction.getTopOfStackPointer(aEnvironment, aStackTop).setCons(SublistCons.getInstance(aEnvironment, copied.getCons()));
    }

    public static void replace(Environment aEnvironment, int aStackTop, boolean aDestructive) throws Exception {
        ConsPointer evaluated = new ConsPointer();
        evaluated.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).getCons());
        // Ok, so lets not check if it is a list, but it needs to be at least a 'function'
        LispError.checkArgument(aEnvironment, aStackTop, evaluated.car() instanceof ConsPointer, 1, "INTERNAL");

        ConsPointer index = new ConsPointer();
        index.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 2).getCons());
        LispError.checkArgument(aEnvironment, aStackTop, index.getCons() != null, 2, "INTERNAL");
        LispError.checkArgument(aEnvironment, aStackTop, index.car() instanceof String, 2, "INTERNAL");
        int ind = Integer.parseInt((String) index.car(), 10);

        ConsPointer copied = new ConsPointer();
        if (aDestructive) {
            copied.setCons(((ConsPointer) evaluated.car()).getCons());
        } else {
            Utility.flatCopy(aEnvironment, aStackTop, copied, (ConsPointer) evaluated.car());
        }
        LispError.checkArgument(aEnvironment, aStackTop, ind > 0, 2, "INTERNAL");

        ConsTraverser consTraverser = new ConsTraverser(aEnvironment, copied);
        while (ind > 0) {
            consTraverser.goNext(aStackTop);
            ind--;
        }

        ConsPointer toInsert = new ConsPointer();
        toInsert.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 3).getCons());
        LispError.checkArgument(aEnvironment, aStackTop, consTraverser.getPointer() != null, 2, "INTERNAL");
        LispError.checkArgument(aEnvironment, aStackTop, consTraverser.getPointer().getCons() != null, 2, "INTERNAL");
        toInsert.cdr().setCons(consTraverser.getPointer().cdr().getCons());
        consTraverser.getPointer().setCons(toInsert.getCons());
        BuiltinFunction.getTopOfStackPointer(aEnvironment, aStackTop).setCons(SublistCons.getInstance(aEnvironment, copied.getCons()));
    }

    /**
     *Implements the MathPiper functions Rulebase and MacroRulebase .
     * The real work is done by Environment.defineRulebase().
     */
    public static void rulebase(Environment aEnvironment, int aStackTop, boolean aListed) throws Exception {

        // Get operator
        ConsPointer argsPointer = new ConsPointer();
        String functionName = null;

        LispError.checkArgument(aEnvironment, aStackTop, BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).getCons() != null, 1, "INTERNAL");
        functionName = (String) BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).car();
        LispError.checkArgument(aEnvironment, aStackTop, functionName != null, 1, "INTERNAL");
        argsPointer.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 2).getCons());

        // Check the arguments.
        LispError.checkIsList(aEnvironment, aStackTop, argsPointer, 2, "INTERNAL");

        // Finally define the rule database.
        aEnvironment.defineRulebase(aStackTop, Utility.getSymbolName(aEnvironment, functionName), ((ConsPointer) argsPointer.car()).cdr(), aListed);

        // Return true
        Utility.putTrueInPointer(aEnvironment, BuiltinFunction.getTopOfStackPointer(aEnvironment, aStackTop));
    }

    public static void newRule(Environment aEnvironment, int aStackTop, boolean aPattern) throws Exception {

        int arity;
        int precedence;

        ConsPointer arityPointer = new ConsPointer();
        ConsPointer precidencePointer = new ConsPointer();
        ConsPointer predicate = new ConsPointer();
        ConsPointer bodyPointer = new ConsPointer();
        String orig = null;

        // Get operator
        LispError.checkArgument(aEnvironment, aStackTop, BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).getCons() != null, 1, "INTERNAL");
        orig = (String) BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).car();
        LispError.checkArgument(aEnvironment, aStackTop, orig != null, 1, "INTERNAL");
        arityPointer.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 2).getCons());
        precidencePointer.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 3).getCons());
        predicate.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 4).getCons());
        bodyPointer.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 5).getCons());

        // The arity
        LispError.checkArgument(aEnvironment, aStackTop, arityPointer.getCons() != null, 2, "INTERNAL");
        LispError.checkArgument(aEnvironment, aStackTop, arityPointer.car() instanceof String, 2, "INTERNAL");
        arity = Integer.parseInt((String) arityPointer.car(), 10);

        // The precedence
        LispError.checkArgument(aEnvironment, aStackTop, precidencePointer.getCons() != null, 3, "INTERNAL");
        LispError.checkArgument(aEnvironment, aStackTop, precidencePointer.car() instanceof String, 3, "INTERNAL");
        precedence = Integer.parseInt((String) precidencePointer.car(), 10);

        // Finally define the rule base
        if(aPattern == true)
        {
            aEnvironment.defineRulePattern(aStackTop, Utility.getSymbolName(aEnvironment, orig),
                arity,
                precedence,
                predicate,
                bodyPointer);
        }
        else
        {
            aEnvironment.defineRule(aStackTop, Utility.getSymbolName(aEnvironment, orig),
                arity,
                precedence,
                predicate,
                bodyPointer);
        }

        // Return true
        Utility.putTrueInPointer(aEnvironment, BuiltinFunction.getTopOfStackPointer(aEnvironment, aStackTop));
    }

    public static void defMacroRulebase(Environment aEnvironment, int aStackTop, boolean aListed) throws Exception {
        // Get operator
        ConsPointer args = new ConsPointer();
        ConsPointer body = new ConsPointer();
        String orig = null;

        LispError.checkArgument(aEnvironment, aStackTop, BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).getCons() != null, 1, "INTERNAL");
        orig = (String) BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 1).car();
        LispError.checkArgument(aEnvironment, aStackTop, orig != null, 1, "INTERNAL");

        // The arguments
        args.setCons(BuiltinFunction.getArgumentPointer(aEnvironment, aStackTop, 2).getCons());
        LispError.checkIsList(aEnvironment, aStackTop, args, 2, "INTERNAL");

        // Finally define the rule base
        aEnvironment.defineMacroRulebase(aStackTop, Utility.getSymbolName(aEnvironment, orig),
                ((ConsPointer) args.car()).cdr(), aListed);

        // Return true
        Utility.putTrueInPointer(aEnvironment, BuiltinFunction.getTopOfStackPointer(aEnvironment, aStackTop));
    }



    public static String dumpRule(int aStackTop, Rule rule, Environment aEnvironment, SingleArityRulebase userFunction) {
        StringBuilder dumpResult = new StringBuilder();
        try {
            int precedence = rule.getPrecedence();

            ConsPointer predicatePointer1 = rule.getPredicatePointer();
            String predicate = "";
            String predicatePointerString = predicatePointer1.toString();

            if (predicatePointerString == null || predicatePointerString.equalsIgnoreCase("Empty.")) {
                predicate = "None.";
            } else {
                predicate = Utility.printMathPiperExpression(aStackTop, predicatePointer1, aEnvironment, 0);
            }

            if (rule instanceof PatternRule) {
                predicate = "(Pattern) ";
                PatternRule branchPattern = (PatternRule) rule;
                ParametersPatternMatcher pattern = branchPattern.getPattern();

                Iterator variablesIterator = pattern.getVariables().iterator();
                String patternVariables = "";
                while (variablesIterator.hasNext()) {
                    String patternVariable = (String) variablesIterator.next();
                    patternVariables += patternVariable + ", ";
                }
                if (patternVariables.contains(",")) {
                    patternVariables = patternVariables.substring(0, patternVariables.lastIndexOf(","));
                }


                Iterator parameterMatchersIterator = pattern.getParameterMatchers().iterator();
                String parameterTypes = "";
                while (parameterMatchersIterator.hasNext()) {
                    PatternParameterMatcher parameter = (PatternParameterMatcher) parameterMatchersIterator.next();
                    String parameterType = (String) parameter.getType();
                    parameterTypes += parameterType + ": " + parameter.toString();
                    parameterTypes += "; ";
                }
                if (parameterTypes.contains(",")) {
                    parameterTypes = parameterTypes.substring(0, parameterTypes.lastIndexOf(","));
                }



                Iterator patternPredicatesIterator = pattern.getPredicates().iterator();
                while (patternPredicatesIterator.hasNext()) {
                    ConsPointer predicatePointer = (ConsPointer) patternPredicatesIterator.next();
                    String patternPredicate = Utility.printMathPiperExpression(aStackTop, predicatePointer, aEnvironment, 0);
                    predicate += patternPredicate + ", ";
                }
                /*if (predicate.contains(",")) {
                predicate = predicate.substring(0, predicate.lastIndexOf(","));
                }*/
                predicate += "\n    Variables: " + patternVariables + ", ";
                predicate += "\n    Types: " + parameterTypes;

            }//end if.

            Iterator paremetersIterator = userFunction.getParameters();
            String parameters = "";
            boolean isHold = false;
            while (paremetersIterator.hasNext()) {
                ParameterName branchParameter = (ParameterName) paremetersIterator.next();
                String parameter = branchParameter.getName();
                isHold = branchParameter.isHold();
                parameters += parameter + "<hold=" + isHold + ">, ";
            }
            if (parameters.contains(",")) {
                parameters = parameters.substring(0, parameters.lastIndexOf(","));
            }

            String body = Utility.printMathPiperExpression(aStackTop, rule.getBodyPointer(), aEnvironment, 0);
            body = body.replace(",", ", ");
            //System.out.println(data);

            String substitutedMacroBody = "";

            if (userFunction instanceof MacroRulebase) {
                BackQuoteSubstitute backQuoteSubstitute = new BackQuoteSubstitute(aEnvironment);
                ConsPointer substitutedBodyPointer = new ConsPointer();
                Utility.substitute(aEnvironment, aStackTop, substitutedBodyPointer, rule.getBodyPointer(), backQuoteSubstitute);
                substitutedMacroBody = Utility.printMathPiperExpression(aStackTop, substitutedBodyPointer, aEnvironment, 0);
            }

            dumpResult.append("Precedence: " + precedence + ", ");
            dumpResult.append("\n" + "Rule Type: " + rule.getClass().getSimpleName() + ", ");
            dumpResult.append("\n" + "Arity: " + userFunction.arity() + ", ");
            dumpResult.append("\n" + "Parameters: " + parameters + ", ");
            dumpResult.append("\n" + "Predicates: " + predicate + ",    ");

            if (userFunction instanceof MacroRulebase) {
                dumpResult.append("\n" + "Body: \n" + body + ", ");
                dumpResult.append("\n" + "Substituted Macro Body: \n" + substitutedMacroBody + "\n");
            } else {
                dumpResult.append("\n" + "Body: \n" + body + "\n");
            }



        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return dumpResult.toString();

    }//end method.

    public static Cons associativeListGet(Environment aEnvironment, int aStackTop, ConsPointer key, Cons listCons) throws Exception {


        while (listCons != null) {
            if (listCons.car() instanceof ConsPointer) {
                Cons sub = ((ConsPointer) listCons.car()).getCons();
                if (sub != null) {
                    sub = sub.cdr().getCons();
                    ConsPointer temp = new ConsPointer();
                    temp.setCons(sub);
                    if (Utility.equals(aEnvironment, aStackTop, key, temp)) {
                        return listCons;
                    }//end if.

                }//end if.

            }//end if.

            listCons = listCons.cdr().getCons();

        }//end if.

        return null;
    }//end method.

    /**
     * Returns the type of a.
     * @param aEnvironment
     * @param expressionPointer
     * @throws java.lang.Exception
     */
    public static String functionType(ConsPointer expressionPointer) throws Exception {
        if (!(expressionPointer.car() instanceof ConsPointer)) {
            return "";
        }

        ConsPointer subList = (ConsPointer) expressionPointer.car();
        Cons head = null;
        head = subList.getCons();
        if (!(head.car() instanceof String)) {
            return "";
        }//end if.

        return (String) head.car();

    }//end method.

    /**
     * Converts a =Java Iterable into a MathPiper List.
     *
     * @param aEnvironment
     * @param iterable
     * @return cons
     * @throws java.lang.Exception
     */
    public static Cons iterableToList(Environment aEnvironment, int aStackTop, java.lang.Iterable iterable) throws Exception {
        
        Cons head = aEnvironment.iListAtom.copy(aEnvironment, false);

        ConsPointer consPointer = new ConsPointer();

        consPointer.setCons(head);

        Iterator iterator = iterable.iterator();

        while (iterator.hasNext()) {
            Object object = iterator.next();

            if(object instanceof String)
            {
                String key = (String) object;

                Cons stringCons = AtomCons.getInstance(aEnvironment, aStackTop, key);

                consPointer.getCons().cdr().setCons(stringCons);
            }
            else
            {
                consPointer.getCons().cdr().setCons(BuiltinObjectCons.getInstance(aEnvironment, aStackTop, new JavaObject(object)));
            }

            consPointer.goNext(aStackTop, aEnvironment);

        }//end while.

        return head;

    }//end method.


    public static ConsPointer mathPiperParse(Environment aEnvironment, int aStackTop, String inputExpression) throws Exception {
        MathPiperTokenizer tokenizer = new MathPiperTokenizer();
        InputStatus someStatus = new InputStatus();
        ConsPointer inputExpressionPointer = new ConsPointer();

        StringBuffer inp = new StringBuffer();
        inp.append(inputExpression);
        inp.append(";");
        StringInputStream inputExpressionBuffer = new StringInputStream(inp, someStatus);

        Parser infixParser = new MathPiperParser(tokenizer, inputExpressionBuffer, aEnvironment, aEnvironment.iPrefixOperators, aEnvironment.iInfixOperators, aEnvironment.iPostfixOperators, aEnvironment.iBodiedOperators);
        infixParser.parse(aStackTop, inputExpressionPointer);

        return inputExpressionPointer;
    }//end method.




    public static ConsPointer lispEvaluate(Environment aEnvironment, int aStackTop, String inputExpression) throws Exception {
        ConsPointer result = new ConsPointer();

        ConsPointer inputExpressionPointer = mathPiperParse(aEnvironment, aStackTop, inputExpression);

        aEnvironment.iLispExpressionEvaluator.evaluate(aEnvironment, aStackTop, result, inputExpressionPointer);

        return result;
    }//end method.




    public static ConsPointer lispEvaluate(Environment aEnvironment, int aStackTop, ConsPointer inputExpressionPointer) throws Exception {
        ConsPointer result = new ConsPointer();
        MathPiperTokenizer tokenizer = new MathPiperTokenizer();
        InputStatus someStatus = new InputStatus();

        aEnvironment.iLispExpressionEvaluator.evaluate(aEnvironment, aStackTop, result, inputExpressionPointer);

        return result;
    }//end method.



    public static void declareFunction(String functionName, String[] parameters, String body, Environment aEnvironment, int aStackTop) throws Exception
    {

        ConsTraverser parameterTraverser = new ConsTraverser(aEnvironment, new ConsPointer());

        for(String parameterName:parameters)
        {
           Cons atomCons = AtomCons.getInstance(aEnvironment, aStackTop, parameterName);

           parameterTraverser.setCons(atomCons);
           
           parameterTraverser.goNext(aStackTop);
        }//end for.

        aEnvironment.defineRulebase(aStackTop, functionName, parameterTraverser.getHeadPointer(), false);

        ConsPointer truePointer = new ConsPointer();

        Utility.putTrueInPointer(aEnvironment, truePointer);

        ConsPointer expressionPointer = Utility.mathPiperParse(aEnvironment, aStackTop, body);

        aEnvironment.defineRule(aStackTop, functionName, parameters.length, 100, truePointer, expressionPointer);
    }

}//end class.

