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