TinyMAZE/
TinyMAZE/config/
TinyMAZE/doc/
TinyMAZE/run/msgs/
TinyMAZE/src/
TinyMAZE/src/db/
TinyMAZE/src/ident/
TinyMAZE/src/io/
TinyMAZE/src/prog/
TinyMAZE/src/softcode/
TinyMAZE/src/util/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "db.h"
#include "externs.h"
#include "config.h"
#include "softcode.h"
#include "scommands.h"
#include "funcs.h"

CODE *code_list = NULL;
CODE *code_ptr = NULL;

void show_line_and_end()
{
  if(!code_ptr)
    return;

  notify(code_ptr->executor, tprintf("%d %s",
    code_ptr->pline->line, code_ptr->pline->code));
  end_program(code_ptr->program, 0);
  return;
}

static int next_pid()
{
  static int pid = 0;
  CODE *c = NULL;
  int i;

  if(pid >= 65535)
    pid = 0;

  for(i = 0;i < 2;++i)
  {
    while(pid++ < 65535)
    {
      for(c = code_list;c;c = c->next)
      {
        if(c->pid == pid)
          break;
      }
      if(!c)
        break;
    }
    if(!c)
      break;
    if(pid == 65535)
      pid = 0;
  }

  if(i == 2)          /* No available pids */
    return(-1);

  return(pid);
}

int is_running(PROG *program)
{
  CODE *c;

  for(c = code_list;c;c = c->next)
    if(c->program == program)
      return(1);

  return(0);
}

void syntax_error()
{
  if(!code_ptr || !code_ptr->pline)
    return;

  notify(code_ptr->executor,
    tprintf("|+R|Syntax error on line |+Y|%d|+R|.",
    code_ptr->pline->line));
  notify(code_ptr->executor, tprintf("%d %s",
    code_ptr->pline->line, code_ptr->pline->code));

  end_program(code_ptr->program, 0);
}

void end_program(PROG *program, int showmsg)
{
  CODE *c, *cprev = NULL;
  SCVAR *var, *varnext;
  SCIF *scif, *scifnext;
  SCFOR *scfor, *scfornext;
  SCINPUT *input, *inputnext;
  SCGOSUB *gosub, *gosubnext;

  if(code_ptr && showmsg && !code_ptr->triggered)
    notify(code_ptr->executor, tprintf("|+G|Program |+Y|%s |+G|finished.",
      code_ptr->program->name));

  for(c = code_list;c;c = c->next)
  {
    if(c->program == program)
      break;
    cprev = c;
  }

  if(!c)
    return;

  if(!cprev)
    code_list = code_list->next;
  else
    cprev->next = c->next;

  for(var = c->vars;var;var = varnext)
  {
    varnext = var->next;

    stack_free(var->name);
    stack_free(var->cvalue);
    stack_free(var);
  }

  for(scif = c->iflist;scif;scif = scifnext)
  {
    scifnext = scif->next;
    stack_free(scif);
  }

  for(scfor = c->forlist;scfor;scfor = scfornext)
  {
    scfornext = scfor->next;
    stack_free(scfor);
  }

  for(gosub = c->gosublist;gosub;gosub = gosubnext)
  {
    gosubnext = gosub->next;
    stack_free(gosub);
  }

  for(input = c->input;input;input = inputnext)
  {
    inputnext = input->next;
    stack_free(input);
  }

  stack_free(c);

  code_ptr = NULL;
}

static void eat_whitespace(char *command)
{
  char *p;

  for(p = command;*p;p++)
  {
    if(*p == '"')
    {
      p++;
      while(*p && *p != '"')
        p++;
      continue;
    }

    if(isspace(*p))
      if(isspace(*(p+1)) || *(p+1) == '=')
      {
        extract_char(p);
        p--;
        continue;
      }

    if(*p == '=')
      if(isspace(*(p+1)))
      {
        extract_char(p+1);
        p--;
        continue;
      }
  }
}

static char *skip_quotes(char *str)
{
  while(*str == '"')
  {
    while(*str && *str != '"')
      str++;
    if(*str)
      str++;
  }

  return(str);
}

static void expand_variables(char *pure, char *retbuf)
{
  char *p, *s, *b, *v;
  int ch;         /* 0 if variable is an integer, 1 if char or string */
  char varname[4096];

  for(b = retbuf, p = pure;*p;)
  {
    ch = 0;

    if(*p == '"')
    {
      if(!*(s = skip_quotes(p)))
      {
        strcpy(b, p);
        return;
      }
      else
      {
        strncpy(b, p, s-p);
        *(b+(s-p)) = '\0';
      }
    }

    if(*p != '~')
    {
      *b++ = *p++;
      continue;
    }

    for(v = ++p;isalnum(*v);v++); /* Whole loop */
    strncpy(varname, p, v-p);
    *(varname+(v-p)) = '\0';

    if(*v == '$')
    {
      ch = 1;
      v++;
    }

    if(ch)
      strcpy(b, cvar_val(varname));
    else
      sprintf(b, "%d", ivar_val(varname));

    b += strlen(b);
    p = v;
  }

  *b = '\0';
}

static void parse_functions(char *str, char *retbuf)
{
  char buf[4096], buf2[4096];
  char buff[4096];             /* Pass this to the functions */
  char *args[10];
  int num_args;
  char *p, *q, *r, *s, *b = retbuf;
  int i, deep;
  int check = 0;
  struct fun *func;

  *retbuf = '\0';
  strcpy(buf, str);

  for(p = buf;*p;p++)
  {
    if(check++ > 999)
    {
      notify(code_ptr->executor,
        tprintf("|+R|Recursion check in |+Y|%d|+R|.",
        code_ptr->pline->line));
      show_line_and_end();
      return;
    }

    for(;;)
    {
      if(*p != '"')
        break;

      *b++ = *p++;
      while(*p && *p != '"')
        *b++ = *p++;
      *b = '\0';
      if(!*p)
        break;
      *b++ = *p++;
      *b = '\0';
    }

    if(*p == ']')
    {
      syntax_error();
      return;
    }
    if(*p != '[')
    {
      *b++ = *p;
      continue;
    }
    p++;            /* Don't save the '[' to the return buffer */
    if(*p == ']')
    {
      extract_char(p-1);
      extract_char(p-1);
      p = buf;
      p--;
      *retbuf = '\0';
      b = retbuf;
      continue;
    }
    if(!strchr(p, ']'))
    {
      syntax_error();
      return;
    }
    r = NULL;
    q = p;
    while((q = strchr(q, '(')))
      r = q++;
    q = r;
    if(!r)
    {
      b = retbuf;
      *b = '\0';
      continue;
    }
    r--;
    while(*r != '[' && *r != '(' && *r != ',')
      r--;
    r++;
    strncpy(buf2, r, q-r);
    *(buf2+(q-r)) = '\0';

    if(!(func = lookup_func(buf2)))
    {
      if(!chk_user_func(code_ptr->object, buf, r))
      {
        syntax_error();
        return;
      }

      p = buf;
      p--;
      b = retbuf;
      *b = '\0';
      continue;
    }

    s = buf2;
    *s = '\0';
    deep = 0;
    num_args = 0;
    for(i = 0;i < 10;++i)
      args[i] = NULL;

    for(++q;*q && *q != ')';q++)
    {
      switch(*q)
      {
        case ',':
          if(num_args < 9 && !deep)
          {
            args[num_args] = stack_string_alloc(buf2, 0);
            s = buf2;
            *s = '\0';
            num_args++;
          }
          break;
        case '{':
          deep++;
          break;
        case '}':
          if(!deep)
          {
            notify(code_ptr->executor,
              tprintf("|+R|Unmatched } in |+Y|%d|+R|.",
              code_ptr->pline->line));
            show_line_and_end();
            return;
          }
          deep--;
          break;
        default:
          if(!deep)
          {
            *s++ = *q;
            *s = '\0';
          }
      }
    }

    if(*buf2)
    {
      args[num_args] = stack_string_alloc(buf2, 0);
      num_args++;
    }

    if(!*q)
    {
      syntax_error();
      return;
    }

    if(deep)
    {
      notify(code_ptr->executor,
        tprintf("|+R|Unmatched { in |+Y|%d|+R|.",
        code_ptr->pline->line));
      show_line_and_end();
      return;
    }

    if(func->nargs != -1 && num_args != func->nargs)
    {
      notify(code_ptr->executor,
        tprintf("|+R|Expect %d args for function |+G|%s |+R|in |+Y|%d|+R|.",
        func->nargs, func->name, code_ptr->pline->line));
      show_line_and_end();
      return;
    }
    for(i = 0;i < num_args;++i)
    {
      if(*args[i] == '"')
      {
        extract_char(args[i]);
        if(*(args[i]+strlen(args[i])-1) != '"')
        {
          notify(code_ptr->executor,
            tprintf("|+R|Unterminated string in |+Y|%d|+R|.",
            code_ptr->pline->line));
          show_line_and_end();
          return;
        }
        extract_char(args[i]+strlen(args[i])-1);
      }
    }

    *buff = '\0';

    if(func->nargs == -1)
      deep = func->func(buff, args, code_ptr->executor, num_args);
    else
      deep = func->func(buff, args, code_ptr->executor);

    if(deep)
    {
      notify(code_ptr->executor,
        tprintf("|+R|Error returned from function %s()", func->name));
      notify(code_ptr->executor,
        tprintf("Message returned: %s", buff));
      end_program(code_ptr->program, 0);
      return;
    }

    while(r <= q)
    {
      extract_char(r);
      q--;
    }
    if(*(r-1) == '[' && *r == ']')
    {
      r--;
      extract_char(r);
      extract_char(r);
    }
    strncpy(buf2, buf, r-buf);
    *(buf2+(r-buf)) = '\0';
    if(func->type == FNUM)
      sprintf(buf2+strlen(buf2), "%s%s", buff, r);
    else
      sprintf(buf2+strlen(buf2), "\"%s\"%s", buff, r);
    strcpy(buf, buf2);
    b = retbuf;
    *b = '\0';
    p = buf;
    p--;
  }

/* Terminate retbuf */
  *b = '\0';
}

static void process_line(PROGLINE *pline)
{
  char command[4096];
  char buf[4096];
  char arg1[4096], arg2[4096], *pure1, *pure2;
  SCSET *s;

/* Make a copy of the code so we can modify it */
  strcpy(command, pline->code);

  eat_whitespace(command);

  for(pure1 = command;*pure1 && !isspace(*pure1);pure1++);

  if(!*pure1)
  {
    pure1 = "";
    pure2 = "";
  }
  else
  {
    *pure1++ = '\0';

    for(pure2 = pure1;*pure2 && *pure2 != '=';pure2++);

    if(!*pure2)
      pure2 = "";
    else
      *pure2++ = '\0';
  }

  expand_variables(pure1, arg1);
  expand_variables(pure2, arg2);

  parse_functions(arg1, buf);
  if(!code_ptr)
    return;
  strcpy(arg1, buf);
  parse_functions(arg2, buf);
  if(!code_ptr)
    return;
  strcpy(arg2, buf);

  for(s = softcode_command_set;*(s->name);s++)
    if(!string_compare(s->name, command))
      break;

  if(!*(s->name))
  {
    syntax_error();
    return;
  }

  if(s->flags & ARG1 || s->flags & PURE1)
  {
    if(s->flags & ARG1)
    {
      if(s->flags & ARG2 || s->flags & PURE2)
      {
        if(s->flags & ARG2)
          s->func(arg1, arg2);
        else
          s->func(arg1, pure2);
      }
      else
      {
        if(*pure2)
        {
          syntax_error();
          return;
        }
        s->func(arg1);
      }
    }
    else
    {
      if(s->flags & ARG2 || s->flags & PURE2)
      {
        if(s->flags & ARG2)
          s->func(pure1, arg2);
        else
          s->func(pure1, pure2);
      }
      else
      {
        if(*pure2)
        {
          syntax_error();
          return;
        }
        s->func(pure1);
      }
    }
  }
  else
  {
    if(*pure1 || *pure2)
    {
      syntax_error();
      return;
    }
    s->func();
  }
}

char *get_command(char *str)
{
  char command[4096];
  char *p;

  if(!(p = strchr(str, ' ')))
    strcpy(command, str);
  else
  {
    strncpy(command, str, p-str);
    *(command+(p-str)) = '\0';
  }

  return(stack_string_alloc(command, 0));
}

static void parse_one_line()
{
/* Sanity check */
  if(!code_ptr)
    return;

  if(code_ptr->wait)
  {
    if(now >= code_ptr->wait)
      code_ptr->wait = 0;
    else
      return;
  }

  if(code_ptr->input)
    return;

  while(!string_compare(get_command(code_ptr->pline->code), "rem"))
    code_ptr->pline = code_ptr->pline->next;

  process_line(code_ptr->pline);

/* See if the program was aborted */
  if(!code_ptr)
    return;

  if(code_ptr->advance)
    code_ptr->pline = code_ptr->pline->next;
  else
    code_ptr->advance =1;

  if(!code_ptr->pline)
    end_program(code_ptr->program, 1);
}

void run_softcode()
{
/* See if there's anything to execute */
  if(!code_list)
    return;

  if(!code_ptr)
    code_ptr = code_list;

  parse_one_line();

  if(code_ptr)
    code_ptr = code_ptr->next;

/* Keep the stack small */
  stack_unalloc();
}

static CODE *new_code()
{
  CODE *newcode, *c;

  newcode = (CODE *)stack_alloc(sizeof(CODE), 1, 0);

  newcode->program = NULL;
  newcode->pline = NULL;
  newcode->vars = NULL;
  newcode->iflist = NULL;
  newcode->forlist = NULL;
  newcode->gosublist = NULL;
  newcode->input = NULL;
  newcode->advance = 1;
  newcode->wait = 0;
  newcode->next = NULL;

  if(!code_list)
    code_list = newcode;
  else
  {
    for(c = code_list;c->next;c = c->next);
    c->next = newcode;
  }

  return(newcode);
}

static void setup_vars(OBJ *player, CODE *code, char **args)
{
  SCVAR *newvar;
  int i;
  bool last = 0;

/* Setup ~player$ */
  newvar = new_scvar(code, "player", VAR_STR);
  newvar->can_modify = 0;
  newvar->cvalue = stack_string_realloc(newvar->cvalue,
    tprintf("\"#%d\"", player->dbref));

/* Setup ~player */
  newvar = new_scvar(code, "player", VAR_NUM);
  newvar->can_modify = 0;
  newvar->nvalue = player->dbref;

/* Setup ~arg1$ through ~arg9$ for user softcode functions */
  for(i = 1;i < 10;++i)
  {
    newvar = new_scvar(code, tprintf("arg%d", i), VAR_STR);
    newvar->can_modify = 0;
  }

  for(i = 0;i < 10;++i)
  {
    newvar = new_scvar(code, tprintf("wm%d", i), VAR_STR);
    if(args && !last)
    {
      if(args[i])
        newvar->cvalue = stack_string_realloc(newvar->cvalue,
          tprintf("\"%s\"", args[i]));
      else
        last = 1;
    }
    newvar->can_modify = 0;
  }
}

static void run_it(OBJ *player, OBJ *thing, PROG *program, int start_line,
                   int triggered, char **args)
{
  CODE *newcode;
  int pid;

  if((pid = next_pid()) < 0)
  {
    notify(player, "No available PIDs!");
    return;
  }

  newcode = new_code();

  newcode->pid = pid;
  newcode->executor = player;
  newcode->object = thing;
  newcode->triggered = triggered;
  newcode->start_time = now;
  newcode->program = program;
  newcode->pline = program->program;

  setup_vars(player, newcode, args);

  if(start_line != 1)
    while(newcode->pline && newcode->pline->line != start_line)
      newcode->pline = newcode->pline->next;

  if(!newcode->pline)
  {
    notify(player, tprintf("Invalid start line: %d", start_line));
    end_program(newcode->program, 0);
    return;
  }
}

static int num_running(OBJ *player)
{
  CODE *c;
  int num = 0;

  for(c = code_list;c;c = c->next)
    if(c->executor == player)
      num++;

  return(num);
}

void do_run(OBJ *player, char *arg1, char *arg2)
{
  OBJ *thing;
  int start_line = 1;
  char *progname;
  PROG *program;
  char **args;
  int num_args = 0;
  char *p;

  if(num_running(player) >= config.max_queue)
  {
    notify(player, "You may not add anymore programs to the queue.");
    return;
  }

  if(*arg2)
  {
    if((p = strchr(arg2, ':')))
    {
      *p++ = '\0';
      start_line = atoi(p);
      if(start_line < 1 || start_line > 65535)
      {
        notify(player, tprintf("Invalid start line: %d", start_line));
        return;
      }
    }
  }

  if(!(progname = strchr(arg1, '/')))
  {
    notify(player, "Usage: run <object>/<program>");
    return;
  }

  *progname++ = '\0';

  thing = match_object(player, arg1, NOTYPE);
  if(!thing)
  {
    notify(player, no_match(arg1));
    return;
  }

  if(!(program = find_prog(thing, progname)))
  {
    notify(player, tprintf("Program %s doesn't exist on %s.",
      progname, unparse_object(player, thing)));
    return;
  }

  if(!could_doit(player, thing, "LUSE") && !power(player, POW_SECURITY))
  {
    notify(player, perm_denied());
    return;
  }

  args = (char **)stack_alloc(sizeof(char *), 0, 0);
  args[0] = NULL;

/* Parse up the args */
  while((p = parse_up(&arg2, ',')))
  {
    args = (char **)stack_realloc_tmp(args, sizeof(char *)*(num_args+2));
    args[num_args++] = p;
    args[num_args] = NULL;
  }

  run_it(player, thing, program, start_line, 0, args);
}

void do_kill(OBJ *player, char *arg)
{
  int pid;
  char *pidlist = NULL;
  CODE *c, *cnext;

  if(!strcmp(arg, "ALL"))
  {
    for(c = code_list;c;c = cnext)
    {
      cnext = c->next;

      if(c->executor == player || power(player, POW_QUEUE))
      {
        if(!pidlist)
          pidlist = stack_string_alloc(tprintf("%d", c->pid), 0);
        else
          pidlist = stack_string_realloc_tmp(pidlist,
            tprintf("%s, %d", pidlist, c->pid));
        end_program(c->program, 0);
      }
    }

    if(!pidlist)
      notify(player, "No processes to kill.");
    else
      notify(player, tprintf("Process(es) %s killed.", pidlist));

    return;
  }

  pid = atoi(arg);

  if(pid < 1 || pid > 65535)
  {
    notify(player, "Invalid PID.");
    return;
  }

  for(c = code_list;c;c = c->next)
    if(c->pid == pid)
      break;

  if(!c)
  {
    notify(player, tprintf("No such process: %d", pid));
    return;
  }

  notify(player, tprintf("Program %s(%d) killed.",
    c->program->name, pid));
  end_program(c->program, 0);
}

void show_softcode_queue(OBJ *player)
{
  int num = 0;
  CODE *c;

  for(c = code_list;c;c = c->next)
  {
    if(c->object != player && !power(player, POW_QUEUE))
      continue;

    if(!num++)
    {
      notify(player, "|+W|softcode commands:");
      notify(player, "|+G| PID  PROGRAM  LINE  OBJECT          EXECUTOR");
    }

    notify(player, tprintf("|+Y|%-5d %s %-5d %s %s", c->pid,
      my_ljust(c->program->name, 8), c->pline->line,
      my_ljust(unparse_object(player, c->object), 15),
      unparse_object(player, c->executor)));
  }

  if(!num)
    notify(player, "|+W|No programs in softcode queue.");
}

int check_softcode_match(OBJ *player, char *command)
{
  OBJ *thing;
  PROG *p = NULL;
  char **args;

  if(num_running(player) >= config.max_queue)
    return(0);

  for(thing = player->location->contents;thing;thing = thing->next_con)
  {
    for(p = thing->program;p;p = p->next)
      if(wildcard_match(p->trigger, command, &args))
        break;

    if(p)
      break;
  }

  if(!p || !thing)
  {
    for(p = player->location->program;p;p = p->next)
      if(wildcard_match(p->trigger, command, &args))
        break;

    if(!p)
      return(0);

    thing = player->location;
  }

  if(!could_doit(player, thing, "LUSE"))
    return(0);

  run_it(player, thing, p, 1, 1, args);

  return(1);
}