/* Copyright (C) 2006 Sergei Golubchik

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2
   as published by the Free Software Foundation

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */

%{

#include <strings.h>
#include <stdlib.h>
#include "expr.h"

int yylex (void);
void yyerror (char const *);

char *yy_err, *yy_str;
Expr *yy_expr;
int yy_nsum, yy_ndiff;

static double e_num(Expr *me, Econtext *ctx) {
  return me->val.num;
}

static double e_var(Expr *me, Econtext *ctx) {
  char c, *s, *e; double v;
  if (ctx->pmatch[me->val.var].rm_so < 0) return 0;
  s=ctx->str+ctx->pmatch[me->val.var].rm_so;
  e=ctx->str+ctx->pmatch[me->val.var].rm_eo;
  c=*e; *e=0; v=strtod(s, 0); *e=c;
  return v;
}

static double e_if(Expr *me, Econtext *ctx) {
  return me->val.arg[0]->eval(me->val.arg[0], ctx) ?
         me->val.arg[1]->eval(me->val.arg[1], ctx) :
         me->val.arg[2]->eval(me->val.arg[2], ctx) ;
}

static double e_lt(Expr *me, Econtext *ctx) {
  return me->val.arg[0]->eval(me->val.arg[0], ctx) < me->val.arg[1]->eval(me->val.arg[1], ctx);
}

static double e_gt(Expr *me, Econtext *ctx) {
  return me->val.arg[0]->eval(me->val.arg[0], ctx) > me->val.arg[1]->eval(me->val.arg[1], ctx);
}

static double e_add(Expr *me, Econtext *ctx) {
  return me->val.arg[0]->eval(me->val.arg[0], ctx) + me->val.arg[1]->eval(me->val.arg[1], ctx);
}

static double e_sub(Expr *me, Econtext *ctx) {
  return me->val.arg[0]->eval(me->val.arg[0], ctx) - me->val.arg[1]->eval(me->val.arg[1], ctx);
}

static double e_mul(Expr *me, Econtext *ctx) {
  return me->val.arg[0]->eval(me->val.arg[0], ctx) * me->val.arg[1]->eval(me->val.arg[1], ctx);
}

static double e_div(Expr *me, Econtext *ctx) {
  return me->val.arg[0]->eval(me->val.arg[0], ctx) / me->val.arg[1]->eval(me->val.arg[1], ctx);
}

static double e_sum(Expr *me, Econtext *ctx) {
  int slot=(long)me->val.arg[1];
  return ctx->sum_acc[slot]+=me->val.arg[0]->eval(me->val.arg[0], ctx);
}

static double e_diff(Expr *me, Econtext *ctx) {
  double e, v;
  int slot=(long)me->val.arg[1];
  ctx->diff_new[slot]=e=me->val.arg[0]->eval(me->val.arg[0], ctx);
  v=e-ctx->diff_old[slot];
  return v;
}

static Expr *new_num(double val) {
  Expr *e=malloc(sizeof(Expr));
  e->eval=e_num;
  e->val.num=val;
  return e;
}

static Expr *new_var(int var) {
  Expr *e=malloc(sizeof(Expr));
  e->eval=e_var;
  e->val.var=var;
  return e;
}

static Expr *new_op(Expr *e1, Expr *e2, double (*eval)(Expr *, Econtext *))
{
  Expr *e=calloc(1, sizeof(Expr));
  e->eval=eval;
  e->val.arg[0]=e1;
  e->val.arg[1]=e2;
  return e;
}

%}

%error-verbose

%union {
  int      var;
  double   val;
  Expr   *expr;
}

/* Bison declarations.  */
%token<val> NUM
%token<var> VAR
%token SUM DIFF
%type <expr> expr

%left '?' ':'
%left '<' '>'
%left '-' '+'
%left '*' '/'
%left NEG

%% /* The grammar follows.  */

input: expr { yy_expr=$1; }

expr:     NUM                 { $$=new_num($1); }
        | VAR                 {
                                if ($1 >= MAX_VAR) {
                                  yyerror("too large register number");
                                  YYERROR;
                                }
                                $$=new_var($1);
                              }
        | SUM '(' expr ')'    { $$=new_op($3, (Expr *)(long)yy_nsum++, e_sum);}
        | DIFF '(' expr ')'   { $$=new_op($3, (Expr *)(long)yy_ndiff++, e_diff);}
        | expr '?' expr ':' expr { $$=new_op($1, $3, e_if); $$->val.arg[2]=$5; }
        | expr '<' expr       { $$=new_op($1, $3, e_lt); }
        | expr '>' expr       { $$=new_op($1, $3, e_gt); }
        | expr '+' expr       { $$=new_op($1, $3, e_add); }
        | expr '-' expr       { $$=new_op($1, $3, e_sub); }
        | expr '*' expr       { $$=new_op($1, $3, e_mul); }
        | expr '/' expr       { $$=new_op($1, $3, e_div); }
        | '-' expr  %prec NEG { $$=new_op(new_num(0) ,$2, e_sub); }
        | '+' expr  %prec NEG { $$=$2; }
        | '(' expr ')'        { $$=$2; }
        ;
%%

#include <ctype.h>
#include <string.h>

#define prefix(S,CONST)    !strncasecmp((S),CONST,sizeof(CONST)-1)

int yylex(void)
{
  while (isspace(*yy_str)) yy_str++;
  if (isdigit(*yy_str)) {
    yylval.val=strtod(yy_str, &yy_str);
    return NUM;
  }
  if (*yy_str == '\\') {
    if (!isdigit(*++yy_str)) return '\\';
    yylval.var=strtol(yy_str, &yy_str, 10);
    return VAR;
  }
  if (prefix(yy_str, "sum")) {
    yy_str +=3;
    return SUM;
  }
  if (prefix(yy_str, "diff")) {
    yy_str +=4;
    return DIFF;
  }
  return *yy_str++;
}

/* Called by yyparse on error.  */
void
yyerror (char const *s)
{
  strcpy(yy_err, s);
}