package nova;

import java.math.*;
import java.util.*;

/**
 * Warranty & Liability
 * To the extent permitted by applicable law and unless explicitly
 * otherwise agreed upon, XLOG Technologies AG makes no warranties
 * regarding the provided information. XLOG Technologies AG assumes
 * no liability that any problems might be solved with the information
 * provided by XLOG Technologies AG.
 *
 * Rights & License
 * All industrial property rights regarding the information - copyright
 * and patent rights in particular - are the sole property of XLOG
 * Technologies AG. If the company was not the originator of some
 * excerpts, XLOG Technologies AG has at least obtained the right to
 * reproduce, change and translate the information.
 *
 * Reproduction is restricted to the whole unaltered document. Reproduction
 * of the information is only allowed for non-commercial uses. Selling,
 * giving away or letting of the execution of the library is prohibited.
 * The library can be distributed as part of your applications and libraries
 * for execution provided this comment remains unchanged.
 *
 * Restrictions
 * Only to be distributed with programs that add significant and primary
 * functionality to the library. Not to be distributed with additional
 * software intended to replace any components of the library.
 *
 * Trademarks
 * Jekejeke is a registered trademark of XLOG Technologies AG.
 */
public final class eval {
    public static final String OP_EVALUATION_ZERO_DIVISOR = "zero_divisor"; /* arithmetic */
    public static final String OP_EVALUATION_FLOAT_OVERFLOW = "float_overflow";
    public static final String OP_EVALUATION_UNDEFINED = "undefined";
    public static final String OP_PERMISSION_NOT_SUPPORTED = "not_supported";
    public static final String OP_DOMAIN_NOT_LESS_THAN_ZERO = "not_less_than_zero";
    public static final String OP_REPRESENTATION_INT = "int";

    private static final Double DOUBLE_PI = Double.valueOf(Math.PI);
    private static final Double DOUBLE_E = Double.valueOf(Math.E);
    private static final Double DOUBLE_EPSILON = Double.valueOf(Math.ulp(1.0));

    public static final int NUM_INTEGER = 0;
    public static final int NUM_BIG_INTEGER = 1;
    public static final int NUM_DOUBLE = 2;
    
    /******************************************************************/
    /* is/2                                                           */
    /******************************************************************/

    /**
     * X is Y: [ISO 8.6.1]
     * The predicate succeeds in X with the evaluation of Y.
     */
    private static boolean test_eval(Object[] args) {
        try {
            Number res = Machine.exec_eval(args[1]);
            return Machine.exec_unify(args[0], res);
        } catch (RuntimeException x) {
            throw mapRuntime(x);
        }
    }

    /**
     * <p>Attempt to map a runtime exception to Prolog exception</p>
     *
     * @param x The runtime exception.
     * @return The Prolog exception or the original runtime exeption.
     */
    public static RuntimeException mapRuntime(RuntimeException x) {
        if (x instanceof ArithmeticException) {
            return Machine.make_error(new Store.Compound("evaluation_error",
                    new Object[]{x.getMessage()}));
        } else if (x instanceof Handler.RangeException) {
            return Machine.make_error(new Store.Compound("domain_error",
                    new Object[]{x.getMessage(), ((Handler.RangeException) x).getCulprit()}));
        } else if (x instanceof UnsupportedOperationException) {
            return Machine.make_error(new Store.Compound("resource_error",
                    new Object[]{x.getMessage()}));
        } else if (x instanceof IllegalArgumentException) {
            return Machine.make_error(new Store.Compound("representation_error",
                    new Object[]{x.getMessage()}));
        } else {
            return x;
        }
    }

    /*****************************************************************/
    /* (-)/2, (+)/3, (-)/3, (*)/3, (/)/3, (//)/3 and (rem)/3.        */
    /*****************************************************************/

    /**
     * -(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the negation of A.
     */
    private static Number arit_neg(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (alpha instanceof Integer) {
            int x = alpha.intValue();
            if (x != Integer.MIN_VALUE) {
                return Integer.valueOf(-x);
            } else {
                return BigInteger.valueOf(-(long) x);
            }
        } else if (alpha instanceof BigInteger) {
            return special.norm_bigint(((BigInteger) alpha).negate());
        } else if (alpha instanceof Double) {
            return Double.valueOf(-alpha.doubleValue());
        } else {
            throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * +(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with the sum of A and B.
     */
    private static Number arit_add(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return add(alpha, beta);
    }

    public static Number add(Number m, Number n) {
        switch (Math.max(numType(m), numType(n))) {
            case NUM_INTEGER:
                return special.norm_smallint((long) m.intValue() + n.intValue());
            case NUM_BIG_INTEGER:
                return special.norm_bigint(special.widen_bigint(m).add(
                        special.widen_bigint(n)));
            case NUM_DOUBLE:
                return special.norm_float(Double.valueOf(special.narrow_float(m) +
                        special.narrow_float(n)));
            default:
                throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * -(A, B, C):
     * The predicate succeeds in C with A subtracted by B.
     */
    private static Number arit_sub(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return subtract(alpha, beta);
    }

    public static Number subtract(Number m, Number n) {
        switch (Math.max(numType(m), numType(n))) {
            case NUM_INTEGER:
                return special.norm_smallint((long) m.intValue() - n.intValue());
            case NUM_BIG_INTEGER:
                return special.norm_bigint(special.widen_bigint(m).subtract(
                        special.widen_bigint(n)));
            case NUM_DOUBLE:
                return special.norm_float(Double.valueOf(special.narrow_float(m) -
                        special.narrow_float(n)));
            default:
                throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * *(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with the product of A and B.
     */
    private static Number arit_mul(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        switch (Math.max(numType(alpha), numType(beta))) {
            case NUM_INTEGER:
                return special.norm_smallint((long) alpha.intValue() * beta.intValue());
            case NUM_BIG_INTEGER:
                return special.norm_bigint(special.widen_bigint(alpha).multiply(
                        special.widen_bigint(beta)));
            case NUM_DOUBLE:
                return special.norm_float(Double.valueOf(special.narrow_float(alpha) *
                        special.narrow_float(beta)));
            default:
                throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * /(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with A float divided by B.
     */
    private static Number arit_quot(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        switch (Math.max(numType(alpha), numType(beta))) {
            case NUM_INTEGER:
            case NUM_BIG_INTEGER:
                double d = special.narrow_float(beta);
                if (d == 0.0)
                    throw new ArithmeticException(OP_EVALUATION_ZERO_DIVISOR);
                return special.norm_float(Double.valueOf(special.narrow_float(alpha) / d));
            case NUM_DOUBLE:
                d = special.narrow_float(beta);
                if (d == 0.0)
                    throw new ArithmeticException(OP_EVALUATION_ZERO_DIVISOR);
                return special.norm_float(Double.valueOf(special.narrow_float(alpha) / d));
            default:
                throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * //(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with A truncate divided by B.
     */
    private static Number arit_intquot(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int u = beta.intValue();
            if (u == 0)
                throw new ArithmeticException(OP_EVALUATION_ZERO_DIVISOR);
            int v = alpha.intValue();
            if (v == Integer.MIN_VALUE && u == -1) {
                return special.NEG_MIN_INTEGER;
            } else {
                return Integer.valueOf(v / u);
            }
        } else {
            BigInteger p = special.widen_bigint(beta);
            if (p.signum() == 0)
                throw new ArithmeticException(OP_EVALUATION_ZERO_DIVISOR);
            return special.norm_bigint(special.widen_bigint(alpha).divide(p));
        }
    }

    /**
     * rem(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with A remainder B.
     */
    private static Number arit_rem(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int u = beta.intValue();
            if (u == 0)
                throw new ArithmeticException(OP_EVALUATION_ZERO_DIVISOR);
            return Integer.valueOf(alpha.intValue() % u);
        } else {
            BigInteger p = special.widen_bigint(beta);
            if (p.signum() == 0)
                throw new ArithmeticException(OP_EVALUATION_ZERO_DIVISOR);
            return special.norm_bigint(special.widen_bigint(alpha).remainder(p));
        }
    }

    /***************************************************************/
    /* float/1, (^)/3, div/3 and mod/3.                            */
    /***************************************************************/

    /**
     * float(A, B): [ISO 9.17]
     * The predicate succeeds in B with the approximated A.
     */
    private static Number arit_float(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return alpha instanceof Double ? alpha :
                Double.valueOf(special.narrow_float(alpha));
    }

    /**
     * ^(A, B, C): [TC2 9.3.10]
     * The predicate succeeds in C with A int power by B.
     */
    private static Number arit_intpow(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        switch (Math.max(numType(alpha), numType(beta))) {
            case NUM_INTEGER:
                int y = beta.intValue();
                if (y < 0)
                    throw new Handler.RangeException(OP_DOMAIN_NOT_LESS_THAN_ZERO, beta);
                int x = alpha.intValue();
                if (x != Integer.MIN_VALUE && bitlength(Math.abs(x)) * y < 63) {
                    return special.norm_smallint(intPow(x, y));
                } else {
                    return special.norm_bigint(BigInteger.valueOf(x).pow(y));
                }
            case NUM_BIG_INTEGER:
                if (beta instanceof BigInteger)
                    throw new IllegalArgumentException(OP_REPRESENTATION_INT);
                y = beta.intValue();
                if (y < 0)
                    throw new Handler.RangeException(OP_DOMAIN_NOT_LESS_THAN_ZERO, beta);
                return special.norm_bigint(((BigInteger) alpha).pow(y));
            case NUM_DOUBLE:
                return special.norm_float(Double.valueOf(Math.pow(special.narrow_float(alpha), special.narrow_float(beta))));
            default:
                throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    private static long intPow(long m, int n) {
        long r = 1;
        while (n != 0) {
            if ((n & 1) != 0)
                r *= m;
            n >>= 1;
            if (n != 0)
                m *= m;
        }
        return r;
    }

    private static int bitlength(long m) {
        return 64 - Long.numberOfLeadingZeros(m);
    }

    /**
     * div(A, B, C): [TC2 9.1.3]
     * The predicate succeeds in C with A floor divided by B.
     */
    private static Number arit_div(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int u = beta.intValue();
            if (u == 0)
                throw new ArithmeticException(OP_EVALUATION_ZERO_DIVISOR);
            int v = alpha.intValue();
            if (v == Integer.MIN_VALUE && u == -1) {
                return special.NEG_MIN_INTEGER;
            } else {
                return Integer.valueOf(Math.floorDiv(v, u));
            }
        } else {
            BigInteger p = special.widen_bigint(beta);
            if (p.signum() == 0)
                throw new ArithmeticException(OP_EVALUATION_ZERO_DIVISOR);
            BigInteger val = bigDiv(special.widen_bigint(alpha), p);
            return special.norm_bigint(val);
        }
    }

    private static BigInteger bigDiv(BigInteger v, BigInteger u) {
        BigInteger h;
        if ((v.signum() < 0) != (u.signum() < 0)) {
            BigInteger[] res = v.divideAndRemainder(u);
            h = res[0];
            if (res[1].signum() != 0)
                h = h.subtract(BigInteger.ONE);
        } else {
            h = v.divide(u);
        }
        return h;
    }

    /**
     * mod(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with A modulus B.
     */
    private static Number arit_mod(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int u = beta.intValue();
            if (u == 0)
                throw new ArithmeticException(OP_EVALUATION_ZERO_DIVISOR);
            return Integer.valueOf(Math.floorMod(alpha.intValue(), u));
        } else {
            BigInteger p = special.widen_bigint(beta);
            if (p.signum() == 0)
                throw new ArithmeticException(OP_EVALUATION_ZERO_DIVISOR);
            BigInteger val = bigMod(special.widen_bigint(alpha), p);
            return special.norm_bigint(val);
        }
    }

    private static BigInteger bigMod(BigInteger v, BigInteger u) {
        BigInteger res = v.remainder(u);
        if ((v.signum() < 0) != (u.signum() < 0)) {
            if (res.signum() != 0)
                res = res.add(u);
        }
        return res;
    }

    /****************************************************************/
    /* abs/2, sign/2, min/3 and max/3                               */
    /****************************************************************/

    /**
     * abs(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the absolute value of A.
     */
    private static Number arit_abs(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (alpha instanceof Integer) {
            int x = alpha.intValue();
            if (x != Integer.MIN_VALUE) {
                return Integer.valueOf(Math.abs(x));
            } else {
                return BigInteger.valueOf(Math.abs((long) x));
            }
        } else if (alpha instanceof BigInteger) {
            return special.norm_bigint(((BigInteger) alpha).abs());
        } else if (alpha instanceof Double) {
            return Double.valueOf(Math.abs(alpha.doubleValue()));
        } else {
            throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * sign(A, B): [ISO 9.1.4]
     * The predicate succeeds in B with the sign of A.
     */
    private static Number arit_sign(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (Machine.is_integer(alpha)) {
            return Integer.valueOf(integer_signum(alpha));
        } else {
            return Double.valueOf(Math.signum(alpha.doubleValue()));
        }
    }

    public static int integer_signum(Number num) {
        if (num instanceof Integer) {
            return Integer.signum(num.intValue());
        } else {
            return ((BigInteger) num).signum();
        }
    }

    /**
     * min(A, B, C): [TC2 9.3.9]
     * The predicate succeeds in C with the minimum of A and B.
     */
    private static Number arit_min(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        switch (Math.max(numType(alpha), numType(beta))) {
            case NUM_INTEGER:
            case NUM_BIG_INTEGER:
                if (integer_compare(alpha, beta) > 0) {
                    return beta;
                } else {
                    return alpha;
                }
            case NUM_DOUBLE:
                double a = special.narrow_float(alpha);
                double b = special.narrow_float(beta);
                if (a > b) {
                    alpha = beta;
                    a = b;
                }
                return (alpha instanceof Double ? alpha : Double.valueOf(a));
            default:
                throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * max(A, B, C): [TC2 9.3.8]
     * The predicate succeeds in C with the maximum of A and B.
     */
    private static Number arit_max(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        switch (Math.max(numType(alpha), numType(beta))) {
            case NUM_INTEGER:
            case NUM_BIG_INTEGER:
                if (integer_compare(alpha, beta) < 0) {
                    return beta;
                } else {
                    return alpha;
                }
            case NUM_DOUBLE:
                double a = special.narrow_float(alpha);
                double b = special.narrow_float(beta);
                if (a < b) {
                    alpha = beta;
                    a = b;
                }
                return (alpha instanceof Double ? alpha : Double.valueOf(a));
            default:
                throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /******************************************************************/
    /* truncate/2, floor/2, ceiling/2 and round/2                     */
    /******************************************************************/

    private static final long MIN_SAFE_LONG = -9007199254740991L;
    private static final long MAX_SAFE_LONG = 9007199254740991L;

    /**
     * truncate(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the truncate of A.
     */
    private static Number arit_truncate(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (alpha instanceof Integer || alpha instanceof BigInteger) {
            return alpha;
        } else if (alpha instanceof Double) {
            double d = alpha.doubleValue();
            if (MIN_SAFE_LONG <= d && d <= MAX_SAFE_LONG) {
                return special.norm_smallint((long) d);
            } else {
                return toInteger(d);
            }
        } else {
            throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * floor(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the floor of A.
     */
    private static Number arit_floor(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (alpha instanceof Integer || alpha instanceof BigInteger) {
            return alpha;
        } else if (alpha instanceof Double) {
            return toInteger(Math.floor(alpha.doubleValue()));
        } else {
            throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * ceiling(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the ceiling of A.
     */
    private static Number arit_ceiling(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (alpha instanceof Integer || alpha instanceof BigInteger) {
            return alpha;
        } else if (alpha instanceof Double) {
            return toInteger(Math.ceil(alpha.doubleValue()));
        } else {
            throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * round(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the rounding of A.
     */
    private static Number arit_round(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (alpha instanceof Integer || alpha instanceof BigInteger) {
            return alpha;
        } else if (alpha instanceof Double) {
            double d = alpha.doubleValue();
            if (MIN_SAFE_LONG <= d && d <= MAX_SAFE_LONG) {
                return special.norm_smallint(Math.round(d));
            } else {
                return toInteger(d);
            }
        } else {
            throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    private static Number toInteger(double d) {
        if (Long.MIN_VALUE <= d && d <= Long.MAX_VALUE) {
            return special.norm_smallint((long) d);
        } else {
            int y = Math.getExponent(d) - 52;
            long x = (long)Math.scalb(d, -y);
            return BigInteger.valueOf(x).shiftLeft(y);
        }
    }

    /*********************************************************************/
    /* =:=/2, =\=/2, </2, >=/2, >/2 and =</2                             */
    /*********************************************************************/

    /**
     * X =:= Y: [ISO 8.7.1]
     * The predicate succeeds when X number equals Y, otherwise fails.
     */
    private static boolean test_numberequal(Object[] args) {
        try {
            Number alpha = Machine.exec_eval(args[0]);
            Number beta = Machine.exec_eval(args[1]);
            return testEq(alpha, beta);
        } catch (RuntimeException x) {
            throw mapRuntime(x);
        }
    }

    /**
     * X =\= Y: [ISO 8.7.1]
     * The predicate succeeds when X does not number equal Y, otherwise fails.
     */
    private static boolean test_numbernotequal(Object[] args) {
        try {
            Number alpha = Machine.exec_eval(args[0]);
            Number beta = Machine.exec_eval(args[1]);
            return !testEq(alpha, beta);
        } catch (RuntimeException x) {
            throw mapRuntime(x);
        }
    }

    /**
     * <p>Check whether two Prolog numbers are equal.</p>
     *
     * @param m The first Prolog number.
     * @param n The second Prolog number.
     * @return True if they are equal, false otherwise.
     */
    private static boolean testEq(Number m, Number n) {
        switch (Math.max(numType(m), numType(n))) {
            case NUM_INTEGER:
            case NUM_BIG_INTEGER:
                return m.equals(n);
            case NUM_DOUBLE:
                return special.narrow_float(m)
                        == special.narrow_float(n);
            default:
                throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    /**
     * X < Y: [ISO 8.7.1]
     * The predicate succeeds when X is number less than Y, otherwise fails.
     */
    private static boolean test_numberless(Object[] args) {
        try {
            Number alpha = Machine.exec_eval(args[0]);
            Number beta = Machine.exec_eval(args[1]);
            return computeCmp(alpha, beta) < 0;
        } catch (RuntimeException x) {
            throw mapRuntime(x);
        }
    }

    /**
     * X >= Y: [ISO 8.7.1]
     * The predicate succeeds when X is number greater or equal to Y, otherwise fails.
     */
    private static boolean test_numbergreaterequal(Object[] args) {
        try {
            Number alpha = Machine.exec_eval(args[0]);
            Number beta = Machine.exec_eval(args[1]);
            return computeCmp(alpha, beta) >= 0;
        } catch (RuntimeException x) {
            throw mapRuntime(x);
        }
    }

    /**
     * X > Y: [ISO 8.7.1]
     * The predicate succeeds when X is number greater than Y, otherwise fails.
     */
    private static boolean test_numbergreater(Object[] args) {
        try {
            Number alpha = Machine.exec_eval(args[0]);
            Number beta = Machine.exec_eval(args[1]);
            return computeCmp(alpha, beta) > 0;
        } catch (RuntimeException x) {
            throw mapRuntime(x);
        }
    }

    /**
     * X =< Y: [ISO 8.7.1]
     * The predicate succeeds when X is number less or equal to Y, otherwise fails.
     */
    private static boolean test_numberlessequal(Object[] args) {
        try {
            Number alpha = Machine.exec_eval(args[0]);
            Number beta = Machine.exec_eval(args[1]);
            return computeCmp(alpha, beta) <= 0;
        } catch (RuntimeException x) {
            throw mapRuntime(x);
        }
    }

    /**
     * <p>Compare two Prolog numbers.</p>
     *
     * @param m The first Prolog number.
     * @param n The second Prolog number.
     * @return <0 m < n,  0 m == m, >0 m > n.
     */
    private static int computeCmp(Number m, Number n) {
        switch (Math.max(numType(m), numType(n))) {
            case NUM_INTEGER:
            case NUM_BIG_INTEGER:
                return integer_compare(m, n);
            case NUM_DOUBLE:
                double a = special.narrow_float(m);
                double b = special.narrow_float(n);
                return Double.compare(a, b);
            default:
                throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    private static int numType(Number m) {
        if (m instanceof Integer) {
            return NUM_INTEGER;
        } else if (m instanceof BigInteger) {
            return NUM_BIG_INTEGER;
        } else if (m instanceof Double) {
            return NUM_DOUBLE;
        } else {
            throw new UnsupportedOperationException(OP_PERMISSION_NOT_SUPPORTED);
        }
    }

    public static int integer_compare(Number alfa, Number beta) {
        if (alfa instanceof Integer) {
            if (beta instanceof Integer) {
                return ((Integer) alfa).compareTo((Integer) beta);
            } else {
                return -((BigInteger) beta).signum();
            }
        } else {
            if (beta instanceof Integer) {
                return ((BigInteger) alfa).signum();
            } else {
                return ((BigInteger) alfa).compareTo((BigInteger) beta);
            }
        }
    }

    /******************************************************************/
    /* sin/3, cos/2, tan/2, asin/2, acos/2, atan/2 and pi/1.          */
    /******************************************************************/

    /**
     * sin(A, B): [ISO 9.3.2]
     * The predicate succeeds in B with the sine of A.
     */
    private static Number arit_sin(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Double.valueOf(Math.sin(
                special.narrow_float(alpha))));
    }

    /**
     * cos(A, B): [ISO 9.3.3]
     * The predicate succeeds in B with the cosine of A.
     */
    private static Number arit_cos(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Double.valueOf(Math.cos(
                special.narrow_float(alpha))));
    }

    /**
     * tan(A, B): [TC2 9.3.14]
     * The predicate succeeds in B with the tangent of A.
     */
    private static Number arit_tan(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Double.valueOf(Math.tan(
                special.narrow_float(alpha))));
    }

    /**
     * asin(A, B): [TC2 9.3.11]
     * The predicate succeeds in B with the arcus sine of A.
     */
    private static Number arit_asin(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Double.valueOf(Math.asin(
                special.narrow_float(alpha))));
    }

    /**
     * acos(A, B): [TC2 9.3.12]
     * The predicate succeeds in B with the arcus cosine of A.
     */
    private static Number arit_acos(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Double.valueOf(Math.acos(
                special.narrow_float(alpha))));
    }

    /**
     * atan(A, B): [ISO 9.3.4]
     * The predicate succeeds in B with the arcus tangent of A.
     */
    private static Number arit_atan(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Double.valueOf(Math.atan(
                special.narrow_float(alpha))));
    }

    /**
     * pi(A): [TC2 9.3.15]
     * The predicate succeeds in A with π.
     */
    private static Number arit_pi(Object[] args) {
        return DOUBLE_PI;
    }

    /******************************************************************/
    /* (**)/3, exp/2, log/2, sqrt/2, e/1, epsilon/1 and atan2/3       */
    /******************************************************************/

    /**
     * **(A, B, C): [ISO 9.3.1]
     * The predicate succeeds in C with A float power by B.
     */
    private static Number arit_pow(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return special.norm_float(Double.valueOf(Math.pow(
                special.narrow_float(alpha),
                special.narrow_float(beta))));
    }

    /**
     * exp(A, B): [ISO 9.3.5]
     * The predicate succeeds in B with e power by A.
     */
    private static Number arit_exp(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Double.valueOf(Math.exp(
                special.narrow_float(alpha))));
    }

    /**
     * log(A, B): [ISO 9.3.6]
     * The predicate succeeds in B with the natural logarithm of A.
     */
    private static Number arit_log(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Double.valueOf(Math.log(
                special.narrow_float(alpha))));
    }

    /**
     * sqrt(A, B): [ISO 9.3.7]
     * The predicate succeeds in B with the square root of A.
     */
    private static Number arit_sqrt(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Double.valueOf(Math.sqrt(
                special.narrow_float(alpha))));
    }

    /**
     * e(A): [N208 9.7.2]
     * The predicate succeeds in A with the Euler number.
     */
    private static Number arit_e(Object[] args) {
        return DOUBLE_E;
    }

    /**
     * epsilon(A): [N208 9.7.3]
     * The predicate succeeds in A with the machine epsilon.
     */
    private static Number arit_epsilon(Object[] args) {
        return DOUBLE_EPSILON;
    }

    /**
     * atan2(A, B, C): [TC2 9.3.13]
     * The predicate succeeds in C with the arc tangent of A and B.
     */
    private static Number arit_atan2(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        double y = special.narrow_float(alpha);
        double x = special.narrow_float(beta);
        if (y == 0 && x == 0) {
            throw new ArithmeticException(OP_EVALUATION_UNDEFINED);
        } else {
            return special.norm_float(Double.valueOf(Math.atan2(y, x)));
        }
    }

    /*********************************************************************/
    /* (\)/2, (/\)/3, (\/)/3, (xor)/3, (>>)/3 and (<</3)                 */
    /*********************************************************************/

    /**
     * \(A, B): [ISO 9.4.5]
     * The predicate succeeds in B with the bitwise not of A.
     */
    private static Number arit_not(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        if (alpha instanceof Integer) {
            return Integer.valueOf(~alpha.intValue());
        } else {
            return special.norm_bigint(((BigInteger) alpha).not());
        }
    }

    /**
     * /\(A, B, C): [ISO 9.4.3]
     * The predicate succeeds in C with the bitwise and of A and B.
     */
    private static Number arit_and(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            return Integer.valueOf(alpha.intValue() & beta.intValue());
        } else {
            BigInteger val = special.widen_bigint(alpha).and(
                    special.widen_bigint(beta));
            return special.norm_bigint(val);
        }
    }

    /**
     * \/(A, B, C): [ISO 9.4.4]
     * The predicate succeeds in C with the bitwise or of A and B.
     */
    private static Number arit_or(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            return Integer.valueOf(alpha.intValue() | beta.intValue());
        } else {
            BigInteger val = special.widen_bigint(alpha).or(
                    special.widen_bigint(beta));
            return special.norm_bigint(val);
        }
    }

    /**
     * xor(A, B, C): [TC2 9.4.6]
     * The predicate succeeds in C with the bitwise xor of A and B.
     */
    private static Number arit_xor(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            return Integer.valueOf(alpha.intValue() ^ beta.intValue());
        } else {
            BigInteger val = special.widen_bigint(alpha).xor(
                    special.widen_bigint(beta));
            return special.norm_bigint(val);
        }
    }

    /**
     * >>(A, B, C): [ISO 9.4.1]
     * The predicate succeeds in C with A shift right by B.
     */
    private static Number arit_shiftright(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int y = beta.intValue();
            if (y == 0) {
                return alpha;
            } else if (y > 0 && y <= 31) {
                return Integer.valueOf(alpha.intValue() >> y);
            } else if (y < 0 && -31 <= y) {
                long val = (long) alpha.intValue() << (-y);
                return special.norm_smallint(val);
            } else {
                return special.norm_bigint(BigInteger.valueOf(alpha.intValue()).shiftRight(y));
            }
        } else {
            if (beta instanceof BigInteger)
                throw new IllegalArgumentException(OP_REPRESENTATION_INT);
            int y = beta.intValue();
            return special.norm_bigint(((BigInteger) alpha).shiftRight(y));
        }
    }

    /**
     * <<(A, B, C): [ISO 9.4.2]
     * The predicate succeeds in C with A shift left by B.
     */
    private static Number arit_shiftleft(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int y = beta.intValue();
            if (y == 0) {
                return alpha;
            } else if (y > 0 && y <= 31) {
                long val = (long) alpha.intValue() << y;
                return special.norm_smallint(val);
            } else if (y < 0 && -31 <= y) {
                return Integer.valueOf(alpha.intValue() >> (-y));
            } else {
                return special.norm_bigint(BigInteger.valueOf(alpha.intValue()).shiftLeft(y));
            }
        } else {
            if (beta instanceof BigInteger)
                throw new IllegalArgumentException(OP_REPRESENTATION_INT);
            int y = beta.intValue();
            return special.norm_bigint(((BigInteger) alpha).shiftLeft(y));
        }
    }

    /***************************************************************/
    /* ==/2, \==/2                                                 */
    /***************************************************************/

    /**
     * S == T: [ISO 8.4.1]
     * The built-in succeeds when S and T are syntactically equivalent
     * Prolog terms, otherwise the built-in fails.
     */
    private static boolean test_equal(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        Object beta = Machine.exec_build(args[1]);
        return equal_term(alpha, beta);
    }

    /**
     * S \== T: [ISO 8.4.1]
     * The built-in succeeds when S and T are not syntactically equivalent
     * Prolog terms, otherwise the built-in fails.
     */
    private static boolean test_notequal(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        Object beta = Machine.exec_build(args[1]);
        return !equal_term(alpha, beta);
    }

    /**
     * Determine whether two Prolog terms are syntactically equivalent.
     * Can handle cyclic terms and deep recursion.
     *
     * @param first  The first Prolog term.
     * @param second The second Prolog term.
     * @return True if they are syntactically equivalent, otherwise false.
     */
    public static boolean equal_term(Object first, Object second) {
        List stack = null;
        List log = null;
        try {
            for (; ; ) {
                first = Store.deref(first);
                second = Store.deref(second);
                if (!Store.is_structure(first)) {
                    if (!Objects.equals(first, second))
                        break;
                } else if (!Store.is_structure(second)) {
                    break;
                } else if (((Store.Structure) first).args.length !=
                        ((Store.Structure) second).args.length) {
                    break;
                } else {
                    first = Machine.union_find((Store.Structure) first);
                    second = Machine.union_find((Store.Structure) second);
                    if (!((Store.Structure) first).functor.equals(
                            ((Store.Structure) second).functor)) {
                        break;
                    } else if (first != second) {
                        log = Machine.union_add(log, (Store.Structure) first,
                                (Store.Structure) second);
                        if (0 != ((Store.Structure) first).args.length - 1) {
                            Machine.Item item2 = new Machine.Item((Store.Structure)first, second, 0);
                            stack = Store.stack_push(stack, item2);
                        }
                        first = ((Store.Structure) first).args[0];
                        second = ((Store.Structure) second).args[0];
                        continue;
                    }
                }
                Machine.Item item = (Machine.Item)Store.stack_peek(stack);
                if (item == null) {
                    return true;
                } else {
                    item.idx++;
                    first = item.first.args[item.idx];
                    second = ((Store.Structure)item.second).args[item.idx];
                    if (item.idx == item.first.args.length - 1)
                        Store.stack_pop(stack);
                }
            }
            return false;
        } finally {
            Machine.union_undo(log);
        }
    }

    /***************************************************************/
    /* same_term/2 and hash_code/2                                 */
    /***************************************************************/

    /**
     * same_term(A, B):
     * The built-in succeeds when A and B are equal objects,
     * otherwise the built-in fails.
     */
    private static boolean test_same_term(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        Object beta = Machine.exec_build(args[1]);
        return Objects.equals(alpha, beta);
    }

    /**
     * hash_code(X, H):
     * The predicate succeeds in H with the hash code of the object X.
     */
    private static boolean test_hash_code(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        alpha = object_hash_code(alpha);
        return Machine.exec_unify(args[1], alpha);
    }

    public static Integer object_hash_code(Object alpha) {
        if (Store.is_variable(alpha)) {
            return Integer.valueOf(variable_serno((Store.Variable) alpha));
        } else if (!Store.is_structure(alpha)) {
            return Integer.valueOf(Objects.hashCode(alpha));
        } else {
            return Integer.valueOf(2467);
        }
    }

    public static int variable_serno(Store.Variable alpha) {
        int val = alpha.flags & Store.VAR_MASK_SERNO;
        if (val == Store.VAR_MASK_SERNO) {
            if (Store.engine.low < Store.engine.high) {
                val = Store.engine.low;
                Store.engine.low = val + 1;
            } else {
                val = Store.engine.serno;
                Store.engine.serno = val + 1;
            }
            alpha.flags &= ~Store.VAR_MASK_SERNO;
            alpha.flags |= val;
        }
        return val;
    }

    /******************************************************************/
    /* atom_codes/2 and char_code/2                                   */
    /******************************************************************/

    /**
     * atom_codes(A, L): [ISO 8.16.5]
     * If A is a variable, the built-in succeeds in A with the atom
     * for the Prolog list L. Otherwise the built-in succeeds in L
     * with the Prolog list from the atom A.
     */
    private static boolean test_atom_codes(Object[] args) {
        Object text = Machine.exec_deref(args[0]);
        if (Store.is_variable(text) || Machine.is_pending(text)) {
            Object res = Machine.exec_build(args[1]);
            res = atom_codes_pack(res);
            return Machine.exec_unify(text, res);
        } else {
            text = Machine.exec_build(text);
            special.check_atom(text);
            text = atom_codes_unpack((String) text);
            return Machine.exec_unify(args[1], text);
        }
    }

    private static String atom_codes_pack(Object peek) {
        int i = 0;
        StringBuilder buf = new StringBuilder();
        while (Store.is_structure(peek) &&
                ".".equals(((Store.Structure) peek).functor) &&
                ((Store.Structure) peek).args.length == 2 &&
                i < special.MAX_ARITY) {
            Object alpha = Store.deref(((Store.Structure) peek).args[0]);
            special.check_integer(alpha);
            int ch = (!special.is_bigint(alpha) ? ((Integer) alpha).intValue() : -1);
            if (ch < 0 || ch > 0x10FFFF)
                throw Machine.make_error(new Store.Compound("type_error",
                        new Object[]{"character_code", alpha}));
            buf.appendCodePoint(ch);
            i++;
            peek = Store.deref(((Store.Structure) peek).args[1]);
        }
        special.check_nil(peek);
        return buf.toString();
    }

    private static Object atom_codes_unpack(String text) {
        Store.Structure back = null;
        Object res = null;
        int i = 0;
        while (i < text.length()) {
            int ch = text.codePointAt(i);
            Store.Structure peek = new Store.Compound(".",
                    new Object[]{Integer.valueOf(ch), Store.UNDEF_OBJ});
            if (back != null) {
                back.args[1] = peek;
            } else {
                res = peek;
            }
            back = peek;
            i += Character.charCount(ch);
        }
        if (back != null) {
            back.args[1] = "[]";
        } else {
            res = "[]";
        }
        return res;
    }

    /**
     * char_code(C, N): [ISO 8.16.6]
     * If C is a variable, the built-in succeeds in C with the
     * character for the code N. Otherwise the built-in succeeds
     * in N with the code from character C.
     */
    private static boolean test_char_code(Object[] args) {
        Object text = Machine.exec_deref(args[0]);
        if (Store.is_variable(text) || Machine.is_pending(text)) {
            Object alpha = Machine.exec_build(args[1]);
            special.check_integer(alpha);
            int ch = (!special.is_bigint(alpha) ? ((Integer) alpha).intValue() : -1);
            if (ch < 0 || ch > 0x10FFFF)
                throw Machine.make_error(new Store.Compound("type_error",
                        new Object[]{"character_code", alpha}));
            if (Character.isBmpCodePoint(ch)) {
                alpha = String.valueOf((char) ch);
            } else {
                alpha = new String(Character.toChars(ch));
            }
            return Machine.exec_unify(text, alpha);
        } else {
            text = Machine.exec_build(text);
            special.check_atom(text);
            int ch;
            if (((String) text).length() == 0 ||
                    ((String) text).length() != Character.charCount(
                            ch = ((String) text).codePointAt(0)))
                throw Machine.make_error(new Store.Compound("type_error",
                        new Object[]{"character", text}));
            return Machine.exec_unify(args[1], Integer.valueOf(ch));
        }
    }

    /******************************************************************/
    /* atom_length/2 and atom_concat/3                                */
    /******************************************************************/

    /**
     * atom_length(X, Y): [ISO 8.16.1]
     * The predicate succeeds in Y with the length of the atom X.
     */
    private static boolean test_atom_length(Object[] args) {
        Object text = Machine.exec_build(args[0]);
        special.check_atom(text);
        int res = atom_count((String) text, 0, ((String) text).length());
        return Machine.exec_unify(args[1], Integer.valueOf(res));
    }

    private static int atom_count(String text, int from, int to) {
        int res = 0;
        while (from < to) {
            int ch = text.codePointAt(from);
            from += Character.charCount(ch);
            res++;
        }
        return res;
    }

    /**
     * atom_concat(X, Y, Z): [ISO 8.16.2]
     * The built-in succeeds when Z is the concatenation of X and Y.
     */
    private static Object special_atom_concat(Object[] args) {
        Object first = Store.deref(args[0]);
        Object second = Store.deref(args[1]);
        Object third = Store.deref(args[2]);
        if (Store.is_variable(second)) {
            if (Store.is_variable(first)) {
                special.check_atom(third);
                return solve2_concat(args, null, 0, null);
            } else {
                special.check_atom(first);
                special.check_atom(third);
                if (!((String) third).startsWith((String) first))
                    return Boolean.FALSE;
                if (!Machine.unify(second, ((String) third).substring(((String) first).length())))
                    return Boolean.FALSE;
            }
        } else if (Store.is_variable(first)) {
            special.check_atom(second);
            special.check_atom(third);
            if (!((String) third).endsWith((String) second))
                return Boolean.FALSE;
            if (!Machine.unify(first, ((String) third).substring(0,
                    ((String) third).length() - ((String) second).length())))
                return Boolean.FALSE;
        } else {
            special.check_atom(first);
            special.check_atom(second);
            if (!Machine.unify(third, (String) first + (String) second))
                return Boolean.FALSE;
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    private static Object solve_concat(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_concat(((Store.Structure) goal).args, rope, at, choice);
    }

    private static Object solve2_concat(Object[] args, Object rope, int at, Machine.Choice choice) {
        String text = (String) Store.deref(args[2]);
        Store.Variable mark = Machine.trail;
        while (at <= text.length()) {
            if (Machine.unify(args[0], text.substring(0, at))) {
                if (Machine.unify(args[1], text.substring(at))) {
                    at = atom_succ(text, at);
                    if (at <= text.length()) {
                        if (choice == null) {
                            choice = new Machine.Choice(eval::solve_concat, null, at, mark);
                        } else {
                            choice.at = at;
                        }
                        Machine.more(choice);
                    }
                    Machine.cont(((Store.Structure) Machine.call).args[1]);
                    return Boolean.TRUE;
                }
            }
            Machine.unbind(mark);
            at = atom_succ(text, at);
        }
        return Boolean.FALSE;
    }

    private static int atom_offset(String text, int pos, int alpha) {
        if (alpha < 0)
            return -1;
        while (alpha > 0 && pos < text.length()) {
            int ch = text.codePointAt(pos);
            pos += Character.charCount(ch);
            alpha--;
        }
        if (alpha > 0)
            return -1;
        return pos;
    }

    private static int atom_succ(String text, int at) {
        if (at < text.length()) {
            int ch = text.codePointAt(at);
            return at + Character.charCount(ch);
        } else {
            return at + 1;
        }
    }

    /******************************************************************/
    /* atom_join/2                                                    */
    /******************************************************************/

    /**
     * atom_join(L, A):
     * The built-in succeeds in A with the join of the atoms L.
     */
    private static boolean test_atom_join(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        Object res = atom_join(alpha);
        return Machine.exec_unify(args[1], res);
    }

    private static String atom_join(Object peek) {
        StringBuilder buf = new StringBuilder();
        int i = 0;
        while (Store.is_structure(peek) &&
                ".".equals(((Store.Structure) peek).functor) &&
                ((Store.Structure) peek).args.length == 2 &&
                i < special.MAX_ARITY) {
            Object val = Store.deref(((Store.Structure) peek).args[0]);
            special.check_atom(val);
            buf.append((String) val);
            peek = Store.deref(((Store.Structure) peek).args[1]);
            i++;
        }
        special.check_nil(peek);
        return buf.toString();
    }

    /******************************************************************/
    /* sys_atom_match/3 and sys_atom_part/4                           */
    /******************************************************************/

    /**
     * sys_atom_match(X, Y, Z):
     * The built-in succeeds if X has substring Y at Z.
     */
    private static Object special_sys_atom_match(Object[] args) {
        Object text = Store.deref(args[0]);
        special.check_atom(text);
        Object part = Store.deref(args[1]);
        special.check_atom(part);
        Object alpha = Store.deref(args[2]);
        if (Store.is_variable(alpha)) {
            return solve2_match(args, Integer.valueOf(0), 0, null);
        } else {
            special.check_integer(alpha);
            if (special.is_bigint(alpha))
                return Boolean.FALSE;
            int from = atom_offset((String) text, 0, ((Integer) alpha).intValue());
            if (from < 0)
                return Boolean.FALSE;
            if (!((String) text).startsWith((String) part, from))
                return Boolean.FALSE;
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    private static Object solve_match(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_match(((Store.Structure) goal).args, rope, at, choice);
    }

    private static Object solve2_match(Object[] args, Object data, int at, Machine.Choice choice) {
        String text = (String) Store.deref(args[0]);
        String part = (String) Store.deref(args[1]);
        int res = ((Integer) data).intValue();
        Store.Variable mark = Machine.trail;
        while (at + part.length() <= text.length()) {
            int pos = text.indexOf(part, at);
            if (pos < 0)
                return Boolean.FALSE;
            res += atom_count(text, at, pos);
            at = pos;
            if (Machine.unify(args[2], Integer.valueOf(res))) {
                at = atom_succ(text, at);
                res++;
                if (at + part.length() <= text.length()) {
                    if (choice == null) {
                        choice = new Machine.Choice(eval::solve_match, Integer.valueOf(res), at, mark);
                    } else {
                        choice.data = Integer.valueOf(res);
                        choice.at = at;
                    }
                    Machine.more(choice);
                }
                Machine.cont(((Store.Structure) Machine.call).args[1]);
                return true;
            }
            Machine.unbind(mark);
            at = atom_succ(text, at);
            res++;
        }
        return false;
    }

    /**
     * sys_atom_part(X, Y, Z, T):
     * The built-in succeeds in T with the substring at
     * offset Y and with length Z from X.
     */
    private static Object special_sys_atom_part(Object[] args) {
        Object text = Store.deref(args[0]);
        special.check_atom(text);
        Object alpha = Store.deref(args[1]);
        if (Store.is_variable(alpha)) {
            Object beta = Store.deref(args[2]);
            special.check_integer(beta);
            if (special.is_bigint(beta))
                return Boolean.FALSE;
            int pos = atom_offset((String) text, 0, ((Integer) beta).intValue());
            if (pos < 0)
                return Boolean.FALSE;
            return solve2_part(args, new int[]{0, 0}, pos, null);
        } else {
            special.check_integer(alpha);
            Object beta = Store.deref(args[2]);
            special.check_integer(beta);
            if (special.is_bigint(alpha))
                return Boolean.FALSE;
            int from = atom_offset((String) text, 0, ((Integer) alpha).intValue());
            if (from < 0)
                return Boolean.FALSE;
            if (special.is_bigint(beta))
                return Boolean.FALSE;
            int to = atom_offset((String) text, from, ((Integer) beta).intValue());
            if (to < 0)
                return Boolean.FALSE;
            if (!Machine.unify(args[3], ((String) text).substring(from, to)))
                return Boolean.FALSE;
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    private static Object solve_part(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_part(((Store.Structure) goal).args, rope, at, choice);
    }

    private static Object solve2_part(Object[] args, Object data, int to, Machine.Choice choice) {
        String text = (String) Store.deref(args[0]);
        int[] pair = (int[]) data;
        Store.Variable mark = Machine.trail;
        while (to <= text.length()) {
            if (Machine.unify(args[1], Integer.valueOf(pair[1]))) {
                if (Machine.unify(args[3], text.substring(pair[0], to))) {
                    pair[0] = atom_succ(text, pair[0]);
                    pair[1]++;
                    to = atom_succ(text, to);
                    if (to <= text.length()) {
                        if (choice == null) {
                            choice = new Machine.Choice(eval::solve_part, pair, to, mark);
                        } else {
                            choice.at = to;
                        }
                        Machine.more(choice);
                    }
                    Machine.cont(((Store.Structure) Machine.call).args[1]);
                    return Boolean.TRUE;
                }
            }
            Machine.unbind(mark);
            pair[0] = atom_succ(text, pair[0]);
            pair[1]++;
            to = atom_succ(text, to);
        }
        return Boolean.FALSE;
    }

    /******************************************************************/
    /* sys_last_atom_match/3 and sys_last_atom_part/4                 */
    /******************************************************************/

    /**
     * sys_last_atom_match(X, Y, Z):
     * The built-in succeeds if X has substring Y at Z.
     */
    public static Object special_sys_last_atom_match(Object[] args) {
        Object text = Store.deref(args[0]);
        special.check_atom(text);
        Object part = Store.deref(args[1]);
        special.check_atom(part);
        Object alpha = Store.deref(args[2]);
        if (Store.is_variable(alpha)) {
            int at = ((String) text).length() - ((String) part).length();
            int res = atom_count((String) text, 0, at);
            return solve2_last_match(args, Integer.valueOf(res), at, null);
        } else {
            special.check_integer(alpha);
            if (special.is_bigint(alpha))
                return Boolean.FALSE;
            int from = atom_offset((String) text, 0, ((Integer) alpha).intValue());
            if (from < 0)
                return Boolean.FALSE;
            if (!((String) text).startsWith((String) part, from))
                return Boolean.FALSE;
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    private static Object solve_last_match(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_last_match(((Store.Structure) goal).args, rope, at, choice);
    }

    public static Object solve2_last_match(Object[] args, Object data, int at, Machine.Choice choice) {
        String text = (String) Store.deref(args[0]);
        String part = (String) Store.deref(args[1]);
        int res = ((Integer) data).intValue();
        Store.Variable mark = Machine.trail;
        while (at >= 0) {
            int pos = text.lastIndexOf(part, at);
            if (pos < 0)
                return false;
            res -= atom_count(text, pos, at);
            at = pos;
            if (Machine.unify(args[2], Integer.valueOf(res))) {
                at = atom_pred(text, at);
                res--;
                if (at >= 0) {
                    if (choice == null) {
                        choice = new Machine.Choice(eval::solve_last_match, res, at, mark);
                    } else {
                        choice.data = res;
                        choice.at = at;
                    }
                    Machine.more(choice);
                }
                Machine.cont(((Store.Structure) Machine.call).args[1]);
                return true;
            }
            Machine.unbind(mark);
            at = atom_pred(text, at);
            res--;
        }
        return false;
    }

    private static int atom_pred(String text, int at) {
        if (0 < at) {
            int ch = text.codePointBefore(at);
            return at - Character.charCount(ch);
        } else {
            return at - 1;
        }
    }

    /**
     * sys_last_atom_part(X, Y, Z, T):
     * The built-in succeeds in T with the substring at
     * offset Y and with length Z from X.
     */
    public static Object special_sys_last_atom_part(Object[] args) {
        Object text = Store.deref(args[0]);
        special.check_atom(text);
        Object alpha = Store.deref(args[1]);
        if (Store.is_variable(alpha)) {
            Object beta = Store.deref(args[2]);
            special.check_integer(beta);
            if (special.is_bigint(beta))
                return Boolean.FALSE;
            int pos = last_atom_offset((String) text, ((String) text).length(),
                    ((Integer) beta).intValue());
            if (pos < 0)
                return Boolean.FALSE;
            int res = atom_count(((String) text), 0, pos);
            return solve2_last_part(args, new int[]{pos, res},
                    ((String) text).length(), null);
        } else {
            special.check_integer(alpha);
            Object beta = Store.deref(args[2]);
            special.check_integer(beta);
            if (special.is_bigint(alpha))
                return Boolean.FALSE;
            int from = atom_offset((String) text, 0, ((Integer) alpha).intValue());
            if (from < 0)
                return Boolean.FALSE;
            if (special.is_bigint(beta))
                return Boolean.FALSE;
            int to = atom_offset((String) text, from, ((Integer) beta).intValue());
            if (to < 0)
                return Boolean.FALSE;
            if (!Machine.unify(args[3], ((String) text).substring(from, to)))
                return Boolean.FALSE;
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    private static int last_atom_offset(String text, int pos, int alpha) {
        if (alpha < 0)
            return -1;
        while (alpha > 0 && 0 < pos) {
            int ch = text.codePointBefore(pos);
            pos -= Character.charCount(ch);
            alpha--;
        }
        if (alpha > 0)
            return -1;
        return pos;
    }

    private static Object solve_last_part(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_last_part(((Store.Structure) goal).args, rope, at, choice);
    }

    public static Object solve2_last_part(Object[] args, Object data, int to, Machine.Choice choice) {
        String text = (String) Store.deref(args[0]);
        int[] pair = (int[]) data;
        Store.Variable mark = Machine.trail;
        while (pair[0] >= 0) {
            if (Machine.unify(args[1], Integer.valueOf(pair[1]))) {
                if (Machine.unify(args[3], text.substring(pair[0], to))) {
                    pair[0] = atom_pred(text, pair[0]);
                    pair[1]--;
                    to = atom_pred(text, to);
                    if (pair[0] >= 0) {
                        if (choice == null) {
                            choice = new Machine.Choice(eval::solve_last_part, pair, to, mark);
                        } else {
                            choice.at = to;
                        }
                        Machine.more(choice);
                    }
                    Machine.cont(((Store.Structure) Machine.call).args[1]);
                    return Boolean.TRUE;
                }
            }
            Machine.unbind(mark);
            pair[0] = atom_pred(text, pair[0]);
            pair[1]--;
            to = atom_pred(text, to);
        }
        return Boolean.FALSE;
    }

    /*********************************************************************/
    /* Eval Init                                                         */
    /*********************************************************************/

    public static void boot() {
        // number specials, basic predicates
        Store.add("is", 2, special.make_check(eval::test_eval));

        // number specials, basic operations
        Store.add("-", 2, special.make_arithmetic(eval::arit_neg));
        Store.add("+", 3, special.make_arithmetic(eval::arit_add));
        Store.add("-", 3, special.make_arithmetic(eval::arit_sub));
        Store.add("*", 3, special.make_arithmetic(eval::arit_mul));
        Store.add("/", 3, special.make_arithmetic(eval::arit_quot));
        Store.add("//", 3, special.make_arithmetic(eval::arit_intquot));
        Store.add("rem", 3, special.make_arithmetic(eval::arit_rem));

        // number specials, more operations
        Store.add("float", 2, special.make_arithmetic(eval::arit_float));
        Store.add("^", 3, special.make_arithmetic(eval::arit_intpow));
        Store.add("div", 3, special.make_arithmetic(eval::arit_div));
        Store.add("mod", 3, special.make_arithmetic(eval::arit_mod));

        // number specials, magnitude operations
        Store.add("abs", 2, special.make_arithmetic(eval::arit_abs));
        Store.add("sign", 2, special.make_arithmetic(eval::arit_sign));
        Store.add("min", 3, special.make_arithmetic(eval::arit_min));
        Store.add("max", 3, special.make_arithmetic(eval::arit_max));

        // number specials, rounding operations
        Store.add("truncate", 2, special.make_arithmetic(eval::arit_truncate));
        Store.add("floor", 2, special.make_arithmetic(eval::arit_floor));
        Store.add("ceiling", 2, special.make_arithmetic(eval::arit_ceiling));
        Store.add("round", 2, special.make_arithmetic(eval::arit_round));

        // number specials, magnitude predicates
        Store.add("=:=", 2, special.make_check(eval::test_numberequal));
        Store.add("=\\=", 2, special.make_check(eval::test_numbernotequal));
        Store.add("<", 2, special.make_check(eval::test_numberless));
        Store.add(">=", 2, special.make_check(eval::test_numbergreaterequal));
        Store.add(">", 2, special.make_check(eval::test_numbergreater));
        Store.add("=<", 2, special.make_check(eval::test_numberlessequal));

        // number specials, trigonometric operations
        Store.add("sin", 2, special.make_arithmetic(eval::arit_sin));
        Store.add("cos", 2, special.make_arithmetic(eval::arit_cos));
        Store.add("tan", 2, special.make_arithmetic(eval::arit_tan));
        Store.add("asin", 2, special.make_arithmetic(eval::arit_asin));
        Store.add("acos", 2, special.make_arithmetic(eval::arit_acos));
        Store.add("atan", 2, special.make_arithmetic(eval::arit_atan));
        Store.add("pi", 1, special.make_arithmetic(eval::arit_pi));

        // number specials, exponential operations
        Store.add("**", 3, special.make_arithmetic(eval::arit_pow));
        Store.add("exp", 2, special.make_arithmetic(eval::arit_exp));
        Store.add("log", 2, special.make_arithmetic(eval::arit_log));
        Store.add("sqrt", 2, special.make_arithmetic(eval::arit_sqrt));
        Store.add("e", 1, special.make_arithmetic(eval::arit_e));
        Store.add("epsilon", 1, special.make_arithmetic(eval::arit_epsilon));
        Store.add("atan2", 3, special.make_arithmetic(eval::arit_atan2));

        // number specials, bitwise operations
        Store.add("\\", 2, special.make_arithmetic(eval::arit_not));
        Store.add("/\\", 3, special.make_arithmetic(eval::arit_and));
        Store.add("\\/", 3, special.make_arithmetic(eval::arit_or));
        Store.add("xor", 3, special.make_arithmetic(eval::arit_xor));
        Store.add(">>", 3, special.make_arithmetic(eval::arit_shiftright));
        Store.add("<<", 3, special.make_arithmetic(eval::arit_shiftleft));

        // term specials, miscellaneous equality
        Store.add("==", 2, special.make_check(eval::test_equal));
        Store.add("\\==", 2, special.make_check(eval::test_notequal));
        Store.add("same_term", 2, special.make_check(eval::test_same_term));
        Store.add("hash_code", 2, special.make_check(eval::test_hash_code));

        // atom specials, standard
        Store.add("atom_codes", 2, special.make_check(eval::test_atom_codes));
        Store.add("char_code", 2, special.make_check(eval::test_char_code));
        Store.add("atom_length", 2, special.make_check(eval::test_atom_length));
        Store.add("atom_concat", 3, special.make_special(eval::special_atom_concat));

        // atom specials, non-standard
        Store.add("atom_join", 2, special.make_check(eval::test_atom_join));
        Store.add("sys_atom_match", 3, special.make_special(eval::special_sys_atom_match));
        Store.add("sys_atom_part", 4, special.make_special(eval::special_sys_atom_part));
        Store.add("sys_last_atom_match", 3, special.make_special(eval::special_sys_last_atom_match));
        Store.add("sys_last_atom_part", 4, special.make_special(eval::special_sys_last_atom_part));
    }

}
