lpmoo-1.2/etc/
lpmoo-1.2/mudlib/
lpmoo-1.2/mudlib/etc/
lpmoo-1.2/mudlib/include/
lpmoo-1.2/mudlib/include/moo/
lpmoo-1.2/mudlib/lpc/
lpmoo-1.2/mudlib/std/auto/
lpmoo-1.2/mudlib/std/bfuns/
/*
 * NAME:	global.c
 * DESCRIPTION:	global object for all MOO objects
 */

# define DEBUG  0

inherit regexp	"/std/cache";
inherit	core	"/std/core";
inherit		"/std/string";
inherit		"/std/data";

# include <objects.h>
# include <moo/data.h>
# include <moo/config.h>
# include <moo/perms.h>
# include <moo/command.h>
# include <moo/verb.h>
# include <moo/verbinfo.h>
# include <dgd/limits.h>
# include <dgd/status.h>

mapping	connections;	/* all user connections, indexed by MOO player obj */
mapping	pfds;		/* all user connections, indexed by pseudo-fd */
mapping	unlogged_in;	/* all unlogged-in connections, indexed by #-num */

mapping	tasks;		/* all forked/suspended tasks, indexed by MOO taskid */
			/* ({ vobj, dgd_taskid, info, start_time, trace }) */

object *players;	/* all MOO player objects */

int	max_object;	/* max_object() */
int    *recycled;	/* list of recycle()'d objects */
object	renumber_obj;	/* an object being renumbered */

int	next_task_id;	/* the next MOO taskid */
int	next_ulog_id;	/* #-num of the next accepted connection */

string *error_names;	/* list of all MOO error names */
string *error_descs;	/* list of all MOO error descriptions */
string *prep_names;	/* list of all MOO command prepositions */
string *prep_list;	/* parsed list of MOO prepositions */
string *vs_names;	/* ({ "none", "any", "this" }) */

mapping	prep_map;	/* preposition codes, indexed by string preposition */
mapping	vs_map;		/* verb spec codes, indexed by vs_names string */

int	unum;		/* unique number */
int	cp_handle;	/* checkpoint call_out() taskid */

int	stack_size;	/* MOO value stack size */
int	max_depth;	/* MOO max verb recursion depth */
string	timezone;	/* the current timezone */
string	logfile;	/* name of the current log file */

static int generate_task_id(void);

/*
 * NAME:	create()
 * DESCRIPTION:	initialize the global object
 */
static
void create(void)
{
  regexp::create();
  core::create();

  tasks = ([ ]);

  connections = ([ ]);
  players     = ({ });
  unlogged_in = ([ ]);
  pfds        = ([ ]);

  next_ulog_id = -2;
  next_task_id = generate_task_id();

  max_object = -1;
  recycled   = ({ });

  error_names = ERROR_NAMES;
  error_descs = ERROR_DESCS;

  vs_names = ({ "none", "any", "this" });
  vs_map   = ([ "none" : VS_NONE, "any" : VS_ANY, "this" : VS_THIS ]);

  logfile = CONFIG->query(CF_LOG_FILE);
}

/*
 * NAME:	get_max_object()
 * DESCRIPTION:	return the highest numbered object number
 */
int get_max_object(void)
{
  return max_object;
}

/*
 * NAME:	generate_task_id()
 * DESCRIPTION:	return a unique, random task id
 */
static
int generate_task_id(void)
{
  int task_id;

  do
    {
      task_id = random(INT_MAX);
    }
  while (tasks[task_id]);

  return task_id;
}

/*
 * NAME:	take_task()
 * DESCRIPTION:	return a task id for a new task
 */
int take_task(void)
{
  int task_id;

  task_id      = next_task_id;
  next_task_id = generate_task_id();

  return task_id;
}

/*
 * NAME:	register_task()
 * DESCRIPTION:	recognize a new MOO task associated with a DGD call_out()
 */
void register_task(mixed *info, int delay, object ob, int dgd_task_id)
{
  int start_time;

  if (delay == -1)
    start_time = -1;
  else if (delay == 0)
    start_time = time() + 1;
  else
    start_time = time() + delay;

  tasks[info[I_TASKID]] = ({ ob, dgd_task_id, info, start_time });
}

/*
 * NAME:	task_started()
 * DESCRIPTION:	a DGD call_out() has begun execution
 */
void task_started(int moo_task_id)
{
  tasks[moo_task_id] = 0;
}

/*
 * NAME:	remove_refs()
 * DESCRIPTION:	delete references to verbs on the MOO call stack
 */
void remove_refs(mixed *info)
{
  while (info)
    {
      object ob;

      if (ob = info[I_VERBOBJ])
	ob->del();

      info = info[I_LINK];
    }
}

/*
 * NAME:	kill_task()
 * DESCRIPTION:	remove a task from the task queue and abort execution of it
 */
int kill_task(int moo_task_id, mixed *info)
{
  mixed *task_info;

  if (! (task_info = tasks[moo_task_id]))
    return E_INVARG;

  if (! task_info[0])	/* lingering destructed verb/user task */
    {
      tasks[moo_task_id] = 0;
      return E_INVARG;
    }

  if (! WIZARDP(info) && PROGRAMMER(info) != PROGRAMMER(task_info[2]))
    return E_PERM;

  tasks[moo_task_id] = 0;

  if (task_info[3] == -1)
    task_info[0]->unseed();
  else
    task_info[0]->abort_task(task_info[1]);

  remove_refs(task_info[2]);

  return E_NONE;
}

/*
 * NAME:	task_info()
 * DESCRIPTION:	retrieve the call stack for a pending task
 */
mixed *task_info(int moo_task_id)
{
  mixed *info;

  if (! (info = tasks[moo_task_id]))
    return 0;

  if (! info[0])  /* lingering destructed verb/user task */
    {
      tasks[moo_task_id] = 0;
      return 0;
    }

  return info[2];
}

/*
 * NAME:	get_queued_tasks()
 * DESCRIPTION:	return a list of queued task information
 */
MOOVAL get_queued_tasks(mixed *info)
{
  MOOVAL *qt_info;
  int *task_ids, i, j, sz, ticks, programmer, wizard;
  mixed *task_info;

  programmer = PROGRAMMER(info);
  wizard     = WIZARDP(info);

  qt_info    = allocate(sz = map_sizeof(tasks));
  task_ids   = map_indices(tasks);
  task_info  = map_values(tasks);

  ticks      = CONFIG->query(CF_BG_TICKS);

  for (i = 0, j = -1; i < sz; ++i)
    {
      mixed *frame;

      if (! task_info[i][0])	/* lingering destructed verb/user task */
	{
	  tasks[task_ids[i]] = 0;
	  continue;
	}

      frame = task_info[i][2];

      if (wizard || programmer == PROGRAMMER(frame))
	{
	  int verbloc;
	  object host;
	  string verbname;

	  host     = frame[I_HOSTOBJ];
	  verbloc  = host ? OBJNUM(host) : -1;
	  verbname = frame[I_NAME];
	  if (verbname == "")
	    verbname = "Input to EVAL";

	  qt_info[++j] = LST( ({
	    NUM(task_ids[i]),		/* task-id */
	    NUM(task_info[i][3]),	/* start-time */
	    NUM(0),			/* clock-id */
	    NUM(ticks),			/* ticks */
	    OBJ(frame[I_PRGMR]),	/* programmer */
	    OBJ(verbloc),		/* verb-loc */
	    STR(verbname),		/* verb-name */
	    NUM(frame[I_LINENO]),	/* line */
	    OBJ(frame[I_THIS]),		/* this */
	  }) );
	}
    }

  return LST(qt_info[.. j]);
}

/*
 * NAME:	error_name()
 * DESCRIPTION:	return the error name for an error num
 */
string error_name(int err)
{
  return error_names[err];
}

/*
 * NAME:	error_desc()
 * DESCRIPTION:	return the error description for an error num
 */
string error_desc(int err)
{
  return error_descs[err];
}

/*
 * NAME:	prep_name()
 * DESCRIPTION:	return the (full) name of a preposition
 */
string prep_name(int prep)
{
  return prep < 0 ? vs_names[-prep - 1] : prep_names[prep - 1];
}

/*
 * NAME:	prep_code()
 * DESCRIPTION:	return the preposition code for a string, or 0
 */
int prep_code(string prep)
{
  int code;
  string cruft;

  prep = tolower(prep);

  if (prep == "none" || prep == "any")
    return vs_map[prep];

  sscanf(prep, "%s/", prep);
  prep = implode(explode(prep, " ") - ({ "" }), " ");

  if ((code = prep_map[prep]) != 0 ||
      (sscanf(prep, "%d%s", code, cruft) == 2 && strlen(cruft) == 0 &&
       (++code, code > 0 && code <= sizeof(prep_names))))
    return code;

  return 0;
}

/*
 * NAME:	vs_name()
 * DESCRIPTION:	return the name for a object specifier
 */
string vs_name(int vs)
{
  return vs_names[-vs - 1];
}

/*
 * NAME:	vs_code()
 * DESCRIPTION:	return the code for an object specifier
 */
int vs_code(string vs)
{
  return vs_map[tolower(vs)];
}

/*
 * NAME:	match_preposition()
 * DESCRIPTION:	used by mooconn to parse a command line into dobj, prep, iobj
 */
string *match_preposition(string *args)
{
  int i, sz, start, len, slen;
  string try, lower;

  sz = sizeof(prep_list);
  for (start = 1, len = sizeof(args); start < len; ++start)
    {
      try   = implode(args[start ..], " ");
      lower = tolower(try) + " ";

      for (i = 0; i < sz; ++i)
	if (sscanf(lower, prep_list[i] + " %*s") == 1)
	  {
	    slen = strlen(prep_list[i]);
	    return ({ implode(args[1 .. start - 1], " "),
		      try[.. slen - 1],
		      (slen >= strlen(try) ? "" : try[slen + 1 ..]) });
	  }
    }

  return ({ implode(args[1 ..], " "), "", "" });
}

/*
 * NAME:	fetch_object()
 * DESCRIPTION:	called by DRIVER to return a new (or renumbered) MOO object
 */
object fetch_object(void)
{
  return renumber_obj ? renumber_obj : clone_object(OBJECT);
}

/*
 * NAME:	create_object()
 * DESCRIPTION:	return a new MOO object reference
 */
object create_object(object parent, int owner)
{
  object ob;
  int id;

  ob = load_object(MOOOBJ_NAME(id = ++max_object));
  ob->init(parent, owner == -1 ? id : owner);

  return ob;
}

/*
 * NAME:	add_recycled()
 * DESCRIPTION:	remember an objnum as recycled
 */
private
void add_recycled(int num)
{
  int i, sz;

  for (i = 0, sz = sizeof(recycled); i < sz; ++i)
    if (recycled[i] > num)
      break;

  recycled = recycled[.. i - 1] + ({ num }) + recycled[i ..];
}

/*
 * NAME:	bootstrap_max_object()
 * DESCRIPTION:	called by dbloader to bootstrap the value of max_object
 */
void bootstrap_max_object(int num)
{
  max_object = num;
}

/*
 * NAME:	bootstrap_recycled()
 * DESCRIPTION:	called by dbloader to bootstrap a recycled object
 */
void bootstrap_recycled(int id)
{
  add_recycled(id);
}

/*
 * NAME:	renumber()
 * DESCRIPTION:	change the objnum of an object
 */
int renumber(object ob)
{
  int oldnum, newnum, i;

  oldnum = OBJNUM(ob);

  if (! sizeof(recycled) || recycled[0] > oldnum)
    return oldnum;

  newnum = recycled[0];
  recycled = recycled[1 ..];

  renumber_obj = ob;
  load_object(MOOOBJ_NAME(newnum));
  renumber_obj = 0;

  add_recycled(oldnum);

  if (ob->is_owner())
    {
      DRIVER->log("Non-optimized renumber(#" + (string) oldnum +
		  ") => #" + (string) newnum + "...");

      for (i = max_object; i--; )
	if (ob = MOOOBJ(i))
	  ob->renumber_owner(oldnum, newnum);

      DRIVER->log("Renumber complete");
    }
  else
    {
      DRIVER->log("Optimized renumber(#" + (string) oldnum +
		  ") => #" + (string) newnum);
      ob->renumber_owner(oldnum, newnum);
    }

  return newnum;
}

/*
 * NAME:	reset_max_object()
 * DESCRIPTION:	make max_object be the highest existing objnum
 */
void reset_max_object(void)
{
  int i;

  for (i = sizeof(recycled); i-- && recycled[i] == max_object; --max_object);
  recycled = recycled[.. i];
}

/*
 * NAME:	log_msg()
 * DESCRIPTION:	write a log message to the server log
 */
void log_msg(string msg)
{
  write_file(logfile, ctime(time())[4 .. 18] + ": " + msg + "\n");
}

/*
 * NAME:	zero()
 * DESCRIPTION:	return #0, which hopefully exists
 */
object zero(void)
{
  object zero;

  if (! (zero = MOOOBJ(0)))
    error("Missing MOO object #0");

  return zero;
}

/*
 * NAME:	arrange_checkpoint()
 * DESCRIPTION:	read #0.dump_interval and arrange a checkpoint
 */
private
void arrange_checkpoint(void)
{
  MOOVAL interval;
  int delay;

  interval = zero()->safe_get_property("dump_interval");
  delay    = NUMP(interval) ? NUMVAL(interval) : 3600;
  if (delay < 60 && delay >= 0)
    delay = 3600;

  if (cp_handle)
    remove_call_out(cp_handle);

  if (delay > 0)
    cp_handle = call_out("checkpoint", delay, JS_INIT, 0);
  else
    cp_handle = 0;
}

/*
 * NAME:	checkpoint_finished()
 * DESCRIPTION:	call #0:checkpoint_finished (called from call_out)
 */
void checkpoint_finished(JS_PROTO, int success)
{
  object zero;

  JS_BEGIN;

  zero = zero();

  JS_PREP(1);
  RET = zero->call_verb(JS_DATA(1), "checkpoint_finished",
			server_vars(-1, zero, "checkpoint_finished",
				    0, NUM(success)));
  JS_END;

  JS_END;
}

/*
 * NAME:	text_dump()
 * DESCRIPTION:	make a text dump of the database (called from call_out)
 */
void text_dump(JS_PROTO)
{
  string file;

  JS_BEGIN;

  file = CONFIG->query(CF_CHECKPOINT_FILE);

  log_msg("CHECKPOINTING on " + file);
  call_out("checkpoint_finished", 1, JS_INIT, 1);

  rename_file(file, file + ".old");
  save_db(file);

  JS_END;
}

/*
 * NAME:	checkpoint()
 * DESCRIPTION:	initiate a checkpoint (called from call_out)
 */
void checkpoint(JS_PROTO, int text)
{
  object zero;

  JS_BEGIN;

  arrange_checkpoint();

  zero = zero();

  JS_PREP(1);
  catch(RET = zero->call_verb(JS_DATA(1), "checkpoint_started",
			      server_vars(-1, zero, "checkpoint_started",
					  0, NUM(text))));

  if (text)
    call_out("text_dump", 0, JS_INIT);
  else
    {
      log_msg("CHECKPOINTING on DGD binary state file");
      DRIVER->checkpoint();
      call_out("checkpoint_finished", 1, JS_INIT, 1);
    }

  JS_END;

  /* do nothing upon resuming a suspended task */

  JS_END;
}

/*
 * NAME:	dump_database()
 * DESCRIPTION:	called by bfun to initiate a checkpoint
 */
void dump_database(int text)
{
  if (cp_handle)
    remove_call_out(cp_handle);

  cp_handle = call_out("checkpoint", 0, JS_INIT, text);
}

/*
 * NAME:	init()
 * DESCRIPTION:	called by restart() to initialize the database
 */
void init(JS_PROTO)
{
  object zero;

  JS_BEGIN;

  zero = zero();

  JS_PREP(1);
  catch(RET = zero->call_verb(JS_DATA(1), "server_started",
			      server_vars(-1, zero, "server_started")));
  arrange_checkpoint();
  JS_END;

  /* do nothing upon resuming a suspended task */

  JS_END;
}

/*
 * NAME:	init_preps()
 * DESCRIPTION:	initialize the list of prepositions
 */
private
void init_preps(string *preps)
{
  int i;

  prep_names = preps;
  prep_list  = explode(implode(prep_names, "/"), "/");
  prep_map   = ([ ]);

  for (i = sizeof(preps); i > 0; --i)
    {
      int j;
      string *split;

      split = explode(preps[i - 1], "/");
      for (j = sizeof(split) - 1; j >= 0; --j)
	prep_map[split[j]] = i;
    }
}

/*
 * NAME:	restart()
 * DESCRIPTION:	perform actions at MOO restart
 */
void restart(int shutdown_time)
{
  string str;
  int i;
  object *conns;
  mixed *task_list;

  logfile = CONFIG->query(CF_LOG_FILE);

  rename_file(logfile, logfile + ".old");

  str = ctime(time());
  write_file(logfile, str[.. 19] + CONFIG->query(CF_TIMEZONE) +
	     " " + str[20 ..] + ": RESTARTED\n");

  /* log file silliness */
  log_msg("STARTING: Version " + CONFIG->query(CF_MOO_VERSION) +
	  " of the LambdaMOO server simulation");
  log_msg("          (DGD version " + status()[ST_VERSION] + ")");
  log_msg("          (LPMOO version " + CONFIG->query(CF_LPMOO_VERSION) + ")");
  log_msg("          (Using protocols defined by DGD)");
  log_msg("          (Task timeouts measured in DGD terms)");

  log_msg("LOADING: DGD binary state dump already loaded");
  log_msg("VALIDATING nothing optimistically ... finished.");
  log_msg("LOADING: using state dump, will dump new database there also");

  /* discard any old connection objects */
  conns = map_values(connections) + map_values(unlogged_in);
  for (i = sizeof(conns); i--; )
    if (conns[i])
      destruct_object(conns[i]);

  connections  = ([ ]);
  unlogged_in  = ([ ]);
  next_ulog_id = -2;

  /* reset the regexp cache */
  regexp::reset(CONFIG->query(CF_PATTERN_CACHE));

  /* query configuration parameters */
  stack_size = CONFIG->query(CF_STACK_SIZE);
  max_depth  = CONFIG->query(CF_MAX_VERB_DEPTH);
  timezone   = CONFIG->query(CF_TIMEZONE);

  /* initialize the preposition list */
  init_preps(CONFIG->query(CF_PREPOSITIONS));

  /* fix task starting times */
  for (i = sizeof(task_list = map_values(tasks)); i--; )
    task_list[i][3] += time() - shutdown_time;

  /* stop checkpoints */
  if (cp_handle)
    {
      remove_call_out(cp_handle);
      cp_handle = 0;
    }

  /* start The Very First Task */
  init(JS_INIT);
}

/*
 * NAME:	user_description()
 * DESCRIPTION:	return a description string of a user
 */
string user_description(int programmer)
{
  object ob;

  if (! (ob = MOOOBJ(programmer)))
    return "#" + programmer;
  else
    return ob->get_name() + " (#" + programmer + ")";
}

/*
 * NAME:	conn_description()
 * DESCRIPTION:	return a description string of a connection
 */
string conn_description(object conn, int id)
{
  return user_description(id) + " on " + conn->get_connection_name();
}

/*
 * NAME:	recycle()
 * DESCRIPTION:	irrevocably destroy an object (called from destroyee)
 */
void recycle(object obj)
{
  int *task_ids, i;
  mixed *task_info;
  object conn;

  add_recycled(OBJNUM(obj));

  /* clean up any dangling tasks */

  task_ids  = map_indices(tasks);
  task_info = map_values(tasks);

  for (i = sizeof(task_ids); i--; )
    if (! task_info[i][0])
      tasks[task_ids[i]] = 0;

  /* clean up connections and players arrays */

  if (conn = connections[obj])
    {
      log_msg("RECYCLED: " + conn_description(conn, OBJNUM(obj)));
      conn->boot("*** Recycled ***");
    }

  players -= ({ obj });
}

/*
 * NAME:	all_players()
 * DESCRIPTION:	return a list of all player objects
 */
object *all_players(void)
{
  return players;
}

/*
 * NAME:	connected_players()
 * DESCRIPTION:	return a list of connected player objects
 */
object *connected_players(void)
{
  return map_indices(connections);
}

/*
 * NAME:	get_connection_obj()
 * DESCRIPTION:	return a corresponding connection object for a player, or 0
 */
object get_connection_obj(object player)
{
  return connections[player];
}

/*
 * NAME:	do_boot_player()
 * DESCRIPTION:	call verbs after booting a player
 */
void do_boot_player(JS_PROTO, object listener, int id)
{
  JS_BEGIN;

  if (! listener)
    return;

  JS_PREP(1);
  RET = listener->call_verb(JS_DATA(1), "user_disconnected",
			    server_vars(id, listener, "user_disconnected",
					0, OBJ(id)));
  JS_END;

  JS_END;
}

/*
 * NAME:	boot_player()
 * DESCRIPTION:	terminate a user's connection
 */
void boot_player(int id)
{
  object player, conn;

  if (player = MOOOBJ(id))
    conn = connections[player];
  else
    conn = unlogged_in[id];

  if (player && conn)
    call_out("do_boot_player", 0, JS_INIT, conn->get_listener(), id);

  if (conn)
    {
      log_msg("DISCONNECTED: " + conn_description(conn, id));
      conn->boot("*** Disconnected ***");
    }
}

/*
 * NAME:	set_player_flag()
 * DESCRIPTION:	add or remove a player object
 */
void set_player_flag(object ob, int flag)
{
  if (flag)
    {
      players += ({ ob });
      return;
    }

  players -= ({ ob });

  if (connections[ob])
    boot_player(OBJNUM(ob));
}

/*
 * NAME:	accept()
 * DESCRIPTION:	assign an unlogged_id to a new connection
 */
int accept(object conn, string msg)
{
  log_msg(msg + ": #" + next_ulog_id + " on " +
	  conn->get_connection_name());
  unlogged_in[next_ulog_id] = conn;

  return next_ulog_id--;
}

/*
 * NAME:	get_unlogged_in()
 * DESCRIPTION:	return a connection associated with an un-logged-in user
 */
object get_unlogged_in(int id)
{
  return unlogged_in[id];
}

/*
 * NAME:	assign_pfd()
 * DESCRIPTION:	allocate a pseudo fd for a connection
 */
int assign_pfd(object conn)
{
  int pfd;

  pfd = 6;
  while (pfds[pfd])
    ++pfd;

  pfds[pfd] = conn;
  return pfd;
}

/*
 * NAME:	connect()
 * DESCRIPTION:	a connection was established
 */
void connect(object conn, int id, object player)
{
  unlogged_in[id] = 0;

  id = OBJNUM(player);
  connections[player] = conn;

  log_msg("CONNECTED: " + conn_description(conn, id));
}

/*
 * NAME:	connect_lpc()
 * DESCRIPTION:	a connection was established to an LPC object
 */
void connect_lpc(object conn, int id, object lpc)
{
  log_msg("LPC CONNECTION: " + object_name(lpc) + ", " +
	  conn_description(conn, id));
}

/*
 * NAME:	redirect()
 * DESCRIPTION:	a connection was redirected
 */
void redirect(object conn, int id, object player)
{
  unlogged_in[id] = 0;

  id = OBJNUM(player);

  log_msg("REDIRECTED: " + user_description(id) + ", was " +
	  connections[player]->get_connection_name() + ", now " +
	  conn->get_connection_name());

  connections[player] = conn;
}

/*
 * NAME:	shutdown()
 * DESCRIPTION:	called to shutdown the server
 */
void shutdown(string message)
{
  object *conns;
  int i;

  for (i = sizeof(conns = map_values(connections)); i--; )
    conns[i]->boot("*** Shutting down: " + message + " ***");

  log_msg("SHUTDOWN: " + message);

  DRIVER->do_shutdown();
}

/*
 * NAME:	unique_number()
 * DESCRIPTION:	return an ever-increasing number
 */
int unique_number(void)
{
  return ++unum;
}

/*
 * NAME:	cache_miss()
 * DESCRIPTION:	return a value for the cache
 */
static
mixed cache_miss(string key)
{
  int insens;
  string pattern;

  sscanf(key, "%d*%s", insens, pattern);

  return regexp_compile(pattern, insens);
}

/*
 * NAME:	get_regexp()
 * DESCRIPTION:	return a compiled regexp pattern
 */
string *get_regexp(string pattern, int insens)
{
  return regexp::fetch((string) insens + "*" + pattern);
}

/*
 * NAME:	get_stack_size()
 * DESCRIPTION:	return the configured value stack size
 */
int get_stack_size(void)
{
  return stack_size;
}

/*
 * NAME:	get_max_depth()
 * DESCRIPTION:	return the configured max verb depth
 */
int get_max_depth(void)
{
  return max_depth;
}

/*
 * NAME:	get_timezone()
 * DESCRIPTION:	return the current configured timezone
 */
string get_timezone(void)
{
  return timezone;
}

/*
 * NAME:	traceback()
 * DESCRIPTION:	perform a MOO traceback (called from DRIVER)
 */
void traceback(mixed *info, string error)
{
  object user;
  int obj, this, first;
  string message, verb;

  if (! (user = connections[MOOOBJ(info[I_PLAYER])]) &&
      ! (user = this_user()))
    user = find_object(DRIVER);

  message = "";
  first = 1;
  while (info)
    {
      obj  = info[I_HOSTOBJ] ? OBJNUM(info[I_HOSTOBJ]) : -1;
      this = info[I_THIS];
      verb = strlen(info[I_NAME]) ? info[I_NAME] : "Input to EVAL";

      message += "#" + (string) obj + ":" + verb;
      if (this != obj)
	message += " (this == #" + (string) this + ")";
      message += ", line " + (string) info[I_LINENO];

      if (first)
	message += ":  " + error;

      user->notify(message);

      info    = info[I_LINK];
      message = "... called from ";
      first   = 0;
    }

  user->notify("(End of traceback)");
}

/*
 * NAME:	compile_verb()
 * DESCRIPTION:	return a verb object from an AST
 */
object compile_verb(mixed *ast, int save)
{
  string name;
  object verb;

  name = "/verb/" + (string) unique_number();

  remove_file(name + ".c");
  compiler->main(ast, name);

  verb = load_object(name);
  if (! DEBUG)
    remove_file(name + ".c");

  if (save)
    verb->set_source(unparser->main(ast));

  return verb;
}