GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-exp-parser.c
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
43static QofLogModule log_module = GNC_MOD_GUI;
44
47typedef struct ParserNum
48{
49 gnc_numeric value;
50} ParserNum;
51
52
54static GHashTable *variable_bindings = NULL;
55static ParseError last_error = PARSER_NO_ERROR;
56static GNCParseError last_gncp_error = NO_ERR;
57static gboolean parser_inited = FALSE;
58
59
62static gchar *
63gnc_exp_parser_filname (void)
64{
65 return gnc_build_userdata_path("expressions-2.0");
66}
67
68void
69gnc_exp_parser_init ( void )
70{
71 gnc_exp_parser_real_init( TRUE );
72}
73
74void
75gnc_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 value = gnc_numeric_from_string (str_value);
102 if (!gnc_numeric_check (value))
103 gnc_exp_parser_set_value (*key, gnc_numeric_reduce (value));
104 }
105 g_strfreev(keys);
106 g_key_file_free(key_file);
107 }
108 g_free(filename);
109 }
110
111 gnc_hook_add_dangler(HOOK_SHUTDOWN, (GFunc)gnc_exp_parser_shutdown, NULL, NULL);
112}
113
114static gboolean
115remove_binding (gpointer key, gpointer value, gpointer not_used)
116{
117 g_free(key);
118 g_free(value);
119
120 return TRUE;
121}
122
123static void
124set_one_key (gpointer key, gpointer value, gpointer data)
125{
126 char *name = key;
127 ParserNum *pnum = value;
128 char *num_str;
129
130 num_str = gnc_numeric_to_string (gnc_numeric_reduce (pnum->value));
131 g_key_file_set_string ((GKeyFile *)data, GEP_GROUP_NAME, name, num_str);
132 g_free (num_str);
133}
134
135void
136gnc_exp_parser_shutdown (void)
137{
138 GKeyFile* key_file;
139 gchar *filename;
140
141 if (!parser_inited)
142 return;
143
144 filename = gnc_exp_parser_filname();
145 key_file = g_key_file_new();
146 g_hash_table_foreach (variable_bindings, set_one_key, key_file);
147 g_key_file_set_comment(key_file, GEP_GROUP_NAME, NULL,
148 " Variables are in the form 'name=value'",
149 NULL);
150 gnc_key_file_save_to_file(filename, key_file, NULL);
151 g_key_file_free(key_file);
152 g_free(filename);
153
154 g_hash_table_foreach_remove (variable_bindings, remove_binding, NULL);
155 g_hash_table_destroy (variable_bindings);
156 variable_bindings = NULL;
157
158 last_error = PARSER_NO_ERROR;
159 last_gncp_error = NO_ERR;
160
161 parser_inited = FALSE;
162
163 gnc_hook_run(HOOK_SAVE_OPTIONS, NULL);
164}
165
166void
167gnc_exp_parser_remove_variable (const char *variable_name)
168{
169 gpointer key;
170 gpointer value;
171
172 if (!parser_inited)
173 return;
174
175 if (variable_name == NULL)
176 return;
177
178 if (g_hash_table_lookup_extended (variable_bindings, variable_name,
179 &key, &value))
180 {
181 g_hash_table_remove (variable_bindings, key);
182 g_free(key);
183 g_free(value);
184 }
185}
186
187void
188gnc_exp_parser_set_value (const char * variable_name, gnc_numeric value)
189{
190 char *key;
191 ParserNum *pnum;
192
193 if (variable_name == NULL)
194 return;
195
196 if (!parser_inited)
197 gnc_exp_parser_init ();
198
199 gnc_exp_parser_remove_variable (variable_name);
200
201 key = g_strdup (variable_name);
202
203 pnum = g_new0(ParserNum, 1);
204 pnum->value = value;
205
206 g_hash_table_insert (variable_bindings, key, pnum);
207}
208
209static void
210make_predefined_vars_helper (gpointer key, gpointer value, gpointer data)
211{
212 var_store_ptr *vars_p = data;
213 ParserNum *pnum_old = value;
214 var_store_ptr var;
215 ParserNum *pnum;
216
217 var = g_new0 (var_store, 1);
218
219 pnum = g_new0 (ParserNum, 1);
220 *pnum = *pnum_old;
221
222 var->variable_name = g_strdup(key);
223 var->value = pnum;
224 var->next_var = *vars_p;
225
226 *vars_p = var;
227}
228
229static void
230make_predefined_vars_from_external_helper( gpointer key, gpointer value, gpointer data )
231{
232 ParserNum *pnum = g_new0( ParserNum, 1 );
233 if ( value != NULL )
234 pnum->value = *(gnc_numeric*)value;
235
236 make_predefined_vars_helper( key, pnum, data );
237 g_free(pnum); /* make_predefined_vars_helper allocs its own copy. */
238}
239
240static var_store_ptr
241make_predefined_variables (void)
242{
243 var_store_ptr vars = NULL;
244
245 g_hash_table_foreach (variable_bindings, make_predefined_vars_helper, &vars);
246
247 return vars;
248}
249
250static void
251free_predefined_variables (var_store_ptr vars)
252{
253 var_store_ptr next;
254
255 while (vars != NULL)
256 {
257 next = vars->next_var;
258
259 g_free(vars->variable_name);
260 vars->variable_name = NULL;
261
262 g_free(vars->value);
263 vars->value = NULL;
264
265 g_free(vars);
266
267 vars = next;
268 }
269}
270
271static void
272update_variables (var_store_ptr vars)
273{
274 for ( ; vars ; vars = vars->next_var )
275 {
276 ParserNum *pnum = vars->value;
277 if (pnum != NULL)
278 gnc_exp_parser_set_value (vars->variable_name, pnum->value);
279 }
280}
281
282static void
283_exception_handler(const char *error_message)
284{
285 PERR("function eval error: [%s]\n", error_message);
286}
287
288static
289void*
290func_op(const char *fname, int argc, void **argv)
291{
292 SCM scmFn, scmArgs, scmTmp;
293 int i;
294 var_store *vs;
295 gchar *str;
296 gnc_numeric n, *result;
297 GString *realFnName;
298
299 realFnName = g_string_sized_new( strlen(fname) + 5 );
300 g_string_printf( realFnName, "gnc:%s", fname );
301 scmFn = scm_internal_catch(SCM_BOOL_T,
302 (scm_t_catch_body)scm_c_eval_string, realFnName->str,
303 scm_handle_by_message_noexit, NULL);
304 g_string_free( realFnName, TRUE );
305 if (!scm_is_procedure(scmFn))
306 {
307 /* FIXME: handle errors correctly. */
308 printf( "gnc:\"%s\" is not a scm procedure\n", fname );
309 return NULL;
310 }
311 scmArgs = scm_list_n (SCM_UNDEFINED);
312 for ( i = 0; i < argc; i++ )
313 {
314 /* cons together back-to-front. */
315 vs = (var_store*)argv[argc - i - 1];
316 switch ( vs->type )
317 {
318 case VST_NUMERIC:
319 n = *(gnc_numeric*)(vs->value);
320 scmTmp = scm_from_double ( gnc_numeric_to_double( n ) );
321 break;
322 case VST_STRING:
323 str = (char*)(vs->value);
324 scmTmp = scm_from_utf8_string( str );
325 break;
326 default:
327 /* FIXME: error */
328 printf( "argument %d not a numeric or string [type = %d]\n",
329 i, vs->type );
330 return NULL;
331 break; /* notreached */
332 }
333 scmArgs = scm_cons( scmTmp, scmArgs );
334 }
335
336 scmTmp = gfec_apply(scmFn, scmArgs, _exception_handler);
337 if (scmTmp == SCM_UNDEFINED)
338 return NULL;
339
340 if (!scm_is_number (scmTmp))
341 {
342 PERR("function gnc:%s does not return a number", fname);
343 return NULL;
344 }
345
346 result = g_new0( gnc_numeric, 1 );
347 *result = double_to_gnc_numeric( scm_to_double(scmTmp),
349 GNC_HOW_DENOM_SIGFIGS(12) | GNC_HOW_RND_ROUND_HALF_UP );
350 if (gnc_numeric_check (*result) != GNC_ERROR_OK)
351 {
352 PERR("Attempt to convert %f to GncNumeric Failed: %s",
353 scm_to_double(scmTmp),
354 gnc_numeric_errorCode_to_string (gnc_numeric_check (*result)));
355 g_free (result);
356 return NULL;
357 }
358 /* FIXME: cleanup scmArgs = scm_list, cons'ed cells? */
359 return (void*)result;
360}
361
362static void *
363trans_numeric(const char *digit_str,
364 gchar *radix_point,
365 gchar *group_char,
366 char **rstr)
367{
368 ParserNum *pnum;
369 gnc_numeric value;
370
371 if (digit_str == NULL)
372 return NULL;
373
374 if (!xaccParseAmount (digit_str, TRUE, &value, rstr))
375 return NULL;
376
377 pnum = g_new0(ParserNum, 1);
378 pnum->value = value;
379
380 return pnum;
381}
382
383static void *
384numeric_ops(char op_sym,
385 void *left_value,
386 void *right_value)
387{
388 ParserNum *left = left_value;
389 ParserNum *right = right_value;
390 ParserNum *result;
391
392 if ((left == NULL) || (right == NULL))
393 return NULL;
394
395 result = (op_sym == ASN_OP) ? left : g_new0(ParserNum, 1);
396
397 switch (op_sym)
398 {
399 case ADD_OP:
400 result->value = gnc_numeric_add (left->value, right->value,
401 GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
402 break;
403 case SUB_OP:
404 result->value = gnc_numeric_sub (left->value, right->value,
405 GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
406 break;
407 case DIV_OP:
408 result->value = gnc_numeric_div (left->value, right->value,
409 GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
410 break;
411 case MUL_OP:
412 result->value = gnc_numeric_mul (left->value, right->value,
413 GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
414 break;
415 case ASN_OP:
416 result->value = right->value;
417 break;
418 }
419
420 return result;
421}
422
423static void *
424negate_numeric(void *value)
425{
426 ParserNum *result = value;
427
428 if (value == NULL)
429 return NULL;
430
431 result->value = gnc_numeric_neg (result->value);
432
433 return result;
434}
435
436static
437void
438gnc_ep_tmpvarhash_check_vals( gpointer key, gpointer value, gpointer user_data )
439{
440 gboolean *allVarsHaveValues = (gboolean*)user_data;
441 gnc_numeric *num = (gnc_numeric*)value;
442 *allVarsHaveValues &= ( num && gnc_numeric_check( *num ) != GNC_ERROR_ARG );
443}
444
445static
446void
447gnc_ep_tmpvarhash_clean( gpointer key, gpointer value, gpointer user_data )
448{
449 if ( key )
450 {
451 g_free( (gchar*)key );
452 }
453 if ( value )
454 {
455 g_free( (gnc_numeric*)value );
456 }
457}
458
459gboolean
460gnc_exp_parser_parse( const char * expression, gnc_numeric *value_p,
461 char **error_loc_p )
462{
463 GHashTable *tmpVarHash;
464 gboolean ret, toRet = TRUE;
465 gboolean allVarsHaveValues = TRUE;
466
467 tmpVarHash = g_hash_table_new( g_str_hash, g_str_equal );
468 ret = gnc_exp_parser_parse_separate_vars( expression, value_p,
469 error_loc_p, tmpVarHash );
470 if ( !ret )
471 {
472 toRet = ret;
473 goto cleanup;
474 }
475
476 g_hash_table_foreach( tmpVarHash,
477 gnc_ep_tmpvarhash_check_vals,
478 &allVarsHaveValues );
479 if ( !allVarsHaveValues )
480 {
481 toRet = FALSE;
482 last_gncp_error = VARIABLE_IN_EXP;
483 }
484
485cleanup:
486 g_hash_table_foreach( tmpVarHash, gnc_ep_tmpvarhash_clean, NULL );
487 g_hash_table_destroy( tmpVarHash );
488
489 return toRet;
490}
491
492gboolean
493gnc_exp_parser_parse_separate_vars (const char * expression,
494 gnc_numeric *value_p,
495 char **error_loc_p,
496 GHashTable *varHash )
497{
498 parser_env_ptr pe;
499 var_store_ptr vars;
500 struct lconv *lc;
501 var_store result;
502 char * error_loc;
503 ParserNum *pnum;
504
505 if (expression == NULL)
506 return FALSE;
507
508 if (!parser_inited)
509 gnc_exp_parser_real_init ( (varHash == NULL) );
510
511 result.variable_name = NULL;
512 result.value = NULL;
513 result.next_var = NULL;
514
515 vars = make_predefined_variables ();
516
517 if ( varHash != NULL )
518 {
519 g_hash_table_foreach( varHash, make_predefined_vars_from_external_helper, &vars);
520 }
521
522 lc = gnc_localeconv ();
523
524 pe = init_parser (vars, lc->mon_decimal_point, lc->mon_thousands_sep,
525 trans_numeric, numeric_ops, negate_numeric, g_free,
526 func_op);
527
528 error_loc = parse_string (&result, expression, pe);
529
530 pnum = result.value;
531
532 if (error_loc == NULL)
533 {
534 if (gnc_numeric_check (pnum->value))
535 {
536 if (error_loc_p != NULL)
537 *error_loc_p = (char *) expression;
538
539 last_error = NUMERIC_ERROR;
540 }
541 else
542 {
543 if (pnum)
544 {
545 if (value_p)
546 *value_p = gnc_numeric_reduce (pnum->value);
547
548 if (!result.variable_name)
549 g_free (pnum);
550 }
551
552 if (error_loc_p != NULL)
553 *error_loc_p = NULL;
554
555 last_error = PARSER_NO_ERROR;
556 }
557 }
558 else
559 {
560 if (error_loc_p != NULL)
561 *error_loc_p = error_loc;
562
563 last_error = get_parse_error (pe);
564 }
565
566 if ( varHash != NULL )
567 {
568 var_store_ptr newVars;
569 gpointer maybeKey, maybeValue;
570 gnc_numeric *numericValue;
571
572 newVars = parser_get_vars( pe );
573 for ( ; newVars ; newVars = newVars->next_var )
574 {
575 if ( g_hash_table_lookup_extended( varHash, newVars->variable_name,
576 &maybeKey, &maybeValue ) )
577 {
578 g_hash_table_remove( varHash, maybeKey );
579 g_free( maybeKey );
580 g_free( maybeValue );
581 }
582 numericValue = g_new0( gnc_numeric, 1 );
583 *numericValue = ((ParserNum*)newVars->value)->value;
584 // WTF?
585 // numericValue = NULL;
586 g_hash_table_insert( varHash,
587 g_strdup(newVars->variable_name),
588 numericValue );
589 }
590 }
591 else
592 {
593 update_variables (vars);
594 }
595
596 free_predefined_variables (vars);
597
598 exit_parser (pe);
599
600 return last_error == PARSER_NO_ERROR;
601}
602
603const char *
604gnc_exp_parser_error_string (void)
605{
606 if ( last_error == PARSER_NO_ERROR )
607 {
608 switch ( last_gncp_error )
609 {
610 default:
611 case NO_ERR:
612 return NULL;
613 break;
614 case VARIABLE_IN_EXP:
615 return _("Illegal variable in expression." );
616 break;
617 }
618 }
619
620 switch (last_error)
621 {
622 default:
623 case PARSER_NO_ERROR:
624 return NULL;
625 case UNBALANCED_PARENS:
626 return _("Unbalanced parenthesis");
627 case STACK_OVERFLOW:
628 return _("Stack overflow");
629 case STACK_UNDERFLOW:
630 return _("Stack underflow");
631 case UNDEFINED_CHARACTER:
632 return _("Undefined character");
633 case NOT_A_VARIABLE:
634 return _("Not a variable");
635 case NOT_A_FUNC:
636 return _("Not a defined function");
637 case PARSER_OUT_OF_MEMORY:
638 return _("Out of memory");
639 case NUMERIC_ERROR:
640 return _("Numeric error");
641 }
642}
File path resolution utility functions.
GKeyFile helper routines.
utility functions for the GnuCash UI
gboolean gnc_key_file_save_to_file(const gchar *filename, GKeyFile *key_file, GError **error)
Write a key/value file from memory to disk.
GKeyFile * gnc_key_file_load_from_file(const gchar *filename, gboolean ignore_error, gboolean return_empty_struct, GError **caller_error)
Open and read a key/value file from disk into memory.
gboolean xaccParseAmount(const char *in_str, gboolean monetary, gnc_numeric *result, char **endstr)
Parses in_str to obtain a numeric result.
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
const char * gnc_numeric_errorCode_to_string(GNCNumericErrorCode error_code)
Returns a string representation of the given GNCNumericErrorCode.
double gnc_numeric_to_double(gnc_numeric in)
Convert numeric to floating-point value.
gnc_numeric gnc_numeric_sub(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Return a-b.
gchar * gnc_numeric_to_string(gnc_numeric n)
Convert to string.
#define GNC_DENOM_AUTO
Values that can be passed as the 'denom' argument.
gnc_numeric double_to_gnc_numeric(double in, gint64 denom, gint how)
Convert a floating-point number to a gnc_numeric.
gnc_numeric gnc_numeric_div(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Division.
gnc_numeric gnc_numeric_from_string(const gchar *str)
Read a gnc_numeric from str, skipping any leading whitespace.
GNCNumericErrorCode gnc_numeric_check(gnc_numeric in)
Check for error signal in value.
gnc_numeric gnc_numeric_mul(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Multiply a times b, returning the product.
gnc_numeric gnc_numeric_neg(gnc_numeric a)
Returns a newly created gnc_numeric that is the negative of the given gnc_numeric value.
#define GNC_HOW_DENOM_SIGFIGS(n)
Build a 'how' value that will generate a denominator that will keep at least n significant figures in...
gnc_numeric gnc_numeric_reduce(gnc_numeric in)
Return input after reducing it by Greater Common Factor (GCF) elimination.
gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Return a+b.
@ GNC_ERROR_OK
No error.
@ GNC_ERROR_ARG
Argument is not a valid number.
Data Types.