%{
#include "aidl_language.h"
#include "aidl_language_y.h"
#include "logging.h"
#include <set>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int yylex(yy::parser::semantic_type *, yy::parser::location_type *, void *);

AidlLocation loc(const yy::parser::location_type& l) {
  CHECK(l.begin.filename == l.end.filename);
  AidlLocation::Point begin {
    .line = l.begin.line,
    .column = l.begin.column,
  };
  AidlLocation::Point end {
    .line = l.end.line,
    .column = l.end.column,
  };
  return AidlLocation(*l.begin.filename, begin, end);
}

#define lex_scanner ps->Scanner()

%}

%initial-action {
    @$.begin.filename = @$.end.filename =
        const_cast<std::string *>(&ps->FileName());
}

%parse-param { Parser* ps }
%lex-param { void *lex_scanner }

%pure-parser
%glr-parser
%skeleton "glr.cc"

%expect-rr 0

%error-verbose

%union {
    AidlToken* token;
    char character;
    std::string *str;
    AidlAnnotation* annotation;
    std::vector<AidlAnnotation>* annotation_list;
    AidlTypeSpecifier* type;
    AidlArgument* arg;
    AidlArgument::Direction direction;
    AidlConstantValue* constant_value;
    std::vector<std::unique_ptr<AidlConstantValue>>* constant_value_list;
    std::vector<std::unique_ptr<AidlArgument>>* arg_list;
    AidlVariableDeclaration* variable;
    std::vector<std::unique_ptr<AidlVariableDeclaration>>* variable_list;
    AidlMethod* method;
    AidlMember* constant;
    std::vector<std::unique_ptr<AidlMember>>* interface_members;
    AidlQualifiedName* qname;
    AidlInterface* interface;
    AidlParcelable* parcelable;
    AidlDefinedType* declaration;
    std::vector<std::unique_ptr<AidlTypeSpecifier>>* type_args;
}

%token<token> ANNOTATION "annotation"
%token<token> C_STR "string literal"
%token<token> IDENTIFIER "identifier"
%token<token> INTERFACE "interface"
%token<token> PARCELABLE "parcelable"
%token<token> ONEWAY "oneway"

%token<character> CHARVALUE "char literal"
%token<token> FLOATVALUE "float literal"
%token<token> HEXVALUE "hex literal"
%token<token> INTVALUE "int literal"

%token '(' ')' ',' '=' '[' ']' '<' '>' '.' '{' '}' ';'
%token CONST "const"
%token UNKNOWN "unrecognized character"
%token CPP_HEADER "cpp_header"
%token IMPORT "import"
%token IN "in"
%token INOUT "inout"
%token OUT "out"
%token PACKAGE "package"
%token TRUE_LITERAL "true"
%token FALSE_LITERAL "false"

%type<declaration> decl
%type<variable_list> variable_decls
%type<variable> variable_decl
%type<interface_members> interface_members
%type<declaration> unannotated_decl
%type<interface> interface_decl
%type<parcelable> parcelable_decl
%type<method> method_decl
%type<constant> constant_decl
%type<annotation> annotation
%type<annotation_list>annotation_list
%type<type> type
%type<type> unannotated_type
%type<arg_list> arg_list
%type<arg> arg
%type<direction> direction
%type<type_args> type_args
%type<qname> qualified_name
%type<constant_value> constant_value
%type<constant_value_list> constant_value_list
%type<constant_value_list> constant_value_non_empty_list

%type<token> identifier error
%%
document
 : package imports decls {};

/* A couple of tokens that are keywords elsewhere are identifiers when
 * occurring in the identifier position. Therefore identifier is a
 * non-terminal, which is either an IDENTIFIER token, or one of the
 * aforementioned keyword tokens.
 */
identifier
 : IDENTIFIER
  { $$ = $1; }
 | CPP_HEADER
  { $$ = new AidlToken("cpp_header", ""); }
 ;

package
 : {}
 | PACKAGE qualified_name ';'
  { ps->SetPackage(unique_ptr<AidlQualifiedName>($2)); };

imports
 : {}
 | import imports {};

import
 : IMPORT qualified_name ';'
  { ps->AddImport(new AidlImport(loc(@2), $2->GetDotName()));
    delete $2;
  };

qualified_name
 : identifier {
    $$ = new AidlQualifiedName(loc(@1), $1->GetText(), $1->GetComments());
    delete $1;
  }
 | qualified_name '.' identifier
  { $$ = $1;
    $$->AddTerm($3->GetText());
    delete $3;
  };

decls
 : decl {
    ps->AddDefinedType(unique_ptr<AidlDefinedType>($1));
  }
 | decls decl {
    ps->AddDefinedType(unique_ptr<AidlDefinedType>($2));
  };

decl
 : annotation_list unannotated_decl
   {
    $$ = $2;

    if ($1->size() > 0) {
      // copy comments from annotation to decl
      $2->SetComments($1->begin()->GetComments());
    }

    $$->Annotate(std::move(*$1));
    delete $1;
   }
 ;

unannotated_decl
 : parcelable_decl
  { $$ = $1; }
 | interface_decl
  { $$ = $1; }
 ;

parcelable_decl
 : PARCELABLE qualified_name ';' {
    $$ = new AidlParcelable(loc(@2), $2, ps->Package(), $1->GetComments());
  }
 | PARCELABLE qualified_name CPP_HEADER C_STR ';' {
    $$ = new AidlParcelable(loc(@2), $2, ps->Package(), $1->GetComments(), $4->GetText());
  }
 | PARCELABLE identifier '{' variable_decls '}' {
    AidlQualifiedName* name = new AidlQualifiedName(loc(@2), $2->GetText(), $2->GetComments());
    $$ = new AidlStructuredParcelable(loc(@2), name, ps->Package(), $1->GetComments(), $4);
 }
 | PARCELABLE error ';' {
    ps->AddError();
    $$ = NULL;
  };

variable_decls
 : /* empty */ {
    $$ = new std::vector<std::unique_ptr<AidlVariableDeclaration>>;
 }
 | variable_decls variable_decl {
    $$ = $1;
    if ($2 != nullptr) {
      $$->push_back(std::unique_ptr<AidlVariableDeclaration>($2));
    }
 };

variable_decl
 : type identifier ';' {
   $$ = new AidlVariableDeclaration(loc(@2), $1, $2->GetText());
 }
 | type identifier '=' constant_value ';' {
   $$ = new AidlVariableDeclaration(loc(@2), $1, $2->GetText(),  $4);
 }
 | error ';' {
   ps->AddError();
   $$ = nullptr;
 }

interface_decl
 : INTERFACE identifier '{' interface_members '}' {
    $$ = new AidlInterface(loc(@1), $2->GetText(), $1->GetComments(), false, $4, ps->Package());
    delete $1;
    delete $2;
  }
 | ONEWAY INTERFACE identifier '{' interface_members '}' {
    $$ = new AidlInterface(loc(@2), $3->GetText(),  $1->GetComments(), true, $5, ps->Package());
    delete $1;
    delete $2;
    delete $3;
  }
 | INTERFACE error '{' interface_members '}' {
    ps->AddError();
    $$ = nullptr;
    delete $1;
    delete $2;
    delete $4;
  };

interface_members
 :
  { $$ = new std::vector<std::unique_ptr<AidlMember>>(); }
 | interface_members method_decl
  { $1->push_back(std::unique_ptr<AidlMember>($2)); }
 | interface_members constant_decl
  { $1->push_back(std::unique_ptr<AidlMember>($2)); }
 | interface_members error ';' {
    ps->AddError();
    $$ = $1;
  };

constant_value
 : TRUE_LITERAL { $$ = AidlConstantValue::Boolean(loc(@1), true); }
 | FALSE_LITERAL { $$ = AidlConstantValue::Boolean(loc(@1), false); }
 | CHARVALUE { $$ = AidlConstantValue::Character(loc(@1), $1); }
 | INTVALUE {
    $$ = AidlConstantValue::Integral(loc(@1), $1->GetText());
    delete $1;
  }
 | FLOATVALUE {
    $$ = AidlConstantValue::Floating(loc(@1), $1->GetText());
    delete $1;
  }
 | HEXVALUE {
    $$ = AidlConstantValue::Hex(loc(@1), $1->GetText());
    delete $1;
  }
 | C_STR {
    $$ = AidlConstantValue::String(loc(@1), $1->GetText());
    delete $1;
  }
 | '{' constant_value_list '}' {
    $$ = AidlConstantValue::Array(loc(@1), $2);
    delete $2;
  }
 ;

constant_value_list
 : /* empty */ {
    $$ = new std::vector<std::unique_ptr<AidlConstantValue>>;
 }
 | constant_value_non_empty_list {
    $$ = $1;
 }
 ;

constant_value_non_empty_list
 : constant_value {
    $$ = new std::vector<std::unique_ptr<AidlConstantValue>>;
    $$->push_back(std::unique_ptr<AidlConstantValue>($1));
 }
 | constant_value_non_empty_list ',' constant_value {
    $$ = $1;
    $$->push_back(std::unique_ptr<AidlConstantValue>($3));
 }
 ;

constant_decl
 : CONST type identifier '=' constant_value ';' {
    $$ = new AidlConstantDeclaration(loc(@3), $2, $3->GetText(), $5);
    delete $3;
   }
 ;

method_decl
 : type identifier '(' arg_list ')' ';' {
    $$ = new AidlMethod(loc(@2), false, $1, $2->GetText(), $4, $1->GetComments());
    delete $2;
  }
 | ONEWAY type identifier '(' arg_list ')' ';' {
    $$ = new AidlMethod(loc(@3), true, $2, $3->GetText(), $5, $1->GetComments());
    delete $1;
    delete $3;
  }
 | type identifier '(' arg_list ')' '=' INTVALUE ';' {
    $$ = new AidlMethod(loc(@2), false, $1, $2->GetText(), $4, $1->GetComments(), std::stoi($7->GetText()));
    delete $2;
  }
 | ONEWAY type identifier '(' arg_list ')' '=' INTVALUE ';' {
    $$ = new AidlMethod(loc(@3), true, $2, $3->GetText(), $5, $1->GetComments(), std::stoi($8->GetText()));
    delete $1;
    delete $3;
  };

arg_list
 :
  { $$ = new std::vector<std::unique_ptr<AidlArgument>>(); }
 | arg {
    $$ = new std::vector<std::unique_ptr<AidlArgument>>();
    $$->push_back(std::unique_ptr<AidlArgument>($1));
  }
 | arg_list ',' arg {
    $$ = $1;
    $$->push_back(std::unique_ptr<AidlArgument>($3));
  };

arg
 : direction type identifier {
    $$ = new AidlArgument(loc(@3), $1, $2, $3->GetText());
    delete $3;
  }
 | type identifier {
    $$ = new AidlArgument(loc(@2), $1, $2->GetText());
    delete $2;
  }
 | error {
    ps->AddError();
  };

unannotated_type
 : qualified_name {
    $$ = new AidlTypeSpecifier(loc(@1), $1->GetDotName(), false, nullptr, $1->GetComments());
    ps->DeferResolution($$);
    delete $1;
  }
 | qualified_name '[' ']' {
    $$ = new AidlTypeSpecifier(loc(@1), $1->GetDotName(), true, nullptr, $1->GetComments());
    ps->DeferResolution($$);
    delete $1;
  }
 | qualified_name '<' type_args '>' {
    $$ = new AidlTypeSpecifier(loc(@1), $1->GetDotName(), false, $3, $1->GetComments());
    ps->DeferResolution($$);
    delete $1;
  };

type
 : annotation_list unannotated_type {
    $$ = $2;
    if ($1->size() > 0) {
      // copy comments from annotation to type
      $2->SetComments($1->begin()->GetComments());
    }
    $2->Annotate(std::move(*$1));
    delete $1;
  };

type_args
 : unannotated_type {
    $$ = new std::vector<std::unique_ptr<AidlTypeSpecifier>>();
    $$->emplace_back($1);
  }
 | type_args ',' unannotated_type {
    $1->emplace_back($3);
  };

annotation_list
 :
  { $$ = new std::vector<AidlAnnotation>(); }
 | annotation_list annotation
  {
    if ($2 != nullptr) {
      $1->emplace_back(std::move(*$2));
      delete $2;
    }
  };

annotation
 : ANNOTATION
  {
    $$ = AidlAnnotation::Parse(loc(@1), $1->GetText());
    if ($$ == nullptr) {
      ps->AddError();
    }
    $$->SetComments($1->GetComments());
  };

direction
 : IN
  { $$ = AidlArgument::IN_DIR; }
 | OUT
  { $$ = AidlArgument::OUT_DIR; }
 | INOUT
  { $$ = AidlArgument::INOUT_DIR; };

%%

#include <ctype.h>
#include <stdio.h>

void yy::parser::error(const yy::parser::location_type& l, const std::string& errstr) {
  AIDL_ERROR(loc(l)) << errstr;
  // parser will return error value
}