pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file boolexp.c
 *
 * \brief Boolean expression parser.
 *
 * This code implements a parser for boolean expressions of the form
 * used in locks. Summary of parsing rules, lowest to highest precedence:
 * \verbatim
 * E -> T; E -> T | E                   (or)
 * T -> F; T -> F & T                   (and)
 * F -> !F;F -> A                       (not)
 * A -> @L; A -> I                      (indirect)
 * I -> =Identifier ; I -> C            (equality)
 * C -> +Identifier ; C -> O            (carry)
 * O -> $Identifier ; O -> L            (owner)
 * L -> (E); L -> eval/attr/flag lock   (parens, special atoms)
 * L -> E, L is an object name or dbref or #t* or #f*   (simple atoms)
 * \endverbatim
 *
 * Previously, the boolexp code just used a parse tree of the
 * boolexp. Now, it turns the parse tree into bytecode that can be
 * stored in the chunk manager. It probably also evaluates faster, but
 * no profiling has been done to support this claim. It certainly
 * involves less non-tail recursion.
 *
 * It's a three-stage process. First, the lock string is turned into a
 * parse tree. Second, the tree is walked and "assembler" instructions
 * are generated, including labels for jumps. Third, the "assembly" is
 * stepped through and bytecode emitted, with labeled jumps replaced
 * by distances that are offsets from the start of the
 * bytecode. Pretty standard stuff.
 *
 * Each bytecode instruction is 5 bytes long (1 byte opcode + 4 byte
 * int argument), and the minimum number of instructions in a compiled
 * boolexp is 2, for a minimum size of 10 bytes. Compare this to the
 * size of one parse-tree node, 16 bytes. Savings appear to be
 * substantial, especially with complex locks with lots of ors or
 * ands.
 *
 * Many lock keys have string arguments. The strings are standard
 * 0-terminated C strings stored in a section of the same string as
 * the bytecode instructions, starting right after the last
 * instruction. They're accessed by offset from the start of the
 * bytecode string. If the same string appears multiple times in the
 * lock, only one copy is actually present in the string section.
 *
 * The VM for the bytecode is a simple register-based one.  The
 * registers are R, the result register, set by test instructions and
 * a few others, and S, the string register, which holds the extra
 * string in the few tests that need two (A:B, A/B). There are
 * instructions for each lock key type. There's a few extra ones to
 * make decompiling back into a string dead easy. Nothing very
 * complex.
 *
 * Future directions?
 * \verbatim
 * [Development] Raevnos is tempted in passing to re-write the boolexp parser in
 * lex and yacc.
 * [Development] Brazil laughs.
 * [Development] Brazil says, "That might not be a bad idea."
 * [Development] Raevnos has redone everything else in boolexp.c in Penn, so why
 * not? :)
 * [Development] Raevnos says, "Using the justification that it's a lot easier to
 * expand the langage by adding new key types that way."
 * \endverbatim
 *
 * So now you know who to blame if that particular item appears in a
 * changelog for Penn or MUX.
 *
 * On a more serious note, a) #1234 is equivalent to b)
 * =#1234|+#1234. Detecting b and turning it into a, or vis versa,
 * would be easy to do. a is a common key, but turning it into b gets
 * rid of a test instruction in the VM, at the cost of more
 * instructions in generated lock bytecode. CISC or RISC? :) It's also
 * easy to turn !!foo into foo, but nobody makes locks like that. Same
 * with !#true and !#false. Of possibly more interest is rearranging
 * the logic when ands, ors and nots are being used together. For
 * example, !a|!b can become !(a&b).
 *
 * There's more useful room for improvement in the lock
 * @warnings. Checking things like flag and power keys for valid flags
 * comes to mind.
 *
 */

#include "copyrite.h"
#include "config.h"

#include <ctype.h>
#include <string.h>

#include "conf.h"
#include "mushdb.h"
#include "match.h"
#include "externs.h"
#include "lock.h"
#include "parse.h"
#include "attrib.h"
#include "flags.h"
#include "dbdefs.h"
#include "log.h"
#include "extchat.h"
#include "strtree.h"
#include "confmagic.h"

#ifdef WIN32
#pragma warning( disable : 4761)	/* disable warning re conversion */
#endif

/* #define DEBUG_BYTECODE */

/** Parse tree node types */
typedef enum boolexp_type {
  BOOLEXP_AND, /**< A&B */
  BOOLEXP_OR, /**< A|B */
  BOOLEXP_NOT, /**< !A */
  BOOLEXP_CONST, /**< A */
  BOOLEXP_ATR, /**< A:B */
  BOOLEXP_IND, /**< @A/B */
  BOOLEXP_CARRY, /**< +A */
  BOOLEXP_IS, /**< =A */
  BOOLEXP_OWNER, /**< $A */
  BOOLEXP_EVAL,	/**< A/B */
  BOOLEXP_FLAG,	/**< A^B */
  BOOLEXP_BOOL /**< #true, #false */
} boolexp_type;

/** An attribute lock specification for the parse tree.
 * This structure is a piece of a boolexp that's used to store 
 * attribute locks (CANDO:1), eval locks (CANDO/1), and flag locks
 * FLAG^WIZARD.
 */
struct boolatr {
  const char *name;		/**< Name of attribute, flag, etc. to test */
  char text[BUFFER_LEN];	/**< Value to test against */
};

/** A boolean expression parse tree node.
 * Boolean expressions are most widely used in locks. This structure
 * is a general representation of the possible boolean expressions
 * that can be specified in MUSHcode. It's used internally by the lock
 * compiler.
 */
struct boolexp_node {
  /** Type of expression.
   * The type of expressio is one of the boolexp_type's, such as
   * and, or, not, constant, attribute, indirect, carry, is,
   * owner, eval, flag, etc.
   */
  boolexp_type type;
  dbref thing;			/**< An object, or a boolean val */
  /** The expression itself.
   * This union comprises the various possible types of data we
   * might need to represent any of the expression types.
   */
  union {
    /** And and or locks: combinations of boolexps.
     * This union member is used with and and or locks.
     */
    struct {
      struct boolexp_node *a;	/**< One boolean expression */
      struct boolexp_node *b;	/**< Another boolean expression */
    } sub;
    struct boolexp_node *n;		/**< Not locks: boolean expression to negate */
    struct boolatr *atr_lock;	/**< Atr, eval and flag locks */
    const char *ind_lock;	/**< Indirect locks */
  } data;
};


/** The opcodes supported by the boolexp virtual machine. */
typedef enum bvm_opcode {
  OP_JMPT, /**< Jump to ARG if R is true */
  OP_JMPF, /**< Jump to ARG if R is false */
  OP_TCONST, /**< Tests plain #ARG */
  OP_TATR, /**< Tests S:ARG */
  OP_TIND, /**< Tests @#ARG/S */
  OP_TCARRY, /**< Tests +#ARG */
  OP_TIS, /**< Tests =#ARG */
  OP_TOWNER, /**< Tests $#ARG */
  OP_TEVAL, /**< Tests S/ARG */
  OP_TFLAG, /**< Tests FLAG^ARG */
  OP_TTYPE, /**< Tests TYPE^ARG */
  OP_TPOWER, /**< Tests POWER^ARG */
  OP_TCHANNEL, /**< Tests CHANNEL^ARG */
  OP_TIP, /**< Tests IP^ARG */
  OP_THOSTNAME,	/**< Tests HOSTNAME^ARG */
  OP_TOBJID, /**< Tests OBJID^ARG */
  OP_LOADS, /**< Load ARG into S */
  OP_LOADR, /**< Load ARG into R */
  OP_NEGR,  /**< Negate R */
  OP_PAREN, /**< ARG = 0 for a (, ARG = 1 for a ) in decompiling */
  OP_LABEL, /**< A label. Not actually in compiled bytecode */
  OP_RET    /**< Stop evaluating bytecode */
} bvm_opcode;

/** The size of a single bytecode instruction. Probably 5 bytes
 * everywhere. */
#define INSN_LEN (1 + sizeof(int))

/** Information describing one VM instruction or label in the
 * intermediate "assembly" generated from a parse tree. The nodes are
 * part of a linked list.  */
struct bvm_asmnode {
  bvm_opcode op; /**< The opcode */
  int arg; /**< The arg value, or a label or string number */
  struct bvm_asmnode *next; /**< Pointer to the next node */
};

/** Information describing a string to emit in the string section of
 * the bytecode. The nodes are part of a linked list.  */
struct bvm_strnode {
  char *s; /**< The string */
  int len; /**< Its length */
  struct bvm_strnode *next; /**< Pointer to the next node */
};

/** A struct describing the complete assembly information needed to
 * generate bytecode */
struct bvm_asm {
  struct bvm_asmnode *head; /**< The start of the list of assembly instructions */
  struct bvm_asmnode *tail; /**< The end of the list */
  struct bvm_strnode *shead; /**< The start of the list of strings */
  struct bvm_strnode *stail; /**< The end of the list */
  int label; /**< The current label id to use */
  int strcount;	/**< The number of nodes in the string list */
};

/** The flag lock key (A^B) only allows a few values for A. This
 * struct and the the following table define the allowable ones. When
 * adding a new type here, a matching new bytecode instruction should
 * be added. */
struct flag_lock_types {
  const char *name; /**< The value of A */
  bvm_opcode op;  /**< The associated opcode */
};

/** What's allowed on the left-hand-side of LHS^RHS lock keys */
static struct flag_lock_types flag_locks[] = {
  {"FLAG", OP_TFLAG},
  {"POWER", OP_TPOWER},
  {"TYPE", OP_TTYPE},
  {"CHANNEL", OP_TCHANNEL},
  {"OBJID", OP_TOBJID},
  {"IP", OP_TIP},
  {"HOSTNAME", OP_THOSTNAME},
  {NULL, 0}
};

static char *
safe_get_bytecode(boolexp b)
  __attribute_malloc__;
    static char *get_bytecode(boolexp b, u_int_16 * storelen);
    static struct boolexp_node *alloc_bool(void) __attribute_malloc__;
    static struct boolatr *alloc_atr(const char *name,
				     const char *s) __attribute_malloc__;
    static void skip_whitespace(void);
    static void free_bool(struct boolexp_node *b);
    static struct boolexp_node *test_atr(char *s, char c);
    static struct boolexp_node *parse_boolexp_R(void);
    static struct boolexp_node *parse_boolexp_L(void);
    static struct boolexp_node *parse_boolexp_O(void);
    static struct boolexp_node *parse_boolexp_C(void);
    static struct boolexp_node *parse_boolexp_I(void);
    static struct boolexp_node *parse_boolexp_A(void);
    static struct boolexp_node *parse_boolexp_F(void);
    static struct boolexp_node *parse_boolexp_T(void);
    static struct boolexp_node *parse_boolexp_E(void);
    static int check_attrib_lock(dbref player, dbref target,
				 const char *atrname, const char *str);
    static void free_boolexp_node(struct boolexp_node *b);
    static int gen_label_id(struct bvm_asm *a);
    static void append_insn(struct bvm_asm *a, bvm_opcode op, int arg,
			    const char *s);
    static struct bvm_asm *generate_bvm_asm(struct boolexp_node *b)
 __attribute_malloc__;
    static void generate_bvm_asm1(struct bvm_asm *a, struct boolexp_node *b,
				  boolexp_type outer);
    static int pos_of_label(struct bvm_asm *a, int label);
    static int offset_to_string(struct bvm_asm *a, int c);
    static struct bvm_asmnode *insn_after_label(struct bvm_asm *a, int label);
    static void opt_thread_jumps(struct bvm_asm *a);
    static void optimize_bvm_asm(struct bvm_asm *a);
    static boolexp emit_bytecode(struct bvm_asm *a, int derefs);
    static void free_bvm_asm(struct bvm_asm *a);
#ifdef DEBUG_BYTECODE
    static int sizeof_boolexp_node(struct boolexp_node *b);
    static void print_bytecode(boolexp b);
#endif
    extern void complain
      (dbref player, dbref i, const char *name, const char *desc, ...)
  __attribute__ ((__format__(__printf__, 4, 5)));
    void check_lock(dbref player, dbref i, const char *name, boolexp be);
    int warning_lock_type(const boolexp l);


/** String tree of attribute names. Used in the parse tree. Might go
 * away as the trees aren't persistant any more. */
    extern StrTree atr_names;
/** String tree of lock names. Used in the parse tree. Might go away
 * as the trees aren't persistant any more. */
    extern StrTree lock_names;
/** Are we currently loading the db? If so, we avoid certain checks that
 * would create a circularity.
 */
    extern int loading_db;

/** Given a chunk id, return the bytecode for a boolexp.  
 * \param b the boolexp to retrieve.
 * \return a malloced copy of the bytecode.
 */
    static char *safe_get_bytecode(boolexp b)
{
  char *bytecode;
  u_int_16 len;

  len = chunk_len(b);
  bytecode = mush_malloc(len, "boolexp.bytecode");
  chunk_fetch(b, bytecode, len);
  return bytecode;
}

/** Given a chunk id, return the bytecode for a boolexp.  
 * \param b The boolexp to retrieve.
 * \return a static copy of the bytecode.
 */
static char *
get_bytecode(boolexp b, u_int_16 * storelen)
{
  static char bytecode[BUFFER_LEN * 2];
  u_int_16 len;

  len = chunk_fetch(b, bytecode, sizeof bytecode);
  if (storelen)
    *storelen = len;
  return bytecode;
}

/* Public functions */

/** Copy a boolexp.
 * This function makes a copy of a boolexp, allocating new memory for
 * the copy.
 * \param b a boolexp to copy.
 * \return an allocated copy of the boolexp.
 */
boolexp
dup_bool(boolexp b)
{
  boolexp r;
  char *bytecode;
  u_int_16 len = 0;

  if (b == TRUE_BOOLEXP)
    return TRUE_BOOLEXP;

  bytecode = get_bytecode(b, &len);

  r = chunk_create(bytecode, len, 1);

  return r;
}

/** Free a boolexp
 * This function deallocates a boolexp
 * \param b a boolexp to delete
 */
void
free_boolexp(boolexp b)
{
  if (b != TRUE_BOOLEXP)
    chunk_delete(b);
}

/** Determine the memory usage of a boolexp.
 * This function computes the total memory usage of a boolexp.
 * \param b boolexp to analyze.
 * \return size of boolexp in bytes.
 */
int
sizeof_boolexp(boolexp b)
{

  if (b == TRUE_BOOLEXP)
    return 0;
  else
    return chunk_len(b);
}

/** Evaluate a boolexp.
 * This is the main function to be called by other hardcode. It
 * determines whether a player can pass a boolexp lock on a given
 * object.
 * \param player the player trying to pass the lock.
 * \param b the boolexp to evaluate.
 * \param target the object with the lock.
 * \retval 0 player fails to pass lock.
 * \retval 1 player successfully passes lock.
 */
int
eval_boolexp(dbref player /* The player trying to pass */ ,
	     boolexp b /* The boolexp */ ,
	     dbref target /* The object with the lock */ )
{
  static int boolexp_recursion = 0;

  if (!GoodObject(player))
    return 0;

  if (boolexp_recursion > MAX_DEPTH) {
    notify(player, T("Too much recursion in lock!"));
    return 0;
  }
  if (b == TRUE_BOOLEXP) {
    return 1;
  } else {
    bvm_opcode op;
    int arg;
    ATTR *a;
    int r = 0;
    char *s = NULL, *bytecode, *pc;

    bytecode = pc = safe_get_bytecode(b);

    while (1) {
      op = (bvm_opcode) * pc;
      memcpy(&arg, pc + 1, sizeof arg);
      pc += INSN_LEN;
      switch (op) {
      case OP_RET:
	goto done;
      case OP_JMPT:
	if (r)
	  pc = bytecode + arg;
	break;
      case OP_JMPF:
	if (!r)
	  pc = bytecode + arg;
	break;
      case OP_LABEL:
      case OP_PAREN:
	break;
      case OP_LOADS:
	s = bytecode + arg;
	break;
      case OP_LOADR:
	r = arg;
	break;
      case OP_NEGR:
	r = !r;
	break;
      case OP_TCONST:
	r = (GoodObject(arg)
	     && !IsGarbage(arg)
	     && (arg == player || member(arg, Contents(player))));
	break;
      case OP_TIS:
	r = (GoodObject(arg)
	     && !IsGarbage(arg)
	     && arg == player);
	break;
      case OP_TCARRY:
	r = (GoodObject(arg)
	     && !IsGarbage(arg)
	     && member(arg, Contents(player)));
	break;
      case OP_TOWNER:
	r = (GoodObject(arg)
	     && !IsGarbage(arg)
	     && Owner(arg) == Owner(player));
	break;
      case OP_TIND:
	/* We only allow evaluation of indirect locks if target can run
	 * the lock on the referenced object.
	 */
	boolexp_recursion++;
	if (!GoodObject(arg) || IsGarbage(arg))
	  r = 0;
	else if (!Can_Read_Lock(target, arg, s))
	  r = 0;
	else
	  r = eval_boolexp(player, getlock(arg, s), arg);
	boolexp_recursion--;
	break;
      case OP_TATR:
	boolexp_recursion++;
	a = atr_get(player, s);
	if (!a || !Can_Read_Attr(target, player, a))
	  r = 0;
	else {
	  char tbuf[BUFFER_LEN];
	  strcpy(tbuf, atr_value(a));
	  r = local_wild_match(bytecode + arg, tbuf);
	}
	boolexp_recursion--;
	break;
      case OP_TEVAL:
	boolexp_recursion++;
	r = check_attrib_lock(player, target, s, bytecode + arg);
	boolexp_recursion--;
	break;
      case OP_TFLAG:
	/* Note that both fields of a boolattr struct are upper-cased */
	if (sees_flag("FLAG", target, player, bytecode + arg))
	  r = 1;
	else
	  r = 0;
	break;
      case OP_TPOWER:
	if (sees_flag("POWER", target, player, bytecode + arg))
	  r = 1;
	else
	  r = 0;
	break;
      case OP_TOBJID:
	{
	  dbref d;
	  d = parse_objid(bytecode + arg);
	  r = (player == d);
	  break;
	}
      case OP_TCHANNEL:
	{
	  CHAN *chan;
	  boolexp_recursion++;
	  find_channel(bytecode + arg, &chan, target);
	  r = chan && onchannel(player, chan);
	  boolexp_recursion--;
	}
	break;
      case OP_TIP:
	boolexp_recursion++;
	if (!Connected(Owner(player)))
	  r = 0;
	else {
	  /* We use the attribute for permission checks, but we
	   * do the actual boolexp itself with the least idle
	   * descriptor's ip address.
	   */
	  a = atr_get(Owner(player), "LASTIP");
	  if (!a || !Can_Read_Attr(target, player, a))
	    r = 0;
	  else {
	    char *p = least_idle_ip(Owner(player));
	    r = p ? quick_wild(bytecode + arg, p) : 0;
	  }
	}
	boolexp_recursion--;
	break;
      case OP_THOSTNAME:
	boolexp_recursion++;
	if (!Connected(Owner(player)))
	  r = 0;
	else {
	  /* See comment for OP_TIP */
	  a = atr_get(Owner(player), "LASTSITE");
	  if (!a || !Can_Read_Attr(target, player, a))
	    r = 0;
	  else {
	    char *p = least_idle_hostname(Owner(player));
	    r = p ? quick_wild(bytecode + arg, p) : 0;
	  }
	}
	boolexp_recursion--;
	break;
      case OP_TTYPE:
	switch (bytecode[arg]) {
	case 'R':
	  r = Typeof(player) == TYPE_ROOM;
	  break;
	case 'E':
	  r = Typeof(player) == TYPE_EXIT;
	  break;
	case 'T':
	  r = Typeof(player) == TYPE_THING;
	  break;
	case 'P':
	  r = Typeof(player) == TYPE_PLAYER;
	  break;
	}
	break;
      default:
	do_log(LT_ERR, 0, 0, "Bad boolexp opcode %d %d in object #%d",
	       op, arg, target);
	report();
	r = 0;
      }
    }
  done:
    mush_free(bytecode, "boolexp.bytecode");
    return r;
  }
}

/** Pretty-print object references for unparse_boolexp().
 * \param player the object seeing the decompiled lock.
 * \param thing the object referenced in the lock.
 * \param flag How to print thing.
 * \param buff The start of the output buffer.
 * \param bp Pointer to the current position in buff.
 * \return 0 on success, true on buffer overflow.
 */
static int
safe_boref(dbref player, dbref thing, enum u_b_f flag, char *buff, char **bp)
{
  switch (flag) {
  case UB_MEREF:
    if (player == thing)
      return safe_strl("me", 2, buff, bp);
    else
      return safe_dbref(thing, buff, bp);
  case UB_DBREF:
    return safe_dbref(thing, buff, bp);
  case UB_ALL:
  default:
    return safe_str(unparse_object(player, thing), buff, bp);
  }
}

/** Display a boolexp.
 * This function returns the textual representation of the boolexp.
 * \param player The object wanting the decompiled boolexp.
 * \param b The boolexp to decompile.
 * \param flag How to format objects in the result.
 * \return a static string with the decompiled boolexp.
 */
char *
unparse_boolexp(dbref player, boolexp b, enum u_b_f flag)
{
  static char boolexp_buf[BUFFER_LEN];
  char *buftop = boolexp_buf;
  char *bytecode = NULL;

  if (b == TRUE_BOOLEXP)
    safe_str("*UNLOCKED*", boolexp_buf, &buftop);
  else {
    bvm_opcode op;
    int arg;
    char *pc;
    char *s = NULL;

    bytecode = pc = get_bytecode(b, NULL);

    while (1) {
      op = (bvm_opcode) * pc;
      memcpy(&arg, pc + 1, sizeof arg);
      pc += INSN_LEN;
      /* Handle most negation cases */
      if (op != OP_RET && (bvm_opcode) * pc == OP_NEGR && op != OP_PAREN)
	safe_chr('!', boolexp_buf, &buftop);
      switch (op) {
      case OP_JMPT:
	safe_chr('|', boolexp_buf, &buftop);
	break;
      case OP_JMPF:
	safe_chr('&', boolexp_buf, &buftop);
	break;
      case OP_RET:
	goto done;
      case OP_LABEL:		/* Will never happen, but shuts up the compiler */
      case OP_NEGR:
	break;
      case OP_LOADS:
	s = bytecode + arg;
	break;
      case OP_LOADR:
	if (arg)
	  safe_str("#TRUE", boolexp_buf, &buftop);
	else
	  safe_str("#FALSE", boolexp_buf, &buftop);
	break;
      case OP_PAREN:
	if (arg == 0) {
	  int pstack = 1, parg;
	  char *tpc = pc;
	  while (1) {
	    if ((bvm_opcode) * tpc == OP_PAREN) {
	      memcpy(&parg, tpc + 1, sizeof parg);
	      if (parg)
		pstack--;
	      else
		pstack++;
	      if (pstack == 0) {
		tpc += INSN_LEN;
		break;
	      }
	    }
	    tpc += INSN_LEN;
	  }
	  if ((bvm_opcode) * tpc == OP_NEGR)
	    safe_strl("!(", 2, boolexp_buf, &buftop);
	  else
	    safe_chr('(', boolexp_buf, &buftop);
	} else if (arg == 1)
	  safe_chr(')', boolexp_buf, &buftop);
	break;
      case OP_TCONST:
	safe_boref(player, arg, flag, boolexp_buf, &buftop);
	break;
      case OP_TATR:
	safe_format(boolexp_buf, &buftop, "%s:%s", s, bytecode + arg);
	break;
      case OP_TIND:
	safe_chr(AT_TOKEN, boolexp_buf, &buftop);
	safe_boref(player, arg, flag, boolexp_buf, &buftop);
	safe_format(boolexp_buf, &buftop, "/%s", s);
	break;
      case OP_TCARRY:
	safe_chr(IN_TOKEN, boolexp_buf, &buftop);
	safe_boref(player, arg, flag, boolexp_buf, &buftop);
	break;
      case OP_TIS:
	safe_chr(IS_TOKEN, boolexp_buf, &buftop);
	safe_boref(player, arg, flag, boolexp_buf, &buftop);
	break;
      case OP_TOWNER:
	safe_chr(OWNER_TOKEN, boolexp_buf, &buftop);
	safe_boref(player, arg, flag, boolexp_buf, &buftop);
	break;
      case OP_TEVAL:
	safe_format(boolexp_buf, &buftop, "%s/%s", s, bytecode + arg);
	break;
      case OP_TFLAG:
	safe_format(boolexp_buf, &buftop, "FLAG^%s", bytecode + arg);
	break;
      case OP_TTYPE:
	safe_format(boolexp_buf, &buftop, "TYPE^%s", bytecode + arg);
	break;
      case OP_TPOWER:
	safe_format(boolexp_buf, &buftop, "POWER^%s", bytecode + arg);
	break;
      case OP_TOBJID:
	safe_format(boolexp_buf, &buftop, "OBJID^%s", bytecode + arg);
	break;
      case OP_TCHANNEL:
	safe_format(boolexp_buf, &buftop, "CHANNEL^%s", bytecode + arg);
	break;
      case OP_TIP:
	safe_format(boolexp_buf, &buftop, "IP^%s", bytecode + arg);
	break;
      case OP_THOSTNAME:
	safe_format(boolexp_buf, &buftop, "HOSTNAME^%s", bytecode + arg);
	break;
      }
    }
  }
done:
  *buftop++ = '\0';

  return boolexp_buf;
}

/* Parser and parse-tree related functions. If the parser returns NULL, you lose */
/** The source string for the lock we're parsing */
static const char *parsebuf;
/** The player from whose perspective we're parsing */
static dbref parse_player;
/** The name of the lock we're parsing */
static lock_type parse_ltype;

/** Allocate a boolatr for a parse tree node.
 * \param name the name of the attribute.
 * \param s a pattern to match against.
 * \return a newly allocated boolatr.
 */
static struct boolatr *
alloc_atr(const char *name, const char *s)
{
  struct boolatr *a;
  size_t len;

  if (s)
    len = strlen(s) + 1;
  else
    len = 1;

  a = (struct boolatr *)
    mush_malloc(sizeof(struct boolatr) - BUFFER_LEN + len, "boolatr");
  if (!a)
    return NULL;
  a->name = st_insert(strupper(name), &atr_names);
  if (!a->name) {
    mush_free(a, "boolatr");
    return NULL;
  }
  if (s)
    memcpy(a->text, s, len);
  else
    a->text[0] = '\0';
  return a;
}

/** Returns a new boolexp_node for the parse tree.
 * \return a new newly allocated boolexp_node.
 */
static struct boolexp_node *
alloc_bool(void)
{
  struct boolexp_node *b;

  b = mush_malloc(sizeof *b, "boolexp_node");

  b->data.sub.a = NULL;
  b->data.sub.b = NULL;
  b->thing = NOTHING;

  return b;
}

/** Frees a boolexp node.
 * \param b the boolexp_node to deallocate.
 */
static void
free_bool(struct boolexp_node *b)
{
  mush_free(b, "boolexp_node");
}

/** Free a boolexp ast node.
 * This function frees a boolexp, including all subexpressions,
 * recursively.
 * \param b boolexp to free.
 */
static void
free_boolexp_node(struct boolexp_node *b)
{
  if (b) {
    switch (b->type) {
    case BOOLEXP_AND:
    case BOOLEXP_OR:
      free_boolexp_node(b->data.sub.a);
      free_boolexp_node(b->data.sub.b);
      free_bool(b);
      break;
    case BOOLEXP_NOT:
      free_boolexp_node(b->data.n);
      free_bool(b);
      break;
    case BOOLEXP_CONST:
    case BOOLEXP_CARRY:
    case BOOLEXP_IS:
    case BOOLEXP_OWNER:
    case BOOLEXP_BOOL:
      free_bool(b);
      break;
    case BOOLEXP_IND:
      if (b->data.ind_lock)
	st_delete(b->data.ind_lock, &lock_names);
      free_bool(b);
      break;
    case BOOLEXP_ATR:
    case BOOLEXP_EVAL:
    case BOOLEXP_FLAG:
      if (b->data.atr_lock) {
	if (b->data.atr_lock->name)
	  st_delete(b->data.atr_lock->name, &atr_names);
	mush_free((Malloc_t) b->data.atr_lock, "boolatr");
      }
      free_bool(b);
      break;
    }
  }
}

/** Skip over leading whitespace characters in parsebuf */
static void
skip_whitespace(void)
{
  while (*parsebuf && isspace((unsigned char) *parsebuf))
    parsebuf++;
}

static struct boolexp_node *
test_atr(char *s, char c)
{
  struct boolexp_node *b;
  char tbuf1[BUFFER_LEN];
  strcpy(tbuf1, strupper(s));
  for (s = tbuf1; *s && (*s != c); s++) ;
  if (!*s)
    return 0;
  *s++ = 0;
  if (strlen(tbuf1) == 0 || !good_atr_name(tbuf1))
    return 0;
  if (c == '^') {
    int n;
    for (n = 0; flag_locks[n].name; n++) {
      if (strcmp(flag_locks[n].name, tbuf1) == 0)
	break;
    }
    if (!flag_locks[n].name)
      return 0;
  }
  b = alloc_bool();
  if (c == ':')
    b->type = BOOLEXP_ATR;
  else if (c == '/')
    b->type = BOOLEXP_EVAL;
  else if (c == '^')
    b->type = BOOLEXP_FLAG;
  b->data.atr_lock = alloc_atr(tbuf1, s);
  return b;
}

/* L -> E, L is an object name or dbref or #t* or #f* */
static struct boolexp_node *
parse_boolexp_R(void)
{
  struct boolexp_node *b;
  char tbuf1[BUFFER_LEN];
  char *p;
  b = alloc_bool();
  b->type = BOOLEXP_CONST;
  p = tbuf1;
  while (*parsebuf
	 && *parsebuf != AND_TOKEN && *parsebuf != '/'
	 && *parsebuf != OR_TOKEN && *parsebuf != ')') {
    *p++ = *parsebuf++;
  }
  /* strip trailing whitespace */
  *p-- = '\0';
  while (isspace((unsigned char) *p))
    *p-- = '\0';
  /* do the match */
  if (loading_db) {
    if (*tbuf1 == '#' && *(tbuf1 + 1)) {
      if (*(tbuf1 + 1) == 't' || *(tbuf1 + 1) == 'T') {
	b->type = BOOLEXP_BOOL;
	b->thing = 1;
      } else if (*(tbuf1 + 1) == 'f' || *(tbuf1 + 1) == 'F') {
	b->type = BOOLEXP_BOOL;
	b->thing = 0;
      } else {
	b->thing = parse_integer(tbuf1 + 1);
      }
    } else {
      /* Ooog. Dealing with a malformed lock in the database. */
      free_bool(b);
      return NULL;
    }
    return b;
  } else {
    /* Are these special atoms? */
    if (*tbuf1 && *tbuf1 == '#' && *(tbuf1 + 1)) {
      if (*(tbuf1 + 1) == 't' || *(tbuf1 + 1) == 'T') {
	b->type = BOOLEXP_BOOL;
	b->thing = 1;
	return b;
      } else if (*(tbuf1 + 1) == 'f' || *(tbuf1 + 1) == 'F') {
	b->type = BOOLEXP_BOOL;
	b->thing = 0;
	return b;
      }
    }
    b->thing = match_result(parse_player, tbuf1, TYPE_THING, MAT_EVERYTHING);
    if (b->thing == NOTHING) {
      notify_format(parse_player, T("I don't see %s here."), tbuf1);
      free_bool(b);
      return NULL;
    } else if (b->thing == AMBIGUOUS) {
      notify_format(parse_player, T("I don't know which %s you mean!"), tbuf1);
      free_bool(b);
      return NULL;
    } else {
      return b;
    }
  }
}

/* L -> (E); L -> eval/attr/flag lock, (lock) */
static struct boolexp_node *
parse_boolexp_L(void)
{
  struct boolexp_node *b;
  char *p;
  const char *savebuf;
  char tbuf1[BUFFER_LEN];
  skip_whitespace();
  switch (*parsebuf) {
  case '(':
    parsebuf++;
    b = parse_boolexp_E();
    skip_whitespace();
    if (b == NULL || *parsebuf++ != ')') {
      free_boolexp_node(b);
      return NULL;
    } else {
      return b;
    }
    /* break; */
  default:
    /* must have hit an object ref */
    /* load the name into our buffer */
    p = tbuf1;
    savebuf = parsebuf;
    while (*parsebuf
	   && *parsebuf != AND_TOKEN
	   && *parsebuf != OR_TOKEN && *parsebuf != ')') {
      *p++ = *parsebuf++;
    }
    /* strip trailing whitespace */
    *p-- = '\0';
    while (isspace((unsigned char) *p))
      *p-- = '\0';
    /* check for an attribute */
    b = test_atr(tbuf1, ':');
    if (b)
      return b;
    /* check for an eval */
    b = test_atr(tbuf1, '/');
    if (b)
      return b;
    /* Check for a flag */
    b = test_atr(tbuf1, '^');
    if (b)
      return b;
    /* Nope. Check for an object reference */
    parsebuf = savebuf;
    return parse_boolexp_R();
  }
}

/* O -> $Identifier ; O -> L */
static struct boolexp_node *
parse_boolexp_O(void)
{
  struct boolexp_node *b2, *t;
  skip_whitespace();
  if (*parsebuf == OWNER_TOKEN) {
    parsebuf++;
    b2 = alloc_bool();
    b2->type = BOOLEXP_OWNER;
    t = parse_boolexp_R();
    if (t == NULL) {
      free_boolexp_node(b2);
      return NULL;
    } else {
      b2->thing = t->thing;
      free_boolexp_node(t);
      return b2;
    }
  }
  return parse_boolexp_L();
}

/* C -> +Identifier ; C -> O */
static struct boolexp_node *
parse_boolexp_C(void)
{
  struct boolexp_node *b2, *t;
  skip_whitespace();
  if (*parsebuf == IN_TOKEN) {
    parsebuf++;
    b2 = alloc_bool();
    b2->type = BOOLEXP_CARRY;
    t = parse_boolexp_R();
    if (t == NULL) {
      free_boolexp_node(b2);
      return NULL;
    } else {
      b2->thing = t->thing;
      free_boolexp_node(t);
      return b2;
    }
  }
  return parse_boolexp_O();
}

/* I -> =Identifier ; I -> C */
static struct boolexp_node *
parse_boolexp_I(void)
{
  struct boolexp_node *b2, *t;
  skip_whitespace();
  if (*parsebuf == IS_TOKEN) {
    parsebuf++;
    b2 = alloc_bool();
    b2->type = BOOLEXP_IS;
    t = parse_boolexp_R();
    if (t == NULL) {
      free_boolexp_node(b2);
      return NULL;
    } else {
      b2->thing = t->thing;
      free_boolexp_node(t);
      return b2;
    }
  }
  return parse_boolexp_C();
}

/* A -> @L; A -> I */
static struct boolexp_node *
parse_boolexp_A(void)
{
  struct boolexp_node *b2, *t;
  skip_whitespace();
  if (*parsebuf == AT_TOKEN) {
    parsebuf++;
    b2 = alloc_bool();
    b2->type = BOOLEXP_IND;
    t = parse_boolexp_R();
    if (t == NULL) {
      free_boolexp_node(b2);
      return NULL;
    }
    b2->thing = t->thing;
    free_boolexp_node(t);
    if (*parsebuf == '/') {
      char tbuf1[BUFFER_LEN], *p;
      const char *m;
      parsebuf++;
      p = tbuf1;
      while (*parsebuf
	     && *parsebuf != AND_TOKEN
	     && *parsebuf != OR_TOKEN && *parsebuf != ')') {
	*p++ = *parsebuf++;
      }
      /* strip trailing whitespace */
      *p-- = '\0';
      while (isspace((unsigned char) *p))
	*p-- = '\0';
      upcasestr(tbuf1);
      if (!good_atr_name(tbuf1)) {
	free_boolexp_node(b2);
	return NULL;
      }
      m = match_lock(tbuf1);
      b2->data.ind_lock = st_insert(m ? m : tbuf1, &lock_names);
    } else {
      b2->data.ind_lock = st_insert(parse_ltype, &atr_names);
    }
    return b2;
  }
  return parse_boolexp_I();
}

/* F -> !F;F -> A */
static struct boolexp_node *
parse_boolexp_F(void)
{
  struct boolexp_node *b2;
  skip_whitespace();
  if (*parsebuf == NOT_TOKEN) {
    parsebuf++;
    b2 = alloc_bool();
    b2->type = BOOLEXP_NOT;
    if ((b2->data.n = parse_boolexp_F()) == NULL) {
      free_boolexp_node(b2);
      return NULL;
    } else
      return b2;
  }
  return parse_boolexp_A();
}


/* T -> F; T -> F & T */
static struct boolexp_node *
parse_boolexp_T(void)
{
  struct boolexp_node *b, *b2;

  if ((b = parse_boolexp_F()) == NULL) {
    return b;
  } else {
    skip_whitespace();
    if (*parsebuf == AND_TOKEN) {
      parsebuf++;
      b2 = alloc_bool();
      b2->type = BOOLEXP_AND;
      b2->data.sub.a = b;
      if ((b2->data.sub.b = parse_boolexp_T()) == NULL) {
	free_boolexp_node(b2);
	return NULL;
      } else {
	return b2;
      }
    } else {
      return b;
    }
  }
}

/* E -> T; E -> T | E */
static struct boolexp_node *
parse_boolexp_E(void)
{
  struct boolexp_node *b, *b2;

  if ((b = parse_boolexp_T()) == NULL) {
    return b;
  } else {
    skip_whitespace();
    if (*parsebuf == OR_TOKEN) {
      parsebuf++;
      b2 = alloc_bool();
      b2->type = BOOLEXP_OR;
      b2->data.sub.a = b;
      if ((b2->data.sub.b = parse_boolexp_E()) == NULL) {
	free_boolexp_node(b2);
	return NULL;
      } else {
	return b2;
      }
    } else {
      return b;
    }
  }
}

/* Functions for turning the parse tree into assembly */

/** Create a label identifier.
 * \param a the assembler list the label is for.
 * \return a new label id
 */
static int
gen_label_id(struct bvm_asm *a)
{
  int l = a->label;
  a->label++;
  return l;
}

/** Add an instruction to the assembler list.
 * \param a the assembler list.
 * \param op the opcode of the instruction.
 * \param arg the argument for the instruction if numeric.
 * \param s the string to use as the argument for the instruction. If non-NULL, arg's value is ignored.
 */
static void
append_insn(struct bvm_asm *a, bvm_opcode op, int arg, const char *s)
{
  struct bvm_asmnode *newop;

  if (s) {
    struct bvm_strnode *newstr;
    int count = 0, found = 0;

    /* Look for an existing string */
    for (newstr = a->shead; newstr; newstr = newstr->next, count++) {
      if (strcmp(newstr->s, s) == 0) {
	arg = count;
	found = 1;
	break;
      }
    }
    /* Allocate a new string if needed. */
    if (!found) {
      newstr = mush_malloc(sizeof *newstr, "bvm.strnode");
      if (!s)
	mush_panic(T("Unable to allocate memory for boolexp string node!"));
      newstr->s = mush_strdup(s, "bvm.string");
      if (!newstr->s)
	mush_panic(T("Unable to allocate memory for boolexp string!"));
      newstr->len = strlen(s) + 1;
      newstr->next = NULL;
      if (a->shead == NULL)
	a->shead = a->stail = newstr;
      else {
	a->stail->next = newstr;
	a->stail = newstr;
      }
      arg = a->strcount;
      a->strcount++;
    }
  }

  newop = mush_malloc(sizeof *newop, "bvm.asmnode");
  if (!newop)
    mush_panic(T("Unable to allocate memory for boolexp asm node!"));
  newop->op = op;
  newop->arg = arg;
  newop->next = NULL;
  if (a->head == NULL)
    a->head = a->tail = newop;
  else {
    a->tail->next = newop;
    a->tail = newop;
  }
}

/** Does the actual work of walking the parse tree and creating an
 * assembler list from it.
 * \param a the assembler list.
 * \param b the root of the parse tree.
 * \param outer the type of root's parent node.
 */
static void
generate_bvm_asm1(struct bvm_asm *a, struct boolexp_node *b, boolexp_type outer)
{
  int lbl;

  switch (b->type) {
  case BOOLEXP_AND:
    lbl = gen_label_id(a);
    if (outer == BOOLEXP_NOT)
      append_insn(a, OP_PAREN, 0, NULL);
    generate_bvm_asm1(a, b->data.sub.a, b->type);
    append_insn(a, OP_JMPF, lbl, NULL);
    generate_bvm_asm1(a, b->data.sub.b, b->type);
    if (outer == BOOLEXP_NOT)
      append_insn(a, OP_PAREN, 1, NULL);
    append_insn(a, OP_LABEL, lbl, NULL);
    break;
  case BOOLEXP_OR:
    lbl = gen_label_id(a);
    if (outer == BOOLEXP_NOT || outer == BOOLEXP_AND)
      append_insn(a, OP_PAREN, 0, NULL);
    generate_bvm_asm1(a, b->data.sub.a, b->type);
    append_insn(a, OP_JMPT, lbl, NULL);
    generate_bvm_asm1(a, b->data.sub.b, b->type);
    if (outer == BOOLEXP_NOT || outer == BOOLEXP_AND)
      append_insn(a, OP_PAREN, 1, NULL);
    append_insn(a, OP_LABEL, lbl, NULL);
    break;
  case BOOLEXP_IND:
    append_insn(a, OP_LOADS, 0, b->data.ind_lock);
    append_insn(a, OP_TIND, b->thing, NULL);
    break;
  case BOOLEXP_IS:
    append_insn(a, OP_TIS, b->thing, NULL);
    break;
  case BOOLEXP_CARRY:
    append_insn(a, OP_TCARRY, b->thing, NULL);
    break;
  case BOOLEXP_OWNER:
    append_insn(a, OP_TOWNER, b->thing, NULL);
    break;
  case BOOLEXP_NOT:
    generate_bvm_asm1(a, b->data.n, b->type);
    append_insn(a, OP_NEGR, 0, NULL);
    break;
  case BOOLEXP_CONST:
    append_insn(a, OP_TCONST, b->thing, NULL);
    break;
  case BOOLEXP_BOOL:
    append_insn(a, OP_LOADR, b->thing, NULL);
    break;
  case BOOLEXP_ATR:
    append_insn(a, OP_LOADS, 0, b->data.atr_lock->name);
    append_insn(a, OP_TATR, 0, b->data.atr_lock->text);
    break;
  case BOOLEXP_EVAL:
    append_insn(a, OP_LOADS, 0, b->data.atr_lock->name);
    append_insn(a, OP_TEVAL, 0, b->data.atr_lock->text);
    break;
  case BOOLEXP_FLAG:
    {
      enum bvm_opcode op = OP_RET;
      int n;
      for (n = 0; flag_locks[n].name; n++) {
	if (strcmp(b->data.atr_lock->name, flag_locks[n].name) == 0) {
	  op = flag_locks[n].op;
	  break;
	}
      }
      append_insn(a, op, 0, b->data.atr_lock->text);
      break;
    }
  }
}

/** Turn a parse tree into an assembler list.
 * \param the parse tree
 * \return newly allocated assembler list.
 */
static struct bvm_asm *
generate_bvm_asm(struct boolexp_node *b)
{
  struct bvm_asm *a;

  if (!b)
    return NULL;

  a = mush_malloc(sizeof *a, "bvm.asm");
  if (!a)
    return NULL;

  a->strcount = a->label = 0;
  a->head = a->tail = NULL;
  a->shead = a->stail = NULL;

  generate_bvm_asm1(a, b, BOOLEXP_CONST);
  append_insn(a, OP_RET, 0, NULL);

  return a;
}

/** Frees an assembler list.
 * \param a the assembler list to deallocate.
 */
static void
free_bvm_asm(struct bvm_asm *a)
{
  struct bvm_asmnode *i, *tmp1;
  struct bvm_strnode *s, *tmp2;

  if (!a)
    return;

  for (i = a->head; i; i = tmp1) {
    tmp1 = i->next;
    mush_free(i, "bvm.asmnode");
  }

  for (s = a->shead; s; s = tmp2) {
    tmp2 = s->next;
    mush_free(s->s, "bvm.string");
    mush_free(s, "bvm.strnode");
  }
  mush_free(a, "bvm.asm");
}

/** Find the position of a labeled instruction.
 * \param as the assembler list.
 * \param the id of the label to find.
 * \return the number of instructions before the label.
 */
static int
pos_of_label(struct bvm_asm *as, int label)
{
  int offset = 0;
  struct bvm_asmnode *a;
  for (a = as->head; a; a = a->next) {
    if (a->op == OP_LABEL && a->arg == label)
      return offset;
    if (a->op != OP_LABEL)
      offset++;
  }
  return offset;		/* Never reached! */
}

/** Find the position of a string.
 * \param a the assembler list
 * \param c The c-th string is the one that's wanted.
 * \return the distance from the start of the string section to the start of the c-th string.
 */
static int
offset_to_string(struct bvm_asm *a, int c)
{
  int offset = 0;
  int n = 0;
  struct bvm_strnode *s;

  for (s = a->shead; s; s = s->next, n++) {
    if (n == c)
      return offset;
    else
      offset += s->len;
  }
  return offset;		/* Never reached! */
}

/** Find the next instruction after a label.
 * \param a the assembler list.
 * \param label the label id to look for.
 * \return a pointer to the first real instruction after a label; where a jump to that label will go to.
 */
static struct bvm_asmnode *
insn_after_label(struct bvm_asm *a, int label)
{
  struct bvm_asmnode *n;

  for (n = a->head; n; n = n->next) {
    if (n->op == OP_LABEL && n->arg == label) {
      do {
	n = n->next;
      } while (n->op == OP_LABEL);
      return n;
    }
  }
  return NULL;
}

/** Avoid jumps that lead straight to another jump. If the second jump
 * is on the same condition as the first one, jump instead to its
 * destination. If it's the opposite condition, jump instead to the
 * first instruction after the second jump to avoid the useless
 * conditional check. 
 * \param a the assembler list to thread. */
static void
opt_thread_jumps(struct bvm_asm *a)
{
  struct bvm_asmnode *n, *target;

  for (n = a->head; n;) {
    if (n->op == OP_JMPT || n->op == OP_JMPF) {
      target = insn_after_label(a, n->arg);
      if (target && (target->op == OP_JMPT || target->op == OP_JMPF)) {
	if (target->op == n->op) {
	  /* Avoid daisy-chained conditional jumps on the same
	     condition.
	   */
	  n->arg = target->arg;
	} else {
	  /* Avoid useless conditional jumps on different conditions by
	     jumping to the next instruction after. Ex: a&b|c */
	  struct bvm_asmnode *newlbl;
	  newlbl = mush_malloc(sizeof *newlbl, "bvm.asmnode");
	  if (!newlbl)
	    mush_panic(T("Unable to allocate memory for boolexp asm node!"));
	  newlbl->op = OP_LABEL;
	  n->arg = newlbl->arg = gen_label_id(a);
	  if (target->next)
	    newlbl->next = target->next;
	  else
	    newlbl->next = NULL;
	  target->next = newlbl;
	  if (a->tail == target)
	    a->tail = newlbl;
	}
      } else
	n = n->next;
    } else
      n = n->next;
  }
}

/** Do some trivial optimizations.  
 * \param a the assembler list to transform.
 */
static void
optimize_bvm_asm(struct bvm_asm *a)
{
  if (!a)
    return;
  opt_thread_jumps(a);
}

/** Turn assembly into bytecode. 
 * \param a the assembly list to emit.
 * \param the compiled bytecode.
 */
static boolexp
emit_bytecode(struct bvm_asm *a, int derefs)
{
  boolexp b;
  struct bvm_asmnode *i;
  struct bvm_strnode *s;
  char *pc, *bytecode;
  u_int_16 len, blen;

  if (!a)
    return TRUE_BOOLEXP;

  /* Calculate the total size of the bytecode */
  len = 0;

  for (i = a->head; i; i = i->next) {
    if (i->op == OP_LABEL)
      continue;
    len++;
  }

  len *= INSN_LEN;
  blen = len;

  for (s = a->shead; s; s = s->next)
    len += s->len;

  pc = bytecode = mush_malloc(len, "boolexp.bytecode");
  if (!pc)
    return TRUE_BOOLEXP;

  /* Emit the instructions */
  for (i = a->head; i; i = i->next) {
    switch (i->op) {
    case OP_LABEL:
      continue;
    case OP_JMPT:
    case OP_JMPF:
      i->arg = pos_of_label(a, i->arg) * INSN_LEN;
      break;
    case OP_LOADS:
    case OP_TEVAL:
    case OP_TATR:
    case OP_TFLAG:
    case OP_TPOWER:
    case OP_TOBJID:
    case OP_TTYPE:
    case OP_TCHANNEL:
    case OP_TIP:
    case OP_THOSTNAME:
      i->arg = blen + offset_to_string(a, i->arg);
      break;
    default:
      break;
    }

    *pc = (char) i->op;
    memcpy(pc + 1, &i->arg, sizeof i->arg);
    pc += INSN_LEN;
  }

  /* Emit the strings section */
  for (s = a->shead; s; s = s->next) {
    memcpy(pc, s->s, s->len);
    pc += s->len;
  }

  b = chunk_create(bytecode, len, derefs);
  mush_free(bytecode, "boolexp.bytecode");
  return b;
}

/** Compile a string into boolexp bytecode.
 * Given a textual representation of a boolexp in a string, parse it into
 * a syntax tree, compile to bytecode, and return a pointer to a boolexp
 * structure.
 * \param player the enactor.
 * \param buf string representation of a boolexp.
 * \param ltype the type of lock for which the boolexp is being parsed.
 * \param derefs the starting deref count for chunk storage.
 * \return pointer to a newly allocated boolexp.
 */
boolexp
parse_boolexp_d(dbref player, const char *buf, lock_type ltype, int derefs)
{
  struct boolexp_node *ast;
  struct bvm_asm *bvasm;
  boolexp bytecode;
  /* Parse */
  parsebuf = buf;
  parse_player = player;
  parse_ltype = ltype;
  ast = parse_boolexp_E();
  if (!ast)
    return TRUE_BOOLEXP;
  bvasm = generate_bvm_asm(ast);
  if (!bvasm) {
    free_boolexp_node(ast);
    return TRUE_BOOLEXP;
  }
  optimize_bvm_asm(bvasm);
  bytecode = emit_bytecode(bvasm, derefs);
#ifdef DEBUG_BYTECODE
  printf("\nSource string: \"%s\"\n", buf);
  printf("Parse tree size: %d bytes\n", sizeof_boolexp_node(ast));
  print_bytecode(bytecode);
#endif
  free_boolexp_node(ast);
  free_bvm_asm(bvasm);
  return bytecode;
}

/** Compile a string into boolexp bytecode.
 * Given a textual representation of a boolexp in a string, parse it into
 * a syntax tree, compile to bytecode, and return a pointer to a boolexp
 * structure.
 * \param player the enactor.
 * \param buf string representation of a boolexp.
 * \param ltype the type of lock for which the boolexp is being parsed.
 * \return pointer to a newly allocated boolexp.
 */
boolexp
parse_boolexp(dbref player, const char *buf, lock_type ltype)
{
  return parse_boolexp_d(player, buf, ltype, 0);
}

/** Test to see if an eval lock passes, with a exact match.
 * \param player the object attempting to pass the lock.
 * \param target the object the lock is on.
 * \param atrname the name of the attribute to evaluate on target.
 * \param str What the attribute should evaluate to to succeed.
 * \retval 1 the lock succeeds.
 * \retval 0 the lock fails.
 */
static int
check_attrib_lock(dbref player, dbref target,
		  const char *atrname, const char *str)
{

  ATTR *a;
  char *asave;
  const char *ap;
  char buff[BUFFER_LEN], *bp;
  char *preserve[NUMQ];
  if (!atrname || !*atrname || !str || !*str)
    return 0;
  /* fail if there's no matching attribute */
  a = atr_get(target, strupper(atrname));
  if (!a)
    return 0;
  if (!Can_Read_Attr(target, target, a))
    return 0;
  asave = safe_atr_value(a);
  /* perform pronoun substitution */
  save_global_regs("check_attrib_lock_save", preserve);
  bp = buff;
  ap = asave;
  process_expression(buff, &bp, &ap, target, player,
		     player, PE_DEFAULT, PT_DEFAULT, NULL);
  *bp = '\0';
  restore_global_regs("check_attrib_lock_save", preserve);
  free(asave);

  return !strcasecmp(buff, str);
}

#ifdef DEBUG_BYTECODE

/** Find the size of a parse tree node, recursively to count all child nodes.
 * \param b the root of the parse tree.
 * \return the size of the parse tree in bytes.
 */
static int
sizeof_boolexp_node(struct boolexp_node *b)
{
  if (!b)
    return 0;
  switch (b->type) {
  case BOOLEXP_CONST:
  case BOOLEXP_IS:
  case BOOLEXP_CARRY:
  case BOOLEXP_OWNER:
  case BOOLEXP_IND:
  case BOOLEXP_BOOL:
    return sizeof *b;
  case BOOLEXP_NOT:
    return sizeof *b + sizeof_boolexp_node(b->data.n);
  case BOOLEXP_AND:
  case BOOLEXP_OR:
    return sizeof *b +
      sizeof_boolexp_node(b->data.sub.a) + sizeof_boolexp_node(b->data.sub.b);
  case BOOLEXP_ATR:
  case BOOLEXP_EVAL:
  case BOOLEXP_FLAG:
    return sizeof *b + sizeof *b->data.atr_lock - BUFFER_LEN +
      strlen(b->data.atr_lock->text) + 1;
  default:
    /* Broken lock */
    return sizeof *b;
  }
}

/** Print out a decompiled-to-assembly bytecode to stdout.
 * \param b the boolexp to decompile.
 */
static void
print_bytecode(boolexp b)
{
  bvm_opcode op;
  int arg, len = 0, pos = 0;
  char *pc, *bytecode;

  if (b == TRUE_BOOLEXP) {
    puts("NULL bytecode!");
    return;
  }

  pc = bytecode = get_bytecode(b, &len);

  printf("Total length of bytecode+strings: %d bytes\n", len);

  while (1) {
    op = (bvm_opcode) * pc;
    memcpy(&arg, pc + 1, sizeof arg);
    pc += INSN_LEN;
    printf("%-5d ", pos);
    pos++;
    switch (op) {
    case OP_RET:
      puts("RET");
      return;
    case OP_PAREN:
      printf("PAREN %c\n", (arg == 0) ? '(' : ((arg == 1) ? ')' : '!'));
      break;
    case OP_JMPT:
      printf("JMPT %d\n", arg / INSN_LEN);
      break;
    case OP_JMPF:
      printf("JMPF %d\n", arg / INSN_LEN);
      break;
    case OP_TCONST:
      printf("TCONST #%d\n", arg);
      break;
    case OP_TCARRY:
      printf("TCARRY #%d\n", arg);
      break;
    case OP_TIS:
      printf("TIS #%d\n", arg);
      break;
    case OP_TOWNER:
      printf("TOWNER #%d\n", arg);
      break;
    case OP_TIND:
      printf("TIND #%d\n", arg);
      break;
    case OP_TATR:
      printf("TATR \"%s\"\n", bytecode + arg);
      break;
    case OP_TEVAL:
      printf("TEVAL \"%s\"\n", bytecode + arg);
      break;
    case OP_TFLAG:
      printf("TFLAG \"%s\"\n", bytecode + arg);
      break;
    case OP_TPOWER:
      printf("TPOWER \"%s\"\n", bytecode + arg);
      break;
    case OP_TOBJID:
      printf("TOBJID \"%s\"\n", bytecode + arg);
      break;
    case OP_TTYPE:
      printf("TTYPE \"%s\"\n", bytecode + arg);
      break;
    case OP_TCHANNEL:
      printf("TCHANNEL \"%s\"\n", bytecode + arg);
      break;
    case OP_TIP:
      printf("TIP \"%s\"\n", bytecode + arg);
      break;
    case OP_THOSTNAME:
      printf("THOSTNAME \"%s\"\n", bytecode + arg);
      break;
    case OP_LOADS:
      printf("LOADS \"%s\"\n", bytecode + arg);
      break;
    case OP_LOADR:
      printf("LOADR %d\n", arg);
      break;
    case OP_NEGR:
      puts("NEGR");
      break;
    default:
      printf("Hmm: %d %d\n", op, arg);
    }
  }
}
#endif

/* Warnings-related stuff here because I don't want to export details
   of the bytecode outside this file. */
#define W_UNLOCKED      0x1  /**< Returned if a boolexp is unlocked */
#define W_LOCKED        0x2  /**< Returned if a boolexp is locked */

/** Check to see if a lock is considered possibly unlocked or not.
 *  This is really simple-minded for efficiency. Basically, if it's *
 *  unlocked, it's unlocked. If it's locked to something starting with
 *  a specific db#, it's locked. Anything else, and we don't know.
 *  \param l the boolexp to check.
 *  \retval W_UNLOCKED the boolexp is unlocked.
 *  \retval W_LOCKED the boolexp is considered locked.
 *  \retval W_LOCKED|W_UNLOCKED the boolexp is in an unknown state.
 */
int
warning_lock_type(const boolexp l)
     /* 0== unlocked. 1== locked, 2== sometimes */
{
  if (l == TRUE_BOOLEXP)
    return W_UNLOCKED;
  /* Two instructions means one of the simple lock cases */
  else if (sizeof_boolexp(l) == (INSN_LEN + INSN_LEN))
    return W_LOCKED;
  else
    return W_LOCKED | W_UNLOCKED;
}

/** Check for lock-check @warnings.
 * Things like non-existant attributes in eval locks, references to
 * garbage objects, or indirect locks that aren't present or visible.
 * \param player the object to report warnings to.
 * \param i the object the lock is on.
 * \param name the lock type.
 * \param be the lock key.
 */
void
check_lock(dbref player, dbref i, const char *name, boolexp be)
{
  char *pc, *bytecode;
  bvm_opcode op;
  int arg;
  char *s = NULL;

  bytecode = pc = get_bytecode(be, NULL);

  while (1) {
    op = (bvm_opcode) * pc;
    memcpy(&arg, pc + 1, sizeof arg);
    pc += INSN_LEN;
    switch (op) {
    case OP_RET:
      return;
    case OP_LOADS:
      s = bytecode + arg;
      break;
    case OP_TCONST:
    case OP_TCARRY:
    case OP_TIS:
    case OP_TOWNER:
      if (!GoodObject(arg) || IsGarbage(arg))
	complain(player, i, "lock-checks",
		 T("%s lock refers to garbage object"), name);
      break;
    case OP_TEVAL:
      {
	ATTR *a;
	a = atr_get(i, s);
	if (!a || !Can_Read_Attr(i, i, a))
	  complain(player, i, "lock-checks",
		   T
		   ("%s lock has eval-lock that uses a nonexistant attribute '%s'."),
		   name, s);
      }
      break;
    case OP_TIND:
      if (!GoodObject(arg) || IsGarbage(arg))
	complain(player, i, "lock-checks",
		 T("%s lock refers to garbage object"), name);
      else if (!(Can_Read_Lock(i, arg, s) && getlock(arg, s) != TRUE_BOOLEXP))
	complain(player, i, "lock-checks",
		 T("%s lock has indirect lock to %s/%s that it can't read"),
		 name, unparse_object(player, arg), s);
      break;
    default:
      break;
    }
  }
}