GnuCash  5.6-150-g038405b370+
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 
43 static QofLogModule log_module = GNC_MOD_GUI;
44 
47 typedef struct ParserNum
48 {
49  gnc_numeric value;
50 } ParserNum;
51 
52 
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 
62 static gchar *
63 gnc_exp_parser_filname (void)
64 {
65  return gnc_build_userdata_path("expressions-2.0");
66 }
67 
68 void
69 gnc_exp_parser_init ( void )
70 {
71  gnc_exp_parser_real_init( TRUE );
72 }
73 
74 void
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  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 
114 static gboolean
115 remove_binding (gpointer key, gpointer value, gpointer not_used)
116 {
117  g_free(key);
118  g_free(value);
119 
120  return TRUE;
121 }
122 
123 static void
124 set_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 
135 void
136 gnc_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 
166 void
167 gnc_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 
187 void
188 gnc_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 
209 static void
210 make_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 
229 static void
230 make_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 
240 static var_store_ptr
241 make_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 
250 static void
251 free_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 
271 static void
272 update_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 
282 static void
283 _exception_handler(const char *error_message)
284 {
285  PERR("function eval error: [%s]\n", error_message);
286 }
287 
288 static
289 void*
290 func_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),
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),
355  g_free (result);
356  return NULL;
357  }
358  /* FIXME: cleanup scmArgs = scm_list, cons'ed cells? */
359  return (void*)result;
360 }
361 
362 static void *
363 trans_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 
383 static void *
384 numeric_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,
402  break;
403  case SUB_OP:
404  result->value = gnc_numeric_sub (left->value, right->value,
406  break;
407  case DIV_OP:
408  result->value = gnc_numeric_div (left->value, right->value,
410  break;
411  case MUL_OP:
412  result->value = gnc_numeric_mul (left->value, right->value,
414  break;
415  case ASN_OP:
416  result->value = right->value;
417  break;
418  }
419 
420  return result;
421 }
422 
423 static void *
424 negate_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 
436 static
437 void
438 gnc_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 
445 static
446 void
447 gnc_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 
459 gboolean
460 gnc_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 
485 cleanup:
486  g_hash_table_foreach( tmpVarHash, gnc_ep_tmpvarhash_clean, NULL );
487  g_hash_table_destroy( tmpVarHash );
488 
489  return toRet;
490 }
491 
492 gboolean
493 gnc_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 
603 const char *
604 gnc_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 }
gboolean xaccParseAmount(const char *in_str, gboolean monetary, gnc_numeric *result, char **endstr)
Parses in_str to obtain a numeric result.
gchar * gnc_build_userdata_path(const gchar *filename)
Make a path to filename in the user&#39;s gnucash data directory.
gnc_numeric double_to_gnc_numeric(double n, gint64 denom, gint how)
Convert a floating-point number to a gnc_numeric.
utility functions for the GnuCash UI
gnc_numeric gnc_numeric_neg(gnc_numeric a)
Returns a newly created gnc_numeric that is the negative of the given gnc_numeric value...
GKeyFile helper routines.
gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Return a+b.
Use any denominator which gives an exactly correct ratio of numerator to denominator.
Definition: gnc-numeric.h:188
gchar * gnc_numeric_to_string(gnc_numeric n)
Convert to string.
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
gnc_numeric gnc_numeric_reduce(gnc_numeric n)
Return input after reducing it by Greater Common Factor (GCF) elimination.
gboolean gnc_key_file_save_to_file(const gchar *filename, GKeyFile *key_file, GError **error)
Write a key/value file from memory to disk.
gdouble gnc_numeric_to_double(gnc_numeric n)
Convert numeric to floating-point value.
gnc_numeric gnc_numeric_mul(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Multiply a times b, returning the product.
const char * gnc_numeric_errorCode_to_string(GNCNumericErrorCode error_code)
Returns a string representation of the given GNCNumericErrorCode.
Argument is not a valid number.
Definition: gnc-numeric.h:224
gnc_numeric gnc_numeric_div(gnc_numeric x, gnc_numeric y, gint64 denom, gint how)
Division.
gnc_numeric gnc_numeric_sub(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Return a-b.
gnc_numeric gnc_numeric_from_string(const gchar *str)
Read a gnc_numeric from str, skipping any leading whitespace.
Round to the nearest integer, rounding away from zero when there are two equidistant nearest integers...
Definition: gnc-numeric.h:165
GNCNumericErrorCode gnc_numeric_check(gnc_numeric a)
Check for error signal in value.
File path resolution utility functions.
No error.
Definition: gnc-numeric.h:223
#define GNC_DENOM_AUTO
Values that can be passed as the &#39;denom&#39; argument.
Definition: gnc-numeric.h:245
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.
Data Types.
#define GNC_HOW_DENOM_SIGFIGS(n)
Build a &#39;how&#39; value that will generate a denominator that will keep at least n significant figures in...
Definition: gnc-numeric.h:217