Note: CGround External Libraries currently have a bug which makes argument values incorrect. Most functions will be unusable until this bug is fixed.
CGround supports creating your own ground functions written in C. While theoretically any language that can compile to a shared object file can be used, C is the language most supported.
Creating an External Library (in C)
For this example, we will make a function that returns "Hello, {arg1}!"
Our ground syntax will be call !Example_GreetUser $username &greet
C code
First, import groundext.h. Then, start a new function. For our example, we will call it greetUser.
#include <groundext.h>
GroundValue greetUser(GroundScope* scope, List args) {
}
To create our function, we need to access our arguments. Use args.values[idx] for an argument. args.values[0] is the first argument, args.values[1] is the second argument, etc.
#include <groundext.h>
GroundValue greetUser(GroundScope* scope, List args) {
const char* argument = args.values[0].data.stringVal;
}
The function expects us to return a GroundValue. After we create our string, we need to convert it to a GroundValue using the groundCreateValue() command. The syntax is groundCreateValue(TYPE, value).
All together, we can write our function like this:
#include <groundext.h>
#include <stdio.h>
#include <string.h>
GroundValue greetUser(GroundScope* scope, List args) {
const char* argument = args.values[0].data.stringVal;
// Create return char*
char* ret = malloc(9+strlen(argument));
snprintf(ret, 9+strlen(argument), "%s%s%s", "Hello, ", argument, "!");
// Convert char* to GroundValue
return groundCreateValue(STRING, ret);
}
We can add as many functions as we want here, but these won't work yet. To allow Ground to access these variables, we need to add the following code
void ground_init(GroundScope* scope) {
groundAddNativeFunction(scope, "name", function, RETURNTYPE, argNum, ARG1TYPE, name, ARG2TYPE, name, ...);
}
For our example, it would look something like this:
#include <groundext.h>
#include <stdio.h>
#include <string.h>
GroundValue greetUser(GroundScope* scope, List args) {
const char* argument = args.values[0].data.stringVal;
// Create return char*
char* ret = malloc(9+strlen(argument));
snprintf(ret, 9+strlen(argument), "%s%s%s", "Hello, ", argument, "!");
// Convert char* to GroundValue
return groundCreateValue(STRING, ret);
}
void ground_init(GroundScope* scope) {
groundAddNativeFunction(scope, "Example_GreetUser", greetUser, STRING, 1, STRING, "username");
}
Now all we need to do is compile with gcc file.c -shared -o file.so -fPIC.
Ground Code
Once we have created the shared object file, we can import it as follows
extern "file"
We can call our function with call !Example_GreetUser $string &out
Example:
extern "file"
call !Example_GreetUser "DiamondNether90" &out
println $out # Prints "Hello, DiamondNether90!"
Best practices
- Namespacing is done with an underscore (
libName_FunctionName). - Use camelCase for the library name and PascalCase for the function name.
Throwing Errors
If your library encounters an error during execution, you can throw an error with the ERROR() macro. The syntax is this:
ERROR("Friendly description of how the error happened", "ErrorCode");
The ErrorCode is what users of your library will be able to catch errors with. If the error is not caught, the friendly description will be shown to the user.