package nova;

import java.lang.reflect.*;
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 special {
    public static final BigInteger MIN_INTEGER = BigInteger.valueOf(Integer.MIN_VALUE);
    public static final BigInteger MAX_INTEGER = BigInteger.valueOf(Integer.MAX_VALUE);
    public static final Double ZERO_DOUBLE = Double.valueOf(0.0);
    public static final BigInteger NEG_MIN_INTEGER = BigInteger.valueOf(-(long) Integer.MIN_VALUE);

    public static int MAX_ARITY = 2147483647;
    public static final String NAT_EXT = ".java";

    public static String[] main_args = new String[0];
    public static String[] classpath = null;

    /**
     * Set the program arguments.
     *
     * @param a The program arguments.
     */
    public static void set_args(String[] a) {
        main_args = a;
    }

    /**
     * <p>Set the class path.</p>
     *
     * @param cp The class path.
     */
    public static void set_classpath(String[] cp) {
        classpath = cp;
    }

    /******************************************************************/
    /* Special Predicate                                              */
    /******************************************************************/

    /**
     * Return a built-in for a special.
     *
     * @param func The special.
     * @return Provable The built-in.
     */
    public static Store.Provable make_special(Handler.Builtin func) {
        Store.Provable peek = new Store.Provable();
        peek.flags |= Store.MASK_PRED_SPECIAL;
        peek.func = func;
        return peek;
    }

    /**
     * Return a built-in for a check.
     *
     * @param func The check.
     * @return Provable The built-in.
     */
    public static Store.Provable make_check(Handler.Check func) {
        Store.Provable peek = new Store.Provable();
        peek.flags |= Store.MASK_PRED_TEST;
        peek.func = func;
        return peek;
    }

    /**
     * Return a built-in for an arithmetic.
     *
     * @param func The arithmetic.
     * @return Provable The built-in.
     */
    public static Store.Provable make_arithmetic(Handler.Funktion func) {
        Store.Provable peek = new Store.Provable();
        peek.flags |= Store.MASK_PRED_ARITH;
        peek.func = func;
        return peek;
    }

    /******************************************************************/
    /* fail/0, '$CUT'/1 and '$MARK'/1                                 */
    /******************************************************************/

    /**
     * fail: [ISO 7.8.2]
     * The built-in fails.
     */
    private static boolean test_fail(Object[] args) {
        return false;
    }

    /**
     * '$CUT'(R): internal only
     * The built-in removes the choice points up to R and succeeds.
     */
    private static boolean test_cut(Object[] args) {
        Object choice = Machine.exec_build(args[0]);
        Machine.more((Machine.Choice) choice);
        return true;
    }

    /**
     * '$MARK'(R): Internal only
     * The built-in binds R to the top choice point.
     */
    private static boolean test_mark(Object[] args) {
        Object choice = Machine.redo;
        return Machine.exec_unify(args[0], choice);
    }

    /******************************************************************/
    /* '$SEQ'/2 and '$ALT'/1                                          */
    /******************************************************************/

    /**
     * '$SEQ'(O, L): internal only
     * The built-in first matches the option O with the top choice point.
     * The built-in then sequentially adds the goals L to the continuation.
     */
    private static Object special_seq(Object[] args) {
        Object temp = Store.deref(args[0]);
        solve_mark(temp);
        temp = Store.deref(args[1]);
        solve_seq(temp);
        return Boolean.TRUE;
    }

    /**
     * If temp is just(R) bind R the top choice point. If temp is
     * nothing do nothing. Otherwise throw an exception.
     *
     * @param temp The Prolog term.
     */
    private static void solve_mark(Object temp) {
        if ("nothing".equals(temp)) {
            /* */
        } else if (Store.is_structure(temp) &&
                "just".equals(((Store.Structure) temp).functor) &&
                ((Store.Structure) temp).args.length == 1) {
            temp = Store.deref(((Store.Structure) temp).args[0]);
            Machine.bind(Machine.redo, (Store.Variable) temp);
        } else {
            Machine.check_nonvar(temp);
            temp = Store.copy_term(temp);
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"maybe", temp}));
        }
    }

    /**
     * Sequentially adds the literals L to the continuation.
     *
     * @param peek The literals.
     */
    public static void solve_seq(Object peek) {
        Store.Structure back = null;
        Object res = null;
        while (Store.is_structure(peek) &&
                ".".equals(((Store.Structure) peek).functor) &&
                ((Store.Structure) peek).args.length == 2) {
            Object temp = Store.deref(((Store.Structure) peek).args[0]);
            temp = new Store.Compound(".",
                    new Object[]{temp, Store.UNDEF_OBJ});
            if (back == null) {
                res = temp;
            } else {
                back.args[1] = temp;
            }
            back = (Store.Structure)temp;
            peek = Store.deref(((Store.Structure) peek).args[1]);
        }
        special.check_nil(peek);
        if (back == null) {
            res = ((Store.Structure) Machine.call).args[1];
        } else {
            back.args[1] = ((Store.Structure) Machine.call).args[1];
        }
        Machine.cont(res);
    }

    /**
     * '$ALT'(L): internal only
     * The built-in alternatively adds the variants L to the
     * continuation and succeeds.
     */
    private static Object special_alt(Object[] args) {
        Object goal = Store.deref(args[0]);
        return solve_alt(goal, -1, null);
    }

    /**
     * Alternatively adds the variants to the continuation.
     *
     * @param goal   The variants.
     * @param at     This argument is ignored.
     * @param choice The choice point for reuse or null.
     * @return boolean True if a variant could be added, otherwise false.
     */
    private static Object solve_alt(Object goal, int at, Machine.Choice choice) {
        if (Store.is_structure(goal) &&
                ".".equals(((Store.Structure) goal).functor) &&
                ((Store.Structure) goal).args.length == 2) {
            Store.Variable mark = Machine.trail;
            Object peek = Store.deref(((Store.Structure) goal).args[0]);
            Object temp = Store.deref(((Store.Structure) peek).args[0]);
            solve_mark(temp);
            goal = Store.deref(((Store.Structure) goal).args[1]);
            if (Store.is_structure(goal) &&
                    ".".equals(((Store.Structure) goal).functor) &&
                    ((Store.Structure) goal).args.length == 2) {
                if (choice == null) {
                    choice = new Machine.Choice(special::solve_alt, goal, -1, mark);
                } else {
                    choice.data = goal;
                }
                Machine.more(choice);
            }
            temp = Store.deref(((Store.Structure) peek).args[1]);
            solve_seq(temp);
            return Boolean.TRUE;
        } else {
            return Boolean.FALSE;
        }
    }

    /*****************************************************************/
    /* sys_raise/1 and sys_trap/3                                    */
    /*****************************************************************/

    /**
     * sys_raise(E): internal only
     * The predicate raises the exception E.
     */
    private static boolean test_sys_raise(Object[] args) {
        Object problem = Machine.exec_build(args[0]);
        problem = Store.copy_term(problem);
        throw new Handler.Problem(problem);
    }

    /**
     * sys_trap(G, E, F): internal only
     * The built-in succeeds whenever G succeeds. If
     * there was an exception that unifies with E, the
     * built-in further succeeds whenever F succeeds.
     */
    private static Object special_sys_trap(Object[] args) {
        Object goal = Store.deref(args[0]);
        Machine.Choice snap = Machine.snap_setup();
        goal = new Store.Compound(".", new Object[]{goal, "[]"});
        Machine.cont(goal);
        return solve_catch(snap, 1, null);
    }

    /**
     * Call, redo or resume a goal.
     * If there is an exception put the handler on the continuation.
     *
     * @param data   The surrounding choice point.
     * @param at     The call or redo flag.
     * @param choice The choice point for reuse or null.
     * @return boolean True if goal succeeded, otherwise false.
     */
    private static Object solve_catch(Object data, int at, Machine.Choice choice) {
        if (choice != null) {
            choice.mark = null;
            choice.cont = "[]";
            choice.tail = null;
        }
        Machine.Choice snap = (Machine.Choice) data;
        Object found = (at != 0 ? Boolean.TRUE : Boolean.FALSE);
        try {
            found = Machine.solve(snap, found);
        } catch (Throwable err) {
            Machine.snap_cleanup(snap);
            Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
            err = map_throwable(err);
            Object beta = (err instanceof Handler.Problem ? ((Handler.Problem) err).term : err);
            if (!Machine.unify(((Store.Structure) goal).args[1], beta))
                theatre.sneaky(err);
            goal = Store.deref(((Store.Structure) goal).args[2]);
            goal = new Store.Compound(".",
                    new Object[]{goal, ((Store.Structure) Machine.call).args[1]});
            Machine.cont(goal);
            return Boolean.TRUE;
        }
        if (found == Boolean.FALSE)
            return Boolean.FALSE;
        if (Machine.redo != snap) {
            if (choice == null) {
                choice = new Machine.Choice(special::solve_catch, snap, 0, Machine.trail);
            } else {
                choice.mark = Machine.trail;
                choice.cont = Machine.call;
                choice.tail = Machine.redo;
            }
            Machine.more(choice);
        } else {
            Machine.more(snap.tail);
        }
        if (found == Boolean.TRUE)
            Machine.cont(((Store.Structure) snap.cont).args[1]);
        return found;
    }

    private static Throwable map_throwable(Throwable err) {
        if (err instanceof StackOverflowError) {
            err = Machine.make_error(new Store.Compound("system_error",
                    new Object[]{"stack_overflow"}));
        } else if (err instanceof OutOfMemoryError) {
            err = Machine.make_error(new Store.Compound("system_error",
                    new Object[]{"out_of_memory"}));
        }
        return err;
    }

    /*******************************************************************/
    /* os_sleep_promise/2 and os_import_promise/3                      */
    /*******************************************************************/

    /**
     * os_sleep_promise(D, P):
     * The predicate succeeds in P with a promise for a delay D.
     */
    private static boolean test_os_sleep_promise(Object[] args) {
        Object obj = Machine.exec_build(args[0]);
        check_integer(obj);
        if (eval.integer_signum((Number) obj) < 0)
            throw Machine.make_error(new Store.Compound("domain_error",
                    new Object[]{"not_less_than_zero", obj}));
        if (!runtime.is_long((Number) obj))
            throw Machine.make_error(new Store.Compound("representation_error",
                    new Object[]{"long", obj}));
        long delay = ((Number) obj).longValue();
        Object buf = Machine.ctx;
        return Machine.exec_unify(args[1], sleep_promise(buf, delay));
    }

    private static Handler.Promise sleep_promise(Object buf, long delay) {
        return new Handler.Promise(() -> {
            Thread self = Thread.currentThread();
            Machine.register_interrupt(buf, self::interrupt);
            try {
                Thread.sleep(delay);
            } catch (InterruptedException x) {
                /* */
            } finally {
                Machine.register_interrupt(buf, () -> {});
            }
        });
    }

    /**
     * os_import_promise(F, M, Q): internal only
     * The predicate succeeds in Q with with a promise for the
     * import M of the file F.
     */
    private static boolean test_os_import_promise(Object[] args) {
        Object url = Machine.exec_build(args[0]);
        special.check_atom(url);
        HashMap res = new HashMap();
        if (!Machine.exec_unify(args[1], res))
            return false;
        Object buf = Machine.ctx;
        return Machine.exec_unify(args[2], import_promise(buf, (String) url, res));
    }

    private static Handler.Promise import_promise(Object buf, String url, Map res) {
        return new Handler.Promise(() -> {
            String name = special.find_name(url);
            if (name == null) {
                Machine.register_signal(buf,
                        new Store.Compound("existence_error",
                                new Object[]{"module", url}));
            } else {
                try {
                    Class clazz = Class.forName(name);
                    res.put("module", clazz);
                } catch (LinkageError x) {
                    Machine.register_signal(buf,
                            new Store.Compound("syntax_error",
                                    new Object[]{"bad_module"}));
                } catch (ClassNotFoundException x) {
                    Machine.register_signal(buf,
                            new Store.Compound("existence_error",
                                    new Object[]{"module", url}));
                }
            }
        });
    }

    /**
     * Find a Java class name from an url
     *
     * @param url The url
     * @return The Java class name or null
     */
    public static String find_name(String url) {
        if (!url.endsWith(special.NAT_EXT))
            return null;
        String path = find_path(url);
        if (path == null)
            return null;
        url = url.substring(path.length(), url.length() - special.NAT_EXT.length());
        url = url.replace("/", ".");
        url = url.replace("\\", ".");
        return url;
    }

    /**
     * Find a classpath entry for an url
     *
     * @param url The url
     * @return The classpath entry or null
     */
    private static String find_path(String url) {
        for (int i = 0; i < special.classpath.length; i++) {
            String path = special.classpath[i];
            if (url.startsWith(path))
                return path;
        }
        return null;
    }

    /**
     * os_invoke_main(M): internal only
     * Invoke the main method of the module M.
     */
    private static boolean test_os_invoke_main(Object[] args) {
        Class clazz = (Class) Machine.exec_build(args[0]);
        try {
            Method method = clazz.getMethod("main");
            method.invoke(null);
        } catch (NoSuchMethodException x) {
            throw Machine.make_error(new Store.Compound("existence_error",
                    new Object[]{"method", "main"}));
        } catch (IllegalAccessException x) {
            throw Machine.make_error(new Store.Compound("permission_error",
                    new Object[]{"invoke", "method", "main"}));
        } catch (InvocationTargetException x) {
            theatre.sneaky(x.getCause());
        }
        return true;
    }

    /*******************************************************************/
    /* '$YIELD'/1, shield/1 and unshield/1                             */
    /*******************************************************************/

    /**
     * '$YIELD'(R): Internal only
     * The built-in stops the interpreter loop with return value R.
     */
    private static Object special_yield(Object[] args) {
        if ((Store.engine.flags & Machine.SYS_MASK_ALLOW_YIELD) == 0)
            throw Machine.make_error(new Store.Compound("system_error",
                    new Object[]{"illegal_yield"}));
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        Machine.more(new Machine.Choice(Machine::solve_signal, null, 0, Machine.trail));
        return Store.deref(args[0]);
    }

    /**
     * shield(G):
     * The predicate succeeds whenever the goal G succeeds.
     * The goal is executed without auto-yield.
     */
    private static Object special_shield(Object[] args) {
        Object goal = Store.deref(args[0]);
        Machine.Choice snap = Machine.snap_setup();
        goal = new Store.Compound(".", new Object[]{goal, "[]"});
        Machine.cont(goal);
        return solve_shield(snap, 1, null);
    }

    /**
     * Call, redo or resume a goal.
     * The goal is executed without auto-yield.
     *
     * @param data   The surrounding choice point.
     * @param at     The call or redo flag.
     * @param choice The choice point for reuse or null.
     * @return boolean True if goal succeeded, otherwise false.
     */
    private static Object solve_shield(Object data, int at, Machine.Choice choice) {
        if (choice != null) {
            choice.mark = null;
            choice.cont = "[]";
            choice.tail = null;
        }
        Machine.Choice snap = (Machine.Choice) data;
        Object found = (at != 0 ? Boolean.TRUE : Boolean.FALSE);
        int back = Store.engine.flags & Machine.SYS_MASK_ASYNC_MODE;
        Store.engine.flags &= ~Machine.SYS_MASK_ASYNC_MODE;
        try {
            found = Machine.solve(snap, found);
        } catch (Throwable x) {
            Store.engine.flags &= ~Machine.SYS_MASK_ASYNC_MODE;
            Store.engine.flags |= back;
            Machine.snap_cleanup(snap);
            throw x;
        }
        Store.engine.flags &= ~Machine.SYS_MASK_ASYNC_MODE;
        Store.engine.flags |= back;
        if (found == Boolean.FALSE)
            return Boolean.FALSE;
        if (Machine.redo != snap) {
            if (choice == null) {
                choice = new Machine.Choice(special::solve_shield, snap, 0, Machine.trail);
            } else {
                choice.mark = Machine.trail;
                choice.cont = Machine.call;
                choice.tail = Machine.redo;
            }
            Machine.more(choice);
        } else {
            Machine.more(snap.tail);
        }
        if (found == Boolean.TRUE)
            Machine.cont(((Store.Structure) snap.cont).args[1]);
        return found;
    }

    /**
     * unshield(G):
     * The predicate succeeds whenever the goal G succeeds.
     * The goal is executed with auto-yield.
     */
    private static Object special_unshield(Object[] args) {
        Object goal = Store.deref(args[0]);
        Machine.Choice snap = Machine.snap_setup();
        goal = new Store.Compound(".", new Object[]{goal, "[]"});
        Machine.cont(goal);
        return solve_unshield(snap, 1, null);
    }

    /**
     * Call, redo or resume a goal.
     * The goal is executed without auto-yield.
     *
     * @param data   The surrounding choice point.
     * @param at     The call or redo flag.
     * @param choice The choice point for reuse or null.
     * @return boolean True if goal succeeded, otherwise false.
     */
    private static Object solve_unshield(Object data, int at, Machine.Choice choice) {
        if (choice != null) {
            choice.mark = null;
            choice.cont = "[]";
            choice.tail = null;
        }
        Machine.Choice snap = (Machine.Choice) data;
        Object found = (at != 0 ? Boolean.TRUE : Boolean.FALSE);
        int back = Store.engine.flags & Machine.SYS_MASK_ASYNC_MODE;
        Store.engine.flags |= Machine.SYS_MASK_ASYNC_MODE;
        try {
            found = Machine.solve(snap, found);
        } catch (Throwable x) {
            Store.engine.flags &= ~Machine.SYS_MASK_ASYNC_MODE;
            Store.engine.flags |= back;
            Machine.snap_cleanup(snap);
            throw x;
        }
        Store.engine.flags &= ~Machine.SYS_MASK_ASYNC_MODE;
        Store.engine.flags |= back;
        if (found == Boolean.FALSE)
            return Boolean.FALSE;
        if (Machine.redo != snap) {
            if (choice == null) {
                choice = new Machine.Choice(special::solve_unshield, snap, 0, Machine.trail);
            } else {
                choice.mark = Machine.trail;
                choice.cont = Machine.call;
                choice.tail = Machine.redo;
            }
            Machine.more(choice);
        } else {
            Machine.more(snap.tail);
        }
        if (found == Boolean.TRUE)
            Machine.cont(((Store.Structure) snap.cont).args[1]);
        return found;
    }

    /**
     * call(A): [ISO 7.8.3]
     * The predicate succeeds whenever the goal A succeeds.
     */
    private static Object special_call(Object[] args) {
        Object goal = Store.deref(args[0]);
        goal = new Store.Compound(".", new Object[]{goal,
                ((Store.Structure) Machine.call).args[1]});
        Machine.cont(goal);
        return true;
    }

    /*********************************************************************/
    /* Type Assertions                                                   */
    /*********************************************************************/

    /**
     * Assure that the object is an atom.
     *
     * @param beta The object.
     */
    public static void check_atom(Object beta) {
        if (!Machine.is_atom(beta)) {
            Machine.check_nonvar(beta);
            beta = Store.copy_term(beta);
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"atom", beta}));
        }
    }

    /**
     * Assure that the object is a number.
     *
     * @param beta The object.
     */
    public static void check_number(Object beta) {
        if (!Machine.is_number(beta)) {
            Machine.check_nonvar(beta);
            beta = Store.copy_term(beta);
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"number", beta}));
        }
    }

    /**
     * Assure that the object is an integer.
     *
     * @param beta The object.
     */
    public static void check_integer(Object beta) {
        if (!Machine.is_integer(beta)) {
            Machine.check_nonvar(beta);
            beta = Store.copy_term(beta);
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"integer", beta}));
        }
    }

    /**
     * Assure that the object is atomic.
     *
     * @param beta The object.
     */
    public static void check_atomic(Object beta) {
        if (Store.is_variable(beta) || Store.is_structure(beta)) {
            Machine.check_nonvar(beta);
            beta = Store.copy_term(beta);
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"atomic", beta}));
        }
    }

    /**
     * Assure that the Prolog term is nil.
     *
     * @param beta The Prolog term.
     */
    public static void check_nil(Object beta) {
        if ("[]".equals(beta))
            return;
        if (Store.is_structure(beta)
                && ".".equals(((Store.Structure) beta).functor)
                && ((Store.Structure) beta).args.length == 2) {
            throw Machine.make_error(new Store.Compound("representation_error",
                    new Object[]{"max_arity"}));
        } else {
            Machine.check_nonvar(beta);
            beta = Store.copy_term(beta);
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"list", beta}));
        }
    }

    /*********************************************************************/
    /* =/2 and copy_term/2                                               */
    /*********************************************************************/

    /**
     * S = T: [ISO 8.2.1]
     * The built-in succeeds when the Prolog terms S and T unify,
     * otherwise the built-in fails.
     */
    private static boolean test_unify(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        return Machine.exec_unify(args[1], alpha);
    }

    /**
     * copy_term(S, T): [ISO 8.5.4]
     * The built-in succeeds in T with a copy of S.
     */
    private static boolean test_copy_term(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        alpha = Store.copy_term(alpha);
        return Machine.exec_unify(args[1], alpha);
    }

    /******************************************************************/
    /* =../2, functor/3 and arg/3                                     */
    /******************************************************************/

    /**
     * T =.. [F|L]: [ISO 8.5.3]
     * If T is a variable, the built-in succeeds in T with the Prolog term
     * from the functor F and arguments L. Otherwise the built-in succeeds in
     * F and L with the functor and arguments of the Prolog term T.
     */
    private static boolean test_univ(Object[] args) {
        Object alpha = Machine.exec_deref(args[0]);
        if (Store.is_variable(alpha) || Machine.is_pending(alpha)) {
            Object beta = Machine.exec_build(args[1]);
            beta = special_univ_pack(beta);
            return Machine.exec_unify(alpha, beta);
        } else {
            alpha = Machine.exec_build(alpha);
            alpha = special_univ_unpack(alpha);
            return Machine.exec_unify(args[1], alpha);
        }
    }

    private static Object special_univ_pack(Object beta) {
        if (Store.is_structure(beta) &&
                ".".equals(((Store.Structure) beta).functor) &&
                ((Store.Structure) beta).args.length == 2) {
            /* */
        } else {
            Machine.check_nonvar(beta);
            beta = Store.copy_term(beta);
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"list", beta}));
        }
        Object functor = Store.deref(((Store.Structure) beta).args[0]);
        check_atomic(functor);
        beta = Store.deref(((Store.Structure) beta).args[1]);
        Object[] args = list_objects(beta);
        if (args.length == 0) {
            /* */
        } else {
            if (Machine.is_number(functor))
                throw Machine.make_error(new Store.Compound("type_error",
                        new Object[]{"atom", functor}));
            functor = new Store.Compound(functor, args);
        }
        return functor;
    }

    public static Object[] list_objects(Object obj) {
        Object peek = obj;
        int i = 0;
        while (Store.is_structure(peek) &&
                ".".equals(((Store.Structure) peek).functor) &&
                ((Store.Structure) peek).args.length == 2 &&
                i < special.MAX_ARITY) {
            i++;
            peek = Store.deref(((Store.Structure) peek).args[1]);
        }
        special.check_nil(peek);
        if (i == 0) {
            return Machine.VOID_ARGS;
        } else {
            Object[] args = new Object[i];
            peek = obj;
            i = 0;
            while (Store.is_structure(peek) &&
                    ".".equals(((Store.Structure) peek).functor) &&
                    ((Store.Structure) peek).args.length == 2) {
                args[i++] = Store.deref(((Store.Structure) peek).args[0]);
                peek = Store.deref(((Store.Structure) peek).args[1]);
            }
            return args;
        }
    }

    private static Object special_univ_unpack(Object alpha) {
        if (Store.is_structure(alpha)) {
            Object[] temp = ((Store.Structure) alpha).args;
            alpha = ((Store.Structure) alpha).functor;
            return new Store.Compound(".", new Object[]{alpha,
                            objects_list(temp, 0, temp.length)});
        } else {
            return new Store.Compound(".", new Object[]{alpha, "[]"});
        }
    }

    public static Object objects_list(Object[] args, int off, int count) {
        Object res = "[]";
        for (int i = off + count - 1; i >= off; i--)
            res = new Store.Compound(".", new Object[]{args[i], res});
        return res;
    }

    /**
     * functor(T, F, A): [ISO 8.5.1]
     * If T is a variable, the built-in succeeds in T with a new Prolog term
     * from the functor F and the arity A. Otherwise the built-in succeeds in
     * F and L with the functor and arguments of the Prolog term T.
     */
    private static boolean test_functor(Object[] args) {
        Object alpha = Machine.exec_deref(args[0]);
        if (Store.is_variable(alpha) || Machine.is_pending(alpha)) {
            Object functor = Machine.exec_build(args[1]);
            check_atomic(functor);
            Object beta = Machine.exec_build(args[2]);
            check_integer(beta);
            if (eval.integer_signum((Number) beta) < 0)
                throw Machine.make_error(new Store.Compound("domain_error",
                        new Object[]{"not_less_than_zero", beta}));
            if (is_bigint(beta))
                throw Machine.make_error(new Store.Compound("representation_error",
                        new Object[]{"max_arity"}));
            int arity = ((Integer) beta).intValue();
            if (arity == 0) {
                /* */
            } else {
                if (Machine.is_number(functor))
                    throw Machine.make_error(new Store.Compound("type_error",
                            new Object[]{"atom", functor}));
                Object[] temp = new Object[arity];
                for (int i = 0; i < arity; i++)
                    temp[i] = new Store.Variable();
                functor = new Store.Compound(functor, temp);
            }
            return Machine.exec_unify(alpha, functor);
        } else {
            alpha = Machine.exec_build(alpha);
            Object functor;
            Object arity;
            if (Store.is_structure(alpha)) {
                Store.Structure cmp = (Store.Structure) alpha;
                functor = cmp.functor;
                arity = Integer.valueOf(cmp.args.length);
            } else {
                functor = alpha;
                arity = Integer.valueOf(0);
            }
            if (!Machine.exec_unify(args[1], functor))
                return false;
            return Machine.exec_unify(args[2], arity);
        }
    }

    /**
     * arg(K, X, Y): [ISO 8.5.2]
     * The predicate succeeds in Y with the K-th argument of X.
     */
    private static boolean test_arg(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        check_integer(alpha);
        Object beta = Machine.exec_build(args[1]);
        Machine.check_callable(beta);
        int arity;
        if (Store.is_structure(beta)) {
            arity = ((Store.Structure) beta).args.length;
        } else {
            arity = 0;
        }
        int pos = (!is_bigint(beta) ? ((Integer) alpha).intValue() : -1);
        if (pos < 1 || arity < pos)
            return false;
        beta = ((Store.Structure) beta).args[pos - 1];
        return Machine.exec_unify(args[2], beta);
    }

    /******************************************************************/
    /* change_arg/3                                                   */
    /******************************************************************/

    /**
     * change_arg(K, X, Y):
     * The predicate succeeds. As a side-effect the K-th argument of X is set to Y.
     */
    private static boolean test_change_arg(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        check_integer(alpha);
        Object beta = Machine.exec_build(args[1]);
        Machine.check_callable(beta);
        Object gamma = Machine.exec_build(args[2]);
        int arity;
        if (Store.is_structure(beta)) {
            arity = ((Store.Structure) beta).args.length;
        } else {
            arity = 0;
        }
        int pos = (!is_bigint(beta) ? ((Integer) alpha).intValue() : -1);
        if (pos < 1 || arity < pos)
            return false;
        linkarg(gamma, (Store.Structure) beta, pos);
        return true;
    }

    private static void linkarg(Object source, Store.Structure term, int pos) {
        if (Store.is_frozen(term))
            throw Machine.make_error(new Store.Compound("permission_error",
                    new Object[]{"modify", "compound", term}));
        if ((((Store.Compound)term).walk & Store.VAR_MASK_STATE) == Machine.gc_mask)
            Machine.mark2_term(source);
        term.args[pos-1] = source;
    }

    /******************************************************************/
    /* term_variables/2                                               */
    /******************************************************************/

    /**
     * term_variables(T, L): [TC2 8.5.5]
     * The built-in succeeds in L with a list of the variables of T.
     */
    private static boolean test_term_variables(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        term_variables2 h = new term_variables2();
        walk_vars(alpha, h.getAcceptor(), Store.VAR_MASK_SEEN);
        if (h.back == null) {
            h.res = "[]";
        } else {
            h.back.args[1] = "[]";
        }
        walk_vars(alpha, node -> false, 0);
        return Machine.exec_unify(args[1], h.res);
    }

    private static class term_variables2 {
        public Object res = null;
        public Store.Structure back = null;

        public boolean run(Object node) {
            Store.Structure peek = new Store.Compound(".",
                    new Object[]{node, Store.UNDEF_OBJ});
            if (back == null) {
                res = peek;
            } else {
                back.args[1] = peek;
            }
            back = peek;
            return false;
        }

        public Handler.Acceptor getAcceptor() {
            return this::run;
        }

    }

    public static boolean walk_vars(Object first,
                                    Handler.Acceptor acceptor,
                                    int state) {
        List stack = null;
        for (; ; ) {
            first = Store.deref(first);
            if (Store.is_variable(first)) {
                if ((((Store.Variable) first).flags & Store.VAR_MASK_SEEN) != state) {
                    if (acceptor.run(first))
                        return true;
                    ((Store.Variable)first).flags = (((Store.Variable)first).flags & ~Store.VAR_MASK_SEEN) | state;
                }
            } else if (Store.is_compound(first)) {
                if ((((Store.Compound) first).walk & Store.VAR_MASK_SEEN) != state) {
                    ((Store.Compound)first).walk = (((Store.Compound)first).walk & ~Store.VAR_MASK_SEEN) | state;
                    if (0 != ((Store.Structure) first).args.length - 1) {
                        ((Store.Compound) first).walk &= ~Store.VAR_MASK_SERNO;
                        stack = Store.stack_push(stack, first);
                    }
                    first = ((Store.Structure) first).args[0];
                    continue;
                }
            }
            Store.Compound item = (Store.Compound)Store.stack_peek(stack);
            if (item == null) {
                return false;
            } else {
                item.walk++;
                first = item.args[item.walk & Store.VAR_MASK_SERNO];
                if ((item.walk & Store.VAR_MASK_SERNO) == item.args.length - 1)
                    Store.stack_pop(stack);
            }
        }
    }

    /******************************************************************/
    /* var/1, compound/1 and reference/1                              */
    /******************************************************************/

    /**
     * var(V): [ISO 8.3.1]
     * The built-in succeeds if V is a Prolog variable. Otherwise, it fails.
     */
    private static boolean test_var(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        return Store.is_variable(alpha);
    }

    /**
     * compound(C): [ISO 8.3.6]
     * The built-in succeeds if C is a Prolog compound. Otherwise, it fails.
     */
    private static boolean test_compound(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        return Store.is_structure(alpha);
    }

    /**
     * reference(A):
     * The built-in succeeds if A is a Prolog reference. Otherwise, it fails.
     */
    private static boolean test_reference(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        if (Store.is_variable(alpha) || Store.is_structure(alpha))
            return false;
        if (Machine.is_atom(alpha) || Machine.is_number(alpha))
            return false;
        return true;
    }

    /******************************************************************/
    /* nonvar/1, atomic/1, number/1 and float/1    */
    /******************************************************************/

    /**
     * nonvar(V): [ISO 8.3.7]
     * The built-in succeeds if V is not a Prolog variable. Otherwise, it fails.
     */
    private static boolean test_nonvar(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        return !Store.is_variable(alpha);
    }

    /**
     * atomic(A): [ISO 8.3.5]
     * The built-in succeeds if A is a Prolog symbol or number. Otherwise, it fails.
     */
    private static boolean test_atomic(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        if (Store.is_structure(alpha) || Store.is_variable(alpha))
            return false;
        return true;
    }

    /**
     * number(A): [ISO 8.3.8]
     * The built-in succeeds if A is a Prolog number. Otherwise, it fails.
     */
    private static boolean test_number(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        return Machine.is_number(alpha);
    }

    /**
     * float(A): [ISO 8.3.4]
     * The built-in succeeds if A is a Prolog float. Otherwise, it fails.
     */
    private static boolean test_float(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        return Machine.is_float(alpha) && !special.is_infinite(alpha);
    }

    /******************************************************************/
    /* integer/1, atom/1, callable/1 and must_be/2                    */
    /******************************************************************/

    /**
     * integer(A): [ISO 8.3.3]
     * The built-in succeeds if A is a Prolog integer. Otherwise, it fails.
     */
    private static boolean test_integer(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        return Machine.is_integer(alpha);
    }

    /**
     * atom(A): [ISO 8.3.2]
     * The built-in succeeds if A is a Prolog atom. Otherwise, it fails.
     */
    private static boolean test_atom(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        return Machine.is_atom(alpha);
    }

    /**
     * callable(C): [TC2 8.3.9]
     * The built-in succeeds if C is a Prolog compound or symbol. Otherwise, it fails.
     */
    private static boolean test_callable(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        if (Store.is_variable(alpha) || Machine.is_number(alpha))
            return false;
        return true;
    }

    /**
     * must_be(T, A):
     * The built-in succeeds if A is of type T. Otherwise, an exception is thrown.
     */
    private static boolean test_must_be(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        check_atom(alpha);
        Object beta = Machine.exec_build(args[1]);
        if ("integer".equals(alpha)) {
            check_integer(beta);
        } else if ("atom".equals(alpha)) {
            check_atom(beta);
        } else if ("callable".equals(alpha)) {
            Machine.check_callable(beta);
        } else {
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"type", alpha}));
        }
        return true;
    }

    /******************************************************************/
    /* Number Utilities                                               */
    /******************************************************************/

    /**
     * Check whether an object is a bigint.
     *
     * @param alpha The object.
     * @return boolean True if the object is a bignum, otherwise false.
     */
    public static boolean is_bigint(Object alpha) {
        return alpha instanceof BigInteger;
    }

    /**
     * Check whether an object is an infinite.
     *
     * @param alpha The object.
     * @return boolean True if the object is an infinite, otherwise false.
     */
    public static boolean is_infinite(Object alpha) {
        return (alpha.equals(eval.DOUBLE_PINF) || alpha.equals(eval.DOUBLE_NINF));
    }

    /**
     * <p>Return the Prolog integer corresponding to the given long.</p>
     *
     * @param val The long.
     * @return The Prolog integer.
     */
    public static Number norm_smallint(long val) {
        if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE) {
            return Integer.valueOf((int) val);
        } else {
            return BigInteger.valueOf(val);
        }
    }

    /**
     * <p>Return the Prolog integer corresponding to the given big integer.</p>
     *
     * @param val The bigint.
     * @return number The Prolog integer.
     */
    public static Number norm_bigint(BigInteger val) {
        if (MIN_INTEGER.compareTo(val) <= 0 && val.compareTo(MAX_INTEGER) <= 0) {
            return Integer.valueOf(val.intValue());
        } else {
            return val;
        }
    }

    /**
     * Widen a Prolog integer.
     *
     * @param num The Prolog integer.
     * @return bigint The JavaScript bigint.
     */
    public static BigInteger widen_bigint(Number num) {
        if (num instanceof BigInteger) {
            return (BigInteger) num;
        } else {
            return BigInteger.valueOf(num.intValue());
        }
    }

    /**
     * Narrow a Prolog number to a float.
     *
     * @param alpha The Prolog number.
     * @return number The JavaScript float.
     */
    public static double narrow_float(Number alpha) {
        double val;
        if (Machine.is_float(alpha)) {
            if (is_infinite(alpha))
                throw new ArithmeticException(eval.OP_EVALUATION_UNDEFINED);
            return alpha.doubleValue();
        } else {
            val = alpha.doubleValue();
            if (Double.isInfinite(val))
                throw new ArithmeticException(eval.OP_EVALUATION_FLOAT_OVERFLOW);
            return val;
        }
    }

    /**
     * Norm a float to a Prolog number.
     *
     * @param alpha The Java float.
     * @return number The Prolog float.
     */
    public static Double norm_float(Double alpha) {
        if (Double.isNaN(alpha.doubleValue()))
            throw new ArithmeticException(eval.OP_EVALUATION_UNDEFINED);
        if (Double.isInfinite(alpha.doubleValue()))
            throw new ArithmeticException(eval.OP_EVALUATION_FLOAT_OVERFLOW);
        if (alpha.doubleValue() == 0.0)
            return ZERO_DOUBLE;
        return alpha;
    }

    /******************************************************************/
    /* atom/1, code_category/2 and code_numeric/2                     */
    /******************************************************************/

    /**
     * code_category(C, T):
     * The predicate succeeds in T with the Unicode general category of C.
     * Otherwise, the predicate succeeds in T with 0.
     */
    private static boolean test_code_category(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        check_integer(alpha);
        int ch = (!is_bigint(alpha) ? ((Integer) alpha).intValue() : -1);
        if (ch < 0 || ch > 0x10FFFF) {
            ch = 0; // UNASSIGNED
        } else {
            ch = Character.getType(ch);
        }
        alpha = Integer.valueOf(ch);
        return Machine.exec_unify(args[1], alpha);
    }

    /**
     * code_numeric(C, V):
     * The predicate succeeds in V with the Unicode numeric value of C,
     * in case it is integer and between 0 and 35. Otherwise, the predicate
     * succeeds in V with -1.
     */
    private static boolean test_code_numeric(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        check_integer(alpha);
        int ch = (!is_bigint(alpha) ? ((Integer) alpha).intValue() : -1);
        if (ch < 0 || ch > 0x10FFFF) {
            ch = -1; // UNASSIGNED
        } else {
            ch = Character.digit(ch, 36);
        }
        alpha = Integer.valueOf(ch);
        return Machine.exec_unify(args[1], alpha);
    }

    /*********************************************************************/
    /* atom_integer/3                                                    */
    /*********************************************************************/

    /**
     * atom_integer(A, R, N):
     * If A is a variable, then the built-in succeeds in A with the
     * atom for the Prolog integer N in radix R. Otherwise the
     * built-in succeeds in N with the Prolog number from the
     * atom A in radix R.
     */
    private static boolean test_atom_integer(Object[] args) {
        Object alpha = Machine.exec_build(args[1]);
        check_integer(alpha);
        int radix = (!is_bigint(alpha) ? ((Integer) alpha).intValue() : -1);
        if (radix < 2 || radix > 36)
            throw Machine.make_error(new Store.Compound("domain_error",
                    new Object[]{"radix", alpha}));
        Object text = Machine.exec_deref(args[0]);
        if (Store.is_variable(text) || Machine.is_pending(text)) {
            Object beta = Machine.exec_build(args[2]);
            check_integer(beta);
            beta = atom_integer_encode(beta, radix);
            return Machine.exec_unify(text, beta);
        } else {
            text = Machine.exec_build(text);
            check_atom(text);
            text = atom_integer_decode((String) text, radix);
            return Machine.exec_unify(args[2], text);
        }
    }

    /**
     * Encode a Prolog integer to a string.
     *
     * @param num   The Prolog integer.
     * @param radix The radix.
     * @return The string.
     */
    private static String atom_integer_encode(Object num, int radix) {
        String res;
        if (is_bigint(num)) {
            res = ((BigInteger) num).toString(radix);
        } else {
            res = Integer.toString(((Integer) num).intValue(), radix);
        }
        return res;
    }

    /**
     * Decode a Prolog integer from a string.
     *
     * @param text  The string
     * @param radix The radix.
     * @return The Prolog integer.
     */
    private static Number atom_integer_decode(String text, int radix) {
        try {
            int step = 63 / (32 - Integer.numberOfLeadingZeros(radix - 1));
            Number res;
            if (text.length() <= step) {
                long val = Long.parseLong(text, radix);
                res = norm_smallint(val);
            } else {
                BigInteger val = new BigInteger(text, radix);
                res = norm_bigint(val);
            }
            return res;
        } catch (NumberFormatException x) {
            throw Machine.make_error(new Store.Compound("syntax_error",
                    new Object[]{"illegal_number"}));
        }
    }

    /******************************************************************/
    /* atom_number/2                                                  */
    /******************************************************************/

    /**
     * atom_number(A, N):
     * If A is a variable, then the built-in succeeds in A with the
     * atom for the Prolog number N. Otherwise the built-in succeeds in N
     * with the Prolog number from the atom A.
     */
    private static boolean test_atom_number(Object[] args) {
        Object text = Machine.exec_deref(args[0]);
        if (Store.is_variable(text) || Machine.is_pending(text)) {
            Object beta = Machine.exec_build(args[1]);
            check_number(beta);
            beta = atom_number_encode(beta);
            return Machine.exec_unify(text, beta);
        } else {
            text = Machine.exec_build(text);
            check_atom(text);
            text = atom_number_decode((String) text);
            return Machine.exec_unify(args[1], text);
        }
    }

    /**
     * Encode a Prolog number to a string.
     *
     * @param num The Prolog number.
     * @return The string.
     */
    private static String atom_number_encode(Object num) {
        if (Machine.is_integer(num)) {
            return num.toString();
        } else {
            String res = String.format(Locale.UK, "%.16g", (Double) num);
            if (Double.parseDouble(res) != ((Double) num).doubleValue())
                res = String.format(Locale.UK, "%.17g", (Double) num);
            return shape_number(res);
        }
    }

    /**
     * Shape the number string so that it has no trailing
     * zeros after the period, always a period, no exponent
     * positive sign and lower case exponent.
     *
     * @param res The ascii number string
     * @return The shaped number string.
     */
    public static String shape_number(String res) {
        int peek = res.indexOf('e');
        if (peek != -1) {
            res = shape_number_mantissa(res.substring(0, peek)) +
                    "e" + shape_number_exponent(res.substring(peek + 1));
        } else {
            res = shape_number_mantissa(res);
        }
        return res;
    }

    private static String shape_number_mantissa(String res) {
        if (res.indexOf('.') != -1) {
            int pos = res.length();
            while (res.charAt(pos - 1) == '0')
                pos--;
            if (res.charAt(pos - 1) == '.')
                pos++;
            if (pos != res.length())
                res = res.substring(0, pos);
        } else {
            res += ".0";
        }
        return res;
    }

    private static String shape_number_exponent(String res) {
        if (res.startsWith("+"))
            res = res.substring(1);
        return res;
    }

    /**
     * Decode a Prolog number from a string. Unlike the ISO
     * core standard and numbers without a period but with
     * an exponent are accepted as float.
     *
     * @param text The string
     * @return The Prolog number.
     */
    private static Number atom_number_decode(String text) {
        try {
            Number res;
            if ((text.indexOf('.') != -1) ||
                    (text.indexOf('e') != -1) ||
                    (text.indexOf('E') != -1)) {
                double val = Double.parseDouble(text);
                res = norm_float(Double.valueOf(val));
            } else {
                if (text.length() <= 18) {
                    long val = Long.parseLong(text);
                    res = norm_smallint(val);
                } else {
                    BigInteger val = new BigInteger(text);
                    res = norm_bigint(val);
                }
            }
            return res;
        } catch (NumberFormatException x) {
            throw Machine.make_error(new Store.Compound("syntax_error",
                    new Object[]{"illegal_number"}));
        }
    }

    /******************************************************************/
    /* atom_exotic/2                                               */
    /******************************************************************/

    /**
     * atom_exotic(A, R):
     * The built-in succeeds in A with the atom for the Prolog exotic R.
     */
    private static boolean test_atom_exotic(Object[] args) {
        Object text = Machine.exec_deref(args[0]);
        if (Store.is_variable(text) || Machine.is_pending(text)) {
            Object obj = Machine.exec_build(args[1]);
            if (obj == Boolean.TRUE) {
                obj = "True";
            } else if (obj == Boolean.FALSE) {
                obj = "False";
            } else if (obj == null) {
                obj = "None";
            } else if (obj.equals(eval.DOUBLE_PINF)) {
                obj = "Inf";
            } else if (obj instanceof Number) {
                obj = "Special";
            } else {
                obj = "Reference";
            }
            return Machine.exec_unify(text, obj);
        } else {
            text = Machine.exec_build(text);
            check_atom(text);
            Object obj;
            if ("True".equals(text)) {
                obj = true;
            } else if ("False".equals(text)) {
                obj = false;
            } else if ("None".equals(text)) {
                obj = null;
            } else if ("Inf".equals(text)) {
                obj = eval.DOUBLE_PINF;
            } else {
                obj = Store.UNDEF_OBJ;
            }
            if (obj == Store.UNDEF_OBJ)
                throw Machine.make_error(new Store.Compound("syntax_error",
                        new Object[]{"illegal_exotic"}));
            return Machine.exec_unify(args[1], obj);
        }
    }

    /******************************************************************/
    /* sys_get_locale/1 and sys_get_args/1                            */
    /******************************************************************/

    /**
     * sys_get_locale(L): Internal only
     * The built-in succeeds in L with the initial locale
     * of the Java environment.
     */
    private static boolean test_sys_get_locale(Object[] args) {
        Locale loc = Locale.getDefault();
        return Machine.exec_unify(args[0], loc.toString());
    }

    /**
     * sys_get_args(L): Internal only
     * The built-in succeeds in L with the initial command
     * line arguments of the Java environment.
     */
    private static boolean test_sys_get_args(Object[] args) {
        Object res = objects_list(main_args, 0, main_args.length);
        return Machine.exec_unify(args[0], res);
    }

    /**
     * os_get_gestalt(M): Internal only
     * The built-in succeeds in M with a host gestalt map.
     */
    private static boolean test_os_get_gestalt(Object[] args) {
        HashMap gestalt = new HashMap();
        String scm = System.getenv("TERM_SCHEME");
        gestalt.put("sys_color", (scm != null ? scm : "normal"));
        Locale loc = Locale.getDefault();
        gestalt.put("sys_locale", loc.toString());
        Object res = objects_list(main_args, 0, main_args.length);
        gestalt.put("argv", res);
        return Machine.exec_unify(args[0], gestalt);
    }

    /******************************************************************/
    /* Special Init                                                   */
    /******************************************************************/

    public static void boot() {
        // Albufeira compiler, control flow
        Store.add("fail", 0, make_check(special::test_fail));
        Store.add("$CUT", 1, make_check(special::test_cut));
        Store.add("$MARK", 1, make_check(special::test_mark));
        Store.add("$SEQ", 2, make_special(special::special_seq));
        Store.add("$ALT", 1, make_special(special::special_alt));
        Store.add("sys_raise", 1, make_check(special::test_sys_raise));
        Store.add("sys_trap", 3, make_special(special::special_sys_trap));

        // Albufeira compiler, async flow
        Store.add("os_sleep_promise", 2, make_check(special::test_os_sleep_promise));
        Store.add("os_import_promise", 3, make_check(special::test_os_import_promise));
        Store.add("os_invoke_main", 1, make_check(special::test_os_invoke_main));
        Store.add("$YIELD", 1, make_special(special::special_yield));
        Store.add("shield", 1, make_special(special::special_shield));
        Store.add("unshield", 1, make_special(special::special_unshield));
        Store.add("call", 1, make_special(special::special_call));

        // term specials
        Store.add("=", 2, make_check(special::test_unify));
        Store.add("copy_term", 2, make_check(special::test_copy_term));
        Store.add("=..", 2, make_check(special::test_univ));
        Store.add("functor", 3, make_check(special::test_functor));
        Store.add("arg", 3, make_check(special::test_arg));
        Store.add("change_arg", 3, make_check(special::test_change_arg));
        Store.add("term_variables", 2, make_check(special::test_term_variables));

        // type specials
        Store.add("var", 1, make_check(special::test_var));
        Store.add("compound", 1, make_check(special::test_compound));
        Store.add("reference", 1, make_check(special::test_reference));
        Store.add("nonvar", 1, make_check(special::test_nonvar));
        Store.add("atomic", 1, make_check(special::test_atomic));
        Store.add("number", 1, make_check(special::test_number));
        Store.add("float", 1, make_check(special::test_float));

        // must specials
        Store.add("integer", 1, make_check(special::test_integer));
        Store.add("atom", 1, make_check(special::test_atom));
        Store.add("callable", 1, make_check(special::test_callable));
        Store.add("must_be", 2, make_check(special::test_must_be));

        // atom specials
        Store.add("code_category", 2, make_check(special::test_code_category));
        Store.add("code_numeric", 2, make_check(special::test_code_numeric));
        Store.add("atom_integer", 3, make_check(special::test_atom_integer));
        Store.add("atom_number", 2, make_check(special::test_atom_number));
        Store.add("atom_exotic", 2, make_check(special::test_atom_exotic));

        // locale specials
        Store.add("sys_get_locale", 1, make_check(special::test_sys_get_locale));
        Store.add("sys_get_args", 1, make_check(special::test_sys_get_args));
        Store.add("os_get_gestalt", 1, make_check(special::test_os_get_gestalt));
    }

}
