Interfacing with C

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 || !f->L->L) return 0;
    {
    value x = arg(f->L->R);
    value y = arg(f->R);
    if (x->T == type_str && y->T == type_str)
        {
        string result = str_concat(get_str(x),get_str(y));
        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.

Line by line description

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 || !f->L->L) return 0;

If only 0 or 1 arguments are present in the value f, the function returns 0 to indicate that evaluation is complete — i.e., 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(get_str(x),get_str(y));

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.

TODO

Discuss these symbols in more detail:

value
type_str
string
str_concat
get_str
Qstr
Qvoid
Q

The value type is declared in value.h.

The type_str, Qstr, and get_str routines are in type_str.c.

The Qvoid value is in standard.c.

The Q routine is in value.c.