Package nexj.core.scripting

Source Code of nexj.core.scripting.Machine

// Copyright 2010 NexJ Systems Inc. This software is licensed under the terms of the Eclipse Public License 1.0
package nexj.core.scripting;

import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import nexj.core.meta.Primitive;
import nexj.core.meta.TypeMismatchException;
import nexj.core.runtime.Context;
import nexj.core.scripting.Intrinsic.Continuation;
import nexj.core.scripting.PCodeFunction.DebugInfo;
import nexj.core.util.HashHolder;
import nexj.core.util.ObjUtil;
import nexj.core.util.StackTrace;
import nexj.core.util.SysUtil;
import nexj.core.util.UncheckedException;
import nexj.core.util.Undefined;

/**
* The scripting engine virtual machine.
*/
public class Machine
{
   // constants
   /**
    * VM p-codes.
    */
   public final static char PUSH_LOCAL = 0; // frameOffset, varOffset
   public final static char PUSH_LOCAL_0 = 1; // varOffset
   public final static char PUSH_LOCAL_1 = 2; // varOffset
   public final static char PUSH_LOCAL_2 = 3; // varOffset
   public final static char PUSH_LOCAL_3 = 4; // varOffset
   public final static char PUSH_LOCAL_4 = 5; // varOffset
   public final static char SET_LOCAL = 6; // frameOffset, varOffset
   public final static char SET_LOCAL_0 = 7; // varOffset
   public final static char SET_LOCAL_1 = 8; // varOffset
   public final static char SET_LOCAL_2 = 9; // varOffset
   public final static char SET_LOCAL_3 = 10; // varOffset
   public final static char SET_LOCAL_4 = 11; // varOffset
   public final static char PUSH_GLOBAL = 12; // constOffset
   public final static char SET_GLOBAL = 13; // constOffset
   public final static char DEF_GLOBAL = 14; // constOffset
   public final static char PUSH_CONST = 15; // constOffset
   public final static char PUSH_ZERO = 16;
   public final static char PUSH_ONE = 17;
   public final static char PUSH_NULL = 18;
   public final static char PUSH_TRUE = 19;
   public final static char PUSH_FALSE = 20;
   public final static char PUSH_CLOSURE = 21; // constOffset
   public final static char PUSH_PC = 22; // codeOffset
   public final static char RETURN = 23;
   public final static char JUMP_TRUE = 24; // codeOffset
   public final static char JUMP_FALSE = 25; // codeOffset
   public final static char JUMP = 26; // codeOffset
   public final static char CALL = 27; // argCount
   public final static char CHECK_FRAME = 28; // argCount
   public final static char SETUP_FRAME = 29; // argCount
   public final static char CHECK_VARARG_FRAME = 30; // argCount
   public final static char SETUP_VARARG_FRAME = 31; // argCount
   public final static char POP = 32;
   public final static char EQ_P = 33;
   public final static char EQ = 34;
   public final static char NE = 35;
   public final static char GT = 36;
   public final static char GE = 37;
   public final static char LT = 38;
   public final static char LE = 39;
   public final static char LIKE = 40;
   public final static char ADD = 41;
   public final static char SUB = 42;
   public final static char MUL = 43;
   public final static char DIV = 44;
   public final static char CAR = 45;
   public final static char CDR = 46;
   public final static char CONS = 47;
   public final static char LIST_1 = 48;
   public final static char LIST_2 = 49;
   public final static char LIST_3 = 50;
   public final static char LIST_4 = 51;
   public final static char DEBUG = 52;
   public final static char CALL_INTRINSIC = 53; // argCount

   // system functions

   public final static PCodeFunction EMPTY_FUNCTION = new PCodeFunction(
      new char[]{RETURN});

   protected final static PCodeFunction TRY_FUNCTION = new PCodeFunction(
      new char[]
      {  // (lambda (expr) ...)
         SETUP_FRAME, 1,
         PUSH_PC, 8,
         PUSH_LOCAL_0, 1,
         CALL, 0, // (expr)
         PUSH_PC, 12,
         CALL_INTRINSIC + 0, 0, // (sys:finalize)
         POP,
         RETURN,
      });

   protected final static PCodeFunction CATCH_FUNCTION = new PCodeFunction(
      new char[]
      {  // (lambda (handler e) ...)
         SETUP_FRAME, 2,
         PUSH_PC, 10,
         PUSH_LOCAL_0, 2,
         PUSH_LOCAL_0, 1,
         CALL, 1, // (handler e)
         PUSH_PC, 14,
         CALL_INTRINSIC + 0, 0, // (sys:finalize)
         POP,
         RETURN,
      });

   protected final static PCodeFunction RETHROW_FUNCTION = new PCodeFunction(
      new char[]
      {  // (lambda (e) (sys:finalize) (throw e))
         SETUP_FRAME, 1,
         PUSH_PC, 6,
         CALL_INTRINSIC + 0, 0, // (sys:finalize)
         POP,
         PUSH_LOCAL_0, 1,
         CALL_INTRINSIC + 1, 1, // (throw e)
         RETURN,
      });

   protected final static PCodeFunction CALL1_FUNCTION = new PCodeFunction(
      new char[]
      {
         // 1 skipped marker instruction that cannot be generated by the compiler
         RETURN,
         // (lambda (consumer value) (apply consumer value '()))
         PUSH_NULL,
         CALL_INTRINSIC + 2, 3, // (apply consumer value '())
      });

   protected final static PCodeFunction CALL_FUNCTION = new PCodeFunction(
      new char[]
      {
         CALL, 0,
      });

   protected final static PCodeFunction POP_CALL_FUNCTION = new PCodeFunction(
      new char[]
      {
         POP,
         CALL, 0,
      });

   protected final static PCodeFunction VALUES_FUNCTION = new PCodeFunction(
      new char[]
      {
         CALL_INTRINSIC + 3, 1, // (sys:return-values nArgCount)
         RETURN,
      });

   /**
    * Minimum stack length.
    */
   protected final static int MIN_STACK_LENGTH = 64;

   /**
    * Minimum exception handler count.
    */
   protected final static int MIN_HANDLER_COUNT = 16;

   /**
    * The debugger singleton instance
    */
   private final static Debugger s_debugger;

   /**
    * If there is or has been a breakpoint installed then this is true. This may
    * only be set to false if it is guaranteed that there is not a DEBUG
    * instruction in any bytecode.
    */
   public static boolean debuggerEnabled;

   static
   {
      Debugger debugger;

      try
      {
         String sDebuggerImpl = SysUtil.getConfigProperties().getProperty("debugger.class",
            "nexj.core.scripting.debugger.Debugger");

         debugger = (Debugger)Class.forName(sDebuggerImpl).newInstance();
      }
      catch (ClassNotFoundException e)
      {
         debugger = new Debugger()
         {
            public void setAnonymousParent(PCodeFunction pCodeFunction, PCodeFunction parent)
            {
            }

            public void hitException(MachineState machineState, Set steppingBreakpointSet)
            {
            }

            public BreakpointSite hitBreakpoint(MachineState state)
            {
               return null;
            }

            public BreakpointSite findInstallationSite(char[] code, int nOffset)
            {
               return null;
            }
         };
      }
      catch (Throwable t)
      {
         throw ObjUtil.rethrow(t);
      }

      s_debugger = debugger;
   }

   // attributes

   /**
    * The argument count passed during the last function call.
    */
   protected int m_nArgCount;

   /**
    * The stack top offset.
    */
   protected int m_nTop;

   /**
    * The stack return offset.
    */
   protected int m_nReturn;

   /**
    * The stack length.
    */
   protected int m_nStackLength = MIN_STACK_LENGTH;

   /**
    * The stack continuation barrier offset.
    */
   protected int m_nStackBarrier;

   /**
    * The VM stack. Grows upwards.
    */
   protected Object[] m_stack = new Object[m_nStackLength];

   /**
    * The exception stack top offset.
    */
   protected int m_nExceptionTop;

   /**
    * The exception stack return offset.
    */
   protected int m_nExceptionReturn;

   /**
    * The exception stack continuation barrier.
    */
   protected int m_nExceptionBarrier;

   /**
    * The exception handler stack. Grows upwards.
    */
   protected Object[] m_exceptionHandlerStack = new Object[MIN_HANDLER_COUNT];

   // associations

   /**
    * The current p-code function.
    */
   protected PCodeFunction m_fun;

   /**
    * The global environment.
    */
   protected GlobalEnvironment m_globalEnv;

   /**
    * The invocation context.
    */
   protected Context m_context;

   /**
    * The default character stream reader.
    */
   private Reader m_reader;

   /**
    * The default character stream writer.
    */
   private Writer m_writer;

   /**
    * When single stepping and we encounter an exception we need to invalidate
    * the breakpoints. This is used to store those breakpoints.
    */
   private Set m_steppingBreakpointSet;

   /**
    * StackTraceElement fields.
    */
   protected static Field s_declaringClassField;
   protected static Field s_methodNameField;
   protected static Field s_fileNameField;
   protected static Field s_lineNumberField;

   static
   {
      try
      {
         Class clazz = StackTraceElement.class;

         s_declaringClassField = clazz.getDeclaredField("declaringClass");
         s_declaringClassField.setAccessible(true);
         s_methodNameField = clazz.getDeclaredField("methodName");
         s_methodNameField.setAccessible(true);
         s_fileNameField = clazz.getDeclaredField("fileName");
         s_fileNameField.setAccessible(true);
         s_lineNumberField = clazz.getDeclaredField("lineNumber");
         s_lineNumberField.setAccessible(true);
      }
      catch (Exception e)
      {
         s_declaringClassField = null;
         s_methodNameField = null;
         s_fileNameField = null;
         s_lineNumberField = null;
      }
   }

   // constructors

   /**
    * Creates a virtual machine with given
    * global environment and invocation context.
    * @param globalEnv The global environment.
    * @param context The runtime context.
    */
   public Machine(GlobalEnvironment globalEnv, Context context)
   {
      m_globalEnv = globalEnv;
      m_context = context;
   }

   /**
    * Creates a virtual machine with the same global environment,
    * invocation context and i/o ports as a given machine.
    * @param machine The machine from which to take the environment.
    */
   public Machine(Machine machine)
   {
      m_globalEnv = machine.m_globalEnv;
      m_context = machine.m_context;
      m_reader = machine.m_reader;
      m_writer = machine.m_writer;
   }

   /**
    * Creates a virtual machine with a given global environment and
    * with invocation context and i/o ports from another machine.
    * @param globalEnv The global environment.
    * @param machine The machine from which to take the context.
    */
   public Machine(GlobalEnvironment globalEnv, Machine machine)
   {
      m_globalEnv = globalEnv;
      m_context = machine.m_context;
      m_reader = machine.m_reader;
      m_writer = machine.m_writer;
   }

   // operations

   /**
    * @return The global environment.
    */
   public GlobalEnvironment getGlobalEnvironment()
   {
      return m_globalEnv;
   }

   /**
    * @return The runtime context.
    */
   public Context getContext()
   {
      return m_context;
   }

   /**
    * Sets the default character stream reader.
    * @param reader The default character stream reader to set.
    */
   public void setReader(Reader reader)
   {
      m_reader = reader;
   }

   /**
    * @return The default character stream reader.
    */
   public Reader getReader()
   {
      return m_reader;
   }

   /**
    * Sets the default character stream writer.
    * @param writer The default character stream writer to set.
    */
   public void setWriter(Writer writer)
   {
      m_writer = writer;
   }

   /**
    * @return The default character stream writer.
    */
   public Writer getWriter()
   {
      return m_writer;
   }

   /**
    * Invokes a function using the VM.
    * @param fun The function to invoke.
    * @param args The function argument values.
    * @return The function return value.
    */
   public Object invoke(Function fun, Object[] args)
   {
      int nReturnSaved = m_nReturn;
      int nExceptionReturnSaved = m_nExceptionReturn;

      try
      {
         m_nReturn = m_nTop;
         m_nExceptionReturn = m_nExceptionTop;
         m_fun = null;

         if (args != null)
         {
            int nCount = args.length;

            for (int i = 0; i < nCount; ++i)
            {
               push(args[i]);
            }
         }

         if (fun.invoke(m_nTop - m_nReturn, this))
         {
            run();
         }

         return pop();
      }
      finally
      {
         m_nTop = m_nReturn;
         m_nReturn = nReturnSaved;
         m_nExceptionTop = m_nExceptionReturn;
         m_nExceptionReturn = nExceptionReturnSaved;
      }
   }

   /**
    * Invokes a function using the VM.
    * @param fun The function to invoke.
    * @param obj The first argument.
    * @param args The function argument values.
    * @return The function return value.
    */
   public Object invoke(Function fun, Object obj, Object[] args)
   {
      int nReturnSaved = m_nReturn;
      int nExceptionReturnSaved = m_nExceptionReturn;

      try
      {
         m_nReturn = m_nTop;
         m_nExceptionReturn = m_nExceptionTop;
         m_fun = null;

         push(obj);

         if (args != null)
         {
            int nCount = args.length;

            for (int i = 0; i < nCount; ++i)
            {
               push(args[i]);
            }
         }

         if (fun.invoke(m_nTop - m_nReturn, this))
         {
            run();
         }

         return pop();
      }
      finally
      {
         m_nTop = m_nReturn;
         m_nReturn = nReturnSaved;
         m_nExceptionTop = m_nExceptionReturn;
         m_nExceptionReturn = nExceptionReturnSaved;
      }
   }

   /**
    * Invokes a function using the VM.
    * @param fun The function to invoke.
    * @param arg1 The first argument.
    * @param arg2 The second argument.
    * @param args The remaining function argument values.
    * @return The function return value.
    */
   public Object invoke(Function fun, Object arg1, Object arg2, Object[] args)
   {
      int nReturnSaved = m_nReturn;
      int nExceptionReturnSaved = m_nExceptionReturn;

      try
      {
         m_nReturn = m_nTop;
         m_nExceptionReturn = m_nExceptionTop;
         m_fun = null;

         push(arg1);
         push(arg2);

         if (args != null)
         {
            int nCount = args.length;

            for (int i = 0; i < nCount; ++i)
            {
               push(args[i]);
            }
         }

         if (fun.invoke(m_nTop - m_nReturn, this))
         {
            run();
         }

         return pop();
      }
      finally
      {
         m_nTop = m_nReturn;
         m_nReturn = nReturnSaved;
         m_nExceptionTop = m_nExceptionReturn;
         m_nExceptionReturn = nExceptionReturnSaved;
      }
   }

   /**
    * Invokes a function using the VM.
    * @param fun The function to invoke.
    * @param argList The function argument value list.
    * @return The function return value.
    */
   public Object invoke(Function fun, List argList)
   {
      int nReturnSaved = m_nReturn;
      int nExceptionReturnSaved = m_nExceptionReturn;

      try
      {
         m_nReturn = m_nTop;
         m_nExceptionReturn = m_nExceptionTop;
         m_fun = null;

         if (argList != null)
         {
            int nCount = argList.size();

            for (int i = 0; i < nCount; ++i)
            {
               push(argList.get(i));
            }
         }

         if (fun.invoke(m_nTop - m_nReturn, this))
         {
            run();
         }

         return pop();
      }
      finally
      {
         m_nTop = m_nReturn;
         m_nReturn = nReturnSaved;
         m_nExceptionTop = m_nExceptionReturn;
         m_nExceptionReturn = nExceptionReturnSaved;
      }
   }

   /**
    * Invokes a function using the VM.
    * @param fun The function to invoke.
    * @param args The function argument values.
    * @return The function return value.
    */
   public Object invoke(Function fun, Pair args)
   {
      int nReturnSaved = m_nReturn;
      int nExceptionReturnSaved = m_nExceptionReturn;

      try
      {
         m_nReturn = m_nTop;
         m_nExceptionReturn = m_nExceptionTop;
         m_fun = null;

         if (args != null)
         {
            for (;;)
            {
               push(args.getHead());

               if (!(args.getTail() instanceof Pair))
               {
                  if (args.getTail() == null)
                  {
                     break;
                  }

                  throw new ScriptingException("err.scripting.callArgs");
               }

               args = args.getNext();
            }
         }

         if (fun.invoke(m_nTop - m_nReturn, this))
         {
            run();
         }

         return pop();
      }
      finally
      {
         m_nTop = m_nReturn;
         m_nReturn = nReturnSaved;
         m_nExceptionTop = m_nExceptionReturn;
         m_nExceptionReturn = nExceptionReturnSaved;
      }
   }

   /**
    * Invokes a function using the VM.
    * @param fun The function to invoke.
    * @param obj The first argument.
    * @param args The function argument values.
    * @return The function return value.
    */
   public Object invoke(Function fun, Object obj, Pair args)
   {
      int nReturnSaved = m_nReturn;
      int nExceptionReturnSaved = m_nExceptionReturn;

      try
      {
         m_nReturn = m_nTop;
         m_nExceptionReturn = m_nExceptionTop;
         m_fun = null;

         push(obj);

         if (args != null)
         {
            for (;;)
            {
               push(args.getHead());

               if (!(args.getTail() instanceof Pair))
               {
                  if (args.getTail() == null)
                  {
                     break;
                  }

                  throw new ScriptingException("err.scripting.callArgs");
               }

               args = args.getNext();
            }
         }

         if (fun.invoke(m_nTop - m_nReturn, this))
         {
            run();
         }

         return pop();
      }
      finally
      {
         m_nTop = m_nReturn;
         m_nReturn = nReturnSaved;
         m_nExceptionTop = m_nExceptionReturn;
         m_nExceptionReturn = nExceptionReturnSaved;
      }
   }

   /**
    * Applies a function.
    * @param fun The function to apply.
    * @param nArgCount The argument count.
    */
   public void apply(Function fun, int nArgCount)
   {
      if (!fun.invoke(nArgCount, this))
      {
         setFunction(EMPTY_FUNCTION, 0);
      }
   }

   /**
    * Invokes a Java method using the arguments on the stack.
    * @param obj The java object, on which to invoke the method.
    * @param nArgCount The number of arguments on the top of the stack.
    * @return The Function.invoke() return value.
    * @throws ScriptingException if a method invocation error occurs.
    */
   public boolean invokeJavaMethod(Object obj, int nArgCount) throws ScriptingException
   {
      if (nArgCount != 0)
      {
         Object sym = m_stack[m_nTop - nArgCount];

         if (sym instanceof Symbol)
         {
            if (obj == null)
            {
               returnValue(null, nArgCount);

               return false;
            }

            Class clazz = (obj instanceof Class) ? (Class)obj : obj.getClass();

            m_stack[m_nTop - nArgCount] = obj;

            return m_globalEnv.getJavaMethod(clazz, (Symbol)sym).invoke(nArgCount, this);
         }

         if (sym instanceof Number)
         {
            int i = ((Number)sym).intValue();

            if (obj != null)
            {
               try
               {
                  if (obj.getClass().isArray())
                  {
                     if (nArgCount == 1)
                     {
                        if (obj instanceof byte[])
                        {
                           returnValue(Primitive.createInteger(((byte[])obj)[i] & 0xFF), nArgCount);
                        }
                        else
                        {
                           returnValue(Array.get(obj, i), nArgCount);
                        }
                     }
                     else if (nArgCount == 2)
                     {
                        Object value = getArg(1, nArgCount);

                        if (obj instanceof byte[])
                        {
                           if (!(value instanceof Number))
                           {
                              throw new TypeMismatchException(Symbol.BYTEVECTOR_U8_SET);
                           }

                           byte nValue = ((Number)value).byteValue();

                           ((byte[])obj)[i] = nValue;
                           returnValue(Primitive.createInteger(nValue & 0xFF), nArgCount);
                        }
                        else
                        {
                           Array.set(obj, i, value);
                           returnValue(value, nArgCount);
                        }
                     }
                     else
                     {
                        throw new ScriptingException("err.scripting.maxArgCount",
                           new Object[]{"#<vector-index>", Primitive.createInteger(2),
                              Primitive.createInteger(nArgCount)});
                     }
                    
                     return false;
                  }
                  else if (obj instanceof List)
                  {
                     if (nArgCount == 1)
                     {
                        returnValue(((List)obj).get(i), nArgCount);
                     }
                     else if (nArgCount == 2)
                     {
                        Object value = getArg(1, nArgCount);

                        ((List)obj).set(i, value);
                        returnValue(value, nArgCount);
                     }
                     else
                     {
                        throw new ScriptingException("err.scripting.maxArgCount",
                           new Object[]{"#<vector-index>", Primitive.createInteger(2),
                              Primitive.createInteger(nArgCount)});
                     }

                     return false;
                  }
               }
               catch (IndexOutOfBoundsException e)
               {
                  throw new ScriptingException("err.scripting.badIndex", new Object[]{sym}, e);
               }
            }
         }
      }

      throw new ScriptingException("err.scripting.funCall");
   }

   /**
    * Determines if the stack is empty.
    * @param nArgCount The number of arguments currently pushed on the stack.
    * @return True if the stack is empty.
    */
   public boolean isStackEmpty(int nArgCount)
   {
      return m_nTop <= nArgCount;
   }

   /**
    * Determines if a given S-expression is supported by eval().
    * @param obj The S-expression.
    * @return True if the S-expression is supported.
    */
   public boolean isEvalSupported(Object obj)
   {
      if (obj instanceof Pair)
      {
         Pair pair = (Pair)obj;

         return isEvalSupported(pair.getHead(), pair.getNext());
      }

      if (obj instanceof Symbol)
      {
         return m_globalEnv.isDefined((Symbol)obj);
      }

      return true;
   }

   /**
    * Determines if a given function or special form is supported by eval().
    * @param obj The object to invoke.
    * @param args The function argument S-expressions.
    * @return True if the S-expression is supported.
    */
   protected boolean isEvalSupported(Object obj, Pair args)
   {
      if (obj instanceof Symbol)
      {
         if (obj == Symbol.IF)
         {
            if (!isEvalSupported(args.getHead()))
            {
               return false;
            }

            args = args.getNext();

            if (args == null)
            {
               return false;
            }

            if (Boolean.FALSE.equals(obj))
            {
               args = args.getNext();

               if (args == null)
               {
                  return true;
               }
            }

            return isEvalSupported(args.getHead());
         }

         if (obj == Symbol.QUOTE)
         {
            return true;
         }

         if (obj == Symbol.GLOBAL)
         {
            Symbol symbol = (Symbol)args.getHead();

            return m_globalEnv.isDefined(symbol);
         }

         obj = m_globalEnv.findVariable((Symbol)obj, Undefined.VALUE);

         if (obj == Undefined.VALUE)
         {
            return false;
         }

         if (obj instanceof Macro)
         {
            return isEvalSupported(invoke((Function)obj, args));
         }
      }
      else if (obj instanceof Pair)
      {
         Pair pair = (Pair)obj;

         if (!isEvalSupported(pair.getHead(), pair.getNext()))
         {
            return false;
         }
      }
      else
      {
         return false;
      }

      for (; args != null; args = args.getNext())
      {
         if (!isEvalSupported(args.getHead()))
         {
            return false;
         }
      }

      return true;
   }

   /**
    * Evaluates an S-expression in interpreted mode.
    * Not all the functionality is supported.
    * This is different from the eval function,
    * where the expression is precompiled.
    * @param obj The S-expression.
    * @return The expression value.
    */
   public Object eval(Object obj)
   {
      if (obj instanceof Pair)
      {
         Pair pair = (Pair)obj;

         return eval(pair.getHead(), pair.getNext());
      }

      if (obj instanceof Symbol)
      {
         return m_globalEnv.getVariable((Symbol)obj);
      }

      return obj;
   }

   /**
    * Evaluates a function or special form.
    * Not all the functionality is supported.
    * This is different from the eval function,
    * where the expression is precompiled.
    * @param obj The object to invoke.
    * @param args The function argument S-expressions.
    * @return The function return value.
    */
   public Object eval(Object obj, Pair args)
   {
      int nReturnSaved = m_nReturn;
      int nExceptionReturnSaved = m_nExceptionReturn;

      try
      {
         m_nReturn = m_nTop;
         m_nExceptionReturn = m_nExceptionTop;
         m_fun = null;

         if (obj instanceof Symbol)
         {
            if (obj == Symbol.IF)
            {
               obj = eval(args.getHead());
               args = args.getNext();

               if (Boolean.FALSE.equals(obj))
               {
                  args = args.getNext();

                  if (args == null)
                  {
                     return null;
                  }
               }

               return eval(args.getHead());
            }

            if (obj == Symbol.QUOTE)
            {
               return args.getHead();
            }

            if (obj == Symbol.GLOBAL)
            {
               return m_globalEnv.getVariable((Symbol)args.getHead());
            }

            obj = m_globalEnv.getVariable((Symbol)obj);

            if (obj instanceof Macro)
            {
               return eval(invoke((Function)obj, args));
            }
         }
         else if (obj instanceof Pair)
         {
            Pair pair = (Pair)obj;

            if (pair.getHead() == Symbol.GLOBAL)
            {
               return eval(pair.getNext().getHead(), args);
            }

            obj = eval(pair.getHead(), pair.getNext());
         }
         else
         {
            obj = null;
         }

         for (; args != null; args = args.getNext())
         {
            push(eval(args.getHead()));
         }

         if (invoke(obj, m_nTop - m_nReturn))
         {
            run();
         }

         return pop();
      }
      finally
      {
         m_nTop = m_nReturn;
         m_nReturn = nReturnSaved;
         m_nExceptionTop = m_nExceptionReturn;
         m_nExceptionReturn = nExceptionReturnSaved;
      }
   }

   /**
    * Invokes a function or a Java method using the arguments on the stack.
    * @param obj The function of Java object.
    * @param nArgCount The number of arguments on the top of the stack.
    * @return The Function.invoke() return value.
    * @throws ScriptingException if a method invocation error occurs.
    */
   protected boolean invoke(Object obj, int nArgCount) throws ScriptingException
   {
      if (obj instanceof Function)
      {
         return ((Function)obj).invoke(nArgCount, this);
      }

      return invokeJavaMethod(obj, nArgCount);
   }

   /**
    * Applies a function.
    * @param fun The function to apply.
    * @param nArgCount The argument count.
    */
   public void apply(Object fun, int nArgCount)
   {
      if (!invoke(fun, nArgCount))
      {
         setFunction(EMPTY_FUNCTION, 0);
      }
   }

   /**
    * Sets the current p-code function.
    * @param fun The p-code function to execute.
    * @param nArgCount The argument count.
    */
   public void setFunction(PCodeFunction fun, int nArgCount)
   {
      m_fun = fun;
      m_nArgCount = nArgCount;
   }

   /**
    * Replaces an argument at a given position on the stack.
    * @param nOrdinal The argument ordinal number, 0-based.
    * @param nArgCount The total argument count.
    * @param value The value to set.
    */
   public void setArg(int nOrdinal, int nArgCount, Object value)
   {
      m_stack[m_nTop - nArgCount + nOrdinal] = value;
   }

   /**
    * Gets an argument from the stack by position.
    * @param nOrdinal The argument ordinal number, 0-based.
    * @param nArgCount The total argument count.
    * @return The argument value.
    */
   public Object getArg(int nOrdinal, int nArgCount)
   {
      return m_stack[m_nTop - nArgCount + nOrdinal];
   }

   /**
    * Gets the last several arguments from the stack.
    * @param args The array where to copy the arguments.
    */
   public void getArgs(Object[] args)
   {
      System.arraycopy(m_stack, m_nTop - args.length, args, 0, args.length);
   }

   /**
    * Shifts the arguments on the top of the stack by removing the first arguments.
    * @param nOffset the number of arguments to remove.
    * @param nArgCount The total number of arguments.
    */
   public void shiftArgs(int nOffset, int nArgCount)
   {
      System.arraycopy(m_stack, m_nTop - nArgCount + nOffset, m_stack, m_nTop - nArgCount, nArgCount - nOffset);
      m_nTop -= nOffset;
   }

   /**
    * Cleans up a stack frame (not a local environment frame) and pushes the
    * return value on top of the stack.
    * @param value The value to return to the caller.
    * @param nArgCount The number of arguments to clean up from the stack frame.
    */
   public void returnValue(Object value, int nArgCount)
   {
      m_nTop -= nArgCount;
      push(value);
   }

   /**
    * Pushes a value on the stack.
    * @param value The value to push, it will be at the stack top.
    */
   public void push(Object value)
   {
      if (m_nTop == m_nStackLength)
      {
         m_nStackLength <<= 1;

         Object[] stack = new Object[m_nStackLength];

         System.arraycopy(m_stack, 0, stack, 0, m_stack.length);
         m_stack = stack;
      }

      m_stack[m_nTop++] = value;
   }

   /**
    * Pops a value from the stack.
    * @return The value that has been on the top of the stack.
    */
   public Object pop()
   {
      return m_stack[--m_nTop];
   }

   /**
    * Pops several values from the stack.
    * @param nCount The number of values to pop.
    */
   public void pop(int nCount)
   {
      m_nTop -= nCount;
   }

   /**
    * Pops all the values from the stack and
    * all the exceptions from the exeption stack.
    */
   public void popAll()
   {
      m_nTop = m_nReturn;
      m_nExceptionTop = m_nExceptionReturn;
   }

   /**
    * Creates and pushes a function pair, either as an exception handler
    * or a dynamic-wind function pair.
    * @param handler The exception handling code or the dynamic-wind pre-function.
    * @param finalizer The finalizer code or the dynamic-wind post-function.
    * @param marker Marker indicating the type of the function pair.
    */
   public void pushExceptionHandler(PCodeFunction handler, PCodeFunction finalizer, Object marker)
   {
      if (m_nExceptionTop == m_exceptionHandlerStack.length)
      {
         Object[] stack = new Object[m_exceptionHandlerStack.length << 1];

         System.arraycopy(m_exceptionHandlerStack, 0, stack, 0, m_exceptionHandlerStack.length);
         m_exceptionHandlerStack = stack;
      }

      m_exceptionHandlerStack[m_nExceptionTop++] = Primitive.createInteger(m_nTop);
      m_exceptionHandlerStack[m_nExceptionTop++] = handler;
      m_exceptionHandlerStack[m_nExceptionTop++] = finalizer;
      m_exceptionHandlerStack[m_nExceptionTop++] = marker;
   }

   /**
    * Pops the top exception handler and returns its finalizer.
    * @return The top exception handler finalizer.
    */
   public PCodeFunction popExceptionHandler()
   {
      m_nExceptionTop -= 4;

      return (PCodeFunction)m_exceptionHandlerStack[m_nExceptionTop + 2];
   }

   /**
    * Sets up a try-catch block.
    * @param fun The function to run.
    * @param handler The exception handler.
    * @param finalizer The finalizer.
    * @param bDynamicWind Flag for dynamic-wind function pair.
    */
   public void setTry(PCodeFunction fun, PCodeFunction handler, PCodeFunction finalizer, boolean bDynamicWind)
   {
      pushExceptionHandler((PCodeFunction)handler, (PCodeFunction)finalizer,
         (bDynamicWind) ? null : Boolean.FALSE);
      push(fun);
      setFunction(TRY_FUNCTION, 1);
   }

   /**
    * Pushes a multiple value consumer and a call function used by returnValues() as a marker.
    * @param consumer The multiple value consumer.
    */
   public void pushValuesConsumer(Object consumer)
   {
      push(consumer);
      push(Primitive.ONE_INTEGER); // Skip the first instruction using return offset of 1
      push(CALL1_FUNCTION);
      push(null);
   }

   /**
    * Returns the current top values from the stack.
    * @param nCount The number of values to return from the top of the stack.
    * @return True if the VM registers should be reset.
    * @see #pushValuesConsumer(Object)
    */
   public boolean returnValues(int nCount)
   {
      int nArgCount = nCount + 4;

      if (m_nTop - m_nReturn >= nArgCount)
      {
         Object obj = getArg(2, nArgCount);

         if (obj instanceof PCodeFunction &&
            Primitive.ONE_INTEGER.equals(getArg(1, nArgCount)) &&
            Arrays.equals(((PCodeFunction)obj).code, CALL1_FUNCTION.code))
         {
            Object fun = getArg(0, nArgCount);

            shiftArgs(4, nArgCount);

            return invoke(fun, nCount);
         }
      }

      if (nCount == 0)
      {
         throw new ScriptingException("err.scripting.minRetCount",
            new Object[]{Primitive.ONE_INTEGER, Primitive.ZERO_INTEGER});
      }

      pop(nCount - 1);

      return false;
   }

   /**
    * Cleans up a stack frame (not a local environment frame) and pushes the
    * return values on top of the stack.
    * @param values The values to return to the caller.
    * @param nArgCount The number of arguments to clean up from the stack frame.
    * @return True if the VM registers should be reset.
    * @see #pushValuesConsumer(Object)
    */
   public boolean returnValues(Object[] values, int nArgCount)
   {
      m_nTop -= nArgCount;

      int nCount = values.length;

      for (int i = 0; i < nCount; ++i)
      {
         push(values[i]);
      }

      return returnValues(nCount);
   }

   /**
    * Cleans up a stack frame (not a local environment frame) and pushes the
    * return values on top of the stack.
    * @param value1 The first value to return to the caller.
    * @param value2 The second value to return to the caller.
    * @param values The remaining values to return to the caller.
    * @param nArgCount The number of arguments to clean up from the stack frame.
    * @return True if the VM registers should be reset.
    * @see #pushValuesConsumer(Object)
    */
   public boolean returnValues(Object value1, Object value2, Object[] values, int nArgCount)
   {
      m_nTop -= nArgCount;
      push(value1);
      push(value2);

      int nCount = 0;

      if (values != null)
      {
         nCount = values.length;

         for (int i = 0; i < nCount; ++i)
         {
            push(values[i]);
         }
      }

      return returnValues(nCount + 2);
   }

   /**
    * Creates a continuation barrier.
    * @return A cookie for restoring the old barrier.
    */
   public Object createBarrier()
   {
      Pair cookie = new Pair(Primitive.createInteger(m_nStackBarrier),
         Primitive.createInteger(m_nExceptionBarrier));

      m_nStackBarrier = m_nTop;
      m_nExceptionBarrier = m_nExceptionTop;

      return cookie;
   }

   /**
    * Restores a continuation barrier.
    * @param cookie The cookie of the barrier to be restored.
    */
   public void restoreBarrier(Object cookie)
   {
      Pair pair = (Pair)cookie;

      m_nStackBarrier = ((Number)pair.getHead()).intValue();
      m_nExceptionBarrier = ((Number)pair.getTail()).intValue();
   }

   /**
    * @return A copy of the current stack.
    */
   public Object[] cloneStack()
   {
      if (m_nReturn > m_nStackBarrier || m_nTop < m_nStackBarrier)
      {
         throw new ScriptingException("err.scripting.callcc");
      }

      int nStackCount = m_nTop - m_nStackBarrier;
      int nExceptionCount = m_nExceptionTop - m_nExceptionBarrier;
      Object[] stack = new Object[nStackCount + nExceptionCount + 1];

      System.arraycopy(m_stack, m_nStackBarrier, stack, 0, nStackCount);
      System.arraycopy(m_exceptionHandlerStack, m_nExceptionBarrier, stack, nStackCount, nExceptionCount);
      stack[nStackCount + nExceptionCount] = Primitive.createInteger(nExceptionCount);

      return stack;
   }

   /**
    * Sets the stack contents and the current p-code function, when switching
    * to a continuation.
    * @param stack The stack contents to set.
    * @param nArgCount The number of arguments at the top of the current stack
    * to be shifted to the top of the resulting stack.
    * @return True if the virtual machine registers should be reset.
    */
   public boolean setContinuation(Object[] stack, int nArgCount)
   {
      if (m_nReturn > m_nStackBarrier || m_nTop < m_nStackBarrier)
      {
         throw new ScriptingException("err.scripting.callcc");
      }

      int nLength = stack.length;
      int nExceptionCount = ((Integer)stack[nLength - 1]).intValue();
      int nStackCount = nLength - nExceptionCount - 1;
      int nEstimatedFuncCount = (m_nExceptionTop - m_nExceptionBarrier + nExceptionCount) >> 2;
      int nActualFuncCount = 0;
      int nIndex = m_nStackBarrier + nStackCount;
      int nMinStackSize = nIndex + nArgCount + 2 + nEstimatedFuncCount * 4;

      if (nMinStackSize > m_nStackLength)
      {
         m_nStackLength = nMinStackSize + 32;
         m_stack = new Object[m_nStackLength];
      }

      nMinStackSize = m_nExceptionBarrier + nExceptionCount;

      if (nMinStackSize > m_exceptionHandlerStack.length)
      {
         m_exceptionHandlerStack = new Object[nMinStackSize + 16];
      }

      System.arraycopy(m_stack, m_nTop - nArgCount, m_stack, nIndex, nArgCount);
      System.arraycopy(stack, 0, m_stack, m_nStackBarrier, nStackCount);

      nIndex += nArgCount;
      m_nTop = nIndex + 2;

      // Push the pre-functions in reversed order.
      for (int i = nLength - 2; i > nStackCount; i -= 4)
      {
         if (stack[i] == null) // dynamic-wind case
         {
            m_stack[m_nTop++] = Primitive.ZERO_INTEGER;
            m_stack[m_nTop++] = POP_CALL_FUNCTION;
            m_stack[m_nTop++] = null;
            m_stack[m_nTop++] = stack[i - 2];
            nActualFuncCount++;
         }
      }

      // Push the post-functions in order.
      for (int i = m_nExceptionBarrier + 3; i < m_nExceptionTop; i += 4)
      {
         if (m_exceptionHandlerStack[i] == null) // dynamic-wind case
         {
            m_stack[m_nTop++] = Primitive.ZERO_INTEGER;
            m_stack[m_nTop++] = POP_CALL_FUNCTION;
            m_stack[m_nTop++] = null;
            m_stack[m_nTop++] = m_exceptionHandlerStack[i - 1];
            nActualFuncCount++;
         }
      }

      System.arraycopy(stack, nStackCount, m_exceptionHandlerStack, m_nExceptionBarrier, nExceptionCount);
      m_nExceptionTop = nMinStackSize;

      PCodeFunction func;

      if (nActualFuncCount == 0)
      {
         m_nTop -= 2;

         if (nArgCount != 1)
         {
            return returnValues(nArgCount);
         }

         func = Machine.EMPTY_FUNCTION;
      }
      else
      {
         m_stack[nIndex++] = Primitive.createInteger(nArgCount);
         m_stack[nIndex++] = VALUES_FUNCTION;
         func = CALL_FUNCTION;
      }

      setFunction(func, nArgCount);

      return true;
   }

   /**
    * Cleans up redundant resources (e.g. by nullifying extra stack space).
    * Should be called when no VM activity is expected in the following ~100 ms.
    */
   public void cleanup()
   {
      if ((m_nStackLength >> 2) > m_nTop && m_nStackLength > (MIN_STACK_LENGTH << 1))
      {
         m_nStackLength = Math.max(m_nTop << 2, MIN_STACK_LENGTH);

         Object[] stack = new Object[m_nStackLength];

         System.arraycopy(m_stack, 0, stack, 0, m_nTop);
         m_stack = stack;
      }
      else
      {
         Arrays.fill(m_stack, m_nTop, m_nStackLength, null);
      }

      int nCount = m_exceptionHandlerStack.length;

      if ((nCount >> 2) > m_nExceptionTop && nCount > (MIN_HANDLER_COUNT << 1))
      {
         Object[] stack = new Object[Math.max(m_nExceptionTop << 2, MIN_HANDLER_COUNT)];

         System.arraycopy(m_exceptionHandlerStack, 0, stack, 0, m_nExceptionTop);
         m_exceptionHandlerStack = stack;
      }
      else
      {
         Arrays.fill(m_exceptionHandlerStack, m_nExceptionTop, nCount, null);
      }

      m_fun = null;
   }

   /**
    * Runs the VM on the current code.
    */
   protected void run()
   {
      int nPC = 0;
      PCodeFunction fun = m_fun;
      char[] code = fun.code;
      char[] altCode = null;
      char[] origCode = null;
      Object[] constants = fun.constants;
      Object[] frame = fun.frame;
      Object[] vars;
      Object value;
      Pair pair;
      int i, n;

      m_steppingBreakpointSet = null;

      for (;;)
      {
         try
         {
            for (;;)
            {
               switch (code[nPC++])
               {
                  case PUSH_LOCAL: // frameOffset, varOffset
                     vars = frame;

                     for (n = code[nPC++]; n != 0; --n)
                     {
                        vars = (Object[])vars[0];
                     }

                     push(vars[code[nPC++]]);

                     break;

                  case PUSH_LOCAL_0: // varOffset
                     push(frame[code[nPC++]]);
                     break;

                  case PUSH_LOCAL_1: // varOffset
                     push(((Object[])frame[0])[code[nPC++]]);
                     break;

                  case PUSH_LOCAL_2: // varOffset
                     push(((Object[])((Object[])frame[0])[0])[code[nPC++]]);
                     break;

                  case PUSH_LOCAL_3: // varOffset
                     push(((Object[])((Object[])((Object[])frame[0])[0])[0])[code[nPC++]]);
                     break;

                  case PUSH_LOCAL_4: // varOffset
                     push(((Object[])((Object[])((Object[])((Object[])frame[0])[0])[0])[0])[code[nPC++]]);
                     break;

                  case SET_LOCAL: // frameOffset, varOffset
                     vars = frame;

                     for (n = code[nPC++]; n != 0; --n)
                     {
                        vars = (Object[])vars[0];
                     }

                     vars[code[nPC++]] = m_stack[m_nTop - 1];

                     break;

                  case SET_LOCAL_0: // varOffset
                     frame[code[nPC++]] = m_stack[m_nTop - 1];
                     break;

                  case SET_LOCAL_1: // varOffset
                     ((Object[])frame[0])[code[nPC++]] = m_stack[m_nTop - 1];
                     break;

                  case SET_LOCAL_2: // varOffset
                     ((Object[])((Object[])frame[0])[0])[code[nPC++]] = m_stack[m_nTop - 1];
                     break;

                  case SET_LOCAL_3: // varOffset
                     ((Object[])((Object[])((Object[])frame[0])[0])[0])[code[nPC++]] = m_stack[m_nTop - 1];
                     break;

                  case SET_LOCAL_4: // varOffset
                     ((Object[])((Object[])((Object[])((Object[])frame[0])[0])[0])[0])[code[nPC++]] = m_stack[m_nTop - 1];
                     break;

                  case PUSH_GLOBAL: // constOffset
                     push(m_globalEnv.getVariable((Symbol)constants[code[nPC++]]));
                     break;

                  case SET_GLOBAL: // constOffset
                     m_globalEnv.setVariable((Symbol)constants[code[nPC++]], m_stack[m_nTop - 1]);
                     break;

                  case DEF_GLOBAL: // constOffset
                     m_globalEnv.defineVariable((Symbol)constants[code[nPC++]], m_stack[m_nTop - 1]);
                     break;

                  case PUSH_CONST: // constOffset
                     push(constants[code[nPC++]]);
                     break;

                  case PUSH_ZERO:
                     push(Primitive.ZERO_INTEGER);
                     break;

                  case PUSH_ONE:
                     push(Primitive.ONE_INTEGER);
                     break;

                  case PUSH_NULL:
                     push(null);
                     break;

                  case PUSH_TRUE:
                     push(Boolean.TRUE);
                     break;

                  case PUSH_FALSE:
                     push(Boolean.FALSE);
                     break;

                  case PUSH_CLOSURE: // constOffset
                     push(((PCodeFunction)constants[code[nPC++]]).bind(frame));
                     break;

                  case PUSH_PC: // codeOffset
                     if (m_nTop + 3 >= m_nStackLength)
                     {
                        m_nStackLength = (m_nStackLength + 3) << 1;
                        vars = new Object[m_nStackLength];
                        System.arraycopy(m_stack, 0, vars, 0, m_stack.length);
                        m_stack = vars;
                     }

                     m_stack[m_nTop++] = Primitive.createInteger(code[nPC++]);
                     m_stack[m_nTop++] = fun;
                     m_stack[m_nTop++] = frame;

                     break;

                  case RETURN:
                     if ((n = m_nTop - 3) <= m_nReturn)
                     {
                        return;
                     }

                     m_nTop = n;
                     frame = (Object[])m_stack[n + 1];
                     fun = (PCodeFunction)m_stack[n];
                     constants = fun.constants;
                     code = fun.code;
                     nPC = ((Integer)m_stack[n - 1]).intValue();
                     m_stack[n - 1] = m_stack[n + 2];

                     break;

                  case JUMP_TRUE: // codeOffset
                     n = code[nPC++];
                     value = m_stack[--m_nTop];

                     if (!(value instanceof Boolean) || ((Boolean)value).booleanValue())
                     {
                        nPC = n;
                     }

                     break;

                  case JUMP_FALSE: // codeOffset
                     n = code[nPC++];
                     value = m_stack[--m_nTop];

                     if (value instanceof Boolean && !((Boolean)value).booleanValue())
                     {
                        nPC = n;
                     }

                     break;

                  case JUMP: // codeOffset
                     nPC = code[nPC++];
                     break;

                  case CALL: // argCount
                     m_nArgCount = code[nPC++];
                     value = m_stack[--m_nTop];

                     if (value instanceof Function)
                     {
                        if (((Function)value).invoke(m_nArgCount, this))
                        {
                           nPC = 0;
                           fun = m_fun;
                           code = fun.code;
                           constants = fun.constants;
                           frame = fun.frame;

                           break;
                        }
                     }
                     else
                     {
                        invokeJavaMethod(value, m_nArgCount);
                     }

                     if ((n = m_nTop - 3) <= m_nReturn)
                     {
                        return;
                     }

                     m_nTop = n;
                     frame = (Object[])m_stack[n + 1];
                     fun = (PCodeFunction)m_stack[n];
                     constants = fun.constants;
                     code = fun.code;
                     nPC = ((Integer)m_stack[n - 1]).intValue();
                     m_stack[n - 1] = m_stack[n + 2];

                     break;

                  case CHECK_FRAME: // argCount
                     n = code[nPC++];

                     if (m_nArgCount != n)
                     {
                        throw new ScriptingException(
                           (m_nArgCount < n) ? "err.scripting.minArgCount" : "err.scripting.maxArgCount",
                              new Object[]{fun.getName(),
                                 Primitive.createInteger(n),
                                 Primitive.createInteger(m_nArgCount)});
                     }

                     m_nTop -= n;

                     break;

                  case SETUP_FRAME: // argCount
                     n = code[nPC++];

                     if (m_nArgCount != n)
                     {
                        throw new ScriptingException(
                           (m_nArgCount < n) ? "err.scripting.minArgCount" : "err.scripting.maxArgCount",
                              new Object[]{fun.getName(),
                                 Primitive.createInteger(n),
                                 Primitive.createInteger(m_nArgCount)});
                     }

                     vars = new Object[n + 1];

                     while (n != 0)
                     {
                        vars[n--] = m_stack[--m_nTop];
                     }

                     vars[0] = frame;
                     frame = vars;

                     break;

                  case CHECK_VARARG_FRAME: // argCount
                     n = code[nPC++];

                     if (m_nArgCount < n)
                     {
                        throw new ScriptingException("err.scripting.minArgCount",
                           new Object[]{fun.getName(),
                              Primitive.createInteger(n),
                              Primitive.createInteger(m_nArgCount)});
                     }

                     m_nTop -= m_nArgCount;

                     break;

                  case SETUP_VARARG_FRAME: // argCount
                     n = code[nPC++];

                     if (m_nArgCount < n)
                     {
                        throw new ScriptingException("err.scripting.minArgCount",
                           new Object[]{fun.getName(),
                              Primitive.createInteger(n),
                              Primitive.createInteger(m_nArgCount)});
                     }

                     vars = new Object[n + 2];
                     pair = null;

                     for (i = n; i != m_nArgCount; ++i)
                     {
                        pair = new Pair(m_stack[--m_nTop], pair);
                     }

                     vars[n + 1] = pair;

                     while (n != 0)
                     {
                        vars[n--] = m_stack[--m_nTop];
                     }

                     vars[0] = frame;
                     frame = vars;

                     break;

                  case POP:
                     --m_nTop;
                     break;

                  case EQ_P:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Boolean.valueOf(Intrinsic.eq(m_stack[m_nTop - 1], value));
                     break;

                  case EQ:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.eq(m_stack[m_nTop - 1], value);
                     break;

                  case NE:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.ne(m_stack[m_nTop - 1], value);
                     break;

                  case GT:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.gt(m_stack[m_nTop - 1], value);
                     break;

                  case GE:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.ge(m_stack[m_nTop - 1], value);
                     break;

                  case LT:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.lt(m_stack[m_nTop - 1], value);
                     break;

                  case LE:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.le(m_stack[m_nTop - 1], value);
                     break;

                  case LIKE:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.like(m_stack[m_nTop - 1], value);
                     break;

                  case ADD:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.add(m_stack[m_nTop - 1], value);
                     break;

                  case SUB:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.subtract(m_stack[m_nTop - 1], value);
                     break;

                  case MUL:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.multiply(m_stack[m_nTop - 1], value);
                     break;

                  case DIV:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = Primitive.divide(m_stack[m_nTop - 1], value);
                     break;

                  case CAR:
                     value = m_stack[m_nTop - 1];

                     if (!(value instanceof Pair))
                     {
                        throw new TypeMismatchException(Symbol.CAR);
                     }

                     m_stack[m_nTop - 1] = ((Pair)value).getHead();

                     break;

                  case CDR:
                     value = m_stack[m_nTop - 1];

                     if (!(value instanceof Pair))
                     {
                        throw new TypeMismatchException(Symbol.CDR);
                     }

                     m_stack[m_nTop - 1] = ((Pair)value).getTail();

                     break;

                  case CONS:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = new Pair(m_stack[m_nTop - 1], value);
                     break;

                  case LIST_1:
                     m_stack[m_nTop - 1] = new Pair(m_stack[m_nTop - 1]);
                     break;

                  case LIST_2:
                     value = m_stack[--m_nTop];
                     m_stack[m_nTop - 1] = new Pair(m_stack[m_nTop - 1], new Pair(value));
                     break;

                  case LIST_3:
                     m_nTop -= 2;
                     m_stack[m_nTop - 1] = new Pair(m_stack[m_nTop - 1], new Pair(m_stack[m_nTop], new Pair(m_stack[m_nTop + 1])));
                     break;

                  case LIST_4:
                     m_nTop -= 3;
                     m_stack[m_nTop - 1] = new Pair(m_stack[m_nTop - 1], new Pair(m_stack[m_nTop],
                        new Pair(m_stack[m_nTop + 1], new Pair(m_stack[m_nTop + 2]))));
                     break;

                  case DEBUG:
                     // Re execute at the same offset
                     --nPC;

                     if (altCode == code)
                     {
                        // Switch back from the alternative code
                        code = origCode;
                        altCode = origCode = null;
                     }
                     else
                     {
                        assert code == fun.code;

                        MachineState state = new MachineState(nPC, fun, frame, null);
                        BreakpointSite site = s_debugger.hitBreakpoint(state);

                        if (site == null)
                        {
                           // A DEBUG instruction could be uninstalled between
                           // the time the DEBUG is hit and the time we lookup
                           // the installation side. Ideally we would always
                           // re-execute at the current PC if lookup fails,
                           // however on the blackberry we cannot guarantee that
                           // breakpoints are not persisted and must throw an
                           // exception.
                           if (code[nPC] == DEBUG)
                           {
                              throw new InvalidDebugBytecodeException();
                           }

                           // re-execute the instruction
                           break;
                        }

                        if (site.isInstalled())
                        {
                           origCode = code;
                           altCode = code = state.createAlternativeCode(site.getInstruction());
                        }
                     }

                     break;

                  default: // argCount
                     n = code[nPC - 1] - CALL_INTRINSIC;
                     m_nArgCount = code[nPC++];

                     if (Intrinsic.getFunction(n).invoke(m_nArgCount, this))
                     {
                        nPC = 0;
                        fun = m_fun;
                        code = fun.code;
                        constants = fun.constants;
                        frame = fun.frame;
                     }

                     break;
               }
            }
         }
         catch (Throwable e)
         {
            if (debuggerEnabled)
            {
               // nPC will not necessarily be at an instruction boundary
               s_debugger.hitException(new MachineState(nPC - 1, fun, frame, e), m_steppingBreakpointSet);

               // m_steppingBreakpointSet is not necessarily empty if the user stepped from the exception.
            }

            if (m_nExceptionTop != m_nExceptionReturn)
            {
               updateStackTrace(e, fun, nPC);
               m_nTop = ((Integer)m_exceptionHandlerStack[m_nExceptionTop - 4]).intValue();

               if (!Boolean.FALSE.equals(m_exceptionHandlerStack[m_nExceptionTop - 1]))
               {
                  push(e);
                  m_nArgCount = 1;
                  fun = RETHROW_FUNCTION;
               }
               else
               {
                  m_exceptionHandlerStack[m_nExceptionTop - 1] = Boolean.TRUE;
                  push(m_exceptionHandlerStack[m_nExceptionTop - 3]);
                  push(e);
                  m_nArgCount = 2;
                  fun = CATCH_FUNCTION;
               }

               nPC = 0;
               code = fun.code;
               constants = fun.constants;
               frame = fun.frame;

               continue;
            }

            updateStackTrace(e, fun, nPC);
            ObjUtil.rethrow(e);
         }
      }
   }

   /**
    * Updates the stack trace in a given exception with p-code trace information.
    * @param t The exception to update.
    * @param fun The p-code function.
    * @param nOffset The program counter offset.
    */
   protected void updateStackTrace(Throwable t, PCodeFunction fun, int nOffset)
   {
      if (s_declaringClassField == null)
      {
         return;
      }

      StackTraceElement[] traceArray = t.getStackTrace();
      int nTraceIndex;

      for (nTraceIndex = 0; nTraceIndex < traceArray.length; ++nTraceIndex)
      {
         StackTraceElement element = traceArray[nTraceIndex];
         String sName = element.getClassName();

         if (sName == null || sName.equals("nexj.core.scripting.Machine"))
         {
            break;
         }

         sName = element.getFileName();

         if (sName != null && !sName.endsWith(".java"))
         {
            return;
         }
      }

      List traceList = new ArrayList(16);

      for (int i = 0; i < nTraceIndex; ++i)
      {
         traceList.add(traceArray[i]);
      }

      if (nTraceIndex == traceArray.length)
      {
         traceArray = new Throwable().getStackTrace();

         if (traceArray.length == 0)
         {
            return;
         }

         nTraceIndex = 0;
      }

      if (updateTrace(traceArray[nTraceIndex], fun, nOffset))
      {
         traceList.add(traceArray[nTraceIndex++]);
      }

      addStackTrace(traceList, traceArray, nTraceIndex);

      if (traceList.isEmpty())
      {
         return;
      }

      traceArray = new StackTraceElement[traceList.size()];
      traceList.toArray(traceArray);
      t.setStackTrace(traceArray);
   }

   /**
    * Updates the stack trace in a given exception with p-code trace information.
    * @param t The exception to update.
    */
   public void updateStackTrace(Throwable t)
   {
      if (s_declaringClassField == null)
      {
         return;
      }

      StackTraceElement[] traceArray = t.getStackTrace();
      List traceList = new ArrayList(16);

      addStackTrace(traceList, traceArray, 0);

      if (traceList.isEmpty())
      {
         return;
      }

      traceArray = new StackTraceElement[traceList.size()];
      traceList.toArray(traceArray);
      t.setStackTrace(traceArray);
   }

   /**
    * Visits all frames in m_stack in the order of m_top to 0 (closest to
    * execution to furthest).
    *
    * Note that frames are guessed by checking the type of three adjacent
    * elements in m_stack. In particular the types are: Integer, PCodeFunction,
    * Object[] or null. That is in some cases may incorrectly identify stack
    * frames.
    *
    * Also removes extra frames that are pushed onto the stack prior to CALLs:
    * If there are frames with duplicate stacks then all but the first are
    * ignored.
    *
    * @param visitor The visitor. visitor.visit() will be called with all
    *           identified stack frames.
    */
   public void visitStackTrace(StackFrameVisitor visitor)
   {
      visitStackTrace(visitor, null);
   }

   /**
    * Visits the stack trace like {@link #visitStackTrace(nexj.core.scripting.Machine.StackFrameVisitor)}
    * but allows the caller to provide the most recent frame to aid in filtering.
    *
    * @param visitor The visitor. visitor.visit() will be called with all
    *           identified stack frames.
    * @param lastFrame The current stack frame. Frames will not be visited that have this frame[].
    */
   public void visitStackTrace(StackFrameVisitor visitor, Object[] lastFrame)
   {
      visitStackTrace(visitor, m_stack, 0, m_nTop, lastFrame);
   }

   /**
    * Visits the stack trace allowing the caller to provide the stack and most
    * recent frame to aid in filtering.
    *
    * @param visitor The visitor. visitor.visit() will be called with all
    *           identified stack frames.
    * @param stack The stack to visit
    * @param nBottom The bottom of the stack
    * @param nTop The top of the stack
    * @param lastFrame The current stack frame. Frames will not be visited that
    *           have this frame[].
    */
   protected static void visitStackTrace(StackFrameVisitor visitor, Object[] stack, int nBottom, int nTop, Object[] lastFrame)
   {
      for (int i = nTop - 3; i >= nBottom; --i)
      {
         if (stack[i + 1] instanceof PCodeFunction && stack[i] instanceof Integer &&
            (stack[i + 2] == null || stack[i + 2] instanceof Object[]))
         {
            PCodeFunction fun = (PCodeFunction)stack[i + 1];
            int nOffset = ((Integer)stack[i]).intValue();
            Object[] frame = (Object[])stack[i + 2];

            if (nOffset >= 0 && nOffset < fun.code.length &&
               (frame != lastFrame || frame == null))
            {
               if (!visitor.visit(fun, nOffset, frame))
               {
                  break;
               }

               lastFrame = frame;
            }

            i -= 2;
         }
      }
   }

   /**
    * Adds stack trace elements to a trace list.
    *
    * @param traceList The trace list, where to add the elements.
    * @param traceArray The trace array to reuse, can be null.
    * @param nTraceIndex The index of the first reusable element in traceArray.
    */
   protected void addStackTrace(final List traceList, final StackTraceElement[] traceArrayIn, final int nTraceIndexIn)
   {
      visitStackTrace(new StackFrameVisitor()
      {
         int nTraceIndex = nTraceIndexIn;

         StackTraceElement[] traceArray = traceArrayIn;

         public boolean visit(PCodeFunction fun, int nOffset, Object[] frame)
         {
            if (nOffset >= 0 && nOffset < fun.code.length)
            {
               if (traceArray == null || nTraceIndex == traceArray.length)
               {
                  traceArray = new Throwable().getStackTrace();
                  nTraceIndex = 0;
               }

               StackTraceElement element = traceArray[nTraceIndex];

               if (updateTrace(element, fun, nOffset))
               {
                  if (traceList.isEmpty() || !traceList.get(traceList.size() - 1).equals(element))
                  {
                     traceList.add(element);
                     ++nTraceIndex;
                  }
               }
            }

            return true;
         }
      });
   }

   /**
    * Updates a stack trace element with p-code trace information.
    * @param trace The element to update.
    * @param fun The p-code function.
    * @param nOffset The program counter offset.
    * @return True if the update succeeded.
    */
   protected static boolean updateTrace(StackTraceElement trace, PCodeFunction fun, int nOffset)
   {
      PCodeFunction.DebugInfo info = fun.getDebugInfo();

      if (info == null)
      {
         return false;
      }

      int nPos = info.getPos(nOffset);
      String sURL = info.getURL(nPos);

      if (sURL == null)
      {
         sURL = "<input>";
      }

      try
      {
         if (sURL.startsWith("class:"))
         {
            int k = sURL.indexOf('.', 6);

            if (k < 0)
            {
               // Validation URLs don't have method part
               k = sURL.indexOf('$', 6);
            }

            String sClassName = sURL.substring(6, k);
            String sMethod = sURL.substring(k + 1);

            s_declaringClassField.set(trace, sClassName);
            s_methodNameField.set(trace, sMethod);
         }
         else
         {
            int nFragIndex = sURL.lastIndexOf('#');

            if (nFragIndex >= 0)
            {
               int i = sURL.indexOf(' ', nFragIndex);

               if (i < 0)
               {
                  i = sURL.length();
               }

               s_declaringClassField.set(trace, sURL.substring(nFragIndex + 1, i));
               s_methodNameField.set(trace, (i == sURL.length()) ? "<unknown>" : sURL.substring(i + 1, sURL.length()));
               sURL = sURL.substring(0, nFragIndex);
            }
            else
            {
               int i = sURL.indexOf(':');
               int k = sURL.lastIndexOf('.');

               if (i < 0 || k <= i)
               {
                  k = sURL.length();
               }

               String sName = info.getName();

               if (sName == null)
               {
                  if (k < sURL.length())
                  {
                     sName = sURL.substring(k + 1);
                  }
                  else
                  {
                     sName = "lambda";
                  }
               }

               s_methodNameField.set(trace, sName);

               if (i > 0)
               {
                  if (sURL.startsWith("library:"))
                  {
                     String sClassName = sURL.substring(i + 1);

                     s_declaringClassField.set(trace, sClassName);
                  }
                  else
                  {
                     s_declaringClassField.set(trace, sURL.substring(i + 1, k));
                  }
               }
               else
               {
                  s_declaringClassField.set(trace, "<environment>");
               }
            }
         }

         s_fileNameField.set(trace, sURL);
         s_lineNumberField.set(trace, Primitive.createInteger(info.getLine(nPos) + 1));
      }
      catch (Exception e)
      {
         return false;
      }

      return true;
   }

   /**
    * Logs a stack trace.
    * @param nLevel The log level - one of the Logger.* constants.
    */
   public void logTrace(int nLevel)
   {
      if (GlobalEnvironment.LOGGER.isLevelEnabled(nLevel))
      {
         StackTrace t = new StackTrace();

         updateStackTrace(t);
         GlobalEnvironment.LOGGER.log(nLevel, "Stack trace", t);
      }
   }

   /**
    * Adds a stepping breakpoint. If an exception is encountered while this
    * Machine is executing then this breakpoint's owner will be notified.
    *
    * @param spec The breakpoint to be registered
    */
   public void addSteppingBreakpoint(Object spec)
   {
      if (m_steppingBreakpointSet == null)
      {
         m_steppingBreakpointSet = new HashHolder(2);
      }

      m_steppingBreakpointSet.add(spec);
   }

   /**
    * Removes a breakpoint from the list of stepping breakpoints. Must be called from the thread that "owns"
    * this Machine.
    *
    * @param spec The breakpoint to be unregistered
    */
   public void removeSteppingBreakpoint(Object spec)
   {
      if (m_steppingBreakpointSet != null)
      {
         m_steppingBreakpointSet.remove(spec);
      }
   }

   /**
    * Determines the total length of a p-code instruction with operands.
    *
    * Note that a DEBUG instruction has undefined length and the caller must
    * ensure that this is not called with a DEBUG instruction.
    *
    * @param code The instruction p-code, one of the Machine.* p-code constants.
    * @return The p-code instruction length (in chars).
    */
   public static int getPCodeLength(char code)
   {
      switch (code)
      {
         case PUSH_LOCAL:
            return 3;

         case PUSH_LOCAL_0:
         case PUSH_LOCAL_1:
         case PUSH_LOCAL_2:
         case PUSH_LOCAL_3:
         case PUSH_LOCAL_4:
            return 2;

         case SET_LOCAL:
            return 3;

         case SET_LOCAL_0:
         case SET_LOCAL_1:
         case SET_LOCAL_2:
         case SET_LOCAL_3:
         case SET_LOCAL_4:
         case PUSH_GLOBAL:
         case SET_GLOBAL:
         case DEF_GLOBAL:
         case PUSH_CONST:
            return 2;

         case PUSH_ZERO:
         case PUSH_ONE:
         case PUSH_NULL:
         case PUSH_TRUE:
         case PUSH_FALSE:
            return 1;

         case PUSH_CLOSURE:
         case PUSH_PC:
            return 2;

         case RETURN:
            return 1;

         case JUMP_TRUE:
         case JUMP_FALSE:
         case JUMP:
         case CALL:
         case CHECK_FRAME:
         case SETUP_FRAME:
         case CHECK_VARARG_FRAME:
         case SETUP_VARARG_FRAME:
            return 2;

         case POP:
         case EQ_P:
         case EQ:
         case NE:
         case GT:
         case GE:
         case LT:
         case LE:
         case LIKE:
         case ADD:
         case SUB:
         case MUL:
         case DIV:
         case CAR:
         case CDR:
         case CONS:
         case LIST_1:
         case LIST_2:
         case LIST_3:
         case LIST_4:
            return 1;

         case DEBUG:
            throw new IllegalStateException();

         default:
            return 2;
      }
   }

   /**
    * @return The debugger singleton
    */
   public static Debugger getDebugger()
   {
      return s_debugger;
   }

   // inner classes

   /**
    * A helper class to encapsulate a Machine state while it is suspended at a
    * breakpoint.
    */
   public class MachineState
   {
      /**
       * The current program counter
       */
      public final int pc;

      /**
       * The currently executing function
       */
      public final PCodeFunction function;

      /**
       * The current frame
       */
      public final Object[] frame;

      /**
       * The exception if applicable, otherwise null.
       */
      public final Throwable exception;

      // constructors

      /**
       * Initializes the machine state.
       *
       * @param nPC The current program counter
       * @param fun The current function
       * @param frame The current frame
       * @param exception The exception that was active at this machine state. May be null.
       */
      public MachineState(int nPC, PCodeFunction fun, Object[] frame, Throwable exception)
      {
         this.pc = nPC;
         this.frame = frame;
         this.function = fun;
         this.exception = exception;

         assert this.function != null;
         assert this.pc >= 0 && this.pc < this.function.code.length;
      }

      // operations

      /**
       * @return The suspended machine
       */
      public Machine getMachine()
      {
         return Machine.this;
      }

      /**
       * @return A copy of the stack
       */
      public List getRawStack()
      {
         List ret = new ArrayList();

         for (int i = 0; i <= m_nTop; i++)
         {
            ret.add(m_stack[i]);
         }

         return ret;
      }

      /**
       * @return The Context instance associated with the machine
       */
      public Context getContext()
      {
         return m_context;
      }

      /**
       * Makes a copy of m_code that has instruction replacing m_code[nPC]. Also sets
       * a DEBUG instruction in the returned code at the location the instruction pointer
       * would be after executing m_code[nPC] based on the machine's state.
       *
       * @param instruction The instruction to replace m_code[nPC]
       */
      public char[] createAlternativeCode(char instruction)
      {
         char[] altCode = new char[function.code.length];
         PCodeLocation target = findNextInstructionLocation(instruction, null, true);

         // Copy the code to altCode
         System.arraycopy(function.code, 0, altCode, 0, function.code.length);

         // remove the debug instruction in altCode
         altCode[pc] = instruction;

         if (target != null && target.function.code == function.code)
         {
            // Need to set switchback breakpoint in altCode otherwise the
            // non-authoritative code will keep executing
            altCode[target.offset] = DEBUG;
         }

         return altCode;
      }

      /**
       * Get the location that the program counter would be after executing
       * site.instruction.
       *
       * NOTE: result is best guess. In particular it might not be possible to
       * always predict the next instruction of CALLs and perhaps some intrinsic
       * functions.
       *
       * @param site The site to base the calculation on
       * @return The location of the program counter after the current
       *         instruction is executed. May be null if cannot be determined.
       */
      public PCodeLocation findNextInstructionLocation(BreakpointSite site, Predictor stepper, boolean bStepIn)
      {
         return findNextInstructionLocation(site.getInstruction(), stepper, bStepIn);
      }

      /**
       * @see this{@link #findNextInstructionLocation(BreakpointSite, Predictor, boolean)}
       *
       * @param instruction The instruction to test execution of. Assumed to be at nPC in
       *    this.fun.code.
       * @return The location of the program counter after the current
       *    instruction is executed. May be null if cannot be determined.
       */
      public PCodeLocation findNextInstructionLocation(char instruction, Predictor stepper, boolean bStepIn)
      {
         TopStackFrameVisitor visitor;
         int nNextPC = this.pc;
         PCodeFunction nextFun = this.function;
         boolean bStepReturnPossible = true;

         switch (instruction)
         {
            case JUMP_FALSE:

               if (Boolean.FALSE.equals(m_stack[m_nTop - 1]))
               {
                  nNextPC = nextFun.code[pc + 1];
               }
               else
               {
                  nNextPC += getPCodeLength(instruction);
               }

               break;

            case JUMP_TRUE:

               if (!Boolean.FALSE.equals(m_stack[m_nTop - 1]))
               {
                  nNextPC = nextFun.code[pc + 1];
               }
               else
               {
                  nNextPC += getPCodeLength(instruction);
               }

               break;

            case JUMP:

               nNextPC = nextFun.code[pc + 1];
               break;

            case CALL:
               Object value = m_stack[m_nTop - 1];

               // We cannot use bStepIn because we can't distinguish tail recursion
               // from actual calls.
               if (value instanceof PCodeFunction)
               {
                  nNextPC = 0;
                  nextFun = (PCodeFunction)value;

                  break;
               }

               if (bStepIn && stepper != null)
               {
                  int nArgCount = nextFun.code[pc + 1];
                  PCodeLocation loc = null;

                  try
                  {
                     m_nTop--;
                     loc = stepper.findStepInLocation(nArgCount, Machine.this, value);
                  }
                  finally
                  {
                     m_nTop++;
                  }

                  if (loc != null)
                  {
                     return loc;
                  }
               }

               visitor = new TopStackFrameVisitor();

               if (value instanceof Intrinsic.Continuation)
               {
                  visitContinuation(visitor, (Intrinsic.Continuation)value);
               }
               else
               {
                  // Fall back:
                  // If we can't step in then get the return address on the
                  // stack and set a breakpoint there.
                  visitStackTrace(visitor);
               }

               nextFun = visitor.getFunction();
               nNextPC = visitor.getOffset();
               bStepReturnPossible = false;

               break;

            case RETURN:

               int n;

               if ((n = m_nTop - 3) <= m_nReturn)
               {
                  return null;
               }

               nextFun = (PCodeFunction)m_stack[n];
               nNextPC = ((Integer)m_stack[n - 1]).intValue();

               break;

            default:

               // Handle intrinsic functions
               if (instruction >= CALL_INTRINSIC)
               {
                  IntrinsicFunction ifun = Intrinsic.getFunction(instruction - CALL_INTRINSIC);

                  if (ifun == Intrinsic.SYS_TRY)
                  {
                     nextFun = TRY_FUNCTION;
                     nNextPC = 0;

                     break;
                  }

                  if (ifun == Intrinsic.SYS_FINALIZE)
                  {
                     nextFun = (PCodeFunction)m_exceptionHandlerStack[m_nExceptionTop - 2];
                     nNextPC = 0;

                     break;
                  }

                  if (ifun == Intrinsic.CALL_CC || ifun == Intrinsic.CALL_WITH_CURRENT_CONTINUATION)
                  {
                     int nArgCount = nextFun.code[pc + 1];

                     if (nArgCount == 1)
                     {
                        value = getArg(0, nArgCount);

                        if (value instanceof PCodeFunction)
                        {
                           nNextPC = 0;
                           nextFun = (PCodeFunction)value;

                           break;
                        }

                        if (value instanceof Intrinsic.Continuation)
                        {
                           visitor = new TopStackFrameVisitor();
                           visitContinuation(visitor, (Intrinsic.Continuation)value);
                           nextFun = visitor.getFunction();
                           nNextPC = visitor.getOffset();

                           break;
                        }
                     }
                  }
                  else if (ifun == Intrinsic.APPLY)
                  {
                     int nArgCount = nextFun.code[pc + 1];

                     if (nArgCount > 0)
                     {
                        value = getArg(0, nArgCount);

                        if (value instanceof PCodeFunction)
                        {
                           nNextPC = 0;
                           nextFun = (PCodeFunction)value;

                           if (function == CALL1_FUNCTION)
                           {
                              bStepReturnPossible = false;
                           }

                           break;
                        }

                        if (value instanceof Intrinsic.Continuation)
                        {
                           visitor = new TopStackFrameVisitor();
                           visitContinuation(visitor, (Intrinsic.Continuation)value);
                           nextFun = visitor.getFunction();
                           nNextPC = visitor.getOffset();

                           if (function == CALL1_FUNCTION)
                           {
                              bStepReturnPossible = false;
                           }

                           break;
                        }
                     }
                  }
                  else if (ifun == Intrinsic.CALL_WITH_VALUES)
                  {
                     int nArgCount = nextFun.code[pc + 1];

                     if (nArgCount == 2)
                     {
                        value = getArg(0, nArgCount);

                        if (value instanceof PCodeFunction ||
                           (!(value instanceof Intrinsic.Continuation) &&
                              (value = getArg(1, nArgCount)) instanceof PCodeFunction))
                        {
                           nNextPC = 0;
                           nextFun = (PCodeFunction)value;

                           break;
                        }

                        if (value instanceof Intrinsic.Continuation)
                        {
                           visitor = new TopStackFrameVisitor();
                           visitContinuation(visitor, (Intrinsic.Continuation)value);
                           nextFun = visitor.getFunction();
                           nNextPC = visitor.getOffset();

                           break;
                        }
                     }
                  }
                  else // detect call-with-values pattern from the producer function
                  {
                     int nArgCount = nextFun.code[pc + 1] + 4;

                     if (m_nTop - m_nReturn >= nArgCount)
                     {
                        value = getArg(2, nArgCount);

                        if (value instanceof PCodeFunction &&
                           Primitive.ONE_INTEGER.equals(getArg(1, nArgCount)) &&
                           Arrays.equals(((PCodeFunction)value).code, CALL1_FUNCTION.code))
                        {
                           value = getArg(0, nArgCount);

                           if (value instanceof PCodeFunction)
                           {
                              nNextPC = 0;
                              nextFun = (PCodeFunction)value;
                              bStepReturnPossible = false;

                              break;
                           }

                           if (value instanceof Intrinsic.Continuation)
                           {
                              visitor = new TopStackFrameVisitor();
                              visitContinuation(visitor, (Intrinsic.Continuation)value);
                              nextFun = visitor.getFunction();
                              nNextPC = visitor.getOffset();
                              bStepReturnPossible = false;

                              break;
                           }
                        }
                     }
                  }
               }

               nNextPC += getPCodeLength(instruction);

               break;
         }

         if (nextFun == null)
         {
            return null;
         }

         int nLength = nextFun.code.length;

         while (nNextPC < nLength && nextFun.code[nNextPC] == JUMP)
         {
            nNextPC = nextFun.code[nNextPC + 1];
         }

         // Some functions may return true, and this PCodeFunction may not
         // contain an instruction after this one.
         if (nNextPC >= nLength)
         {
            visitor = new TopStackFrameVisitor();
            visitStackTrace(visitor);

            nextFun = visitor.getFunction();
            nNextPC = visitor.getOffset();
         }
         else if (nNextPC == 0)
         {
            char c = nextFun.code[nNextPC];

            switch (c)
            {
               case CHECK_FRAME:
               case SETUP_FRAME:
               case CHECK_VARARG_FRAME:
               case SETUP_VARARG_FRAME:
                  nNextPC += getPCodeLength(c);
                  break;
            }
         }

         return new PCodeLocation(nextFun, nNextPC, bStepReturnPossible);
      }

      /**
       * Use the visitor to find the next code that will be executed once the
       * continuation is executed.
       *
       * @param visitor The visitor to use
       * @param value The continuation
       */
      protected void visitContinuation(TopStackFrameVisitor visitor, Continuation value)
      {
         // See Machine.setContinuation()
         Object[] stack = ((Intrinsic.Continuation)value).getStack();
         int nExceptionCount = ((Integer)stack[stack.length - 1]).intValue();

         visitStackTrace(visitor, stack, 0, stack.length - nExceptionCount - 1, null);

         if (!visitor.isVisited())
         {
            visitStackTrace(visitor, m_stack, 0, m_nExceptionBarrier, null);
         }
      }

      /**
       * Find the closest exception handler.
       *
       * @return The next exception handler that will be executed or null if none.
       */
      public PCodeFunction findNextExceptionHandler()
      {
         PCodeFunction fun;

         if (m_nExceptionTop == m_nExceptionReturn)
         {
            fun = null;
         }
         else if (!Boolean.FALSE.equals(m_exceptionHandlerStack[m_nExceptionTop - 1]))
         {
            // Thrown from catch - get the finalizer
            fun = (PCodeFunction)m_exceptionHandlerStack[m_nExceptionTop - 2];

            assert fun != null;
         }
         else
         {
            // Get the catch
            fun = (PCodeFunction)m_exceptionHandlerStack[m_nExceptionTop - 3];

            assert fun != null;
         }

         return fun;
      }
   }

   /**
    * Encapsulates a particular point in this PCodeFunction
    */
   public static class PCodeLocation
   {
      /**
       * Offset in code[]
       */
      public final int offset;

      /**
       * Function this location applies to
       */
      public final PCodeFunction function;

      /**
       * Indicates whether the action to perform at this location may be step return.
       */
      public final boolean stepReturnPossible;

      /**
       * Initializes the code location.
       *
       * @param fun Function to use
       * @param nOffset Offset to use
       */
      public PCodeLocation(PCodeFunction fun, int nOffset)
      {
         this(fun, nOffset, false);
      }

      /**
       * Initializes the code location.
       *
       * @param fun Function to use
       * @param nOffset Offset to use
       * @param bStepReturnPossible True if the action to perform at this location may be step return.
       */
      public PCodeLocation(PCodeFunction fun, int nOffset, boolean bStepReturnPossible)
      {
         this.offset = nOffset;
         this.function = fun;
         this.stepReturnPossible = bStepReturnPossible;

         if (nOffset < 0 || nOffset >= function.code.length)
         {
            throw new ArrayIndexOutOfBoundsException(nOffset);
         }
      }

      /**
       * @return The instruction this location refers to
       */
      public char getInstruction()
      {
         return function.code[offset];
      }

      /**
       * @return Gets the line number if debug information available, or -1
       *         otherwise
       */
      public int getLine()
      {
         DebugInfo di = function.getDebugInfo();

         return (di == null) ? -1 : di.getLine(di.getPos(offset + 1));
      }

      /**
       * @return Gets the URL if debug information available, or -1 otherwise
       */
      public String getURL()
      {
         DebugInfo di = function.getDebugInfo();

         return (di == null) ? null : di.getURL(di.getPos(offset + 1));
      }

      /**
       * Determine if this location refers to the same line and URL as other.
       *
       * Note: even if this returns true other.getFun() is not necessarily the
       * same as getFun(). For example when a PCodeFunction is bind()ed
       *
       * @param other To be compared against this
       * @return True if same line and URL or false if debug info not available
       */
      public boolean isSameLine(PCodeLocation other)
      {
         if (function.getDebugInfo() == null)
         {
            return false;
         }

         return (getLine() == other.getLine()) && ObjUtil.equal(getURL(), other.getURL());
      }

      /**
       * @see java.lang.Object#equals(java.lang.Object)
       */
      public boolean equals(Object o)
      {
         if (o instanceof PCodeLocation)
         {
            PCodeLocation other = (PCodeLocation)o;

            return other.function.code == function.code && other.offset == offset;
         }

         return false;
      }

      /**
       * @see java.lang.Object#hashCode()
       */
      public int hashCode()
      {
         return (((function.code == null) ? 0 : function.code.hashCode())) * 31 + offset;
      }
   }

   /**
    * Used by
    * {@link Machine#visitStackTrace(nexj.core.scripting.Machine.StackFrameVisitor)}
    * to visit stack traces.
    */
   public interface StackFrameVisitor
   {
      /**
       * Called once for each frame in the stack
       *
       * @param fun The function in this stack frame
       * @param nOffset Offset of the program counter in fun.code
       * @param frame The current frame, may be null.
       * @return True to continue the iteration, false to stop it.
       */
      boolean visit(PCodeFunction fun, int nOffset, Object[] frame);
   }

   /**
    * Stack frame visitor that peeks the top frame information.
    */
   public static class TopStackFrameVisitor implements StackFrameVisitor
   {
      /**
       * The stack frame code offset.
       */
      protected int m_nOffset;

      /**
       * The stack frame function.
       */
      protected PCodeFunction m_function;

      /**
       * @see nexj.core.scripting.Machine.StackFrameVisitor#visit(nexj.core.scripting.PCodeFunction, int, java.lang.Object[])
       */
      public boolean visit(PCodeFunction fun, int nOffset, Object[] frame)
      {
         m_nOffset = nOffset;
         m_function = fun;

         return false;
      }

      /**
       * @return The stack frame code offset.
       */
      public int getOffset()
      {
         return m_nOffset;
      }

      /**
       * @return The stack frame function.
       */
      public PCodeFunction getFunction()
      {
         return m_function;
      }

      /**
       * @return Whether this visitor successfully visited the top frame
       */
      public boolean isVisited()
      {
         return m_function != null;
      }
   }

   public static class InvalidDebugBytecodeException extends UncheckedException
   {
      private final static long serialVersionUID = 6220079379960381845L;

      /**
       * Constructor
       */
      public InvalidDebugBytecodeException()
      {
         super("err.debugger.invalidDebugBytecodeInstruction");
      }
   }

   /**
    * Encapsulates platform specific functionality for stepping in. For example the
    * Java client does not ship with the Metaclass class.
    */
   public interface Predictor
   {
      /**
       * Step into something
       * @param nArgCount The argument count
       * @param machine The machine
       * @param value The thing we are to step into
       *
       * @return Step in location, or null on error
       */
      PCodeLocation findStepInLocation(int nArgCount, Machine machine, Object value);
   }

   /**
    * Encapsulates global state of the debugger
    */
   public interface Debugger
   {
      /**
       * Handle a breakpoint hit.
       *
       * @param state Machine state when the DEBUG instruction was encountered.
       * @return The site corresponding to the location at which the breakpoint
       *         was hit. Could be null if not installed.
       */
      public BreakpointSite hitBreakpoint(MachineState state);

      /**
       * Called when an exception is caught in the scripting virtual machine.
       *
       * @param state The state at the time the exception was thrown. The
       *           exception field must be set.
       * @param steppingBreakpointSet Set of stepping breakpoints currently
       *           active in state. May be null. May be live.
       */
      public void hitException(MachineState machineState, Set steppingBreakpointSet);

      /**
       * In some cases a top level function is a closure bound at runtime to
       * another frame. The parent frame is discarded at compile-time so we save
       * it here. This can be used for variable name information.
       *
       * @param child A "top level function".
       * @param parent The parent of child.
       */
      public void setAnonymousParent(PCodeFunction pCodeFunction, PCodeFunction parent);

      /**
       * Find an installation site directly by its keys
       *
       * @param code Code[] of the installation site
       * @param nOffset Offset in code
       * @return May be null
       */
      public BreakpointSite findInstallationSite(char[] code, int nOffset);
   }

   /**
    * A location at which breakpoints can be installed
    */
   public interface BreakpointSite
   {
      /**
       * @return Whether the DEBUG instruction is currently installed in
       *         bytecode
       */
      boolean isInstalled();

      /**
       * @return The original instruction at the installation site
       */
      char getInstruction();
   }
}
TOP

Related Classes of nexj.core.scripting.Machine

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.