
// -*- mode: c++; c-basic-offset:4 -*-

// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.

// Copyright (c) 2014 OPeNDAP, Inc.
// Author: James Gallagher <jgallagher@opendap.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 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
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.

%skeleton "lalr1.cc" /* -*- C++ -*- */
%require "2.5"
%defines

// The d4_function_parser.tab.cc and .hh files define and declare this class
%define parser_class_name {D4FunctionParser}

// D4FunctionParser is in this namespace
%define api.namespace {libdap}

%define parse.trace
%define parse.error verbose
%define parse.assert

// Could not get this to work with a C++ scanner built by flex. 8/10/13 jhrg
// %define api.token.constructor
%define api.value.type variant

// Because the code uses the C++ mode of flex, we don't use this. 8/8/13 jhrg
// %define api.prefix { d4_function_ }

%code requires {

#include "D4FunctionEvaluator.h"
#include "D4RValue.h"
#include "dods-datatypes.h"

namespace libdap {
    class D4FunctionScanner;
}

}

// Pass both the scanner and parser objects to both the automatically generated
// parser and scanner.
%lex-param   { D4FunctionScanner  &scanner  }
%parse-param { D4FunctionScanner  &scanner  }

%lex-param   { D4FunctionEvaluator  &evaluator  }
%parse-param { D4FunctionEvaluator  &evaluator  }

%locations
%initial-action
{
    // Initialize the initial location. This is printed when the parser builds
    // its own error messages - when the parse fails as opposed to when the 
    // function(s) name(s) a missing variable, ...

    @$.initialize (evaluator.expression());
};

%code {
    #include "BaseType.h"
    #include "DMR.h"
    #include "D4RValue.h"
    #include "ServerFunctionsList.h"
   
    #include "parser-util.h"

    /* include for all driver functions */
    #include "D4FunctionEvaluator.h"

    using namespace libdap ;
    
    /* this is silly, but I can't figure out a way around it */
    static int yylex(libdap::D4FunctionParser::semantic_type *yylval,
                     libdap::location *loc,
                     libdap::D4FunctionScanner  &scanner,
                     libdap::D4FunctionEvaluator   &evaluator);
}

%type <D4RValueList*> functions "functions"
%type <D4RValueList*> args "arguments"

%type <D4RValue*> arg "argument"
%type <D4RValue*> function "function"

%type <D4Function> fname "function name"
%type <D4RValue*> variable_or_constant "variable or constant"
%type <D4RValue*> array_constant "array constant"

%type <std::vector<dods_byte>*> fast_byte_arg_list "fast byte arg list"
%type <std::vector<dods_int8>*> fast_int8_arg_list "fast int8 arg list"
%type <std::vector<dods_uint16>*> fast_uint16_arg_list "fast uint16 arg list"
%type <std::vector<dods_int16>*> fast_int16_arg_list "fast int16 arg list"
%type <std::vector<dods_uint32>*> fast_uint32_arg_list "fast uint32 arg list"
%type <std::vector<dods_int32>*> fast_int32_arg_list "fast int32 arg list"
%type <std::vector<dods_uint64>*> fast_uint64_arg_list "fast uint64 arg list"
%type <std::vector<dods_int64>*> fast_int64_arg_list "fast int64 arg list"
%type <std::vector<dods_float32>*> fast_float32_arg_list "fast float32 arg list"
%type <std::vector<dods_float64>*> fast_float64_arg_list "fast float64 arg list"

%type <std::string> id path group name

// The strings used in the token definitions are used for error messages
%token <std::string> WORD "word"
%token <std::string> STRING "string"

%token 
    END  0  "end of file"
    
    SEMICOLON ";"
    COLON ":"

    LPAREN "("
    RPAREN ")"
    
    COMMA ","

    GROUP_SEP "/"
    PATH_SEP "."
    
    DOLLAR_BYTE "$Byte"
    DOLLAR_UINT8 "$UInt8"
    DOLLAR_INT8 "$Int8"
    DOLLAR_UINT16 "$UInt16"
    DOLLAR_INT16 "$Int16"
    DOLLAR_UINT32 "$UInt32"
    DOLLAR_INT32 "$Int32"
    DOLLAR_UINT64 "$UInt64"
    DOLLAR_INT64 "$Int64"
    DOLLAR_FLOAT32 "$Float32"
    DOLLAR_FLOAT64 "$Float64"
;

%%

%start program;

program : functions 
{ 
    evaluator.set_result($1); 
}
;

functions : function 
{
    $$ = new D4RValueList($1); 
}
| functions ";" function 
{ 
    $1->add_rvalue($3);
    $$ = $1; 
}
;
                    
function : fname "(" args ")" 
{ 
    $$ = new D4RValue($1, $3); // Build a D4RValue from a D4Function pointer and a D4RValueList 
} 
;

fname: WORD 
{ 
    D4Function f;
    if (!evaluator.sf_list()->find_function($1, &f)) {
        // ...cloud use @1.{first,last}_column in these error messages.
        throw Error(malformed_expr, "'" + $1 + "' is not a registered DAP4 server function.");
    }

    $$ = f;
}        
;

args: arg
{ 
    $$ = new D4RValueList($1); // build a D4RValueList from the D4RValue
} 
| args "," arg 
{ 
    $1->add_rvalue($3);
    $$ = $1; // Append the D4RValue ($3) to the D4RValueList ($1), then return
} 
;

arg: function
{
    $$ = $1;
}
| variable_or_constant
{
    $$ = $1;
}
| array_constant 
{
    $$ = $1;
}
;

variable_or_constant : id
{
    D4RValue *rvalue = evaluator.build_rvalue($1);
    if (!rvalue) {
        throw Error(malformed_expr, "'" + $1 + "' is not a variable, number or string.");
    }
    
    $$ = rvalue;
}
;
  
array_constant : 
DOLLAR_BYTE "(" arg_length_hint ":" fast_byte_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
|
DOLLAR_UINT8 "(" arg_length_hint ":" fast_byte_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
|
DOLLAR_INT8 "(" arg_length_hint ":" fast_int8_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
|
DOLLAR_UINT16 "(" arg_length_hint ":" fast_uint16_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
|
DOLLAR_INT16 "(" arg_length_hint ":" fast_int16_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
|
DOLLAR_UINT32 "(" arg_length_hint ":" fast_uint32_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
|
DOLLAR_INT32 "(" arg_length_hint ":" fast_int32_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
|
DOLLAR_UINT64 "(" arg_length_hint ":" fast_uint64_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
|
DOLLAR_INT64 "(" arg_length_hint ":" fast_int64_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
|
DOLLAR_FLOAT32 "(" arg_length_hint ":" fast_float32_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
|
DOLLAR_FLOAT64 "(" arg_length_hint ":" fast_float64_arg_list ")"
{
    $$ = new D4RValue(*($5));
    delete $5;
}
;

/* Here the arg length hint is stored in the eval class so it can be used by the 
   method that allocates the vector. The value is passed to vector::reserve().
   This rule is run for it's side-effect only. This is also used to track the 
   current arg number so that the hint can be used. */
   
arg_length_hint : WORD
{
    evaluator.set_arg_length_hint(get_uint64($1.c_str()));
}
;

fast_byte_arg_list: WORD
{
    $$ = evaluator.init_arg_list(dods_byte(strtol($1.c_str(), 0, 0)));
}
| fast_byte_arg_list "," WORD
{
    $1->push_back(strtol($3.c_str(), 0, 0));
    $$ = $1;
}
;

fast_int8_arg_list: WORD
{
    $$ = evaluator.init_arg_list(dods_int8(strtol($1.c_str(), 0, 0)));
}
| fast_int8_arg_list "," WORD
{
    $1->push_back(strtol($3.c_str(), 0, 0));
    $$ = $1;
}
;

fast_uint16_arg_list: WORD
{
    $$ = evaluator.init_arg_list(dods_uint16(strtol($1.c_str(), 0, 0)));
}
| fast_uint16_arg_list "," WORD
{
    $1->push_back(strtol($3.c_str(), 0, 0));
    $$ = $1;
}
;

fast_int16_arg_list: WORD
{
    $$ = evaluator.init_arg_list(dods_int16(strtol($1.c_str(), 0, 0)));
}
| fast_int16_arg_list "," WORD
{
    $1->push_back(strtol($3.c_str(), 0, 0));
    $$ = $1;
}
;

fast_uint32_arg_list: WORD
{
    $$ = evaluator.init_arg_list(dods_uint32(strtoul($1.c_str(), 0, 0)));
}
| fast_uint32_arg_list "," WORD
{
    $1->push_back(strtoul($3.c_str(), 0, 0));
    $$ = $1;
}
;
fast_int32_arg_list: WORD
{
    $$ = evaluator.init_arg_list(dods_int32(strtol($1.c_str(), 0, 0)));
}
| fast_int32_arg_list "," WORD
{
    $1->push_back(strtol($3.c_str(), 0, 0));
    $$ = $1;
}
;

fast_uint64_arg_list: WORD
{
    $$ = evaluator.init_arg_list(dods_uint64(strtoull($1.c_str(), 0, 0)));
}
| fast_uint64_arg_list "," WORD
{
    $1->push_back(strtoull($3.c_str(), 0, 0));
    $$ = $1;
}
;

fast_int64_arg_list: WORD
{
    $$ = evaluator.init_arg_list(dods_int64(strtoll($1.c_str(), 0, 0)));
}
| fast_int64_arg_list "," WORD
{
    $1->push_back(strtoll($3.c_str(), 0, 0));
    $$ = $1;
}
;

// I'm using path for the $Float constants so that tokens with dots will 
// parse. I could add a FLOAT token, and I might have to do that later on
// when filters are added to the CE, but this will work for now.
fast_float32_arg_list: path
{
    $$ = evaluator.init_arg_list(dods_float32(strtof($1.c_str(), 0)));
}
| fast_float32_arg_list "," path
{
    $1->push_back(strtof($3.c_str(), 0));
    $$ = $1;
}
;

fast_float64_arg_list: path
{
    $$ = evaluator.init_arg_list(dods_float64(strtod($1.c_str(), 0)));
}
| fast_float64_arg_list "," path
{
    $1->push_back(strtod($3.c_str(), 0));
    $$ = $1;
}
;

id : path
{
    $$ = $1;
}
| "/" path
{
    $$.append("/");
    $$.append($2);
}
| group "/" path
{
    $1.append("/");
    $1.append($3);
    $$ = $1;
}
;

group : "/" name
{
    $$.append("/");
    $$.append($2);
}
| group "/" name
{
    $1.append(".");
    $1.append($3);
    $$ = $1;
}
;

path : name 
{
    $$ = $1;
}
| path "." name
{
    $1.append(".");
    $1.append($3);
    $$ = $1;
}
;

// Because some formats/datasets allow 'any' name for a variable, it's possible
// that a variable name will be a number, etc. The grammar also allows STRING
// to support "name"."name with spaces and dots (.)".x
name : WORD 
{
    $$=$1;
}
| STRING 
{
    $$=$1;
}
;

%%

// Forward the error to the driver for handling. The location parameter
// provides the line number and character position of the error.
void
libdap::D4FunctionParser::error(const location_type &l, const std::string &m)
{
    evaluator.error(l, m);
}

/* include for access to scanner.yylex */
#include "D4FunctionScanner.h"

static int yylex(libdap::D4FunctionParser::semantic_type *yylval,
                 libdap::location *loc,
                 libdap::D4FunctionScanner &scanner,
                 libdap::D4FunctionEvaluator &evaluator)
{
    if (evaluator.trace_scanning())
        scanner.set_debug(true);
    
    return( scanner.yylex(yylval, loc) );
}
