/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

%{

#include "AST.h"
#include "Declaration.h"
#include "Type.h"
#include "VarDeclaration.h"
#include "FunctionDeclaration.h"
#include "CompositeDeclaration.h"
#include "Define.h"
#include "Include.h"
#include "EnumVarDeclaration.h"
#include "Note.h"
#include "TypeDef.h"
#include "Expression.h"

#include "c2hal_y.h"

#include <stdio.h>
#include <algorithm>

using namespace android;

extern int yylex(YYSTYPE *yylval_param, YYLTYPE *llocp, void *);

int yyerror(YYLTYPE *llocp, AST *, const char *s) {
    extern bool should_report_errors;

    if (!should_report_errors) {
      return 0;
    }

    fflush(stdout);
    LOG(ERROR) << " "
               << s
               << " near line "
               << llocp->first_line;

    return 0;
}

#define scanner ast->scanner()

std::string get_last_comment() {
    extern std::string last_comment;

    std::string ret{last_comment};

    // clear the last comment now that it's been taken
    last_comment = "";

    return ret;
}

%}

%parse-param { android::AST *ast }
%lex-param   { void *scanner }
%locations
%pure-parser
%glr-parser

/* These have to do with the fact that
 * struct_or_union_declaration and enum_declaration
 * both start with STRUCT/UNION/ENUM opt_id
 * and type_qualifiers contain these.
 */
%expect 3

%token START_HEADER
%token START_EXPR

%token STRUCT
%token UNION
%token ENUM
%token CLASS
%token CONST
%token VOID
%token INCLUDE
%token DEFINE
%token TYPEDEF
%token UNSIGNED
%token SIGNED
%token LSHIFT
%token RSHIFT
%token VARARGS
%token NAMESPACE
%token EXTERN
%token C_STRING

%left ','
%right '?' ':'
%left '|'
%left '^'
%left '&'
%left RSHIFT LSHIFT
%left '+' '-'
%left '*' '/' '%'
%right '~' '!' UMINUS UPLUS
%left ARRAY_SUBSCRIPT FUNCTION_CALL

%right STRUCT ENUM

%token<str> ID
%token<str> COMMENT
%token<str> VALUE
%token<str> INTEGRAL_VALUE
%token<str> INCLUDE_FILE
%token<str> FUNCTION
%token<str> DEFINE_SLURP
%token<str> OTHER_STATEMENT

%type<expression> array
%type<expressions> arrays
%type<expression> expr
%type<expressions> args
%type<type> type
%type<type> opt_enum_base_type
%type<qualifier> type_qualifier
%type<qualifiers> type_qualifiers
%type<declaration> declaration
%type<declarations> declarations
%type<composite> struct_or_union_declaration
%type<composite> enum_declaration
%type<param> param
%type<params> params
%type<qualification> struct_or_union
%type<str> opt_id
%type<include> include
%type<enum_var> enum_var
%type<declarations> enum_vars enum_vars_all_but_last
%type<declaration> enum_var_line enum_var_last_line

%start parse_selector

%union {
    const char *str;
    int count;
    android::Declaration *declaration;
    android::CompositeDeclaration *composite;
    std::vector<android::Declaration *> *declarations;
    android::EnumVarDeclaration *enum_var;
    android::Declaration *param;
    std::vector<android::Declaration *> *params;
    android::Type *type;
    android::Type::Qualifier *qualifier;
    android::Type::Qualifier::Qualification qualification;
    std::vector<android::Type::Qualifier*> *qualifiers;
    android::Include *include;
    std::vector<android::Include *> *includes;
    android::Expression *expression;
    std::vector<android::Expression *> *expressions;
}

%%

parse_selector
    : START_HEADER header
    | START_EXPR expr_parser
    ;

expr_parser
    : expr
      {
        ast->setExpression($1);
      }
    ;

header
    : declarations /* well, we are a header file */
      {
        ast->setDeclarations($1);
      }
    ;

declarations
    : /* EMPTY */
      {
        $$ = new std::vector<Declaration *>;
      }
    | declarations declaration
      {
        $$ = $1;
        $$->push_back($2);
      }
    | declarations EXTERN C_STRING '{' declarations '}'
      {
        $1->push_back(new Note("extern \"C\" { "));
        $1->insert($1->end(), $5->begin(), $5->end());
        $1->push_back(new Note("} // end of extern C"));
        delete $5;

        $$ = $1;
      }
    ;

declaration
    : param ';'
      {
        $$ = $1;
        $$->setComment(get_last_comment());
      }
    | struct_or_union_declaration ';'
      {
        $$ = $1;
      }
    | enum_declaration ';'
      {
        $$ = $1;
      }
    | TYPEDEF struct_or_union_declaration ';'
      {
        // ignore that it is a typedef, for our purposes it doesn't matter
        $$ = $2;
      }
    | TYPEDEF enum_declaration ';'
      {
        // ignore that it is a typedef, for our purposes it doesn't matter
        $$ = $2;
      }
    | TYPEDEF param ';' /* looks like 'typedef const int8_t store;' */
      {
        $$ = new TypeDef($2->getName(), $2);
        $$->setComment(get_last_comment());
      }
    | DEFINE ID DEFINE_SLURP
      {
        $$ = new Define($2, $3);
        $$->setComment(get_last_comment());
      }
    | OTHER_STATEMENT
      {
        $$ = new Note($1);
        $$->setComment(get_last_comment());
      }
    | FUNCTION
      {
        $$ = new Note($1);
        $$->setComment(get_last_comment());
      }
    | type ID '=' expr ';'
      {
        $$ = new Note($1->decorateName($2) + " = " + $4->toString());
      }
    | include
      {
        $$ = $1;
        $$->setComment(get_last_comment());
      }
    | NAMESPACE ID '{' declarations '}'
      {
        $$ = new CompositeDeclaration(Type::Qualifier::STRUCT,
                                               $2,
                                               $4);

        get_last_comment(); // clear it
        $$->setComment("/* from namespace declaration */");
      }
    ;

include
    : INCLUDE '<' INCLUDE_FILE '>'
      {
        $$ = new Include($3, true /* isLibrary */);
      }
    | INCLUDE '"' INCLUDE_FILE '"'
      {
        $$ = new Include($3, false /* isLibrary */);
      }
    ;

struct_or_union_declaration
    : struct_or_union opt_id
      {
        $<str>$ = strdup(get_last_comment().c_str());
      }
                             '{' declarations '}' opt_id
      {
        $$ = new CompositeDeclaration($1, $2, $5);
        $$->setComment($<str>3);

        if(!std::string($7).empty()) {
          $$->setName($7);
        }
      }
    ;

opt_comma
    : /* EMPTY */
    | ','
    ;

enum_key
    : ENUM
    | ENUM CLASS  /* c++11 */
    | ENUM STRUCT /* c++11 */
    ;

opt_enum_base_type
    : /* EMPTY */ { $$ = NULL; }
    | ':' type    { $$ = $2; }
    ;

enum_declaration
    : enum_key opt_id
      {
        $<str>$ = strdup(get_last_comment().c_str());
      }
                        opt_enum_base_type '{' enum_vars '}' opt_id
      {
        $$ = new CompositeDeclaration(Type::Qualifier::ENUM, $2, $6);
        $$->setComment($<str>3);

        if($4) {
          $$->setEnumTypeName($4->decorateName(""));
          delete $4;
        }

        if(!std::string($8).empty()) {
          $$->setName($8);
        }
      }
    ;

enum_vars
    : /* EMPTY */
      {
        $$ = new std::vector<Declaration *>;
      }
    | enum_vars_all_but_last enum_var_last_line
      {
        $$ = $1;
        $$->push_back($2);
      }

enum_vars_all_but_last
    : /* EMPTY */
      {
        $$ = new std::vector<Declaration *>;
      }
    | enum_vars_all_but_last enum_var_line
      {
        $$ = $1;
        $$->push_back($2);
      }
    ;

enum_var_last_line
    : enum_var opt_comma { $$ = $1; }
    | OTHER_STATEMENT
      {
        $$ = new Note($1);
        $$->setComment(get_last_comment());
      }
    ;

enum_var_line
    : enum_var ',' { $$ = $1; }
    | OTHER_STATEMENT
      {
        $$ = new Note($1);
        $$->setComment(get_last_comment());
      }
    ;

enum_var
    : ID
      {
        $$ = new EnumVarDeclaration($1, NULL);
        $$->setComment(get_last_comment());
      }
    | ID '=' expr
      {
        $$ = new EnumVarDeclaration($1, $3);
        $$->setComment(get_last_comment());
      }
    ;

params
    : /* EMPTY */
      {
        $$ = new std::vector<Declaration *>;
      }
    | param
      {
        $$ = new std::vector<Declaration *>;
        $$->push_back($1);
      }
    | params ',' param
      {
        $$ = $1;
        $$->push_back($3);
      }
    ;

param
    : type arrays
      {
        $1->setArrays($2);

        // allow for either "const int myvar" or "const int"
        // as a parameter declaration
        std::string lastId = $1->removeLastId();

        $$ = new VarDeclaration($1, lastId);
      }
    | type '(' '*' ID arrays ')' '(' params ')'
      {
        $1->setArrays($5);
        $$ = new FunctionDeclaration($1, $4, $8);
      }
    | type ID '(' params ')'
      {
        $$ = new FunctionDeclaration($1, $2, $4);
      }
    | type '(' ID ')' '(' params ')'
      {
        $$ = new FunctionDeclaration($1, $3, $6);
      }
    | VARARGS
      {
        $$ = new VarDeclaration(new Type(NULL), "...");
      }
    ;

type
    : type_qualifiers
      {
        $$ = new Type($1);
      }
    ;

type_qualifiers
    : type_qualifier
     {
        $$ = new std::vector<Type::Qualifier *>;
        $$->push_back($1);
     }
    | type_qualifiers type_qualifier
     {
        $$ = $1;
        $$->push_back($2);
     }
    ;

opt_id
    : /* EMPTY */             { $$ = ""; }
    |
     ID                       { $$ = $1; }
    ;

expr
    : ID
      {
        $$ = Expression::atom(Expression::Type::UNKNOWN, $1, true /* isId*/ );
      }
    | VALUE                   { $$ = Expression::atom(Expression::Type::UNKNOWN, $1); }
    | INTEGRAL_VALUE          { $$ = Expression::atom(Expression::integralType($1), $1); }
    | '(' expr ')'            { $$ = Expression::parenthesize($2); }
    | ID '[' expr ']' %prec ARRAY_SUBSCRIPT {
                                $$ = Expression::arraySubscript($1, $3);
                              }
    | ID '(' args ')' %prec FUNCTION_CALL {
                                $$ = Expression::functionCall($1, $3);
                              }
    | expr '?' expr ':' expr  { $$ = Expression::ternary($1, $3, $5); }
    | expr '+' expr           { $$ = Expression::binary($1, "+", $3); }
    | expr '-' expr           { $$ = Expression::binary($1, "-", $3); }
    | expr '/' expr           { $$ = Expression::binary($1, "/", $3); }
    | expr '*' expr           { $$ = Expression::binary($1, "*", $3); }
    | expr '%' expr           { $$ = Expression::binary($1, "%%", $3); }
    | expr '&' expr           { $$ = Expression::binary($1, "&", $3); }
    | expr '|' expr           { $$ = Expression::binary($1, "|", $3); }
    | expr '^' expr           { $$ = Expression::binary($1, "^", $3); }
    | expr LSHIFT expr        { $$ = Expression::binary($1, "<<", $3); }
    | expr RSHIFT expr        { $$ = Expression::binary($1, ">>", $3); }
    | '~' expr                { $$ = Expression::unary("~", $2); }
    | '-' expr %prec UMINUS   { $$ = Expression::unary("-", $2); }
    | '+' expr %prec UPLUS    { $$ = Expression::unary("+", $2); }
    ;

args
    : /* empty */
      {
        $$ = new std::vector<Expression *>;
      }
    | expr
      {
        $$ = new std::vector<Expression *>;
        $$->push_back($1);
      }
    | args ',' expr
      {
        $$ = $1;
        $$->push_back($3);
      }
    ;

type_qualifier
    : UNSIGNED                { $$ = new Type::Qualifier(Type::Qualifier::UNSIGNED); }
    | SIGNED                  { $$ = new Type::Qualifier(Type::Qualifier::SIGNED); }
    | VOID                    { $$ = new Type::Qualifier(Type::Qualifier::VOID); }
    | '*'                     { $$ = new Type::Qualifier(Type::Qualifier::POINTER); }
    | CONST                   { $$ = new Type::Qualifier(Type::Qualifier::CONST); }
    | ID                      { $$ = new Type::Qualifier(Type::Qualifier::ID, $1); }
    | '<' type '>'            { $$ = new Type::Qualifier(Type::Qualifier::GENERICS, $2); }
    | enum_key                { $$ = new Type::Qualifier(Type::Qualifier::ENUM); }
    | struct_or_union         { $$ = new Type::Qualifier($1); }
    ;

struct_or_union
    : STRUCT                  { $$ = android::Type::Qualifier::STRUCT; }
    | UNION                   { $$ = android::Type::Qualifier::UNION; }
    ;

arrays
    : /* empty */             { $$ = new std::vector<Expression *>; }
    | arrays array            {
                                $$ = $1;
                                $$->push_back($2);
                              }
    ;

array
    : '[' ']'                 { $$ = Expression::atom(Expression::Type::UNKNOWN, " "); }
    | '[' expr ']'            { $$ = $2; }
    ;

%%