Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
#include "js_api.h"
// Sometimes you want to access the js "this" value in a function
// function Point(x, y) { this.x = x; this.y = y; }
bool point_constructor(js_context* C) {
js_set(C, 0, "x", 1); // this.x = arguments[0]
js_set(C, 0, "y", 2); // this.y = arguments[1]
return true; // js returns undefined, but C returns true meaning no error.
// Point.prototype.add = function () { var x = this.x | 0; var y = this.y | 0; return x + y; };
bool point_add(js_context* C) {
int32_t x = js_get_int32(C, 0, "x"); // var x = this.x
int32_t y = js_get_int32(C, 0, "y"); // var y = this.y
return js_return_int32(C, x + y); // return x + y
bool export_point(js_context* C) {
int Point = js_create_function(C, point_constructor);
// All functions in JavaScript already have a prototype object. Get it.
int proto = js_get(C, Point, "prototype");
// Add a function to the prototype object
js_set_function(C, proto, "add", &point_add);
// Then return the constructor as the module exports
return js_return(C, Point);
#include <stddef.h> /* size_t */
#include <stdbool.h> /* bool, true, false */
#include <stdint.h> /* uint32_t, etc.. */
typedef struct {
/* private stuff goes in here */
/* This is very opaque and implementation dependent */
} js_context;
// Custom functions have a very simple signature
typedef bool (*js_function)(js_context* C);
// on_gc callback types for external string, binary and pointer types
// No access to the js context is given because you should only clean up memory
// here.
typedef void (*js_string_gc)(char* value, size_t length);
typedef void (*js_string8_gc)(uint8_t* value, size_t length);
typedef void (*js_string16_gc)(uint16_t* value, size_t length);
typedef void (*js_binary_gc)(uint8_t* value, size_t length);
typedef void (*js_pointer_gc)(void* value, const char* type);
// We could maybe have another interface that returned an enum value of the type
typedef enum {
/* ... */
} js_type;
// converting functions. These are used to read a value from a slot and return a C type for it.
// If the value in the slot is not of the right type, they will coerce the value.
int32_t js_to_int32(js_context* C, int slot);
uint32_t js_to_uint32(js_context* C, int slot);
int64_t js_to_int64(js_context* C, int slot);
uint64_t js_to_uint64(js_context* C, int slot);
double js_to_double(js_context* C, int slot);
bool js_to_bool(js_context* C, int slot);
const char* js_to_string(js_context* C, int slot);
const uint8_t* js_to_string8(js_context* C, int slot);
const uint16_t* js_to_string16(js_context* C, int slot);
// The pointer will be 0 in case the type is wrong.
uint8_t* js_to_binary(js_context* C, size_t* len, int slot);
void* js_to_pointer(js_context* C, int slot, const char* type);
// Getting arguments length
size_t js_argc(js_context* c);
// create functions. These are used to create a new js value from a C value.
// They return the slot value of the newly created JS value.
int js_create_int32(js_context* C, int32_t value);
int js_create_uint32(js_context* C, uint32_t value);
int js_create_int64(js_context* C, int64_t value);
int js_create_uint64(js_context* C, uint64_t value);
int js_create_double(js_context* C, double value);
int js_create_bool(js_context* C, bool value);
int js_create_undefined(js_context* C);
int js_create_null(js_context* C);
int js_create_string(js_context* C, char* value);
int js_create_string8(js_context* C, uint8_t* value);
int js_create_string16(js_context* C, uint16_t* value);
int js_create_object(js_context* C);
int js_create_object_with_proto(js_context* C, int proto_slot);
int js_create_array(js_context* C, int length);
int js_create_function(js_context* C, js_function value);
int js_create_closure(js_context* C, js_function value, int scope_slot);
int js_create_error(js_context* C, const char* message, ...);
int js_create_type_error(js_context* C, const char* message, ...);
// Warning: memory returned by these is owned by the VM and may move betweeen
// calls as the GC compacts.
int js_create_binary(js_context* C, size_t len);
int js_create_pointer(js_context* C, size_t len, const char* type);
int js_create_pointer_with_proto(js_context* C, size_t len, const char* type, int proto_slot);
// Creating values around external memory
// Define a couple callback types for the on_gc event to clean up the memory.
int js_create_external_string(js_context* C, char* data, size_t len, js_string_gc on_gc);
int js_create_external_string8(js_context* C, uint8_t* data, size_t len, js_string8_gc on_gc);
int js_create_external_string16(js_context* C, uint16_t* data, size_t len, js_string16_gc on_gc);
int js_create_external_binary(js_context* C, uint8_t* data, size_t len, js_binary_gc on_gc);
int js_create_external_pointer(js_context* C, void* data, const char* type, js_pointer_gc on_gc);
int js_create_external_pointer_with_proto(js_context* C, void* data, const char* type, int proto_slot, js_pointer_gc on_gc);
// Adding arbitrary values to the GC roots (to store inside C structs)
// Add value in slot to set of GC roots and return opaque ref value.
int js_ref(js_context* C, int slot);
// Convert ref value to local slot, but keep referenced.
int js_read_ref(js_context* C, int ref);
// Remove ref value from roots and return local slot.
int js_unref(js_context* C, int ref);
// You can also store named refs in the current context (Like for shared prototypes)
void js_named_ref(js_context* C, int slot, const char* name);
int js_named_read_ref(js_context* C, const char* name);
int js_named_unref(js_context* C, const char* name);
// checking functions. These are used to check what type a slot is.
bool js_is_integer(js_context* C, int slot);
bool js_is_number(js_context* C, int slot);
bool js_is_boolean(js_context* C, int slot);
bool js_is_function(js_context* C, int slot);
bool js_is_null(js_context* C, int slot);
bool js_is_undefined(js_context* C, int slot);
bool js_is_object(js_context* C, int slot);
bool js_is_array(js_context* C, int slot);
bool js_is_binary(js_context* C, int slot);
/* ... */
// Or a more general version that acts like JS typeof
js_type js_get_type(js_context* C, int slot);
int js_check_int32(js_context* C, int32_t* value, int slot);
int js_check_int32(js_context* C, int32_t* value, int slot);
// Object manipulation
int js_set(js_context* C, int object_slot, const char* key, int value_slot); // returns the value slot for chaining
int js_get(js_context* C, int object_slot, const char* key);
int js_set_dynamic(js_context* C, int object_slot, int key_slot, int value_slot); // returns the value slot for chaining
int js_get_dynamic(js_context* C, int object_slot, int key_slot);
// These three could technically be done by calling the existing js global functions
int js_get_proto(js_context* C, int object_slot);
int js_get_keys(js_context* C, int object_slot); // get keys as js array
int js_get_own_props(js_context* C, int object_slot); // get keys as js array
// Array manipulation
int js_array_set(js_context* C, int array_slot, int index, int value_slot); // returns the value slot for chaining
int js_array_get(js_context* C, int array_slot, int index);
int js_array_pop(js_context* C, int array_slot);
int js_array_shift(js_context* C, int array_slot);
int js_array_push(js_context* C, int array_slot, int value_slot); // returns the value slot for chaining
int js_array_unshift(js_context* C, int array_slot, int value_slot); // returns the value slot for chaining
int js_array_remove(js_context* C, int array_slot, int index);
int js_array_insert(js_context* C, int array_slot, int index, int value_slot); // returns the value slot for chaining
size_t js_array_length(js_context* C, int array_slot);
// Global manipulation
int js_global_get(js_context* C, const char* key);
int js_global_set(js_context* C, const char* key, int value_slot); // returns value for chaining
// Closure manipulation
int js_closure_get(js_context* C, const char* key);
// TODO: what should happen when closure_set tries to access an undefined variable name.
int js_closure_set(js_context* C, const char* key, int value_slot); // returns value for chaining
// Function calling
// return value is true on success and false on exception
// value is return value or thrown value
bool js_call(js_context* C, int* result, int function_slot, int argc, const int* args); // call with undefined "this"
bool js_call_bound(js_context* C, int* result, int function_slot, int this_slot, int argc, const int* args); // call with custom "this"
bool js_new(js_context* C, int* result, int constructor_slot, int argc, const int* args); // call as constructor
// These two aren't necessary, but maybe they give enough information to the backend to optimize things?
bool js_call_method(js_context* C, int* result, int object_slot, const char* key, int argc, const int* args);
bool js_call_method_dynamic(js_context* C, int* result, int object_slot, int key_slot, int argc, const int* args);
// Error Handling
// functions return true when then want to return a value, they return false when there is an error.
// The helpers js_return and js_throw help with setting the return value and returning true or false
bool js_return(js_context* C, int value_slot);
bool js_throw(js_context* C, int value_slot);
/* ... */
// Return Macros Make returning various values easier
#define js_return_int32(C, value) js_return(C, js_create_int32(C, value))
#define js_return_uint32(C, value) js_return(C, js_create_uint32(C, value))
#define js_return_int64(C, value) js_return(C, js_create_int64(C, value))
#define js_return_uint64(C, value) js_return(C, js_create_uint64(C, value))
#define js_return_double(C, value) js_return(C, js_create_double(C, value))
#define js_return_bool(C, value) js_return(C, js_create_bool(C, value))
#define js_return_string(C, value) js_return(C, js_create_string(C, value))
#define js_return_string8(C, value) js_return(C, js_create_string8(C, value))
#define js_return_string16(C, value) js_return(C, js_create_string16(C, value))
#define js_return_object(C) js_return(C, js_create_object(C))
#define js_return_object_with_proto(C, proto) js_return(C, js_create_object_with_proto(C, proto))
#define js_return_array(C, length) js_return(C, js_create_array(C, length))
#define js_return_function(C, c_function) js_return(C, js_create_function(C, c_function))
#define js_return_closure(C, c_function, scope) js_return(C, js_create_closure(C, c_function, scope))
#define js_return_external_pointer_with_proto(C, value, type, proto, on_gc) \
js_return(C, js_create_external_pointer_with_proto(C, value, type, proto, on_gc))
// ...
// Helpers for throwing error and type errors
#define js_throw_error(C, ...) js_throw(C, js_create_error(C, __VA_ARGS__))
#define js_throw_type_error(C, ...) js_throw(C, js_create_type_error(C, __VA_ARGS__))
// Set Macros. Making setting object properties easier.
#define js_set_int32(C, obj, key, value) js_set(C, obj, key, js_create_int32(C, value))
#define js_set_function(C, obj, key, value) js_set(C, obj, key, js_create_function(C, value))
// ... Same thing for all other js_create_* functions ...
// Get Macros. Making getting object properties easier.
#define js_get_int32(C, obj, key) js_to_int32(C, js_get(C, obj, key))
#define js_get_double(C, obj, key) js_to_double(C, js_get(C, obj, key))
#define js_get_pointer(C, obj, key, type) js_to_pointer(C, js_get(C, obj, key), type)
// ... Same thing for all other js_to_* functions ...
#include "js_api.h"
#include <stdlib.h> // malloc, free
// Suppose I want to create a point type that's backed by a real C struct for points.
typedef struct {
double x;
double y;
} my_point;
void cleanup_point(void* point, const char* type) {
bool create_point(js_context* C) {
my_point* point = (my_point*)malloc(sizeof(my_point));
point->x = js_to_double(C, 1);
point->y = js_to_double(C, 2);
int proto = js_closure_get(C, "proto");
return js_return_external_pointer_with_proto(C, point, "my_point", proto, cleanup_point);
bool add_method(js_context* C) {
my_point* point = (my_point*)js_to_pointer(C, 0, "my_point");
if (!point) {
return js_throw_type_error(C, "Expected this to be 'my_point' instance");
return js_return_double(C, point->x * point->y);
bool export_point(js_context* C) {
int proto = js_create_object(C);
js_set_function(C, proto, "add", add_method);
int scope = js_create_object(C);
js_set(C, scope, "proto", proto);
return js_return_closure(C, create_point, scope);
#include "js_api.h"
// Equal to the following JS snippet:
// function addNumbers(a, b) { a |= 0; b |= 0; return a + b; }
bool simple_add_numbers(js_context* C) {
int32_t a = js_to_int32(C, 1); // Coerce whatever value is in arguments[0] to an integer and return the value
int32_t b = js_to_int32(C, 2); // Same for arguments[1]
return js_return_int32(C, a + b); // Create a new javascript number and return it. (probably -1 since this is the first pushed value)
// The actual value returned is a local position of the new js value, not a real C pointer.
// So you could return arguments as well by doing `return js_return(C, 1)` to return the first arg.
// Export the function directly
bool export_simple(js_context* C) {
return js_return_function(C, simple_add_numbers);
// If we wanted to export an object of functions instead, we could do:
// The module exports an object with functions on it.
bool export_simple2(js_context* C) {
int exports = js_create_object(C); // Create a new object and get a local handle
js_set_function(C, exports, "addNumbers", simple_add_numbers);
return js_return(C, exports);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment