Hacer una función nativa

La FFI en C (interfaz de función externa) de Berry opera en una pila virtual para interactuar con la máquina virtual. Si necesitamos hacer una función add para sumar dos números y usarla en Berry de esta manera:

result = add(1, 2)

Necesitamos saber cómo el código C obtiene los argumentos de la llamada a la función Berry y cómo devolver el valor.

Los argumentos de la función se almacenan en una pila, y desde el primer argumento hasta el último argumento de la función se almacenan desde la parte inferior de la pila hasta la parte superior de la pila. Si desea utilizar C para obtener elementos de la pila, utilice el siguiente conjunto de FFI:

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);

Si desea probar si un valor en la pila es de un tipo específico, use el siguiente conjunto de FFI:

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);

Si necesita enviar valores a la pila, use estos FFI:

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 es la posición del elemento en la pila, un valor positivo es el desplazamiento desde la parte inferior de la pila hasta la parte superior de la pila, y un valor negativo es el desplazamiento desde la parte superior de la pila hasta la parte inferior de la pila.

El valor de retorno utiliza dos FFI:

be_return(vm)
be_return_nil(vm)

Estos FFI son en realidad macros. be_return devuelve el objeto en la parte superior de la pila, y be_return_nil devuelve nil.

Estas FFI se definen en berry.h.

Ahora implementemos la función add:

int my_add_func(bvm *vm)
{
    /* comprobar que los argumentos son todos enteros */
    if (be_isint(vm, 1) && be_isint(vm, 2)) {
        bint a = be_toint(vm, 1); /* obtener el primer argumento */
        bint b = be_toint(vm, 2); /* obtener el segundo argumento */
        be_pushint(vm, a + b); /* empuja el resultado a la pila */
    } else if (be_isnumber(vm, 1) && be_isnumber(vm, 2)) { /* comprobar que los argumentos son todos números */
        breal a = be_toreal(vm, 1); /* obtener el primer argumento */
        breal b = be_toreal(vm, 1); /* empuja el resultado a la pila */
        be_pushreal(vm, a + b); /* empuja el resultado a la pila */
    } else { /* parámetros inaceptables */
        be_pushnil(vm); /* empuja nil a la pila */
    }
    be_return(vm); /* devuelve el resultado del cálculo */
}

Luego regístrelo en el lugar apropiado:

be_regcfunc(vm, "add", my_add_func);

Crear una instancia de un objeto list en una función nativa

La generación de clases nativas instanciadas en C puede ser engorrosa en comparación con los tipos simples. Esta sección guiará al lector a instanciar la clase list.

La clase list es un contenedor alrededor de la estructura de la lista, que tiene una propiedad .data para la estructura de la lista. Por lo tanto, primero necesitamos construir una estructura de lista:

be_newlist(vm);

La función be_newlist construye un valor de tipo BE_LIST. Entonces podemos operar sobre los datos:

be_pushint(vm, 100);
be_data_append(vm, -2);
be_pop(vm, 1); /* extraer el entero 100 */

Las dos primeras líneas de código se utilizan para añadir el entero 100 a la lista, y la tercera línea del entero 100 se extrae para facilitar las operaciones posteriores.

Dado que el tipo BE_LIST no se puede usar directamente en Berry, pero lo usa la clase list, tenemos que construir la clase list para él:

be_getglobal(vm, "list");
be_pushvalue(vm, -2); /* empuja los datos de la lista al principio */
be_call(vm, 1); /* llama al constructor */

El constructor de la clase list permite el uso del parámetro de tipo BE_LIST, que toma el argumento como datos de lista.

El código completo es el siguiente:

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);
}

Registre la función nativa en el lugar apropiado:

be_regcfunc(vm, "listtest", m_listtest);