/*
    Original version from plan by Thomas Driemeyer <thomas@bitrot.de>

    Adapted for use in KOrganizer by
        Preston Brown <pbrown@kde.org> and
        Reinhold Kainhofer <reinhold@kainhofer.com>

    Portions contributed by
        Peter Littlefield <plittle@sofkin.ca>

    Major rewrite using Bison C++ skeleton:
        Copyright 2010 John Layt <john@layt.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

%{ /*** C/C++ Declarations ***/

#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <string>

#include <QString>

#include <KCalendarSystem>

%}

/*** yacc/bison Declarations ***/

/* Require bison 2.3 or later */
%require "2.3"

/* add debug output code to generated parser. disable this for release
 * versions. */
%debug

/* write out a header file containing the token defines */
%defines

/* use newer C++ skeleton file */
%skeleton "lalr1.cc"

/* namespace to enclose parser in */
%name-prefix="KHolidays"

/* set the parser's class identifier */
%define "parser_class_name" "HolidayParserPlan"

/* keep track of the current position within the input */
%locations
%initial-action
{
    // initialize the initial location object
    @$.begin.filename = new std::string( driver.filePath().toLocal8Bit().data() );
    @$.end.filename = @$.begin.filename;
};

/* The driver is passed by reference to the parser and to the scanner. This
 * provides a simple but effective pure interface, not relying on global
 * variables. */
%parse-param { class HolidayParserDriverPlan& driver }

/* verbose error messages */
%error-verbose

/*** Grammar Tokens ***/

%union { int  ival; char *sval; }

%type   <ival> stringcolor daycolor offset conditionaloffset length expr pexpr number month reldate wdaycondition monthnumber
%token  END 0
%token  <ival> NUMBER MONTH WDAY COLOR
%token  <sval> STRING CALENDAR
%token  INOP PLUS MINUS SMALL YEAR LEAPYEAR SHIFT IF
%token  LENGTH EASTER EQ NE LE GE LT GT PASCHA COUNTRY LANGUAGE NAME DESCRIPTION

%left OR
%left AND
%right EQ NE LE GE LT GT
%left '-' '+'
%left '*' '/' '%'
%nonassoc '!'
%nonassoc UMINUS
%left '?' ':'

%start planfile

/*** End Grammar Tokens ***/

%{

#include "holidayparserdriverplan_p.h"
#include "holidayscannerplan_p.h"

/* this "connects" the bison parser in the driver to the flex scanner class
 * object. it defines the yylex() function call to pull the next token from the
 * current lexer object of the driver context. */
#undef yylex
#define yylex driver.m_scanner->lex

%}

%% /*** Grammar Rules ***/

planfile : metadata list
;

metadata : countrycode languagecode name description
;

countrycode :                { driver.setFileCountryCode( QString() ); }
            | COUNTRY STRING { driver.setFileCountryCode( QString::fromUtf8( $2 ) ); }
;

languagecode :                 { driver.setFileLanguageCode( QString() ); }
             | LANGUAGE STRING { driver.setFileLanguageCode( QString::fromUtf8( $2 ) ); }
;

name :             { driver.setFileName( QString() ); }
     | NAME STRING { driver.setFileName( QString::fromUtf8( $2 ) ); }
;

description :                    { driver.setFileDescription( QString() ); }
            | DESCRIPTION STRING { driver.setFileDescription( QString::fromUtf8( $2 ) ); }
;

list :
     | list small stringcolor eventname daycolor calendar eventrule
;

small :
      | SMALL
;

stringcolor :        { driver.setEventColorName( 0 ); }
            | COLOR  { driver.setEventColorName( $1 ); }
;

daycolor :        { driver.setEventColorDay( 0 ); }
         | COLOR  { driver.setEventColorDay( $1 ); }
;

eventname : STRING { driver.setEventName( QString::fromUtf8( $1 ) ); }
;

calendar :           { driver.setEventCalendarType( "gregorian" ); }
         | CALENDAR  { driver.setEventCalendarType( QString::fromUtf8( $1 ) ); }
;

eventrule : EASTER offset length                  { driver.setFromEaster( $2, $3 ); }
          | PASCHA offset length                  { driver.setFromPascha( $2, $3 ); }
          | date offset conditionaloffset length  { driver.setFromDate( $2, $3, $4 ); }
          | WDAY offset length                    { driver.setFromWeekdayInMonth( 1, $1, 1, $2, $3 ); }
          | pexpr WDAY offset length              { driver.setFromWeekdayInMonth( $1, $2, 1, $3, $4 ); }
          | pexpr WDAY INOP month offset length   { driver.setFromWeekdayInMonth( $1, $2, $4, $5, $6 ); }
          | WDAY pexpr date offset length         { driver.setFromRelativeWeekday( $2, $1, $4, $5 ); }
;

offset :             { $$ =   0; }
       | PLUS expr   { $$ =  $2; }
       | MINUS expr  { $$ = -$2; }
;

conditionaloffset :                                       { $$ = 0; }
                  | SHIFT wdaycondition IF wdaycondition  { $$ = ( $2 << 8 ) | $4; }
;

wdaycondition :                        { $$ = 0; }
              | WDAY                   { $$ = ( 1 << $1 ); }
              | WDAY OR wdaycondition  { $$ = ( 1 << $1 ) | $3; }
;

length :              { $$ =  1; }
       | LENGTH expr  { $$ = $2; }
;

date : pexpr '.' month            { driver.setEventDate( -99999, $3, $1 ); }
     | pexpr '.' month '.'        { driver.setEventDate( -99999, $3, $1 ); }
     | pexpr '.' month '.' expr   { driver.setEventDate(     $5, $3, $1 ); }
     | month '/' pexpr            { driver.setEventDate( -99999, $1, $3 ); }
     | month '/' pexpr '/' pexpr  { driver.setEventDate(     $5, $1, $3 ); }
     | monthnumber pexpr          { driver.setEventDate( -99999, $1, $2 ); }
     | monthnumber pexpr pexpr    { driver.setEventDate(     $3, $1, $2 ); }
     | pexpr monthnumber          { driver.setEventDate( -99999, $2, $1 ); }
     | pexpr monthnumber pexpr    { driver.setEventDate(     $3, $2, $1 ); }
     | pexpr '.' MONTH pexpr      { driver.setEventDate(     $4, $3, $1 ); }
     | pexpr                      { driver.setEventDate( $1 ); }
;

reldate : STRING                 { $$ = driver.julianDayFromEventName( $1 ); }
        | EASTER                 { $$ = driver.julianDayFromEaster(); }
        | PASCHA                 { $$ = driver.julianDayFromPascha(); }
        | pexpr '.' month        { $$ = driver.julianDayFromMonthDay( $3, $1 ); }
        | pexpr '.' month '.'    { $$ = driver.julianDayFromMonthDay( $3, $1 ); }
        | month '/' pexpr        { $$ = driver.julianDayFromMonthDay( $1, $3 ); }
        | pexpr MONTH            { $$ = driver.julianDayFromMonthDay( $2, $1 ); }
        | monthnumber pexpr      { $$ = driver.julianDayFromMonthDay( $1, $2 ); }
        | WDAY pexpr pexpr       { $$ = driver.julianDayFromRelativeWeekday( $2, $1, $3 ); }
        | pexpr WDAY INOP month  { $$ = driver.julianDayFromWeekdayInMonth( $1, $2, $4 ); }
;

month : monthnumber
      | pexpr        { $$ = driver.adjustedMonthNumber( $1 ); }
;

monthnumber : MONTH  { $$ = driver.adjustedMonthNumber( $1 ); }
;

expr : pexpr                   { $$ = $1; }
     | expr OR  expr           { $$ = $1 || $3; }
     | expr AND expr           { $$ = $1 && $3; }
     | expr EQ  expr           { $$ = $1 == $3; }
     | expr NE  expr           { $$ = $1 != $3; }
     | expr LE  expr           { $$ = $1 <= $3; }
     | expr GE  expr           { $$ = $1 >= $3; }
     | expr LT  expr           { $$ = $1 <  $3; }
     | expr GT  expr           { $$ = $1 >  $3; }
     | expr '+' expr           { $$ = $1 +  $3; }
     | expr '-' expr           { $$ = $1 -  $3; }
     | expr '*' expr           { $$ = $1 *  $3; }
     | expr '/' expr           { $$ = $3 ?  $1 / $3 : 0; }
     | expr '%' expr           { $$ = $3 ?  $1 % $3 : 0; }
     | expr '?' expr ':' expr  { $$ = $1 ?  $3 : $5; }
     | '!' expr                { $$ = !$2; }
     | '[' reldate ']'         { $$ = $2; }
;

pexpr : '(' expr ')'  { $$ = $2; }
      | number        { $$ = $1; }
;

number : NUMBER
       | '-' NUMBER %prec UMINUS  { $$ = -$2; }
       | YEAR                     { $$ = driver.parseYear(); }
       | LEAPYEAR pexpr           { $$ = driver.isLeapYear( $2 ); }
;

%%

/*** Private Yacc callbacks and helper functions ***/

void KHolidays::HolidayParserPlan::error( const KHolidays::HolidayParserPlan::location_type &errorLocation, const std::string &errorMessage )
{
    driver.error( errorLocation, errorMessage.c_str() );
}
