/
teeny/db/
teeny/dbm/
teeny/docs/
teeny/includes/
teeny/misc/
teeny/news/
teeny/text/
/* 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
#ifdef GENDER_BOOL_LOCK
#define MALE_LOCK -100
#define FEMALE_LOCK -101
#define NEUTER_LOCK -102
#endif				/* GENDER_BOOL_LOCK */

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

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;

  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';
#ifdef GENDER_BOOL_LOCK
    if (!strcmp(q, "sex:male"))
    {
      obj = MALE_LOCK;
    } else
    if (!strcmp(q, "sex:female"))
    {
      obj = FEMALE_LOCK;
    } else
    if (!strcmp(q, "sex:it") || !strcmp(q, "sex:neuter"))
    {
      obj = NEUTER_LOCK;
    } else
#endif				/* GENDER_BOOL_LOCK */
      obj = resolve_object(player, q, 1);	/* Allow *<playername> */
    p[1] = ch;

    if (obj == -1)
    {
      *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 (0);
  }
  tp = sp = 0;
  count = *exp++;

  while (*exp != STOP && count)
  {
    switch (*exp)
    {
    case AND:
    case OR:
      if (sp < 2 || tp > 63)
      {
        log_error("bool_display, player was #%d\n", player);
	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, ret;

  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;
#ifdef GENDER_BOOL_LOCK
  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;
#endif				/* GENDER_BOOL_LOCK */
  default:
    if (s < 9)
    {
      return (b);
    }
    ret = stuff_name(player, trees[t].dat, b, s - 1);
    if (ret == -1)
    {
      strcpy(b, "<unknown>");
      p = b + 9;
    } else
    {
      p = b + ret;
    }
    break;
  }
  return (p);
}

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

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

  if (ret = isflaglocked(player, thing))
    return (ret);

  if (get_lock_elt(thing, LOCK, &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;
#ifdef GENDER_BOOL_LOCK
      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;
#endif				/* GENDER_BOOL_LOCK */
      }
    } 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)
  {
    log_error("islocked: player = %d, thing = %d\n", player, thing);
    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);
}

#ifdef GENDER_BOOL_LOCK
static int 
check_player_gender(player, code)
  int             player;
  int             code;
{
  char           *gender;

  if (get_str_elt(player, GENDER, &gender) == -1)
  {
    warning("check_player_gender", "bad gender ref on player");
    return (0);
  }
  if (gender == NULL)
    return (0);

  switch (code)
  {
  case MALE_LOCK:
    if (*gender == 'm' || *gender == 'M')
    {
      return (1);
    } else
    {
      return (0);
    }
    break;
  case FEMALE_LOCK:
    if (*gender == 'f' || *gender == 'F')
    {
      return (1);
    } else
    {
      return (0);
    }
    break;
  case NEUTER_LOCK:
    if (*gender == 'i' || *gender == 'I' || *gender == 'n' ||
	*gender == 'N')
    {
      return (1);
    } else
    {
      return (0);
    }
    break;
  default:
    warning("check_player_gender", "bad code");
    return (0);
  }
  return (0);
}

#endif				/* GENDER_BOOL_LOCK */
int 
isflaglocked(player, thing)
  int             player;
  int             thing;
{
  if (ishaven(thing) && ishaven(player))
  {
    return 1;
  } else
  {
    if (isexit(thing) && istemple(thing) && istemple(player))
    {
      return 1;
    } else
    {
      if (iswiz(thing) && !iswiz(player))
      {
	return 1;
      } else
      {
	return 0;
      }
    }
  }
}