Files
gnucash/src/gnc-exp-parser.c
T

544 lines
12 KiB
C
Raw Normal View History

2000-08-31 07:16:55 +00:00
/********************************************************************\
* gnc-exp-parser.c -- Implementation of expression parsing for *
* GnuCash using the routines in 'calculation'. *
* Copyright (C) 2000 Dave Peticolas <dave@krondo.com> *
* *
* 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
\********************************************************************/
#include "config.h"
#include <ctype.h>
2001-04-25 21:49:16 +00:00
#include <locale.h>
#include <glib.h>
#include <guile/gh.h>
2000-08-31 07:16:55 +00:00
#include "finproto.h"
#include "fin_spl_protos.h"
#include "global-options.h"
2000-08-31 07:16:55 +00:00
#include "gnc-exp-parser.h"
#include "messages.h"
#include "gnc-ui-util.h"
2000-08-31 07:16:55 +00:00
/** Data Types *****************************************************/
typedef struct ParserNum
{
2000-10-30 10:46:46 +00:00
gnc_numeric value;
2000-08-31 07:16:55 +00:00
} ParserNum;
/** Static Globals *************************************************/
static GHashTable *variable_bindings = NULL;
static ParseError last_error = PARSER_NO_ERROR;
static gboolean parser_inited = FALSE;
/** Implementations ************************************************/
void
gnc_exp_parser_init (void)
{
SCM alist;
2000-08-31 07:16:55 +00:00
if (parser_inited)
gnc_exp_parser_shutdown ();
variable_bindings = g_hash_table_new (g_str_hash, g_str_equal);
/* This comes after the statics have been initialized. Not at the end! */
2000-08-31 07:16:55 +00:00
parser_inited = TRUE;
alist = gnc_lookup_option ("__exp_parser", "defined_variables", SCM_EOL);
while (gh_list_p(alist) && !gh_null_p(alist))
{
char *name;
SCM assoc;
2000-10-30 10:46:46 +00:00
SCM val_scm;
gnc_numeric value;
gboolean good;
assoc = gh_car (alist);
alist = gh_cdr (alist);
2000-10-30 10:46:46 +00:00
if (!gh_pair_p (assoc))
continue;
name = gh_scm2newstr (gh_car (assoc), NULL);
if (name == NULL)
continue;
2000-10-30 10:46:46 +00:00
val_scm = gh_cdr (assoc);
good = TRUE;
if (gh_number_p (val_scm))
{
double dvalue;
dvalue = gh_scm2double (val_scm);
2001-02-12 22:46:55 +00:00
value = double_to_gnc_numeric (dvalue, GNC_DENOM_AUTO,
GNC_DENOM_SIGFIGS(6) | GNC_RND_ROUND);
2000-10-30 10:46:46 +00:00
}
else if (gh_string_p (val_scm))
{
char *s;
const char *err;
s = gh_scm2newstr (val_scm, NULL);
err = string_to_gnc_numeric (s, &value);
if (err == NULL)
good = FALSE;
free (s);
}
else
good = FALSE;
2000-10-30 10:46:46 +00:00
if (good)
gnc_exp_parser_set_value (name, gnc_numeric_reduce (value));
2000-10-30 10:46:46 +00:00
free (name);
}
2000-08-31 07:16:55 +00:00
}
static gboolean
remove_binding (gpointer key, gpointer value, gpointer not_used)
{
g_free(key);
g_free(value);
return TRUE;
}
static void
binding_cons (gpointer key, gpointer value, gpointer data)
{
char *name = key;
ParserNum *pnum = value;
SCM *alist_p = data;
2000-10-30 10:46:46 +00:00
char *num_str;
SCM assoc;
2000-10-30 10:46:46 +00:00
num_str = gnc_numeric_to_string (gnc_numeric_reduce (pnum->value));
assoc = gh_cons (gh_str02scm (name), gh_str02scm (num_str));
g_free (num_str);
*alist_p = gh_cons (assoc, *alist_p);
}
2000-08-31 07:16:55 +00:00
void
gnc_exp_parser_shutdown (void)
{
SCM alist;
2000-08-31 07:16:55 +00:00
if (!parser_inited)
return;
alist = SCM_EOL;
g_hash_table_foreach (variable_bindings, binding_cons, &alist);
gnc_set_option ("__exp_parser", "defined_variables", alist);
2000-08-31 07:16:55 +00:00
g_hash_table_foreach_remove (variable_bindings, remove_binding, NULL);
g_hash_table_destroy (variable_bindings);
variable_bindings = NULL;
last_error = PARSER_NO_ERROR;
parser_inited = FALSE;
}
static void
prepend_name (gpointer key, gpointer value, gpointer data)
{
GList **list = data;
*list = g_list_prepend (*list, key);
}
GList *
gnc_exp_parser_get_variable_names (void)
{
GList *names = NULL;
if (!parser_inited)
return NULL;
g_hash_table_foreach (variable_bindings, prepend_name, &names);
return names;
}
void
gnc_exp_parser_remove_variable (const char *variable_name)
{
gpointer key;
gpointer value;
if (!parser_inited)
return;
if (variable_name == NULL)
return;
if (g_hash_table_lookup_extended (variable_bindings, variable_name,
&key, &value))
{
g_hash_table_remove (variable_bindings, key);
g_free(key);
g_free(value);
}
}
void
gnc_exp_parser_remove_variable_names (GList * variable_names)
{
if (!parser_inited)
return;
while (variable_names != NULL)
{
gnc_exp_parser_remove_variable (variable_names->data);
variable_names = variable_names->next;
}
}
gboolean
2000-10-30 10:46:46 +00:00
gnc_exp_parser_get_value (const char * variable_name, gnc_numeric *value_p)
2000-08-31 07:16:55 +00:00
{
ParserNum *pnum;
if (!parser_inited)
return FALSE;
if (variable_name == NULL)
return FALSE;
pnum = g_hash_table_lookup (variable_bindings, variable_name);
if (pnum == NULL)
return FALSE;
if (value_p != NULL)
*value_p = pnum->value;
return TRUE;
}
void
2000-10-30 10:46:46 +00:00
gnc_exp_parser_set_value (const char * variable_name, gnc_numeric value)
2000-08-31 07:16:55 +00:00
{
char *key;
ParserNum *pnum;
if (variable_name == NULL)
return;
if (!parser_inited)
gnc_exp_parser_init ();
gnc_exp_parser_remove_variable (variable_name);
key = g_strdup (variable_name);
pnum = g_new0(ParserNum, 1);
2000-08-31 07:16:55 +00:00
pnum->value = value;
g_hash_table_insert (variable_bindings, key, pnum);
}
static void
make_predefined_vars_helper (gpointer key, gpointer value, gpointer data)
{
var_store_ptr *vars_p = data;
2000-09-11 11:09:49 +00:00
ParserNum *pnum_old = value;
2000-08-31 07:16:55 +00:00
var_store_ptr var;
2000-09-11 11:09:49 +00:00
ParserNum *pnum;
2000-08-31 07:16:55 +00:00
var = g_new0 (var_store, 1);
pnum = g_new0 (ParserNum, 1);
2000-09-11 11:09:49 +00:00
*pnum = *pnum_old;
var->variable_name = g_strdup(key);
var->value = pnum;
2000-08-31 07:16:55 +00:00
var->next_var = *vars_p;
*vars_p = var;
}
static void
make_predefined_vars_from_external_helper( gpointer key, gpointer value, gpointer data )
{
ParserNum *pnum = g_new0( ParserNum, 1 );
if ( value != NULL )
pnum->value = *(gnc_numeric*)value;
make_predefined_vars_helper( key, pnum, data );
}
2000-08-31 07:16:55 +00:00
static var_store_ptr
make_predefined_variables (void)
{
var_store_ptr vars = NULL;
g_hash_table_foreach (variable_bindings, make_predefined_vars_helper, &vars);
return vars;
}
static void
free_predefined_variables (var_store_ptr vars)
{
var_store_ptr next;
while (vars != NULL)
{
next = vars->next_var;
2000-09-11 11:09:49 +00:00
g_free(vars->variable_name);
vars->variable_name = NULL;
g_free(vars->value);
vars->value = NULL;
g_free(vars);
vars = next;
}
}
static void
update_variables (var_store_ptr vars)
{
for ( ; vars ; vars = vars->next_var )
{
ParserNum *pnum = vars->value;
if (pnum != NULL)
gnc_exp_parser_set_value (vars->variable_name, pnum->value);
}
}
2000-08-31 07:16:55 +00:00
static void *
trans_numeric(const char *digit_str,
char radix_point,
char group_char,
char **rstr)
{
ParserNum *pnum;
2000-10-30 10:46:46 +00:00
gnc_numeric value;
2000-08-31 07:16:55 +00:00
if (digit_str == NULL)
return NULL;
2000-10-30 10:46:46 +00:00
if (!xaccParseAmount (digit_str, TRUE, &value, rstr))
2000-08-31 07:16:55 +00:00
return NULL;
pnum = g_new0(ParserNum, 1);
pnum->value = value;
return pnum;
}
static void *
numeric_ops(char op_sym,
void *left_value,
void *right_value)
{
ParserNum *left = left_value;
ParserNum *right = right_value;
ParserNum *result;
if ((left == NULL) || (right == NULL))
return NULL;
result = (op_sym == ASN_OP) ? left : g_new0(ParserNum, 1);
2000-08-31 07:16:55 +00:00
switch (op_sym)
{
case ADD_OP:
2000-10-30 10:46:46 +00:00
result->value = gnc_numeric_add (left->value, right->value,
GNC_DENOM_AUTO, GNC_DENOM_EXACT);
2000-08-31 07:16:55 +00:00
break;
case SUB_OP:
2000-10-30 10:46:46 +00:00
result->value = gnc_numeric_sub (left->value, right->value,
GNC_DENOM_AUTO, GNC_DENOM_EXACT);
2000-08-31 07:16:55 +00:00
break;
case DIV_OP:
2000-10-30 10:46:46 +00:00
result->value = gnc_numeric_div (left->value, right->value,
GNC_DENOM_AUTO, GNC_DENOM_EXACT);
2000-08-31 07:16:55 +00:00
break;
case MUL_OP:
2000-10-30 10:46:46 +00:00
result->value = gnc_numeric_mul (left->value, right->value,
GNC_DENOM_AUTO, GNC_DENOM_EXACT);
2000-08-31 07:16:55 +00:00
break;
case ASN_OP:
result->value = right->value;
break;
}
return result;
}
static void *
negate_numeric(void *value)
{
ParserNum *result = value;
if (value == NULL)
return NULL;
2000-10-30 10:46:46 +00:00
result->value = gnc_numeric_neg (result->value);
2000-08-31 07:16:55 +00:00
return result;
}
gboolean
gnc_exp_parser_parse( const char * expression, gnc_numeric *value_p,
char **error_loc_p )
{
return gnc_exp_parser_parse_seperate_vars( expression, value_p,
error_loc_p, NULL );
}
gboolean
gnc_exp_parser_parse_seperate_vars (const char * expression, gnc_numeric *value_p,
char **error_loc_p, GHashTable *varHash )
2000-08-31 07:16:55 +00:00
{
parser_env_ptr pe;
var_store_ptr vars;
struct lconv *lc;
var_store result;
char * error_loc;
ParserNum *pnum;
if (expression == NULL)
return FALSE;
if (!parser_inited)
gnc_exp_parser_init ();
result.variable_name = NULL;
result.value = NULL;
result.next_var = NULL;
vars = make_predefined_variables ();
if ( varHash != NULL ) {
g_hash_table_foreach( varHash, make_predefined_vars_from_external_helper, &vars);
}
2000-08-31 07:16:55 +00:00
lc = gnc_localeconv ();
pe = init_parser (vars, *lc->mon_decimal_point, *lc->mon_thousands_sep,
trans_numeric, numeric_ops, negate_numeric, g_free);
error_loc = parse_string (&result, expression, pe);
pnum = result.value;
if (error_loc == NULL)
{
if (gnc_numeric_check (pnum->value))
2000-12-12 09:21:10 +00:00
{
if (error_loc_p != NULL)
*error_loc_p = (char *) expression;
2000-12-12 09:21:10 +00:00
last_error = NUMERIC_ERROR;
2000-12-12 09:21:10 +00:00
}
else
{
if (pnum)
{
if (value_p)
*value_p = gnc_numeric_reduce (pnum->value);
2000-08-31 07:16:55 +00:00
if (!result.variable_name)
g_free (pnum);
}
2000-08-31 07:16:55 +00:00
if (error_loc_p != NULL)
*error_loc_p = NULL;
last_error = PARSER_NO_ERROR;
}
2000-08-31 07:16:55 +00:00
}
else
{
if (error_loc_p != NULL)
*error_loc_p = error_loc;
last_error = get_parse_error (pe);
}
if ( varHash != NULL ) {
var_store_ptr newVars;
gpointer maybeKey, maybeValue;
gnc_numeric *numericValue;
newVars = parser_get_vars( pe );
for ( ; newVars ; newVars = newVars->next_var ) {
pnum = newVars->value;
if ( g_hash_table_lookup_extended( varHash, newVars->variable_name,
&maybeKey, &maybeValue ) ) {
g_hash_table_remove( varHash, maybeKey );
g_free( maybeKey );
g_free( maybeValue );
}
numericValue = g_new0( gnc_numeric, 1 );
*numericValue = ((ParserNum*)newVars->value)->value;
numericValue = NULL;
g_hash_table_insert( varHash,
g_strdup(newVars->variable_name),
numericValue );
}
} else {
update_variables (vars);
update_variables (parser_get_vars (pe));
}
free_predefined_variables (vars);
2000-08-31 07:16:55 +00:00
exit_parser (pe);
return last_error == PARSER_NO_ERROR;
2000-08-31 07:16:55 +00:00
}
const char *
gnc_exp_parser_error_string (void)
{
switch (last_error)
{
default:
case PARSER_NO_ERROR:
return NULL;
case UNBALANCED_PARENS:
return _("Unbalanced parenthesis");
case STACK_OVERFLOW:
return _("Stack overflow");
case STACK_UNDERFLOW:
return _("Stack underflow");
case UNDEFINED_CHARACTER:
return _("Undefined character");
case NOT_A_VARIABLE:
return _("Not a variable");
case PARSER_OUT_OF_MEMORY:
return _("Out of memory");
case NUMERIC_ERROR:
return _("Numeric error");
}
2000-08-31 07:16:55 +00:00
}