/* GENIUS Calculator * Copyright (C) 1997-2007 Jiri (George) Lebl * * Author: Jiri (George) Lebl * * This file is part of Genius. * * Genius is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "eval.h" #include "dict.h" #include "util.h" #include "funclib.h" #include "compil.h" /* Note: this won't be completely mem-debug friendly, but only mostly */ /* #define MEM_DEBUG_FRIENDLY 1 */ /*the context stack structure*/ typedef struct _GelDictContext { GSList *stack; GSList *subststack; GSList *stackname; int top; } GelDictContext; static GelDictContext context = {NULL, NULL, NULL, -1}; static GHashTable *dictionary; extern GHashTable *uncompiled; extern const char *genius_toplevels[]; extern const char *genius_operators[]; GelEFunc *free_funcs = NULL; #define GET_NEW_FUNC(n) \ if (free_funcs == NULL) { \ n = g_new (GelEFunc, 1); \ } else { \ n = free_funcs; \ free_funcs = free_funcs->data.next; \ } /*return current context number (0 is global, -1 is uninitialized)*/ int d_curcontext (void) { return context.top; } /*make builtin function and return it*/ GelEFunc * d_makebifunc (GelToken *id, GelBIFunction f, int nargs) { GelEFunc *n; GET_NEW_FUNC (n); memset (n, 0, sizeof (GelEFunc)); n->id = id; n->data.func = f; n->nargs = nargs; n->context = context.top; n->type = GEL_BUILTIN_FUNC; /* n->vararg = 0; n->on_subst_list = 0; n->named_args = NULL; n->extra_dict = NULL; n->no_mod_all_args = FALSE; n->propagate_mod = FALSE; */ return n; } /*make a user function and return it*/ GelEFunc * d_makeufunc (GelToken *id, GelETree *value, GSList *argnames, int nargs, const GSList *extra_dict) { GelEFunc *n; GET_NEW_FUNC (n); memset (n, 0, sizeof (GelEFunc)); n->id = id; n->data.user = value; n->nargs = nargs; n->named_args = argnames; n->context = context.top; n->type = GEL_USER_FUNC; /* look at the memset n->vararg = 0; n->on_subst_list = 0; n->no_mod_all_args = FALSE; n->propagate_mod = FALSE; n->extra_dict = NULL; */ if (extra_dict != NULL) { GSList *li; n->extra_dict = g_slist_copy ((GSList *)extra_dict); for (li = n->extra_dict; li != NULL; li = li->next) li->data = d_copyfunc (li->data); } return n; } /*make a variable function and return it*/ GelEFunc * d_makevfunc(GelToken *id, GelETree *value) { GelEFunc *n; GET_NEW_FUNC (n); memset (n, 0, sizeof (GelEFunc)); n->id = id; n->data.user = value; n->context = context.top; n->type = GEL_VARIABLE_FUNC; /* look at the memset n->nargs = 0; n->vararg = 0; n->on_subst_list = 0; n->named_args = NULL; n->extra_dict = NULL; n->no_mod_all_args = FALSE; n->propagate_mod = FALSE; */ return n; } /*make a user function and return it*/ GelEFunc * d_makereffunc(GelToken *id, GelEFunc *ref) { GelEFunc *n; GET_NEW_FUNC (n); memset (n, 0, sizeof (GelEFunc)); n->id = id; n->data.ref = ref; n->context = context.top; n->type = GEL_REFERENCE_FUNC; /* look at the memset n->nargs = 0; n->vararg = 0; n->on_subst_list = 0; n->named_args = NULL; n->extra_dict = NULL; n->no_mod_all_args = FALSE; n->propagate_mod = FALSE; */ return n; } /*copy a function*/ GelEFunc * d_copyfunc(GelEFunc *o) { GelEFunc *n; GSList *li; GET_NEW_FUNC (n); memcpy (n, o, sizeof (GelEFunc)); if(n->type == GEL_USER_FUNC || n->type == GEL_VARIABLE_FUNC) { D_ENSURE_USER_BODY (o); n->data.user=copynode(o->data.user); } n->named_args = g_slist_copy (o->named_args); n->extra_dict = g_slist_copy (o->extra_dict); for (li = n->extra_dict; li != NULL; li = li->next) li->data = d_copyfunc (li->data); if (n->on_subst_list) { n->on_subst_list = 0; d_put_on_subst_list (n); } return n; } /*make a real function from a fake*/ GelEFunc * d_makerealfunc(GelEFunc *o,GelToken *id, gboolean use) { GelEFunc *n; GET_NEW_FUNC (n); memcpy (n, o, sizeof (GelEFunc)); n->id = id; if (o->symbolic_id == NULL) n->symbolic_id = o->id; else n->symbolic_id = o->symbolic_id; n->context = context.top; if(n->type == GEL_USER_FUNC || n->type == GEL_VARIABLE_FUNC) { D_ENSURE_USER_BODY (o); if(use) { n->data.user = o->data.user; o->data.user = NULL; } else n->data.user = copynode(o->data.user); } if(use) { o->named_args = NULL; o->named_args = 0; } else n->named_args = g_slist_copy(o->named_args); if (use) { o->extra_dict = NULL; } else { GSList *li; n->extra_dict = g_slist_copy (o->extra_dict); for (li = n->extra_dict; li != NULL; li = li->next) li->data = d_copyfunc (li->data); } if (n->on_subst_list) { n->on_subst_list = 0; d_put_on_subst_list (n); } return n; } /*make real func and replace n with it, without changing n's context or id*/ /*if use is set, we USE the original function, NULLing approriately*/ void d_setrealfunc(GelEFunc *n,GelEFunc *fake, gboolean use) { if(n->type == GEL_USER_FUNC || n->type == GEL_VARIABLE_FUNC) gel_freetree(n->data.user); n->type = fake->type; n->data = fake->data; n->symbolic_id = fake->symbolic_id; if (fake->symbolic_id == NULL) n->symbolic_id = fake->id; if(n->type == GEL_USER_FUNC || n->type == GEL_VARIABLE_FUNC) { D_ENSURE_USER_BODY (fake); if(use) { n->data.user = fake->data.user; fake->data.user = NULL; } else n->data.user = copynode(fake->data.user); } if(use) { n->named_args = fake->named_args; n->nargs = fake->nargs; fake->named_args = NULL; fake->nargs = 0; } else { n->named_args = g_slist_copy(fake->named_args); n->nargs = fake->nargs; } if (use) { n->extra_dict = fake->extra_dict; fake->extra_dict = NULL; } else { GSList *li; n->extra_dict = g_slist_copy (fake->extra_dict); for (li = n->extra_dict; li != NULL; li = li->next) li->data = d_copyfunc (li->data); } if (fake->on_subst_list) { d_put_on_subst_list (n); } } void d_initcontext(void) { GelToken *tok; context.top = 0; /*0 means that element 0 exists!*/ /*add an empty dictionary*/ context.stack = g_slist_prepend (NULL,NULL); context.subststack = g_slist_prepend (NULL,NULL); context.stackname = g_slist_prepend (NULL,NULL); dictionary = g_hash_table_new (g_str_hash, g_str_equal); /*add Ans and ans as the same token*/ tok = g_new0 (GelToken, 1); tok->token = g_strdup ("Ans"); g_hash_table_insert (dictionary, tok->token, tok); g_hash_table_insert (dictionary, g_strdup("ans"), tok); /*this is where the built in functions register into the global dictionary*/ gel_funclib_addall (); } /*compare two GelEFunc's by their context numbers*/ /*static int compare_func_bycontext(gconstpointer p1, gconstpointer p2) { GelEFunc *func1 = (GelEFunc *)p1; GelEFunc *func2 = (GelEFunc *)p2; return func1->context < func2->context; }*/ /*add a function struct to the dict (in current context)*/ GelEFunc * d_addfunc (GelEFunc *func) { GelEFunc *n; g_return_val_if_fail (func->context == context.top, func); /*we already found it (in current context)*/ n = d_lookup_local(func->id); if(n) { d_replacefunc(n,func); return n; } context.stack->data = g_slist_prepend(context.stack->data,func); func->id->refs = g_slist_prepend(func->id->refs,func); func->id->curref = func; return func; } /*add a function struct to the dict (in global context)*/ GelEFunc * d_addfunc_global (GelEFunc *func) { GelEFunc *n; GSList *last; g_return_val_if_fail (func->context == 0, func); /* get the function in the lowest context */ last = g_slist_last (func->id->refs); n = last != NULL ? last->data : NULL; /* if this function is global */ if (n != NULL && n->context == 0) { d_replacefunc (n, func); return n; } last = g_slist_last (context.stack); g_assert (last != NULL); last->data = g_slist_prepend (last->data, func); func->id->refs = g_slist_append (func->id->refs, func); if (func->id->curref == NULL) func->id->curref = func; return func; } /*set value of an existing function (in local context), used for arguments WARNING, does not free the memory allocated by previous value!*/ gboolean d_setvalue (GelToken *id, GelETree *value) { GelEFunc *f; f=d_lookup_local(id); if(!f || (f->type!=GEL_USER_FUNC && f->type!=GEL_VARIABLE_FUNC)) return FALSE; f->data.user=value; return TRUE; } /*dictionary functions*/ /*lookup a function in the dictionary in the current context*/ GelEFunc * d_lookup_local(GelToken *id) { GelEFunc *func; if(!id || !id->refs) return NULL; /*the first one must be the lowest context*/ func = id->refs->data; /*not in currect context and we only want the currect context ones*/ if(func->contextrefs) return NULL; /*the first one must be the lowest context*/ func = id->refs->data; if(func->contextrefs->next) return NULL; return id->refs->next->data; } /*lookup a function in the dictionary but only in the toplevel context */ GelEFunc * d_lookup_only_global (GelToken *id) { GSList *li; GelEFunc *func; if(!id || !id->refs) return NULL; li = id->refs; while (li->next != NULL) li = li->next; /* this must be our function */ func = li->data; if(func->context == 0) return func; else return NULL; } GelToken * d_intern (const char *id) { GelToken * tok; if (id == NULL) return NULL; tok = g_hash_table_lookup (dictionary, id); if (tok == NULL) { tok = g_new0 (GelToken, 1); tok->token = g_strdup (id); g_hash_table_insert (dictionary, tok->token, tok); } return tok; } gboolean d_delete(GelToken *id) { /*FIXME: Delete function!*/ return FALSE; } /*clear all context dictionaries and pop out all the contexts except the global one also init the context stack if it hasn't been done*/ void d_singlecontext(void) { if(context.stack==NULL) d_initcontext(); else while(context.top>0) d_popcontext (); } /*free all memory allocated by a dictionary*/ void d_freedict (GSList *n) { GSList *li; for (li = n; li != NULL; li = li->next) { d_freefunc (li->data); li->data = NULL; } g_slist_free (n); } static void whack_from_lists (GelEFunc *func) { GSList *li; for (li = context.subststack; li != NULL; li = li->next) { GSList *fl = g_slist_find (li->data, func); if (fl != NULL) { li->data = g_slist_delete_link (li->data, fl); return; } } } /* Put on subst local var list for this current stack */ void d_put_on_subst_list (GelEFunc *func) { if (func->on_subst_list) { /* On a lower stackframe? */ /* weird but true. So whack it and put it here, * it will get to the lower one eventually */ whack_from_lists (func); } context.subststack->data = g_slist_prepend (context.subststack->data, func); func->on_subst_list = 1; } void d_freefunc (GelEFunc *n) { GSList *li; if(!n) return; /* g_assert(!n->id || g_slist_find(n->id->refs,n)==NULL); */ if (n->on_subst_list) { whack_from_lists (n); n->on_subst_list = 0; } /*if(n->id) { n->id->refs = g_slist_remove(n->id->refs,n); n->id->curref = n->id->refs?n->id->refs->data:NULL; } */ if((n->type == GEL_USER_FUNC || n->type == GEL_VARIABLE_FUNC) && n->data.user) gel_freetree(n->data.user); g_slist_free(n->named_args); for (li = n->extra_dict; li != NULL; li = li->next) { d_freefunc (li->data); li->data = NULL; } g_slist_free (n->extra_dict); #ifndef MEM_DEBUG_FRIENDLY /*prepend to free list*/ n->data.next = free_funcs; free_funcs = n; #else g_free (n); #endif } /*replace old with stuff from new and free new, new has to have the same id, also new->id should not hold a reference to new*/ void d_replacefunc(GelEFunc *old,GelEFunc *_new) { GSList *li; /* assert some things we don't deal with. Should we? */ g_assert ( ! _new->on_subst_list); g_assert ( ! old->on_subst_list); g_return_if_fail(old && _new); g_return_if_fail(old->id == _new->id); if(old->type == GEL_USER_FUNC || old->type == GEL_VARIABLE_FUNC) gel_freetree(old->data.user); g_slist_free(old->named_args); for (li = old->extra_dict; li != NULL; li = li->next) { d_freefunc (li->data); li->data = NULL; } g_slist_free (old->extra_dict); memcpy(old,_new,sizeof(GelEFunc)); #ifndef MEM_DEBUG_FRIENDLY /*prepend to free list*/ _new->data.next = free_funcs; free_funcs = _new; #else g_free (_new); #endif } /*set_ref*/ void d_set_ref(GelEFunc *n,GelEFunc *ref) { if(!n || !ref) return; if(n->type == GEL_USER_FUNC || n->type == GEL_VARIABLE_FUNC) gel_freetree(n->data.user); n->type = GEL_REFERENCE_FUNC; g_slist_free(n->named_args); n->nargs = 0; n->named_args = NULL; n->data.ref = ref; } /*set_value*/ void d_set_value(GelEFunc *n,GelETree *value) { if(!n || !value) return; if(n->type == GEL_USER_FUNC || n->type == GEL_VARIABLE_FUNC) gel_freetree(n->data.user); n->type = GEL_VARIABLE_FUNC; g_slist_free(n->named_args); n->nargs = 0; n->named_args = NULL; n->data.user = value; } /*push a new dictionary onto the context stack*/ gboolean d_addcontext(void) { context.stack = g_slist_prepend (context.stack, NULL); context.subststack = g_slist_prepend (context.subststack, NULL); context.stackname = g_slist_prepend (context.stackname, NULL); context.top++; return TRUE; } gboolean d_addcontext_named (GelToken *name) { context.stack = g_slist_prepend (context.stack, NULL); context.subststack = g_slist_prepend (context.subststack, NULL); context.stackname = g_slist_prepend (context.stackname, name); context.top++; return TRUE; } /*pop the context stack*/ void d_popcontext(void) { if (context.top != -1) { GSList *li; GSList *dict = context.stack->data; GSList *subst = context.subststack->data; GSList *substlast = NULL; if (context.top != 0) { for (li = subst; li != NULL; li = li->next) { GelEFunc *func = li->data; if (context.top == 1) func->on_subst_list = 0; if (func->type == GEL_USER_FUNC || func->type == GEL_VARIABLE_FUNC) { D_ENSURE_USER_BODY (func); func->extra_dict = gel_subst_local_vars (func->extra_dict, func->data.user); } substlast = li; } } for (li = dict; li != NULL; li = li->next) { GelEFunc *func = li->data; func->id->refs = g_slist_remove(func->id->refs,func); func->id->curref = func->id->refs?func->id->refs->data:NULL; } context.top--; context.stack = g_slist_delete_link (context.stack, context.stack); context.subststack = g_slist_delete_link (context.subststack, context.subststack); context.stackname = g_slist_delete_link (context.stackname, context.stackname); /* substitute lower variables unless we are on the toplevel */ if (substlast != NULL && context.top > 0) { substlast->next = context.subststack->data; context.subststack->data = subst; subst = NULL; } d_freedict (dict); g_slist_free (subst); } } /*gimme the current dictinary*/ GSList * d_getcontext (void) { if (context.top == -1) return NULL; else return context.stack->data; } GSList * d_get_all_contexts (void) { if (context.top == -1) return NULL; else return context.stack; } GSList * d_get_context_names (void) { if (context.top == -1) return NULL; else return context.stackname; } /*gimme the current global dictinary*/ GSList * d_getcontext_global (void) { if (context.top == -1) { return NULL; } else { GSList *last; last = g_slist_last (context.stack); g_assert (last != NULL); return last->data; } } static int lowercase_ascii_sum (const char *id) { int sum = 0; int i; for (i = 0; id[i] != '\0'; i++) { sum += g_ascii_tolower (id[i]) - 'a'; } return sum; } static int lowercase_kronecker_difference (const char *id1, const char *id2) { int sum = 0; int i; for (i = 0; id1[i] != '\0' && id2[i] != '\0'; i++) { if (g_ascii_tolower (id1[i]) != g_ascii_tolower (id2[i])) sum += 1; } /* plus the ends */ sum += strlen (&id1[i]); sum += strlen (&id2[i]); return sum; } static char * construct_deleted (const char *id, int d, int len) { int i, y; char *out = g_new0 (char, len); y = 0; for (i = 0; i < len; i++) { if (i != d) out[y++] = id[i]; } return out; } static gboolean try_deletions (const char *id1, int len1, const char *id2) { int i; for (i = 0; i < len1; i++) { char *d = construct_deleted (id1, i, len1); int allowed = len1 * 0.2; if (lowercase_kronecker_difference (id2, d) <= allowed) { g_free (d); return TRUE; } g_free (d); } return FALSE; } static gboolean are_ids_similar (const char *id1, const char *id2) { int len1 = strlen (id1), len2 = strlen (id2); int dif1, dif2, dif3; int allowed; if (ABS (len1 - len2) > 1) return FALSE; if (g_ascii_strcasecmp (id1, id2) == 0) return TRUE; if (len1 > 6 && len1 == len2) { int sum1, sum2; sum1 = lowercase_ascii_sum (id1); sum2 = lowercase_ascii_sum (id2); /* just a reordering (possibly) (won't work right on small words) */ if (sum1 == sum2) { return TRUE; } } /* simple cases for length 2 and 3 */ if (len1 == 2 && len2 == len1 && id1[0] == id2[1] && id1[1] == id2[0]) { return TRUE; } if (len1 == 3 && len2 == len1 && ( (id1[0] == id2[1] && id1[1] == id2[0] && id1[2] == id2[2]) || (id1[0] == id2[0] && id1[1] == id2[2] && id1[2] == id2[1]) )) { return TRUE; } if (len1 <= 2 && len2 <= 2) { return FALSE; } if (try_deletions (id1, len1, id2)) return TRUE; if (try_deletions (id2, len2, id1)) return TRUE; dif1 = lowercase_kronecker_difference (id1, id2); dif2 = lowercase_kronecker_difference (id1, &id2[1]); dif3 = lowercase_kronecker_difference (&id1[1], id2); allowed = 0.2 * len1; if (dif1 > allowed+1 && dif2 > allowed && dif3 > allowed) return FALSE; return TRUE; } GSList * d_find_similar_globals (const char *id) { GSList *ret = NULL; GSList *li; int i; for (li = d_getcontext_global (); li != NULL; li = li->next) { GelEFunc *n = li->data; if (n->id != NULL && n->id->token != NULL && are_ids_similar (n->id->token, id)) { ret = g_slist_prepend (ret, g_strdup (n->id->token)); } } for (i = 0; genius_toplevels[i] != NULL; i++) { if (are_ids_similar (genius_toplevels[i], id)) { ret = g_slist_prepend (ret, g_strdup (genius_toplevels[i])); } } for (i = 0; genius_operators[i] != NULL; i++) { if (are_ids_similar (genius_operators[i], id)) { ret = g_slist_prepend (ret, g_strdup (genius_operators[i])); } } return ret; } void d_protect_all(void) { GSList *funcs; GSList *li; funcs = d_getcontext(); if(!funcs) return; for(li=funcs;li;li=g_slist_next(li)) { GelEFunc *func = li->data; if(!func->id || strcmp(func->id->token,"Ans")==0) continue; if ( ! func->id->parameter) func->id->protected_ = 1; } } void d_add_named_args (GelEFunc *f, const char *args) { int i; char **s; if (args == NULL || args[0] == '\0') return; s = g_strsplit (args, ",", -1); for (i = 0; s[i] != NULL; i++) { f->named_args = g_slist_append (f->named_args, d_intern (s[i])); } g_strfreev (s); }