ludus/build/driver.cpp
2024-06-06 18:47:04 -04:00

144 lines
4.4 KiB
C++

#include <cstdint>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <string>
#include <stdio.h>
#include "janet.h"
using std::string;
// set all our exported Janet functions as null pointers
static JanetFunction *janet_ludus = NULL;
static JanetFunction *janet_hello = NULL;
// these let us look up functions
Janet env_lookup(JanetTable *env, const char *name) {
Janet out;
janet_resolve(env, janet_csymbol(name), &out);
return out;
}
JanetFunction *env_lookup_function(JanetTable *env, const char *name) {
Janet value = env_lookup(env, name);
if (!janet_checktype(value, JANET_FUNCTION)) {
janet_panicf("expected %s to be a function, got %q\n", name, value);
}
return janet_unwrap_function(value);
}
// this lets us call a function
bool call_fn(JanetFunction *fn, int argc, const Janet *argv, Janet *out) {
JanetFiber *fiber = NULL;
if (janet_pcall(fn, argc, argv, out, &fiber) == JANET_SIGNAL_OK) {
return true;
} else {
janet_stacktrace(fiber, *out);
return false;
}
}
// this is darkish magic, reads an embedded file
// do not fuck with this, fellas
unsigned char *read_file(const char *filename, size_t *length) {
size_t capacity = 2 << 17;
unsigned char *src = (unsigned char *)malloc(capacity * sizeof(unsigned char));
assert(src);
size_t total_bytes_read = 0;
FILE *file = fopen(filename, "r");
assert(file);
size_t bytes_read;
do {
size_t remaining_capacity = capacity - total_bytes_read;
if (remaining_capacity == 0) {
capacity <<= 1;
src = (unsigned char*)realloc(src, capacity * sizeof(unsigned char));
assert(src);
remaining_capacity = capacity - total_bytes_read;
}
bytes_read = fread(&src[total_bytes_read], sizeof(unsigned char), remaining_capacity, file);
total_bytes_read += bytes_read;
} while (bytes_read > 0);
fclose(file);
*length = total_bytes_read;
return src;
}
// these are the C++ functions that wrap our Janet functions
// simplest case: takes nothing, returns nothing
void hello() {
Janet result; // we need a place to put the result
// args are: fn ptr, argc, argv, result
call_fn(janet_hello, 0, {}, &result);
}
// finally, getting a string back
// this is our result type
struct StringResult {
string value;
};
// this is our result constructor
// Janet's getcstring resturns const char*
StringResult string_result(const char* cstr) {
// ...which we have to cast to a std::string
return (StringResult) {.value = (string) cstr };
}
// and this is a function that takes and returns a string
// it returns a StringResult, tho
StringResult ludus(string source) {
Janet result;
const Janet args[1] = {janet_cstringv(source.c_str())};
call_fn(janet_ludus, 1, args, &result);
// get the cstring in the result
// the 0 passed here is the index in the result of the string
const char* cstr = janet_getcstring(&result, 0);
// return a constructed StringResult
return string_result(cstr);
}
// This function sets up our Janet interpreter, and fixes the null pointers
EMSCRIPTEN_KEEPALIVE
int main() {
janet_init(); // start the interpreter
JanetTable *core_env = janet_core_env(NULL); // get a core env
JanetTable *lookup = janet_env_lookup(core_env); // and get an env table
// load the janet image into memory
// note that the image is hardcoded here
size_t image_length;
unsigned char *image = read_file("ludus.jimage", &image_length);
// load the image into the Janet environment
Janet env = janet_unmarshal(image, image_length, 0, lookup, NULL);
if(!janet_checktype(env, JANET_TABLE)) {
janet_panicf("invalid image %q", env);
}
// fix the null pointers, as above
// note that the bound symbols are just the normal fn names
// no namespacing
janet_ludus = env_lookup_function(janet_unwrap_table(env), "ludus");
janet_gcroot(janet_wrap_function(janet_ludus));
janet_hello = env_lookup_function(janet_unwrap_table(env), "hello");
janet_gcroot(janet_wrap_function(janet_hello));
}
// these bindings are exported into javascript
EMSCRIPTEN_BINDINGS(module) {
using namespace emscripten;
// these are the functions that will be available
function("ludus", &ludus, allow_raw_pointers());
function("hello", &hello, allow_raw_pointers());
// we also want a wrapper for our StringResult
// we won't access it directly, but emcc makes it nice
value_object<StringResult>("StringResult")
.field("value", &StringResult::value);
}