/********************************************************************\ * gnc-exp-parser.c -- Implementation of expression parsing for * * GnuCash using the routines in 'calculation'. * * Copyright (C) 2000 Dave Peticolas * * * * 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 #include #include #include #include #include #include #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"); } }