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