/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.thesett.aima.logic.fol;

import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import com.thesett.aima.search.Operator;

/**
 * Functors are compositions of other terms, expressing membership of a set denoted by the functors name. The 'arity' of
 * a functor is the number of terms it accepts as arguments and defines the dimensions of the set that it defines. A
 * special case is functors of arity zero, which are considered to be constants or atoms of the language of first order
 * logic (or sets with only one member, if you like).
 *
 *
For example, the functor, "dog", is a constant. The functor, animal(dog), is a unary predicate, partially
 * defining the set of animals by expressing that a dog is an animal. The functor, dislikes(dog, cat), is a binary
 * predicate over dogs and cats, expressing a relationship between them.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Define a named concept ranging over other terms.  {@link Term}.
 *
 Contain the arguments of the functor.  {@link Term}.
 *
 Provide structural equality matching over functors.  {@link Term}.
 *
 Provide quick name and arity based equality checking.
 *
 Provide an iterator over the argument of the functor for search methods.
 *
 Provide query conversion over the functors arguments.  {@link Term}.
 *
 Accept a term visitor to transform the functor.  {@link FunctorTransformer}.
 *
 Provide pretty printing to strings of the functor and its arguments.  {@link Term}.
 *

 *
 * @author Rupert Smith
 * @todo   Should recursively free all variables within the functor when free is called.
 * @todo   Write an empty iterator implementation for the default getChildren method on atoms.
 */
public class Functor extends BaseTerm implements Term
{
    /* private static final Logger log = Logger.getLogger(Functor.class.getName()); */

    /**
     * The name of the functor, or the set that the functor denotes. This is an integer that refers to an interned
     * object that provides the textual name and arity of the functor. An integer representation is used for fast
     * comparisons.
     */
    protected int name;

    /** Holds the functors arguments. */
    protected Term[] arguments;

    /** Holds the arity of the functor. */
    protected int arity;

    /** Holds the cached deep hash key for the functor, to speed up the {@link #deepHashKey} method. */
    protected int deepHashKey;

    /**
     * Creates a new functor with the specified arguments.
     *
     * @param name      The name of the functor.
     * @param arguments The functors arguments.
     */
    public Functor(int name, Term[] arguments)
    {
        this.name = name;
        this.arguments = arguments;
        this.arity = (arguments == null) ? 0 : arguments.length;
    }

    /**
     * Reports whether or not this term is a functor.
     *
     * @return Always <tt>true</tt>.
     */
    public boolean isFunctor()
    {
        return true;
    }

    /**
     * Reports whether or not this term is a constant (a number or a functor of arity zero).
     *
     * @return <tt>true</tt> if this functor has arity zero, <tt>false</tt> otherwise.
     */
    public boolean isConstant()
    {
        return arity == 0;
    }

    /**
     * Reports whether or not this term is compound (a functor of arity one or more).
     *
     * @return <tt>true</tt> if this funtor has arity more than zero, <tt>fals</tt> otherwise.
     */
    public boolean isCompound()
    {
        return arity > 0;
    }

    /**
     * Reports whether or not this term is an atom (a functor of arity zero).
     *
     * @return <tt>true</tt> if this functor has arity zero, <tt>false</tt> otherwise.
     */
    public boolean isAtom()
    {
        return arity == 0;
    }

    /**
     * Reports whether or not this term is a ground term. Constants (functors of arity zero) and numbers are ground
     * terms, as are functors all of the arguments of which are ground term.
     *
     * @return <tt>true</tt> if this functor is a ground term, <tt>false</tt> othewise.
     */
    public boolean isGround()
    {
        for (int i = 0; i < arity; i++)
        {
            if (!arguments[i].isGround())
            {
                return false;
            }
        }

        return true;
    }

    /**
     * Frees all assigned variables in the term, leaving them unnassigned. In the case of functors and other compund
     * structures, this recurses down into the term calling free on all argument, so that the call reaches all variables
     * in leaf positions of the term.
     */
    public void free()
    {
        if (arguments != null)
        {
            for (Term argument : arguments)
            {
                argument.free();
            }
        }
    }

    /**
     * Gets the actual value of a term, which is either the term itself, or in the case of variables, the value that is
     * currently assigned to the variable. For functors, the value is the functor itself.
     *
     * @return The functor itself.
     */
    public Term getValue()
    {
        return this;
    }

    /**
     * Gets the argument within the functor with the specified index.
     *
     * @param  index The index to get the argument for.
     *
     * @return The argument.
     */
    public Term getArgument(int index)
    {
        if ((arguments == null) || (index > (arguments.length - 1)))
        {
            return null;
        }
        else
        {
            return arguments[index];
        }
    }

    /**
     * Provides all of this functors arguments.
     *
     * @return All of this functors arguments, possibly <tt>null</tt>.
     */
    public Term[] getArguments()
    {
        return arguments;
    }

    /**
     * Sets the argument within the functor to the specified value.
     *
     * @param index The index to set the argument for.
     * @param value The argument.
     */
    public void setArgument(int index, Term value)
    {
        arguments[index] = value;
    }

    /**
     * Reports the number of arguments that this functor takes.
     *
     * @return The number of arguments that this functor takes.
     */
    public int getArity()
    {
        return arity;
    }

    /**
     * Reports the name of this functor, or of the set that it denotes.
     *
     * @return This functors name.
     */
    public int getName()
    {
        return name;
    }

    /**
     * Compares this term for structural equality with another. Two terms are structurally equal if they are the same
     * functor with the same arguments, or are the same unbound variable, or the bound values of the left or right
     * variable operands are structurally equal. Structural equality is a stronger equality than unification and unlike
     * unification it does not produce any variable bindings. Two unified terms will always be structurally equal.
     *
     * @param  term The term to compare with this one for structural equality.
     *
     * @return <tt>true</tt> if the two terms are structurally eqaul, <tt>false</tt> otherwise.
     */
    public boolean structuralEquals(Term term)
    {
        Term comparator = term.getValue();

        if (this == comparator)
        {
            return true;
        }

        if ((comparator == null) || !(getClass().isAssignableFrom(comparator.getClass())))
        {
            return false;
        }

        Functor functor = (Functor) comparator;

        if ((arity != functor.arity) || (name != functor.name))
        {
            return false;
        }

        // Check the arguments of this functor and the comparator for structural equality.
        boolean passedArgCheck = true;

        if (arguments != null)
        {
            for (int i = 0; i < arguments.length; i++)
            {
                Term leftArg = arguments[i];
                Term rightArg = functor.arguments[i];

                if (!leftArg.structuralEquals(rightArg))
                {
                    passedArgCheck = false;

                    break;
                }
            }
        }

        return passedArgCheck;
    }

    /**
     * Compares this functor with another, to check if they are equal by name and arity. Two functors deemed equal by
     * this method, may not be fully equal terms in first order logic. This equality method checks the natural key, that
     * is, the name and arity, of the functor only. It is intended to be used to skim check potentially unifiable
     * functors, and in data structures that need to efficiently compare functors that may be unifiable.
     *
     *
Another way of looking at it is to say that, if this method returns false, the two functors are definitely
     * not equal, and cannot be unified. The false answer is the definite one. If this method returns true, the two
     * functors may be unifiable.
     *
     * @param  comparator The object to compare to.
     *
     * @return <tt>true</tt> if the comparator has the same name and arity as this one, <tt>false</tt> otherwise.
     */
    public boolean equals(Object comparator)
    {
        if (this == comparator)
        {
            return true;
        }

        if ((comparator == null) || (getClass() != comparator.getClass()))
        {
            return false;
        }

        Functor functor = (Functor) comparator;

        return (arity == functor.arity) && (name == functor.name);
    }

    /**
     * Computes a hash code for this functor that is based on the same fields as the {@link #equals} method, that is,
     * the functors name and arity.
     *
     * @return A hash code based on the name and arity.
     */
    public int hashCode()
    {
        int result;
        result = name;
        result = (31 * result) + arity;

        return result;
    }

    /**
     * Provides an iterator over the child terms, if there are any. Only functors are compound, and built across a list
     * of child arguments.
     *
     * @param  reverse Set, if the children should be presented in reverse order.
     *
     * @return The sub-terms of a compound term.
     */
    public Iterator<Operator<Term>> getChildren(boolean reverse)
    {
        if ((traverser != null) && (traverser instanceof FunctorTraverser))
        {
            return ((FunctorTraverser) traverser).traverse(this, reverse);
        }
        else
        {
            if (arguments == null)
            {
                return new LinkedList<Operator<Term>>().iterator();
            }
            else if (!reverse)
            {
                return Arrays.asList((Operator<Term>[]) arguments).iterator();
            }
            else
            {
                List<Operator<Term>> argList = new LinkedList<Operator<Term>>();

                for (int i = arity - 1; i >= 0; i--)
                {
                    argList.add(arguments[i]);
                }

                return argList.iterator();
            }
        }
    }

    /**
     * Makes a clone of the term, converting its variables to refer directly to their storage cells.
     *
     * @return A copy of this term, with entirely independent variables to the term it was copied from.
     */
    public Functor queryConversion()
    {
        /*log.fine("public Functor queryConversion(): called)");*/

        Functor copy = (Functor) super.queryConversion();

        if (arguments != null)
        {
            copy.arguments = new Term[arguments.length];

            for (int i = 0; i < arguments.length; i++)
            {
                copy.arguments[i] = arguments[i].queryConversion();
            }
        }

        return copy;
    }

    /**
     * Creates a string representation of this functor, mostly used for debugging purposes.
     *
     * @return A string representation of this functor.
     */
    public String toString()
    {
        return "Functor: [ name = " + name + ", arity = " + arity + ", arguments = " + toStringArguments() + " ]";
    }

    /** {@inheritDoc} */
    public void accept(TermVisitor visitor)
    {
        if (visitor instanceof FunctorVisitor)
        {
            ((FunctorVisitor) visitor).visit(this);
        }
        else
        {
            super.accept(visitor);
        }
    }

    /** {@inheritDoc} */
    public Functor acceptTransformer(TermTransformer transformer)
    {
        Functor result;

        if (transformer instanceof FunctorTransformer)
        {
            result = ((FunctorTransformer) transformer).transform(this);
        }
        else
        {
            result = (Functor) super.acceptTransformer(transformer);
        }

        if (arguments != null)
        {
            for (int i = 0; i < arguments.length; i++)
            {
                result.arguments[i] = arguments[i].acceptTransformer(transformer);
            }
        }

        return result;
    }

    /** {@inheritDoc} */
    public String toString(VariableAndFunctorInterner interner, boolean printVarName, boolean printBindings)
    {
        if (name < 0)
        {
            return "internal_built_in";
        }

        String result = interner.getFunctorName(name);

        if (arity > 0)
        {
            result += "(";

            for (int i = 0; i < arity; i++)
            {
                Term nextArg = arguments[i];
                result += nextArg.toString(interner, printVarName, printBindings) + ((i < (arity - 1)) ? ", " : "");
            }

            result += ")";
        }

        return result;
    }

    /**
     * Creates a string representation of this functors arguments, mostly used for debugging purposes.
     *
     * @return A string reprenestation of this functors arguments.
     */
    protected String toStringArguments()
    {
        String result = "";

        if (arity > 0)
        {
            result += "[ ";

            for (int i = 0; i < arity; i++)
            {
                Term nextArg = arguments[i];
                result += ((nextArg != null) ? nextArg.toString() : "<null>") + ((i < (arity - 1)) ? ", " : " ");
            }

            result += " ]";
        }

        return result;
    }
}


/*
Copyright (c) 2013, Groupon, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

Neither the name of GROUPON nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.groupon.novie.internal.services;

import java.util.List;
import java.util.Map.Entry;

import javax.annotation.Resource;

import com.groupon.novie.internal.exception.NovieRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service;

import com.groupon.novie.SchemaDefinition;
import com.groupon.novie.internal.engine.QueryParameter;
import com.groupon.novie.internal.engine.builder.SqlQueryBuilder;
import com.groupon.novie.internal.engine.QueryParameter.QueryParameterKind;
import com.groupon.novie.internal.response.MeasureAppender;

/**
 * The service in charge of managing calls to the {@link SqlQueryBuilder}.
 *
 * @author thomas
 */
@Service
public class SqlQueryEngine {

    private static final Logger LOG = LoggerFactory.getLogger(SqlQueryEngine.class);

    @Resource(name = "novieEngineJdbcTemplate")
    private NamedParameterJdbcTemplate jdbcTemplate;

    /**
     * Execute a query to retrieve the summary. This do not have any group by element
     *
     * @param schemaDefinition The schema definition
     * @param resultClazz      The result object
     * @param queryParameter   The Query parameters object
     * @return the summary object if exist. Otherwise throws an exception.
     * @throws com.groupon.novie.internal.exception.NovieRuntimeException
     */
    public <T extends MeasureAppender> T retrieveSummary(SchemaDefinition schemaDefinition, Class<T> resultClazz, QueryParameter queryParameter) throws NovieRuntimeException {
        final SqlQueryBuilder<T> sqlQueryBuilder = new SqlQueryBuilder<T>(schemaDefinition, resultClazz,
                queryParameter.partialCopy(QueryParameterKind.GROUPS, QueryParameterKind.PAGE));
        List<T> result = executeQuery(sqlQueryBuilder);
        if (result.isEmpty()) {
            throw new NovieRuntimeException("Summary doesn't return any result.");
        }
        if (result.size() > 1) {
            throw new NovieRuntimeException("Summary returns more than one result.");
        }
        return result.get(0);
    }

    /**
     * Execute a query to retrieve the records.
     *
     * @param schemaDefinition The schema definition
     * @param resultClazz      The result object
     * @param queryParameter   The Query parameters object
     * @return the summary object if exist. Otherwise throws an exception.
     * @throws com.groupon.novie.internal.exception.NovieRuntimeException
     */
    public <T extends MeasureAppender> List<T> retrieveRecords(SchemaDefinition schemaDefinition, Class<T> resultClazz, QueryParameter queryParameter) throws NovieRuntimeException {
        final SqlQueryBuilder<T> sqlQueryBuilder = new SqlQueryBuilder<T>(schemaDefinition, resultClazz, queryParameter);
        return executeQuery(sqlQueryBuilder);
    }

    /**
     * Private methode called by the public ones to effectively run the query.
     *
     * @param sqlQueryBuilder the QueryBuilder used to run the query
     * @return a list of resultObject
     * @throws NovieRuntimeException
     */
    private <T extends MeasureAppender> List<T> executeQuery(final SqlQueryBuilder<T> sqlQueryBuilder) throws NovieRuntimeException {
        sqlQueryBuilder.buildQuery();
        final String queryString = sqlQueryBuilder.getQueryString();
        LOG.debug(queryString);

        long beforeQuery = System.currentTimeMillis();
        List<T> returnValue = jdbcTemplate.query(queryString, sqlQueryBuilder.getMapSqlParameterSource(), sqlQueryBuilder);

        if (LOG.isInfoEnabled()) {
            LOG.info("SQL query successfully ran in " + (System.currentTimeMillis() - beforeQuery) + "ms.");
        }

        if (LOG.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder();
            for (Entry<String, Object> e : sqlQueryBuilder.getMapSqlParameterSource().getValues().entrySet()) {
                if (sb.length() > 0) {
                    sb.append(",");
                }
                sb.append(e.getKey());
                sb.append("=");
                sb.append(e.getValue());

            }
            sb.insert(0, "Parameters [");
            sb.append("]");
            LOG.debug(sb.toString());
        }
        return returnValue;
    }

}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.thesett.aima.logic.fol.wam.debugger.monitor;

import java.nio.ByteBuffer;

import com.thesett.aima.logic.fol.wam.debugger.InternalMemoryLayoutBean;
import com.thesett.aima.logic.fol.wam.debugger.InternalRegisterBean;
import com.thesett.aima.logic.fol.wam.machine.WAMResolvingMachineDPI;
import com.thesett.aima.logic.fol.wam.machine.WAMResolvingMachineDPIMonitor;

/**
 * MachineMonitor is the top level DPI monitor implementation for the debugger. Its purpose is to link together child
 * monitor components for different parts of the machine, that in turn update the user interface model in response to
 * events on the machine.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Connect together the child monitor components.
 *      {@link RegisterSetMonitor}, {@link MemoryLayoutMonitor},
 *          {@link BreakpointMonitor}, {@link ByteCodeMonitor}

 *
 Initiate the update of child monitor components in response to machine evens.
 *      {@link RegisterSetMonitor}, {@link MemoryLayoutMonitor},
 *          {@link BreakpointMonitor}, {@link ByteCodeMonitor}

 *

 *
 * @author Rupert Smith
 */
public class MachineMonitor implements WAMResolvingMachineDPIMonitor
{
    /** Holds a copy of the internal registers and monitors them for changes. */
    private InternalRegisterBean internalRegisters;

    /** Holds a copy of the memory layout registers and monitors them for changes. */
    private InternalMemoryLayoutBean layoutRegisters;

    /** Holds the monitor to listen for changes to the register set. */
    private final RegisterSetMonitor registerSetMonitor;

    /** Holds the monitor to listen for changes to the memory layout. */
    private final MemoryLayoutMonitor layoutMonitor;

    /** The monitor to listen for breakpoints. */
    private final BreakpointMonitor breakpointMonitor;

    /** The monitor to listen for bytecode changes on the machine. */
    private final ByteCodeMonitor byteCodeMonitor;

    /**
     * Creates the machine monitor.
     *
     * @param registerSetMonitor The monitor to listen for changes to the register set.
     * @param layoutMonitor      The monitor to listen for changes to the memory layout.
     * @param breakpointMonitor  The monitor to listen for breakpoints.
     * @param byteCodeMonitor    The monitor to listen for bytecode changes on the machine.
     */
    public MachineMonitor(RegisterSetMonitor registerSetMonitor, MemoryLayoutMonitor layoutMonitor,
        BreakpointMonitor breakpointMonitor, ByteCodeMonitor byteCodeMonitor)
    {
        this.registerSetMonitor = registerSetMonitor;
        this.layoutMonitor = layoutMonitor;
        this.breakpointMonitor = breakpointMonitor;
        this.byteCodeMonitor = byteCodeMonitor;
    }

    /** {@inheritDoc} */
    public void onReset(WAMResolvingMachineDPI dpi)
    {
        internalRegisters = new InternalRegisterBean(0, 0, 0, 0, 0, 0, 0, 0, 0, false);
        internalRegisters.addPropertyChangeListener(registerSetMonitor);
        internalRegisters.addPropertyChangeListener(breakpointMonitor);
        internalRegisters.updateRegisters(dpi.getInternalRegisters());

        layoutRegisters = new InternalMemoryLayoutBean(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
        layoutRegisters.addPropertyChangeListener(layoutMonitor);
        layoutRegisters.updateRegisters(dpi.getMemoryLayout());
    }

    /** {@inheritDoc} */
    public void onCodeUpdate(WAMResolvingMachineDPI dpi, int start, int length)
    {
        ByteBuffer codeBuffer = dpi.getCodeBuffer(start, length);
        byteCodeMonitor.onCodeUpdate(codeBuffer, start, length, dpi.getVariableAndFunctorInterner(), dpi);
    }

    /** {@inheritDoc} */
    public void onExecute(WAMResolvingMachineDPI dpi)
    {
        internalRegisters.updateRegisters(dpi.getInternalRegisters());
        layoutRegisters.updateRegisters(dpi.getMemoryLayout());
    }

    /** {@inheritDoc} */
    public void onStep(WAMResolvingMachineDPI dpi)
    {
        internalRegisters.updateRegisters(dpi.getInternalRegisters());
        layoutRegisters.updateRegisters(dpi.getMemoryLayout());

        breakpointMonitor.pause();
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.thesett.aima.logic.fol.wam.compiler;

import java.nio.ByteBuffer;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.FunctorName;
import com.thesett.aima.logic.fol.LinkageException;
import com.thesett.aima.logic.fol.Term;
import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
import com.thesett.aima.logic.fol.wam.machine.WAMCodeView;
import com.thesett.aima.logic.fol.wam.machine.WAMMachine;
import com.thesett.common.util.Pair;
import com.thesett.common.util.Sizeable;
import com.thesett.common.util.SizeableLinkedList;
import com.thesett.common.util.SizeableList;
import com.thesett.common.util.doublemaps.SymbolKey;

/**
 * WAMInstruction provides a structured in-memory representation of the WAM instruction set, as well as utilities to
 * emmit the instructions as byte code, disassemble them back into the structured representation, and pretty print them.
 *
 *
Instructions implement {@link Sizeable} reporting their length in bytes as their 'size of'.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Provide the translation of the WAM instruction set down to bytes.
 *
 Provide dissasembly of byte encoded instructions back into structured instructions.
 *
 Calculate the length of an instruction in bytes.  {@link com.thesett.common.util.Sizeable}
 *

 *
 * @author Rupert Smith
 */
public class WAMInstruction implements Sizeable
{
    /** Instruction to write out a struc onto the heap. */
    public static final byte PUT_STRUC = 0x01;

    /** The instruction to set a register as a variable. */
    public static final byte SET_VAR = 0x02;

    /** The instruction to set a register to a heap location. */
    public static final byte SET_VAL = 0x03;

    /** The instruction to compare a register to a structure on the heap. */
    public static final byte GET_STRUC = 0x04;

    /** The instruction to unify a register with a variable. */
    public static final byte UNIFY_VAR = 0x05;

    /** The instruction to unify a register with a location on the heap. */
    public static final byte UNIFY_VAL = 0x06;

    /** The instruction to copy a heap location into an argument register. */
    public static final byte PUT_VAR = 0x07;

    /** The instruction to copy a register into an argument register. */
    public static final byte PUT_VAL = 0x08;

    /** The instruction to unify an argument register with a variable. */
    public static final byte GET_VAR = 0x09;

    /** The instruction to unify a register with a location on the heap. */
    public static final byte GET_VAL = 0x0a;

    /** The instruction to copy a constant into an argument register. */
    public static final byte PUT_CONST = 0x12;

    /** The instruction to compare or bind a reference from a register to a constant. */
    public static final byte GET_CONST = 0x13;

    /** The instruction to write a constant onto the heap. */
    public static final byte SET_CONST = 0x14;

    /** The instruction to unify the heap with a constant. */
    public static final byte UNIFY_CONST = 0x15;

    /** The instruction to copy a list pointer into an argument register. */
    public static final byte PUT_LIST = 0x16;

    /** The instruction to compare or bind a reference from a register to a list pointer. */
    public static final byte GET_LIST = 0x17;

    /** The instruction to write an anonymous variable onto the heap. */
    public static final byte SET_VOID = 0x18;

    /** The instruction to unify with anonymous variables on the heap. */
    public static final byte UNIFY_VOID = 0x19;

    /** The instruction to initialize an argument register, ensuring the stack variable it references is globalized. */
    public static final byte PUT_UNSAFE_VAL = 0x1c;

    /** The instruction to set a register, ensuring the stack variable it references is globalized. */
    public static final byte SET_LOCAL_VAL = 0x1d;

    /** The instruction to unify a register, ensuring any stack variable it references is globalized. */
    public static final byte UNIFY_LOCAL_VAL = 0x1e;

    /** The instruction to call a predicate. */
    public static final byte CALL = 0x0b;

    /** The instruction to tail call a predicate. */
    public static final byte EXECUTE = 0x1a;

    /** The instruction to return from a called predicate. */
    public static final byte PROCEED = 0x0c;

    /** The stack frame allocation instruction. */
    public static final byte ALLOCATE = 0x1b;

    /** The stack frame allocation instruction for queries. */
    public static final byte ALLOCATE_N = 0x0d;

    /** The stack frame de-allocation instruction. */
    public static final byte DEALLOCATE = 0x0e;

    /** The first clause try instruction. */
    public static final byte TRY_ME_ELSE = 0x0f;

    /** The middle clause retry instruction. */
    public static final byte RETRY_ME_ELSE = 0x10;

    /** The final clause trust or fail instruction. */
    public static final byte TRUST_ME = 0x11;

    /** The first clause indexing try instruction. */
    public static final byte TRY = 0x1f;

    /** The middle clause indexing retry instruction. */
    public static final byte RETRY = 0x20;

    /** The final clause indexing trust or fail instruction. */
    public static final byte TRUST = 0x21;

    /** The second level indexing instruction. */
    public static final byte SWITCH_ON_TERM = 0x22;

    /** The third level indexing instruction for constants. */
    public static final byte SWITCH_ON_CONST = 0x23;

    /** The third level indexing instruction for structures. */
    public static final byte SWITCH_ON_STRUC = 0x24;

    /** The simpler neck-cut instruction. */
    public static final byte NECK_CUT = 0x25;

    /** The get level instruction for cuts. */
    public static final byte GET_LEVEL = 0x26;

    /** The full deep cut instruction. */
    public static final byte CUT = 0x27;

    /** The continue instruction for inline choice points. */
    public static final byte CONTINUE = 0x28;

    /** The no-op instruction, useful for inserting labels into the code. */
    public static final byte NO_OP = 0x29;

    /** The internal call instruction for intrinsics. */
    public static final byte CALL_INTERNAL = 0x2a;

    /** The suspend operation. */
    public static final byte SUSPEND = 0x7f;

    // === Defines the addressing modes.

    /** Used to specify addresses relative to registers. */
    public static final byte REG_ADDR = 0x01;

    /** Used to specify addresses relative to the current stack frame. */
    public static final byte STACK_ADDR = 0x02;

    // === Defines the heap cell marker types.

    /** Indicates a reference data type. */
    public static final byte REF = 0x00;

    /** Indicates a structure data type. */
    public static final byte STR = 0x01;

    /** Indicates a constant atom data type. */
    public static final byte CON = 0x02;

    /** Indicates a list data type. */
    public static final byte LIS = 0x03;

    /** Defines the L0 virtual machine instruction set as constants. */
    public enum WAMInstructionSet
    {
        /** Instruction to write out a struc onto the heap. */
        PutStruc(PUT_STRUC, "put_struc", 7, 0xb)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Fn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Fn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Fn(pretty, instruction);
            }
        },

        /** The instruction to set a register as a variable. */
        SetVar(SET_VAR, "set_var", 3, 0x3),

        /** The instruction to set a register to a heap location. */
        SetVal(SET_VAL, "set_val", 3, 0x3),

        /** The instruction to set a register, ensuring the stack variable it references is globalized. */
        SetLocalVal(SET_LOCAL_VAL, "set_local_val", 3, 0x3),

        /** The instruction to compare a register to a structure on the heap. */
        GetStruc(GET_STRUC, "get_struc", 7, 0xb)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Fn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Fn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Fn(pretty, instruction);
            }
        },

        /** The instruction to unify a register with a variable. */
        UnifyVar(UNIFY_VAR, "unify_var", 3, 0x3),

        /** The instruction to unify a register with a location on the heap. */
        UnifyVal(UNIFY_VAL, "unify_val", 3, 0x3),

        /** The instruction to unify a register, ensuring any stack variable it references is globalized. */
        UnifyLocalVal(UNIFY_LOCAL_VAL, "unify_local_val", 3, 0x3),

        /** The instruction to copy a heap location into an argument register. */
        PutVar(PUT_VAR, "put_var", 4, 0x7)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Reg2(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Reg2(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Reg2(pretty, instruction);
            }
        },

        /** The instruction to copy a register into an argument register. */
        PutVal(PUT_VAL, "put_val", 4, 0x7)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Reg2(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Reg2(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Reg2(pretty, instruction);
            }
        },

        /**
         * The instruction to initialize an argument register, ensuring the stack variable it references is globalized.
         */
        PutUnsafeVal(PUT_UNSAFE_VAL, "put_unsafe_val", 4, 0x7)
        {
            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Reg2(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Reg2(pretty, instruction);
            }
        },

        /** The instruction to unify an argument register with a variable. */
        GetVar(GET_VAR, "get_var", 4, 0x7)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Reg2(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Reg2(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Reg2(pretty, instruction);
            }
        },

        /** The instruction to unify a register with a location on the heap. */
        GetVal(GET_VAL, "get_val", 4, 0x7)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Reg2(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Reg2(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Reg2(pretty, instruction);
            }
        },

        /** The instruction to copy a constant into an argument register. */
        PutConstant(PUT_CONST, "put_const", 7, 0xb)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Fn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                emmitCodeReg1Fn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Fn(pretty, instruction);
            }
        },

        /** The instruction to compare or bind a reference from a register to a constant. */
        GetConstant(GET_CONST, "get_const", 7, 0xb)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Fn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                emmitCodeReg1Fn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Fn(pretty, instruction);
            }
        },

        /** The instruction to write a constant onto the heap. */
        SetConstant(SET_CONST, "set_const", 5, 0x8)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleFn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                emmitCodeFn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringFn(pretty, instruction);
            }
        },

        /** The instruction to unify the heap with a constant. */
        UnifyConstant(UNIFY_CONST, "unify_const", 5, 0x8)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleFn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                emmitCodeFn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringFn(pretty, instruction);
            }
        },

        /** The instruction to copy a list pointer into an argument register. */
        PutList(PUT_LIST, "put_list", 3, 0x3),

        /** The instruction to compare or bind a reference from a register to a list pointer. */
        GetList(GET_LIST, "get_list", 3, 0x3),

        /** The instruction to write an anonymous variable onto the heap. */
        SetVoid(SET_VOID, "set_void", 2, 0x2)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1NoMode(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1NoMode(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + instruction.reg1;
            }
        },

        /** The instruction to unify with anonymous variables on the heap. */
        UnifyVoid(UNIFY_VOID, "unify_void", 2, 0x2)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1NoMode(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1NoMode(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + instruction.reg1;
            }
        },

        /** The instruction to call a predicate. */
        Call(CALL, "call", 7, 0xa)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int entryPoint = codeBuf.getInt(ip + 1);
                int arity = codeBuf.get(ip + 5);
                instruction.reg1 = codeBuf.get(ip + 6);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int toCall = machine.internFunctorName(instruction.fn);

                WAMCallPoint callPointToCall = machine.resolveCallPoint(toCall);

                // Ensure that a valid call point was returned, otherwise a linkage error has occurred.
                if (callPointToCall == null)
                {
                    throw new LinkageException("Could not resolve call to " + instruction.fn + ".", null, null,
                        "Unable to resolve call to " + instruction.fn.getName() + "/" + instruction.fn.getArity() +
                        ".");
                }

                int entryPoint = callPointToCall.entryPoint;

                codeBuf.put(code);
                codeBuf.putInt(entryPoint);
                codeBuf.put((byte) instruction.fn.getArity());
                codeBuf.put(instruction.reg1);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " +
                    ((instruction.fn != null) ? (instruction.fn.getName() + "/" + instruction.fn.getArity()) : "") +
                    (", " + instruction.reg1);
            }
        },

        /** The instruction to tail call a predicate. */
        Execute(EXECUTE, "execute", 6, 0x8)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int entryPoint = codeBuf.getInt(ip + 1);
                int arity = codeBuf.get(ip + 5);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int toCall = machine.internFunctorName(instruction.fn);

                WAMCallPoint callPointToCall = machine.resolveCallPoint(toCall);

                // Ensure that a valid call point was returned, otherwise a linkage error has occurred.
                if (callPointToCall == null)
                {
                    throw new LinkageException("Could not resolve call to " + instruction.fn + ".", null, null,
                        "Unable to resolve call to " + instruction.fn.getName() + "/" + instruction.fn.getArity() +
                        ".");
                }

                int entryPoint = callPointToCall.entryPoint;

                codeBuf.put(code);
                codeBuf.putInt(entryPoint);
                codeBuf.put((byte) instruction.fn.getArity());
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " +
                    ((instruction.fn != null) ? (instruction.fn.getName() + "/" + instruction.fn.getArity()) : "");
            }
        },

        /** The instruction to return from a called predicate. */
        Proceed(PROCEED, "proceed", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The stack frame allocation instruction. */
        Allocate(ALLOCATE, "allocate", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The stack frame allocation instruction for queries. */
        AllocateN(ALLOCATE_N, "allocate_n", 2, 0x2)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1NoMode(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1NoMode(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + instruction.reg1;
            }
        },

        /** The stack frame deallocation instruction. */
        Deallocate(DEALLOCATE, "deallocate", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The first clause try instruction. */
        TryMeElse(TRY_ME_ELSE, "try_me_else", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Intern the alternative forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                WAMLabel label = instruction.target1;

                return pretty + " " + ((label != null) ? label.toPrettyString() : "");
            }
        },

        /** The middle clause retry instruction. */
        RetryMeElse(RETRY_ME_ELSE, "retry_me_else", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                // Intern the alternative forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                WAMLabel label = instruction.target1;

                return pretty + " " + ((label != null) ? label.toPrettyString() : "");
            }
        },

        /** The final clause trust or fail instruction. */
        TrustMe(TRUST_ME, "trust_me", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The first clause indexing try instruction. */
        Try(TRY, "try", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                if (instruction.label != null)
                {
                    int label = machine.internFunctorName(instruction.label);
                    machine.resolveLabelPoint(label, ip);
                }

                // Intern the forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                WAMLabel label = instruction.target1;

                return pretty + " " + ((label != null) ? label.toPrettyString() : "");
            }
        },

        /** The middle clause indexing retry instruction. */
        Retry(RETRY, "retry", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                if (instruction.label != null)
                {
                    int label = machine.internFunctorName(instruction.label);
                    machine.resolveLabelPoint(label, ip);
                }

                // Intern the forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                WAMLabel label = instruction.target1;

                return pretty + " " + ((label != null) ? label.toPrettyString() : "");
            }
        },

        /** The final clause indexing trust or fail instruction. */
        Trust(TRUST, "trust", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                if (instruction.label != null)
                {
                    int label = machine.internFunctorName(instruction.label);
                    machine.resolveLabelPoint(label, ip);
                }

                // Intern the forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The second level indexing instruction. */
        SwitchOnTerm(SWITCH_ON_TERM, "switch_on_term", 17, 0x0)
        {
            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                // Intern the alternative forward labels, and write them out as zero initially, for later completion.
                int jump = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(jump, ip + 1);

                jump = machine.internFunctorName(instruction.target2);
                machine.reserveReferenceToLabel(jump, ip + 5);

                jump = machine.internFunctorName(instruction.target3);
                machine.reserveReferenceToLabel(jump, ip + 9);

                jump = machine.internFunctorName(instruction.target4);
                machine.reserveReferenceToLabel(jump, ip + 13);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + labelToString(instruction.target1) + " / " + labelToString(instruction.target2) +
                    " / " + labelToString(instruction.target3) + " / " + labelToString(instruction.target4);
            }
        },

        /** The third level indexing instruction for constants. */
        SwitchOnConst(SWITCH_ON_CONST, "switch_on_const", 9, 0x0)
        {
            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                // Intern the alternative forward labels, and write them out as zero initially, for later completion.
                int jump = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(jump, ip + 1);

                jump = machine.internFunctorName(instruction.target2);
                machine.reserveReferenceToLabel(jump, ip + 5);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + labelToString(instruction.target1) + " / " + labelToString(instruction.target2);
            }
        },

        /** The third level indexing instruction for structures. */
        SwitchOnStruc(SWITCH_ON_STRUC, "switch_on_struc", 9, 0x0)
        {
            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                // Intern the alternative forward labels, and write them out as zero initially, for later completion.
                int jump = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(jump, ip + 1);

                jump = machine.internFunctorName(instruction.target2);
                machine.reserveReferenceToLabel(jump, ip + 5);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + labelToString(instruction.target1) + " / " + labelToString(instruction.target2);
            }
        },

        /** The simpler neck-cut instruction. */
        NeckCut(NECK_CUT, "neck_cut", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                codeBuf.put(code);
            }

            /** {@inheritDoc} */

            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The get level instruction for cuts. */
        GetLevel(GET_LEVEL, "get_level", 2, 0x2)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1NoMode(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1NoMode(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + instruction.reg1;
            }
        },

        /** The full deep cut instruction. */
        Cut(CUT, "cut", 2, 0x2)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1NoMode(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1NoMode(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + instruction.reg1;
            }
        },

        /** The continue instruction for inline choice points. */
        Continue(CONTINUE, "continue", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Intern the forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                WAMLabel label = instruction.target1;

                return pretty + " " + ((label != null) ? label.toPrettyString() : "");
            }
        },

        /** The no-op instruction, useful for inserting labels into the code. */
        NoOp(NO_OP, "no_op", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The instruction to call a predicate. */
        CallInternal(CALL_INTERNAL, "call_internal", 7, 0xa)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int entryPoint = codeBuf.getInt(ip + 1);
                int arity = codeBuf.get(ip + 5);
                instruction.reg1 = codeBuf.get(ip + 6);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int toCall = machine.internFunctorName(instruction.fn);

                WAMCallPoint callPointToCall = machine.resolveCallPoint(toCall);

                // Ensure that a valid call point was returned, otherwise a linkage error has occurred.
                if (callPointToCall == null)
                {
                    throw new LinkageException("Could not resolve internal call to " + instruction.fn + ".", null, null,
                        "Unable to resolve internal call to " + instruction.fn.getName() + "/" +
                        instruction.fn.getArity() + ".");
                }

                int entryPoint = callPointToCall.entryPoint;

                codeBuf.put(code);
                codeBuf.putInt(entryPoint);
                codeBuf.put((byte) instruction.fn.getArity());
                codeBuf.put(instruction.reg1);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " +
                    ((instruction.fn != null) ? (instruction.fn.getName() + "/" + instruction.fn.getArity()) : "") +
                    (", " + instruction.reg1);
            }
        },

        /** The suspend on success instruction. */
        Suspend(SUSPEND, "suspend", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        };

        /** Holds a mapping of the instruction by byte code. */
        private static final Map<Byte, WAMInstructionSet> codeToValue = new HashMap<Byte, WAMInstructionSet>();

        static
        {
            for (WAMInstructionSet instruction : EnumSet.allOf(WAMInstructionSet.class))
            {
                codeToValue.put(instruction.code, instruction);
            }
        }

        /** Holds the byte representation of the instruction. */
        protected byte code;

        /** Holds the human readable form of the instruction. */
        protected String pretty;

        /** Holds the length of the instruction in bytes. */
        protected int length;

        /**
         * Holds a bit mask defining which argument fields this op-code uses. The correspondence between bits and fields
         * is:
         *
         *

Bits to Fields

         *
 Bit  Field
         *
  0   {@link WAMInstruction#mode1}
         *
  1   {@link WAMInstruction#reg1}
         *
  2   {@link WAMInstruction#reg2}
         *
  3   {@link WAMInstruction#fn}
         *
  4   {@link WAMInstruction#target1}
         *

         */
        protected int fieldMask;

        /**
         * Creates a new L0 instruction with the specified byte code.
         *
         * @param i      The byte code for the instruction.
         * @param pretty The human readable form of the instruction.
         * @param length The length of the instruction plus arguments in bytes.
         */
        private WAMInstructionSet(byte i, String pretty, int length, int fieldMask)
        {
            this.code = i;
            this.pretty = pretty;
            this.length = length;
            this.fieldMask = fieldMask;
        }

        /**
         * Creates an instruction from a byte code.
         *
         * @param  code The byte coded form of the instruction.
         *
         * @return An instruction matching the byte code, or <tt>null <tt>if no instruction matches the code.
         */
        public static WAMInstructionSet fromCode(byte code)
        {
            return codeToValue.get(code);
        }

        /**
         * Provides the base name of register 1, "X" for register addressing, "Y" for stack mode addressing.
         *
         * @param  instruction The instruction to get the base register name from;
         *
         * @return The base name of register 1, "X" for register addressing, "Y" for stack mode addressing.
         */
        public static String reg1Name(WAMInstruction instruction)
        {
            return (instruction.mode1 == STACK_ADDR) ? "Y" : "X";
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param  instruction The instruction, including its arguments.
         * @param  codeBuf     The code buffer to write to.
         * @param  machine     The binary machine to write the code into.
         *
         * @throws LinkageException If required symbols to link to cannot be found in the binary machine.
         */
        public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            throws LinkageException
        {
            emmitCodeReg1(codeBuf, code, instruction);
        }

        /**
         * Provides the field mask describing which argument fields this instruction uses.
         *
         * @return The field mask describing which argument fields this instruction uses.
         */
        public int getFieldMask()
        {
            return fieldMask;
        }

        /**
         * Provides the human readable form of the instruction.
         *
         * @return The human readable form of the instruction.
         */
        public String getPretty()
        {
            return pretty;
        }

        /**
         * Prints the human readable form of the instruction for debugging purposes.
         *
         * @param  instruction The instruction, including its arguments.
         *
         * @return The human readable form of the instruction for debugging purposes.
         */
        public String toString(WAMInstruction instruction)
        {
            return toStringReg1(pretty, instruction);
        }

        /**
         * Gets the byte coded representation of the instruction.
         *
         * @return The byte coded representation of the instruction.
         */
        public byte getCode()
        {
            return code;
        }

        /**
         * Calculates the length of this instruction in bytes.
         *
         * @return The length of this instruction in bytes.
         */
        public int length()
        {
            return length;
        }

        /**
         * Disassembles the arguments of an instruction. This is a default implementation to handle the most common
         * case; instructions with one register argument.
         *
         * @param instruction The instruction, including its arguments.
         * @param ip          The start of the instruction within the buffer.
         * @param codeBuf     The code buffer.
         * @param interner    The interner to look up function and variable names against.
         */
        protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
            VariableAndFunctorInterner interner)
        {
            disassembleReg1(codeBuf, ip, instruction);
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param codeBuf     The code buffer to write to.
         * @param code        The instruction mnemonic.
         * @param instruction The instruction, including its arguments.
         */
        private static void emmitCodeReg1(ByteBuffer codeBuf, byte code, WAMInstruction instruction)
        {
            codeBuf.put(code);
            codeBuf.put(instruction.mode1);
            codeBuf.put(instruction.reg1);
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param codeBuf     The code buffer to write to.
         * @param code        The instruction mnemonic.
         * @param instruction The instruction, including its arguments.
         */
        private static void emmitCodeReg1NoMode(ByteBuffer codeBuf, byte code, WAMInstruction instruction)
        {
            codeBuf.put(code);
            codeBuf.put(instruction.reg1);
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param codeBuf     The code buffer to write to.
         * @param code        The instruction mnemonic.
         * @param instruction The instruction, including its arguments.
         */
        private static void emmitCodeReg1Reg2(ByteBuffer codeBuf, byte code, WAMInstruction instruction)
        {
            codeBuf.put(code);
            codeBuf.put(instruction.mode1);
            codeBuf.put(instruction.reg1);
            codeBuf.put(instruction.reg2);
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param codeBuf     The code buffer to write to.
         * @param code        The instruction mnemonic.
         * @param instruction The instruction, including its arguments.
         * @param interner    The binary machine to write the code into.
         */
        private static void emmitCodeReg1Fn(ByteBuffer codeBuf, byte code, WAMInstruction instruction,
            VariableAndFunctorInterner interner)
        {
            codeBuf.put(code);
            codeBuf.put(instruction.mode1);
            codeBuf.put(instruction.reg1);

            int arity = instruction.fn.getArity() << 24;
            int name = interner.internFunctorName(instruction.fn) & 0x00ffffff;
            codeBuf.putInt(arity | name);
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param codeBuf     The code buffer to write to.
         * @param code        The instruction mnemonic.
         * @param instruction The instruction, including its arguments.
         * @param interner    The binary machine to write the code into.
         */
        private static void emmitCodeFn(ByteBuffer codeBuf, byte code, WAMInstruction instruction, VariableAndFunctorInterner interner)
        {
            codeBuf.put(code);

            int arity = instruction.fn.getArity() << 24;
            int name = interner.internFunctorName(instruction.fn) & 0x00ffffff;
            codeBuf.putInt(arity | name);
        }

        /**
         * Helper print function that prints an instruction with one register argument.
         *
         * @param  pretty      The pretty printed instruction mnenomic.
         * @param  instruction The instruction data.
         *
         * @return A pretty printed instruction.
         */
        private static String toStringReg1(String pretty, WAMInstruction instruction)
        {
            return pretty + " " + reg1Name(instruction) + instruction.reg1;
        }

        /**
         * Helper print function that prints an instruction with two register arguments.
         *
         * @param  pretty      The pretty printed instruction mnenomic.
         * @param  instruction The instruction data.
         *
         * @return A pretty printed instruction.
         */
        private static String toStringReg1Reg2(String pretty, WAMInstruction instruction)
        {
            return pretty + " " + reg1Name(instruction) + instruction.reg1 + ", A" + instruction.reg2;
        }

        /**
         * Helper print function that prints an instruction with one register argument and a functor reference.
         *
         * @param  pretty      The pretty printed instruction mnenomic.
         * @param  instruction The instruction data.
         *
         * @return A pretty printed instruction.
         */
        private static String toStringReg1Fn(String pretty, WAMInstruction instruction)
        {
            return pretty + " " +
                ((instruction.fn != null) ? (instruction.fn.getName() + "/" + instruction.fn.getArity()) : "") + ", A" +
                instruction.reg1;
        }

        /**
         * Helper print function that prints an instruction with one functor reference.
         *
         * @param  pretty      The pretty printed instruction mnenomic.
         * @param  instruction The instruction data.
         *
         * @return A pretty printed instruction.
         */
        private static String toStringFn(String pretty, WAMInstruction instruction)
        {
            return pretty + " " +
                ((instruction.fn != null) ? (instruction.fn.getName() + "/" + instruction.fn.getArity()) : "");
        }

        /**
         * Disassembles the arguments to an instruction that takes one register and one functor reference.
         *
         * @param codeBuf     The code buffer to disassemble from.
         * @param ip          The instruction pointer within the code buffer.
         * @param instruction The instruction to store the disassembles arguments in.
         * @param interner    The interner to look up function and variable names against.
         */
        private static void disassembleReg1Fn(ByteBuffer codeBuf, int ip, WAMInstruction instruction,
            VariableAndFunctorInterner interner)
        {
            instruction.mode1 = codeBuf.get(ip + 1);
            instruction.reg1 = codeBuf.get(ip + 2);

            int fn = codeBuf.getInt(ip + 3);
            int f = fn & 0x00ffffff;
            instruction.fn = interner.getDeinternedFunctorName(f);
        }

        /**
         * Disassembles the arguments to an instruction that takes one functor reference.
         *
         * @param codeBuf     The code buffer to disassemble from.
         * @param ip          The instruction pointer within the code buffer.
         * @param instruction The instruction to store the disassembles arguments in.
         * @param interner    The interner to look up function and variable names against.
         */
        private static void disassembleFn(ByteBuffer codeBuf, int ip, WAMInstruction instruction,
            VariableAndFunctorInterner interner)
        {
            int fn = codeBuf.getInt(ip + 1);
            int f = fn & 0x00ffffff;
            instruction.fn = interner.getDeinternedFunctorName(f);
        }

        /**
         * Disassembles the arguments to an instruction that takes one register argument.
         *
         * @param codeBuf     The code buffer to disassemble from.
         * @param ip          The instruction pointer within the code buffer.
         * @param instruction The instruction to store the disassembles arguments in.
         */
        private static void disassembleReg1(ByteBuffer codeBuf, int ip, WAMInstruction instruction)
        {
            instruction.mode1 = codeBuf.get(ip + 1);
            instruction.reg1 = codeBuf.get(ip + 2);
        }

        /**
         * Disassembles the arguments to an instruction that takes one register argument and no mode flag.
         *
         * @param codeBuf     The code buffer to disassemble from.
         * @param ip          The instruction pointer within the code buffer.
         * @param instruction The instruction to store the disassembles arguments in.
         */
        private static void disassembleReg1NoMode(ByteBuffer codeBuf, int ip, WAMInstruction instruction)
        {
            instruction.reg1 = codeBuf.get(ip + 1);
        }

        /**
         * Disassembles the arguments to an instruction that takes one register argument.
         *
         * @param codeBuf     The code buffer to disassemble from.
         * @param ip          The instruction pointer within the code buffer.
         * @param instruction The instruction to store the disassembles arguments in.
         */
        private static void disassembleReg1Reg2(ByteBuffer codeBuf, int ip, WAMInstruction instruction)
        {
            instruction.mode1 = codeBuf.get(ip + 1);
            instruction.reg1 = codeBuf.get(ip + 2);
            instruction.reg2 = codeBuf.get(ip + 3);
        }
    }

    /** The optional address label of the instruction. */
    protected WAMLabel label;

    /** The instruction. */
    protected WAMInstructionSet mnemonic;

    /** Holds the addressing mode of the first register argument to the instruction. */
    protected byte mode1;

    /** Holds the first register argument to the instruction. */
    protected byte reg1;

    /** Holds the second register argument to the instruction. */
    protected byte reg2;

    /** Holds the functor argument to the instruction. */
    protected FunctorName fn;

    /** An optional target address label. */
    protected WAMLabel target1;

    /** An optional target address label. */
    protected WAMLabel target2;

    /** An optional target address label. */
    protected WAMLabel target3;

    /** An optional target address label. */
    protected WAMLabel target4;

    /** Holds the symbol key of the argument that is held in the first register of this instruction. */
    private SymbolKey symbolKeyReg1;

    /** Holds the functor name of the argument that is assigned to the first register of this instruction. */
    private Integer functorNameReg1;

    /** An optional list of entries for an indexing table. */
    protected List<Pair<Integer, WAMLabel>> indexTable;

    /**
     * Creates an instruction for the specified mnemonic.
     *
     * @param mnemonic The instruction mnemonic.
     */
    public WAMInstruction(WAMInstructionSet mnemonic)
    {
        this.mnemonic = mnemonic;
    }

    /**
     * Creates an instruction with the mnemonic resolved from its byte encoded form.
     *
     * @param code The byte encoded form of the instruction mnemonic.
     */
    public WAMInstruction(byte code)
    {
        this.mnemonic = WAMInstructionSet.fromCode(code);
    }

    /**
     * Creates an instruction for the specified mnemonic that takes one register and one functor argument.
     *
     * @param mnemonic The instruction mnemonic.
     * @param mode1    The addressing mode to use with the register argument.
     * @param reg1     The register argument.
     * @param fn       The functor argument.
     * @param reg1Term The term that is assigned or associated with reg1.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte mode1, byte reg1, FunctorName fn, Term reg1Term)
    {
        this.mnemonic = mnemonic;
        this.mode1 = mode1;
        this.reg1 = reg1;
        this.fn = fn;

        // Record the symbol keys of the term that resulted in the creation of the instruction, and are associated
        // with reg1 of the instruction.
        symbolKeyReg1 = reg1Term.getSymbolKey();
        functorNameReg1 = reg1Term.isFunctor() ? ((Functor) reg1Term).getName() : null;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes one register and one functor argument.
     *
     * @param mnemonic The instruction mnemonic.
     * @param mode1    The addressing mode to use with the register argument.
     * @param reg1     The register argument.
     * @param fn       The functor argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte mode1, byte reg1, FunctorName fn)
    {
        this.mnemonic = mnemonic;
        this.mode1 = mode1;
        this.reg1 = reg1;
        this.fn = fn;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes two register arguments.
     *
     * @param mnemonic The instruction mnemonic.
     * @param mode1    The addressing mode to use with the first register argument.
     * @param reg1     The first register argument.
     * @param reg2     The second register argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte mode1, byte reg1, byte reg2)
    {
        this.mnemonic = mnemonic;
        this.mode1 = mode1;
        this.reg1 = reg1;
        this.reg2 = reg2;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes a single register argument.
     *
     * @param mnemonic The instruction mnemonic.
     * @param mode1    The addressing mode to use with the register argument.
     * @param reg1     The single register argument.
     * @param reg1Term The term that is assigned or associated with reg1.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte mode1, byte reg1, Term reg1Term)
    {
        this.mnemonic = mnemonic;
        this.mode1 = mode1;
        this.reg1 = reg1;

        // Record the symbol keys of the term that resulted in the creation of the instruction, and are associated
        // with reg1 of the instruction.
        symbolKeyReg1 = reg1Term.getSymbolKey();
        functorNameReg1 = reg1Term.isFunctor() ? ((Functor) reg1Term).getName() : null;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes a single register argument.
     *
     * @param mnemonic The instruction mnemonic.
     * @param mode1    The addressing mode to use with the register argument.
     * @param reg1     The single register argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte mode1, byte reg1)
    {
        this.mnemonic = mnemonic;
        this.mode1 = mode1;
        this.reg1 = reg1;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes a single register argument, and no addressing mode,
     * for situations where the addressing mode is already inferred by the instruction type.
     *
     * @param mnemonic The instruction mnemonic.
     * @param reg1     The single register argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte reg1)
    {
        this.mnemonic = mnemonic;
        this.reg1 = reg1;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes a single functor argument.
     *
     * @param mnemonic The instruction mnemonic.
     * @param fn       The functor argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, FunctorName fn)
    {
        this.mnemonic = mnemonic;
        this.fn = fn;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes a single functor argument and a single register.
     *
     * @param mnemonic The instruction mnemonic.
     * @param reg1     The single register argument.
     * @param fn       The functor argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte reg1, FunctorName fn)
    {
        this.mnemonic = mnemonic;
        this.reg1 = reg1;
        this.fn = fn;
    }

    /**
     * Creates a labelled instruction for the specified mnemonic that takes a single functor argument.
     *
     * @param label    The instructions address label.
     * @param mnemonic The instruction mnemonic.
     * @param fn       The functor argument.
     */
    public WAMInstruction(WAMLabel label, WAMInstructionSet mnemonic, WAMLabel fn)
    {
        this.label = label;
        this.mnemonic = mnemonic;
        this.target1 = fn;
    }

    /**
     * Creates a labelled instruction for the specified mnemonic.
     *
     * @param label    The instructions address label.
     * @param mnemonic The instruction mnemonic.
     */
    public WAMInstruction(WAMLabel label, WAMInstructionSet mnemonic)
    {
        this.label = label;
        this.mnemonic = mnemonic;
    }

    /**
     * Disassembles the instructions from the specified byte buffer, starting at a given location (ip). An interner for
     * the functor names encountered in the instruction buffer must also be supplied, in order to look up the functor
     * names by encoded value.
     *
     * @param  start    The start offset into the buffer to disassemble.
     * @param  length   The length of data in the buffer to disassemble.
     * @param  codeBuf  The code buffer.
     * @param  interner The interner to look up interned names with.
     * @param  codeView A view onto the machines code buffer.
     *
     * @return A list of instructions disassembles from the code buffer.
     */
    public static SizeableList<WAMInstruction> disassemble(int start, int length, ByteBuffer codeBuf,
        VariableAndFunctorInterner interner, WAMCodeView codeView)
    {
        SizeableList<WAMInstruction> result = new SizeableLinkedList<WAMInstruction>();
        int ip = start;

        while (ip < (start + length))
        {
            // Decode the instruction and its arguments.
            byte iCode = codeBuf.get(ip);

            WAMInstruction instruction = new WAMInstruction(iCode);
            instruction.mnemonic.disassembleArguments(instruction, ip, codeBuf, interner);

            // Restore any label on the instruction.
            Integer label = codeView.getNameForAddress(ip);

            if (label != null)
            {
                FunctorName name = interner.getDeinternedFunctorName(label);

                if (name instanceof WAMLabel)
                {
                    instruction.label = (WAMLabel) name;
                }
            }

            result.add(instruction);

            ip += instruction.mnemonic.length();
        }

        return result;
    }

    /**
     * Gets the instruction mnemonic.
     *
     * @return The instruction mnemonic.
     */
    public WAMInstructionSet getMnemonic()
    {
        return mnemonic;
    }

    /**
     * Gets the addressing mode of the first register argument to the instruction.
     *
     * @return The addressing mode of the first register argument to the instruction.
     */
    public byte getMode1()
    {
        return mode1;
    }

    /**
     * Gets the first register to which the instruction applies.
     *
     * @return The first register to which the instruction applies.
     */
    public byte getReg1()
    {
        return reg1;
    }

    /**
     * Sets the first register to which the instruction applies.
     *
     * @param reg1 The first register to which the instruction applies.
     */
    public void setReg1(byte reg1)
    {
        this.reg1 = reg1;
    }

    /**
     * Gets the second register to which the instruction applies.
     *
     * @return The second register to which the instruction applies.
     */
    public byte getReg2()
    {
        return reg2;
    }

    /**
     * Sets the second register to which the instruction applies.
     *
     * @param reg2 The second register to which the instruction applies.
     */
    public void setReg2(byte reg2)
    {
        this.reg2 = reg2;
    }

    /**
     * Provides the symbol key of the argument that is assigned to the first register of this instruction.
     *
     * @return The symbol key, or <tt>null</tt> if none was associated with reg1.
     */
    public SymbolKey getSymbolKeyReg1()
    {
        return symbolKeyReg1;
    }

    /**
     * Provides the functor name of the argument that is assigned to the first register of this instruction.
     *
     * @return The functor name, or <tt>null</tt> if none was associated with reg1.
     */
    public Integer getFunctorNameReg1()
    {
        return functorNameReg1;
    }

    /**
     * Gets the functor argument, if any, to which the instruction applies.
     *
     * @return The functor argument to which the instruction applies, or <tt>null <tt>if there is none.
     */
    public FunctorName getFn()
    {
        return fn;
    }

    /**
     * Provides the target label argument, if any, to which the instruction applies.
     *
     * @return The target label argument to which the instruction applies, or <tt>null <tt>if there is none.
     */
    public WAMLabel getTarget1()
    {
        return target1;
    }

    /**
     * Sets the functor argument to which the instruction applies.
     *
     * @param fn The functor argument to which the instruction applies.
     */
    public void setFn(FunctorName fn)
    {
        this.fn = fn;
    }

    /**
     * Gets the label for the instruction, if any is set.
     *
     * @return The label for the instruction, or <tt>null</tt> if none is set.
     */
    public WAMLabel getLabel()
    {
        return label;
    }

    /**
     * Writes out the instruction plus arguments in the byte code format to the specified location within a code buffer.
     *
     * @param  codeBuffer The code buffer to write to.
     * @param  machine    The binary machine to write the code into.
     *
     * @throws LinkageException If required symbols to link to cannot be found in the binary machine.
     */
    public void emmitCode(ByteBuffer codeBuffer, WAMMachine machine) throws LinkageException
    {
        mnemonic.emmitCode(this, codeBuffer, machine);
    }

    /**
     * Calculates the length of this instruction in bytes.
     *
     * @return The length of this instruction in bytes.
     */
    public long sizeof()
    {
        return mnemonic.length();
    }

    /**
     * Prints the human readable form of the instruction for debugging purposes.
     *
     * @return The human readable form of the instruction for debugging purposes.
     */
    public String toString()
    {
        return mnemonic.toString(this);
    }

    /**
     * Pretty prints a label as a string.
     *
     * @param  label The label to print.
     *
     * @return A pretty version of the label.
     */
    private static String labelToString(WAMLabel label)
    {
        return ((label != null) ? label.toPrettyString() : "");
    }
}