A C/C++ function needs to be of type (ccInt)(ccInt, char **) in order to be called from within Cicada. ccInt will just be C’s basic int type unless you go in and change it. The two arguments to the C function are: 1) the number of variables/arrays passed to the function, and 2) an array of pointers to these variables; and its return value is usually interpreted as an error code. Each variable or array passed to the function is a list of one or more Booleans, characters, integers, floating-point numbers, or strings stored in linked lists.
Here is an example of a Cicada-compatible function that has one argument of each possible data type:
#include "userfn.h"
ccInt myFunction(ccInt argc, char **argv)
{
ccBool firstArg = *argv[0];
char secondArg = *argv[1];
ccInt *thirdArg = (ccInt *) argv[2];
ccFloat *fourthArg = (ccFloat *) argv[3];
linkedlist *fifthArg = (linkedlist *) argv[4];
...
return 0; // no error
}
We want to point out several features of this function call. First, we included Cicada’s userfn.h header file. Second, notice that we passed the first two arguments by value but the other three arguments by reference. We must pass an argument by reference if it might be changed, particularly if it is a string (the linkedlist type). It’s especially important to always pass strings by reference because otherwise any resize of the string will probably crash the program. Arrays should also be passed by reference because that’s how they’re represented in C.
Finally, we see that our C function used several Cicada-defined types: for example ccInt rather than int. Ordinarily these two types are interchangeable, according to definitions found in the lnklst.h header file. However, it is possible to change Cicada’s default integer type to, say, a long integer by making changes to two of Cicada’s files. The first change is the type definition in lnklst.h, which becomes
typedef long int ccInt;
#define ccIntMax LONG_MAX
#define ccIntMin LONG_MIN
The second change ensures that Cicada will print our new variables properly. At the top of cmpile.c we slightly change two lines to:
const char *printIntFormatString = "%li";
const char *readIntFormatString = "%li%n";
We can also change Cicada’s default floating-point type ccFloat in lnklst.h, as well as ccBool which is the integer type used to store Booleans. For floating-point changes we will also want to modify the definitions at the top of cmpile.c to ensure proper printing (and possibly give the type a new name in cicada.c). On the other hand, character variables are always simply of type char, and string variables always use the linkedlist data type. Cicada’s lnklst source files provide routines for accessing and resizing linked lists, as described in the reference section.
Cicada’s userfn source files provide a handy getArgs() function, which helps us simplify our C routine.
#include "userfn.h"
ccInt myFunction(ccInt argc, char **argv)
{
ccBool firstArg;
char secondArg;
ccInt *thirdArg;
ccFloat *fourthArg;
linkedlist *fifthArg;
getArgs(argc, argv, byValue(&firstArg), byValue(&secondArg),
&thirdArg, &fourthArg, &fifthArg);
...
return 0; // no error
}
The first two arguments of getArgs() are the two arguments to our C/C++ function: in this case argc and argv. After that we list the address of each variable to load, using a getArgs() macro for each C variable that is not a pointer variable. If we don’t want to load all arguments, we can use the endArgs macro. For example our function could have loaded just the first three arguments with the command
getArgs(argc, argv, byValue(&firstArg), byValue(&secondArg), &thirdArg, endArgs);
Likewise getArgs() can skip to a particular argument using the fromArg() macro, as in
getArgs(argc, argv, fromArg(2), &thirdArg, &fourthArg, &fifthArg);
Notice that fromArg() uses C-style indexing, so the next argument read after fromArg(2) is argv[2] which is the third argument.
To give our C function some context, let’s look an example script that calls it. A typical script first sets up variables and arrays to pass, and then runs the C code using either Cicada’s call() function or its shorthand syntax where the function-name string follows a dollar sign (without extra spaces on either side). Notice that the function name-string is defined separately from the actual function name, in the file userfn.c.
nums :: [2][5] int
call("myFunctionInC", true, 'q', nums, pi, "a sample string") | syntax 1
$myFunctionInC(true, 'q', nums, pi, "a sample string") | syntax 2
This is pretty straightforward: three of the arguments are constants, pi was pre-defined in user.cicada, and nums is a two-dimensional array. It should be noted that nums is effectively a one-dimensional list as far as the C routine is concerned, and that the order of elements in the list is [1][1], [1][2], ..., [1][5], [2][1], ..., [2][5] (Cicada indices begin at 1). So, for example, the array variable written in Cicada as nums[2][3] is in memory slot thirdArg[7] within our C code.
Finally, the call() function actually passes one extra parameter, located at argv[argc], which is a list containing argc elements of type arg_info (defined in userfn.h) giving the types and numbers of elements of each parameter passed from the script. This is useful for argument-checking, and it also tells the C code the total number of elements in each array that was passed (though not the sizes of the individual dimensions of 2+ dimensional Cicada arrays). The type of a parameter is given by the argType field of its respective arg_info variable, as defined in Table 1. The total number of array elements is given by a argIndices field.
type # (macro) | Cicada type name | C type name | default data type |
0 (bool_type) | bool | ccBool | char |
1 (char_type) | char | char | char |
2 (int_type) | int | ccInt | int |
3 (double_type) | double | ccFloat | double |
4 (string_type) | string | linkedlist | { int; void *; int; int; } |
5 (composite_type) | { } | N/A | N/A |
6 (array_type) | [ ] | N/A | N/A |
Continuing our last example, we might want to take advantage of the arg_info array by adding these few extra lines to myFunction().
#include "lnklst.h"
ccInt myFunction(ccInt argc, char **argv)
{
arg_info myArgsInfo = argv[argc];
ccInt numThirdArgElements = myArgsInfo[2].argIndices;
...
if (myArgsInfo[2].argType != int_type) return 1; // do some type-checking
...
}
Last update: May 8, 2024