Making a Native Function

Berry’s C FFI (Foreign Function Interface) operates on a virtual stack to interact with the VM. If we need to make an add function to add two numbers and use it in Berry in this way:

result = add(1, 2)

We need to know how the C code gets the arguments from the Berry function call and how to return the value.

The function arguments are stored in a stack, and the first argument to the last argument of the function is stored from the bottom of the stack to the top of the stack. If you want to use C to fetch elements from the stack, use the following set of FFIs:

int be_toint(bvm *vm, int index);
breal be_toreal(bvm *vm, int index);
int be_tobool(bvm *vm, int index);
const char* be_tostring(bvm *vm, int index);
void* be_tocomptr(bvm *vm, int index);

If you want to test if a value in the stack is a specified type, use the following set of FFIs:

int be_isnil(bvm *vm, int index);
int be_isbool(bvm *vm, int index);
int be_isint(bvm *vm, int index);
int be_isreal(bvm *vm, int index);
int be_isnumber(bvm *vm, int index);
int be_isstring(bvm *vm, int index);
int be_isclosure(bvm *vm, int index);
int be_isntvclos(bvm *vm, int index);
int be_isfunction(bvm *vm, int index);
int be_isproto(bvm *vm, int index);
int be_isclass(bvm *vm, int index);
int be_isinstance(bvm *vm, int index);
int be_islist(bvm *vm, int index);
int be_ismap(bvm *vm, int index);
int be_iscomptr(bvm *vm, int index);

If you need to push values onto the stack, use these FFIs:

void be_pushnil(bvm *vm);
void be_pushbool(bvm *vm, int b);
void be_pushint(bvm *vm, bint i);
void be_pushreal(bvm *vm, breal r);
void be_pushstring(bvm *vm, const char *str);
void be_pushnstring(bvm *vm, const char *str, size_t n);
const char* be_pushfstring(bvm *vm, const char *format, ...);
void be_pushvalue(bvm *vm, int index);
void be_pushntvclosure(bvm *vm, bntvfunc f, int nupvals);
void be_pushntvfunction(bvm *vm, bntvfunc f);
void be_pushclass(bvm *vm, const char *name, const bnfuncinfo *lib);
void be_pushcomptr(bvm *vm, void *ptr);

index is the position of the element on the stack, the positive value is the offset from the bottom of the stack to the top of the stack, and the negative value is the offset from the top of the stack to the bottom of the stack.

The return value uses two FFIs:

be_return(vm)
be_return_nil(vm)

These FFIs are actually macros. be_return returns the object at the top of the stack, and be_return_nil returns nil.

These FFIs are defined in berry.h.

Now let’s implement the add function:

int my_add_func(bvm *vm)
{
    /* check the arguments are all integers */
    if (be_isint(vm, 1) && be_isint(vm, 2)) {
        bint a = be_toint(vm, 1); /* get the first argument */
        bint b = be_toint(vm, 2); /* get the second argument */
        be_pushint(vm, a + b); /* push the result to the stack */
    } else if (be_isnumber(vm, 1) && be_isnumber(vm, 2)) { /* check the arguments are all numbers */
        breal a = be_toreal(vm, 1); /* get the first argument */
        breal b = be_toreal(vm, 1); /* get the second argument */
        be_pushreal(vm, a + b); /* push the result to the stack */
    } else { /* unacceptable parameters */
        be_pushnil(vm); /* push the nil to the stack */
    }
    be_return(vm); /* return calculation result */
}

Then register it in the appropriate place:

be_regcfunc(vm, "add", my_add_func);

Instantiate a list object in a native function

Generating instantiated native classes in C can be cumbersome compared to simple types. This section will guide the reader to instantiate the list class.

The list class is a wrapper around the list structure, which has a .data property for the list structure. Therefore, we first need to construct a list structure:

be_newlist(vm);

The be_newlist function constructs a value of type BE_LIST. Then we can operate on the data:

be_pushint(vm, 100);
be_data_append(vm, -2);
be_pop(vm, 1); /* popping the integer 100 */

The first two lines of code are used to append the integer 100 to the list, and the third line to the integer 100 is popped to facilitate subsequent operations.

Since the BE_LIST type cannot be used directly in Berry, but is used by the list class, we have to build the list class for it:

be_getglobal(vm, "list");
be_pushvalue(vm, -2); /* push the list data to top */
be_call(vm, 1); /* call constructor */

The constructor of the list class allows the use of the BE_LIST type parameter, which takes the argument as list data.

The complete code is as follows:

int m_listtest(bvm *vm)
{
    be_getglobal(vm, "list");
    be_newlist(vm);
    be_pushint(vm, 100);
    be_data_append(vm, -2);
    be_pop(vm, 1);
    be_call(vm, 1);
    be_pop(vm, 1); /* pop the arguments */
    be_return(vm);
}

Register the native function in the appropriate place:

be_regcfunc(vm, "listtest", m_listtest);