/* parser.y -- Bison parser for libexp
 *
 * Copyright (C) 2001 Janusz Gregorczyk <jgregor@kki.net.pl>
 *
 * This file is part of xvs.
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 */

/* C declarations. */
%{
#include <ctype.h>
#include <glib.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <string.h>

#include "dict.h"
#include "execute.h"
#include "function.h"
#include "parser.h"
#include "storage.h"

#define YYPARSE_PARAM yyparam
#define YYLEX_PARAM yyparam

static gboolean expr_add_compile (expression_t *expr, symbol_dict_t *dict,
				  char *str);

#define GENERATE(str) if (!expr_add_compile (((parser_control *)yyparam)->expr, \
				        ((parser_control *)yyparam)->dict, str)) \
                   YYABORT;
%}

%pure_parser
     
/* Data types. */
%union {
char *s_value;
char c_value;
double d_value;
int i_value;
}

/* Terminal symbols. */     
%token <s_value> NAME
%token <d_value> NUMBER

/* Precedence rules. */     
%right '='
%left '-' '+'
%left '*' '/'
%left NEG
%right '^'
     
/* Grammar follows */
%%

/* Input consits of a (possibly empty) list of expressions. */
input:			/* empty */
			| input expression_list
;

/* expression_list is a ';' separated list of expressions. */
expression_list:	/* empty */
    			| expression
			    { } 
                        | expression_list ';'
    			| error ';'	
			    { yyerrok; }

/* argument list is a comma separated list od expressions */
argument_list:
			expression
                            {
                            }
                        | argument_list ',' expression
                            {
                            }
     
/* expression is a C-like expression. */
expression:		NUMBER
			    { 
                              char *buf = g_strdup_printf ("c%f:", $1);
                              GENERATE (buf); 
                              g_free (buf);
                            }
		        | NAME
			    { 
                              char *buf = g_strdup_printf ("l%s:", $1);
                              GENERATE (buf); 
                              g_free (buf);
                            }
    			| NAME '=' expression
			    { 
                              char *buf = g_strdup_printf ("s%s:", $1);
                              GENERATE (buf); 
                              g_free (buf);
                            }
                        | NAME '(' argument_list ')'
                            {
                              char *buf = g_strdup_printf ("f%s:", $1);
                              GENERATE (buf); 
                              g_free (buf);
                            }

			| expression '>' expression
			    { GENERATE (">"); }
			| expression '<' expression
			    { GENERATE ("<"); }

			| expression '+' expression
			    { GENERATE ("+"); }
			| expression '-' expression
			    { GENERATE ("-"); }
			| expression '*' expression
			    { GENERATE ("*"); }
			| expression '/' expression
			    { GENERATE ("/"); }
			| '-' expression %prec NEG
			    { GENERATE ("n"); }
			| expression '^' expression
			    { GENERATE ("^"); }
			| '(' expression ')'
			    { }
;

%%
/* End of grammar */

/* Called by yyparse on error. */
int yyerror (char *s) {
  /* Ignore errors, just print a warning. */
  g_warning ("%s\n", s);
  return 0;
}

int yylex (YYSTYPE *yylval, void *yyparam) {
  int c;
  parser_control *pc = (parser_control *) yyparam;
  
  /* Ignore whitespace, get first nonwhite character. */
  while ((c = fgetc (pc->input)) == ' ' || c == '\t' || c == '\n');
  
  /* End of input ? */
  if (c == EOF)
    return 0;
  
  /* Char starts a number => parse the number. */
  if (isdigit (c)) {
    ungetc (c, pc->input);/* Put the char back. */
    {
      char *old_locale, *saved_locale;

      old_locale = setlocale (LC_ALL, NULL);
      saved_locale = g_strdup (old_locale);
      setlocale (LC_ALL, "C");
      fscanf (pc->input, "%lf", &yylval->d_value);
      setlocale (LC_ALL, saved_locale);
      g_free (saved_locale);
    }
    return NUMBER;
  }
     
  /* Char starts an identifier => read the name. */
  if (isalpha (c)) {
    GString *sym_name;

    sym_name = g_string_new (NULL);
    
    do {
      sym_name = g_string_append_c (sym_name, c);

      /* Get another character. */
      c = fgetc (pc->input);
    } while (c != EOF && isalnum (c));
    
    ungetc (c, pc->input);

    yylval->s_value = sym_name->str;
    
    g_string_free (sym_name, FALSE);
    
    return NAME;
  }

  /* Any other character is a token by itself. */
  return c;
}

static int load_name (char *str, char **name) {
  int count = 0;
  GString *new = g_string_new (NULL);

  while (*str != 0 && *str != ':') {
    g_string_append_c (new, *str++);
    count++;
  }

  *name = new->str;
  g_string_free (new, FALSE);

  return count;
}

static gboolean expr_add_compile (expression_t *expr, symbol_dict_t *dict, 
				  char *str) {
  char op;
  double dval;
  int i;
  char *name;

  while ((op = *str++)) {
    switch (op) {
    case 'c':			/* A constant. */
      store_byte (expr, 'c');
      sscanf (str, "%lf%n", &dval, &i);
      str += i;
      store_double (expr, dval);
      str++;			/* Skip ';' */
      break;

    case 'f':			/* A function call. */
      store_byte (expr, 'f');
      str += load_name (str, &name);
      i = function_lookup (name);
      if (i < 0) return FALSE;	/* Fail on error. */
      store_int (expr, i);
      g_free (name);
      str++;			/* Skip ';' */
      break;

    case 'l':			/* Load a variable. */
    case 's':			/* Store a variable. */
      store_byte (expr, op);
      str += load_name (str, &name);
      i = dict_lookup (dict, name);
      store_int (expr, i);
      g_free (name);
      str++;			/* Skip ';' */
      break;

    default:			/* Copy verbatim. */
      store_byte (expr, op);
      break;
    }
  }

  return TRUE;
}
     
expression_t *expr_compile_string (const char* str, symbol_dict_t *dict) {
  parser_control pc;
  FILE *stream;

  stream = fmemopen (str, strlen (str), "r");

  pc.input = stream;
  pc.expr = expr_new ();
  pc.dict = dict;

  if (yyparse (&pc) != 0) {
    /* Check for error. */
    expr_free (pc.expr);
    pc.expr = NULL;
  }

  fclose (stream);

  return pc.expr;
}
