/* * func.c: Function management and utility routines. * * Author: * Miguel de Icaza (miguel@gnu.org) * Michael Meeks (mmeeks@gnu.org) * Morten Welinder (terra@gnome.org) * Jody Goldberg (jody@gnome.org) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum { PROP_0, PROP_NAME, PROP_TRANSLATION_DOMAIN, PROP_IN_USE, }; enum { SIG_LOAD_STUB, SIG_LINK_DEP, SIG_DERIVATIVE, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static GList *categories; static GnmFuncGroup *unknown_cat; static GHashTable *functions_by_name; static GHashTable *functions_by_localized_name; static GnmFunc *fn_if; /** * gnm_func_init_: (skip) */ void gnm_func_init_ (void) { functions_by_name = g_hash_table_new (go_ascii_strcase_hash, go_ascii_strcase_equal); /* FIXME: ascii??? */ functions_by_localized_name = g_hash_table_new (go_ascii_strcase_hash, go_ascii_strcase_equal); gnm_func_builtin_init (); fn_if = gnm_func_lookup ("if", NULL); } /** * gnm_func_shutdown_: (skip) */ void gnm_func_shutdown_ (void) { fn_if = NULL; while (unknown_cat != NULL && unknown_cat->functions != NULL) { GnmFunc *func = unknown_cat->functions->data; if (func->usage_count > 0) { g_warning ("Function %s still has %d users.\n", gnm_func_get_name (func, FALSE), func->usage_count); func->usage_count = 0; } g_object_unref (func); } gnm_func_builtin_shutdown (); g_hash_table_destroy (functions_by_name); functions_by_name = NULL; g_hash_table_destroy (functions_by_localized_name); functions_by_localized_name = NULL; } /** * gnm_func_enumerate: * * Return value: (element-type GnmFunc) (transfer container): */ GPtrArray * gnm_func_enumerate (void) { GPtrArray *res = g_ptr_array_new (); GHashTableIter hiter; gpointer value; g_hash_table_iter_init (&hiter, functions_by_name); while (g_hash_table_iter_next (&hiter, NULL, &value)) g_ptr_array_add (res, value); return res; } static GnmValue * error_function_no_full_info (GnmFuncEvalInfo *ei, int argc, GnmExprConstPtr const *argv) { return value_new_error (ei->pos, _("Function implementation not available.")); } static void gnm_func_load_stub (GnmFunc *func) { g_return_if_fail (func->fn_type == GNM_FUNC_TYPE_STUB); g_signal_emit (G_OBJECT (func), signals[SIG_LOAD_STUB], 0); if (func->fn_type == GNM_FUNC_TYPE_STUB) { g_printerr ("Failed to load %s\n", func->name); gnm_func_set_varargs (func, error_function_no_full_info, NULL); gnm_func_set_help (func, NULL, 0); } } inline void gnm_func_load_if_stub (GnmFunc *func) { if (func->fn_type == GNM_FUNC_TYPE_STUB) gnm_func_load_stub (func); } static char * split_at_colon (char const *s, char **rest) { char *dup = g_strdup (s); char *colon = strchr (dup, ':'); if (colon) { *colon = 0; if (rest) *rest = colon + 1; } else { if (rest) *rest = NULL; } return dup; } /* ------------------------------------------------------------------------- */ static void gnm_func_group_unref (GnmFuncGroup *fn_group) { g_return_if_fail (fn_group != NULL); g_return_if_fail (fn_group->functions == NULL); if (fn_group->ref_count-- > 1) return; go_string_unref (fn_group->internal_name); go_string_unref (fn_group->display_name); g_free (fn_group); } static GnmFuncGroup * gnm_func_group_ref (GnmFuncGroup *fn_group) { fn_group->ref_count++; return fn_group; } GType gnm_func_group_get_type (void) { static GType t = 0; if (t == 0) { t = g_boxed_type_register_static ("GnmFuncGroup", (GBoxedCopyFunc)gnm_func_group_ref, (GBoxedFreeFunc)gnm_func_group_unref); } return t; } static gint function_category_compare (gconstpointer a, gconstpointer b) { GnmFuncGroup const *cat_a = a; GnmFuncGroup const *cat_b = b; return go_string_cmp (cat_a->display_name, cat_b->display_name); } GnmFuncGroup * gnm_func_group_fetch (char const *name, char const *translation) { GnmFuncGroup *cat = NULL; GList *l; g_return_val_if_fail (name != NULL, NULL); for (l = categories; l != NULL; l = l->next) { cat = l->data; if (strcmp (cat->internal_name->str, name) == 0) { break; } } if (l == NULL) { cat = g_new (GnmFuncGroup, 1); cat->internal_name = go_string_new (name); cat->ref_count = 1; if (translation != NULL) { cat->display_name = go_string_new (translation); cat->has_translation = TRUE; } else { cat->display_name = go_string_new (name); cat->has_translation = FALSE; } cat->functions = NULL; categories = g_list_insert_sorted ( categories, cat, &function_category_compare); } else if (translation != NULL && translation != name && !cat->has_translation) { go_string_unref (cat->display_name); cat->display_name = go_string_new (translation); cat->has_translation = TRUE; categories = g_list_remove_link (categories, l); g_list_free_1 (l); categories = g_list_insert_sorted ( categories, cat, &function_category_compare); } return cat; } GnmFuncGroup * gnm_func_group_get_nth (int n) { return g_list_nth_data (categories, n); } static void gnm_func_group_add_func (GnmFuncGroup *fn_group, GnmFunc *func) { g_return_if_fail (fn_group != NULL); g_return_if_fail (func != NULL); fn_group->functions = g_slist_prepend (fn_group->functions, func); } static void gnm_func_group_remove_func (GnmFuncGroup *fn_group, GnmFunc *func) { g_return_if_fail (fn_group != NULL); g_return_if_fail (func != NULL); fn_group->functions = g_slist_remove (fn_group->functions, func); if (fn_group->functions == NULL) { categories = g_list_remove (categories, fn_group); if (unknown_cat == fn_group) unknown_cat = NULL; gnm_func_group_unref (fn_group); } } /******************************************************************************/ static void gnm_func_create_arg_names (GnmFunc *func) { int i; GPtrArray *ptr; g_return_if_fail (func != NULL); ptr = g_ptr_array_new (); for (i = 0; i < func->help_count; i++) { const char *s; if (func->help[i].type != GNM_FUNC_HELP_ARG) continue; s = gnm_func_gettext (func, func->help[i].text); g_ptr_array_add (ptr, split_at_colon (s, NULL)); } func->arg_names = ptr; } gboolean gnm_func_is_varargs (GnmFunc const *func) { gnm_func_load_if_stub ((GnmFunc *)func); return func->fn_type == GNM_FUNC_TYPE_NODES; } gboolean gnm_func_is_fixargs (GnmFunc const *func) { gnm_func_load_if_stub ((GnmFunc *)func); return func->fn_type == GNM_FUNC_TYPE_ARGS; } void gnm_func_set_stub (GnmFunc *func) { func->fn_type = GNM_FUNC_TYPE_STUB; g_free (func->arg_spec); func->arg_spec = NULL; g_free (func->arg_types); func->arg_types = NULL; func->min_args = func->max_args = 0; func->nodes_func = NULL; func->args_func = NULL; gnm_func_set_help (func, NULL, 0); } /** * gnm_func_set_varargs: (skip) * @func: #GnmFunc * @fn: evaluation function * @spec: (nullable): argument type specification */ void gnm_func_set_varargs (GnmFunc *func, GnmFuncNodes fn, const char *spec) { g_return_if_fail (GNM_IS_FUNC (func)); g_return_if_fail (fn != NULL); gnm_func_set_stub (func); // Clear out stuff func->fn_type = GNM_FUNC_TYPE_NODES; func->nodes_func = fn; func->arg_spec = g_strdup (spec); func->min_args = 0; func->max_args = G_MAXINT; if (spec) { const char *p = strchr (spec, '|'); const char *q = strchr (spec, '.'); // "..." if (p) func->min_args = p - spec; if (!q) func->min_args = strlen (spec) - (p != NULL); } } /** * gnm_func_set_fixargs: (skip) * @func: #GnmFunc * @fn: evaluation function * @spec: argument type specification */ void gnm_func_set_fixargs (GnmFunc *func, GnmFuncArgs fn, const char *spec) { char *p; g_return_if_fail (GNM_IS_FUNC (func)); g_return_if_fail (fn != NULL); g_return_if_fail (spec != NULL); gnm_func_set_stub (func); // Clear out stuff func->fn_type = GNM_FUNC_TYPE_ARGS; func->args_func = fn; func->arg_spec = g_strdup (spec); func->arg_types = g_strdup (func->arg_spec); p = strchr (func->arg_types, '|'); if (p) { func->min_args = p - func->arg_types; memmove (p, p + 1, strlen (p)); } else func->min_args = strlen (func->arg_types); func->max_args = strlen (func->arg_types); } /** * gnm_func_get_help: * @func: #GnmFunc * @n: (out) (optional): number of help items, not counting the end item * * Returns: (transfer none) (array length=n) (nullable): @func's help items. */ GnmFuncHelp const * gnm_func_get_help (GnmFunc const *func, int *n) { if (n) *n = 0; g_return_val_if_fail (GNM_IS_FUNC (func), NULL); g_return_val_if_fail (func->help, NULL); if (n) *n = func->help_count; return func->help; } void gnm_func_set_help (GnmFunc *func, GnmFuncHelp const *help, int n) { g_return_if_fail (GNM_IS_FUNC (func)); g_return_if_fail (n <= 0 || help != NULL); if (n < 0) { for (n = 0; help && help[n].type != GNM_FUNC_HELP_END; ) n++; } if (func->help) { int i; for (i = 0; i <= func->help_count; i++) g_free ((char *)(func->help[i].text)); g_free (func->help); func->help = NULL; } if (func->arg_names) { g_ptr_array_foreach (func->arg_names, (GFunc)g_free, NULL); g_ptr_array_free (func->arg_names, TRUE); func->arg_names = NULL; } if (help) { int i; func->help = g_new (GnmFuncHelp, n + 1); for (i = 0; i < n; i++) { func->help[i].type = help[i].type; func->help[i].text = g_strdup (help[i].text); } func->help[n].type = GNM_FUNC_HELP_END; func->help[n].text = NULL; func->help_count = n; gnm_func_create_arg_names (func); } else { func->help_count = 0; } } static void gnm_func_set_localized_name (GnmFunc *fd, const char *lname) { gboolean in_hashes = !(fd->flags & GNM_FUNC_IS_WORKBOOK_LOCAL); if (g_strcmp0 (fd->localized_name, lname) == 0) return; if (in_hashes && fd->localized_name) g_hash_table_remove (functions_by_localized_name, fd->localized_name); g_free (fd->localized_name); fd->localized_name = g_strdup (lname); if (in_hashes && lname) g_hash_table_insert (functions_by_localized_name, fd->localized_name, fd); } /** * gnm_func_inc_usage: * @func: #GnmFunc * * This function increments the usage count of @func. A non-zero usage count * prevents the unloading of the function. * * Returns: (transfer full): a new reference to @func. */ GnmFunc * gnm_func_inc_usage (GnmFunc *func) { g_return_val_if_fail (func != NULL, NULL); func->usage_count++; if (func->usage_count == 1) g_object_notify (G_OBJECT (func), "in-use"); return func; } /** * gnm_func_dec_usage: * @func: (transfer full): #GnmFunc * * This function decrements the usage count of @func. When the usage count * reaches zero, the function may be unloaded, for example by unloading the * plugin that defines it. */ void gnm_func_dec_usage (GnmFunc *func) { g_return_if_fail (func != NULL); g_return_if_fail (func->usage_count > 0); func->usage_count--; if (func->usage_count == 0) g_object_notify (G_OBJECT (func), "in-use"); } gboolean gnm_func_get_in_use (GnmFunc *func) { g_return_val_if_fail (func != NULL, FALSE); return func->usage_count > 0; } /** * gnm_func_lookup: * @name: name of function * @scope: (nullable): scope of function, %NULL for global * * Returns: (nullable) (transfer none): the function of that name. */ GnmFunc * gnm_func_lookup (char const *name, Workbook *scope) { GnmFunc *fd = g_hash_table_lookup (functions_by_name, name); if (fd != NULL) return fd; if (scope == NULL || scope->sheet_local_functions == NULL) return NULL; return g_hash_table_lookup (scope->sheet_local_functions, (gpointer)name); } /** * gnm_func_lookup_localized: * @name: localized name of function * @scope: (nullable): scope of function, %NULL for global * * Returns: (nullable) (transfer none): the function of that name. */ GnmFunc * gnm_func_lookup_localized (char const *name, Workbook *scope) { GnmFunc *fd; GHashTableIter hiter; gpointer value; /* Must localize all function names. */ g_hash_table_iter_init (&hiter, functions_by_name); while (g_hash_table_iter_next (&hiter, NULL, &value)) { GnmFunc *fd = value; (void)gnm_func_get_name (fd, TRUE); } fd = g_hash_table_lookup (functions_by_localized_name, name); if (fd != NULL) return fd; if (scope == NULL || scope->sheet_local_functions == NULL) return NULL; return g_hash_table_lookup (scope->sheet_local_functions, (gpointer)name); } /** * gnm_func_lookup_prefix: * @prefix: prefix to search for * @scope: * @trans: whether to search translated function names * * Returns: (element-type GnmFunc*) (transfer full): A list of functions * whose names start with @prefix. **/ GSList * gnm_func_lookup_prefix (char const *prefix, Workbook *scope, gboolean trans) { GSList *res = NULL; GHashTableIter hiter; gpointer value; /* * Always iterate over functions_by_name as the localized name * might not be set yet. */ g_hash_table_iter_init (&hiter, functions_by_name); while (g_hash_table_iter_next (&hiter, NULL, &value)) { GnmFunc *fd = value; if (!(fd->flags & (GNM_FUNC_IS_PLACEHOLDER | GNM_FUNC_INTERNAL))) { const char *name = gnm_func_get_name (fd, trans); if (g_str_has_prefix (name, prefix)) { gnm_func_inc_usage (fd); res = g_slist_prepend (res, fd); } } } return res; } /** * gnm_func_get_translation_domain: * @func: #GnmFunc * * Returns: (transfer none): the translation domain for @func's help text. */ char const * gnm_func_get_translation_domain (GnmFunc const *func) { g_return_val_if_fail (GNM_IS_FUNC (func), NULL); return func->tdomain->str; } /** * gnm_func_set_translation_domain: * @func: #GnmFunc * @tdomain: (nullable): Translation domain, %NULL for Gnumeric's. */ void gnm_func_set_translation_domain (GnmFunc *func, const char *tdomain) { g_return_if_fail (GNM_IS_FUNC (func)); if (!tdomain) tdomain = GETTEXT_PACKAGE; if (g_strcmp0 (func->tdomain->str, tdomain) == 0) return; go_string_unref (func->tdomain); func->tdomain = go_string_new (tdomain); g_object_notify (G_OBJECT (func), "translation-domain"); } /** * gnm_func_gettext: * @func: #GnmFunc * @str: string to translate * * Returns: (transfer none): @str translated in @func's translation * domain. */ char const * gnm_func_gettext (GnmFunc const *func, const char *str) { g_return_val_if_fail (GNM_IS_FUNC (func), NULL); g_return_val_if_fail (str != NULL, NULL); return dgettext (func->tdomain->str, str); } GnmFuncFlags gnm_func_get_flags (GnmFunc const *func) { g_return_val_if_fail (GNM_IS_FUNC (func), GNM_FUNC_SIMPLE); return func->flags; } void gnm_func_set_flags (GnmFunc *func, GnmFuncFlags f) { g_return_if_fail (GNM_IS_FUNC (func)); func->flags = f; } GnmFuncImplStatus gnm_func_get_impl_status (GnmFunc const *func) { g_return_val_if_fail (GNM_IS_FUNC (func), GNM_FUNC_IMPL_STATUS_UNIMPLEMENTED); return func->impl_status; } void gnm_func_set_impl_status (GnmFunc *func, GnmFuncImplStatus st) { g_return_if_fail (GNM_IS_FUNC (func)); func->impl_status = st; } GnmFuncTestStatus gnm_func_get_test_status (GnmFunc const *func) { g_return_val_if_fail (GNM_IS_FUNC (func), GNM_FUNC_TEST_STATUS_UNKNOWN); return func->test_status; } void gnm_func_set_test_status (GnmFunc *func, GnmFuncTestStatus st) { g_return_if_fail (GNM_IS_FUNC (func)); func->test_status = st; } /** * gnm_func_get_function_group: * @func: #GnmFunc * * Returns: (transfer none): the function group to which @func belongs. */ GnmFuncGroup * gnm_func_get_function_group (GnmFunc *func) { g_return_val_if_fail (GNM_IS_FUNC (func), NULL); return func->fn_group; } void gnm_func_set_function_group (GnmFunc *func, GnmFuncGroup *group) { g_return_if_fail (GNM_IS_FUNC (func)); g_return_if_fail (group != NULL); if (func->fn_group == group) return; if (func->fn_group) gnm_func_group_remove_func (func->fn_group, func); func->fn_group = group; gnm_func_group_add_func (group, func); if (group == unknown_cat) func->flags |= GNM_FUNC_IS_PLACEHOLDER; else func->flags &= ~GNM_FUNC_IS_PLACEHOLDER; } void gnm_func_set_from_desc (GnmFunc *func, GnmFuncDescriptor const *desc) { //static char const valid_tokens[] = "fsbraAES?|"; g_return_if_fail (GNM_IS_FUNC (func)); g_return_if_fail (desc != NULL); // Not setting name, localized_name. Also not setting things not present // in desc, such as translation domain. if (desc->fn_args != NULL) { gnm_func_set_fixargs (func, desc->fn_args, desc->arg_spec); } else if (desc->fn_nodes != NULL) { gnm_func_set_varargs (func, desc->fn_nodes, desc->arg_spec); } else { gnm_func_set_stub (func); return; } gnm_func_set_help (func, desc->help, -1); func->flags = desc->flags; func->impl_status = desc->impl_status; func->test_status = desc->test_status; } /** * gnm_func_add: * @group: * @descriptor: * @tdomain: (nullable): * * Returns: (transfer full): a new #GnmFunc. */ GnmFunc * gnm_func_add (GnmFuncGroup *fn_group, GnmFuncDescriptor const *desc, const char *tdomain) { GnmFunc *func; g_return_val_if_fail (fn_group != NULL, NULL); g_return_val_if_fail (desc != NULL, NULL); func = g_object_new (GNM_FUNC_TYPE, "name", desc->name, NULL); gnm_func_set_translation_domain (func, tdomain); gnm_func_set_from_desc (func, desc); if (func->fn_type == GNM_FUNC_TYPE_STUB) { g_warning ("Invalid function has neither args nor nodes handler"); g_object_unref (func); return NULL; } gnm_func_set_function_group (func, fn_group); if (!(func->flags & GNM_FUNC_IS_WORKBOOK_LOCAL)) g_hash_table_insert (functions_by_name, (gpointer)(func->name), func); return func; } /* Handle unknown functions on import without losing their names */ static GnmValue * unknownFunctionHandler (GnmFuncEvalInfo *ei, G_GNUC_UNUSED int argc, G_GNUC_UNUSED GnmExprConstPtr const *argv) { return value_new_error_NAME (ei->pos); } static char * invent_name (const char *pref, GHashTable *h, const char *template) { static int count = 0; char *name = g_utf8_strdown (pref, -1); while (g_hash_table_lookup (h, name)) { count++; g_free (name); name = g_strdup_printf (template, count); } return name; } static GnmFunc * gnm_func_add_placeholder_full (Workbook *scope, char const *gname, char const *lname, char const *type) { GnmFuncDescriptor desc; GnmFunc *func; char const *unknown_cat_name = N_("Unknown Function"); gboolean copy_gname = TRUE; gboolean copy_lname = TRUE; g_return_val_if_fail (gname || lname, NULL); g_return_val_if_fail (gname == NULL || gnm_func_lookup (gname, scope) == NULL, NULL); g_return_val_if_fail (lname == NULL || gnm_func_lookup_localized (lname, scope) == NULL, NULL); if (!unknown_cat) unknown_cat = gnm_func_group_fetch (unknown_cat_name, _(unknown_cat_name)); if (!gname) { /* * This is actually a bit of a problem if we don't end up * with a copy of lname (because there already is a function * with that name). We're likely to save a template name, * but I don't see what else to do. */ gname = invent_name (lname, functions_by_name, "unknown%d"); copy_gname = FALSE; } if (!lname) { /* xgettext: This represents a made-up translated function name. */ lname = invent_name (gname, functions_by_localized_name, _("unknown%d")); copy_lname = FALSE; } if (gnm_debug_flag ("func")) g_printerr ("Adding placeholder for %s (aka %s)\n", gname, lname); memset (&desc, 0, sizeof (GnmFuncDescriptor)); desc.name = gname; desc.arg_spec = NULL; desc.help = NULL; desc.fn_args = NULL; desc.fn_nodes = &unknownFunctionHandler; desc.flags = GNM_FUNC_IS_PLACEHOLDER; desc.impl_status = GNM_FUNC_IMPL_STATUS_EXISTS; desc.test_status = GNM_FUNC_TEST_STATUS_UNKNOWN; if (scope != NULL) desc.flags |= GNM_FUNC_IS_WORKBOOK_LOCAL; else { #if 0 /* WISHLIST : it would be nice to have a log if these. */ g_warning ("Unknown %s function : %s", type, desc.name); #endif } func = gnm_func_add (unknown_cat, &desc, NULL); if (lname) { gnm_func_set_localized_name (func, lname); if (!copy_lname) g_free ((char *)lname); } if (!copy_gname) g_free ((char *)gname); if (scope != NULL) { if (scope->sheet_local_functions == NULL) scope->sheet_local_functions = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, g_object_unref); g_hash_table_insert (scope->sheet_local_functions, (gpointer)func->name, func); } return func; } /** * gnm_func_add_placeholder: * @scope: (nullable): scope to defined placeholder, %NULL for global * @name: (nullable): function name * @type: * * Returns: (transfer none): a placeholder with the given name. */ GnmFunc * gnm_func_add_placeholder (Workbook *scope, char const *name, char const *type) { return gnm_func_add_placeholder_full (scope, name, NULL, type); } /** * gnm_func_add_placeholder_localized: * @gname: (nullable): function name * @lname: localized function name * * Returns: (transfer none): a placeholder with the given localized name. */ GnmFunc * gnm_func_add_placeholder_localized (char const *gname, char const *lname) { return gnm_func_add_placeholder_full (NULL, gname, lname, "?"); } /** * gnm_func_lookup_or_add_placeholder: * @name: function name * * Returns: (transfer none): a #GnmFunc named @name, either an existing * one or a placeholder. */ GnmFunc * gnm_func_lookup_or_add_placeholder (char const *name) { GnmFunc * f = gnm_func_lookup (name, NULL); if (f == NULL) f = gnm_func_add_placeholder (NULL, name, ""); return f; } /** * gnm_func_get_name: * @func: #GnmFunc to query * @localized: if %TRUE, use localized name * * Returns: (transfer none): @func's name */ char const * gnm_func_get_name (GnmFunc const *func, gboolean localized) { int i; GnmFunc *fd = (GnmFunc *)func; g_return_val_if_fail (func != NULL, NULL); if (!localized) return func->name; if (func->localized_name) return func->localized_name; /* * Deduce the translated names from the help texts. This * code doesn't currently check for clashes in translated * names. */ gnm_func_load_if_stub (fd); for (i = 0; func->localized_name == NULL && i < func->help_count; i++) { const char *s, *sl; char *U; if (func->help[i].type != GNM_FUNC_HELP_NAME) continue; s = func->help[i].text; sl = gnm_func_gettext (fd, s); if (s == sl) /* String not actually translated. */ continue; U = split_at_colon (sl, NULL); if (U) { char *lname = g_utf8_strdown (U, -1); gnm_func_set_localized_name (fd, lname); g_free (lname); } g_free (U); } if (!func->localized_name) gnm_func_set_localized_name (fd, fd->name); return func->localized_name; } /** * gnm_func_get_description: * @func: #GnmFunc * * Returns: (transfer none): the description of the function **/ char const * gnm_func_get_description (GnmFunc const *func) { gint i; g_return_val_if_fail (func != NULL, NULL); gnm_func_load_if_stub ((GnmFunc *)func); for (i = 0; i < func->help_count; i++) { const char *desc; if (func->help[i].type != GNM_FUNC_HELP_NAME) continue; desc = strchr (gnm_func_gettext (func, func->help[i].text), ':'); return desc ? (desc + 1) : ""; } return ""; } /** * gnm_func_count_args: * @func: pointer to function definition * @min: (out): location for minimum args * @max: (out): location for maximum args * * This calculates the maximum and minimum number of args that can be passed. * For a vararg function, the maximum will be set to G_MAXINT. **/ void gnm_func_count_args (GnmFunc const *func, int *min, int *max) { g_return_if_fail (min != NULL); g_return_if_fail (max != NULL); g_return_if_fail (func != NULL); gnm_func_load_if_stub ((GnmFunc *)func); *min = func->min_args; *max = func->max_args; } /** * gnm_func_get_arg_type: * @func: the fn defintion * @arg_idx: zero-based argument offset * * Returns: the type of the argument **/ char gnm_func_get_arg_type (GnmFunc const *func, int arg_idx) { g_return_val_if_fail (func != NULL, '?'); gnm_func_load_if_stub ((GnmFunc *)func); g_return_val_if_fail (arg_idx >= 0 && arg_idx < func->max_args, '?'); return func->arg_types ? func->arg_types[arg_idx] : '?'; } /** * gnm_func_get_arg_type_string: * @func: the fn defintion * @arg_idx: zero-based argument offset * * Return value: (transfer none): the type of the argument as a string **/ char const * gnm_func_get_arg_type_string (GnmFunc const *func, int arg_idx) { switch (gnm_func_get_arg_type (func, arg_idx)) { case 'f': return _("Number"); case 's': return _("String"); case 'b': return _("Boolean"); case 'r': return _("Cell Range"); case 'A': return _("Area"); case 'E': return _("Scalar, Blank, or Error"); case 'S': return _("Scalar"); case '?': /* Missing values will be NULL. */ return _("Any"); default: g_warning ("Unknown arg type"); return "Broken"; } } /** * gnm_func_get_arg_name: * @func: #GnmFunc * @arg_idx: zero-based argument offset * * Returns: (transfer full) (nullable): the name of the argument **/ char * gnm_func_get_arg_name (GnmFunc const *func, guint arg_idx) { g_return_val_if_fail (func != NULL, NULL); gnm_func_load_if_stub ((GnmFunc *)func); if (func->arg_names && arg_idx < func->arg_names->len) return g_strdup (g_ptr_array_index (func->arg_names, arg_idx)); return NULL; } /** * gnm_func_get_arg_description: * @func: the fn defintion * @arg_idx: zero-based argument offset * * Returns: (transfer none): the description of the argument **/ char const * gnm_func_get_arg_description (GnmFunc const *func, guint arg_idx) { gint i; g_return_val_if_fail (func != NULL, NULL); gnm_func_load_if_stub ((GnmFunc *)func); for (i = 0; i < func->help_count; i++) { gchar const *desc; if (func->help[i].type != GNM_FUNC_HELP_ARG) continue; if (arg_idx--) continue; desc = strchr (gnm_func_gettext (func, func->help[i].text), ':'); if (!desc) return ""; desc++; while (g_unichar_isspace (g_utf8_get_char (desc))) desc = g_utf8_next_char (desc); return desc; } return ""; } /** * gnm_func_convert_markup_to_pango: * @desc: the fn or arg description string * @target: target widget for the markup. * * Return value: the escaped string with @{} markup converted to * pango markup **/ char * gnm_func_convert_markup_to_pango (char const *desc, GtkWidget *target) { GString *str; gchar *markup, *at; GdkRGBA link_color; PangoColor pg; char *link_color_text, *span_text; size_t span_text_len; gnm_get_link_color (target, &link_color); pg.red = 65535 * link_color.red; pg.green = 65535 * link_color.green; pg.blue = 65535 * link_color.blue; link_color_text = pango_color_to_string (&pg); span_text = g_strdup_printf ("", link_color_text); span_text_len = strlen (span_text); g_free (link_color_text); markup = g_markup_escape_text (desc, -1); str = g_string_new (markup); g_free (markup); while ((at = strstr (str->str, "@{"))) { gint len = at - str->str; go_string_replace (str, len, 2, span_text, -1); if ((at = strstr (str->str + len + span_text_len, "}"))) { len = at - str->str; go_string_replace (str, len, 1, "", -1); } else g_string_append (str, ""); } g_free (span_text); return g_string_free (str, FALSE); } /* ------------------------------------------------------------------------- */ static inline void free_values (GnmValue **values, int top) { int i; for (i = 0; i < top; i++) if (values [i]) value_release (values [i]); } /* ------------------------------------------------------------------------- */ /** * function_call_with_exprs: * @ei: EvalInfo containing valid fn_def! * * Do the guts of calling a function. * * Returns the result. **/ GnmValue * function_call_with_exprs (GnmFuncEvalInfo *ei) { GnmFunc const *fn_def; int i, iter_count, iter_width = 0, iter_height = 0; char arg_type; GnmValue **args, *tmp = NULL; int *iter_item = NULL; int argc; GnmExprConstPtr *argv; GnmExprEvalFlags flags, pass_flags; g_return_val_if_fail (ei != NULL, NULL); g_return_val_if_fail (ei->func_call != NULL, NULL); flags = ei->flags; argc = ei->func_call->argc; argv = ei->func_call->argv; fn_def = ei->func_call->func; gnm_func_load_if_stub ((GnmFunc *)fn_def); /* Functions that deal with ExprNodes */ if (fn_def->fn_type == GNM_FUNC_TYPE_NODES) return fn_def->nodes_func (ei, argc, argv); /* Functions that take pre-computed Values */ if (argc > fn_def->max_args || argc < fn_def->min_args) return value_new_error_NA (ei->pos); args = g_alloca (sizeof (GnmValue *) * fn_def->max_args); iter_count = (eval_pos_is_array_context (ei->pos) && (flags & GNM_EXPR_EVAL_PERMIT_NON_SCALAR)) ? 0 : -1; /* Optimization for IF when implicit iteration is not used. */ if (ei->func_call->func == fn_if && iter_count == -1) return gnumeric_if2 (ei, argc, argv, flags); pass_flags = (flags & 0); // Nothing right now. for (i = 0; i < argc; i++) { char arg_type = fn_def->arg_types[i]; /* expr is always non-null, missing args are encoded as * const = empty */ GnmExpr const *expr = argv[i]; if (arg_type == 'A' || arg_type == 'r') { tmp = args[i] = gnm_expr_eval (expr, ei->pos, pass_flags | GNM_EXPR_EVAL_PERMIT_NON_SCALAR | GNM_EXPR_EVAL_WANT_REF); if (VALUE_IS_ERROR (tmp)) { free_values (args, i); return tmp; } if (VALUE_IS_CELLRANGE (tmp)) { gnm_cellref_make_abs (&tmp->v_range.cell.a, &tmp->v_range.cell.a, ei->pos); gnm_cellref_make_abs (&tmp->v_range.cell.b, &tmp->v_range.cell.b, ei->pos); /* Array args accept scalars */ } else if (arg_type != 'A' && !VALUE_IS_ARRAY (tmp)) { free_values (args, i + 1); return value_new_error_VALUE (ei->pos); } continue; } /* force scalars whenever we are certain */ tmp = args[i] = gnm_expr_eval (expr, ei->pos, pass_flags | GNM_EXPR_EVAL_PERMIT_EMPTY | (iter_count >= 0 || arg_type == '?' ? GNM_EXPR_EVAL_PERMIT_NON_SCALAR : 0)); if (arg_type == '?') /* '?' arguments are unrestriced */ continue; /* optional arguments can be blank */ if (i >= fn_def->min_args && VALUE_IS_EMPTY (tmp)) { if (arg_type == 'E' && !gnm_expr_is_empty (expr)) { /* An actual argument produced empty. Make sure function sees that. */ args[i] = value_new_empty (); } continue; } if (tmp == NULL) tmp = args[i] = value_new_empty (); /* Handle implicit intersection or iteration depending on flags */ if (VALUE_IS_CELLRANGE (tmp) || VALUE_IS_ARRAY (tmp)) { if (iter_count > 0) { if (iter_width != value_area_get_width (tmp, ei->pos) || iter_height != value_area_get_height (tmp, ei->pos)) { free_values (args, i + 1); return value_new_error_VALUE (ei->pos); } } else { if (iter_count < 0) { g_warning ("Damn I thought this was impossible"); iter_count = 0; } iter_item = g_alloca (sizeof (int) * argc); iter_width = value_area_get_width (tmp, ei->pos); iter_height = value_area_get_height (tmp, ei->pos); } iter_item [iter_count++] = i; /* no need to check type, we would fail comparing a range against a "b, f, or s" */ continue; } /* All of these argument types must be scalars */ switch (arg_type) { case 'b': if (VALUE_IS_STRING (tmp)) { gboolean err; gboolean b = value_get_as_bool (tmp, &err); if (err) { free_values (args, i + 1); return value_new_error_VALUE (ei->pos); } value_release (args[i]); tmp = args[i] = value_new_bool (b); break; } /* Fall through. */ case 'f': if (VALUE_IS_STRING (tmp)) { tmp = format_match_number (value_peek_string (tmp), NULL, sheet_date_conv (ei->pos->sheet)); if (tmp == NULL) { free_values (args, i + 1); return value_new_error_VALUE (ei->pos); } value_release (args [i]); args[i] = tmp; } else if (VALUE_IS_ERROR (tmp)) { free_values (args, i); return tmp; } else if (VALUE_IS_EMPTY (tmp)) { value_release (args [i]); tmp = args[i] = value_new_int (0); } if (!VALUE_IS_NUMBER (tmp)) return value_new_error_VALUE (ei->pos); break; case 's': case 'S': if (VALUE_IS_ERROR (tmp)) { free_values (args, i); return tmp; } break; case 'E': /* nothing necessary */ break; /* case '?': handled above */ default: g_warning ("Unknown argument type '%c'", arg_type); break; } } while (i < fn_def->max_args) args [i++] = NULL; if (iter_item != NULL) { int x, y; GnmValue *res = value_new_array_empty (iter_width, iter_height); GnmValue const *elem, *err; GnmValue **iter_vals = g_alloca (sizeof (GnmValue *) * iter_count); GnmValue **iter_args = g_alloca (sizeof (GnmValue *) * iter_count); /* collect the args we will iterate on */ for (i = 0 ; i < iter_count; i++) iter_vals[i] = args[iter_item[i]]; for (x = iter_width; x-- > 0 ; ) for (y = iter_height; y-- > 0 ; ) { /* marshal the args */ err = NULL; for (i = 0 ; i < iter_count; i++) { elem = value_area_get_x_y (iter_vals[i], x, y, ei->pos); arg_type = fn_def->arg_types[iter_item[i]]; if (arg_type == 'b' || arg_type == 'f') { if (VALUE_IS_EMPTY (elem)) elem = value_zero; else if (VALUE_IS_STRING (elem)) { tmp = format_match_number (value_peek_string (elem), NULL, sheet_date_conv (ei->pos->sheet)); if (tmp != NULL) { args [iter_item[i]] = iter_args [i] = tmp; continue; } else break; } else if (VALUE_IS_ERROR (elem)) { err = elem; break; } else if (!VALUE_IS_NUMBER (elem)) break; } else if (arg_type == 's') { if (VALUE_IS_EMPTY (elem)) { args [iter_item[i]] = iter_args [i] = value_new_string (""); continue; } else if (VALUE_IS_ERROR (elem)) { err = elem; break; } else if (!VALUE_IS_STRING (elem)) break; } else if (elem == NULL) { args [iter_item[i]] = iter_args [i] = value_new_empty (); continue; } args [iter_item[i]] = iter_args [i] = value_dup (elem); } res->v_array.vals[x][y] = (i == iter_count) ? fn_def->args_func (ei, (GnmValue const * const *)args) : ((err != NULL) ? value_dup (err) : value_new_error_VALUE (ei->pos)); free_values (iter_args, i); } /* free the primaries, not the already freed iteration */ for (i = 0 ; i < iter_count; i++) args[iter_item[i]] = iter_vals[i]; tmp = res; i = fn_def->max_args; } else tmp = fn_def->args_func (ei, (GnmValue const * const *)args); free_values (args, i); return tmp; } /* * Use this to invoke a register function: the only drawback is that * you have to compute/expand all of the values to use this */ GnmValue * function_call_with_values (GnmEvalPos const *ep, char const *fn_name, int argc, GnmValue const * const *values) { GnmFunc *fn_def; g_return_val_if_fail (ep != NULL, NULL); g_return_val_if_fail (fn_name != NULL, NULL); g_return_val_if_fail (ep->sheet != NULL, NULL); /* FIXME : support workbook local functions */ fn_def = gnm_func_lookup (fn_name, NULL); if (fn_def == NULL) return value_new_error_NAME (ep); return function_def_call_with_values (ep, fn_def, argc, values); } GnmValue * function_def_call_with_values (GnmEvalPos const *ep, GnmFunc const *fn_def, int argc, GnmValue const * const *values) { GnmValue *retval; GnmExprFunction ef; GnmFuncEvalInfo fs; fs.pos = ep; fs.func_call = &ef; ef.func = (GnmFunc *)fn_def; gnm_func_load_if_stub (ef.func); if (fn_def->fn_type == GNM_FUNC_TYPE_NODES) { /* * If function deals with ExprNodes, create some * temporary ExprNodes with constants. */ GnmExprConstant *expr = g_new (GnmExprConstant, argc); GnmExprConstPtr *argv = g_new (GnmExprConstPtr, argc); int i; for (i = 0; i < argc; i++) { gnm_expr_constant_init (expr + i, values[i]); argv[i] = (GnmExprConstPtr)(expr + i); } retval = fn_def->nodes_func (&fs, argc, argv); g_free (argv); g_free (expr); } else retval = fn_def->args_func (&fs, values); return retval; } /* ------------------------------------------------------------------------- */ typedef struct { FunctionIterateCB callback; void *closure; gboolean strict; gboolean ignore_subtotal; } IterateCallbackClosure; /** * cb_iterate_cellrange: * * Helper routine used by the function_iterate_do_value routine. * Invoked by the sheet cell range iterator. **/ static GnmValue * cb_iterate_cellrange (GnmCellIter const *iter, gpointer user) { IterateCallbackClosure *data = user; GnmCell *cell; GnmValue *res; GnmEvalPos ep; if (NULL == (cell = iter->cell)) { ep.sheet = iter->pp.sheet; ep.dep = NULL; ep.eval.col = iter->pp.eval.col; ep.eval.row = iter->pp.eval.row; return (*data->callback)(&ep, NULL, data->closure); } if (data->ignore_subtotal && gnm_cell_has_expr (cell) && gnm_expr_top_contains_subtotal (cell->base.texpr)) return NULL; gnm_cell_eval (cell); eval_pos_init_cell (&ep, cell); /* If we encounter an error for the strict case, short-circuit here. */ if (data->strict && (NULL != (res = gnm_cell_is_error (cell)))) return value_new_error_str (&ep, res->v_err.mesg); /* All other cases -- including error -- just call the handler. */ return (*data->callback)(&ep, cell->value, data->closure); } /* * function_iterate_do_value: * * Helper routine for function_iterate_argument_values. */ static GnmValue * function_iterate_do_value (GnmEvalPos const *ep, FunctionIterateCB callback, gpointer closure, GnmValue const *value, gboolean strict, CellIterFlags iter_flags) { GnmValue *res = NULL; switch (value->v_any.type){ case VALUE_ERROR: if (strict) { res = value_dup (value); break; } /* Fall through. */ case VALUE_EMPTY: case VALUE_BOOLEAN: case VALUE_FLOAT: case VALUE_STRING: res = (*callback)(ep, value, closure); break; case VALUE_ARRAY: { int x, y; /* Note the order here. */ for (y = 0; y < value->v_array.y; y++) { for (x = 0; x < value->v_array.x; x++) { res = function_iterate_do_value ( ep, callback, closure, value->v_array.vals [x][y], strict, CELL_ITER_IGNORE_BLANK); if (res != NULL) return res; } } break; } case VALUE_CELLRANGE: { IterateCallbackClosure data; data.callback = callback; data.closure = closure; data.strict = strict; data.ignore_subtotal = (iter_flags & CELL_ITER_IGNORE_SUBTOTAL) != 0; res = workbook_foreach_cell_in_range (ep, value, iter_flags, cb_iterate_cellrange, &data); } } return res; } /** * function_iterate_argument_values: * @ep: The position in a workbook at which to evaluate * @callback: (scope call): The routine to be invoked for every value computed * @callback_closure: Closure for the callback. * @argc: * @argv: * @strict: If TRUE, the function is considered "strict". This means * that if an error value occurs as an argument, the iteration * will stop and that error will be returned. If FALSE, an * error will be passed on to the callback (as a GnmValue * * of type VALUE_ERROR). * @iter_flags: * * Return value: * NULL : if no errors were reported. * GnmValue * : if an error was found during strict evaluation * VALUE_TERMINATE : if the callback requested termination of the iteration. * * This routine provides a simple way for internal functions with variable * number of arguments to be written: this would iterate over a list of * expressions (expr_node_list) and will invoke the callback for every * GnmValue found on the list (this means that ranges get properly expanded). **/ GnmValue * function_iterate_argument_values (GnmEvalPos const *ep, FunctionIterateCB callback, void *callback_closure, int argc, GnmExprConstPtr const *argv, gboolean strict, CellIterFlags iter_flags) { GnmValue *result = NULL; int a; for (a = 0; result == NULL && a < argc; a++) { GnmExpr const *expr = argv[a]; GnmValue *val; if (iter_flags & CELL_ITER_IGNORE_SUBTOTAL && gnm_expr_contains_subtotal (expr)) continue; /* need to drill down into names to handle things like * sum(name) with name := (A:A,B:B) */ while (GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_NAME) { GnmExprTop const *texpr = expr->name.name->texpr; expr = texpr ? texpr->expr : NULL; if (expr == NULL) { if (strict) return value_new_error_REF (ep); break; } } if (!expr) continue; /* Handle sets as a special case */ if (GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_SET) { result = function_iterate_argument_values (ep, callback, callback_closure, expr->set.argc, expr->set.argv, strict, iter_flags); continue; } /* We need a cleaner model of what to do here. * In non-array mode * SUM(Range) * will obviously return Range * * SUM(INDIRECT(Range)) * SUM(INDIRECT(Range):....) * will do implicit intersection on Range (in non-array mode), * but allow non-scalar results from indirect (no intersection) * * SUM(Range=3) * will do implicit intersection in non-array mode */ if (GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_CONSTANT) val = value_dup (expr->constant.value); else if (eval_pos_is_array_context (ep) || GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_FUNCALL || GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_RANGE_CTOR || GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_INTERSECT) val = gnm_expr_eval (expr, ep, GNM_EXPR_EVAL_PERMIT_EMPTY | GNM_EXPR_EVAL_PERMIT_NON_SCALAR); else val = gnm_expr_eval (expr, ep, GNM_EXPR_EVAL_PERMIT_EMPTY); if (val == NULL) continue; if (strict && VALUE_IS_ERROR (val)) { /* Be careful not to make VALUE_TERMINATE into a real value */ return val; } result = function_iterate_do_value (ep, callback, callback_closure, val, strict, iter_flags); value_release (val); } return result; } /** * gnm_eval_info_get_func: * @ei: #GnmFuncEvalInfo * * Returns: (transfer none): the called function. */ GnmFunc const * gnm_eval_info_get_func (GnmFuncEvalInfo const *ei) { return ei->func_call->func; } int gnm_eval_info_get_arg_count (GnmFuncEvalInfo const *ei) { return ei->func_call->argc; } GnmDependentFlags gnm_func_link_dep (GnmFunc *func, GnmFuncEvalInfo *ei, gboolean qlink) { int res = DEPENDENT_NO_FLAG; g_signal_emit (func, signals[SIG_LINK_DEP], 0, ei, qlink, &res); return (GnmDependentFlags)res; } /** * gnm_func_derivative: * @func: #GnmFunc * @expr: expression that calls @func * @ep: position of @expr * @info: #GnmExprDeriv * * Returns: (transfer full) (nullable): the derivative of @expr with respect to * @info. */ GnmExpr const * gnm_func_derivative (GnmFunc *func, GnmExpr const *expr, GnmEvalPos const *ep, GnmExprDeriv *info) { GnmExpr *res = NULL; g_return_val_if_fail (GNM_IS_FUNC (func), NULL); g_signal_emit (func, signals[SIG_DERIVATIVE], 0, expr, ep, info, &res); return res; } /* ------------------------------------------------------------------------- */ static GObjectClass *parent_class; typedef struct { GObjectClass parent; void (*load_stub) (GnmFunc *func); int (*link_dep) (GnmFunc *func, GnmFuncEvalInfo *ei, gboolean qlink); GnmExpr* (*derivative) (GnmFunc *func, GnmExpr const *expr, GnmEvalPos *ep, GnmExprDeriv *info); } GnmFuncClass; static void gnm_func_finalize (GObject *obj) { GnmFunc *func = GNM_FUNC (obj); g_free (func->arg_types); g_free ((char *)func->name); go_string_unref (func->tdomain); parent_class->finalize (obj); } static void gnm_func_real_dispose (GObject *obj) { GnmFunc *func = GNM_FUNC (obj); if (func->usage_count != 0) { g_printerr ("Function %s still has a usage count of %d\n", func->name, func->usage_count); } gnm_func_set_stub (func); if (func->fn_group) { gnm_func_group_remove_func (func->fn_group, func); func->fn_group = NULL; } gnm_func_set_localized_name (func, NULL); if (!(func->flags & GNM_FUNC_IS_WORKBOOK_LOCAL)) { g_hash_table_remove (functions_by_name, func->name); } parent_class->dispose (obj); } void gnm_func_dispose (GnmFunc *func) { g_object_run_dispose (G_OBJECT (func)); } static void gnm_func_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GnmFunc *func = (GnmFunc *)object; switch (property_id) { case PROP_NAME: g_value_set_string (value, func->name); break; case PROP_TRANSLATION_DOMAIN: g_value_set_string (value, func->tdomain->str); break; case PROP_IN_USE: g_value_set_boolean (value, func->usage_count > 0); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gnm_func_set_property (GObject *object, guint property_id, GValue const *value, GParamSpec *pspec) { GnmFunc *func = (GnmFunc *)object; switch (property_id) { case PROP_NAME: func->name = g_value_dup_string (value); break; case PROP_TRANSLATION_DOMAIN: gnm_func_set_translation_domain (func, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gnm_func_init (GnmFunc *func) { func->tdomain = go_string_new (GETTEXT_PACKAGE); func->flags = GNM_FUNC_SIMPLE; func->impl_status = GNM_FUNC_IMPL_STATUS_UNIMPLEMENTED; func->test_status = GNM_FUNC_TEST_STATUS_UNKNOWN; } static void gnm_func_class_init (GObjectClass *gobject_class) { parent_class = g_type_class_peek_parent (gobject_class); gobject_class->finalize = gnm_func_finalize; gobject_class->dispose = gnm_func_real_dispose; gobject_class->get_property = gnm_func_get_property; gobject_class->set_property = gnm_func_set_property; g_object_class_install_property (gobject_class, PROP_NAME, g_param_spec_string ("name", P_("Name"), P_("The name of the function."), NULL, GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (gobject_class, PROP_TRANSLATION_DOMAIN, g_param_spec_string ("translation-domain", P_("Translation Domain"), P_("The translation domain for help texts"), NULL, GSF_PARAM_STATIC | G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_IN_USE, g_param_spec_boolean ("in-use", P_("In use"), P_("Is function being used?"), FALSE, GSF_PARAM_STATIC | G_PARAM_READABLE)); /** * GnmFunc::load-stub: * @func: the #GnmFunc that needs to be loaded * * Signals that @func, which is a stub, needs to be loaded now. Anyone * creating a stub function should arrange for this signal to be caught * and the function to be properly instantiated. */ signals[SIG_LOAD_STUB] = g_signal_new ("load-stub", GNM_FUNC_TYPE, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GnmFuncClass, load_stub), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * GnmFunc::link-dep: * @func: the #GnmFunc that is being linked or unlinked * @ei: #GnmFuncEvalInfo for the call initiating the link or unlink. * @qlink: %TRUE for link, %FALSE for unlink * * Signals that an expressions that is a call to @func is being linked * or unlinked. Most functions do not need this. * * Returns: A #GnmDependentFlags allowing arguments not be be linked if * that is appropriate. */ signals[SIG_LINK_DEP] = g_signal_new ("link-dep", GNM_FUNC_TYPE, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GnmFuncClass, link_dep), NULL, NULL, gnm__INT__POINTER_BOOLEAN, // GnmDependentFlags ... GnmFuncEvalInfo G_TYPE_INT, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN); /** * GnmFunc::derivative: * @func: #GnmFunc * @expr: #GnmExpr for the call for which the derivative is sought * @ep: position f @expr * @info: #GnmExprDeriv telling which derivative is sought * * Signals that a function call's derivative should be calculated * * Returns: (transfer full) (nullable): #GnmExpr representing the * derivative, %NULL for error. */ signals[SIG_DERIVATIVE] = g_signal_new ("derivative", GNM_FUNC_TYPE, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GnmFuncClass, derivative), NULL, NULL, gnm__BOXED__BOXED_BOXED_BOXED, gnm_expr_get_type(), 3, gnm_expr_get_type(), gnm_eval_pos_get_type(), gnm_expr_deriv_info_get_type()); } GSF_CLASS (GnmFunc, gnm_func, gnm_func_class_init, gnm_func_init, G_TYPE_OBJECT)