A C/C++ function needs to be of type (ccInt)(argsType) in order to be called from within Cicada. ccInt will just be C’s basic int type unless you go in and change it inside of cicada.h. The argsType variable contains all the variables and arrays passed from Cicada into the C function, along with information about their datatypes and array sizes. Each variable or array passed to the C function is a list of one or more Booleans (one per byte), characters, integers, floating-point numbers, or strings. The number of elements in each argument is fixed; the only dynamic memory allocation intended for C is through resizable strings, as each string is stored in a linked list (see the reference section).
This is the structure of an argsType variable named ‘a’:
a.num // the number of arguments
a.p[i] // a pointer to the i'th argument
a.type[i] // an integer giving the primitive type of the i'th argument
a.indices[i] // the number of array elements in the i'th argument (=1 if it is a scalar)
Table 1 lists all of the Cicada types, although our C function will only ever see the primitive types (0-4). Composite (aka structure) types are broken into their constituent primitive arrays before being passed into a C function, and multidimensional arrays are unrolled into 1D lists.
| type # (macro) | Cicada type name | C type name | default data type |
| 0 (bool_type) | bool | bool | bool |
| 1 (char_type) | char | char | char |
| 2 (int_type) | int | ccInt | int |
| 3 (double_type) | double | ccFloat | double |
| 5 (composite_type) | { } | N/A | N/A |
| 6 (array_type) | [ ] | N/A | N/A |
| 7 (list_type) | [[]] | N/A | N/A |
Here is a Cicada-compatible C function that has one argument of each possible data type. It also checks a) that the correct argument types are being passed, and b) that the first two arguments are scalars.
ccInt myFunction(argsType args)
{
bool firstArg = *(bool *) args.p[0];
char secondArg[2];
ccInt *thirdArg = (ccInt *) args.p[2];
ccFloat *fourthArg = (ccFloat *) args.p[3];
secondArg[0] = ((char *) args.p[1])[0]; // copy array data manually here
secondArg[1] = ((char *) args.p[1])[1];
if (args.num != 4) return wrong_argument_count_err;
if ((args.type[0][0] != 0) || (args.type[1][0] != 1)
|| (args.type[2][0] != 2) || (args.type[3][0] != 3)) return type_mismatch_err;
if ((args.indices[0] != 1) || (args.indices[2] != 1)) return type_mismatch_err;
...
return 0; // no error
}
This function copies its first two arguments by value and the latter two arguments by reference. We must pass an argument by reference if it might be changed in the C code. Arrays are usually passed by reference for efficiency.
Our C function used two Cicada-defined types: ccInt and ccFloat. By default, these two types correspond to int and double; however these definitions can be changed in cicada.h. If we change ccInt we should also make changes to ccIntMin, ccIntMax, printIntFormatString, and readIntFormatString; and if we change ccFloat then we should also make corresponding changes to maxPrintableDigits, printFloatFormatString, print_stringFloatFormatString, and readFloatFormatString.
There is a handy getArgs() function which simplifies the loading of C variables.
ccInt myFunction(argsType args)
{
...
if (args.num != 4) return wrong_argument_count_err;
if ((args.type[0][0] != 0) || (args.type[1][0] != 1)
|| (args.type[2][0] != 2) || (args.type[3][0] != 3)) return type_mismatch_err;
if ((args.indices[0] != 1) || (args.indices[2] != 1)) return type_mismatch_err;
getArgs(args, byValue(&firstArg), byValue(secondArg), &thirdArg, &fourthArg);
...
}
The first argument of getArgs() must be the argument variable; following that is the address of each variable to load, using the byValue() macro for each C variable that is not a pointer variable.
Using a different set of macros, we can both load the arguments and perform type-checking:.
ccInt myFunction(argsType args)
{
ccInt errCode;
...
if (args.num != 4) return wrong_argument_count_err;
errCode = getArgs(args, scalarValue(bool_type, &firstArg), arrayValue(char_type, secondArg),
scalarRef(int_type, &thirdArg), arrayRef(double_type, &fourthArg));
if (errCode != 0) return errCode;
...
}
This example uses all four macros that check-while-passing arguments. The scalarValue() and arrayValue() macros copy data into their arguments; the other two copy references (pointers). The scalarValue() and scalarRef() macros enforce that the number of indices should be one; the other two do not.
If we only want to load certain selected arguments, we can use getArgs() to skip to an argument, and endArgs to stop loading arguments. Here are some possibilities:
getArgs(args, byValue(&firstArg), byValue(secondArg), endArgs);
getArgs(args, fromArg(1), byValue(secondArg), &thirdArg, endArgs);
getArgs(args, fromArg(1), byValue(secondArg), fromArg(3), &fourthArg);
Notice that fromArg() uses C-style indexing, so the next argument read after fromArg(1) is args.p[1] which is the second argument.
Let’s look an example script that calls ‘myFunction()’. A typical script first sets up variables and arrays to pass, and then calls the C routine using its Cicada name (the Cfunction string passed into runCicada()) prefaced by a dollar sign. We’ll assume the Cicada name is the same as the C function name.
(dbls :: [2][5] double) = { { 0, 1, 2, 3, 4 }, { 5, 6, 7, 8, 9} }
$myFunction(true, "hi", 5, dbls)
This is pretty straightforward: two of the arguments are constants, ``hi" is a string of two characters, and dbls is a two-dimensional array. It should be noted that dbls 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] (in Cicada notation where indices begin at 1). So, for example, the array variable written in Cicada as dbls[2][3] is in memory slot fourthArg[7] within our C code. dbls has 10 array elements in total, so args.indices[3] will be 10. The individual dimensions (2, 5) were not passed into C, so if our function needs to know them, then we need to pass them as separate parameters.
Last update: November 12, 2025