// Filename: xParser.yxx
// Created by:  drose (03Oct04)
// 
////////////////////////////////////////////////////////////////////

// The grammar defined in this file is taken more-or-less from the
// Microsoft DirectX File Format Specification document, version 1.13.
// The document actually describes a slightly incomplete and incorrect
// grammar, so small changes had to be made, but an attempt was made
// to be as faithful as possible to the intention of the spec.

%{
#include "xLexerDefs.h"
#include "xParserDefs.h"
#include "xFile.h"
#include "xFileTemplate.h"
#include "xFileDataDef.h"
#include "xFileArrayDef.h"
#include "xFileDataNodeTemplate.h"
#include "xFileDataNodeReference.h"
#include "pointerTo.h"
#include "dcast.h"

// Because our token type contains objects of type string, which
// require correct copy construction (and not simply memcpying), we
// cannot use bison's built-in auto-stack-grow feature.  As an easy
// solution, we ensure here that we have enough yacc stack to start
// with, and that it doesn't ever try to grow.
#define YYINITDEPTH 1000
#define YYMAXDEPTH 1000

static XFile *x_file = (XFile *)NULL;
static XFileNode *current_node = (XFileNode *)NULL;
static PT(XFileDataDef) current_data_def = (XFileDataDef *)NULL;

////////////////////////////////////////////////////////////////////
// Defining the interface to the parser.
////////////////////////////////////////////////////////////////////

void
x_init_parser(istream &in, const string &filename, XFile &file) {
  x_file = &file;
  current_node = &file;
  x_init_lexer(in, filename);
}

void
x_cleanup_parser() {
  x_file = (XFile *)NULL;
  current_node = (XFileNode *)NULL;
}

%}

// These token values are taken from the DirectX spec; the particular
// numeric values are useful when reading an .x file in binary mode
// (which basically just streams the tokens retrieved by the lexer).

%token <str> TOKEN_NAME 1
%token <str> TOKEN_STRING 2
%token <u.number> TOKEN_INTEGER 3
%token <guid> TOKEN_GUID 5
%token <int_list> TOKEN_INTEGER_LIST 6
%token <double_list> TOKEN_REALNUM_LIST 7

%token TOKEN_OBRACE 10
%token TOKEN_CBRACE 11
%token TOKEN_OPAREN 12
%token TOKEN_CPAREN 13
%token TOKEN_OBRACKET 14
%token TOKEN_CBRACKET 15
%token TOKEN_OANGLE 16
%token TOKEN_CANGLE 17
%token TOKEN_DOT 18
%token TOKEN_COMMA 19
%token TOKEN_SEMICOLON 20
%token TOKEN_TEMPLATE 31
%token TOKEN_WORD 40
%token TOKEN_DWORD 41
%token TOKEN_FLOAT 42
%token TOKEN_DOUBLE 43
%token TOKEN_CHAR 44
%token TOKEN_UCHAR 45
%token TOKEN_SWORD 46
%token TOKEN_SDWORD 47
%token TOKEN_VOID 48
%token TOKEN_LPSTR 49
%token TOKEN_UNICODE 50
%token TOKEN_CSTRING 51
%token TOKEN_ARRAY 52

%type <u.node> template
%type <u.node> object
%type <u.primitive_type> primitive_type
%type <int_list> integer_list
%type <double_list> realnum_list
%type <str> name
%type <str> optional_name
%type <str> string
%type <guid> class_id
%type <guid> optional_class_id
%type <u.node> data_reference

%%

xfile:
        empty
        | xfile template
        | xfile object
        ;

template:
	 TOKEN_TEMPLATE name TOKEN_OBRACE class_id
{
  $$ = current_node;
  XFileTemplate *templ = new XFileTemplate(x_file, $2, $4);
  current_node->add_child(templ);
  current_node = templ;
}
	 template_parts TOKEN_CBRACE
{
  $$ = current_node;
  current_node = $<u.node>5;
}
         ;

template_parts: 
         template_members_part TOKEN_OBRACKET template_option_info TOKEN_CBRACKET
         | template_members_list
         ;

template_members_part: 
         empty
         | template_members_list
         ;

template_option_info: 
         ellipsis
{
  DCAST(XFileTemplate, current_node)->set_open(true);
}
         | template_option_list
         ;
                       
template_members_list:
         template_members
         | template_members_list template_members
         ;

template_members: 
         primitive
         | array
         | template_reference
         ;

primitive:
         primitive_type optional_name TOKEN_SEMICOLON
{
  current_data_def = new XFileDataDef(x_file, $2, $1);
  current_node->add_child(current_data_def);
}
         ;

array: 
         TOKEN_ARRAY array_data_type dimension_list TOKEN_SEMICOLON
         ;

template_reference: 
         name optional_name TOKEN_SEMICOLON
{
  XFileTemplate *xtemplate = x_file->find_template($1);
  if (xtemplate == (XFileTemplate *)NULL) {
    yyerror("Unknown template: " + $1);
  } else {
    current_data_def = new XFileDataDef(x_file, $2, XFileDataDef::T_template, xtemplate);
    current_node->add_child(current_data_def);
  }
}
         ;

primitive_type:
        TOKEN_WORD
{
  $$ = XFileDataDef::T_word;
}
        | TOKEN_DWORD
{
  $$ = XFileDataDef::T_dword;
}
        | TOKEN_FLOAT
{
  $$ = XFileDataDef::T_float;
}
        | TOKEN_DOUBLE
{
  $$ = XFileDataDef::T_double;
}
        | TOKEN_CHAR
{
  $$ = XFileDataDef::T_char;
}
        | TOKEN_UCHAR
{
  $$ = XFileDataDef::T_uchar;
}
        | TOKEN_SWORD
{
  $$ = XFileDataDef::T_sword;
}
        | TOKEN_SDWORD
{
  $$ = XFileDataDef::T_sdword;
}
        | TOKEN_LPSTR
{
  $$ = XFileDataDef::T_string;
}
        | TOKEN_UNICODE
{
  $$ = XFileDataDef::T_unicode;
}
        | TOKEN_CSTRING
{
  $$ = XFileDataDef::T_cstring;
}
        ;

array_data_type:
        primitive_type name
{
  current_data_def = new XFileDataDef(x_file, $2, $1);
  current_node->add_child(current_data_def);
}
        | name name
{
  XFileTemplate *xtemplate = x_file->find_template($1);
  if (xtemplate == (XFileTemplate *)NULL) {
    yyerror("Unknown template: " + $1);
  } else {
    current_data_def = new XFileDataDef(x_file, $2, XFileDataDef::T_template, xtemplate);
    current_node->add_child(current_data_def);
  }
}
        ;

dimension_list:
        dimension
        | dimension_list dimension
        ;

dimension:
        TOKEN_OBRACKET dimension_size TOKEN_CBRACKET
        ;

dimension_size:
        TOKEN_INTEGER
{
  current_data_def->add_array_def(XFileArrayDef($1));
}
        | name
{
  XFileNode *data_def = current_node->find_child($1);
  if (data_def == (XFileNode *)NULL) {
    yyerror("Unknown identifier: " + $1);
  } else {
    current_data_def->add_array_def(XFileArrayDef(DCAST(XFileDataDef, data_def)));
  }
}
        ;

template_option_list: 
        template_option_part
{
}
        | template_option_list template_option_part
{
}
	;

template_option_part:
	name
{
  XFileTemplate *xtemplate = x_file->find_template($1);
  if (xtemplate == (XFileTemplate *)NULL) {
    yyerror("Unknown template: " + $1);
  } else {
    DCAST(XFileTemplate, current_node)->add_option(xtemplate);
  }
}
	| name class_id
{
  XFileTemplate *xtemplate = x_file->find_template($2);
  if (xtemplate == (XFileTemplate *)NULL) {
    yyerror("Unknown template: " + $1);
  } else {
    if (xtemplate->get_name() != $1) {
      xyywarning("GUID identifies template " + xtemplate->get_name() +
                 ", not " + $1);
    }
    DCAST(XFileTemplate, current_node)->add_option(xtemplate);
  }
}
        ;

name: 
        TOKEN_NAME
        ;

optional_name:
        empty
{
  $$ = string();
}
        | name
        ;

class_id:
  	TOKEN_GUID
        ;

optional_class_id: 
        empty
{
  $$ = WindowsGuid();
}
        | class_id
        ;

ellipsis:
        TOKEN_DOT TOKEN_DOT TOKEN_DOT
        ;

object:
        name optional_name TOKEN_OBRACE
{
  XFileTemplate *xtemplate = x_file->find_template($1);
  $$ = current_node;
  
  if (xtemplate == (XFileTemplate *)NULL) {
    yyerror("Unknown template: " + $1);
  } else {
    XFileDataNodeTemplate *templ = 
      new XFileDataNodeTemplate(x_file, $2, xtemplate);
    current_node->add_child(templ);
    current_node = templ;
  }
}
	optional_class_id data_parts_list TOKEN_CBRACE
{
  XFileDataNodeTemplate *current_template = 
    DCAST(XFileDataNodeTemplate, current_node);
  current_template->finalize_parse_data();

  $$ = current_node;
  current_node = $<u.node>4;
}
	;

data_parts_list:
        empty
        | data_parts_list data_part
        ;

data_part:
        TOKEN_OBRACE data_reference TOKEN_CBRACE
{
  // nested references should be added as children too.
  current_node->add_child($2);
}
	| object
{
  // nested objects are just quietly added as children.
}
        | integer_list
{
  XFileDataNodeTemplate *current_template = 
    DCAST(XFileDataNodeTemplate, current_node);
  current_template->add_parse_int($1);
}
        | realnum_list
{
  XFileDataNodeTemplate *current_template = 
    DCAST(XFileDataNodeTemplate, current_node);
  current_template->add_parse_double($1);
}
	| string list_separator
{
  XFileDataNodeTemplate *current_template = 
    DCAST(XFileDataNodeTemplate, current_node);
  current_template->add_parse_string($1);
}
        | list_separator
{
}
        ;
        
integer_list:
	TOKEN_INTEGER_LIST
        ;

realnum_list:
	TOKEN_REALNUM_LIST
        ;

string:
	TOKEN_STRING
        ;

list_separator:
	TOKEN_SEMICOLON
	| TOKEN_COMMA
	;

data_reference:
        name
{
  XFileDataNodeTemplate *data_object = x_file->find_data_object($1);
  if (data_object == (XFileDataObject *)NULL) {
    yyerror("Unknown data_object: " + $1);
  }

  $$ = new XFileDataNodeReference(data_object);
}
	| name class_id
{
  XFileDataNodeTemplate *data_object = x_file->find_data_object($2);
  if (data_object == (XFileDataObject *)NULL) {
    yyerror("Unknown data_object: " + $1);
  } else {
    if (data_object->get_name() != $1) {
      xyywarning("GUID identifies data_object " + data_object->get_name() +
                 ", not " + $1);
    }
  }

  $$ = new XFileDataNodeReference(data_object);
}
        ;

empty:
        ;
