// Filename: dcParser.yxx
// Created by:  drose (05Oct00)
// 
////////////////////////////////////////////////////////////////////

%{
#include "dcLexerDefs.h"
#include "dcParserDefs.h"
#include "dcFile.h"
#include "dcClass.h"
#include "dcAtomicField.h"
#include "dcMolecularField.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 DCFile *dc_file = (DCFile *)NULL;
static DCClass *current_class = (DCClass *)NULL;
static DCAtomicField *current_atomic = (DCAtomicField *)NULL;
static DCMolecularField *current_molecular = (DCMolecularField *)NULL;
static DCAtomicField::ElementType atomic_element;

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

void
dc_init_parser(istream &in, const string &filename, DCFile &file) {
  dc_file = &file;
  dc_init_lexer(in, filename);
}

void
dc_cleanup_parser() {
  dc_file = (DCFile *)NULL;
}

%}

%token <u.integer> INTEGER
%token <u.real> REAL
%token <str> STRING HEX_STRING IDENTIFIER

%token KW_DCLASS 

%token KW_INT8
%token KW_INT16
%token KW_INT32
%token KW_INT64
%token KW_UINT8
%token KW_UINT16
%token KW_UINT32
%token KW_UINT64
%token KW_FLOAT64
%token KW_STRING
%token KW_BLOB
%token KW_INT8ARRAY
%token KW_INT16ARRAY
%token KW_INT32ARRAY
%token KW_UINT8ARRAY
%token KW_UINT16ARRAY
%token KW_UINT32ARRAY
%token KW_UINT32UINT8ARRAY

%token KW_MOL

%token KW_REQUIRED
%token KW_BROADCAST
%token KW_P2P
%token KW_RAM
%token KW_DB
%token KW_CLSEND
%token KW_CLRECV
%token KW_OWNSEND

%type <u.dclass> dclass_name
%type <u.atomic> atomic_name
%type <u.subatomic> type_token

%%

dc:
        empty
        | dc ';'
        | dc dclass
        ;

dclass:
        KW_DCLASS IDENTIFIER 
{
  current_class = new DCClass;
  current_class->_name = $2;
  if (!dc_file->add_class(current_class)) {
    yyerror("Duplicate class name: " + current_class->_name);
  }
}
        dclass_derivation '{' dclass_fields '}'
        ;

dclass_name:
        IDENTIFIER
{
  DCFile::ClassesByName::const_iterator ni;
  ni = dc_file->_classes_by_name.find($1);
  if (ni == dc_file->_classes_by_name.end()) {
    yyerror("Unknown class: " + $1);
    $$ = (DCClass *)NULL;
  } else {
    $$ = (*ni).second;
  }
}

dclass_derivation:
        empty
        | ':' base_list
        ;

base_list:
        dclass_name
{
  if ($1 != (DCClass *)NULL) {
    current_class->_parents.push_back($1);
  }
}
        | base_list ',' dclass_name
{
  if ($3 != (DCClass *)NULL) {
    current_class->_parents.push_back($3);
  }
}
        ;

dclass_fields:
        empty
        | dclass_fields ';'
        | dclass_fields atomic_field
        | dclass_fields molecular_field
        ;

atomic_field:
        IDENTIFIER '('
{
  current_atomic = new DCAtomicField;
  current_atomic->_name = $1;
  if (!current_class->add_field(current_atomic)) {
    yyerror("Duplicate field name: " + current_atomic->_name);
  }
}
        parameter_list ')' atomic_flags
        ;

atomic_name:
        IDENTIFIER
{
  DCField *field = current_class->get_field_by_name($1);
  $$ = (DCAtomicField *)NULL;
  if (field == (DCField *)NULL) {
    yyerror("Unknown field: " + $1);
  } else {
    $$ = field->as_atomic_field();
    if ($$ == (DCAtomicField *)NULL) {
      yyerror("Not an atomic field: " + $1);
    }
  }
}

parameter_list:
        empty
        | nonempty_parameter_list
        ;

nonempty_parameter_list:
        atomic_element
        | nonempty_parameter_list ',' atomic_element
        ;

atomic_element:
        type_token 
{
  atomic_element = DCAtomicField::ElementType();
  atomic_element._type = $1;
}
        atomic_element_definition
{
  current_atomic->_elements.push_back(atomic_element)
}
        ;

atomic_element_definition:
        empty
        | atomic_element_definition '/' INTEGER
{
  atomic_element._divisor = $3;
}
        | atomic_element_definition IDENTIFIER
{
  atomic_element._name = $2;
}
        | atomic_element_definition '=' INTEGER
{
  if (!atomic_element.set_default_value($3)) {
    yyerror("Invalid default value: " + $<str>3);
  }
}
        | atomic_element_definition '=' REAL
{
  if (!atomic_element.set_default_value($3)) {
    yyerror("Invalid default value: " + $<str>3);
  }
}
        | atomic_element_definition '=' STRING
{
  if (!atomic_element.set_default_value($3)) {
    yyerror("Invalid default value: \"" + $3 + "\"");
  }
}
        | atomic_element_definition '=' HEX_STRING
{
  if (!atomic_element.set_default_value_literal($3)) {
    yyerror("Invalid default hex string value");
  }
}
        | atomic_element_definition '=' '{' default_array '}'
{
  if (!atomic_element.end_array()) {
    yyerror("Array default value inappropriate");
  } else {
    atomic_element._has_default_value = true;
  }
}
        ;

default_array:
        empty
        | default_array_def maybe_comma
        ;

maybe_comma:
        empty
        | ','
        ;

default_array_def:
        default_array_element
        | default_array_def ',' default_array_element
        ;

default_array_element:
        INTEGER
{
  if (!atomic_element.add_default_value($1)) {
    yyerror("Invalid default value: " + $<str>1);
  }
}
        | REAL
{
  if (!atomic_element.add_default_value($1)) {
    yyerror("Invalid default value: " + $<str>1);
  }
}
        | STRING
{
  if (!atomic_element.add_default_value($1)) {
    yyerror("Invalid default value: " + $<str>1);
  }
}
        | HEX_STRING
{
  if (!atomic_element.add_default_value_literal($1)) {
    yyerror("Invalid hex literal in default array");
  }
}
        | INTEGER '*' INTEGER
{
  for (int i = 0; i < $3; i++) {
    if (!atomic_element.add_default_value($1)) {
      yyerror("Invalid default value: " + $<str>1);
      break;
    }
  }
}
        | REAL '*' INTEGER
{
  for (int i = 0; i < $3; i++) {
    if (!atomic_element.add_default_value($1)) {
      yyerror("Invalid default value: " + $<str>1);
      break;
    }
  }
}
        | HEX_STRING '*' INTEGER
{
  for (int i = 0; i < $3; i++) {
    if (!atomic_element.add_default_value_literal($1)) {
      yyerror("Invalid hex literal in default array");
      break;
    }
  }
}
        ;  

type_token:
        KW_INT8
{
  $$ = ST_int8;
}
        | KW_INT16
{
  $$ = ST_int16;
}
        | KW_INT32
{
  $$ = ST_int32;
}
        | KW_INT64
{
  $$ = ST_int64;
}
        | KW_UINT8
{
  $$ = ST_uint8;
}
        | KW_UINT16
{
  $$ = ST_uint16;
}
        | KW_UINT32
{
  $$ = ST_uint32;
}
        | KW_UINT64
{
  $$ = ST_uint64;
}
        | KW_FLOAT64
{
  $$ = ST_float64;
}
        | KW_STRING
{
  $$ = ST_string;
}
        | KW_BLOB
{
  $$ = ST_blob;
}
        | KW_INT8ARRAY
{
  $$ = ST_int8array;
}
        | KW_INT16ARRAY
{
  $$ = ST_int16array;
}
        | KW_INT32ARRAY
{
  $$ = ST_int32array;
}
        | KW_UINT8ARRAY
{
  $$ = ST_uint8array;
}
        | KW_UINT16ARRAY
{
  $$ = ST_uint16array;
}
        | KW_UINT32ARRAY
{
  $$ = ST_uint32array;
}
        | KW_UINT32UINT8ARRAY
{
  $$ = ST_uint32uint8array;
}
        ;

atomic_flags:
        empty
        | atomic_flags KW_REQUIRED
{
  current_atomic->_flags |= DCAtomicField::F_required;
}
        | atomic_flags KW_BROADCAST
{
  current_atomic->_flags |= DCAtomicField::F_broadcast;
}
        | atomic_flags KW_P2P
{
  current_atomic->_flags |= DCAtomicField::F_p2p;
}
        | atomic_flags KW_RAM
{
  current_atomic->_flags |= DCAtomicField::F_ram;
}
        | atomic_flags KW_DB
{
  current_atomic->_flags |= DCAtomicField::F_db;
}
        | atomic_flags KW_CLSEND
{
  current_atomic->_flags |= DCAtomicField::F_clsend;
}
        | atomic_flags KW_CLRECV
{
  current_atomic->_flags |= DCAtomicField::F_clrecv;
}
        | atomic_flags KW_OWNSEND
{
  current_atomic->_flags |= DCAtomicField::F_ownsend;
}
        ;

molecular_field:
        IDENTIFIER ':'
{
  current_molecular = new DCMolecularField;
  current_molecular->_name = $1;
  if (!current_class->add_field(current_molecular)) {
    yyerror("Duplicate field name: " + current_molecular->_name);
  }
}
        molecular_atom_list
        ;

molecular_atom_list:
        atomic_name
{
  if ($1 != (DCAtomicField *)NULL) {
    current_molecular->_fields.push_back($1);
  }
}
        | molecular_atom_list ',' atomic_name
{
  if ($3 != (DCAtomicField *)NULL) {
    current_molecular->_fields.push_back($3);
    if (current_molecular->_fields[0]->_flags != $3->_flags) {
      yyerror("Mismatched flags in molecule between " + 
              current_molecular->_fields[0]->_name + " and " +
              $3->_name);
    }
  }
}
        ;

empty:
        ;
