This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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_NUMBER, | |
JS_STRING, | |
JS_OBJECT | |
/* ... */ | |
} 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 ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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) { | |
free(point); | |
} | |
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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