mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-11-27 11:20:27 -06:00
bf55c30aeb
There are a very few left that need deeper study, but this gets rid of most of the noise. For the most part it's just getting rid of extra variables or removing an assignment that is always replaced later but before any reads of the variable. A few are discarded result variables.
639 lines
16 KiB
C
639 lines
16 KiB
C
/********************************************************************\
|
|
* 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 <glib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <libguile.h>
|
|
#include <ctype.h>
|
|
#include <locale.h>
|
|
#include <string.h>
|
|
|
|
#include "gfec.h"
|
|
#include "finproto.h"
|
|
#include "fin_spl_protos.h"
|
|
#include "gnc-filepath-utils.h"
|
|
#include "gnc-gkeyfile-utils.h"
|
|
#include "gnc-exp-parser.h"
|
|
#include "gnc-ui-util.h"
|
|
#include "gnc-locale-utils.h"
|
|
#include "guile-mappings.h"
|
|
|
|
#define GEP_GROUP_NAME "Variables"
|
|
|
|
static QofLogModule log_module = GNC_MOD_GUI;
|
|
|
|
/** Data Types *****************************************************/
|
|
|
|
typedef struct ParserNum
|
|
{
|
|
gnc_numeric value;
|
|
} ParserNum;
|
|
|
|
|
|
/** Static Globals *************************************************/
|
|
static GHashTable *variable_bindings = NULL;
|
|
static ParseError last_error = PARSER_NO_ERROR;
|
|
static GNCParseError last_gncp_error = NO_ERR;
|
|
static gboolean parser_inited = FALSE;
|
|
|
|
|
|
/** Implementations ************************************************/
|
|
|
|
static gchar *
|
|
gnc_exp_parser_filname (void)
|
|
{
|
|
return gnc_build_userdata_path("expressions-2.0");
|
|
}
|
|
|
|
void
|
|
gnc_exp_parser_init ( void )
|
|
{
|
|
gnc_exp_parser_real_init( TRUE );
|
|
}
|
|
|
|
void
|
|
gnc_exp_parser_real_init ( gboolean addPredefined )
|
|
{
|
|
gchar *filename, **keys, **key, *str_value;
|
|
GKeyFile *key_file;
|
|
gnc_numeric value;
|
|
|
|
if (parser_inited)
|
|
gnc_exp_parser_shutdown ();
|
|
|
|
/* The parser uses fin.scm for financial functions, so load it here. */
|
|
scm_primitive_load_path(scm_from_utf8_string("fin"));
|
|
variable_bindings = g_hash_table_new (g_str_hash, g_str_equal);
|
|
|
|
/* This comes after the statics have been initialized. Not at the end! */
|
|
parser_inited = TRUE;
|
|
|
|
if ( addPredefined )
|
|
{
|
|
filename = gnc_exp_parser_filname();
|
|
key_file = gnc_key_file_load_from_file(filename, TRUE, FALSE, NULL);
|
|
if (key_file)
|
|
{
|
|
keys = g_key_file_get_keys(key_file, GEP_GROUP_NAME, NULL, NULL);
|
|
for (key = keys; key && *key; key++)
|
|
{
|
|
str_value = g_key_file_get_string(key_file, GEP_GROUP_NAME, *key, NULL);
|
|
if (str_value && string_to_gnc_numeric(str_value, &value))
|
|
{
|
|
gnc_exp_parser_set_value (*key, gnc_numeric_reduce (value));
|
|
}
|
|
}
|
|
g_strfreev(keys);
|
|
g_key_file_free(key_file);
|
|
}
|
|
g_free(filename);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
remove_binding (gpointer key, gpointer value, gpointer not_used)
|
|
{
|
|
g_free(key);
|
|
g_free(value);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
set_one_key (gpointer key, gpointer value, gpointer data)
|
|
{
|
|
char *name = key;
|
|
ParserNum *pnum = value;
|
|
char *num_str;
|
|
|
|
num_str = gnc_numeric_to_string (gnc_numeric_reduce (pnum->value));
|
|
g_key_file_set_string ((GKeyFile *)data, GEP_GROUP_NAME, name, num_str);
|
|
g_free (num_str);
|
|
}
|
|
|
|
void
|
|
gnc_exp_parser_shutdown (void)
|
|
{
|
|
GKeyFile* key_file;
|
|
gchar *filename;
|
|
|
|
if (!parser_inited)
|
|
return;
|
|
|
|
filename = gnc_exp_parser_filname();
|
|
key_file = g_key_file_new();
|
|
g_hash_table_foreach (variable_bindings, set_one_key, key_file);
|
|
g_key_file_set_comment(key_file, GEP_GROUP_NAME, NULL,
|
|
" Variables are in the form 'name=value'",
|
|
NULL);
|
|
gnc_key_file_save_to_file(filename, key_file, NULL);
|
|
g_key_file_free(key_file);
|
|
g_free(filename);
|
|
|
|
g_hash_table_foreach_remove (variable_bindings, remove_binding, NULL);
|
|
g_hash_table_destroy (variable_bindings);
|
|
variable_bindings = NULL;
|
|
|
|
last_error = PARSER_NO_ERROR;
|
|
last_gncp_error = NO_ERR;
|
|
|
|
parser_inited = FALSE;
|
|
}
|
|
|
|
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_set_value (const char * variable_name, gnc_numeric value)
|
|
{
|
|
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);
|
|
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;
|
|
ParserNum *pnum_old = value;
|
|
var_store_ptr var;
|
|
ParserNum *pnum;
|
|
|
|
var = g_new0 (var_store, 1);
|
|
|
|
pnum = g_new0 (ParserNum, 1);
|
|
*pnum = *pnum_old;
|
|
|
|
var->variable_name = g_strdup(key);
|
|
var->value = pnum;
|
|
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 );
|
|
g_free(pnum); /* make_predefined_vars_helper allocs its own copy. */
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
static char* _function_evaluation_error_msg = NULL;
|
|
static void
|
|
_exception_handler(const char *error_message)
|
|
{
|
|
_function_evaluation_error_msg = (char*)error_message;
|
|
}
|
|
|
|
static
|
|
void*
|
|
func_op(const char *fname, int argc, void **argv)
|
|
{
|
|
SCM scmFn, scmArgs, scmTmp;
|
|
int i;
|
|
var_store *vs;
|
|
gchar *str;
|
|
gnc_numeric n, *result;
|
|
GString *realFnName;
|
|
|
|
realFnName = g_string_sized_new( strlen(fname) + 5 );
|
|
g_string_printf( realFnName, "gnc:%s", fname );
|
|
scmFn = scm_internal_catch(SCM_BOOL_T,
|
|
(scm_t_catch_body)scm_c_eval_string, realFnName->str,
|
|
scm_handle_by_message_noexit, NULL);
|
|
g_string_free( realFnName, TRUE );
|
|
if (!scm_is_procedure(scmFn))
|
|
{
|
|
/* FIXME: handle errors correctly. */
|
|
printf( "gnc:\"%s\" is not a scm procedure\n", fname );
|
|
return NULL;
|
|
}
|
|
scmArgs = scm_list_n (SCM_UNDEFINED);
|
|
for ( i = 0; i < argc; i++ )
|
|
{
|
|
/* cons together back-to-front. */
|
|
vs = (var_store*)argv[argc - i - 1];
|
|
switch ( vs->type )
|
|
{
|
|
case VST_NUMERIC:
|
|
n = *(gnc_numeric*)(vs->value);
|
|
scmTmp = scm_from_double ( gnc_numeric_to_double( n ) );
|
|
break;
|
|
case VST_STRING:
|
|
str = (char*)(vs->value);
|
|
scmTmp = scm_from_utf8_string( str );
|
|
break;
|
|
default:
|
|
/* FIXME: error */
|
|
printf( "argument %d not a numeric or string [type = %d]\n",
|
|
i, vs->type );
|
|
return NULL;
|
|
break; /* notreached */
|
|
}
|
|
scmArgs = scm_cons( scmTmp, scmArgs );
|
|
}
|
|
|
|
//scmTmp = scm_apply(scmFn, scmArgs , SCM_EOL);
|
|
scmTmp = gfec_apply(scmFn, scmArgs, _exception_handler);
|
|
if (_function_evaluation_error_msg != NULL)
|
|
{
|
|
PERR("function eval error: [%s]\n", _function_evaluation_error_msg);
|
|
_function_evaluation_error_msg = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
result = g_new0( gnc_numeric, 1 );
|
|
*result = double_to_gnc_numeric( scm_to_double(scmTmp),
|
|
GNC_DENOM_AUTO,
|
|
GNC_HOW_DENOM_SIGFIGS(12) | GNC_HOW_RND_ROUND_HALF_UP );
|
|
if (gnc_numeric_check (*result) != GNC_ERROR_OK)
|
|
{
|
|
PERR("Attempt to convert %f to GncNumeric Failed: %s",
|
|
scm_to_double(scmTmp),
|
|
gnc_numeric_errorCode_to_string (gnc_numeric_check (*result)));
|
|
g_free (result);
|
|
return NULL;
|
|
}
|
|
/* FIXME: cleanup scmArgs = scm_list, cons'ed cells? */
|
|
return (void*)result;
|
|
}
|
|
|
|
static void *
|
|
trans_numeric(const char *digit_str,
|
|
gchar *radix_point,
|
|
gchar *group_char,
|
|
char **rstr)
|
|
{
|
|
ParserNum *pnum;
|
|
gnc_numeric value;
|
|
|
|
if (digit_str == NULL)
|
|
return NULL;
|
|
|
|
if (!xaccParseAmount (digit_str, TRUE, &value, rstr))
|
|
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);
|
|
|
|
switch (op_sym)
|
|
{
|
|
case ADD_OP:
|
|
result->value = gnc_numeric_add (left->value, right->value,
|
|
GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
|
|
break;
|
|
case SUB_OP:
|
|
result->value = gnc_numeric_sub (left->value, right->value,
|
|
GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
|
|
break;
|
|
case DIV_OP:
|
|
result->value = gnc_numeric_div (left->value, right->value,
|
|
GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
|
|
break;
|
|
case MUL_OP:
|
|
result->value = gnc_numeric_mul (left->value, right->value,
|
|
GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
|
|
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;
|
|
|
|
result->value = gnc_numeric_neg (result->value);
|
|
|
|
return result;
|
|
}
|
|
|
|
static
|
|
void
|
|
gnc_ep_tmpvarhash_check_vals( gpointer key, gpointer value, gpointer user_data )
|
|
{
|
|
gboolean *allVarsHaveValues = (gboolean*)user_data;
|
|
gnc_numeric *num = (gnc_numeric*)value;
|
|
*allVarsHaveValues &= ( num && gnc_numeric_check( *num ) != GNC_ERROR_ARG );
|
|
}
|
|
|
|
static
|
|
void
|
|
gnc_ep_tmpvarhash_clean( gpointer key, gpointer value, gpointer user_data )
|
|
{
|
|
if ( key )
|
|
{
|
|
g_free( (gchar*)key );
|
|
}
|
|
if ( value )
|
|
{
|
|
g_free( (gnc_numeric*)value );
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gnc_exp_parser_parse( const char * expression, gnc_numeric *value_p,
|
|
char **error_loc_p )
|
|
{
|
|
GHashTable *tmpVarHash;
|
|
gboolean ret, toRet = TRUE;
|
|
gboolean allVarsHaveValues = TRUE;
|
|
|
|
tmpVarHash = g_hash_table_new( g_str_hash, g_str_equal );
|
|
ret = gnc_exp_parser_parse_separate_vars( expression, value_p,
|
|
error_loc_p, tmpVarHash );
|
|
if ( !ret )
|
|
{
|
|
toRet = ret;
|
|
goto cleanup;
|
|
}
|
|
|
|
g_hash_table_foreach( tmpVarHash,
|
|
gnc_ep_tmpvarhash_check_vals,
|
|
&allVarsHaveValues );
|
|
if ( !allVarsHaveValues )
|
|
{
|
|
toRet = FALSE;
|
|
last_gncp_error = VARIABLE_IN_EXP;
|
|
}
|
|
|
|
cleanup:
|
|
g_hash_table_foreach( tmpVarHash, gnc_ep_tmpvarhash_clean, NULL );
|
|
g_hash_table_destroy( tmpVarHash );
|
|
|
|
return toRet;
|
|
}
|
|
|
|
gboolean
|
|
gnc_exp_parser_parse_separate_vars (const char * expression,
|
|
gnc_numeric *value_p,
|
|
char **error_loc_p,
|
|
GHashTable *varHash )
|
|
{
|
|
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_real_init ( (varHash == NULL) );
|
|
|
|
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);
|
|
}
|
|
|
|
lc = gnc_localeconv ();
|
|
|
|
pe = init_parser (vars, lc->mon_decimal_point, lc->mon_thousands_sep,
|
|
trans_numeric, numeric_ops, negate_numeric, g_free,
|
|
func_op);
|
|
|
|
error_loc = parse_string (&result, expression, pe);
|
|
|
|
pnum = result.value;
|
|
|
|
if (error_loc == NULL)
|
|
{
|
|
if (gnc_numeric_check (pnum->value))
|
|
{
|
|
if (error_loc_p != NULL)
|
|
*error_loc_p = (char *) expression;
|
|
|
|
last_error = NUMERIC_ERROR;
|
|
}
|
|
else
|
|
{
|
|
if (pnum)
|
|
{
|
|
if (value_p)
|
|
*value_p = gnc_numeric_reduce (pnum->value);
|
|
|
|
if (!result.variable_name)
|
|
g_free (pnum);
|
|
}
|
|
|
|
if (error_loc_p != NULL)
|
|
*error_loc_p = NULL;
|
|
|
|
last_error = PARSER_NO_ERROR;
|
|
}
|
|
}
|
|
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 )
|
|
{
|
|
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;
|
|
// WTF?
|
|
// numericValue = NULL;
|
|
g_hash_table_insert( varHash,
|
|
g_strdup(newVars->variable_name),
|
|
numericValue );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
update_variables (vars);
|
|
}
|
|
|
|
free_predefined_variables (vars);
|
|
|
|
exit_parser (pe);
|
|
|
|
return last_error == PARSER_NO_ERROR;
|
|
}
|
|
|
|
const char *
|
|
gnc_exp_parser_error_string (void)
|
|
{
|
|
if ( last_error == PARSER_NO_ERROR )
|
|
{
|
|
switch ( last_gncp_error )
|
|
{
|
|
default:
|
|
case NO_ERR:
|
|
return NULL;
|
|
break;
|
|
case VARIABLE_IN_EXP:
|
|
return _("Illegal variable in expression." );
|
|
break;
|
|
}
|
|
}
|
|
|
|
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 NOT_A_FUNC:
|
|
return _("Not a defined function");
|
|
case PARSER_OUT_OF_MEMORY:
|
|
return _("Out of memory");
|
|
case NUMERIC_ERROR:
|
|
return _("Numeric error");
|
|
}
|
|
}
|