%{
/*
 * $Id: overlord.l,v 1.24 2003/01/03 17:23:07 sean Exp $
 *
 * lexical scanner for overlord policy language
 *
 * Copyright, 2001-2003, Astra Network Inc.  All Rights Reserved
 *
 * This source code has been published by Astra Network Inc. However, any
 * use, reproduction, modification, distribution or transfer of this
 * software, or any software which includes or is based upon any of this
 * code, is only permitted if expressly authorized by a written license
 * agreement from Astra. Contact your Astra representative directly for
 * more information.
 *
 */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "defines.h"
#include "overlord.tab.h" // token definitions created by bison

int yylineno = 0;
int yytokenpos = 0;
char yylinebuf[500];

static int rc;

typedef struct filestack
{
  YY_BUFFER_STATE buf;
  char *name;
  int lineno;
  int tokenpos;

  struct filestack *next;
} filestack_t;

static filestack_t *filestack_head = NULL;
static char push_buffer(YY_BUFFER_STATE buf, char *name);
static YY_BUFFER_STATE pop_buffer(void);
int ident_keyword( char *lex );
static void define_token(char *text);
static void include_file(char *fn);
char *extract_filename(char *text);

%}
let      [_a-zA-Z]     
alnum    [_a-zA-Z0-9]  
h        [0-9a-fA-F]   
o        [0-7]         
d        [0-9]         
white    [\x00-\s]     
suffix   [kKmM]        

%%

^.*$                   { strcpy(yylinebuf, yytext);
                         yylineno++;
                         yytokenpos = 0;
                         REJECT; // Let the line get parsed.
                       }

\"([^\"\n\r]|(\\\"))*\" {
                         yytokenpos += yyleng;
                         yylval.string = yytext;
                         return STRING;
                       }

\"([^\"\n\r]|(\\\"))*[\r\n] {
                           yyerror("String constant missing ending \" character");
                           yylineno++;
                           return STRING;
                       }

0{o}*                  |
0x{h}+                 |
[1-9]{d}*              {
                         yytokenpos += yyleng;
                         sscanf(yytext, "%i", &(yylval.value));
                         return NUM;
                       }

[1-9]{d}*{suffix}      {
                         yytokenpos += yyleng;
                         yylval.value = size2num( yytext );
                         return NUM;
                       }

"("                    { yytokenpos += yyleng; return '('; }
")"                    { yytokenpos += yyleng; return ')'; }
"{"                    { yytokenpos += yyleng; return '{'; }
"}"                    { yytokenpos += yyleng; return '}'; }

"*"                    { yytokenpos += yyleng; return '*'; }
[/%]                   { yytokenpos += yyleng; return '/'; }
"+"                    { yytokenpos += yyleng; return '+'; }
"-"                    { yytokenpos += yyleng; return '-'; }

"="                    { yytokenpos += yyleng; return '='; }
"=="                   { yytokenpos += yyleng; return EQ; }
"!="                   { yytokenpos += yyleng; return NE; }
"<"                    { yytokenpos += yyleng; return '<'; }
">"                    { yytokenpos += yyleng; return '>'; }
"<="                   { yytokenpos += yyleng; return LTE; }
">="                   { yytokenpos += yyleng; return GTE; }

"||"                   { yytokenpos += yyleng; return OROR; }
"&&"                   { yytokenpos += yyleng; return ANDAND; }
":"                    { yytokenpos += yyleng; return ':'; }
";"                    { yytokenpos += yyleng; return ';'; }
","                    { yytokenpos += yyleng; return ','; }

[Ii][Nn][Cc][Ll][Uu][Dd][Ee][ \t]+\".+\"[ \t\n\r]*";"  { yytokenpos += yyleng; include_file(extract_filename(yytext)); }
[Ii][Nn][Cc][Ll][Uu][Dd][Ee][ \t]+\".+\"  { yytokenpos += yyleng; yyerror ("parse error, expecting ';'"); }
[Dd][Ee][Ff][Ii][Nn][Ee][ \t]+{let}{alnum}*[ \t]+.*"//".*$ { yyerror("Comments are not allowed in DEFINE definitions");
                                                       yytokenpos += yyleng; }
[Dd][Ee][Ff][Ii][Nn][Ee][ \t]+{let}{alnum}*[ \t]+.*$ { yytokenpos += yyleng; 
                                                       define_token (yytext); }

{let}{alnum}*          { yytokenpos += yyleng;
                         rc = ident_keyword( yytext ); 
                         if (rc != -1) return (rc); }

"//".*$                { /* ignore comments */ }

[ \t]+                 yytokenpos += yyleng;
\n                     { /* ignore newline */ }
.                      yyerror("Illegal char <%s>\n", yytext );
%%

typedef struct
{
  char *name;
  int val;
} KWORD;

KWORD Ktab[] = 
{
  { "dependadd",   DEPENDADD },
  { "dependfail",  DEPENDFAIL },
  { "dependremove",DEPENDREMOVE },
  { "die",         DIE },
  { "else",        ELSE },
  { "endif",       ENDIF },
  { "endloop",     ENDLOOP },
  { "false",       TOK_FALSE },
  { "grow",        GROW },
  { "if",          IF },
  { "isalive",     ISALIVE },
  { "isup",        ISUP },
  { "loop",        LOOP },
  { "module",      MODULE },
  { "num",         ARG_NUM },
  { "proc",        PROC },
  { "process",     PROCESS },
  { "restart",     RESTART },
  { "return",      RETURN },
  { "sigchld",     TOK_SIGCHLD },
  { "sigusr1",     TOK_SIGUSR1 },
  { "sigusr2",     TOK_SIGUSR2 },
  { "stackpolicy", STACKPOLICY },
  { "stacksize",   STACKSIZE },
  { "start",       START },
  { "string",      ARG_STRING },
  { "system",      SYSTEM },
  { "true",        TOK_TRUE },
};

typedef struct defined_token
{
  char *ident;
  char *replace;

  struct defined_token *next;
} defined_token_t;

static defined_token_t *defines = NULL;

static int cmp( const void *a, const void *b )
{
  KWORD *wa = (KWORD *)a;
  KWORD *wb = (KWORD *)b;
  return strcmp( wa->name, wb->name );
}

static void lower_string(char *str)
{
  while (*str)
  {
    *str = tolower(*str);
    str ++;
  }
}

int ident_keyword( char *lex )
{
  KWORD *p;
  KWORD dummy;
  defined_token_t *def;
  unsigned int ln;

  lower_string (lex); // language is case insensitive, but pcode is not!

  dummy.name = lex;
  p = bsearch( &dummy, Ktab, sizeof(Ktab)/sizeof(KWORD), sizeof(KWORD), cmp );

  /** If we've "define"d it, lex the replacement string **/
  if (!p)
  {
    def = defines;
    while (def)
    {
      if (strcmp(def->ident, lex) == 0)
      {
        ln = yylineno; // Preserve line number.
        if (push_buffer(YY_CURRENT_BUFFER, lex))
        {
          yylineno = ln;
          yy_switch_to_buffer(yy_scan_string(def->replace));
        }
        return (-1); // Flag to lexer not to return
      }
      def = def->next;
    }
  }

  yylval.string = strdup( lex );
  if( yylval.string == NULL )
     {
       yyerror( "Out of memory\n");
       exit( -1 );
     }

  return p ? p->val : IDENT;
}

char push_buffer (YY_BUFFER_STATE buf, char *name)
{
  filestack_t *node;
  filestack_t *prevnode;

  // See if we're already including the file -- any recursion will cause
  // infinite loops.
  node = filestack_head;
  prevnode = NULL;
  while(node)
  {
    if (strcmp (node->name, name) == 0)
    {
      if (prevnode)
        yyerror ("\"%s\" refers to itself in \"%s\"", name, prevnode->name);
      else
        yyerror ("\"%s\" refers to itself", name);
      return (0);
    }
    prevnode = node;
    node = node->next;
  }

  node = malloc(sizeof(filestack_t));
  if (!node)
    yyfatalerror("Out of memory");

  node->name = strdup(name);
  if (!node->name)
    yyfatalerror("Out of memory");

  node->buf = buf;
  node->lineno = yylineno;
  node->tokenpos = yytokenpos;
  yylineno = 1;
  yytokenpos = 0;
  node->next = filestack_head;
  filestack_head = node;

  return(1);
}
  
YY_BUFFER_STATE pop_buffer(void)
{
  filestack_t *node;
  YY_BUFFER_STATE buf;

  node = filestack_head;
  if (node)
  {
    filestack_head = node->next;
    buf = node->buf;
    yylineno = node->lineno;
    yytokenpos = node->tokenpos;
    free (node);
    return (buf);
  }
  return (NULL);
}      

char *extract_filename(char *text)
{
  strtok(text, "\"");
  return (strtok(NULL, "\""));
}

int yywrap(void)
{
  YY_BUFFER_STATE buf;
  buf = pop_buffer();
  if (buf)
  {
    yy_delete_buffer(YY_CURRENT_BUFFER);
    yy_switch_to_buffer(buf);
    return (0);
  }
  return (1);
}

static void include_file(char *fn)
{
  FILE *fp;

  fp = open_include_file (fn);

  if (fp)
  {
    if(push_buffer(YY_CURRENT_BUFFER, fn))
    {
      yyin = fp;
      yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
    }
  }
  else
    yyerror("Attempted to include file \"%s\" which cannot be opened", fn);
}

static void define_token(char *text)
{
  char *name;
  char *replace;
  defined_token_t *def;

  /* Get various strings */
  strtok(text, " \t");          // "define"
  name = strtok(NULL, " \t");   
  replace = strtok(NULL, "\0");

  lower_string (name);

  /* Check against previously defined keywords */
  def = defines;
  while (def)
  { 
    if (strcmp(def->ident, name) == 0)
    {
      yyerror ("Cannot redefine keyword \"%s\"", name);
      return;
    }
    def = def->next;
  }

  /* Check against keyword list */
  if (ident_keyword(name) != IDENT)
  {
    yyerror ("Cannot redefine keyword \"%s\"", name);
    return;
  }

  /* Add to list. */

  def = malloc (sizeof (defined_token_t));
  if (!def)
    yyfatalerror("Out of memory");

  def->ident = strdup(name);
  if (!def->ident)
    yyfatalerror("Out of memory");

  def->replace = strdup(replace);
  if (!def->replace)
    yyfatalerror("Out of memory");

  def->next = defines;
  defines = def;
}
