(*
This file is part of licht.
licht is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
licht 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with licht.  If not, see .
*)
%{
    open ScTypes
    open ScTypes.Result
    module S = Symbols
    let u = UTF8.from_utf8string
    let extractColumnNameFromNum (fixed, (str, value)) = (fixed, value)
    let build_call ident = function
    | []          -> Expr.call0 ident
    | [p1]        -> Expr.call1 ident p1
    | [p1;p2]     -> Expr.call2 ident p1 p2
    | [p1;p2;p3]  -> Expr.call3 ident p1 p2 p3
    | n           -> Expr.callN ident n
%}
%token  REAL
%token  NUM
%token  STR
%token  LETTERS
%token DOLLAR
%token LPAREN
%token RPAREN
%token PLUS
%token TIMES
%token DIVIDE
%token MINUS
%token EQ NEQ
%token LT LE GT GE
%token EOF
%token SEMICOLON
%token COLON
%token POW
%nonassoc EQ NEQ LT LE GT GE
%left PLUS MINUS
%left TIMES DIVIDE
%left POW
%start value
%start content
%%
value:
  | EQ expr EOF             {$2}
content:
  | basic EOF               {$1}
basic:
  | PLUS num                {Ok (Type.number $2)}
  | MINUS num               {Ok (Type.number (DataType.Num.neg $2))}
  | num                     {Ok (Type.number $1)}
  | NUM DIVIDE NUM DIVIDE NUM {Ok (
      Type.date (
        DataType.Date.get_julian_day
          (int_of_string $1)
          (int_of_string $3)
          (int_of_string $5)
        ))}
  | NUM COLON NUM COLON NUM {Ok (
      Type.date (
        let nhour = DataType.Num.div (DataType.Num.of_int @@ int_of_string $1) (DataType.Num.of_int 24)
        and nmin  = DataType.Num.div (DataType.Num.of_int @@ int_of_string $3) (DataType.Num.of_int 1440)
        and nsec  = DataType.Num.div (DataType.Num.of_int @@ int_of_string $5) (DataType.Num.of_int 86400)
        in DataType.Num.add (DataType.Num.add nhour nmin) nsec
      )
  )}
expr:
  | num                     {Expr.value (Type.number ($1))}
  | MINUS expr              {Expr.call1 S.sub $2}
  | PLUS expr               {Expr.call1 S.add $2}
  | LETTERS ident LPAREN  separated_list(SEMICOLON, expr) RPAREN { build_call (u($1 ^ $2)) $4 }
  | cell                    {Expr.ref (Refs.cell $1)}
  | cell COLON cell         {Expr.ref (Refs.range $1 $3)}
  | LPAREN expr RPAREN      {Expr.expression $2}
  | STR                     {Expr.value (Type.string (u $1))}
  (* Mathematical operators *)
  | expr MINUS expr         {Expr.call2 S.sub $1 $3}
  | expr DIVIDE expr        {Expr.call2 S.div $1 $3}
  | expr TIMES expr         {Expr.call2 S.mul $1 $3}
  | expr PLUS  expr         {Expr.call2 S.add $1 $3}
  | expr POW  expr          {Expr.call2 S.pow $1 $3}
  (* Comparaison *)
  | expr EQ expr            {Expr.call2 S.eq  $1 $3}
  | expr NEQ expr           {Expr.call2 S.neq $1 $3}
  | expr LT expr            {Expr.call2 S.lt  $1 $3}
  | expr GT expr            {Expr.call2 S.gt  $1 $3}
  | expr LE expr            {Expr.call2 S.le  $1 $3}
  | expr GE expr            {Expr.call2 S.ge  $1 $3}
%inline cell:
  | LETTERS NUM               { Cell.from_string (false, $1) (false, int_of_string $2) }
  | DOLLAR LETTERS NUM        { Cell.from_string (true,  $2) (false, int_of_string $3) }
  | LETTERS DOLLAR NUM        { Cell.from_string (false, $1) (true,  int_of_string $3) }
  | DOLLAR LETTERS DOLLAR NUM { Cell.from_string (true,  $2) (true,  int_of_string $4) }
num:
  | REAL                    {DataType.Num.of_float @@ float_of_string $1}
  | NUM                     {DataType.Num.of_int   @@ int_of_string $1}
ident:
  | text*                   { String.concat "" $1 }
text:
  | LETTERS                 { $1 }
  | NUM                     { $1 }