18 Calling C functions

Syntax
extern = [ "export" ] "extern" c-name "(" effect ")" "from" STRING [ "as" NAME ] ;
c-name = NAME | reserved-word | STRING ;

(1) extern NAME (signature) from "LIBRARY" makes one C function a word. The processor does not open libraries itself. At load it asks the host’s resolver for (library, name) and calls the function the host gives. When the host has set no resolver, or gives no function, the load is refused. So the C functions a script can call are the host’s choice.

(2) With as OTHER at the end the script calls it by that name; the name asked of the host stays the same. When the name of the C function is a core word or a reserved word, or holds characters that are not those of a name (it is then written in quotes), as is required.

(3) The signature may have: as arguments int (and bool, narrow integers (7.6), declared types and other values represented as int), handles, str and cstr; as the result one int or handle, or none. or fail goes only with a handle result: a null pointer from the function is a failure. cstr is a type of this signature only.

(4) How the function is called: every int and every handle’s object pointer is one intptr_t argument; a str is two arguments, (const char *, size_t), not NUL-terminated; a cstr is one const char * to a NUL-terminated copy. At most eight C arguments. The result is read as one intptr_t. Functions of other shapes (floating point arguments, an int result, structs, variable arguments) are wrapped in host words.

18-extern.c

#include "pinax.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>

/* Two C functions the script names. Every integer is an intptr_t; a str is (pointer, length). */
static intptr_t gcd(intptr_t a, intptr_t b) {
    while (b) { intptr_t t = a % b; a = b; b = t; }
    return a;
}
static intptr_t count_vowels(const char *s, size_t n) {
    intptr_t k = 0;
    for (size_t i = 0; i < n; ++i)
        k += strchr("aeiou", s[i]) != NULL && s[i] != '\0';
    return k;
}

/* The host decides what "mathlib" means: here, a table of its own functions. */
static pinax_extern_fn resolve(void *user, pinax_str library, pinax_str symbol) {
    (void)user;
    if (library.len != 7 || memcmp(library.ptr, "mathlib", 7) != 0)
        return NULL;
    if (symbol.len == 3 && memcmp(symbol.ptr, "gcd", 3) == 0)
        return (pinax_extern_fn)gcd;
    if (symbol.len == 12 && memcmp(symbol.ptr, "count_vowels", 12) == 0)
        return (pinax_extern_fn)count_vowels;
    return NULL;
}

static void try_load(pinax_state *st, const char *name, const char *text) {
    if (pinax_load(st, name, (pinax_str){(const unsigned char *)text, strlen(text)}, PINAX_LOAD_CODE) != PINAX_OK) {
        pinax_str why = pinax_error_text(st);
        printf("%s: %.*s\n", name, (int)why.len, (const char *)why.ptr);
    }
}

int main(void) {
    static unsigned char block[1 << 20];
    pinax_config config = {.struct_size = sizeof config};
    pinax_state *st;
    if (pinax_open((pinax_mem){block, sizeof block}, &config, &st) != PINAX_OK)
        return 1;
    pinax_set_resolver(st, resolve, NULL);
    try_load(st, "main",
             "extern gcd (a int, b int gives int) from \"mathlib\"\n"
             "extern count_vowels (s str gives int) from \"mathlib\" as vowels\n"
             "word answer (gives int) 84 36 gcd \"embeddable\" vowels add end\n");
    pinax_word w;
    int64_t out = 0;
    pinax_find(st, "answer", "(gives int)", 0, &w);
    pinax_run(st, w, &(pinax_args){.ri = &out});
    printf("gcd(84, 36) + vowels(\"embeddable\") = %lld\n", (long long)out);

    try_load(st, "other", "extern sqrt (x int gives int) from \"libm\"\n");   /* the host gives no such thing */
    pinax_close(st);
    return 0;
}

Output

$ ./18-extern
gcd(84, 36) + vowels("embeddable") = 16
other: extern: the host does not give this function

(5) A signature that does not match the C function is undefined behaviour in C, and the processor cannot know. A host that runs scripts it does not trust sets no resolver.

Constraints

(6) A function whose name exists already (a core word, for instance) shall be given another name with as.

18-extern-as.pnx

extern abs (n int gives int) from "libc"

Refused

$ pnx 18-extern-as.pnx
18-extern-as.pnx:1:8: this name is already defined: give the function another with `as`

(7) A signature that (3) does not allow is refused.

18-extern-result.pnx

extern strlen (s cstr gives n int, m int) from "libc"

Refused

$ pnx 18-extern-result.pnx
18-extern-result.pnx:1:8: extern: int, handle, str and cstr arguments (eight for C at most), one int or handle result; or fail only with a handle result
Diagnostics
ConditionMessage
A name that exists, without asthis name is already defined: give the function another with `as`
A signature not allowedextern: int, handle, str and cstr arguments (eight for C at most), one int or handle result; or fail only with a handle result
The host gives no such functionextern: the host does not give this function