%{
#include <stdio.h>
#include "layout.h"
#include "builder.h"

FormclassPtr yyparsedform;
static FormclassPtr bform;
static FieldPtr bf;
static NodePtr bn;
char *strdup(char *);
char *yylexany(void);
void yybegin(void);
%}

%union {
  char *s;
  char **sl;
  int i;
  int *il;
}

%token <s> ACTIONLIST BOOLEANINPUT0 BOXDISPLAY0 CALCULATE CHAR CHOICES COL
%token <s> COLUMNS DATA DEFAULTQUEUE DEFAULTVALUE DEFAULTVALUES DETAIL
%token <s> DIRECTION DISPLAYFIELD DISPLAY0 DISTANCE DOWN EXCLUSIVE0 FIELD FORM
%token <s> FORMNAME HEIGHT HIDDENIF INPUTFIELD INPUTSOURCE LEFT LENGTH
%token <s> LINEDISPLAY0 LOCKEDIF NAME NODENUMBER NONEXCLUSIVE0 NONE NOTIFY
%token <s> NUMERICINPUT0 NUMLINES PROMPT RIGHT ROUTE ROW SCREEN SELECTIONINPUT0
%token <s> STATUSSTRING TERMINAL TEXTATTRS TEXTDISPLAY0 TEXTINPUT0 TYPE UP
%token <s> WIDTH WRAP WORD

%token <s> ID ANY STRING
%token <i> INTEGER

%type <s>  id value
%type <sl> stringlist
%type <il> intlist

%nonassoc SHIFTNL
%nonassoc '\n'
/* This stuff is required to implement 'optnl': an optional sequence of
   newlines. Because of this, many shift-reduce conflicts resulted with
   other 'optional' things. For example: if yacc is working with
                thing optnl optional-thing '\n' anbsjkdabfdjksabf
   and it sees a \n, should it:
		(a) stick the \n into optnl and keep going?
		(b) say optnl and optional-thing are both empty, and
                    continue with anbsjkdabfdjksabf?
   The answer is (a), and many %prec declarations of SHIFTNL on empty rules
   below enforce it. */

%%

form		: { yybegin(); bform = NewFormclass(); yyparsedform = NULL; }
		  lb FORM optnl formslots fields rb lb ROUTE optnl routes rb
		  { yyparsedform = bform; }
		| error { FreeFormclass(bform); }
		  ;

formslots	: formslots formslot optnl %prec SHIFTNL
		| %prec SHIFTNL
		  ;
formslot	: FORMNAME ':' id '\n' { strdupinto(&bform->formname, $3); }
		| error '\n'
		  ;

fields		: fields field
		| ;
field		: lb FIELD optnl { bf = NewField(bform); }
		  fieldslots detail fieldslots rb
		  ;

fieldslots	: fieldslots fieldslot optnl %prec SHIFTNL
		| ;
fieldslot	: NAME ':' id '\n' { strdupinto(&bf->name, $3); }
		| ROW ':' INTEGER '\n' { bf->row = $3; }
		| COL ':' INTEGER '\n' { bf->col = $3; }
		| UP ':' id '\n' { strdupinto(&bf->up, $3); }
		| DOWN ':' id '\n' { strdupinto(&bf->down, $3); }
		| LEFT ':' id '\n' { strdupinto(&bf->left, $3); }
		| RIGHT ':' id '\n' { strdupinto(&bf->right, $3); }
		| LOCKEDIF ':' value '\n' { strdupinto(&bf->lockedif, $3); }
		| HIDDENIF ':' value '\n' { strdupinto(&bf->hiddenif, $3); }
		| TEXTATTRS ':' textattributes '\n'
		| SCREEN ':' intlist '\n' { putinto(&bf->screen, $3); }
		| error '\n'
		  ;

intlist		: intlist ',' INTEGER { $$ = intlistconcat($1, $3); }
		| INTEGER { $$ = intlistconcat(NULL, $1); }
		  ;

textattributes  : value { SetTextAttributes(bf, $1); }
		  ;

detail		: DETAIL ':' lb INPUTFIELD { MakeFieldDetail(bf, INPUT); }
		  optnl inputslots INPUTSOURCE ':' input inputslots rb
		| DETAIL ':' lb DISPLAYFIELD { MakeFieldDetail(bf, DISPLAY); }
		  optnl displayslots DISPLAY0 ':' display displayslots rb
		  ;

inputslots	: inputslots inputslot optnl %prec SHIFTNL
		| ;
inputslot	: DISTANCE ':' INTEGER '\n' { bf->detail->inputptr->distance = $3; }
		| PROMPT ':' STRING '\n' { strdupinto(&bf->detail->inputptr->prompt, $3); }
		| error '\n'
		  ;

displayslots	: ;

input		: lb TEXTINPUT0 { MakeField(bf, TEXTINPUT); }
		  optnl textinputslots rb
		| lb NUMERICINPUT0 { MakeField(bf, NUMERICINPUT); }
		  optnl numericinputslots rb
		| lb BOOLEANINPUT0 { MakeField(bf, BOOLEANINPUT); }
		  optnl booleaninputslots rb
		| lb SELECTIONINPUT0 { MakeField(bf, SELECTIONINPUT); }
		  optnl selectioninputslots rb
		  ;

textinputslots	: textinputslots textinputslot optnl %prec SHIFTNL
		| %prec SHIFTNL
		  ;
textinputslot	: DEFAULTVALUE ':' value '\n' { SetDefaultValue(bform, bf, $3); }
		| LENGTH ':' INTEGER '\n' { bf->detail->inputptr->inputsource->text->length = $3; }
		| NUMLINES ':' INTEGER '\n' { bf->detail->inputptr->inputsource->text->numlines = $3; }
		| WRAP ':' NONE '\n' { bf->detail->inputptr->inputsource->text->wrap = WrapNone; }
		| WRAP ':' CHAR '\n' { bf->detail->inputptr->inputsource->text->wrap = WrapChar; }
		| WRAP ':' WORD '\n' { bf->detail->inputptr->inputsource->text->wrap = WrapWord; }
		| CALCULATE ':' value '\n' { strdupinto(&bf->detail->inputptr->inputsource->text->calculate, $3); }
		| error '\n'
		  ;

numericinputslots: numericinputslots numericinputslot optnl %prec SHIFTNL
		| %prec SHIFTNL
		  ;
numericinputslot: DEFAULTVALUE ':' value '\n' { SetDefaultValue(bform, bf, $3); }
		| CALCULATE ':' value '\n' { strdupinto(&bf->detail->inputptr->inputsource->numeric->calculate, $3); }
		| error '\n'
		  ;

booleaninputslots: booleaninputslots booleaninputslot optnl %prec SHIFTNL
		| %prec SHIFTNL
		  ;
booleaninputslot: DEFAULTVALUE ':' value '\n' { SetDefaultValue(bform, bf, $3); }
		| CALCULATE ':' value '\n' { strdupinto(&bf->detail->inputptr->inputsource->boolean->calculate, $3); }
		| error '\n'
		  ;

selectioninputslots: selectioninputslots selectioninputslot optnl %prec SHIFTNL
		| %prec SHIFTNL
		  ;
selectioninputslot: DEFAULTVALUES ':' value '\n' { SetDefaultValue(bform, bf, $3); }
		| TYPE ':' EXCLUSIVE0 '\n' { bf->detail->inputptr->inputsource->selection->type = EXCLUSIVE; }
		| TYPE ':' NONEXCLUSIVE0 '\n' { bf->detail->inputptr->inputsource->selection->type = NONEXCLUSIVE; }
		| CHOICES ':' stringlist '\n' { putinto(&bf->detail->inputptr->inputsource->selection->choices, $3); }
		| COLUMNS ':' INTEGER '\n' { bf->detail->inputptr->inputsource->selection->columns = $3; }
		| CALCULATE ':' value '\n' { strdupinto(&bf->detail->inputptr->inputsource->selection->calculate, $3); }
		| error '\n'
		  ;

stringlist	: stringlist ',' STRING { $$ = stringlistconcat($1, $3); }
		| STRING { $$ = stringlistconcat(NULL, $1); }
		  ;

display		: lb BOXDISPLAY0 { MakeField(bf, BOXDISPLAY); }
		  optnl boxdisplayslots rb
		| lb LINEDISPLAY0 { MakeField(bf, LINEDISPLAY); }
		  optnl linedisplayslots rb
		| lb TEXTDISPLAY0 { MakeField(bf, TEXTDISPLAY); }
		  optnl textdisplayslots rb
		  ;

boxdisplayslots	: boxdisplayslots boxdisplayslot optnl %prec SHIFTNL
		| %prec SHIFTNL
		  ;
boxdisplayslot	: HEIGHT ':' INTEGER '\n' { bf->detail->displayptr->display->box->height = $3; }
		| WIDTH ':' INTEGER '\n' { bf->detail->displayptr->display->box->width = $3; }
		| error '\n'
		  ;

linedisplayslots: linedisplayslots linedisplayslot optnl %prec SHIFTNL
		| %prec SHIFTNL
		  ;
linedisplayslot	: LENGTH ':' INTEGER '\n' { bf->detail->displayptr->display->line->length = $3; }
		| DIRECTION ':' INTEGER '\n' { bf->detail->displayptr->display->line->direction = $3; }
		| error '\n'
		  ;

textdisplayslots: textdisplayslots textdisplayslot optnl %prec SHIFTNL
		| %prec SHIFTNL
		  ;
textdisplayslot	: DATA ':' STRING '\n' { strdupinto(&bf->detail->displayptr->display->text->data, $3); }
		| error '\n'
		  ;

routes		: routes route
		| %prec SHIFTNL
		  ;
route		: lb { bn = NewNode(bform); }
		  routeslots NODENUMBER ':' INTEGER '\n' { bn->nodenumber = $6; }
		  routeslots rb
		| error '}'
		  ;

routeslots	: routeslots routeslot optnl %prec SHIFTNL
		| ;
routeslot	: STATUSSTRING ':' STRING '\n' { strdupinto(&bn->statusstring, $3); }
		| ACTIONLIST ':' actionlist '\n'
		| TERMINAL ':' INTEGER '\n' { bn->terminal = $3; }
		| DEFAULTQUEUE ':' id '\n' { strdupinto(&bn->defaultqueue, $3); }
		| NOTIFY ':' INTEGER '\n' { bn->notify = $3; }
		| error '\n'
		  ;

actionlist	: actionsplural
		| ;
actionsplural	: actionsplural ',' action
		| action
		  ;
action		: '{' STRING ',' INTEGER '}' { NewAction(bn, strdup($2), $4); }
		| '{' '}'
		  ;

value		: { $$ = yylexany(); }
		  ;

id		: ID
		| ACTIONLIST
		| BOOLEANINPUT0
		| BOXDISPLAY0
		| CALCULATE
		| CHAR
		| CHOICES
		| COL
		| COLUMNS
		| DATA
		| DEFAULTQUEUE
		| DEFAULTVALUE
		| DEFAULTVALUES
		| DETAIL
		| DIRECTION
		| DISPLAYFIELD
		| DISPLAY0
		| DISTANCE
		| DOWN
		| EXCLUSIVE0
		| FIELD
		| FORM
		| FORMNAME
		| HEIGHT
		| HIDDENIF
		| INPUTFIELD
		| INPUTSOURCE
		| LEFT
		| LENGTH
		| LINEDISPLAY0
		| LOCKEDIF
		| NAME
		| NODENUMBER
		| NONE
		| NONEXCLUSIVE0
		| NOTIFY
		| NUMERICINPUT0
		| NUMLINES
		| PROMPT
		| RIGHT
		| ROUTE
		| ROW
		| SCREEN
		| SELECTIONINPUT0
		| STATUSSTRING
		| TERMINAL
		| TEXTATTRS
		| TEXTDISPLAY0
		| TEXTINPUT0
		| TYPE
		| UP
		| WIDTH
		| WORD
		| WRAP
		  ;

lb		: optnl '{' optnl %prec SHIFTNL
		  ;
rb		: optnl '}' optnl %prec SHIFTNL
		  ;
optnl		: optnl '\n'
		| ;

%%

char *strdup(char *s) {
  char *q = (char *)malloctrace(strlen(s)+1);
  strcpy(q, s);
  return q;
}

void strdupinto(char **into, char *s) {
  if (*into) freetrace(*into);
  *into = strdup(s);
}

void putinto(void **into, void *p) {
  if (*into) freetrace(*into);
  *into = p;
}

char **stringlistconcat(char **list, char *new) {
  int siz;
  char **trav;
  if (!list) list = (char **)calloctrace(sizeof(char *), 1);
  for (trav = list, siz = 0; *trav; trav++) siz++;
  list = (char **)realloctrace(list, sizeof(char *) * (siz + 2));
  list[siz] = NULL;
  strdupinto(&list[siz], new);
  list[siz+1] = NULL;
  return list;
}

int *intlistconcat(int *list, int new) {
  int siz, *trav;
  if (!list) {
    list = (int *)malloctrace(sizeof(int));
    *list = -1;
  }
  for (trav = list, siz = 0; *trav >= 0; trav++) siz++;
  list = (int *)realloctrace(list, sizeof(int) * (siz + 2));
  list[siz] = new;
  list[siz+1] = -1;
  return list;
}
