NostrScript is a WebAssembly implementation that interacts with Damus. It enables dynamic scripting that can be used to power custom list views, enabling pluggable algorithms. The web has JavaScript, Damus has NostrScript. NostrScripts can be written in any language that compiles to WASM. This commit adds a WASM interpreter I've written as a mostly-single C file for portability and embeddability. In the future we could JIT-compile these for optimal performance if NostrScripts get large and complicated. For now an interpreter is simple enough for algorithm list view plugins. Changelog-Added: Add initial NostrScript implementation Signed-off-by: William Casarin <jb55@jb55.com>
174 lines
4.4 KiB
C
174 lines
4.4 KiB
C
//
|
|
// nostrscript.c
|
|
// damus
|
|
//
|
|
// Created by William Casarin on 2023-06-02.
|
|
//
|
|
|
|
#include "nostrscript.h"
|
|
#include "wasm.h"
|
|
#include "array_size.h"
|
|
|
|
// function to check if the character is in surrogate pair range
|
|
static INLINE int is_surrogate(uint16_t uc) {
|
|
return (uc - 0xd800u) < 2048u;
|
|
}
|
|
|
|
// function to convert utf16 to utf8
|
|
static int utf16_to_utf8(u16 utf16, u8 *utf8) {
|
|
if (utf16 < 0x80) { // 1-byte sequence
|
|
utf8[0] = (uint8_t) utf16;
|
|
return 1;
|
|
}
|
|
else if (utf16 < 0x800) { // 2-byte sequence
|
|
utf8[0] = (uint8_t) (0xc0 | (utf16 >> 6));
|
|
utf8[1] = (uint8_t) (0x80 | (utf16 & 0x3f));
|
|
return 2;
|
|
}
|
|
else if (!is_surrogate(utf16)) { // 3-byte sequence
|
|
utf8[0] = (uint8_t) (0xe0 | (utf16 >> 12));
|
|
utf8[1] = (uint8_t) (0x80 | ((utf16 >> 6) & 0x3f));
|
|
utf8[2] = (uint8_t) (0x80 | (utf16 & 0x3f));
|
|
return 3;
|
|
}
|
|
else { // surrogate pair, return error
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int nostr_cmd(struct wasm_interp *interp) {
|
|
struct val *params = NULL;
|
|
const char *val = NULL;
|
|
int len, cmd, ival;
|
|
|
|
if (!get_params(interp, ¶ms, 3) || params == NULL)
|
|
return interp_error(interp, "get params");
|
|
|
|
// command
|
|
cmd = params[0].num.i32;
|
|
|
|
// value
|
|
|
|
ival = params[1].num.i32;
|
|
if (!mem_ptr_str(interp, ival, &val))
|
|
val = 0;
|
|
|
|
// length
|
|
len = params[2].num.i32;
|
|
|
|
return nscript_nostr_cmd(interp, cmd, val ? (void*)val : (void*)ival, len);
|
|
}
|
|
|
|
static int print_utf16_str(u16 *chars) {
|
|
u16 *p = chars;
|
|
int c;
|
|
|
|
while (*p) {
|
|
if (utf16_to_utf8(*p, (u8*)&c) == -1)
|
|
return 0;
|
|
|
|
printf("%c", c);
|
|
|
|
p++;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int nostr_log(struct wasm_interp *interp) {
|
|
struct val *vals;
|
|
const char *str;
|
|
struct callframe *callframe;
|
|
|
|
if (!get_params(interp, &vals, 1))
|
|
return interp_error(interp, "nostr_log get params");
|
|
|
|
if (!mem_ptr_str(interp, vals[0].num.i32, &str))
|
|
return interp_error(interp, "nostr_log log param");
|
|
|
|
if (!(callframe = top_callframes(&interp->callframes, 2)))
|
|
return interp_error(interp, "nostr_log callframe");
|
|
|
|
printf("nostr_log:%s: ", callframe->func->name);
|
|
|
|
print_utf16_str((u16*)str);
|
|
printf("\n");
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int nostr_pool_send_to(struct wasm_interp *interp) {
|
|
struct val *params = NULL;
|
|
const u16 *req, *to;
|
|
int req_len, to_len;
|
|
|
|
if (!get_params(interp, ¶ms, 4) || params == NULL)
|
|
return 0;
|
|
|
|
if (!mem_ptr_str(interp, params[0].num.i32, (const char**)&req))
|
|
return 0;
|
|
|
|
req_len = params[1].num.i32;
|
|
|
|
if (!mem_ptr_str(interp, params[2].num.i32, (const char**)&to))
|
|
return 0;
|
|
|
|
to_len = params[3].num.i32;
|
|
|
|
return nscript_pool_send_to(interp, req, req_len, to, to_len);
|
|
}
|
|
|
|
static int nscript_abort(struct wasm_interp *interp) {
|
|
struct val *params = NULL;
|
|
const char *msg = "", *filename;
|
|
int line, col;
|
|
|
|
if (!get_params(interp, ¶ms, 4) || params == NULL)
|
|
return interp_error(interp, "get params");
|
|
|
|
if (params[0].ref.addr != 0 && !mem_ptr_str(interp, params[0].ref.addr, &msg))
|
|
return interp_error(interp, "abort msg");
|
|
|
|
if (!mem_ptr_str(interp, params[1].ref.addr, &filename))
|
|
return interp_error(interp, "abort filename");
|
|
|
|
line = params[2].num.i32;
|
|
col = params[3].num.i32;
|
|
|
|
printf("nscript_abort:");
|
|
print_utf16_str((u16*)filename);
|
|
printf(":%d:%d: ", line, col);
|
|
print_utf16_str((u16*)msg);
|
|
printf("\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct builtin nscript_builtins[] = {
|
|
{ .name = "null", .fn = 0 },
|
|
{ .name = "nostr_log", .fn = nostr_log },
|
|
{ .name = "nostr_cmd", .fn = nostr_cmd },
|
|
{ .name = "nostr_pool_send_to", .fn = nostr_pool_send_to },
|
|
{ .name = "abort", .fn = nscript_abort },
|
|
};
|
|
|
|
int nscript_load(struct wasm_parser *p, struct wasm_interp *interp, unsigned char *wasm, unsigned long len) {
|
|
wasm_parser_init(p, wasm, len, len * 16, nscript_builtins, ARRAY_SIZE(nscript_builtins));
|
|
|
|
if (!parse_wasm(p)) {
|
|
wasm_parser_free(p);
|
|
return NSCRIPT_PARSE_ERR;
|
|
}
|
|
|
|
if (!wasm_interp_init(interp, &p->module)) {
|
|
print_error_backtrace(&interp->errors);
|
|
wasm_parser_free(p);
|
|
return NSCRIPT_INIT_ERR;
|
|
}
|
|
|
|
//setup_wasi(&interp, argc, argv, env);
|
|
//wasm_parser_free(&p);
|
|
|
|
return NSCRIPT_LOADED;
|
|
}
|