To illustrate interfacing Fexl to C code, let us consider the task of writing a Fexl function which concatenates two strings together. When the function is done, you can call it like this:
concat "abc" "de"
The value of that expression will be:
"abcde"
Note that this particular function is already built into Fexl, and you can see its implementation in type_str.c. But I include it here as an example.
Here is the C code which implements the function:
// (concat x y) is the concatenation of strings x and y. value type_concat(value f) { if (f->L->L == 0) return keep(f); { value x = arg(f->L->R); value y = arg(f->R); if (x->T == type_str && y->T == type_str) { string result = str_concat(x->v_ptr,y->v_ptr); f = Qstr(result); } else f = hold(Qvoid); drop(x); drop(y); return f; } }
You would put that code in a .c code file, and add this declaration to the corresponding .h header file:
extern value type_concat(value f);
To make that function callable from Fexl, you go into standard.c and add the following case to the "standard" function there. Make sure that standard.c includes your header file.
if (match("concat")) return Q(type_concat);
Then run "./build" in the src direction, and now you have a version of Fexl with the "concat" function built into the standard context.
Note that this function is already provided in production Fexl with the name "." (dot):
if (match(".")) return Q(type_concat);
Now let's examine what the type_concat function does, line by line. The first line declares the function as taking a single argument f of type value, and returning a result of type value. That is the signature of all Fexl function types.
value type_concat(value f)
The next line checks the value to see if two arguments have been supplied:
if (f->L->L == 0) return keep(f);
If only 1 argument is present in the value f, the function simply keeps the argument and does nothing. There is nothing to do until the concat function receives 2 arguments.
The next two lines evaluate the first and second arguments, naming them x and y:
value x = arg(f->L->R); value y = arg(f->R);
The arg function increments the reference count of its argument, then calls the eval routine to get the final value of that argument.
The next line checks that the two arguments have the correct type, in this case that both are strings:
if (x->T == type_str && y->T == type_str)
If they are both strings, then the next line gets the string values out of each argument, and calls the str_concat function to get the concatenated string value:
string result = str_concat(x->v_ptr,y->v_ptr);
The next line calls Qstr, which wraps the string result into a value structure that can be used by Fexl. It assigns the result to f, which will be returned as the final value of the function. Although this overwrites the value of f that was passed in as an argument, it does not in any way alter the value to which f originally pointed. Reusing an argument value on the stack in this way is a fairly common practice in C.
f = Qstr(result);
It then drops through to this code:
drop(x); drop(y); return f;
The drop function decrements the reference count of its argument, and if that count drops to 0 it reclaims the space used by that argument. If you did not call drop on x and y, you would see a memory leak error at runtime when your program finished.
The "return f" line returns the final value to the interpreter.
In the case where either argument is not a string, you would reach this code:
f = hold(Qvoid);
That will return a void value to the interpreter. An example would be if you tried to concatenate a string with a number. If either argument is not a string, the concat function returns void.
Discuss these symbols in more detail:
value type_str string str_concat Qstr Qvoid Q
The value type is declared in value.h.
The type_str and Qstr routines are in type_str.c.
The Qvoid value is in standard.c.
The Q routine is in value.c.