/* $Header: /belch_a/users/rearl/tinymuck/src/RCS/interp.c,v 1.15 90/09/28 12:23:30 rearl Exp $ */

/*
 * $Log:	interp.c,v $
 * Revision 1.15  90/09/28  12:23:30  rearl
 * Took out some useless stuff, added more speed enhancements, fixed
 * negative ROTATE operation.
 * 
 * Revision 1.14  90/09/24  20:46:00  rearl
 * Pre-rewrite checkin.
 * 
 * Revision 1.13  90/09/18  07:58:52  rearl
 * Changed lots of stuff, upper/lowercase lookup, speedups with register
 * declarations, fixed a few primitives.
 * 
 * Revision 1.12  90/09/16  04:42:21  rearl
 * Preparation code added for disk-based MUCK.
 * 
 * Revision 1.11  90/09/15  22:24:17  rearl
 * Put in negative operand ROTATE.  Fixed a few COMPRESS property bugs.
 * 
 * Revision 1.10  90/09/13  06:26:12  rearl
 * Added unreadable properties, ok_name() check in setname, EXECUTE.
 * 
 * Revision 1.9  90/09/12  09:01:15  rearl
 * Added ELSE constructs as well as quoted functions with the EXECUTE
 * primitive.
 * 
 * Revision 1.8  90/09/01  05:58:12  rearl
 * Fixed compress conflicts with SET<field>, also null string
 * possibility in SETNAME.
 * 
 * Revision 1.7  90/08/27  03:27:36  rearl
 * Simplified type-checking, aborting, added some new primitives.
 * Fixed CONTENTS so it returns dbrefs in the correct order.
 * 
 * Revision 1.6  90/08/06  18:04:41  rearl
 * EXPLODE and SUBST work with any delimit/replacement string of length > 0,
 * fixed NULL bug in strncmp, fixed some interpreter error messages.
 * 
 * Revision 1.5  90/08/05  03:19:21  rearl
 * Redid matching routines.
 * 
 * Revision 1.4  90/07/29  23:46:26  rearl
 * Added string primitives: strncmp, strcut, strlen.
 * 
 * Revision 1.3  90/07/29  17:37:11  rearl
 * Added writeonly flag (for ROOM programs), debugging support,
 * fixed Wizard priveledges once again, but not permanently as yet.
 * 
 * Revision 1.2  90/07/22  04:25:45  casie
 * Added pronoun_sub primitive for o-message-like % substitutions.
 * 
 * Revision 1.1  90/07/19  23:03:47  casie
 * Initial revision
 * 
 *
 */

#include "copyright.h"
#include "config.h"

#include <sys/types.h>
#include "db.h"
#include "inst.h"
#include "externs.h"
#include "match.h"
#include "interface.h"
#include "params.h"
#include "strings.h"

/* This package performs the interpretation of mud forth programs.
   It is a basically push pop kinda thing, but I'm making some stuff
   inline for maximum efficiency.

   Oh yeah, because it is an interpreted language, please do type
   checking during this time.  While you're at it, any objects you
   are referencing should be checked against db_top.
   */

/* in cases of boolean expressions, we do return a value, the stuff
   that's on top of a stack when a mud-forth program finishes executing.
   In cases where they don't, leave a value, we just return a 0.  Note:
   this stuff does not return string or whatnot.  It at most can be
   relied on to return a boolean value.
   
   interp sets up a player's frames and so on and prepares it for
   execution.
   */

/* Some icky machine/compiler #defines. --jim */
/*
#ifdef __GNUC__
#define INLINE inline
#else
#define INLINE
#endif
*/
#define INLINE
#ifdef MIPS
typedef char * voidptr;
#define MIPSCAST (char *)
#else
typedef void * voidptr;
#define MIPSCAST
#endif
#define UPCASE(x) (uppercase[x])
#define DOWNCASE(x) (lowercase[x])
#define DoNullInd(x) ((x) ? (x) -> data : "")

INLINE void push (struct inst *stack, int *top, int type, voidptr res);

INLINE int valid_object(struct inst *oper);

void dispatch(dbref player, dbref program, struct inst *pc, struct inst *arg, int *top, struct frame *fr);

int
  interp(dbref player, dbref program, dbref source)
{
  struct frame *fr;
  
  fr = DBFETCH(player)->run =
    (struct frame *) calloc(1, sizeof(struct frame));
  if (!can_link_to(source, TYPE_EXIT, program) &&
      (OWNER(source) != OWNER(program)))  /* in case owner of source is quell */
    {
      notify(player, "Program call: Permission denied.");
      return 0;
    }

  if (DBFETCH(program)->sp.program.start == 0) {
    /* try to compile it... */
    DBSTORE(program, sp.program.first, read_program(program));
    do_compile(player, program);
    free_prog_text(DBFETCH(program)->sp.program.first);
  }
  if (DBFETCH(program)->sp.program.start == 0) {
    /* compile failed... */
    notify(player, "Program not compiled. Cannot run.");
    free((void *) fr);
    return 0;
  }

  fr -> system.top = 1;
  fr -> argument.top = 0;
  fr -> pc = DBFETCH(program)->sp.program.start;
  fr -> writeonly = ((Typeof(source) == TYPE_ROOM)
		     || (FLAGS(player)&INTERACTIVE));
  
  /* set basic variables */
  fr -> variables[0].type = PROG_OBJECT;
  fr -> variables[0].data.number = player;
  fr -> variables[1].type = PROG_OBJECT;
  fr -> variables[1].data.number = DBFETCH(player)->location;
  fr -> variables[2].type = PROG_OBJECT;
  fr -> variables[2].data.number = source;
  
  push(fr->argument.st, &(fr->argument.top), PROG_STRING, match_args ?
       MIPSCAST alloc_prog_string(match_args) : 0);
  return (interp_loop(player, program, fr));
}

/* clean up the stack. */
INLINE
  void
  prog_clean(struct frame *fr)
{
  int     i;
  
  fr -> system.top = 0;
  for (i = 0; i < fr -> argument.top; i++)
    CLEAR(&fr -> argument.st[i]);
  
  for (i = 0; i < MAX_VAR; i++)
    CLEAR(&fr -> variables[i]);

  fr -> argument.top = 0;
  fr -> pc = 0;
}

INLINE
  void
  reload(struct frame *fr, int atop, int stop)
{
  fr -> argument.top = atop;
  fr -> system.top = stop;
}

INLINE
  int
  false (struct inst *p)
{
  return ( (p -> type == PROG_STRING && (p -> data.string == 0 || !(*p -> data.string->data)))
	  || (p -> type == PROG_INTEGER && p -> data.number == 0)
	  || (p -> type == PROG_OBJECT && p -> data.objref == NOTHING) );
}

static int err;
static int already_created;

INLINE
  void
  copyinst(struct inst *from, struct inst *to)
{
  *to = *from;
  if (from -> type == PROG_STRING && from -> data.string) {
    from -> data.string->links++;
  }
}

#define abort_loop(S) { notify(player, S); reload(fr, atop, stop); prog_clean(fr); return 0; }
int
  interp_loop(dbref player, dbref program, struct frame *fr)
{
  register struct inst    *pc;
  register int		  atop;
  register struct inst    *arg;
  register struct inst    *temp1;
  register struct inst    *temp2;
  register struct inst    *sys;
  register int            stop;
  int                     tmp, writeonly;
  dbref                   obj;
  
  /* load everything into local stuff */
  pc = fr -> pc;
  atop = fr -> argument.top;
  stop = fr -> system.top;
  arg = fr -> argument.st;
  sys = fr -> system.st;
  writeonly = fr -> writeonly;
  already_created = 0;
  
  if (!pc)
    {
      notify(player, "Program not compiled. Cannot run.");
      return 0;
    }
  
  err = 0;
  
  /* This is the 'natural' way to exit a function */
  while (stop)
    {
      if (FLAGS(program) & DARK) {  /* if trace mode -- lpb */
	char *m;
        m = debug_inst(pc, arg, atop, program);
        notify(player, m);
      }
      switch (pc -> type)
	{
	case PROG_INTEGER:
	case PROG_ADD:
	case PROG_OBJECT:
	case PROG_VAR:
	case PROG_STRING:
	  if (atop >= STACK_SIZE)
	    abort_loop("Program Constant: Stack overflow.");
	  copyinst(pc, arg + atop);
	  pc++; atop++;
	  break;
	  
	case PROG_PRIMITIVE:
	  /* All pc modifiers and stuff like that should stay here,
	     everything else call with an independent dispatcher.  */
	  switch (pc -> data.number)
	    {
	    case IN_IF:
	      if (atop < 2)
		abort_loop("IF: Stack Underflow.");
	      temp1 = arg + --atop;
	      temp2 = arg + --atop;
	      if (temp1->type != PROG_ADD) {
	        CLEAR(temp2);
		abort_loop("Program internal error.  Aborted.");
	      }
	      if ( false(temp2) )
		pc = temp1->data.call;
	      else
		pc++;
	      CLEAR(temp2);
	      break;
	    case IN_JMP:
	      if (atop < 1)
		abort_loop("Program internal error.  Aborted.");
	      temp1 = arg + --atop;
	      if (temp1->type != PROG_ADD)
		abort_loop("Program internal error.  Aborted.");
	      pc = temp1->data.call;
	      break;
	    case IN_EXECUTE:
	      if (atop < 1)
		abort_loop("Program word: Stack Underflow.");
	      temp1 = arg + --atop;
	      if (temp1->type != PROG_ADD)
		abort_loop("Program word: Program internal error.");
	      if (stop >= STACK_SIZE)
		abort_loop("Program word: Stack Overflow");
	      sys[stop++].data.call = pc + 1;
	      pc = temp1->data.call;
	      break;
	    case IN_CALL:
	      if (atop < 1)
		abort_loop("CALL: Stack Underflow.");
	      temp1 = arg + --atop;
	      if (!valid_object(temp1)
		  || Typeof(temp1->data.objref) != TYPE_PROGRAM)
		abort_loop("CALL: Invalid object.");
	      obj = temp1->data.objref;
	      if(OWNER(obj) !=
		 (((FLAGS(program) & STICKY) != 0) ? OWNER(program) : OWNER(player))
		 && !Linkable(obj)
		 && !Wizard(program))
	        abort_loop("CALL: Permission denied.");
	      if (stop >= STACK_SIZE)
		abort_loop("CALL: Stack Overflow.");
	      if (atop >= STACK_SIZE)
		abort_loop("CALL: Stack Overflow.");

	      if (DBFETCH(obj)->sp.program.start == 0) {
		/* try to compile it... */
		DBSTORE(obj, sp.program.first, read_program(obj));
		do_compile(player, obj);
		free_prog_text(DBFETCH(obj)->sp.program.first);
	      }
	      if (DBFETCH(obj)->sp.program.start == 0) {
		/* compile failed... */
		abort_loop("Program not compiled.");
	      }

	      sys[stop++].data.call = pc + 1;
	      pc = DBFETCH(obj)->sp.program.start;
	      if(FLAGS(obj)&ARCHITECT) {
		tmp = atop;
		push(arg, &tmp, PROG_OBJECT, MIPSCAST &program);
		atop = tmp;
	      }
	      program = obj;
	      break;
	    case IN_PROGRAM:
	      if (atop < 1)
		abort_loop("Program internal error -- aborted in_program.");
	      temp1 = arg + --atop;
	      if (!valid_object(temp1)
		  || Typeof(temp1->data.objref) != TYPE_PROGRAM
		  || (!(DBFETCH(temp1->data.objref)->sp.program.code)))
		abort_loop("Program internal error -- aborted. in_program!");
	      program = temp1->data.objref;
	      pc++;
	      break;
	    case IN_RET:
	      pc = sys[--stop].data.call;
	      break;
	    case IN_READ:
	      if (writeonly)
		abort_loop("READ: Program is write-only.");
	      FLAGS(player) |= INTERACTIVE;
	      reload(fr, atop, stop);
	      DBSTORE(player, run -> pc, pc + 1);
	      DBSTORE(player, curr_prog, program);
	      return 0;
	      /*NOTREACHED*/
	      break;
	    default:
	      tmp = atop;
	      dispatch(player, program, pc, arg, &tmp, fr);
	      atop = tmp;
	      pc++;
	      break;
	    } /* switch */
	  break;
	default:
	  abort_loop("Program internal error.  Aborted.");
	} /* switch */
      if (err)
	{
	  reload(fr, atop, stop);
	  prog_clean(fr);
	  return 0;
	}
    } /* while */
  
  if (atop)
    {
      int  retval;
      
      retval = !false(arg + atop - 1);
      reload(fr, atop, stop);
      prog_clean(fr);
      return retval;
    }
  reload(fr, atop, stop);
  prog_clean(fr);
  return 0;
}

void
  interp_err(dbref player, const char *msg1, const char *msg2)
{
  char buf[BUFSIZ];
  
  err++;
  notify(player, "Programmer Error.  Tell whoever made this program the next message.");
  strcpy(buf, msg1);
  strcat(buf, ": ");
  strcat(buf, msg2);
  notify(player, buf);
}

INLINE
  void
  push (struct inst *stack, int *top, int type, voidptr res)
{
  stack[*top].type = type;
  switch (type) {
  case PROG_VAR:
  case PROG_INTEGER:
    stack[*top].data.number = *(int *)res;
    break;
  case PROG_STRING:
    stack[*top].data.string = (struct shared_string *)res;
    break;
  case PROG_OBJECT:
    stack[*top].data.objref = *(dbref *)res;
    break;
  case PROG_CON:
    stack[*top].data.con = (struct descriptor_data *)res;
    break;
  case PROG_ADD:
    stack[*top].data.call = (struct inst *) res;
    break;
  }
  
  (*top)++;
}

INLINE
  void
  copyobj(dbref player, dbref old, dbref new)
{
  struct object *oldp = DBFETCH(old);
  struct object *newp = DBFETCH(new);
  NAME(new) = alloc_string(NAME(old));
  newp->attributes = copy_attr(old);
  newp->properties = copy_prop(old);
  newp->key = copy_bool(oldp->key);
  newp->exits = NOTHING;
  newp->contents = NOTHING;
  newp->location = NOTHING;
  newp->next = NOTHING;
  
/*  printf("Player: %d, loc %d, new: %d\n",player,DBFETCH(player)->location,
	 new); */
  moveto(new, (DBFETCH(player)->location==NOTHING)?player:
	 DBFETCH(player)->location);
  sprintf(buf, "#%d", (int) new);
  add_attr(player, "It", buf);
  DBDIRTY(new);
}

INLINE
  int
  valid_object(struct inst *oper)
{
  return ((oper->type == PROG_OBJECT)
#ifdef RECYCLE
	    && (Typeof(oper->data.objref) != TYPE_GARBAGE)
#else /* RECYCLE */
	    && (oper->data.objref < db_top)
#endif /* RECYCLE */
	    && (oper->data.objref >= 0));
}

INLINE
  int
  is_home(struct inst *oper)
{
  return (oper->type == PROG_OBJECT && oper->data.objref == HOME);
}

INLINE
  int
  permissions(dbref player, dbref thing)
{
  
  if (thing == player || thing == HOME)
    return 1;
  
  switch (Typeof(thing))
    {
    case TYPE_PLAYER:
      return 0;
    case TYPE_EXIT:
      return ((OWNER(thing) == player) || (OWNER(thing) == NOTHING));
    case TYPE_ROOM:
    case TYPE_THING:
    case TYPE_PROGRAM:
      return (OWNER(thing) == player);
    }
  
  return 0;
}

INLINE
  int
  arith_type(struct inst *op1, struct inst *op2)
{
  return ((op1->type == PROG_INTEGER && op2->type == PROG_INTEGER) /* real
								      stuff */
	  || (op1->type == PROG_VAR && op2->type == PROG_INTEGER)); /* offset
								       array */
}

#define CHECKOP(N) { if ((*top) < (N)) { interp_err(player, insttotext(pc), "Stack underflow."); return; } nargs = (N); }
#define POP() (arg + --(*top))
#define abort_interp(C) { interp_err(player, insttotext(pc), (C)); \
			    switch(nargs) { \
					    case 4: CLEAR(oper4); \
					    case 3: CLEAR(oper3); \
					    case 2: CLEAR(oper2); \
					    case 1: CLEAR(oper1); } \
					      return; }

/* Regular primitive dispatcher.  Basically one HUGE switch.
 */
void
  dispatch(dbref player, dbref program, struct inst *pc, struct inst *arg, int *top, struct frame *fr)
{
  register struct inst  *oper1, *oper2, *oper3, *oper4;
  struct inst temp1, temp2, temp3;
  struct inst *add, *sys;
  int    result;
  struct shared_string   *string;
  char   buf[BUFFER_LEN];
  dbref  ref;
  dbref  uid;
  int    nargs = 0; /* DO NOT TOUCH THIS VARIABLE */
  int    tmp;
  long   ltmp;
  int    wizard;
  int    mucker;
  int    stop;

  stop = fr -> system.top;
  sys = fr -> system.st;

  wizard = (Wizard(program));
#ifndef MUCKER_ALL
  mucker = (Wizard(program) || FLAGS(program)&MUCKER);
#else
  mucker = 1;
#endif /* MUCKER_ALL */
  uid = (((FLAGS(program) & STICKY) != 0) ? OWNER(program) : player);

  switch (pc -> data.number) {
  case IN_ADD:
  case IN_SUBTRACT:
  case IN_MULTIPLY:
  case IN_DIVIDE:
  case IN_MOD:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if (!arith_type(oper2, oper1))
      abort_interp("Invalid argument type.");
    if (pc -> data.number == IN_ADD)
      result = oper1->data.number + oper2->data.number;
    else if (pc -> data.number == IN_SUBTRACT)
      result = oper2->data.number - oper1->data.number;
    else if (pc -> data.number == IN_MULTIPLY)
      result = oper1->data.number * oper2->data.number;
    else if (pc -> data.number == IN_MOD)
      {
	if (oper1->data.number)
	  result = oper2->data.number % oper1->data.number;
	else
	  result = 0;
      }
    else
      {
	if (oper1->data.number)
	  result = oper2->data.number / oper1->data.number;
	else
	  result = 0;
      }
    CLEAR(oper1); CLEAR(oper2);
    push(arg, top, oper2->type, MIPSCAST &result);
    break;
  case IN_POP:
    CHECKOP(1);
    oper1 = POP();
    CLEAR(oper1);
    break;
  case IN_DUP:
    CHECKOP(1);
    copyinst(&arg[*top - 1], &arg[*top]);
    (*top)++;
    break;
  case IN_NUMBERP:
    CHECKOP(1);
    oper1 = POP();
    if (oper1->type != PROG_STRING || !oper1->data.string)
      result = 0;
    else
      result = number(oper1->data.string->data);
    CLEAR(oper1);
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
  case IN_STRINGCMP:
  case IN_STRCMP:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if (oper1->type != PROG_STRING || oper2->type != PROG_STRING)
      abort_interp("Non-string argument.");
    if (oper1->data.string == oper2->data.string)
      result = 0;
    else if (!(oper2->data.string && oper1->data.string))
      result = oper1->data.string ? -1 : 1;
    else {
      if (pc -> data.number == IN_STRCMP)
	result = strcmp(oper2->data.string->data, oper1->data.string->data);
      else /* IN_STRINGCMP */
	result = string_compare(oper2->data.string->data,
				oper1->data.string->data);
    }
    CLEAR(oper1); CLEAR(oper2);
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
    case IN_STRINGNCMP:
    case IN_STRNCMP:
      CHECKOP(3);
      oper1 = POP(); oper2 = POP(); oper3 = POP();
      if (oper1->type != PROG_INTEGER)
        abort_interp("Non-integer argument.");
      if (oper2->type != PROG_STRING || oper3->type != PROG_STRING)
        abort_interp("Non-string argument.");
      if (oper2->data.string == oper3->data.string)
        result = 0;
      else if (!(oper3->data.string && oper2->data.string))
        result = oper2->data.string ? -1 : 1;
      else if (pc -> data.number == IN_STRNCMP)
        result = strncmp(oper3->data.string->data, oper2->data.string->data,
                         oper1->data.number);
      else
        result = strncasecmp(oper3->data.string->data,
                             oper2->data.string->data,
                             oper1->data.number);
      CLEAR(oper1); CLEAR(oper2); CLEAR(oper3);
      push(arg, top, PROG_INTEGER, MIPSCAST &result);
      break;
  case IN_STRCUT:
    CHECKOP(2);
    temp1 = *(oper1 = POP()); temp2 = *(oper2 = POP());
    if (temp1.type != PROG_INTEGER)
      abort_interp("Non-integer argument (2)");
    if (temp1.data.number < 0)
      abort_interp("Argument must be a positive integer.");
    if (temp2.type != PROG_STRING)
      abort_interp("Non-string argument (1)");
    if (!temp2.data.string) {
      push(arg, top, PROG_STRING, 0);
      push(arg, top, PROG_STRING, 0);
    } else {
      if (temp1.data.number > temp2.data.string->length) {
	temp2.data.string->links++;
	push(arg, top, PROG_STRING, MIPSCAST temp2.data.string);
	push(arg, top, PROG_STRING, 0);
      } else {
	bcopy(temp2.data.string->data, buf, temp1.data.number);
	buf[temp1.data.number] = '\0';
	push(arg, top, PROG_STRING, MIPSCAST alloc_prog_string(buf));
	if (temp2.data.string->length > temp1.data.number) {
	  bcopy(temp2.data.string->data + temp1.data.number, buf,
		temp2.data.string->length - temp1.data.number + 1);
	  push(arg, top, PROG_STRING, MIPSCAST alloc_prog_string(buf));
	} else {
	  push(arg, top, PROG_STRING, 0);
	}
      }
    }
    CLEAR(&temp1); CLEAR(&temp2);
    break;
  case IN_STRLEN:
    CHECKOP(1);
    oper1 = POP();
    if (oper1->type != PROG_STRING)
      abort_interp("Non-string argument.");
    if (!oper1->data.string) result = 0;
    else result = oper1->data.string->length;
    CLEAR(oper1);
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
  case IN_STRCAT:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if (oper1->type != PROG_STRING || oper2->type != PROG_STRING)
      abort_interp("Non-string argument.");
    if (!oper1->data.string && !oper2->data.string)
      string = NULL;
    else if (!oper2->data.string) {
      oper1->data.string->links++;
      string = oper1->data.string;
    } else if (!oper1->data.string) {
      oper2->data.string->links++;
      string = oper2->data.string;
    } else if (oper1->data.string->length + oper2->data.string->length
	       > (BUFFER_LEN) - 1)
      {
	abort_interp("Operation would result in overflow.");
      }
    else
      {
	bcopy(oper2->data.string->data, buf, oper2->data.string->length);
	bcopy(oper1->data.string->data, buf + oper2->data.string->length,
	      oper1->data.string->length + 1);
	string = alloc_prog_string(buf);
      }
    CLEAR(oper1); CLEAR(oper2);
    push(arg, top, PROG_STRING, MIPSCAST string);
    break;
  case IN_AND:
  case IN_OR:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if (pc -> data.number == IN_AND)
      result = !false(oper1) && !false(oper2);
    else
      result = !false(oper1) || !false(oper2);
    CLEAR(oper1); CLEAR(oper2);
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
  case IN_NOT:
    CHECKOP(1);
    oper1 = POP();
    result = false(oper1);
    CLEAR(oper1);
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
  case IN_NOTIFY:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if (!valid_object(oper2))
      abort_interp("Invalid object (1)");
    if (oper1->type != PROG_STRING)
      abort_interp("Non-string argument (2)");
    if (oper1->data.string)
      notify(oper2->data.objref, oper1->data.string->data);
    CLEAR(oper1); CLEAR(oper2);
    break;
  case IN_NOTIFY_EXCEPT:
    CHECKOP(3);
    oper1 = POP(); oper2 = POP(); oper3 = POP();
    if (!valid_object(oper3))
      abort_interp("Non-room argument (1)");
    if (oper2->type != PROG_OBJECT)
      abort_interp("Invalid object argument (2)");
    if (oper1->type != PROG_STRING)
      abort_interp("Non-string argument (3)");
    if (oper1->data.string)
      notify_except(DBFETCH(oper3->data.objref)->contents,
                      oper2->data.objref, oper1->data.string->data);
    CLEAR(oper1); CLEAR(oper2); CLEAR(oper3);
    break;
  case IN_ADDPENNIES:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if(!mucker)
      abort_interp("Permission denied.");
    if (!valid_object(oper2))
      abort_interp("Invalid object.");
    if (oper1->type != PROG_INTEGER)
      abort_interp("Non-integer argument (2)");
    ref = oper2->data.objref;
    if (Typeof(ref) == TYPE_PLAYER) {
      result = DBFETCH(ref)->sp.player.pennies + oper1->data.number;
      if ((result > MAX_PENNIES) && !wizard)
	abort_interp("Would exceed MAX_PENNIES.");
      if ((result < 0) && !wizard)
	abort_interp("Amount would be negative.");
      DBFETCH(ref)->sp.player.pennies += oper1->data.number;
      DBDIRTY(ref);
    } else if (Typeof(ref) == TYPE_THING) {
      if (!wizard)
	abort_interp("Permission denied.");
      result = DBFETCH(ref)->sp.thing.value + oper1->data.number;
      if (result < 1)
	abort_interp("Result must be positive.");
      DBFETCH(ref)->sp.thing.value += oper1->data.number;
      DBDIRTY(ref);
    } else {
      abort_interp("Invalid object type.");
    }
    CLEAR(oper1); CLEAR(oper2);
    break;
  case IN_AT:
    CHECKOP(1);
    temp1 = *(oper1 = POP());
    if (temp1.type != PROG_VAR || temp1.data.number >= MAX_VAR)
      abort_interp("Non-variable argument.");
    copyinst(&(fr -> variables[temp1.data.number]), &arg[(*top)++]);
    CLEAR(&temp1);
    break;
  case IN_BANG:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if(oper1->type != PROG_VAR || oper1->data.number >= MAX_VAR ||
	oper1->data.number < 0)
      abort_interp("Non-variable argument (2)");
    CLEAR(&fr -> variables[oper1->data.number]);
    copyinst(oper2, &(fr -> variables[oper1->data.number]));
    CLEAR(oper1); CLEAR(oper2);
    break;
  case IN_GETPROPVAL:
  case IN_GETPROPSTR:
  case IN_REMOVE_PROP:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if (oper1->type != PROG_STRING)
      abort_interp("Non-string argument (2)");
    if (!oper1->data.string)
      abort_interp("Empty string argument (2)");
    if (!valid_object(oper2))
      abort_interp("Non-object argument (1)");
    if (pc -> data.number == IN_REMOVE_PROP) {
      if (!wizard && (!permissions(uid, oper2->data.objref)
		      && (*oper1->data.string->data != PROP_WRITABLE)))
	abort_interp("Permission denied.");
      remove_property(oper2->data.objref, oper1->data.string->data);
      CLEAR(oper1); CLEAR(oper2);
    }
    else {
      const char *temp;

      if ((*oper1->data.string->data == PROP_PRIVATE
	   || *oper1->data.string->data == PROP_WIZARD)
	  && !permissions(uid, oper2->data.objref) && !wizard)
	abort_interp("Permission denied.");

      temp = get_property_class(oper2->data.objref, oper1->data.string->data);

      if (pc -> data.number == IN_GETPROPVAL) {
	result = temp ? atoi(temp) : 0;
	CLEAR(oper1); CLEAR(oper2);
	push(arg, top, PROG_INTEGER, MIPSCAST &result);
      }
      else {
	CLEAR(oper1); CLEAR(oper2);
	push(arg, top, PROG_STRING, MIPSCAST alloc_prog_string(temp));
      }
    }
    break;
  case IN_SETPROP:
    CHECKOP(3);
    oper1 = POP(); oper2 = POP(); oper3 = POP();
    if ((oper1->type != PROG_STRING) && (oper1->type != PROG_INTEGER))
      abort_interp("Non-string/non-integer argument (3)");
    if (oper2->type != PROG_STRING)
      abort_interp("Non-string argument (2)");
    if (!oper2->data.string)
      abort_interp("Empty string argument (2)");
    if (!valid_object(oper3))
      abort_interp("Non-object argument (1)");
    if (!wizard && (*oper2->data.string->data != PROP_WRITABLE)
	&& !permissions(uid, oper3->data.objref))
      abort_interp("Permission denied.");
    if (!wizard && (*oper2->data.string->data == PROP_WIZARD))
      if(!get_property_class(oper3->data.objref, oper2->data.string->data))
	abort_interp("Permission denied.");
    {
      const char *temp;

      if(oper1->type == PROG_INTEGER) {
	sprintf(buf, "%d", oper1->data.number);
	temp = alloc_string(buf);
	add_property(oper3->data.objref, oper2->data.string->data, temp);
	free((void *) temp);
      }
      else {
	temp = oper1->data.string ? oper1->data.string->data : 0;
	if(!temp)
	  remove_property(oper3->data.objref, oper2->data.string->data);
	else
	  add_property(oper3->data.objref, oper2->data.string->data,
		       temp);
      }
    }
    CLEAR(oper1); CLEAR(oper2); CLEAR(oper3);
    break;
  case IN_MOVETO:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if (!(valid_object(oper1) && valid_object(oper2)) && !is_home(oper1))
      abort_interp("Non-object argument.");
    {
      dbref victim, dest;
      
      victim = oper2->data.objref;
      dest = oper1->data.objref;
      
      if (Typeof(dest) == TYPE_EXIT || Typeof(victim) == TYPE_EXIT)
	abort_interp("Argument is an exit.");
      if (!(FLAGS(victim) & JUMP_OK)
	  && !permissions(uid, victim) && !wizard)
	abort_interp("Object can't be moved.");
      switch (Typeof(victim)) {
      case TYPE_PLAYER:
      case TYPE_PROGRAM:
      case TYPE_THING:
	if (Typeof(dest) != TYPE_ROOM && Typeof(dest) != TYPE_PLAYER &&
	    Typeof(dest) != TYPE_THING)
	  abort_interp("Bad destination.");
	/* Check permissions */
	if(is_ok(victim) && is_ok(dest)) /* might be home. */
	  if(parent_loop_check(victim,dest))
	    abort_interp("Would create a loop.");
	if (!wizard) {
	  if (!(FLAGS(DBFETCH(victim)->location) & JUMP_OK)
	      && (uid != OWNER(DBFETCH(victim)->location)))
	    abort_interp("Source not JUMP_OK.");
	  if (!is_home(oper1) && !(FLAGS(dest) & JUMP_OK)
	      && (uid != OWNER(dest)))
	    abort_interp("Destination not JUMP_OK.");
	}
	moveto(victim,dest);
	break;
      case TYPE_ROOM:
	if (Typeof(dest) != TYPE_ROOM)
	  abort_interp("Bad destination.");
	if (victim == GLOBAL_ENVIRONMENT)
	  abort_interp("Permission denied.");
	if (dest == HOME) {
	  dest = GLOBAL_ENVIRONMENT;
	} else {
	  if (!wizard && !permissions(uid, victim)
	      || !can_link_to(uid, NOTYPE, dest))
	    abort_interp("Permission denied.");
	  if (parent_loop_check(victim, dest)) {
	    abort_interp("Parent room would create a loop.");
	  }
	}
	moveto(victim, dest);
	break;
      default:
	abort_interp("Invalid object type (1)");
      }
    }
    CLEAR(oper1); CLEAR(oper2);
    break;
  case IN_PENNIES:
    CHECKOP(1);
    oper1 = POP();
    if (!valid_object(oper1))
      abort_interp("Invalid argument.");
    switch(Typeof(oper1->data.objref))
      {
      case TYPE_PLAYER:
	result = DBFETCH(oper1->data.objref)->sp.player.pennies;
	break;
      case TYPE_THING:
	result = DBFETCH(oper1->data.objref)->sp.thing.value;
	break;
      default:
	abort_interp("Invalid argument.");
      }
    CLEAR(oper1);
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
  case IN_LESSTHAN:
  case IN_GREATHAN:
  case IN_EQUAL:
  case IN_LESSEQ:
  case IN_GREATEQ:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if (!(oper1->type == PROG_INTEGER && oper2->type == PROG_INTEGER))
      abort_interp("Invalid argument type.");
    switch (pc -> data.objref) {
    case IN_LESSTHAN:
      result = oper2->data.number < oper1->data.number; break;
    case IN_GREATHAN:
      result = oper2->data.number > oper1->data.number; break;
    case IN_EQUAL:
      result = oper1->data.number == oper2->data.number; break;
    case IN_LESSEQ:
      result = oper2->data.number <= oper1->data.number; break;
    case IN_GREATEQ:
      result = oper2->data.number >= oper1->data.number; break;
    }
    CLEAR(oper1); CLEAR(oper2);
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
  case IN_RANDOM:
    result = random();
    if ((*top) >= STACK_SIZE) abort_interp("Stack overflow.");
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
  case IN_INTOSTR:
    CHECKOP(1);
    oper1 = POP();
    switch(oper1->type) {
    case PROG_INTEGER:
      sprintf(buf, "%d", oper1->data.number);
      break;
    case PROG_OBJECT:
      sprintf(buf, "#%d", (int) oper1->data.objref);
      break;
    default:
      abort_interp("Non-integer/non-dbref argument.");
    }
    CLEAR(oper1);
    push(arg, top, PROG_STRING, MIPSCAST alloc_prog_string(buf));
    break;
  case IN_DBCOMP:
    CHECKOP(2);
    oper1 = POP(); oper2 = POP();
    if (oper1->type != PROG_OBJECT || oper2->type != PROG_OBJECT)
      abort_interp("Invalid argument type.");
    result = oper1->data.objref == oper2->data.objref;
    CLEAR(oper1); CLEAR(oper2);
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
  case IN_DBREF:
    CHECKOP(1);
    oper1 = POP();
    switch(oper1->type) {
    case PROG_STRING:
      if(oper1->data.string) {
	if(*(oper1->data.string->data) = '#')
	  ref = (dbref) atoi(oper1->data.string->data + 1);
      } else {
	ref = (dbref) -1;
      }
      break;
    case PROG_INTEGER:
      ref = (dbref) oper1->data.number;
      break;
    default:
      abort_interp("Non-string/non-integer argument.");
    }
    CLEAR(oper1);
    push(arg, top, PROG_OBJECT, MIPSCAST &ref);
    break;
  case IN_ATOI:
    CHECKOP(1);
    oper1 = POP();
    if(oper1->type != PROG_STRING)
      abort_interp("Non-string argument.");
    result = (oper1->data.string ? atoi(oper1->data.string->data) : 0);
    CLEAR(oper1);
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
  case IN_INT:
    CHECKOP(1);
    oper1 = POP();
    switch(oper1->type) {
    case PROG_STRING:
      result = (oper1->data.string ? atoi(oper1->data.string->data) : 0);
      break;
    case PROG_VAR:
      result = oper1->data.number;
      break;
    case PROG_OBJECT:
      result = (int) oper1->data.objref;
      break;
    default:
      abort_interp("Invalid argument type.");
    }
    CLEAR(oper1);
    push(arg, top, PROG_INTEGER, MIPSCAST &result);
    break;
  case IN_VAR:
    CHECKOP(1);
    oper1 = POP();
    if (oper1->type != PROG_INTEGER)
      abort_interp("Non-integer argument.");
    result = oper1->data.number;
    CLEAR(oper1);
    push(arg, top, PROG_VAR, MIPSCAST &result);
    break;
  case IN_SWAP:
    CHECKOP(2);
    oper1 = POP(); temp2 = *(oper2 = POP());
    arg[(*top)++] = *oper1;
    arg[(*top)++] = temp2;
    /* don't clean! */
    break;
  case IN_CONTENTS:
    CHECKOP(1);
    oper1 = POP();
    if (!valid_object(oper1))
      abort_interp("Invalid argument type.");
    ref = DBFETCH(oper1->data.objref)->contents;
    CLEAR(oper1);
    push(arg, top, PROG_OBJECT, MIPSCAST &ref);
    break;
  case IN_EXITS:
    CHECKOP(1);
    oper1 = POP();
    if (!valid_object(oper1))
      abort_interp("Invalid object.");
    ref = oper1->data.objref;
    if (!wizard && !permissions(uid, ref))
      abort_interp("Permission denied.");
    switch(Typeof(ref)) {
    case TYPE_PLAYER:
    case TYPE_ROOM:
    case TYPE_THING:
      ref = DBFETCH(ref)->exits;
      break;
    default:
      abort_interp("Invalid object.");
    }
    CLEAR(oper1);
    push(arg, top, PROG_OBJECT, MIPSCAST &ref);
    break;
  case IN_NEXT:
    CHECKOP(1);
    oper1 = POP();
    if (!valid_object(oper1))
      abort_interp("Invalid object.");
    ref = DBFETCH(oper1->data.objref)->next;
    CLEAR(oper1);
    push(arg, top, PROG_OBJECT, MIPSCAST &ref);
    break;
  default:
    switch (pc -> data.number)
      {
      case IN_NAME:
	CHECKOP(1);
	oper1 = POP();
	if (!valid_object(oper1))
	  abort_interp("Invalid argument type.");
	ref = oper1->data.objref;
	if(NAME(ref)) {
	  strcpy(buf, NAME(ref));
	} else {
	  buf[0] = '\0';
	}
	CLEAR(oper1);
	push(arg, top, PROG_STRING, MIPSCAST alloc_prog_string(buf));
	break;
      case IN_SETNAME:
	CHECKOP(2);
	oper1 = POP(); oper2 = POP();
	if (!valid_object(oper2))
	  abort_interp("Invalid argument type (1)");
	if (oper1->type != PROG_STRING)
	  abort_interp("Non-string argument (2)");
	ref = oper2->data.objref;
	if (!wizard && !permissions(uid, ref))
	  abort_interp("Permission denied.");
	{
	  const char *b = DoNullInd(oper1->data.string);
	  if (!ok_name(b)) abort_interp("Invalid name.");
	  if (Typeof(ref) == TYPE_PLAYER)
	    abort_interp("Permission denied.");
	  if (NAME(ref))	  free((void *) NAME(ref));
	  NAME(ref) = alloc_string(b);
	}
	CLEAR(oper1); CLEAR(oper2);
	break;
      case IN_EXPLODE:
	CHECKOP(2);
	temp1 = *(oper1 = POP()); temp2 = *(oper2 = POP());
	if (temp1.type != PROG_STRING)
	  abort_interp("Non-string argument (2)");
	if (temp2.type != PROG_STRING)
	  abort_interp("Non-string argument (1)");
	if (!temp1.data.string)
	  abort_interp("Empty string argument (2)");
	{
	  int   i;
	  const char *delimit = temp1.data.string->data;
	  
	  if (!temp2.data.string) {
	    result = 1;
	    CLEAR(&temp1); CLEAR(&temp2);
	    push(arg, top, PROG_STRING, 0);
	    push(arg, top, PROG_INTEGER, MIPSCAST &result);
	    break;
	  } else {
	    result = 0;
	    bcopy(temp2.data.string->data, buf, temp2.data.string->length + 1);
	    for (i = temp2.data.string->length - 1; i >= 0; i--)
	      {
		if (!strncmp(buf + i, delimit, temp1.data.string->length))
		  {
		    buf[i] = '\0';
		    if (*top >= STACK_SIZE)
		      abort_interp("Stack Overflow.");
		    push(arg, top, PROG_STRING, MIPSCAST
			 alloc_prog_string(buf + i +
					   temp1.data.string->length));
		    result++;
		  }
	      }
	    if (*top >= STACK_SIZE) abort_interp("Stack Overflow.");
	    push(arg, top, PROG_STRING, MIPSCAST alloc_prog_string(buf));
	    result++;
	  }
	}
	if (*top >= STACK_SIZE) abort_interp("Stack Overflow.");
	CLEAR(&temp1); CLEAR(&temp2);
	push(arg, top, PROG_INTEGER, MIPSCAST &result);
	break;
	
      case IN_MATCH:
	CHECKOP(1);
	oper1 = POP();
	if (oper1->type != PROG_STRING)
	  abort_interp("Non-string argument.");
	if (!oper1->data.string)
	  abort_interp("Empty string argument.");
	{
	  struct match_data md;
	  init_match(player, oper1->data.string->data, NOTYPE, &md);
	  match_all_exits(&md);
	  match_neighbor(&md);
	  match_possession(&md);
	  match_me(&md);
	  match_here(&md);
	  match_home(&md);
	  if(Arch(uid) || wizard) {
	    match_absolute(&md);
	    match_player(&md);
	  }
	  ref = match_result(&md);
	}
	CLEAR(oper1);
	push(arg, top, PROG_OBJECT, MIPSCAST &ref);
	break;
	
      case IN_RMATCH:
	CHECKOP(2);
	oper1 = POP(); oper2 = POP();
	if (oper1->type != PROG_STRING)
	  abort_interp("Invalid argument (2)");
	if (oper2->type != PROG_OBJECT
	    || Typeof(oper2->data.objref) == TYPE_PROGRAM
	    || Typeof(oper2->data.objref) == TYPE_EXIT)
	  abort_interp("Invalid argument (1)");
	{
	  struct match_data md;
	  init_match(player, DoNullInd(oper1->data.string), TYPE_THING, &md);
	  match_rmatch(oper2->data.objref, &md);
	  ref = match_result(&md);
	}
	CLEAR(oper1); CLEAR(oper2);
	push(arg, top, PROG_OBJECT, MIPSCAST &ref);
	break;
	
#ifdef COPYOBJ
      case IN_COPYOBJ:
	CHECKOP(1);
	oper1 = POP();
	if (!valid_object(oper1))
	  abort_interp("Invalid object.");
	if (already_created)
	  abort_interp("Can't create/destroy any more objects.");
	ref = oper1->data.objref;
	if (!mucker)
	  abort_interp("Permission denied.");  /* no copyobj for !M progs */
	if (Typeof(ref) != TYPE_THING)
	  abort_interp("Invalid object type.");
	already_created++;
	{
	  dbref newobj;
	  
	  newobj = new_object();
	  /*	*DBFETCH(newobj)= *DBFETCH(ref);*/
	  bcopy(DBFETCH(ref),DBFETCH(newobj),sizeof(*DBFETCH(newobj)));
	  copyobj(player, ref, newobj);
	  CLEAR(oper1);
	  push(arg, top, PROG_OBJECT, MIPSCAST &newobj);
	}
	break; 
#endif /* COPYOBJ */
	
      case IN_SUBST:
	CHECKOP(3);
	oper1 = POP(); oper2 = POP(); oper3 = POP();
	if (!oper1->data.string)
	  abort_interp("Empty string argument (3)");
	if (oper1->type != PROG_STRING)
	  abort_interp("Non-string argument (3)");
	if (oper2->type != PROG_STRING)
	  abort_interp("Non-string argument (2)");
	if (oper3->type != PROG_STRING)
	  abort_interp("Non-string argument (1)");
	{
	  int i = 0, j = 0;
	  const char *match;
	  const char *replacement;
	  char xbuf[BUFFER_LEN];
	  
	  buf[0] = '\0';
	  if (oper3->data.string) {
	    bcopy(oper3->data.string->data, xbuf, oper3->data.string->length + 1);
	    match = oper1->data.string->data;
	    replacement = DoNullInd(oper2->data.string);
	    while (xbuf[i]) {
	      if (!strncmp(xbuf + i, match, oper1->data.string->length)) {
		strcat(buf, replacement);
		i += oper1->data.string->length;
		j += *replacement ? oper2->data.string->length : 0;
	      } else {
		buf[j++] = xbuf[i++];
		buf[j] = '\0';
	      }
	    }
	  }
	}
	CLEAR(oper1); CLEAR(oper2); CLEAR(oper3);
	push(arg, top, PROG_STRING, MIPSCAST alloc_prog_string(buf));
	break;
	
      case IN_INSTR:
      case IN_RINSTR:
	CHECKOP(2);
	oper1 = POP(); oper2 = POP();
	if (oper1->type != PROG_STRING)
	  abort_interp("Invalid argument type (2)");
	if(!(oper1->data.string))
	  abort_interp("Empty string argument (2)");
	if (oper2->type != PROG_STRING)
	  abort_interp("Non-string argument (1)");
	if (!oper2->data.string) {
	  result = 0;
	} else {
	  
	  const char *remaining = oper2->data.string->data;
	  const char *match = oper1->data.string->data;
	  int step = (pc -> data.number == IN_INSTR) ? 1 : -1;
	  
	  if (pc -> data.number == IN_RINSTR)
	    remaining += oper2->data.string->length - 1;
	  
	  result = 0;
	  do {
	    if (!strncmp(remaining, match, oper1->data.string->length)) {
	      result = remaining - oper2->data.string->data + 1;
	      break;
	    }
	    remaining += step;
	  } while (remaining >= oper2->data.string->data && *remaining);
	}
	CLEAR(oper1); CLEAR(oper2);
	push(arg, top, PROG_INTEGER, MIPSCAST &result);
	break;
	
      case IN_SET:
      case IN_FLAGP:
	CHECKOP(2);
	oper1 = POP(); oper2 = POP();
	if (oper1->type != PROG_STRING)
	  abort_interp("Invalid argument type (2)");
	if(!(oper1->data.string))
	  abort_interp("Empty string argument (2)");
	if (!valid_object(oper2))
	  abort_interp("Invalid object.");
	ref = oper2->data.objref;

	result = ((*oper1->data.string->data == '!') && (pc->data.number == IN_SET));
	{
	  const char *flag = oper1->data.string->data;

	  if (result) flag++;

	  ltmp = lookup_flag(flag);
	}
	if (pc -> data.number == IN_SET) {
	  if (!ltmp) abort_interp("Unrecognized flag.");
	  if ((!wizard && !permissions(uid, ref))
	      || restricted(program, ref, ltmp))
	    abort_interp("Permission denied.");
	  if (!result) {
	    FLAGS(ref) |= ltmp;
	    DBDIRTY(ref);
	  } else {
	    FLAGS(ref) &= ~ltmp;
	    DBDIRTY(ref);
	  }
	  CLEAR(oper1); CLEAR(oper2);
	} else {
	  result = (ltmp && ((FLAGS(ref) & ltmp) != 0));
	  CLEAR(oper1); CLEAR(oper2);
	  push(arg, top, PROG_INTEGER, MIPSCAST &result);
	}
	break;
	
      case IN_PLAYERP:
      case IN_THINGP:
      case IN_ROOMP:
      case IN_PROGRAMP:
      case IN_EXITP:
	CHECKOP(1);
	oper1 = POP();
/*	if (!valid_object(oper1) && !is_home(oper1))
	  abort_interp("Invalid argument type."); */
	ref = oper1->data.objref;
	switch (pc -> data.number) {
	case IN_PLAYERP: result = (Typeof(ref) == TYPE_PLAYER); break;
	case IN_THINGP: result = (Typeof(ref) == TYPE_THING); break;
	case IN_ROOMP: result = (Typeof(ref) == TYPE_ROOM); break;
	case IN_PROGRAMP: result = (Typeof(ref) == TYPE_PROGRAM); break;
	case IN_EXITP: result = (Typeof(ref) == TYPE_EXIT); break;
	}
	CLEAR(oper1);
	push(arg, top, PROG_INTEGER, MIPSCAST &result);
	break;
      case IN_OKP:
	CHECKOP(1);
	oper1 = POP();
	result = (valid_object(oper1));
	CLEAR(oper1);
	push(arg, top, PROG_INTEGER, MIPSCAST &result);
	break;
      case IN_LOCATION:
      case IN_OWNER:
	CHECKOP(1);
	oper1 = POP();
	if (!valid_object(oper1))
	  abort_interp("Invalid object.");
	if (pc -> data.number == IN_LOCATION)
	  ref = DBFETCH(oper1->data.objref)->location;
	else
	  ref = OWNER(oper1->data.objref);
	CLEAR(oper1);
	push(arg, top, PROG_OBJECT, MIPSCAST &ref);
	break;
      case IN_OVER:
	CHECKOP(2);
	copyinst(&arg[*top - 2], &arg[*top]);
	(*top)++;
	break;
      case IN_PICK:
	CHECKOP(1);
	temp1 = *(oper1 = POP());
	if (temp1.type != PROG_INTEGER || temp1.data.number <= 0)
	  abort_interp("Operand not a positive integer.");
	CHECKOP(temp1.data.number);
	copyinst(&arg[*top - temp1.data.number], &arg[*top]);
	(*top)++;
	break;
      case IN_PUT:
	CHECKOP(2);
	oper1 = POP(); oper2 = POP();
	if (oper1->type != PROG_INTEGER || oper1->data.number <= 0)
	  abort_interp("Operand not a positive integer.");
	tmp = oper1->data.number;
	CHECKOP(tmp);
	CLEAR(&arg[*top - tmp]);
	copyinst(oper2, &arg[*top - tmp]);
	CLEAR(oper1); CLEAR(oper2);
	break;
      case IN_ROT:
	CHECKOP(3);
	oper1 = POP(); oper2 = POP(); temp3 = *(oper3 = POP());
	arg[(*top)++] = *oper2;
	arg[(*top)++] = *oper1;
	arg[(*top)++] = temp3;
	break;
      case IN_ROTATE:
	CHECKOP(1);
	oper1 = POP();
	if (oper1->type != PROG_INTEGER)
	  abort_interp("Invalid argument type.");
	tmp = oper1->data.number;  /* Depth on stack */
	CHECKOP(abs(tmp));
	if (tmp > 0) {
	  temp2 = arg[*top - tmp];
	  for (; tmp > 0; tmp--)
	    arg[*top - tmp] = arg[*top - tmp + 1];
	  arg[*top - 1] = temp2;
	} else if (tmp < 0) {
	  temp2 = arg[*top - 1];
	  for (tmp = -1; tmp > oper1->data.number; tmp--)
	    arg[*top + tmp] = arg[*top + tmp - 1];
	  arg[*top + tmp] = temp2;
	}
	CLEAR(oper1);
	break;
      case IN_GETLINK:
	CHECKOP(1);
	oper1 = POP();
	if (!valid_object(oper1))
	  abort_interp("Invalid object.");
	if (Typeof(oper1->data.objref) == TYPE_PROGRAM)
	  abort_interp("Illegal object referenced.");
	switch (Typeof(oper1->data.objref)) {
	case TYPE_EXIT:
	  ref = (DBFETCH(oper1->data.objref)->sp.exit.ndest) ?
	    (DBFETCH(oper1->data.objref)->sp.exit.dest)[0] : NOTHING;
	  break;
	case TYPE_PLAYER:
	  ref = DBFETCH(oper1->data.objref)->link;
	  break;
	case TYPE_THING:
	  ref = DBFETCH(oper1->data.objref)->link;
	  break;
	case TYPE_ROOM:
	  ref = DBFETCH(oper1->data.objref)->link;
	  break;
	default:
	  ref = NOTHING;
	  break;
	}
	CLEAR(oper1);
	push(arg, top, PROG_OBJECT, MIPSCAST &ref);
	break;
      case IN_PRONOUN_SUB:
	CHECKOP(2);
	oper1 = POP(); oper2 = POP();  /* oper1 is a string, oper2 a dbref */
	if (!valid_object(oper2))
	  abort_interp("Invalid argument (1)");
	if (oper1->type != PROG_STRING)
	  abort_interp("Invalid argument (2)");
	CLEAR(oper1); CLEAR(oper2);
	push(arg, top, PROG_STRING, (oper1->data.string) ?
	     MIPSCAST
	     alloc_prog_string(pronoun_substitute(oper2->data.objref,
						  oper1->data.string->data,
						  uid))
	     : MIPSCAST alloc_prog_string(""));
	break;
      case IN_PRONOUN_EVAL:
	CHECKOP(3);
	oper3 = POP();
	oper1 = POP(); oper2 = POP();  /* oper1 is a string, oper2 a dbref */
	if (!valid_object(oper2))
	  abort_interp("Invalid argument (1)");
	if (oper1->type != PROG_STRING)
	  abort_interp("Invalid argument (2)");
	if(!valid_object(oper3))
	  abort_interp("Invalid argument (3)");
	if (!wizard && !permissions(uid, oper3->data.objref))
	  abort_interp("Permission denied.");
	CLEAR(oper1); CLEAR(oper2); CLEAR(oper3);
	push(arg, top, PROG_STRING, (oper1->data.string) ?
	     MIPSCAST
	     alloc_prog_string(pronoun_substitute(oper2->data.objref,
						  oper1->data.string->data,
						  oper3->data.objref))
	     : MIPSCAST alloc_prog_string(""));
	break;
      case IN_HALT:
	CHECKOP(1);
	oper1 = POP();
	if(!valid_object(oper1))
	  abort_interp("Invalid object");
	if(Typeof(oper1->data.objref) != TYPE_THING
	   && Typeof(oper1->data.objref) != TYPE_PLAYER)
	  abort_interp("Invalid object type.");
	if(!permissions(uid, oper1->data.objref))
	  abort_interp("Permission denied.");
	halt_long(oper1->data.objref);
	{
	  char buf[BUFFER_LEN];
	  sprintf(buf, "%s halted.",unparse_object(OWNER(oper1->data.objref),
						   oper1->data.objref));
	  notify(OWNER(oper1->data.objref), buf);
	}
	CLEAR(oper1);
	break;
      case IN_TRIGOBJ:
	CHECKOP(2);
	oper1 = POP(); oper2 = POP();
	if(oper2->type != PROG_OBJECT)
	  abort_interp("Non-dbref argument (1)");
	if(!valid_object(oper2))
	  abort_interp("Invalid object.");
	if(oper1->type!=PROG_STRING)
	  abort_interp("Non-string argument (2)");
	if(!wizard && !permissions(OWNER(program), oper2->data.objref))
	  abort_interp("Permission denied.");
	if(Typeof(oper2->data.objref) != TYPE_PLAYER &&
	   Typeof(oper2->data.objref) != TYPE_THING)
	  abort_interp("Invalid object type.");
	if(God(oper2->data.objref))
	  abort_interp("Permission denied.");
	if(!oper1->data.string || !oper1->data.string->data)
	  abort_interp("Empty string argument.");
	trigobj(oper2->data.objref,oper1->data.string->data,player);
	CLEAR(oper1); CLEAR(oper2);
	break;
      case IN_SYSTIME:
	if ((*top) >= STACK_SIZE) abort_interp("Stack overflow.");
	{
	  time_t lt;
	  lt = time((time_t *) 0);
	  push(arg, top, PROG_INTEGER, MIPSCAST &lt);
	}
	break;
      case IN_TIME:
	if ((*top) + 2 >= STACK_SIZE) abort_interp("Stack overflow.");
	{
	  time_t lt;
	  struct tm *delta;
	  lt = time((time_t *) 0);
	  delta = localtime(&lt);
	  result = delta -> tm_sec;
	  push(arg, top, PROG_INTEGER, MIPSCAST &result);
	  result = delta -> tm_min;
	  push(arg, top, PROG_INTEGER, MIPSCAST &result);
	  result = delta -> tm_hour;
	  push(arg, top, PROG_INTEGER, MIPSCAST &result);
	}
	break;
      case IN_STRFTIME:
	CHECKOP(2);
	oper1 = POP(); oper2 = POP();
	if(oper1->type != PROG_STRING)
	  abort_interp("Non-string argument (2)");
	if(oper2->type != PROG_INTEGER)
	  abort_interp("Non-integer argument (1)");
	muck_strftime(buf, (size_t) BUFFER_LEN, oper1->data.string->data,
		      (time_t) oper2->data.number);
	CLEAR(oper1); CLEAR(oper2);
	push(arg, top, PROG_STRING, MIPSCAST alloc_prog_string(buf));
	break;
      case IN_ONLINE:
	{
	  struct descriptor_data *d;

	  result = 0;
	  for(d = descriptor_list; d; d = d->next) {
	    if(d->connected) {
	      ref = d->player;
	      if(*top >= STACK_SIZE) abort_interp("Stack Overflow.");
	      push(arg, top, PROG_OBJECT, MIPSCAST &ref);
	      result++;
	    }
	  }
	  if(*top >= STACK_SIZE) abort_interp("Stack Overflow.");
	  push(arg, top, PROG_INTEGER, MIPSCAST &result);
	}
	break;
      case IN_AWAKEP:
	CHECKOP(1);
	oper1 = POP();
	if (oper1->type != PROG_OBJECT)
	  abort_interp("Invalid argument type.");
	result = check_awake(oper1->data.objref);
	CLEAR(oper1);
	push(arg, top, PROG_INTEGER, MIPSCAST &result);
	break;
      case IN_CONNECTIONS:
      case IN_CONCOUNT:
	{
	  struct descriptor_data *d;
	  
	  result = 0;
	  for(d = descriptor_list; d; d = d -> next)
	    {
	      if(d->connected)
		{
		  if(pc -> data.number == IN_CONNECTIONS) {
		    if(*top >= STACK_SIZE) abort_interp("Stack Overflow.");
		    push(arg, top, PROG_CON, MIPSCAST d);
		  }
		  result++;
		}
	    }
	  if(*top >= STACK_SIZE) abort_interp("Stack Overflow.");
	  push(arg, top, PROG_INTEGER, MIPSCAST &result);
	}
	break;
      case IN_CONBOOT:
	CHECKOP(1);
	oper1 = POP();
	if (oper1 -> type != PROG_CON)
	  abort_interp("Non-descriptor argument.");
	if (!wizard)
	  abort_interp("Permission denied.");
#ifdef GOD_PRIV
	if (is_god(oper1->data.con -> player))
	  abort_interp("Permission denied.");
#else
	if (Wizard(oper1->data.con -> player))
	  abort_interp("Permission denied.");
#endif /* GOD_PRIV */
	process_output(oper1->data.con);
	shutdownsock(oper1->data.con);
	CLEAR(oper1);
	break;
      case IN_CONDBREF:
      case IN_CONHOST:
      case IN_CONIDLE:
      case IN_CONTIME:
	CHECKOP(1);
	oper1 = POP();
	if (oper1-> type != PROG_CON)
	  abort_interp("Non-descriptor argument.");
	if(pc -> data.number == IN_CONDBREF) {
	  ref = oper1->data.con -> player;
	  push(arg, top, PROG_OBJECT, MIPSCAST &ref);
	}
	else if(pc -> data.number == IN_CONIDLE) {
	  result = time((long *) 0) - (oper1->data.con -> last_time);
	  push(arg, top, PROG_INTEGER, MIPSCAST &result);
	}
	else if(pc -> data.number == IN_CONTIME) {
	  result = oper1->data.con -> connected_at;
	  push(arg, top, PROG_INTEGER, MIPSCAST &result);
	}
	else { /* IN_CONHOST */
	  if(!wizard)
	    abort_interp("Permission denied.");
	  sprintf(buf, "%s", oper1->data.con -> hostname);
	  push(arg, top, PROG_STRING, MIPSCAST alloc_prog_string(buf));
	}
	break;
      case IN_PROG:
	if ((*top) >= STACK_SIZE) abort_interp("Stack Overflow.");
	ref = program;
	push(arg, top, PROG_OBJECT, MIPSCAST &ref);
	break;
/* IN_SELF doesn't work right */
/*      case IN_SELF:
	if ((*top) >= STACK_SIZE) abort_interp("Stack Overflow.");
	add = (sys[stop].data.call - 2) -> data.call;
	push(arg, top, PROG_ADD, MIPSCAST add);
	break;
	*/
      case IN_DEPTH:
	if ((*top) >= STACK_SIZE) abort_interp("Stack Overflow.");
	result = *top;
	push(arg, top, PROG_INTEGER, MIPSCAST &result);
	break;
      case IN_WILD_MATCH:
	CHECKOP(2);
	oper1=POP(); oper2=POP();
	if(oper1->type != PROG_STRING)
	  abort_interp("Non-string argument (2)");
	if(oper2->type != PROG_STRING)
	  abort_interp("Non-string argument (1)");
	if(!oper1->data.string || !oper1->data.string->data ||
	   !oper2->data.string || !oper2->data.string->data)
	  abort_interp("Empty string argument.");
	{
	  int tmp;
	  puts("About to wild match");
	  tmp = wild_match(oper1->data.string->data,
			   oper2->data.string->data);
	  puts("Done wild match");
	  CLEAR(oper1); CLEAR(oper2);
	  push(arg, top, PROG_INTEGER, MIPSCAST &tmp);
	  for(tmp = 0; tmp <= 9; tmp++)
	    if(wptr[tmp])
	      awptr[tmp] = alloc_string(wptr[tmp]);
	  /* Ok, we got the junk copied so that pronoun_sub can read it */
	}
	break;
      case IN_CHECKFUNC:
	CHECKOP(1); /* this does nothing for the momnet */
	/* oper1=POP();
	   if(oper1->type!=PROG_STRING)
	   abort_interp("Non string argument");
	   if(!oper1->data.string || !oper1->data.string->data)
	   push(arg,top,PROG_STRING,MIPSCAST alloc_prog_string(""));
	   else {
	   char *y;
	   y=check_arg(uncompress(oper1->data.string->data),player,global_cause);
	   if(y)
	   push(arg,top,PROG_STRING,MIPSCAST alloc_prog_string(y));
	   else
	   push(arg,top,PROG_STRING,MIPSCAST alloc_prog_string(""));
	   }
	   CLEAR(oper1); */
	break;
      default:
	abort_interp("Unrecognized primitive.");
	/*NOTREACHED*/
	break;
      }
    break;
  }
}