1 /********************************************************************\
2 * gnc-exp-parser.c -- Implementation of expression parsing for *
3 * GnuCash using the routines in 'calculation'. *
4 * Copyright (C) 2000 Dave Peticolas <dave@krondo.com> *
5 * *
6 * This program is free software; you can redistribute it and/or *
7 * modify it under the terms of the GNU General Public License as *
8 * published by the Free Software Foundation; either version 2 of *
9 * the License, or (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License*
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
19 \********************************************************************/
20
21 #include <config.h>
22
23 #include <glib.h>
24 #include <glib/gi18n.h>
25 #include <libguile.h>
26 #include <ctype.h>
27 #include <locale.h>
28 #include <string.h>
29
30 #include "gfec.h"
31 #include "finproto.h"
32 #include "fin_spl_protos.h"
33 #include "gnc-filepath-utils.h"
34 #include "gnc-gkeyfile-utils.h"
35 #include "gnc-hooks.h"
36 #include "gnc-exp-parser.h"
37 #include "gnc-ui-util.h"
38 #include "gnc-locale-utils.h"
39 #include "guile-mappings.h"
40
41 #define GEP_GROUP_NAME "Variables"
42
43 static QofLogModule log_module = GNC_MOD_GUI;
44
45 /** Data Types *****************************************************/
46
47 typedef struct ParserNum
48 {
49 gnc_numeric value;
50 } ParserNum;
51
52
53 /** Static Globals *************************************************/
54 static GHashTable *variable_bindings = NULL;
55 static ParseError last_error = PARSER_NO_ERROR;
56 static GNCParseError last_gncp_error = NO_ERR;
57 static gboolean parser_inited = FALSE;
58
59
60 /** Implementations ************************************************/
61
62 static gchar *
gnc_exp_parser_filname(void)63 gnc_exp_parser_filname (void)
64 {
65 return gnc_build_userdata_path("expressions-2.0");
66 }
67
68 void
gnc_exp_parser_init(void)69 gnc_exp_parser_init ( void )
70 {
71 gnc_exp_parser_real_init( TRUE );
72 }
73
74 void
gnc_exp_parser_real_init(gboolean addPredefined)75 gnc_exp_parser_real_init ( gboolean addPredefined )
76 {
77 gchar *filename, **keys, **key, *str_value;
78 GKeyFile *key_file;
79 gnc_numeric value;
80
81 if (parser_inited)
82 gnc_exp_parser_shutdown ();
83
84 /* The parser uses fin.scm for financial functions, so load it here. */
85 scm_primitive_load_path(scm_from_utf8_string("gnucash/app-utils/fin"));
86 variable_bindings = g_hash_table_new (g_str_hash, g_str_equal);
87
88 /* This comes after the statics have been initialized. Not at the end! */
89 parser_inited = TRUE;
90
91 if ( addPredefined )
92 {
93 filename = gnc_exp_parser_filname();
94 key_file = gnc_key_file_load_from_file(filename, TRUE, FALSE, NULL);
95 if (key_file)
96 {
97 keys = g_key_file_get_keys(key_file, GEP_GROUP_NAME, NULL, NULL);
98 for (key = keys; key && *key; key++)
99 {
100 str_value = g_key_file_get_string(key_file, GEP_GROUP_NAME, *key, NULL);
101 if (str_value && string_to_gnc_numeric(str_value, &value))
102 {
103 gnc_exp_parser_set_value (*key, gnc_numeric_reduce (value));
104 }
105 }
106 g_strfreev(keys);
107 g_key_file_free(key_file);
108 }
109 g_free(filename);
110 }
111
112 gnc_hook_add_dangler(HOOK_SHUTDOWN, (GFunc)gnc_exp_parser_shutdown, NULL, NULL);
113 }
114
115 static gboolean
remove_binding(gpointer key,gpointer value,gpointer not_used)116 remove_binding (gpointer key, gpointer value, gpointer not_used)
117 {
118 g_free(key);
119 g_free(value);
120
121 return TRUE;
122 }
123
124 static void
set_one_key(gpointer key,gpointer value,gpointer data)125 set_one_key (gpointer key, gpointer value, gpointer data)
126 {
127 char *name = key;
128 ParserNum *pnum = value;
129 char *num_str;
130
131 num_str = gnc_numeric_to_string (gnc_numeric_reduce (pnum->value));
132 g_key_file_set_string ((GKeyFile *)data, GEP_GROUP_NAME, name, num_str);
133 g_free (num_str);
134 }
135
136 void
gnc_exp_parser_shutdown(void)137 gnc_exp_parser_shutdown (void)
138 {
139 GKeyFile* key_file;
140 gchar *filename;
141
142 if (!parser_inited)
143 return;
144
145 filename = gnc_exp_parser_filname();
146 key_file = g_key_file_new();
147 g_hash_table_foreach (variable_bindings, set_one_key, key_file);
148 g_key_file_set_comment(key_file, GEP_GROUP_NAME, NULL,
149 " Variables are in the form 'name=value'",
150 NULL);
151 gnc_key_file_save_to_file(filename, key_file, NULL);
152 g_key_file_free(key_file);
153 g_free(filename);
154
155 g_hash_table_foreach_remove (variable_bindings, remove_binding, NULL);
156 g_hash_table_destroy (variable_bindings);
157 variable_bindings = NULL;
158
159 last_error = PARSER_NO_ERROR;
160 last_gncp_error = NO_ERR;
161
162 parser_inited = FALSE;
163
164 gnc_hook_run(HOOK_SAVE_OPTIONS, NULL);
165 }
166
167 void
gnc_exp_parser_remove_variable(const char * variable_name)168 gnc_exp_parser_remove_variable (const char *variable_name)
169 {
170 gpointer key;
171 gpointer value;
172
173 if (!parser_inited)
174 return;
175
176 if (variable_name == NULL)
177 return;
178
179 if (g_hash_table_lookup_extended (variable_bindings, variable_name,
180 &key, &value))
181 {
182 g_hash_table_remove (variable_bindings, key);
183 g_free(key);
184 g_free(value);
185 }
186 }
187
188 void
gnc_exp_parser_set_value(const char * variable_name,gnc_numeric value)189 gnc_exp_parser_set_value (const char * variable_name, gnc_numeric value)
190 {
191 char *key;
192 ParserNum *pnum;
193
194 if (variable_name == NULL)
195 return;
196
197 if (!parser_inited)
198 gnc_exp_parser_init ();
199
200 gnc_exp_parser_remove_variable (variable_name);
201
202 key = g_strdup (variable_name);
203
204 pnum = g_new0(ParserNum, 1);
205 pnum->value = value;
206
207 g_hash_table_insert (variable_bindings, key, pnum);
208 }
209
210 static void
make_predefined_vars_helper(gpointer key,gpointer value,gpointer data)211 make_predefined_vars_helper (gpointer key, gpointer value, gpointer data)
212 {
213 var_store_ptr *vars_p = data;
214 ParserNum *pnum_old = value;
215 var_store_ptr var;
216 ParserNum *pnum;
217
218 var = g_new0 (var_store, 1);
219
220 pnum = g_new0 (ParserNum, 1);
221 *pnum = *pnum_old;
222
223 var->variable_name = g_strdup(key);
224 var->value = pnum;
225 var->next_var = *vars_p;
226
227 *vars_p = var;
228 }
229
230 static void
make_predefined_vars_from_external_helper(gpointer key,gpointer value,gpointer data)231 make_predefined_vars_from_external_helper( gpointer key, gpointer value, gpointer data )
232 {
233 ParserNum *pnum = g_new0( ParserNum, 1 );
234 if ( value != NULL )
235 pnum->value = *(gnc_numeric*)value;
236
237 make_predefined_vars_helper( key, pnum, data );
238 g_free(pnum); /* make_predefined_vars_helper allocs its own copy. */
239 }
240
241 static var_store_ptr
make_predefined_variables(void)242 make_predefined_variables (void)
243 {
244 var_store_ptr vars = NULL;
245
246 g_hash_table_foreach (variable_bindings, make_predefined_vars_helper, &vars);
247
248 return vars;
249 }
250
251 static void
free_predefined_variables(var_store_ptr vars)252 free_predefined_variables (var_store_ptr vars)
253 {
254 var_store_ptr next;
255
256 while (vars != NULL)
257 {
258 next = vars->next_var;
259
260 g_free(vars->variable_name);
261 vars->variable_name = NULL;
262
263 g_free(vars->value);
264 vars->value = NULL;
265
266 g_free(vars);
267
268 vars = next;
269 }
270 }
271
272 static void
update_variables(var_store_ptr vars)273 update_variables (var_store_ptr vars)
274 {
275 for ( ; vars ; vars = vars->next_var )
276 {
277 ParserNum *pnum = vars->value;
278 if (pnum != NULL)
279 gnc_exp_parser_set_value (vars->variable_name, pnum->value);
280 }
281 }
282
283 static char* _function_evaluation_error_msg = NULL;
284 static void
_exception_handler(const char * error_message)285 _exception_handler(const char *error_message)
286 {
287 _function_evaluation_error_msg = (char*)error_message;
288 }
289
290 static
291 void*
func_op(const char * fname,int argc,void ** argv)292 func_op(const char *fname, int argc, void **argv)
293 {
294 SCM scmFn, scmArgs, scmTmp;
295 int i;
296 var_store *vs;
297 gchar *str;
298 gnc_numeric n, *result;
299 GString *realFnName;
300
301 realFnName = g_string_sized_new( strlen(fname) + 5 );
302 g_string_printf( realFnName, "gnc:%s", fname );
303 scmFn = scm_internal_catch(SCM_BOOL_T,
304 (scm_t_catch_body)scm_c_eval_string, realFnName->str,
305 scm_handle_by_message_noexit, NULL);
306 g_string_free( realFnName, TRUE );
307 if (!scm_is_procedure(scmFn))
308 {
309 /* FIXME: handle errors correctly. */
310 printf( "gnc:\"%s\" is not a scm procedure\n", fname );
311 return NULL;
312 }
313 scmArgs = scm_list_n (SCM_UNDEFINED);
314 for ( i = 0; i < argc; i++ )
315 {
316 /* cons together back-to-front. */
317 vs = (var_store*)argv[argc - i - 1];
318 switch ( vs->type )
319 {
320 case VST_NUMERIC:
321 n = *(gnc_numeric*)(vs->value);
322 scmTmp = scm_from_double ( gnc_numeric_to_double( n ) );
323 break;
324 case VST_STRING:
325 str = (char*)(vs->value);
326 scmTmp = scm_from_utf8_string( str );
327 break;
328 default:
329 /* FIXME: error */
330 printf( "argument %d not a numeric or string [type = %d]\n",
331 i, vs->type );
332 return NULL;
333 break; /* notreached */
334 }
335 scmArgs = scm_cons( scmTmp, scmArgs );
336 }
337
338 //scmTmp = scm_apply(scmFn, scmArgs , SCM_EOL);
339 scmTmp = gfec_apply(scmFn, scmArgs, _exception_handler);
340 if (_function_evaluation_error_msg != NULL)
341 {
342 PERR("function eval error: [%s]\n", _function_evaluation_error_msg);
343 _function_evaluation_error_msg = NULL;
344 return NULL;
345 }
346
347 if (!scm_is_number (scmTmp))
348 {
349 PERR("function gnc:%s does not return a number", fname);
350 return NULL;
351 }
352
353 result = g_new0( gnc_numeric, 1 );
354 *result = double_to_gnc_numeric( scm_to_double(scmTmp),
355 GNC_DENOM_AUTO,
356 GNC_HOW_DENOM_SIGFIGS(12) | GNC_HOW_RND_ROUND_HALF_UP );
357 if (gnc_numeric_check (*result) != GNC_ERROR_OK)
358 {
359 PERR("Attempt to convert %f to GncNumeric Failed: %s",
360 scm_to_double(scmTmp),
361 gnc_numeric_errorCode_to_string (gnc_numeric_check (*result)));
362 g_free (result);
363 return NULL;
364 }
365 /* FIXME: cleanup scmArgs = scm_list, cons'ed cells? */
366 return (void*)result;
367 }
368
369 static void *
trans_numeric(const char * digit_str,gchar * radix_point,gchar * group_char,char ** rstr)370 trans_numeric(const char *digit_str,
371 gchar *radix_point,
372 gchar *group_char,
373 char **rstr)
374 {
375 ParserNum *pnum;
376 gnc_numeric value;
377
378 if (digit_str == NULL)
379 return NULL;
380
381 if (!xaccParseAmount (digit_str, TRUE, &value, rstr))
382 return NULL;
383
384 pnum = g_new0(ParserNum, 1);
385 pnum->value = value;
386
387 return pnum;
388 }
389
390 static void *
numeric_ops(char op_sym,void * left_value,void * right_value)391 numeric_ops(char op_sym,
392 void *left_value,
393 void *right_value)
394 {
395 ParserNum *left = left_value;
396 ParserNum *right = right_value;
397 ParserNum *result;
398
399 if ((left == NULL) || (right == NULL))
400 return NULL;
401
402 result = (op_sym == ASN_OP) ? left : g_new0(ParserNum, 1);
403
404 switch (op_sym)
405 {
406 case ADD_OP:
407 result->value = gnc_numeric_add (left->value, right->value,
408 GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
409 break;
410 case SUB_OP:
411 result->value = gnc_numeric_sub (left->value, right->value,
412 GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
413 break;
414 case DIV_OP:
415 result->value = gnc_numeric_div (left->value, right->value,
416 GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
417 break;
418 case MUL_OP:
419 result->value = gnc_numeric_mul (left->value, right->value,
420 GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
421 break;
422 case ASN_OP:
423 result->value = right->value;
424 break;
425 }
426
427 return result;
428 }
429
430 static void *
negate_numeric(void * value)431 negate_numeric(void *value)
432 {
433 ParserNum *result = value;
434
435 if (value == NULL)
436 return NULL;
437
438 result->value = gnc_numeric_neg (result->value);
439
440 return result;
441 }
442
443 static
444 void
gnc_ep_tmpvarhash_check_vals(gpointer key,gpointer value,gpointer user_data)445 gnc_ep_tmpvarhash_check_vals( gpointer key, gpointer value, gpointer user_data )
446 {
447 gboolean *allVarsHaveValues = (gboolean*)user_data;
448 gnc_numeric *num = (gnc_numeric*)value;
449 *allVarsHaveValues &= ( num && gnc_numeric_check( *num ) != GNC_ERROR_ARG );
450 }
451
452 static
453 void
gnc_ep_tmpvarhash_clean(gpointer key,gpointer value,gpointer user_data)454 gnc_ep_tmpvarhash_clean( gpointer key, gpointer value, gpointer user_data )
455 {
456 if ( key )
457 {
458 g_free( (gchar*)key );
459 }
460 if ( value )
461 {
462 g_free( (gnc_numeric*)value );
463 }
464 }
465
466 gboolean
gnc_exp_parser_parse(const char * expression,gnc_numeric * value_p,char ** error_loc_p)467 gnc_exp_parser_parse( const char * expression, gnc_numeric *value_p,
468 char **error_loc_p )
469 {
470 GHashTable *tmpVarHash;
471 gboolean ret, toRet = TRUE;
472 gboolean allVarsHaveValues = TRUE;
473
474 tmpVarHash = g_hash_table_new( g_str_hash, g_str_equal );
475 ret = gnc_exp_parser_parse_separate_vars( expression, value_p,
476 error_loc_p, tmpVarHash );
477 if ( !ret )
478 {
479 toRet = ret;
480 goto cleanup;
481 }
482
483 g_hash_table_foreach( tmpVarHash,
484 gnc_ep_tmpvarhash_check_vals,
485 &allVarsHaveValues );
486 if ( !allVarsHaveValues )
487 {
488 toRet = FALSE;
489 last_gncp_error = VARIABLE_IN_EXP;
490 }
491
492 cleanup:
493 g_hash_table_foreach( tmpVarHash, gnc_ep_tmpvarhash_clean, NULL );
494 g_hash_table_destroy( tmpVarHash );
495
496 return toRet;
497 }
498
499 gboolean
gnc_exp_parser_parse_separate_vars(const char * expression,gnc_numeric * value_p,char ** error_loc_p,GHashTable * varHash)500 gnc_exp_parser_parse_separate_vars (const char * expression,
501 gnc_numeric *value_p,
502 char **error_loc_p,
503 GHashTable *varHash )
504 {
505 parser_env_ptr pe;
506 var_store_ptr vars;
507 struct lconv *lc;
508 var_store result;
509 char * error_loc;
510 ParserNum *pnum;
511
512 if (expression == NULL)
513 return FALSE;
514
515 if (!parser_inited)
516 gnc_exp_parser_real_init ( (varHash == NULL) );
517
518 result.variable_name = NULL;
519 result.value = NULL;
520 result.next_var = NULL;
521
522 vars = make_predefined_variables ();
523
524 if ( varHash != NULL )
525 {
526 g_hash_table_foreach( varHash, make_predefined_vars_from_external_helper, &vars);
527 }
528
529 lc = gnc_localeconv ();
530
531 pe = init_parser (vars, lc->mon_decimal_point, lc->mon_thousands_sep,
532 trans_numeric, numeric_ops, negate_numeric, g_free,
533 func_op);
534
535 error_loc = parse_string (&result, expression, pe);
536
537 pnum = result.value;
538
539 if (error_loc == NULL)
540 {
541 if (gnc_numeric_check (pnum->value))
542 {
543 if (error_loc_p != NULL)
544 *error_loc_p = (char *) expression;
545
546 last_error = NUMERIC_ERROR;
547 }
548 else
549 {
550 if (pnum)
551 {
552 if (value_p)
553 *value_p = gnc_numeric_reduce (pnum->value);
554
555 if (!result.variable_name)
556 g_free (pnum);
557 }
558
559 if (error_loc_p != NULL)
560 *error_loc_p = NULL;
561
562 last_error = PARSER_NO_ERROR;
563 }
564 }
565 else
566 {
567 if (error_loc_p != NULL)
568 *error_loc_p = error_loc;
569
570 last_error = get_parse_error (pe);
571 }
572
573 if ( varHash != NULL )
574 {
575 var_store_ptr newVars;
576 gpointer maybeKey, maybeValue;
577 gnc_numeric *numericValue;
578
579 newVars = parser_get_vars( pe );
580 for ( ; newVars ; newVars = newVars->next_var )
581 {
582 if ( g_hash_table_lookup_extended( varHash, newVars->variable_name,
583 &maybeKey, &maybeValue ) )
584 {
585 g_hash_table_remove( varHash, maybeKey );
586 g_free( maybeKey );
587 g_free( maybeValue );
588 }
589 numericValue = g_new0( gnc_numeric, 1 );
590 *numericValue = ((ParserNum*)newVars->value)->value;
591 // WTF?
592 // numericValue = NULL;
593 g_hash_table_insert( varHash,
594 g_strdup(newVars->variable_name),
595 numericValue );
596 }
597 }
598 else
599 {
600 update_variables (vars);
601 }
602
603 free_predefined_variables (vars);
604
605 exit_parser (pe);
606
607 return last_error == PARSER_NO_ERROR;
608 }
609
610 const char *
gnc_exp_parser_error_string(void)611 gnc_exp_parser_error_string (void)
612 {
613 if ( last_error == PARSER_NO_ERROR )
614 {
615 switch ( last_gncp_error )
616 {
617 default:
618 case NO_ERR:
619 return NULL;
620 break;
621 case VARIABLE_IN_EXP:
622 return _("Illegal variable in expression." );
623 break;
624 }
625 }
626
627 switch (last_error)
628 {
629 default:
630 case PARSER_NO_ERROR:
631 return NULL;
632 case UNBALANCED_PARENS:
633 return _("Unbalanced parenthesis");
634 case STACK_OVERFLOW:
635 return _("Stack overflow");
636 case STACK_UNDERFLOW:
637 return _("Stack underflow");
638 case UNDEFINED_CHARACTER:
639 return _("Undefined character");
640 case NOT_A_VARIABLE:
641 return _("Not a variable");
642 case NOT_A_FUNC:
643 return _("Not a defined function");
644 case PARSER_OUT_OF_MEMORY:
645 return _("Out of memory");
646 case NUMERIC_ERROR:
647 return _("Numeric error");
648 }
649 }
650