dbm/
misc/
old-docs/
/* boolexp.c */

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

#include <stdio.h>
#ifdef STRING_H
#include <string.h>
#else
#include <strings.h>
#endif				/* STRING_H */
#include <ctype.h>

#include "teeny.h"

/*
 * Routines for parsing, evaluating and displaying boolean expressions.
 */

/*
 * Opcodes for our little Boolean expression evaluator.
 */

#define STOP -1
#define AND -2
#define OR -3
#define NOT -4
#define OPEN -5
#define CLOSE -6
#define BADREF -7
#define MALE_LOCK -100
#define FEMALE_LOCK -101
#define NEUTER_LOCK -102
#define WIZARD_LOCK -200
#define ROBOT_LOCK -201

static char    *get_tok();
static char    *to_infix();
void            parse_lock();

static int      stack[64];	/* Expressions can't be too big. */
static int      work[256];

static char     toocomplex[] = "Lock is too complex\r\n";
static char     badlock[] = "I don't understand that lock.\r\n";

/*
 * We use these structs, an array of them, to build parse trees from our
 * internal RPN locks so we can write them out in infix.
 */

struct tree {
  int             dat;
  short           left, right;	/* Array offsets */
};

/*
 * Parses a boolean expression, stuffs it into a hunk of memory, and returns
 * a pointer to said hunk. Returns NULL if it can't cope.
 */

int            *
                bool_parse(player, str)
  int             player;
  char           *str;
{
  int             wp, sp;
  int             tok, *thelock;

  sp = 0;
  wp = 1;

  if (str == NULL) {
    notify_player(player, badlock);
    return (NULL);
  }
  do {
    str = get_tok(player, str, &tok);

    if (tok == BADREF) {
      notify_player(player, badlock);
      return (NULL);
    }
    switch (tok) {
    case OPEN:
      if (sp < 63) {
	stack[sp++] = OPEN;
      } else {
	notify_player(player, toocomplex);
	return (NULL);
      }
      break;
    case CLOSE:
      while (sp > 0 && stack[sp - 1] != OPEN && wp < 255) {
	work[wp++] = stack[--sp];
      }
      if (sp == 0 || stack[sp - 1] != OPEN) {
	notify_player(player, "Too complex, or unbalanced parens.\r\n");
	return (NULL);
      }
      sp--;
      break;
    case AND:
      if (sp > 0 && stack[sp - 1] == OR) {
	notify_player(player, badlock);
	return (NULL);
      }
      while (sp > 0 && stack[sp - 1] == NOT && wp < 255) {
	work[wp++] = stack[--sp];
      }
      if (wp == 255) {
	notify_player(player, toocomplex);
	return (NULL);
      }
      if (sp < 63) {
	stack[sp++] = AND;
      } else {
	notify_player(player, toocomplex);
	return (NULL);
      }
      break;
    case OR:
      if (sp > 0 && (stack[sp - 1] == AND || stack[sp - 1] == NOT)) {
	notify_player(player, badlock);
	return (NULL);
      }
      while (sp > 0 && (stack[sp - 1] == NOT ||
			stack[sp - 1] == AND) && wp < 255) {
	work[wp++] = stack[--sp];
      }
      if (wp == 255) {
	notify_player(player, toocomplex);
	return (NULL);
      }
      if (sp < 63) {
	stack[sp++] = OR;
      } else {
	notify_player(player, toocomplex);
	return (NULL);
      }
      break;
    case NOT:
      if (sp < 63) {
	stack[sp++] = NOT;
      } else {
	notify_player(player, toocomplex);
	return (NULL);
      }
      break;
    case STOP:
      while (sp > 0 && wp < 255) {
	if (stack[sp - 1] != AND && stack[sp - 1] != OR
	    && stack[sp - 1] != NOT) {
	  notify_player(player, badlock);
	  return (NULL);
	}
	work[wp++] = stack[--sp];
      }
      if (wp == 255) {
	notify_player(player, toocomplex);
	return (NULL);
      }
      work[wp++] = STOP;
      break;
    default:
      work[wp++] = tok;
      break;
    }
  } while (tok != STOP);

  /* Stow it away somewhere. */

  thelock = (int *) ty_malloc(sizeof(int) * wp, "bool_parse");
  thelock[0] = --wp;
  while (wp > 0) {
    thelock[wp] = work[wp];
    wp--;
  }
  return (thelock);
}

/*
 * Grabs the next token out a string, and returns a pointer to the next thing
 * in the string.
 */

static char    *
                get_tok(player, p, tok)
  char           *p;
  int            *tok;
{
  char           *q, ch;
  int             obj;

  while (*p && isspace(*p))
    p++;

  switch (*p) {
  case '&':
    *tok = AND;
    break;
  case '|':
    *tok = OR;
    break;
  case '!':
    *tok = NOT;
    break;
  case '(':
    *tok = OPEN;
    break;
  case ')':
    *tok = CLOSE;
    break;
  case '\0':
    *tok = STOP;
    break;
  default:
    /* Snarf out an object name */
    q = p;
    while (*p != '&' && *p != '|' && *p != '!' && *p != ')' && *p) {
      p++;
    }
    p--;
    while (isspace(*p))
      p--;
    ch = p[1];
    p[1] = '\0';
    if (!strcasecmp(q, "sex:male")) {
      obj = MALE_LOCK;
    } else if (!strcasecmp(q, "sex:female")) {
      obj = FEMALE_LOCK;
    } else if (!strcasecmp(q, "sex:it") || !strcasecmp(q, "sex:neuter")) {
      obj = NEUTER_LOCK;
    } else if (!strcasecmp(q, "flag:wizard")) {
      obj = WIZARD_LOCK;
    } else if (!strcasecmp(q, "flag:robot")) {
      obj = ROBOT_LOCK;
    } else
      obj = resolve_object(player, q, 1);	/* Allow *<playername> */
    p[1] = ch;

    if (!exists_object(obj) && obj > -2) {
      *tok = BADREF;
    } else {
      *tok = obj;
    }
  }
  /* p points at the last char of the token. */

  p++;
  return (p);
}

/*
 * Writes a boolean expression into a sized buffer.
 */

int             bool_display(player, exp, buff, buffsiz)
  int             player;
  int            *exp;
  char           *buff;
  int             buffsiz;
{
  struct tree     trees[64];
  int             tp;
  char           *p;
  int             sp;
  int             count;

  if (exp == NULL) {
    strcpy(buff, "*UNLOCKED*");
    return (1);
  }
  tp = sp = 0;
  count = *exp++;

  while (*exp != STOP && count) {
    switch (*exp) {
    case AND:
    case OR:
      if (sp < 2 || tp > 63) {
	warning("bool_display", "lock bad or too big");
	return (-1);
      }
      trees[tp].dat = *exp;
      trees[tp].left = (short) (stack[--sp]);
      trees[tp].right = (short) (stack[--sp]);
      stack[sp++] = tp++;
      break;
    case NOT:
      if (sp < 1 || tp > 63) {
	warning("bool_display", "lock bad or too big");
	return (-1);
      }
      trees[tp].dat = NOT;
      trees[tp].right = (short) (stack[--sp]);
      stack[sp++] = tp++;
      break;
    default:
      if (sp > 255 || tp > 63) {
	warning("bool_display", "lock bad or too big");
	return (-1);
      }
      trees[tp].dat = *exp;
      if (sp < 63) {
	stack[sp++] = tp++;
      } else {
	warning("to_infix", "Internal lock too complex!");
	return (-1);
      }
      break;
    }
    exp++;
  }

  /* OK. We've built this parse tree thing. Now traverse the tree. */

  if (sp != 1) {
    warning("bool_display", "bad internal boolean expression");
    return (-1);
  }
  p = to_infix(player, trees, tp - 1, buff, buffsiz);
  *p = '\0';
  return (0);
}

/*
 * Convert a tree to infix. Recursive as hell.
 */

static char    *
                to_infix(player, trees, t, b, s)
  int             player;
  struct tree    *trees;
  int             t;
  char           *b;
  int             s;
{
  char           *p;

  if (s < 5) {
    return (b);
  }
  switch (trees[t].dat) {
  case AND:
    *b++ = '(';
    p = to_infix(player, trees, trees[t].left, b, s);
    if (s - (p - b) < 6) {
      return (p);
    }
    *p++ = ' ';
    *p++ = '&';
    *p++ = ' ';
    p = to_infix(player, trees, trees[t].right, p, s - (p - b));
    *p++ = ')';
    break;
  case OR:
    *b++ = '(';
    p = to_infix(player, trees, trees[t].left, b, s);
    if (s - (p - b) < 5) {
      return (p);
    }
    *p++ = ' ';
    *p++ = '|';
    *p++ = ' ';
    p = to_infix(player, trees, trees[t].right, p, s - (p - b));
    *p++ = ')';
    break;
  case NOT:
    if (s < 4) {
      return (p);
    }
    *b++ = '!';
    *b++ = '(';
    p = to_infix(player, trees, trees[t].right, b, s - 2);
    *p++ = ')';
    break;
  case MALE_LOCK:
    if (s < 8) {
      return (b);
    }
    strcpy(b, "sex:male");
    p = b + 8;
    break;
  case FEMALE_LOCK:
    if (s < 10) {
      return (b);
    }
    strcpy(b, "sex:female");
    p = b + 10;
    break;
  case NEUTER_LOCK:
    if (s < 6) {
      return (b);
    }
    strcpy(b, "sex:it");
    p = b + 6;
    break;
  case WIZARD_LOCK:
    if (s < 11) {
      return (b);
    }
    strcpy(b, "flag:WIZARD");
    p = b + 11;
    break;
  case ROBOT_LOCK:
    if (s < 13) {
      return (b);
    }
    strcpy(b, "flag:ROBOT");
    p = b + 13;
    break;
  default:
    if (s < 9) {
      return (b);
    } else {
      char           *z;
      z = stuff_name(player, trees[t].dat);
      if (strlen(z) > (s - 1))
	z[s - 2] = 0;		/* just trunicate it */
      (void) strcpy(b, z);
      p = b + strlen(z);
    }
    break;
  }
  return (p);
}

/*
 * Returns 1 if thing is locked against player, 0 if not.
 */

int             islocked(player, thing, code)
  int             player;
  int             thing;
  int             code;
{
  int            *exp;
  int             sp, op1, op2;

  if ((code != LOCK) && (code != ELOCK)) {
    warning("islocked", "bad code");
    return (0);
  }
  if (get_lock_elt(thing, code, &exp) == -1) {
    warning("islocked", "bad lock reference");
    return (0);
  }
  if (exp == NULL) {		/* No lock */
    return (0);
  }
  /* Run down the expression until you hit a STOP */

  exp++;			/* Skip the count field */
  sp = 0;
  while (*exp != STOP) {
    if (*exp < 0) {
      switch (*exp) {
      case NOT:
	op1 = stack[--sp];
	stack[sp++] = !op1;
	break;
      case AND:
	op1 = stack[--sp];
	op2 = stack[--sp];
	stack[sp++] = op1 && op2;
	break;
      case OR:
	op1 = stack[--sp];
	op2 = stack[--sp];
	stack[sp++] = op1 || op2;
	break;
      case MALE_LOCK:
	stack[sp++] = (check_player_gender(player, MALE_LOCK)
		       ? TRUE : FALSE);
	break;
      case FEMALE_LOCK:
	stack[sp++] = (check_player_gender(player, FEMALE_LOCK)
		       ? TRUE : FALSE);
	break;
      case NEUTER_LOCK:
	stack[sp++] = (check_player_gender(player, NEUTER_LOCK)
		       ? TRUE : FALSE);
	break;
      case WIZARD_LOCK:
	stack[sp++] = (iswiz(player) ? TRUE : FALSE);
	break;
      case ROBOT_LOCK:
	stack[sp++] = (isrobot(player) ? TRUE : FALSE);
	break;
      }
    } else {
      /* See if the player has this object */

      if (sp < 63) {
	stack[sp++] = (player_has(player, *exp) ? TRUE : FALSE);
      } else {
	warning("islocked", "Internal lock too complex!");
	return (1);
      }
    }
    exp++;
  }

  if (sp != 1) {
    warning("islocked", "bad internal boolean expression");
    return (0);
  }
  return (!stack[0]);
}

/*
 * Basic tester, sees if a player is or has a specific object.
 */

int             player_has(player, obj)
  int             player;
  int             obj;
{
  int             current;

  if (player == obj) {
    return (1);
  }
  /* Search the list */

  if (get_int_elt(player, CONTENTS, &current) == -1) {
    warning("player_has", "bad contents ref on player");
    return (0);
  }
  while (current != -1) {
    if (current == obj) {
      return (1);
    }
    if (get_int_elt(current, NEXT, &current) == -1) {
      warning("player_has", "bad NEXT ref");
      return (0);
    }
  }

  /* Didn't have it, I guess... */

  return (0);
}

static int      check_player_gender(player, code)
  int             player;
  int             code;
{
  int             flags;

  if (get_int_elt(player, FLAGS, &flags) == -1) {
    warning("check_player_gender", "bad flags ref on player");
    return (0);
  }
  switch (code) {
  case MALE_LOCK:
    return ((flags & GENDER_MALE) ? 1 : 0);
    break;
  case FEMALE_LOCK:
    return ((flags & GENDER_FEMALE) ? 1 : 0);
    break;
  case NEUTER_LOCK:
    return ((flags & GENDER_NEUTER) ? 1 : 0);
    break;
  default:
    warning("check_player_gender", "bad code");
    return (0);
  }
  return (0);
}
/* @unlock, @lock moved here from buildcmds.c */

void            parse_lock(str, obj, attr)
  char           *str, **obj;
  int            *attr;
{
  char           *p;

  *attr = LOCK;
  *obj = str;
  for (p = str; *p && *p != '/'; p++);
  if (*p == '/')
    *p++ = 0;			/* term obj */
  else				/* obj is already terminated */
    return;

  /* parse attr */
  if (stringprefix(p, "ELOCK")) {
    *attr = ELOCK;
    return;
  } else if (stringprefix(p, "LOCK")) {
    *attr = LOCK;
    return;
  } else
    *attr = -1;
}

voidfunc        do_unlock(player, arg)
  int             player;
  char           *arg;
{
  int             attr, obj;
  char           *objname;

  if (!arg || !*arg) {
    notify_player(player, "Unlock what?\r\n");
    return;
  }
  parse_lock(arg, &objname, &attr);
  if (attr == -1) {
    notify_player(player, "No such attribute.\r\n");
    return;
  }
  if ((obj = resolve_object(player, objname, 0)) == -1)
    if ((obj = resolve_exit(player, objname)) == -1) {
      notify_player(player, "I don't see that here.\r\n");
      return;
    }
  if (obj == -2) {
    notify_player(player, "I can't tell which one you mean.\r\n");
    return;
  }
  if (!controls(player, obj)) {
    notify_player(player, "You can't unlock that!\r\n");
    return;
  }
  if (set_lock_elt(obj, attr, (int *) NULL) == -1) {
    notify_bad(player);
    return;
  }
  notify_player(player, "Unlocked.\r\n");
}

voidfunc        do_lock(player, argone, argtwo)
  int             player;
  char           *argone;
  char           *argtwo;
{
  int             obj, *exp, attr;
  char           *objname;

  if (!argone || !*argone) {
    notify_player(player, "Lock what to what?\r\n");
    return;
  }
  parse_lock(argone, &objname, &attr);
  if (attr == -1) {
    notify_player(player, "No such attribute.\r\n");
    return;
  }
  if ((obj = resolve_object(player, objname, 0)) == -1) {
    if ((obj = resolve_exit(player, objname)) == -1) {
      notify_player(player, "I don't see that here.\r\n");
      return;
    }
  }
  if (obj == -2) {
    notify_player(player, "I don't know which one you mean.\r\n");
    return;
  }
  if (!controls(player, obj)) {
    notify_player(player, "You can't do that!\r\n");
    return;
  }
  /* OK. We can mess with the lock on this thing. */

  exp = bool_parse(player, argtwo);
  if (exp == NULL)
    return;			/* bool_parse() already told the player off. */

  if (set_lock_elt(obj, attr, exp) == -1) {
    notify_bad(player);
    return;
  }
  notify_player(player, "Locked.\r\n");
}