%pure-parser
%lex-param {struct tmplpro_state* state}
%lex-param {struct expr_parser* exprobj}
%parse-param {struct tmplpro_state* state}
%parse-param {struct expr_parser* exprobj}
%parse-param {PSTRING* expr_retval_ptr}
%{
#include <math.h>  /* For math functions, cos(), sin(), etc.  */
#include <stdio.h> /* for printf */
#include <stdlib.h> /* for malloc */
#include <ctype.h> /* for yylex alnum */
#include "calc.h"  /* Contains definition of `symrec'.  */
#include "tmpllog.h"
#include "pabstract.h"
#include "prostate.h"
#include "provalue.h"
#include "pparam.h"
#include "pmiscdef.h"
/* for expr-specific only */
#include "exprtool.h"
#include "exprpstr.h"
#include "parse_expr.h"
  /* Remember unsigned char assert on win32
Debug Assertion Failed! f:\dd\vctools\crt_bld\self_x86\crt\src \isctype.c Expression:(unsigned)(c + 1) <= 256 
   */
  %}
%union {
  struct exprval numval;   /* For returning numbers.  */
  const symrec_const  *tptr;   /* For returning symbol-table pointers.  */
  struct user_func_call extfunc;  /* for user-defined function name */
  PSTRING uservar;
}
%{
  /* the second section is required as we use YYSTYPE here */
  static void yyerror (struct tmplpro_state* state, struct expr_parser* exprobj, PSTRING* expr_retval_ptr, char const *);
  static int yylex (YYSTYPE *lvalp, struct tmplpro_state* state, struct expr_parser* exprobj);
%}
%start line
%token <numval>  NUM        /*  poly type.  */
%token <extfunc> EXTFUNC    /* user-defined function */
%token <tptr> BUILTIN_VAR	    /* built-in Variable  */
%token <tptr> BUILTIN_FNC_DD /* built-in D Function (D).  */
%token <tptr> BUILTIN_FNC_DDD /* built-in D Function (D,D).  */
%token <tptr> BUILTIN_FNC_EE /* built-in E Function (E).  */
%token <uservar> VAR    /* user-supplied variable.  */
%type  <numval>  numEXP
%type  <extfunc> arglist

/*%right '='*/
%left OR
%left AND
%nonassoc strGT strGE strLT strLE strEQ strNE strCMP
%nonassoc numGT numGE numLT numLE numEQ numNE '<' '>'
%nonassoc reLIKE reNOTLIKE
%left '-' '+'
%left '*' '/' '%'
%left  '!' NOT NEG /* negation--unary minus */
%right '^'    /* exponentiation */
%% /* The grammar follows.  */

line: numEXP		
		 { 
		   expr_to_str1(state, &$1);
		   *expr_retval_ptr=$1.val.strval;
		 }
;
/* | error { yyerrok;                  } */

numEXP: NUM			{ $$ = $1;			}
| BUILTIN_VAR			{ $$.type=EXPR_TYPE_DBL; $$.val.dblval = $1->var; }
/*| BUILTIN_VAR '=' numEXP 		{ $$ = $3; $1->value.var = $3;	} */
| VAR		{
		  PSTRING varvalue=_get_variable_value(state->param, $1);
		  if (varvalue.begin==NULL) {
		    int loglevel = state->param->warn_unused ? TMPL_LOG_ERROR : TMPL_LOG_INFO;
		    log_expr(exprobj,loglevel, "non-initialized variable %.*s\n",(int)($1.endnext-$1.begin),$1.begin);
		  }
		  $$.type=EXPR_TYPE_PSTR;
		  $$.val.strval=varvalue;
  }
| arglist ')'
                 {
		   $$ = call_expr_userfunc(exprobj, state->param, $1);
		 }
| EXTFUNC '(' ')'
                 {
		   $1.arglist=state->param->InitExprArglistFuncPtr(state->param->ext_calluserfunc_state);
		   $$ = call_expr_userfunc(exprobj, state->param, $1);
		 }
| BUILTIN_FNC_EE '(' ')'
                 {
		   struct exprval e = NEW_EXPRVAL(EXPR_TYPE_PSTR);
		   e.val.strval.begin = NULL;
		   e.val.strval.endnext = NULL;
		   $$ = (*((func_t_ee)$1->fnctptr))(exprobj, e);
		 }
| BUILTIN_FNC_DD '(' numEXP ')'	
                 {
		   $$.type=EXPR_TYPE_DBL;
		   expr_to_dbl1(exprobj, &$3);
		   $$.val.dblval = (*((func_t_dd)$1->fnctptr))($3.val.dblval); 
		 }
| BUILTIN_FNC_DDD '(' numEXP ',' numEXP ')'
                 {
		   $$.type=EXPR_TYPE_DBL;
		   expr_to_dbl(exprobj, &$3, &$5);
		   $$.val.dblval = (*((func_t_ddd)$1->fnctptr))($3.val.dblval,$5.val.dblval);
		 }
| BUILTIN_FNC_EE '(' numEXP ')'
                 {
		   $$ = (*((func_t_ee)$1->fnctptr))(exprobj,$3);
		 }
| numEXP '+' numEXP		{ DO_MATHOP(exprobj, $$,+,$1,$3);	}
| numEXP '-' numEXP		{ DO_MATHOP(exprobj, $$,-,$1,$3);	}
| numEXP '*' numEXP		{ DO_MATHOP(exprobj, $$,*,$1,$3);	}
| numEXP '%' numEXP
		 { 
		   $$.type=EXPR_TYPE_INT;
		   expr_to_int(exprobj, &$1,&$3);
		   $$.val.intval = $1.val.intval % $3.val.intval;
		 }
/* old division; now always return double (due to compains 1/3==0)
| numEXP '/' numEXP
                 {
		   switch ($$.type=expr_to_int_or_dbl(&$1,&$3)) {
		   case EXPR_TYPE_INT: 
                   if ($3.val.intval)
                     $$.val.intval = $1.val.intval / $3.val.intval;
                   else
                     {
                       $$.val.intval = 0;
		       log_expr(exprobj, TMPL_LOG_ERROR, "%s\n", "division by zero");
                     }
		   ;break;
		   case EXPR_TYPE_DBL: 
                   if ($3.val.dblval)
                     $$.val.dblval = $1.val.dblval / $3.val.dblval;
                   else
                     {
                       $$.val.dblval = 0;
		       log_expr(exprobj, TMPL_LOG_ERROR, "%s\n", "division by zero");
                     }
		   }
		   ;break;
		 }
*/
| numEXP '/' numEXP
                 {
		   $$.type=EXPR_TYPE_DBL;
		   expr_to_dbl(exprobj, &$1,&$3);
                   if ($3.val.dblval)
                     $$.val.dblval = $1.val.dblval / $3.val.dblval;
                   else
                     {
                       $$.val.dblval = 0;
		       log_expr(exprobj, TMPL_LOG_ERROR, "%s\n", "division by zero");
                     }
		 }
| '-' numEXP  %prec NEG
		 { 
		   switch ($$.type=$2.type) {
		   case EXPR_TYPE_INT: 
		     $$.val.intval = -$2.val.intval;
		   ;break;
		   case EXPR_TYPE_DBL: 
		     $$.val.dblval = -$2.val.dblval;
		   ;break;
		   }
		 }
| numEXP '^' numEXP 		
                 { 
		   $$.type=EXPR_TYPE_DBL;
		   expr_to_dbl(exprobj, &$1,&$3);
		   $$.val.dblval = pow ($1.val.dblval, $3.val.dblval);
                 }
| numEXP OR numEXP
 		 {
		   if (exprobj->is_tt_like_logical) {
		     $$=$1;
		     switch (expr_to_int_or_dbl_logop1(exprobj, &$$)) {
		     case EXPR_TYPE_INT: $$= ($1.val.intval ? $1 : $3); break;
		     case EXPR_TYPE_DBL: $$= ($1.val.dblval ? $1 : $3); break;
		     }
		   } else {
		     DO_LOGOP(exprobj, $$,||,$1,$3);
		   }
		 }
| numEXP AND numEXP
 		 {
		   if (exprobj->is_tt_like_logical) {
		     $$=$1;
		     switch (expr_to_int_or_dbl_logop1(exprobj, &$$)) {
		     case EXPR_TYPE_INT: $$= ($1.val.intval ? $3 : $1); break;
		     case EXPR_TYPE_DBL: $$= ($1.val.dblval ? $3 : $1); break;
		     }
		   } else {
		     DO_LOGOP(exprobj, $$,&&,$1,$3);
		   }
		 }
| numEXP numGE numEXP 		{ DO_CMPOP(exprobj, $$,>=,$1,$3);	}
| numEXP numLE numEXP 		{ DO_CMPOP(exprobj, $$,<=,$1,$3);	}
| numEXP numNE numEXP 		{ DO_CMPOP(exprobj, $$,!=,$1,$3);	}
| numEXP numEQ numEXP 		{ DO_CMPOP(exprobj, $$,==,$1,$3);	}
| numEXP '>' numEXP %prec numGT	{ DO_CMPOP(exprobj, $$,>,$1,$3);	}
| numEXP '<' numEXP %prec numLT	{ DO_CMPOP(exprobj, $$,<,$1,$3);	}
| '!' numEXP  %prec NOT		{ DO_LOGOP1(exprobj, $$,!,$2);		}
| NOT numEXP			{ DO_LOGOP1(exprobj, $$,!,$2);		}
| '(' numEXP ')'		{ $$ = $2;			}
| numEXP strCMP numEXP 		{ 
  expr_to_str(state, &$1,&$3); 
  $$.type=EXPR_TYPE_INT; $$.val.intval = pstring_ge ($1.val.strval,$3.val.strval)-pstring_le ($1.val.strval,$3.val.strval);
}
| numEXP strGE numEXP 		{ DO_TXTOP($$,pstring_ge,$1,$3,state);}
| numEXP strLE numEXP 		{ DO_TXTOP($$,pstring_le,$1,$3,state);}
| numEXP strNE numEXP 		{ DO_TXTOP($$,pstring_ne,$1,$3,state);}
| numEXP strEQ numEXP 		{ DO_TXTOP($$,pstring_eq,$1,$3,state);}
| numEXP strGT numEXP		{ DO_TXTOP($$,pstring_gt,$1,$3,state);}
| numEXP strLT numEXP		{ DO_TXTOP($$,pstring_lt,$1,$3,state);}
| numEXP reLIKE numEXP		{ DO_TXTOPLOG($$,re_like,$1,$3,exprobj);}
| numEXP reNOTLIKE numEXP	{ DO_TXTOPLOG($$,re_notlike,$1,$3,exprobj);}
;

arglist: EXTFUNC '(' numEXP 	 	{
  $1.arglist=state->param->InitExprArglistFuncPtr(state->param->expr_func_map);
  pusharg_expr_userfunc(exprobj,state->param,$1,$3);
  $$ = $1;
}
| arglist ',' numEXP	 { pusharg_expr_userfunc(exprobj,state->param,$1,$3); $$ = $1;	}
;

/* End of grammar.  */
%%

/* Called by yyparse on error.  */
static void
yyerror (struct tmplpro_state* state, struct expr_parser* exprobj, PSTRING* expr_retval_ptr, char const *s)
{
  log_expr(exprobj, TMPL_LOG_ERROR, "not a valid expression: %s\n", s);
}

#include "calc.inc"

static
const symrec_const
builtin_funcs_symrec[] =
  {
    /* built-in funcs */
    {SYMREC("sin"), BUILTIN_FNC_DD,	0,	  sin},
    {SYMREC("cos"), BUILTIN_FNC_DD,	0,	  cos},
    {SYMREC("atan"), BUILTIN_FNC_DD,	0,	 atan},
    {SYMREC("log"), BUILTIN_FNC_DD,	0,	  log},
    {SYMREC("exp"), BUILTIN_FNC_DD,	0,	  exp},
    {SYMREC("sqrt"), BUILTIN_FNC_DD,	0,	 sqrt},
    {SYMREC("atan2"), BUILTIN_FNC_DDD,	0,	atan2},
    {SYMREC("abs"), BUILTIN_FNC_EE,	0,	builtin_abs},
    {SYMREC("defined"), BUILTIN_FNC_EE,	0,	builtin_defined},
    {SYMREC("int"), BUILTIN_FNC_EE,	0,	builtin_int},
    {SYMREC("hex"), BUILTIN_FNC_EE,	0,	builtin_hex},
    {SYMREC("length"), BUILTIN_FNC_EE,	0,	builtin_length},
    {SYMREC("oct"), BUILTIN_FNC_EE,	0,	builtin_oct},
    {SYMREC("rand"), BUILTIN_FNC_EE,	0,	builtin_rand},
    {SYMREC("srand"), BUILTIN_FNC_EE,	0,	builtin_srand},
    {SYMREC("version"), BUILTIN_FNC_EE,	0,	builtin_version},
    /* end mark */
    {0, 0, 0}
  };

static
const symrec_const
builtin_ops_symrec[] =
  {
    /* built-in ops */
    {SYMREC("eq"),  strEQ,	0,	NULL},
    {SYMREC("ne"),  strNE,	0,	NULL},
    {SYMREC("gt"),  strGT,	0,	NULL},
    {SYMREC("ge"),  strGE,	0,	NULL},
    {SYMREC("lt"),  strLT,	0,	NULL},
    {SYMREC("le"),  strLE,	0,	NULL},
    {SYMREC("cmp"), strCMP,	0,	NULL},
    {SYMREC("or"),  OR,	0,	NULL},
    {SYMREC("and"),AND,	0,	NULL},
    {SYMREC("not"),NOT,	0,	NULL},
    /* end mark */
    {0, 0, 0}
  };

TMPLPRO_LOCAL
PSTRING 
parse_expr(PSTRING expression, struct tmplpro_state* state)
{
  PSTRING expr_retval;
  struct expr_parser exprobj;
  expr_retval.begin=expression.begin;
  expr_retval.endnext=expression.begin;
  exprobj.expr_curpos=expression.begin;
  exprobj.exprarea=expression;
  exprobj.state = state;
  exprobj.is_expect_quote_like=1;
  // TODO!!
  exprobj.is_tt_like_logical=0;
  yyparse (state, &exprobj, &expr_retval);
  if (NULL!=expr_retval.begin && NULL==expr_retval.endnext) log_expr(&exprobj, TMPL_LOG_ERROR, "parse_expr internal warning: %s\n", "endnext is null pointer");
  return expr_retval;
}

static
void 
log_expr(struct expr_parser* exprobj, int loglevel, const char* fmt, ...)
{
  va_list vl;
  va_start(vl, fmt);
  log_state(exprobj->state, loglevel, "in EXPR:at pos " MOD_TD " [" MOD_TD "]: ", 
	   TO_PTRDIFF_T((exprobj->expr_curpos)-(exprobj->state->top)),
	   TO_PTRDIFF_T((exprobj->expr_curpos)-(exprobj->exprarea).begin));
  tmpl_vlog(loglevel, fmt, vl);
  va_end(vl);
}

static
PSTRING 
fill_symbuf (struct expr_parser* exprobj, int is_accepted(unsigned char)) {
  /* skip first char, already tested */
  PSTRING retval = {exprobj->expr_curpos++};
  while (exprobj->expr_curpos < (exprobj->exprarea).endnext && is_accepted(*exprobj->expr_curpos)) exprobj->expr_curpos++;
  retval.endnext= exprobj->expr_curpos;
  return retval;
}

static 
int 
is_alnum_lex (unsigned char c)
{
  return (c == '_' || isalnum (c));
}

static 
int 
is_not_identifier_ext_end (unsigned char c)
{ 
  return (c != '}');
} 

#define TESTOP(c1,c2,z)  if (c1 == c) { char d=*++(exprobj->expr_curpos); if (c2 != d) return c; else (exprobj->expr_curpos)++; return z; }
#define TESTOP3(c1,c2,c3,num2,str3)  if (c1 == c) { char d=*++(exprobj->expr_curpos); if (c2 == d) {(exprobj->expr_curpos)++; return num2;} else if (c3 == d) {(exprobj->expr_curpos)++; exprobj->is_expect_quote_like=1; return str3;} else return c; }

static 
int
yylex (YYSTYPE *lvalp, struct tmplpro_state* state, struct expr_parser* exprobj)
{
  register unsigned char c = 0;
  int is_identifier_ext; 
  /* TODO: newline? */
  /* Ignore white space, get first nonwhite character.  */
  while ((exprobj->expr_curpos)<(exprobj->exprarea).endnext && ((c = *(exprobj->expr_curpos)) == ' ' || c == '\t')) (exprobj->expr_curpos)++;
  if ((exprobj->expr_curpos)>=(exprobj->exprarea).endnext) return 0;

  /* Char starts a quote => read a string */
  if ('\''==c || '"'==c || (exprobj->is_expect_quote_like && '/'==c) ) {
    PSTRING strvalue;
    unsigned char terminal_quote=c;
    int escape_flag = 0;
    c =* ++(exprobj->expr_curpos);
    strvalue.begin = exprobj->expr_curpos;
    strvalue.endnext = exprobj->expr_curpos;

    while ((exprobj->expr_curpos)<(exprobj->exprarea).endnext && c != terminal_quote) {
      /* any escaped char with \ , incl. quote */
      if ('\\' == c) {
	escape_flag = 1;
	exprobj->expr_curpos+=2;
	c =*(exprobj->expr_curpos);
      } else {
	c = * ++(exprobj->expr_curpos);
      }
    }

    strvalue.endnext = exprobj->expr_curpos;
    if ((exprobj->expr_curpos)<(exprobj->exprarea).endnext && ((c = *(exprobj->expr_curpos)) == terminal_quote)) (exprobj->expr_curpos)++;
    if (escape_flag) {
      (*lvalp).numval.type=EXPR_TYPE_UPSTR;
    } else {
      (*lvalp).numval.type=EXPR_TYPE_PSTR;
    }
    (*lvalp).numval.val.strval=strvalue;
    exprobj->is_expect_quote_like=0;
    return NUM;
  }
	
  exprobj->is_expect_quote_like=0;
  /* Char starts a number => parse the number.         */
  if (c == '.' || isdigit (c))
    {
      (*lvalp).numval=exp_read_number (exprobj, &(exprobj->expr_curpos), (exprobj->exprarea).endnext);
      return NUM;
    }

  /* 
   * Emiliano Bruni extension to Expr:
   * original HTML::Template allows almost arbitrary chars in parameter names,
   * but original HTML::Template::Expr (as to 0.04) allows only
   * var to be m![A-Za-z_][A-Za-z0-9_]*!.
   * with this extension, arbitrary chars can be used 
   * if bracketed in ${}, as, for example, EXPR="${foo.bar} eq 'a'".
   * first it was bracketing in {}, but it is changed 
   *
   * COMPATIBILITY WARNING.
   * Currently, this extension is not present in HTML::Template::Expr (as of 0.04).
   */
  /* Let's try to see if this is an identifier between two { } - Emiliano */
  is_identifier_ext = (int) (c == '{' || c == '$');

  /* Char starts an identifier => read the name.       */
  /* variables with _leading_underscore are allowed too */
  if (isalpha (c) || c=='_' || is_identifier_ext) {
    const symrec_const *s;
    PSTRING name;
    if (is_identifier_ext) {
      (exprobj->expr_curpos)++; /* jump over $ or { */
      if ('$' == c && '{' == *(exprobj->expr_curpos)) {
	(exprobj->expr_curpos)++; /* jump over { */
#ifndef ALLOW_OLD_BRACKETING_IN_EXPR
      } else {
      	log_expr(exprobj, TMPL_LOG_ERROR, "{} bracketing is deprecated. Use ${} bracketing.\n");
#endif
      }
      name=fill_symbuf(exprobj, is_not_identifier_ext_end);
      if ((exprobj->expr_curpos)<(exprobj->exprarea).endnext) (exprobj->expr_curpos)++; /* Jump the last } - Emiliano */
    } else {
      name=fill_symbuf(exprobj, is_alnum_lex);
    }
    s = getsym (builtin_ops_symrec, name);
    if (s != 0) {
      (*lvalp).tptr = s;
      return s->type;
    }

    {
      const char* next_char= exprobj->expr_curpos;
      /* optimization: funcs is always followed by ( */
      while ((next_char<(exprobj->exprarea).endnext) && isspace(*next_char)) next_char++;
      if ((*next_char)=='(') {
	/* user-defined functions have precedence over buit-in */
	if (((*lvalp).extfunc.func=(state->param->IsExprUserfncFuncPtr)(state->param->expr_func_map, name))) {
	  return EXTFUNC;
	}
	s = getsym (builtin_funcs_symrec, name);
	if (s != 0) {
	  (*lvalp).tptr = s;
	  return s->type;
	}
      }
      (*lvalp).uservar=name;
      /*log_expr(exprobj,TMPL_LOG_DEBUG2, "yylex: returned variable name %.*s\n",(int)(name.endnext-name.begin),name.begin);*/
      return VAR;
    }
  }

  TESTOP3('=','=','~',numEQ,reLIKE)
  TESTOP3('!','=','~',numNE,reNOTLIKE)
  TESTOP('>','=',numGE)
  TESTOP('<','=',numLE)
  TESTOP('&','&',AND)
  TESTOP('|','|',OR)

  /* Any other character is a token by itself. */
  (exprobj->expr_curpos)++;
  return c;
}

static
struct exprval
call_expr_userfunc(struct expr_parser* exprobj, struct tmplpro_param* param, struct user_func_call USERFUNC) {
  struct exprval emptyval = {EXPR_TYPE_PSTR};
  emptyval.val.strval.begin=NULL;
  emptyval.val.strval.endnext=NULL;
  exprobj->userfunc_call = emptyval;
  param->CallExprUserfncFuncPtr(param->ext_calluserfunc_state, USERFUNC.arglist, USERFUNC.func, &(exprobj->userfunc_call));
  if (param->debug>6) _tmplpro_expnum_debug (exprobj->userfunc_call, "EXPR: function call: returned ");
  param->FreeExprArglistFuncPtr(USERFUNC.arglist);
  USERFUNC.arglist = NULL;
  /* never happen; tmplpro_set_expr_as_* never set EXPR_TYPE_NULL *
   * if (exprobj->userfunc_call.type == EXPR_TYPE_NULL) exprobj->userfunc_call.type = EXPR_TYPE_PSTR;  */
  return exprobj->userfunc_call;
}

static
void
pusharg_expr_userfunc(struct expr_parser* exprobj, struct tmplpro_param* param, struct user_func_call USERFUNC, struct exprval arg) {
  if (arg.type == EXPR_TYPE_UPSTR) {
    arg.val.strval=expr_unescape_pstring_val(&(exprobj->state->expr_left_pbuffer),arg.val.strval);
    arg.type=EXPR_TYPE_PSTR;
  }
  exprobj->userfunc_call = arg;
  param->PushExprArglistFuncPtr(USERFUNC.arglist,&(exprobj->userfunc_call));
  if (param->debug>6) _tmplpro_expnum_debug (arg, "EXPR: arglist: pushed ");
}

#include "exprtool.inc"
#include "exprpstr.inc"
