#ifdef RCS
static char rcsid[]="$Id: parse.c,v 1.1.1.1 2000/11/13 02:42:45 holsta Exp $";
#endif
/******************************************************************************
 *                    Internetting Cooperating Programmers
 * ----------------------------------------------------------------------------
 *
 *  ____    PROJECT
 * |  _ \  __ _ _ __   ___ ___ _ __ 
 * | | | |/ _` | '_ \ / __/ _ \ '__|
 * | |_| | (_| | | | | (_|  __/ |   
 * |____/ \__,_|_| |_|\___\___|_|   the IRC bot
 *
 * All files in this archive are subject to the GNU General Public License.
 *
 * $Source: /cvsroot/dancer/dancer/src/parse.c,v $
 * $Revision: 1.1.1.1 $
 * $Date: 2000/11/13 02:42:45 $
 * $Author: holsta $
 * $State: Exp $
 * $Locker:  $
 *
 * ---------------------------------------------------------------------------
 *****************************************************************************/

/******************************************************************************
 * Thoughts and theories...

 Hi!

 Here are some of my confused and mixed first plans and thougts around
 first the aliases we've been discussing before, but then also a little
 about how the future "extensions" of the bot could look like. Overkill
 for a bot? naah... ;)

alias COMMAND REPLACEMENT - sets an alias

REPLACEMENT can be any sequence of
 - plain text like in

        alias commandlist cmdslist

 - arguments from the entered sequence, specified with $<number> when making
   the alias, like in:

        alias fastban ban $1 10

   Writing it like $2- means all arguments from the 2nd to the end of the
   line, like in

        alias msg do privmsg $1 : $2-

   Writing it like $2? means number two if there is any number 2! Can also
   be used together with - like in:

        alias kick DO kick $channel $1 :$2?-

 - generic 'variables' and inline information, specified with $<name> for
   the info you'd like added at the place, like in:

        alias topic do topic $channel :$1-

 - $-letters for inclusion in the aliased command must be written with two
   of them in a row, like in:

        alias dollars say please send me some $$ now!!!

 - built-in functionality for random picks, like:

        alias killme $( ban $nick $| say no way, you're my friend $)

   or more sofisticated:

        alias saysome $( kick $nick $| $(say hi $| say no $) $)

   lots of alternatives:

        alias tryme $(ban $nick $| say one $| say two $| say three $)

 - alias-controlled "new command" separator ($;), like in:

        alias killme say my pleasure! $; ban $nick


 Variables/info that might be interesting to offer:

  $channel - Channel name of the bot
  $nick    - Nickname of the user
  $bot     - Nickname of the bot
  $time    - Bot time

  $year
  $date


 We should here consider adding $-variables like:
 * outputed text of a command (the slightly unix inspired) $'command'
 * more "low level" variables


 In an even more advanced level, I suggest full FPL support to enable
 (at least) the bot maintainer to fix things like

        alias -fpl mykick mykick(\" $1 \");

 which builds an FPL program line that is run, the mykick() in the above
 example line has of course already been defined in a file like:

        export void mykick(char *nick)
        {
          if(Joined(nick))
            Printf("%s: BOO!!", nick);
          else
            Printf("%s: you missed him! ;(", _nick); // built-in variable
        }

#
# Advanced setup file for new commands/context control for Dancer.
#
# Syntax of this file is: 'FLAGS STRING(S) [ACTION]'
#
# FLAGS:
# C   - straight command alias (used as normal commands)
# P   - publically available   (for commands)
# D   - DCC chat required      (for commands)
# *   - password required      (for commands)
# !0-9 - (in a sequence like !50 or !25) level required to trigger
# +0-9 - highest command level allowed to trigger (below level to trigger)
# S   - string match (anyware on the line)
# E   - regex match (anyware on the line)
# F   - string match (first on the line)
# =   - the rest of the line is treated as a help-text for the command/
#       alias written on the previous line
# &   - the rest of the line is treated as a syntax-description for the
#       command/alias
#
# STRING(S)
# one or more strings according to the flags/actions wanted.
# white spaces can only be used if the string is placed within quotes as
# in "white space". Several strings are written like one,two,three.
#
# ACTION
# A complete Dancer command line. With $-style replacements as documented
# elsewhere.
#
C!20 topic do topic $channel :$1-
= Change topic on the currrent channel.
& <topic>
C!50 kick  do kick $channel: $1 $2?-
= Kick the specified user from the channel. Optionally with a given kick message.
& <user> [message]
F!0  brb   say Hurry back $user!
F!0  bbl   say I'll be waiting for you, $user!


******************************************************************************/

#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "parse.h"
#include "list.h"
#include "function.h"
#include "transfer.h"
#include "command.h"


#define ALIAS_MAX_ARGS 16 /* Allocation steps */

#define ARG_TYPEMASK   0x0f

#define ARG_RESTOFLINE 0x10 /* from this parameter to the rest of line */
#define ARG_IFPOSSIBLE 0x20 /* if this parameter was specified */

typedef enum {
   ARG_NONE,
   ARG_PAR,      /* parameter from the line when alias command is invoked */
   ARG_VAR,      /* program variable */
   ARG_WORD,     /* straight word */
   ARG_PICKLIST, /* a list with a selection of different picks (pick) */
   ARG_ENDOFCMD, /* push away the left as one separate command, another one
                    follows! ;) */
} aliasArguments;

typedef struct Alias {
  struct Alias *next; /* in a "linked case" (pick) */
  long amount;        /* number of links in a "linked case" (pick) */
  long reqargs;       /* required amount of arguments required for the alias to run */
  long args;          /* args in the data list */
  long *flags;
  void **data;
  long entries;       /* number of array entries allocated */
} itemalias;

typedef struct FuncStruct {
  struct Header h;
  char *name;     /* name of the new func-alias */
  itemalias *alias;
  long lowlevel;  /* lowest required status */
  long highlevel; /* highest required status */
  int percent;    /* likeliness 0 - 100 % */
  long flags;     /* mode stuff */
  char *help;     /* possible help text */
  char *syntax;   /* possible syntax description */
  int id;         /* unique id for the func-alias */
} itemfunc;

extern char *errfrom;

itemfunc *funcHead = NULL;


static int funcid = 0;

static char *PutAlias(itemalias *a, char *name, char *line, char *buffer, size_t buffer_size)
{
  static char *argv[ALIAS_MAX_ARGS];
  static int argc = 0;
  int i, j;

  snapshot;
  buffer[0] = (char)0;

  if (name && line) {
    /* Split into arguments */
    argc = 0;

    argv[argc++] = name;

    do {
      while (iswhite(*line))
        line++;

      if ((char)0 == *line)
        break;

      if ('\"' == *line) {
        argv[argc++] = ++line;
        while (*line && ('\"' != *line))
          line++;
      }
      else {
        argv[argc++] = line;
        while (isgraph(*line))
          line++;
      }

      if ((argc < ALIAS_MAX_ARGS) && *line)
        *line++ = (char)0;

    } while (argc < ALIAS_MAX_ARGS);
  }

  if (argc <= a->reqargs)
    return "not enough arguments";

  for (i = 0; i < a->args; i++) {
    switch (a->flags[i] & ARG_TYPEMASK) {

      case ARG_PAR:
        if (argc <= (int)a->data[i]) /* ARG_IFPOSSIBLE */
          continue; /* for */

        StrAppendMax(buffer, buffer_size, argv[(int)a->data[i]]);

        if (a->flags[i] & ARG_RESTOFLINE) {
          for (j = (int)a->data[i] + 1; j < argc; j++)
            StrFormatAppendMax(buffer, buffer_size, " %s", argv[j]);
        }

        break;

      case ARG_WORD:
        StrAppendMax(buffer, buffer_size, (char *)a->data[i]);
        break;

      case ARG_ENDOFCMD:
        StrAppendMax(buffer, buffer_size, "\n");
        break;

      case ARG_PICKLIST:
        {
          itemalias *alias = (itemalias *)a->data[i];
          char *errptr;
          int rnd;

          if (alias->amount) {
            rnd = (int)(Rnd() * (alias->amount + 1));
            while (rnd-- && alias->next)
              alias = alias->next;
          }

          j = StrLength(buffer);
          if (errptr = PutAlias(alias, NULL, NULL, &buffer[j], buffer_size - j))
            return errptr;
        }
        break;

      case ARG_VAR:
        {
          char *str;

          str = VarInfo((lineVariables)a->data[i]);
          if (str)
            StrAppendMax(buffer, buffer_size, str);
          else
            return "unknown variable";
        }
        break;

      default:
        break;

    }
  }

  return NULL;
}

/* --- AliasAddArgument ------------------------------------------- */

char *AliasAddArgument(itemalias *a, void *data, aliasArguments flags)
{
  snapshot;
  if (a) {
    if (a->entries <= a->args) {
      void *newdata, *newflags;

      newdata = realloc((void *)a->data, (a->entries + ALIAS_MAX_ARGS) * sizeof(void *));
      if (newdata) {
        newflags = realloc((void *)a->flags, (a->entries + ALIAS_MAX_ARGS) * sizeof(long));
        if (newflags) {
          a->data = (void **)newdata;
          a->flags = (long *)newflags;
          a->entries += ALIAS_MAX_ARGS;
        }
        else {
          free(newdata);
        }
      }

      if (a->entries <= a->args)
        return "out of memory";
    }

    switch (flags) {

      case ARG_WORD:
        data = (void *)StrDuplicate((char *)data);
        if (NULL == data)
          return "out of memory";
        break;

      default:
        break;

    }

    a->data[a->args] = data;
    a->flags[a->args] = (long)flags;
    a->args++;
  }

  return NULL; /* OK, or at least we assume so */
}

/* --- FreeAlias -------------------------------------------------- */

void FreeAlias(void *v)
{
  int i;
  itemalias *a, *next;

  snapshot;
  for (a = (itemalias *)v; a; a = next) {
    next = a->next;

    for (i = 0; i < a->args; i++) {
      switch (a->flags[i] & ARG_TYPEMASK) {

        case ARG_WORD:
          StrFree((char *)a->data[i]);
          break;

        case ARG_PICKLIST:
          FreeAlias(a->data[i]);
          break;

        default:
          break;

      }
    }

    if (a->data)
      free((void *)a->data);
    if (a->flags)
      free((void *)a->flags);

    free(a);
  }
}

char *AliasAddFailed(itemalias *a, char *errptr)
{
  snapshot;
  if (a) {
    FreeAlias(a);
  }

  return errptr;
}

char *AliasAdd(itemalias **alias, char **string)
{
  char *ptr, *nextptr, *errptr;
  itemalias *newone;

  snapshot;
  *alias = NULL;

  newone = NewEntry(itemalias);
  if (NULL == newone)
    return "out of memory";

  ptr = *string;
  while (nextptr = StrIndex(ptr, '$')) {
    if (nextptr == ptr) {
      nextptr++;

      switch (*nextptr) {

        case '$':
          if (errptr = AliasAddArgument(newone, "$", ARG_WORD))
            return AliasAddFailed(newone, errptr);
          ptr = nextptr + 1;
          break;

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
          {
            long argnum;
            aliasArguments arg = 0;

            argnum = StrToLong(nextptr, &ptr, 0);

            if ('?' == *ptr) {
              arg |= ARG_IFPOSSIBLE;
              ptr++;
            }
            else if (newone->reqargs < argnum) {
              newone->reqargs = argnum;
            }

            if ('-' == *ptr) {
              arg |= ARG_RESTOFLINE;
              ptr++;
            }

            if (errptr = AliasAddArgument(newone, (void *)argnum, (arg | ARG_PAR)))
              return AliasAddFailed(newone, errptr);
          }
          break;

        case '(':
          {
            /* Random-pick list coming up, gee, big fun */
            itemalias *alter;
            itemalias *prev;
            itemalias *first;
            char c;

            nextptr++;
            if (errptr = AliasAdd(&first, &nextptr))
              return AliasAddFailed(newone, errptr);

            if (errptr = AliasAddArgument(newone, first, ARG_PICKLIST)) {
              FreeAlias(first);
              return AliasAddFailed(newone, errptr);
            }

            prev = first;
            for (c = *nextptr++; c != ')'; c = *nextptr++) {
              if ('|' == c) {
                if (errptr = AliasAdd(&alter, &nextptr))
                  return AliasAddFailed(newone, errptr);

                prev->next = alter;
                prev = alter;
                first->amount++;
              }
              else
                return AliasAddFailed(newone, "illegal random-pick sequence");
            }

            ptr = nextptr;
          }
          break;

        case '|':
        case ')':
          *alias = newone;
          *string = nextptr;
          return NULL;

        case ';':
          if (errptr = AliasAddArgument(newone, NULL, ARG_ENDOFCMD))
            return AliasAddFailed(newone, errptr);

          ptr = nextptr + 1;
          while (iswhite(*ptr))
            ptr++;
          break;

        default:
        {
          char name[MINIBUFFER];
          lineVariables var = 0;

          /* Variable name coming up! */
          if (1 == StrScan(nextptr, "%"MINIBUFFERTXT"[a-zA-Z0-9]", name)) {
            if (StrEqual(name, "CHANNEL"))
              var = VAR_CHANNEL;
            else if (StrEqual(name, "NICK"))
              var = VAR_NICKNAME;
            else if (StrEqual(name, "BOT"))
              var = VAR_BOTNAME;
          }

          if (0 == var)
            return AliasAddFailed(newone, "unknown variable");

          if (errptr = AliasAddArgument(newone, (void *)var, ARG_VAR))
            return AliasAddFailed(newone, errptr);

          ptr = nextptr + StrLength(name);
          break;
        }
      }
    }
    else {
      ptr = StrDuplicateMax(ptr, nextptr - ptr + 1);
      if (ptr) {
        if (errptr = AliasAddArgument(newone, ptr, ARG_WORD))
          return AliasAddFailed(newone, errptr);
        StrFree(ptr);
      }

      ptr = nextptr;
    }
  }

  if (*ptr) {
    if (errptr = AliasAddArgument(newone, ptr, ARG_WORD))
      return AliasAddFailed(newone, errptr);
  }

  *alias = newone;
  return NULL;
}

/* --- FuncRun ---------------------------------------------------- */

char *FuncRun(char *from,  /* from who it came */
              char *cmd,   /* actual command name */
              char *param, /* parameters */
              long status, /* status of the user using this */
              long mode,   /* flags about this */
              char oktorun) /* no level checks */
{
  static char buffer[KILOBUFFER];
  char *errptr;
  itemfunc *f;

  snapshot;
  for (f = First(funcHead); f; f = Next(f)) {
    if ((oktorun || (f->lowlevel <= status)) && StrEqual(f->name, cmd)) {
      if (!oktorun) {
        if ((0 == (f->flags & FUNC_PUBLIC)) && (mode & FUNC_PUBLIC)) {
          Send(errfrom, GetText(msg_cant_use_in_public));
          break;
        }

        if ((f->flags & FUNC_DCC) && (0 == (mode & FUNC_DCC))) {
          Send(errfrom, GetText(msg_use_chat));
          break;
        }

        if ((f->flags & FUNC_PWD) && (0 == (mode & FUNC_PWD))) {
          Send(errfrom, GetText(msg_use_pass_first));
          break;
        }
      }

      errptr = PutAlias(f->alias, cmd, param, buffer, sizeof(buffer));
      if (errptr) {
        Send(errfrom, errptr);
        return NULL;
      }

      return buffer; /* This will be run as a standard command line */
    }
  }
  return NULL;
}

/* --- FuncMatch -------------------------------------------------- */

char *FuncMatch(char *from,  /* from who it came */
                char *line,  /* text line */
                long status, /* status of the user using this */
                long mode)   /* flags about this */
{
  static char buffer[KILOBUFFER];
  char *errptr;
  itemfunc *f;

  snapshot;
  for (f = First(funcHead); f; f = Next(f)) {
    if (f->flags & (FUNC_MATCH | FUNC_REGEX | FUNC_STRINGFIRST)) {
      if ((f->lowlevel <= status) && (status <= f->highlevel)) {
        if (f->flags & FUNC_MATCH) {
          if (!StrMatch(line, f->name))
            continue;
        }
        else if (f->flags & FUNC_REGEX) {
          if (!PatternExist(line, f->name))
            continue;
        }
        else { /* (f->flags & FUNC_STRINGFIRST) */
          if (!StrEqualMax(line, StrLength(f->name), f->name))
            continue;
        }

        if (f->percent < 100) {
          /* Not to be done always */
          if (f->percent <= (int)(Rnd() * 100))
            continue;
        }

        errptr = PutAlias(f->alias, f->name, line, buffer, sizeof(buffer));
        if (errptr) {
          Send(errfrom, errptr);
          return NULL;
        }

        return buffer; /* This will be run as a standard command line */
      }
    }
  }

  return NULL;
}

/* --- FuncAddHelp ------------------------------------------------ */

void FuncAddHelp(itemfunc *f, char *line)
{
  char help[BIGBUFFER];

  snapshot;
  if (1 == StrScan(line, "%"BIGBUFFERTXT"[^\n]", help)) {
    if (f->help)
      StrFree(f->help);
    f->help = StrDuplicate(help);
  }
}

/* --- FuncAddSyntax ---------------------------------------------- */

void FuncAddSyntax(itemfunc *f, char *line)
{
  char syntax[BIGBUFFER];

  snapshot;
  if (1 == StrScan(line, "%"BIGBUFFERTXT"[^\n]", syntax)) {
    if (f->syntax)
      StrFree(f->syntax);
    f->syntax = StrDuplicate(syntax);
  }
}

/* --- FuncAddItem ------------------------------------------------ */

itemfunc *FuncAddItem(char *line)
{
  char flagbuffer[MINIBUFFER], namebuffer[MIDBUFFER], aliasbuffer[KILOBUFFER];
  char *pointer, *errptr;
  long flags = 0;
  long low = 0;
  long high = 999999999;
  int percent = 100;
  itemalias *a;
  itemfunc *f;

  snapshot;
  if ((3 == StrScan(line, "%"MINIBUFFERTXT"s \"%"MIDBUFFERTXT"[^\"]\" %"KILOBUFFERTXT"[^\n]",
                    flagbuffer, namebuffer, aliasbuffer)) ||
      (3 == StrScan(line, "%"MINIBUFFERTXT"s %"MIDBUFFERTXT"s %"KILOBUFFERTXT"[^\n]",
                    flagbuffer, namebuffer, aliasbuffer))) {

    pointer = flagbuffer;
    while (*pointer) {
      switch (toupper(*pointer++)) {

        case 'C':
          flags |= FUNC_REGULAR;
          break;

        case 'P':
          flags |= FUNC_PUBLIC;
          break;

        case 'D':
          flags |= FUNC_DCC;
          break;

        case '*':
          flags |= FUNC_PWD;
          break;

        case '!':
          flags |= FUNC_LOWLEVEL;
          low = StrToLong(pointer, &pointer, 0);
          break;

        case '+':
          flags |= FUNC_HIGHLEVEL;
          high = StrToLong(pointer, &pointer, 0);
          break;

        case '%':
          percent = StrToLong(pointer, &pointer, 0);
          break;

        case 'S':
          flags |= FUNC_MATCH;
          break;

        case 'E':
          flags |= FUNC_REGEX;
          break;

        case 'F':
          flags |= FUNC_STRINGFIRST;
          break;

        default:
          break;

      }
    }

    pointer = aliasbuffer;
    errptr = AliasAdd(&a, &pointer);
    if (errptr) {
      StrScan(line, "%"BIGBUFFERTXT"[^\n]", aliasbuffer);
      Logf(LOGINIT, "Func AliasAdd() error: %s\nLINE: %s", errptr, aliasbuffer);
      return NULL;
    }

    f = NewEntry(itemfunc);
    if (f) {
      f->name = StrDuplicate(namebuffer);
      if (f->name) {
        InsertLast(funcHead, f);
        f->alias     = a;
        f->lowlevel  = low;
        f->highlevel = high;
        f->percent   = percent;
        f->flags     = flags;
        f->id        = ++funcid;
        return f;
      }
      free(f);
    }
    FreeAlias(a);
  }
  return NULL;
}

/* --- FuncLoad --------------------------------------------------- */

bool FuncLoad(char *filename)
{
  char line[2048];
  itemfunc *f = NULL;
  FILE *file;

  snapshot;
  if ((NULL == funcHead) || (NULL == filename) || ((char)0 == filename[0]))
    return FALSE;

  file = fopen(filename, "r");
  if (file) {
    while (fgets(line, sizeof(line), file)) {
      switch (line[0]) {

        case '#':
        case '\n':
          break;

        case '=':
          if (f)
            FuncAddHelp(f, line + 1);
          break;

        case '&':
          if (f)
            FuncAddSyntax(f, line + 1);
          break;

        default:
          f = FuncAddItem(line);
          break;

      }
    }
    fclose(file);

    return TRUE;
  }
  return FALSE;
}

/* --- FuncInit --------------------------------------------------- */

void FuncInit(void)
{
  extern char funcfile[];

  snapshot;
  funcHead = NewList(itemfunc);
  FuncLoad(funcfile);
}

void FreeFunc(void *v)
{
  itemfunc *f;

  snapshot;
  f = (itemfunc *)v;
  if (f) {
    if (f->name)
      StrFree(f->name);
    if (f->help)
      StrFree(f->help);
    if (f->syntax)
      StrFree(f->syntax);
    if (f->alias)
      FreeAlias(f->alias);
  }
}

/* --- FuncCleanup ------------------------------------------------ */

void FuncCleanup(void)
{
  snapshot;
  DeleteList(funcHead, FreeFunc);
}

/* --- FuncReload ------------------------------------------------- */

void FuncReload(char *filename)
{
  snapshot;
  FlushList(funcHead, FreeFunc);
  FuncLoad(filename);
}

#ifdef FUNCS_HACK
/* --- FuncAdd ---------------------------------------------------- */

void FuncAdd(char *from, char *line)
{
  itemfunc *f;

  snapshot;
  f = FuncAddItem(line);
  if (f) {
    Sendf(from, "Added new func \"%s\"", f->name);
  }
  else
    CmdSyntax(errfrom, "FUNCADD");
}

/* --- FuncDel ---------------------------------------------------- */

void FuncDel(char *from, char *line)
{
  char *ptr;
  bool deleted = FALSE;
  int number;
  itemfunc *f, *next;

  snapshot;
  ptr = NextQuoteWord(line);
  if (ptr && *ptr) {
    number = atoi(ptr);

    for (f = First(funcHead); f; f = next) {
      next = Next(f);
      if ((number && (f->id == number)) || StrMatch(f->name, ptr)) {
        Sendf(from, "%d. \"%s\" removed", f->id, f->name);
        DeleteEntry(funcHead, f, FreeFunc);
        deleted = TRUE;
      }
    }

    if (!deleted)
      Sendf(from, "Func \"%s\" not found for deletion", ptr);
  }
  else
    CmdSyntax(errfrom, "FUNCDEL");
}

/* --- FuncList --------------------------------------------------- */

void FuncList(char *from, char *line)
{
  itemfunc *f;

  snapshot;
  if (First(funcHead)) {
    for (f = First(funcHead); f; f = Next(f))
      Sendf(from, "%d. \"%s\"", f->id, f->name);
  }
  else {
    Send(from, "No funcs to list");
  }
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1