//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 <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "defines.h"

#define YYERROR_VERBOSE

extern system_t *sys;
extern unsigned int stacksize;
extern sp_t stackpol;

static unsigned int proc_context = 1;
static unsigned char global = 1;
static unsigned char uninstantiated = 0;
%}

%union
{
  char *string;
  int value;
  dependency_t *dep;
  state_node_t *state;
  policy_t *policy;
  process_t *process;
  nebuloid_t *nebuloid;
  start_t *start;
  restart_t *restart;
  dependfail_t *dependfail;
  isup_t *isup;
  isalive_t *isalive;
  proc_t *proc;
  sighandler_t *sighandler;
  argtype_t *argtype;
}

%token PROCESS PROC IF ENDIF START RESTART DEPENDFAIL SYSTEM DEPENDREMOVE
%token ELSE RETURN TOK_TRUE TOK_FALSE MODULE ARG_NUM ARG_STRING DEPENDADD
%token EQ NE LTE GTE OROR ANDAND GROW DIE STACKSIZE LOOP ENDLOOP
%token STACKPOLICY ISUP ISALIVE TOK_SIGUSR1 TOK_SIGUSR2 TOK_SIGCHLD

%token <string> STRING IDENT
%token <value> NUM

%type <state> statement statements statement_list expr expr_statement arguments 
%type <state> argument_list argument static_arg_list static_args static_arg
%type <dep> dependencies 
%type <policy> policy inner_process_defn process_policy
%type <process> process_defn
%type <nebuloid> nebuloid_defn
%type <start> start_defn
%type <restart> restart_defn
%type <dependfail> dependfail_defn
%type <isup> isup_defn
%type <isalive> isalive_defn
%type <proc> proc_defn
%type <sighandler> sigchld_defn sigusr_defn
%type <string> process_name proc_name module_name nebuloid_name
%type <argtype> arg_defn arg_defn_list arg_type

%left OROR ANDAND
%left '='
%left '+' '-'
%left '*' '/'
%nonassoc EQ NE '<' '>' LTE GTE
%nonassoc UMINUS

%% 

system_defn:
         header SYSTEM ':' dependencies '{' policy '}' 
         { 
           sys = new_system($4, $6); 

           verify_system(sys);
           YYACCEPT;
         }
    ;

header:
         header module_defn
    |    header stacksize_defn
    |    header stackpol_defn
    |    /* empty */
    ;

stacksize_defn:
         STACKSIZE NUM ';'
{ 
  if (stacksize != 0)
    yywarning("Multiple stack size definitions; using %u", $2);

  stacksize = $2;
}
    ;

stackpol_defn:
         STACKPOLICY { if (stackpol != SP_NONE) yywarning ("Multiple stack policies; using last"); }
                     stack_policies
    ;

stack_policies:
         DIE ';' { stackpol = SP_DIE; }
    |    GROW ';' { stackpol = SP_GROW; }
    ;

module_defn:   
         MODULE IDENT arg_defn arg_defn ';' { add_argtype ($2, $3, $4); add_name (NT_MODULE, $2, 0); }
    ;

arg_defn:
         '(' arg_defn_list ')' { $$ = $2;   }
    |    '(' ')'               { $$ = NULL; }
    ;

arg_defn_list:
         arg_type                   { $$ = $1; }
    |    arg_type ',' arg_defn_list { $$ = $1; $$->next = $3; }
    ;

arg_type:
         ARG_STRING { $$ = new_argtype (AT_STRING); }
    |    ARG_NUM    { $$ = new_argtype (AT_NUM); }
    ;

policy:
         policy process_defn  { $$ = new_policy (PT_PROCESS, $2, $1, 0); }
    |    policy nebuloid_defn { $$ = new_policy (PT_NEBULOID, $2, $1, 0); }
    |    policy proc_defn     { $$ = new_policy (PT_PROC, $2, $1, 0); }
    |    policy sigusr_defn   { $$ = new_policy (PT_SIGNAL, $2, $1, 0); }
    |    /* empty */          { $$ = NULL; }
    ;

process_name:
         IDENT { $$ = $1; add_name(NT_PROCESS, $1, 0); global = 0; }
    ;

process_defn:
         PROCESS process_name NUM ':' dependencies inner_process_defn 
{ 
  $$ = new_process($2, $3, $5, $6, proc_context); 
  proc_context ++;
  global = 1;
}
    |    PROCESS process_name ':' dependencies inner_process_defn 
{ 
  $$ = new_process($2, 0, $4, $5, proc_context); 
  proc_context ++;
  global = 1;
}
    ;

dependencies:
         dependencies IDENT { $$ = new_dependency ($2, $1); }
    |    /* empty */        { $$ = NULL; }
    ;

inner_process_defn:
         '{' process_policy '}' { $$ = $2; }
    |    '{' error '}'          { yyerror ("Invalid policy in process"); $$ = NULL; }
    ;

process_policy:
         
         process_policy nebuloid_defn { $$ = new_policy (PT_NEBULOID, $2, $1, proc_context); }
    |    process_policy proc_defn { $$ = new_policy (PT_PROC, $2, $1, proc_context); }
    |    process_policy start_defn { $$ = new_policy (PT_START, $2, $1, proc_context); }
    |    process_policy restart_defn { $$ = new_policy (PT_RESTART, $2, $1, proc_context); }
    |    process_policy dependfail_defn { $$ = new_policy (PT_DEPENDFAIL, $2, $1, proc_context); }
    |    process_policy isup_defn { $$ = new_policy (PT_ISUP, $2, $1, proc_context); }
    |    process_policy isalive_defn { $$ = new_policy (PT_ISALIVE, $2, $1, proc_context); }
    |    process_policy sigchld_defn { $$ = new_policy (PT_SIGNAL, $2, $1, proc_context); }
    |    /* empty */ { $$ = NULL; }
    ;

sigchld_defn:
         TOK_SIGCHLD statement { $$ = new_sighandler (SH_SIGCHLD, $2); }
    ;

sigusr_defn:
         TOK_SIGUSR1 statement { $$ = new_sighandler (SH_SIGUSR1, $2); }
    |    TOK_SIGUSR2 statement { $$ = new_sighandler (SH_SIGUSR2, $2); }
    ;

nebuloid_defn:
         module_name nebuloid_name neb_dummy static_args
{
  if (pop_argtype()) // Still more arguments to be processed?
  {
    yyerror ("Not enough arguments");
    while(pop_argtype()); // Pop until finished.
  }
  // Associate the module's dynamic arguments with the nebuloid name
  add_argtype ($2, NULL, get_argtype ($1, AT_DYNAMIC));
} NUM statement  { $$ = new_nebuloid($1, $2, $4, $6, $7); }

    |    module_name nebuloid_name neb_dummy static_args ';'
{ 
  $$ = new_nebuloid ($1, $2, $4, 0, NULL); 
  if (pop_argtype()) // Still more arguments to be processed?
  {
    yyerror ("Not enough arguments");
    while(pop_argtype()); // Pop until finished.
  }
  // Associate the module's dynamic arguments with the nebuloid name
  add_argtype ($2, NULL, get_argtype ($1, AT_DYNAMIC));
}
    ;

neb_dummy:
         { push_all_args (get_argtype($<string>-1, AT_STATIC)); }
    ;

module_name:
         IDENT 
{ 
  $$ = $1; 
  if (!find_name (NT_MODULE, $1, 0)) 
    yyerror ("Attempting to use undefined module \"%s\"", $1);
}
    ;

nebuloid_name:
         IDENT { $$ = $1; add_name (NT_NEBULOID, $1, global ? 0 : proc_context); }
    ;

arguments:
         '(' argument_list ')' { $$ = $2; }
    |    '(' ')' { $$ = NULL; }
    ;

argument_list:
         argument                   { $$ = $1; }
    |    argument ',' argument_list { $1->d.arg.next = $3; $$ = $1; }
    ;

argument:
         expr   
{
  argtype_t *arg;

  $$ = new_argument ($1); 
  arg = pop_argtype();
  if (!arg)
  {
    yyerror ("Too many arguments:\nopcode = %d", $1->d.op.opcode);
    push_argtype(NULL); // put the NULL back on the stack.
  }
  else
  {
    if (arg->type != AT_NUM)
      yyerror ("Expecting STRING as an argument");
  }
}
    |    STRING 
{ 
  argtype_t *arg;

  arg = pop_argtype();

  $$ = new_argument (ident(parse_string($1))); 
  if (!arg)
  {
    yyerror ("Too many arguments");
    push_argtype(NULL); // Put the NULL back on the stack.
  }
  else
  {
    if (arg->type != AT_STRING)
      yyerror ("Expecting NUM as an argument");
  }
}
    ;

static_args:
         '(' static_arg_list ')' { $$ = $2; }
    |    '(' ')'                 { $$ = NULL; }
    ;

static_arg_list:
         static_arg                     { $$ = $1; }
    |    static_arg ',' static_arg_list { $1->d.arg.next = $3; $$ = $1; }
    ;

static_arg:
         NUM   
{
  argtype_t *arg;

  arg = pop_argtype();

  $$ = new_argument (con($1)); 
  if (!arg)
  {
    yyerror ("Too many arguments");
    push_argtype(NULL); // Push the NULL back on the stack.
  }
  else
  {
    if (arg->type != AT_NUM)
      yyerror ("Expecting STRING as an argument");
  }
}
    |    STRING 
{ 
  argtype_t *arg;

  arg = pop_argtype();

  $$ = new_argument (ident(parse_string($1))); 
  if (!arg)
  {
    yyerror ("Too many arguments");
    push_argtype(NULL); // Push the NULL back on the stack.
  }
  else
  {
    if (arg->type != AT_STRING)
      yyerror ("Expecting NUM as an argument");
  }
}
    ;

proc_name:
         IDENT { $$ = $1; add_name (NT_PROC, $1, global ? 0 : proc_context); }
    ;

proc_defn:
         PROC proc_name statement      { $$ = new_proc ($2, 0, $3); }
    |    PROC proc_name NUM statement  { $$ = new_proc ($2, $3, $4); }
    ;

start_defn:
         START {uninstantiated = 1;} statement { $$ = new_start ($3); uninstantiated = 0;}
    ;

restart_defn:
         RESTART {uninstantiated = 1;} statement { $$ = new_restart ($3); uninstantiated = 0;}
    ;

dependfail_defn:
         DEPENDFAIL statement { $$ = new_dependfail ($2); }
    ;

isup_defn:
         ISUP NUM statement { $$ = new_isup($2, $3); }
    ;

isalive_defn:
         ISALIVE statement { $$ = new_isalive($2); }
    ;

statements:
         '{' statement_list '}'       { $$ = $2; }
    |    '{' '}'                      { $$ = NULL; }
    |    '{' error '}'                { $$ = NULL; }
    ;

statement_list:
         statement
    |    statement_list statement { $$ = op(OP_OPLIST, 2, $1, $2); }
    ;

statement:
         RETURN TOK_TRUE ';'       { $$ = op(OP_RETURN_TRUE, 0); }
    |    RETURN TOK_FALSE ';'      { $$ = op(OP_RETURN_FALSE, 0); }
    |    expr_statement ';'    { $$ = $1; $$->d.op.expr = 0; }
    |    IDENT '=' expr ';'    
{ 
  if ((!global && !find_name(NT_NEBULOID, $1, proc_context)) &&
      !find_name(NT_NEBULOID, $1, 0) && !find_name(NT_PROCESS, $1, 0))
  {
    yyerror("Nebuloid \"%s\" must be defined to assign to it", $1);
  }
  $$ = op(OP_ASSIGN, 2, ident($1), $3); 
}
    |    IF '(' expr ')' statement_list ENDIF ';' { $$ = op (OP_IF, 3, $3, $5, NULL); }
    |    IF '(' expr ')' statement_list ELSE statement_list ENDIF ';' { $$ = op (OP_IF, 3, $3, $5, $7); }
    |    LOOP '(' expr ')' statement_list ENDLOOP ';' { $$ = op (OP_LOOP, 2, $3, $5); }
    |    DEPENDADD IDENT ';'    
{
  if (global)
    yyerror ("Cannot add dependencies dynamically to system");
  else
    add_dyn_depend($2, proc_context);
  $$ = op (OP_DEPADD, 1, ident($2));
}
    |    DEPENDREMOVE IDENT ';' 
{ 
  if (global)
    yyerror ("Cannot remove dependencies dynamically from system");
  else
    add_dyn_depend($2, proc_context);
  $$ = op (OP_DEPREMOVE, 1, ident($2)); 
}
    |    statements
    |    error ';' { yyerrok; $$ = NULL; }
    ;

expr:
         expr_statement          { $$ = $1; $$->d.op.expr = 1; }
    |    NUM                     { $$ = con($1); }
    |    expr '+' expr           { $$ = op(OP_PLUS, 2, $1, $3); } 
    |    expr '-' expr           { $$ = op (OP_MINUS, 2, $1, $3); }
    |    expr '*' expr           { $$ = op (OP_TIMES, 2, $1, $3); }
    |    expr '/' expr           { $$ = op (OP_DIVIDE, 2, $1, $3); } 
    |    expr OROR expr          { $$ = op (OP_OR, 2, $1, $3); }
    |    expr ANDAND expr        { $$ = op (OP_AND, 2, $1, $3); }
    |    expr '>' expr           { $$ = op(OP_GT, 2, $1, $3); }
    |    expr '<' expr           { $$ = op(OP_GT, 2, $3, $1); }
    |    expr EQ expr            { $$ = op(OP_EQ, 2, $1, $3); }
    |    expr GTE expr           { $$ = op(OP_GTE, 2, $1, $3); }
    |    expr LTE expr           { $$ = op(OP_GTE, 2, $3, $1); }
    |    expr NE expr            { $$ = op(OP_NE, 2, $1, $3); }
    |    '-' expr %prec UMINUS   { $$ = op(OP_MINUS, 2, con(0), $2); }
    |    '(' expr  ')'           { $$ = $2; }
//    |    '(' error ')'		 { $$ = NULL; }
    ;

expr_statement:
         IDENT           
{
  if (uninstantiated && find_name (NT_NEBULOID, $1, proc_context))
  {
    yyerror ("Nebuloid \"%s\" cannot be used in this context:\nit has yet to be instantiated.", $1);
  }
  if (!find_name(NT_PROC, $1, 0) && (!global && !find_name(NT_PROC, $1, proc_context)))
  {
    // not a proc
    if ( (!global && find_name(NT_NEBULOID, $1, proc_context)) ||
       find_name(NT_NEBULOID, $1, 0) || find_name(NT_MODULE, $1, 0))
    {
      // Is a nebuloid/module
      if(get_argtype ($1, AT_DYNAMIC)) // HAS arguments
        yyerror ("Nebuloid \"%s\" must be called with arguments", $1);
      if(find_name(NT_MODULE, $1, 0) && get_argtype($1, AT_STATIC))
        yyerror ("Module \"%s\" requires instantiation", $1);
    }
    else
    {
      if (!find_name (NT_PROCESS, $1, 0))
      { // Isn't anything we want to call!
        yyerror ("Attempting to call nebuloid/proc \"%s\":\nhas not been declared", $1);
      }
    }
  }
  $$ = op(OP_CALL, 2, ident($1), NULL); 
}

/** This one's really messy.  Error checking sucks. "IDENT arguments" **/
    |    IDENT 
{
  if (uninstantiated && find_name (NT_NEBULOID, $1, proc_context))
  {
    yyerror ("Nebuloid \"%s\" cannot be used in this context:\nit has yet to be instantiated.", $1);
  }
  if ( (!global && !find_name(NT_NEBULOID, $1, proc_context)) &&
       !find_name(NT_NEBULOID, $1, 0) && !find_name(NT_MODULE, $1, 0) &&
       !find_name(NT_PROCESS, $1, 0))
  {
    yyerror ("Attempting to call a nebuloid \"%s\" which has not been declared", $1);
    $$ = NULL;
  }

  if(find_name(NT_MODULE, $1, 0) && get_argtype($1, AT_STATIC))
    yyerror ("Module \"%s\" requires instantiation", $1);

  if (find_name(NT_PROCESS, $1, 0))
    push_argtype(NULL);
  else
    push_all_args(get_argtype ($1, AT_DYNAMIC));
}       
        arguments 
{
  if (pop_argtype()) // still more to be processed!
  {
    yyerror ("Not enough arguments");
    while(pop_argtype()); // Pop remaining arguments off the stack.
  }
  $$ = op(OP_CALL, 2, ident($1), $3); 
}
    ;

%%
