Writing External C

There are a few things you have to keep in mind when writing C that interoperates with Gera, which shall be explained in this section. It's a lot more technical than the previous sections and requires knowledge of the C programming language, so unless you want to write Gera code that interoperates with C code feel free to skip this section.

About Referenced Types

When working with Gera types in C, you must handle referenced types with extra care. These types are always written in PascalCase, and they always have an allocation-property.

When you create a copy of one of these types, you need to pass their allocation-property to gera___ref_copied.

When a copy is deleted, you need to pass their allocation-property to gera___ref_deleted.

Gera uses these functions to manage memory. For example, let's consider the following external definition (external mapping file):

proc example::println(str) = example_println
And now let's implement it in C:
#include <gera.h>
#include <stdio.h>

void example_println(GeraString line) {
// write string contents to stdout using 'fwrite'
fwrite(line.data, sizeof(char), line.length_bytes, stdout);
putchar('\n');
// reference is deleted at the end of this scope
gera___ref_deleted(line.allocation);
}

About GeraAllocation

To create your own instances of referenced types, you will need to create a GeraAllocation*. You can achieve this by using the gera___alloc-function, which takes the allocation size and a free handler function (taking the GeraAllocation* as an argument and returning nothing).

Let's say we want to allocate an array of 16 integers, then we'd write

// passing null for the free handler is also valid
GeraAllocation* a = gera___alloc(sizeof(gint) * 16, NULL);
gint* data = (gint*) a->data;
// now we can write our integers to 'data'
GeraArray array = (GeraArray) { .allocation = a, .length = 16 };

About GeraString

A GeraString has the following definition:

typedef struct GeraString {
GeraAllocation* allocation;
size_t length; // length of the string in unicode code points
size_t length_bytes; // length of the string in bytes
const char* data; // string data
} GeraString;

The gera___alloc_string-procedure allows you to create a GeraString from a null-terminated string. gera___wrap_static does the same, but does not allocate any memory (and therefore requires the given string to be static, like a string literal). core::substring and core::concat are available as C functions as gera___substring and gera___concat. However, note that their C counterparts do not do bounds checking or handle negative indices in any way.

About GeraClosure

A GeraClosure has the following definition:

typedef struct GeraClosure {
GeraAllocation* allocation;
const void* body; // function pointer to the body
} GeraClosure;

To call a GeraClosure you will need to get an actual function pointer from it, which can be achieved using the GERA_CLOSURE_FPTR and GERA_CLOSURE_FPTR_NOARGS-macros.

GERA_CLOSURE_FPTR(closure, ret, ...) takes the given GeraClosure, return type and argument types and returns a function pointer that takes the allocation-property of the closure together with the provided argument types and returns the specified type.

GERA_CLOSURE_FPTR_NOARGS(closure, ret) takes the given GeraClosure and return type and returns a function pointer that takes the allocation-property of the closure and returns the specified type.

For example, let's consider the following external mappings file:

proc do_math(|float, float| -> float, float, float) -> float = do_math
And now let's implement it in C:
#include <gera.h>

gfloat do_math(GeraClosure op, gfloat a, gfloat b) {
// call 'op', passing 'a' and 'b'
gfloat r = (GERA_CLOSURE_FPTR(op, gfloat, gfloat, gfloat))(op.allocation, a, b);
gera___ref_deleted(op);
return r;
}

About GeraArray

A GeraArray has the following definition:

typedef struct GeraArray {
GeraAllocation* allocation;
size_t length;
} GeraArray;

The array data is available under my_array.allocation->data (cast that pointer to a pointer to the element type).

For example, let's consider this external mapping definition of an int array sum function:

proc example::int_arr_sum([int]) -> int = int_arr_sum
And now let's implement it in C:
#include <gera.h>

gint int_arr_sum(GeraArray vals) {
gint* data = (gint*) vals.allocation->data;
gint sum = 0;
for(size_t i = 0; i < vals.length; i += 1) {
sum += data[i];
}
gera___ref_deleted(vals.allocation);
return sum;
}

About GeraObject

A GeraObject has the following definition:

typedef struct GeraObject {
GeraAllocation* allocation;
} GeraObject;

The object data may be accessed by casting the pointer in the data-property of the allocation-property to a struct with the same layout as declared in the external mappings file.

For example, let's consider the following external mappings file:

type Cat = {
name = str,
age = int,
hunger = float
}
proc example::feed_cat(Cat) = feed_cat
And now let's implement feed_cat in C:
#include <gera.h>

// make sure this is the same order and the same types
// as in the external mappings file!
typedef struct CatLayout {
GeraString name;
gint age;
gfloat hunger;
} CatLayout;

void feed_cat(GeraObject cat) {
CatLayout* data = (CatLayout*) cat.allocation->data;
data->hunger = 0.0;
gera___ref_deleted(cat.allocation);
}