////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2016 by EMC Corporation, All Rights Reserved
///
/// 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.
///
/// Copyright holder is EMC Corporation
///
/// @author Andrey Abramov
/// @author Vasiliy Nabatchikov
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief request C++ API and customize names and arguments
////////////////////////////////////////////////////////////////////////////////
%skeleton "glr.cc" // required since "lalr1.cc" does not suppot %glr-parser
%require "2.4"
%glr-parser
%defines
%define namespace "iresearch::iql"
%expect 7    // expect +7 shift/reduce as per bison generation of grammer below
%expect-rr 9 // expect +9 reduce/reduce as per bison generation of grammer below
%parse-param { iresearch::iql::context& ctx }

////////////////////////////////////////////////////////////////////////////////
/// @brief add to start of HH file
////////////////////////////////////////////////////////////////////////////////
%code requires {
  #define YYSTYPE size_t

  #if YYDEBUG
    #define YYERROR_VERBOSE 1

    // this is required only for %skeleton "glr.cc" since it lacks default init
    // not needed for %skeleton "lalr1.cc" or without %skeleton
    #define YYLTYPE iresearch::iql::location_type1
  #endif

  namespace iresearch {
    namespace iql {
      class context;
    }
  }

  // Suppress warnings due to Bison generated code (push section, pop just below)
  #if defined(_MSC_VER)
    #pragma warning(disable : 4512)
  #endif

  // ALWAYS!!! define YYDEBUG 1 for the length of the header so as to avoid
  // alignment issues when linkning without YYDEBUG agains a library that was
  // built with YYDEBUG or vice versa, reverse at end of header
  #undef YYDEBUG_REQUESTED
  #if YYDEBUG
    #define YYDEBUG_REQUESTED
  #else
    #undef YYDEBUG
    #define YYDEBUG 1
  #endif
}

////////////////////////////////////////////////////////////////////////////////
/// @brief add to end of HH file
////////////////////////////////////////////////////////////////////////////////
%code provides {
  // ALWAYS!!! define YYDEBUG 1 for the length of the header so as to avoid
  // alignment issues when linkning without YYDEBUG agains a library that was
  // built with YYDEBUG or vice versa, reverse at end of header
  #ifdef YYDEBUG_REQUESTED
    #undef YYDEBUG_REQUESTED
  #else
    #undef YYDEBUG
    #define YYDEBUG 0
  #endif

  // end of suppress of warnings due to Bison generated code (pop section, push just above)
  #if defined(_MSC_VER)
    #pragma warning(default : 4512)
  #endif

  namespace iresearch {
    namespace iql {
      class context {
      public:
        // destructor
        virtual ~context() = default;

        // parser operations
        virtual void yyerror(parser::location_type const& location, std::string const& sError) = 0;
        virtual parser::token_type yylex(parser::semantic_type& value, parser::location_type& location) = 0;

        // value operations
        virtual parser::semantic_type sequence(parser::location_type const& location) = 0;

        // node operations
        virtual parser::semantic_type append(parser::semantic_type const& value, parser::location_type const& location) = 0;
        virtual parser::semantic_type boost(parser::semantic_type const& value, parser::location_type const& location) = 0;
        virtual parser::semantic_type function(parser::semantic_type const& name, parser::semantic_type const& args) = 0;
        virtual parser::semantic_type list(parser::semantic_type const& value1, parser::semantic_type const& value2) = 0;
        virtual parser::semantic_type negation(parser::semantic_type const& value) = 0;
        virtual parser::semantic_type range(parser::semantic_type const& value1, bool bInclusive1, parser::semantic_type const& value2, bool bInclusive2) = 0;

        // comparison operations
        virtual parser::semantic_type op_eq(parser::semantic_type const& value1, parser::semantic_type const& value2) = 0;
        virtual parser::semantic_type op_like(parser::semantic_type const& value1, parser::semantic_type const& value2) = 0;

        // filter operations
        virtual parser::semantic_type op_and(parser::semantic_type const& value1, parser::semantic_type const& value2) = 0;
        virtual parser::semantic_type op_or(parser::semantic_type const& value1, parser::semantic_type const& value2) = 0;

        // query operations
        virtual bool addOrder(parser::semantic_type const& value, bool bAscending = true) = 0;
        virtual bool setLimit(parser::semantic_type const& value) = 0;
        virtual bool setQuery(parser::semantic_type const& value) = 0;
      };

      void debug(parser& parser, bool bEnable);

      // this is required only for %skeleton "glr.cc" since it lacks default init
      // not needed for %skeleton "lalr1.cc" or without %skeleton
      #if YYDEBUG
        class location_type1: public location {};
      #endif
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
/// @brief add to CC file
////////////////////////////////////////////////////////////////////////////////
%code {
  #define yylex(pValue, pLocation) ctx.yylex(*pValue, *pLocation)

  namespace iresearch {
    namespace iql {
      void parser::error(parser::location_type const& location, std::string const& sError) {
          ctx.yyerror(location, sError);
      }
    }
  }

  // this is required only for %skeleton "glr.cc" since it lacks default init
  // but yy_symbol_print uses yylocationp->[begin|end].filename and segfaults
  // not needed for %skeleton "lalr1.cc" or without %skeleton
  #if YYDEBUG
    static void yy_symbol_print(FILE*, int, iresearch::iql::parser::semantic_type const*, iresearch::iql::parser::location_type const*, iresearch::iql::parser&, iresearch::iql::context&);
    static void yy_symbol_print(FILE* filep, int yytype, YYSTYPE const* yyvaluep, YYLTYPE const* yylocationp, iresearch::iql::parser& yyparser, iresearch::iql::context& ctx) {
      iresearch::iql::parser::location_type yylocation = *yylocationp;

      yylocation.begin.filename = nullptr;
      yylocation.end.filename = nullptr;
      yy_symbol_print(filep, yytype, yyvaluep, &yylocation, yyparser, ctx);
    }
  #endif

  // ALWAYS!!! define YYDEBUG 1 for the length of the header so as to avoid
  // alignment issues when linkning without YYDEBUG agains a library that was
  // built with YYDEBUG or vice versa
  // add method stub implementations here for debug methods declared in header
  // since they are masked out in Bison generated code by !YYDEBUG
  #if !YYDEBUG
    namespace iresearch {
      namespace iql {
        inline void parser::yy_symbol_value_print_(int /* yytype */, const semantic_type* /* yyvaluep */, const location_type* /* yylocationp */) {}
        inline void parser::yy_symbol_print_(int /* yytype */, const semantic_type* /* yyvaluep */, const location_type* /* yylocationp */) {}
      }
    }
  #endif

  // Suppress warnings due to Bison generated code (push section, pop at end of file)
  #if defined(_MSC_VER)
    #pragma warning(disable : 4100)
    #pragma warning(disable : 4127)
    #pragma warning(disable : 4244)
    #pragma warning(disable : 4505)
    #pragma warning(disable : 4611)
    #pragma warning(disable : 4706)
  #endif
}

// -----------------------------------------------------------------------------
// --SECTION--                                                Bison declarations
// -----------------------------------------------------------------------------

// basic tokens
%token IQL_EOF 0
%token IQL_UNKNOWN   "unknown token marker"
%token IQL_SEP       "token separator"
%token IQL_SEQUENCE  "data sub-sequence"

// logical operators
%token IQL_NOT       "negation operator"
%token IQL_EXCLAIM   "exclamation mark"
%token IQL_AND        "logical AND operator"
%token IQL_AMPAMP     "double ampersand"
%token IQL_OR         "logical OR operator"
%token IQL_PIPEPIPE   "double pipe"

// comparison operators
%token IQL_NE        "not equal operator"
%token IQL_LE        "less than or equal operator"
%token IQL_EQ        "equal operator"
%token IQL_GE        "greater than or equal operator"
%token IQL_LIKE      "similar operator"

// modification operators
%token IQL_ASTERISK  "asterisk operator"
%token IQL_ASC       "ascending sort order"
%token IQL_DESC      "descending sort order"

// grammer delimiters
%token IQL_COMMA     "comma"
%token IQL_SQUOTE    "single quote"
%token IQL_DQUOTE    "double quote"
%token IQL_LCHEVRON  "left chevron '<' delimiter"
%token IQL_RCHEVRON  "right chevron '>' delimiter"
%token IQL_LPAREN    "left parentheses '(' delimiter"
%token IQL_RPAREN    "right parentheses ')' delimiter"
%token IQL_LSBRACKET "left square bracket '[' delimiter"
%token IQL_RSBRACKET "right square bracket ']' delimiter"

// grammer tokens
%token IQL_LIMIT     "result limit section header"
%token IQL_ORDER     "sort order section header"

%start input

// -----------------------------------------------------------------------------
// --SECTION--                                                     Grammar rules
// -----------------------------------------------------------------------------
%%

input:
  optional_sep union order limit optional_sep { if (!ctx.setQuery($2)) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
;

optional_sep:
// empty since it is optional
| optional_sep IQL_SEP
;

union:
  intersection
| union IQL_SEP      IQL_OR       IQL_SEP      intersection { if (!($$ = ctx.op_or($1, $5))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| union optional_sep IQL_PIPEPIPE optional_sep intersection { if (!($$ = ctx.op_or($1, $5))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
;

intersection:
  expression
| intersection IQL_SEP      IQL_AND    IQL_SEP      expression { if (!($$ = ctx.op_and($1, $5))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| intersection optional_sep IQL_AMPAMP optional_sep expression { if (!($$ = ctx.op_and($1, $5))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
;

expression:
  boost
| compare
| negation
| subexpression
;

boost:
  plain_literal optional_sep IQL_ASTERISK optional_sep subexpression { if (!($$ = ctx.boost($5, @1))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| subexpression optional_sep IQL_ASTERISK optional_sep plain_literal { if (!($$ = ctx.boost($1, @5))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
;

negation:
  IQL_NOT     optional_sep subexpression { if (!($$ = ctx.negation($3))) YYERROR; @$.begin = @1.begin; @$.end = @3.end; }
| IQL_EXCLAIM optional_sep subexpression { if (!($$ = ctx.negation($3))) YYERROR; @$.begin = @1.begin; @$.end = @3.end; }
;

subexpression:
  IQL_LPAREN optional_sep union optional_sep IQL_RPAREN { $$ = $3; @$.begin = @1.begin; @$.end = @5.end; }
| function
;

function:
  sequence IQL_LPAREN optional_sep IQL_RPAREN                                { if (!($$ = ctx.function($1, $3))) YYERROR; @$.begin = @1.begin; @$.end = @4.end; }
| sequence IQL_LPAREN optional_sep function_arg_list optional_sep IQL_RPAREN { if (!($$ = ctx.function($1, $4))) YYERROR; @$.begin = @1.begin; @$.end = @6.end; }
;

function_arg:
  sequence
| expression
;

function_arg_list:
  function_arg
| function_arg_list list_sep function_arg { if (!($$ = ctx.list($1, $3))) YYERROR; @$.begin = @1.begin; @$.end = @3.end; }

compare:
  term optional_sep IQL_LIKE     optional_sep term  { if (!($$ = ctx.op_like($1, $5))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| term optional_sep IQL_NE       optional_sep term  { if (!($$ = ctx.negation($$ = ctx.op_eq($1, $$ = ctx.range($5, true, $5, true))))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| term optional_sep IQL_LCHEVRON optional_sep term  { if (!($$ = ctx.op_eq($1, $$ = ctx.range($3, true, $5, false)))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| term optional_sep IQL_LE       optional_sep term  { if (!($$ = ctx.op_eq($1, $$ = ctx.range($3, true, $5, true)))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| term optional_sep IQL_EQ       optional_sep term  { if (!($$ = ctx.op_eq($1, $$ = ctx.range($5, true, $5, true)))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| term optional_sep IQL_GE       optional_sep term  { if (!($$ = ctx.op_eq($1, $$ = ctx.range($5, true, $3, true)))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| term optional_sep IQL_RCHEVRON optional_sep term  { if (!($$ = ctx.op_eq($1, $$ = ctx.range($5, false, $3, true)))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| term optional_sep IQL_NE       optional_sep range { if (!($$ = ctx.negation($$ = ctx.op_eq($1, $5)))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
| term optional_sep IQL_EQ       optional_sep range { if (!($$ = ctx.op_eq($1, $5))) YYERROR; @$.begin = @1.begin; @$.end = @5.end; }
;

range:
  IQL_LSBRACKET optional_sep term list_sep term optional_sep IQL_RSBRACKET { if (!($$ = ctx.range($3, true, $5, true))) YYERROR; @$.begin = @1.begin; @$.end = @7.end; }
| IQL_LSBRACKET optional_sep term list_sep term optional_sep IQL_RPAREN    { if (!($$ = ctx.range($3, true, $5, false))) YYERROR; @$.begin = @1.begin; @$.end = @7.end; }
| IQL_LPAREN    optional_sep term list_sep term optional_sep IQL_RPAREN    { if (!($$ = ctx.range($3, false, $5, false))) YYERROR; @$.begin = @1.begin; @$.end = @7.end; }
| IQL_LPAREN    optional_sep term list_sep term optional_sep IQL_RSBRACKET { if (!($$ = ctx.range($3, false, $5, true))) YYERROR; @$.begin = @1.begin; @$.end = @7.end; }
;

term:
  sequence
| function
;

list_sep:
  optional_sep IQL_COMMA optional_sep
;

sequence:
  plain_literal
| dquoted_literal
| squoted_literal
;

plain_literal:
  IQL_SEQUENCE               { if (!($$ = ctx.sequence(@1))) YYERROR; @$.begin = @1.begin; @$.end = @1.end; }
| plain_literal IQL_SEQUENCE { if (!($$ = ctx.append($1, @2))) YYERROR; @$.begin = @1.begin; @$.end = @2.end; }
;

dquoted_literal:
  IQL_DQUOTE IQL_SEQUENCE IQL_DQUOTE                 { if (!($$ = ctx.sequence(@2))) YYERROR; @$.begin = @1.begin; @$.end = @3.end; }
| dquoted_literal IQL_DQUOTE IQL_SEQUENCE IQL_DQUOTE { if (!($$ = ctx.append($$ = ctx.append($1, @2), @3))) YYERROR; @$.begin = @1.begin; @$.end = @4.end; }
;

squoted_literal:
  IQL_SQUOTE IQL_SEQUENCE IQL_SQUOTE                 { if (!($$ = ctx.sequence(@2))) YYERROR; @$.begin = @1.begin; @$.end = @3.end; }
| squoted_literal IQL_SQUOTE IQL_SEQUENCE IQL_SQUOTE { if (!($$ = ctx.append($$ = ctx.append($1, @2), @3))) YYERROR; @$.begin = @1.begin; @$.end = @4.end; }
;

limit:
// empty since it is optional
| IQL_SEP IQL_LIMIT IQL_SEP term { if (!ctx.setLimit($4)) YYERROR; @$.begin = @1.begin; @$.end = @4.end; };
;

order:
// empty since it is optional
| IQL_SEP IQL_ORDER IQL_SEP order_list
;

order_list:
  order_term
| order_list list_sep order_term
;

order_term:
  term                  { if (!ctx.addOrder($1)) YYERROR; @$.begin = @1.begin; @$.end = @1.end; }
| term IQL_SEP IQL_ASC  { if (!ctx.addOrder($1, true)) YYERROR; @$.begin = @1.begin; @$.end = @3.end; }
| term IQL_SEP IQL_DESC { if (!ctx.addOrder($1, false)) YYERROR; @$.begin = @1.begin; @$.end = @3.end; }
;

%%

// -----------------------------------------------------------------------------
// --SECTION--                                                          Epilogue
// -----------------------------------------------------------------------------

// define after 'yydebug' is declared by bison
namespace iresearch {
  namespace iql {
    void debug(parser& parser, bool bEnable) {
      #if YYDEBUG
        // suppress MSVC C4505 warnings ('#pragma warning(disable:4505)' is ignored by MSVS)
        // defined here only for the sake of being inside some function
        #if defined(_MSC_VER)
          if (false) yypstack(nullptr, 0);
          if (false) yypdumpstack(nullptr);
        #endif

        yydebug = bEnable ? 1 : 0;
        parser.set_debug_level(bEnable ? 1 : 0);
      #endif
    }
  }
}

// end of suppress of warnings due to Bison generated code (pop section, push at start of file)
#if defined(_MSC_VER)
  #pragma warning(default : 4706)
  #pragma warning(default : 4611)
  #pragma warning(default : 4505)
  #pragma warning(default : 4244)
  #pragma warning(default : 4127)
  #pragma warning(default : 4100)
#endif
