1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
|
Howto write builtin functions for gnumeric, version 0.2
by Michael Meeks <mmeeks@gnu.org>
This text describes the API and conventions a programmer should use
to implement and register new functions for use in gnumeric.
-------------------------------------------------------------------
Functions in Gnumeric should be registered in a category, to do this
you must get a category handle:
FunctionCategory *cat = function_get_category (_("My functions"));
If the category already exists it will be returned, otherwise a new
category will be created. Then, you will have to add your functions
individualy to the category. For example, a simple routine to sum
two numbers, you would do:
char *help_add2numbers = "ADD2NUMBERS: Adds two numbers";
This C declaration defines the HELP associated with this
function.
function_add_args (cat, "add2numbers", "ff", "number1,number2",
help_add2numbers, add2numbers);
[ NB. there are several types of function; more of this later ]
This defines our function, the paramters in order are:
'cat' The category handle obtained earlier
'add2numbers' The function name ( as used in gnumeric )
'ff' The argument token type string, this tells gnumeric that
we are expecting two floating point arguments.
'number1,...' Symbolic names for the arguments (in case our function
supports them, this allows users to specify named paramters)
'help_add2..' This links in the help text
'add2numbers' This is the name of the function that gnumeric will call
to evaluate the function.
Now, our routine is pretty simple: it will always require two arguments
to be passed, so we can let Gnumeric deal with argument count and we
can let the Gnumeric engine compute the values in the expression for
us. If we had a more complex function we would need to use
function_add_nodes ( more later ).
When the user calls the function "ADD2NUMBERS" in a Gnumeric
expression, our routine will be invoked, this is how the add2numbers
function signature looks like:
static Value *
add2numbers (FunctionEvalInfo *ei, Value *argv [])
{
}
The function should return a newly allocated Value structure.
Arguments are passed in the argv array and they contain Value
structures (look in gnumeric/src/value.h for the definition of the
_Value union, which is typedef'ed to Value in gnumeric.h).
The first parameter, ei is a pointer to the FunctionEvalInfo
structure, this looks like this: ( from expr.h )
struct _FunctionEvalInfo {
EvalPosition pos;
FunctionDefinition *func_def;
};
EvalPosition - Position at which the function is being evaluated.
NB. Unless you are doing something _extremely_ clever the correct way to
return an error is thus:
if (foo && bar)
return value_new_error (&ei->pos, _("My error"));
FunctionDefinition - This returns a handle to a magic structure that
should not be used. In future this will allow for
extra function attributes to be set. This is normally
ignored, but some functions might want to access this
(for example, a single function callback might implement
various functions and can figure how it was invoked by
poking at this value).
Here is the actual implementation:
static Value *
add2numbers (FunctionEvalPos *ei, Value *argv [])
{
Value *result;
float number_a, number_b;
switch (argv [0].type){
case VALUE_INTEGER:
number_a = argv [0].v.v_int;
break;
case VALUE_FLOAT:
number_a = argv [0].v.v_float;
break;
default:
return value_new_error (&ei->pos, "Invalid argument type");
}
switch (argv [1].type){
case VALUE_INTEGER:
number_b = argv [1].v.v_int;
break;
case VALUE_FLOAT:
number_b = argv [1].v.v_float;
break;
default:
return value_new_error (&ei->pos, "Invalid argument type");
}
result = value_float (number_a + number_b);
return result;
}
Note that although typechecking is done in upper layers, both INTEGER
and FLOAT types are passed through the 'f' function declaration token.
It is of course good practice to provide a sensible default: case.
If there is an error during the function processing, an error value
should be returned. The error string is managed by gnumeric in the
same reference counted fashion that all other strings are handled.
Return values and Strings.
--------------------------
There are a several utility functions provided by Gnumeric to create
Values: value_new_float, value_new_int, value_new_bool, value_new_string,
value_new_cellrange, value_new_array, value_new_error. [ the nomenclature
is as of different constructors for the same class: Value ]. All Values
should idealy be created with these routines.
Functions with some optional arguments
--------------------------------------
Gnumeric support three types of functions:
* Fixed number of arguments ( dealt with above )
* Optional argument functions and
* Variable argument functions.
The difference between the latter two is that eg. the SUM ()
function will take an indefinate amount of arguments, whereas the
ERF function is best described ERF(lower limit[,upper_limit]).
In this instance, it is not worth the complexity of manualy
traversing the raw expression tree as in SUM. Instead using the
standard type checking the argument can be specified as optional,
and if unspecified a NULL will be passed in the appropriate
argv[] entry. Hence for erf (fn-eng.c):
function_add_args (cat, "erf", "f|f", "lower,upper",
&help_erf, gnumeric_erf );
Note "f|f" specifying 'upper' is optional. Any arguments that
appear after a '|' symbol in the function definition string are
deemed optional.
Hence in the code, it is vital to check optional pointers
before assuming they are valid hence:
static Value *
gnumeric_erf (FunctionEvalInfo *ei, Value **argv)
{
float_t ans, lower, upper=0.0 ;
lower = value_get_as_double(argv[0]) ;
if (argv[1])
upper = value_get_as_double(argv[1]) ;
...
}
Note there must be a default value for optional arguments.
Here upper has a default value of 0.0.
Functions with a variable number of arguments
---------------------------------------------
In this case, the function writer is presented with the raw expression
tree data as a list, to traverse himself.
If you want to implement a function that takes a variable number of
arguments you have to change your FunctionDefinition: instead of
setting the fifth parameter as NULL and the sixth pointing to your
function, you should set the sixth parameter to NULL and you fill in
the fifth parameter, like this:
char *help_factadd = "Adds all of its arguments";
function_add_nodes (cat, "addall", "", "...", &help_addall,
addall);
NB. _add_nodes not _add_all.
We will now implement addall, a function that takes any number and
kind of arguments: strings, integers, floating point numbers, arrays
and cell ranges.
This is a simple usage of addall, lets imagine cell A1 contains the
value 5 and the cell A2 contains the value 3.
addall (1) returns 1
addall (1,2) returns 3
addall (a1:a3) returns 8
addall (a1:a3,1) returns 9
Here is the function signature for addall:
Value *
addall (FunctionEvalInfo *ei, GList *expr_node_list)
As usual, this routine would return a newly allocated Value or a COPY of
an error if one is found.
The first argument points to the usual EvalInfo structure, and the second
argument is a linked list (a GList) that contains (ExprTree *) nodes.
The ei->pos structure can be used to determine position ( as normal )
struct _EvalPosition {
Sheet *sheet;
int eval_col;
int eval_row;
};
'sheet' points to the sheet in which the function is being evaluated.
The 'eval_col' and 'eval_row' parameters are used to inform the function
in which context the expression should be evaluated (ie, from which
cell it is being invoked) and it is used when invoking the expr_eval
function.
Function Tokens
---------------
With intersection and iteration support
b : boolean identical to f, Do we need this ?
f : float no errors, string conversion attempted, blanks
converted to int 0
s : string no errors, blanks accepted and passed as empty
S : scalar any scalar non-error, blanks passed as empty
E : scalar any scalar non blank value
B : scalar any scalar even a blank
Without intersection or iteration support
r : cell range content is _NOT_ guaranteed to have been evaluated yet
A : area array, range, (range as above), or scalar
a : array
? : anything
|