1
0
mirror of git://jb55.com/damus synced 2024-09-29 16:30:44 +00:00

NostrScript

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>
This commit is contained in:
William Casarin 2023-06-02 18:51:49 -07:00
parent 0c0c58c0cc
commit 97f10e865f
31 changed files with 9623 additions and 117 deletions

8
Makefile Normal file
View File

@ -0,0 +1,8 @@
all: nostrscript/primal.wasm
nostrscript/%.wasm: nostrscript/%.ts nostrscript/nostr.ts Makefile
asc $< --runtime stub --outFile $@ --optimize
clean:
rm nostrscript/*.wasm

View File

@ -35,7 +35,7 @@ typedef struct mention_bech32_block {
struct nostr_bech32 bech32;
} mention_bech32_block_t;
typedef struct block {
typedef struct note_block {
enum block_type type;
union {
struct str_block str;
@ -45,12 +45,12 @@ typedef struct block {
} block;
} block_t;
typedef struct blocks {
typedef struct note_blocks {
int num_blocks;
struct block *blocks;
struct note_block *blocks;
} blocks_t;
void blocks_init(struct blocks *blocks);
void blocks_free(struct blocks *blocks);
void blocks_init(struct note_blocks *blocks);
void blocks_free(struct note_blocks *blocks);
#endif /* block_h */

View File

@ -1,24 +1,432 @@
//
// cursor.h
// damus
//
// Created by William Casarin on 2023-04-09.
//
#ifndef cursor_h
#define cursor_h
#ifndef PROTOVERSE_CURSOR_H
#define PROTOVERSE_CURSOR_H
#include "typedefs.h"
#include "varint.h"
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
typedef unsigned char u8;
#define unlikely(x) __builtin_expect((x),0)
#define likely(x) __builtin_expect((x),1)
struct cursor {
const u8 *p;
const u8 *start;
const u8 *end;
unsigned char *start;
unsigned char *p;
unsigned char *end;
};
struct array {
struct cursor cur;
unsigned int elem_size;
};
static inline void reset_cursor(struct cursor *cursor)
{
cursor->p = cursor->start;
}
static inline void wipe_cursor(struct cursor *cursor)
{
reset_cursor(cursor);
memset(cursor->start, 0, cursor->end - cursor->start);
}
static inline void make_cursor(u8 *start, u8 *end, struct cursor *cursor)
{
cursor->start = start;
cursor->p = start;
cursor->end = end;
}
static inline void make_array(struct array *a, u8* start, u8 *end, unsigned int elem_size)
{
make_cursor(start, end, &a->cur);
a->elem_size = elem_size;
}
static inline int cursor_eof(struct cursor *c)
{
return c->p == c->end;
}
static inline void *cursor_malloc(struct cursor *mem, unsigned long size)
{
void *ret;
if (mem->p + size > mem->end) {
return NULL;
}
ret = mem->p;
mem->p += size;
return ret;
}
static inline void *cursor_alloc(struct cursor *mem, unsigned long size)
{
void *ret;
if (!(ret = cursor_malloc(mem, size))) {
return 0;
}
memset(ret, 0, size);
return ret;
}
static inline int cursor_slice(struct cursor *mem, struct cursor *slice, size_t size)
{
u8 *p;
if (!(p = cursor_alloc(mem, size))) {
return 0;
}
make_cursor(p, mem->p, slice);
return 1;
}
static inline void copy_cursor(struct cursor *src, struct cursor *dest)
{
dest->start = src->start;
dest->p = src->p;
dest->end = src->end;
}
static inline int pull_byte(struct cursor *cursor, u8 *c)
{
if (unlikely(cursor->p >= cursor->end))
return 0;
*c = *cursor->p;
cursor->p++;
return 1;
}
static inline int cursor_pull_c_str(struct cursor *cursor, const char **str)
{
*str = (const char*)cursor->p;
for (; cursor->p < cursor->end; cursor->p++) {
if (*cursor->p == 0) {
cursor->p++;
return 1;
}
}
return 0;
}
static inline int cursor_push_byte(struct cursor *cursor, u8 c)
{
if (unlikely(cursor->p + 1 > cursor->end)) {
return 0;
}
*cursor->p = c;
cursor->p++;
return 1;
}
static inline int cursor_pull(struct cursor *cursor, u8 *data, int len)
{
if (unlikely(cursor->p + len > cursor->end)) {
return 0;
}
memcpy(data, cursor->p, len);
cursor->p += len;
return 1;
}
static inline int pull_data_into_cursor(struct cursor *cursor,
struct cursor *dest,
unsigned char **data,
int len)
{
int ok;
if (unlikely(dest->p + len > dest->end)) {
printf("not enough room in dest buffer\n");
return 0;
}
ok = cursor_pull(cursor, dest->p, len);
if (!ok) return 0;
*data = dest->p;
dest->p += len;
return 1;
}
static inline int cursor_dropn(struct cursor *cur, int size, int n)
{
if (n == 0)
return 1;
if (unlikely(cur->p - size*n < cur->start)) {
return 0;
}
cur->p -= size*n;
return 1;
}
static inline int cursor_drop(struct cursor *cur, int size)
{
return cursor_dropn(cur, size, 1);
}
static inline unsigned char *cursor_topn(struct cursor *cur, int len, int n)
{
n += 1;
if (unlikely(cur->p - len*n < cur->start)) {
return NULL;
}
return cur->p - len*n;
}
static inline unsigned char *cursor_top(struct cursor *cur, int len)
{
if (unlikely(cur->p - len < cur->start)) {
return NULL;
}
return cur->p - len;
}
static inline int cursor_top_int(struct cursor *cur, int *i)
{
u8 *p;
if (unlikely(!(p = cursor_top(cur, sizeof(*i))))) {
return 0;
}
*i = *((int*)p);
return 1;
}
static inline int cursor_pop(struct cursor *cur, u8 *data, int len)
{
if (unlikely(cur->p - len < cur->start)) {
return 0;
}
cur->p -= len;
memcpy(data, cur->p, len);
return 1;
}
static inline int cursor_push(struct cursor *cursor, u8 *data, int len)
{
if (unlikely(cursor->p + len >= cursor->end)) {
return 0;
}
if (cursor->p != data)
memcpy(cursor->p, data, len);
cursor->p += len;
return 1;
}
static inline int cursor_push_int(struct cursor *cursor, int i)
{
return cursor_push(cursor, (u8*)&i, sizeof(i));
}
static inline size_t cursor_count(struct cursor *cursor, size_t elem_size)
{
return (cursor->p - cursor->start)/elem_size;
}
/* TODO: push_varint */
static inline int push_varint(struct cursor *cursor, int n)
{
int ok, len;
unsigned char b;
len = 0;
while (1) {
b = (n & 0xFF) | 0x80;
n >>= 7;
if (n == 0) {
b &= 0x7F;
ok = cursor_push_byte(cursor, b);
len++;
if (!ok) return 0;
break;
}
ok = cursor_push_byte(cursor, b);
len++;
if (!ok) return 0;
}
return len;
}
/* TODO: pull_varint */
static inline int pull_varint(struct cursor *cursor, int *n)
{
int ok, i;
unsigned char b;
*n = 0;
for (i = 0;; i++) {
ok = pull_byte(cursor, &b);
if (!ok) return 0;
*n |= ((int)b & 0x7F) << (i * 7);
/* is_last */
if ((b & 0x80) == 0) {
return i+1;
}
if (i == 4) return 0;
}
return 0;
}
static inline int cursor_pull_int(struct cursor *cursor, int *i)
{
return cursor_pull(cursor, (u8*)i, sizeof(*i));
}
static inline int cursor_push_u16(struct cursor *cursor, u16 i)
{
return cursor_push(cursor, (u8*)&i, sizeof(i));
}
static inline void *index_cursor(struct cursor *cursor, unsigned int index, int elem_size)
{
u8 *p;
p = &cursor->start[elem_size * index];
if (unlikely(p >= cursor->end))
return NULL;
return (void*)p;
}
static inline int push_sized_str(struct cursor *cursor, const char *str, int len)
{
return cursor_push(cursor, (u8*)str, len);
}
static inline int cursor_push_str(struct cursor *cursor, const char *str)
{
return cursor_push(cursor, (u8*)str, (int)strlen(str));
}
static inline int cursor_push_c_str(struct cursor *cursor, const char *str)
{
return cursor_push_str(cursor, str) && cursor_push_byte(cursor, 0);
}
/* TODO: push varint size */
static inline int push_prefixed_str(struct cursor *cursor, const char *str)
{
int ok, len;
len = (int)strlen(str);
ok = push_varint(cursor, len);
if (!ok) return 0;
return push_sized_str(cursor, str, len);
}
static inline int pull_prefixed_str(struct cursor *cursor, struct cursor *dest_buf, const char **str)
{
int len, ok;
ok = pull_varint(cursor, &len);
if (!ok) return 0;
if (unlikely(dest_buf->p + len > dest_buf->end)) {
return 0;
}
ok = pull_data_into_cursor(cursor, dest_buf, (unsigned char**)str, len);
if (!ok) return 0;
ok = cursor_push_byte(dest_buf, 0);
return 1;
}
static inline int cursor_remaining_capacity(struct cursor *cursor)
{
return (int)(cursor->end - cursor->p);
}
#define max(a,b) ((a) > (b) ? (a) : (b))
static inline void cursor_print_around(struct cursor *cur, int range)
{
unsigned char *c;
printf("[%ld/%ld]\n", cur->p - cur->start, cur->end - cur->start);
c = max(cur->p - range, cur->start);
for (; c < cur->end && c < (cur->p + range); c++) {
printf("%02x", *c);
}
printf("\n");
c = max(cur->p - range, cur->start);
for (; c < cur->end && c < (cur->p + range); c++) {
if (c == cur->p) {
printf("^");
continue;
}
printf(" ");
}
printf("\n");
}
#undef max
static inline int pull_bytes(struct cursor *cur, int count, const u8 **bytes) {
if (cur->p + count > cur->end)
return 0;
*bytes = cur->p;
cur->p += count;
return 1;
}
static inline int parse_str(struct cursor *cur, const char *str) {
int i;
char c, cs;
unsigned long len;
len = strlen(str);
if (cur->p + len >= cur->end)
return 0;
for (i = 0; i < len; i++) {
c = tolower(cur->p[i]);
cs = tolower(str[i]);
if (c != cs)
return 0;
}
cur->p += len;
return 1;
}
static inline int is_whitespace(char c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
}
@ -35,13 +443,6 @@ static inline int is_alphanumeric(char c) {
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
}
static inline void make_cursor(struct cursor *c, const u8 *content, size_t len)
{
c->start = content;
c->end = content + len;
c->p = content;
}
static inline int consume_until_boundary(struct cursor *cur) {
char c;
@ -110,47 +511,4 @@ static inline int peek_char(struct cursor *cur, int ind) {
return *(cur->p + ind);
}
static inline int pull_byte(struct cursor *cur, u8 *byte) {
if (cur->p >= cur->end)
return 0;
*byte = *cur->p;
cur->p++;
return 1;
}
static inline int pull_bytes(struct cursor *cur, int count, const u8 **bytes) {
if (cur->p + count > cur->end)
return 0;
*bytes = cur->p;
cur->p += count;
return 1;
}
static inline int parse_str(struct cursor *cur, const char *str) {
int i;
char c, cs;
unsigned long len;
len = strlen(str);
if (cur->p + len >= cur->end)
return 0;
for (i = 0; i < len; i++) {
c = tolower(cur->p[i]);
cs = tolower(str[i]);
if (c != cs)
return 0;
}
cur->p += len;
return 1;
}
#endif /* cursor_h */
#endif

View File

@ -5,3 +5,7 @@
#include "damus.h"
#include "bolt11.h"
#include "amount.h"
#include "nostr_bech32.h"
#include "wasm.h"
#include "nostrscript.h"

View File

@ -28,9 +28,9 @@ static int parse_digit(struct cursor *cur, int *digit) {
}
static int parse_mention_index(struct cursor *cur, struct block *block) {
static int parse_mention_index(struct cursor *cur, struct note_block *block) {
int d1, d2, d3, ind;
const u8 *start = cur->p;
u8 *start = cur->p;
if (!parse_str(cur, "#["))
return 0;
@ -59,9 +59,9 @@ static int parse_mention_index(struct cursor *cur, struct block *block) {
return 1;
}
static int parse_hashtag(struct cursor *cur, struct block *block) {
static int parse_hashtag(struct cursor *cur, struct note_block *block) {
int c;
const u8 *start = cur->p;
u8 *start = cur->p;
if (!parse_char(cur, '#'))
return 0;
@ -81,7 +81,7 @@ static int parse_hashtag(struct cursor *cur, struct block *block) {
return 1;
}
static int add_block(struct blocks *blocks, struct block block)
static int add_block(struct note_blocks *blocks, struct note_block block)
{
if (blocks->num_blocks + 1 >= MAX_BLOCKS)
return 0;
@ -90,9 +90,9 @@ static int add_block(struct blocks *blocks, struct block block)
return 1;
}
static int add_text_block(struct blocks *blocks, const u8 *start, const u8 *end)
static int add_text_block(struct note_blocks *blocks, const u8 *start, const u8 *end)
{
struct block b;
struct note_block b;
if (start == end)
return 1;
@ -104,8 +104,8 @@ static int add_text_block(struct blocks *blocks, const u8 *start, const u8 *end)
return add_block(blocks, b);
}
static int parse_url(struct cursor *cur, struct block *block) {
const u8 *start = cur->p;
static int parse_url(struct cursor *cur, struct note_block *block) {
u8 *start = cur->p;
if (!parse_str(cur, "http"))
return 0;
@ -137,8 +137,8 @@ static int parse_url(struct cursor *cur, struct block *block) {
return 1;
}
static int parse_invoice(struct cursor *cur, struct block *block) {
const u8 *start, *end;
static int parse_invoice(struct cursor *cur, struct note_block *block) {
u8 *start, *end;
char *fail;
struct bolt11 *bolt11;
// optional
@ -177,8 +177,8 @@ static int parse_invoice(struct cursor *cur, struct block *block) {
}
static int parse_mention_bech32(struct cursor *cur, struct block *block) {
const u8 *start = cur->p;
static int parse_mention_bech32(struct cursor *cur, struct note_block *block) {
u8 *start = cur->p;
if (!parse_str(cur, "nostr:"))
return 0;
@ -197,7 +197,7 @@ static int parse_mention_bech32(struct cursor *cur, struct block *block) {
return 1;
}
static int add_text_then_block(struct cursor *cur, struct blocks *blocks, struct block block, const u8 **start, const u8 *pre_mention)
static int add_text_then_block(struct cursor *cur, struct note_blocks *blocks, struct note_block block, u8 **start, const u8 *pre_mention)
{
if (!add_text_block(blocks, *start, pre_mention))
return 0;
@ -210,14 +210,14 @@ static int add_text_then_block(struct cursor *cur, struct blocks *blocks, struct
return 1;
}
int damus_parse_content(struct blocks *blocks, const char *content) {
int damus_parse_content(struct note_blocks *blocks, const char *content) {
int cp, c;
struct cursor cur;
struct block block;
const u8 *start, *pre_mention;
struct note_block block;
u8 *start, *pre_mention;
blocks->num_blocks = 0;
make_cursor(&cur, (const u8*)content, strlen(content));
make_cursor((u8*)content, (u8*)content + strlen(content), &cur);
start = cur.p;
while (cur.p < cur.end && blocks->num_blocks < MAX_BLOCKS) {
@ -256,12 +256,12 @@ int damus_parse_content(struct blocks *blocks, const char *content) {
return 1;
}
void blocks_init(struct blocks *blocks) {
blocks->blocks = malloc(sizeof(struct block) * MAX_BLOCKS);
void blocks_init(struct note_blocks *blocks) {
blocks->blocks = malloc(sizeof(struct note_block) * MAX_BLOCKS);
blocks->num_blocks = 0;
}
void blocks_free(struct blocks *blocks) {
void blocks_free(struct note_blocks *blocks) {
if (!blocks->blocks) {
return;
}

View File

@ -9,10 +9,10 @@
#define damus_h
#include <stdio.h>
#include "nostr_bech32.h"
#include "block.h"
typedef unsigned char u8;
int damus_parse_content(struct blocks *blocks, const char *content);
int damus_parse_content(struct note_blocks *blocks, const char *content);
#endif /* damus_h */

15
damus-c/debug.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef PROTOVERSE_DEBUG_H
#define PROTOVERSE_DEBUG_H
#include <stdio.h>
#define unusual(...) fprintf(stderr, "UNUSUAL: " __VA_ARGS__)
#ifdef DEBUG
#define debug(...) printf(__VA_ARGS__)
#else
#define debug(...)
#endif
#endif /* PROTOVERSE_DEBUG_H */

34
damus-c/error.c Normal file
View File

@ -0,0 +1,34 @@
#include "error.h"
#include <stdlib.h>
#include <stdarg.h>
int note_error_(struct errors *errs_, struct cursor *p, const char *fmt, ...)
{
static char buf[512];
struct error err;
struct cursor *errs;
va_list ap;
errs = &errs_->cur;
if (errs_->enabled == 0)
return 0;
va_start(ap, fmt);
vsprintf(buf, fmt, ap);
va_end(ap);
err.msg = buf;
err.pos = p ? (int)(p->p - p->start) : 0;
if (!cursor_push_error(errs, &err)) {
fprintf(stderr, "arena OOM when recording error, ");
fprintf(stderr, "errs->p at %ld, remaining %ld, strlen %ld\n",
errs->p - errs->start, errs->end - errs->p, strlen(buf));
}
return 0;
}

33
damus-c/error.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef PROTOVERSE_ERROR_H
#define PROTOVERSE_ERROR_H
#include "cursor.h"
struct error {
int pos;
const char *msg;
};
struct errors {
struct cursor cur;
int enabled;
};
#define note_error(errs, p, fmt, ...) note_error_(errs, p, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
static inline int cursor_push_error(struct cursor *cur, struct error *err)
{
return cursor_push_int(cur, err->pos) &&
cursor_push_c_str(cur, err->msg);
}
static inline int cursor_pull_error(struct cursor *cur, struct error *err)
{
return cursor_pull_int(cur, &err->pos) &&
cursor_pull_c_str(cur, &err->msg);
}
int note_error_(struct errors *errs, struct cursor *p, const char *fmt, ...);
#endif /* PROTOVERSE_ERROR_H */

View File

@ -52,9 +52,13 @@
*/
#define unlikely(cond) __builtin_expect(!!(cond), 0)
#else
#ifndef likely
#define likely(cond) (!!(cond))
#endif
#ifndef unlikely
#define unlikely(cond) (!!(cond))
#endif
#endif
#else /* CCAN_LIKELY_DEBUG versions */
#include <ccan/str/str.h>

View File

@ -218,7 +218,7 @@ static int parse_nostr_bech32_nrelay(struct cursor *cur, struct bech32_nrelay *n
}
int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) {
const u8 *start, *end;
u8 *start, *end;
start = cur->p;
@ -257,7 +257,7 @@ int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) {
}
struct cursor bcur;
make_cursor(&bcur, obj->buffer, obj->buflen);
make_cursor(obj->buffer, obj->buffer + obj->buflen, &bcur);
switch (obj->type) {
case NOSTR_BECH32_NOTE:

173
damus-c/nostrscript.c Normal file
View File

@ -0,0 +1,173 @@
//
// 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, &params, 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, &params, 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, &params, 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;
}

23
damus-c/nostrscript.h Normal file
View File

@ -0,0 +1,23 @@
//
// nostrscript.h
// damus
//
// Created by William Casarin on 2023-06-02.
//
#ifndef nostrscript_h
#define nostrscript_h
#define NSCRIPT_LOADED 1
#define NSCRIPT_PARSE_ERR 2
#define NSCRIPT_INIT_ERR 3
#include <stdio.h>
#include "wasm.h"
int nscript_load(struct wasm_parser *p, struct wasm_interp *interp, unsigned char *wasm, unsigned long len);
int nscript_nostr_cmd(struct wasm_interp *interp, int, void*, int);
int nscript_pool_send_to(struct wasm_interp *interp, const u16*, int, const u16 *, int);
#endif /* nostrscript_h */

42
damus-c/parser.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef CURSOR_PARSER
#define CURSOR_PARSER
#include "cursor.h"
static int consume_bytes(struct cursor *cursor, const unsigned char *match, int len)
{
int i;
if (cursor->p + len > cursor->end) {
fprintf(stderr, "consume_bytes overflow\n");
return 0;
}
for (i = 0; i < len; i++) {
if (cursor->p[i] != match[i])
return 0;
}
cursor->p += len;
return 1;
}
static inline int consume_byte(struct cursor *cursor, unsigned char match)
{
if (unlikely(cursor->p >= cursor->end))
return 0;
if (*cursor->p != match)
return 0;
cursor->p++;
return 1;
}
static inline int consume_u32(struct cursor *cursor, unsigned int match)
{
return consume_bytes(cursor, (unsigned char*)&match, sizeof(match));
}
#endif /* CURSOR_PARSER */

14
damus-c/typedefs.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef PROTOVERSE_TYPEDEFS_H
#define PROTOVERSE_TYPEDEFS_H
#include <stdint.h>
typedef unsigned char u8;
typedef unsigned int u32;
typedef unsigned short u16;
typedef uint64_t u64;
typedef int64_t s64;
#endif /* PROTOVERSE_TYPEDEFS_H */

14
damus-c/varint.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef PROTOVERSE_VARINT_H
#define PROTOVERSE_VARINT_H
#define VARINT_MAX_LEN 9
#include <stddef.h>
#include <stdint.h>
size_t varint_put(unsigned char buf[VARINT_MAX_LEN], uint64_t v);
size_t varint_size(uint64_t v);
size_t varint_get(const unsigned char *p, size_t max, int64_t *val);
#endif /* PROTOVERSE_VARINT_H */

7293
damus-c/wasm.c Normal file

File diff suppressed because it is too large Load Diff

840
damus-c/wasm.h Normal file
View File

@ -0,0 +1,840 @@
#ifndef PROTOVERSE_WASM_H
#define PROTOVERSE_WASM_H
static const unsigned char WASM_MAGIC[] = {0,'a','s','m'};
#define WASM_VERSION 0x01
#define MAX_U32_LEB128_BYTES 5
#define MAX_U64_LEB128_BYTES 10
#define MAX_CUSTOM_SECTIONS 32
#define MAX_BUILTINS 64
#define BUILTIN_SUSPEND 42
#define FUNC_TYPE_TAG 0x60
#include "cursor.h"
#include "error.h"
#ifdef NOINLINE
#define INLINE __attribute__((noinline))
#else
#define INLINE inline
#endif
#define interp_error(p, fmt, ...) note_error(&((p)->errors), interp_codeptr(p), fmt, ##__VA_ARGS__)
#define parse_err(p, fmt, ...) note_error(&((p)->errs), &(p)->cur, fmt, ##__VA_ARGS__)
enum valtype {
val_i32 = 0x7F,
val_i64 = 0x7E,
val_f32 = 0x7D,
val_f64 = 0x7C,
val_ref_null = 0xD0,
val_ref_func = 0x70,
val_ref_extern = 0x6F,
};
enum const_instr {
ci_const_i32 = 0x41,
ci_const_i64 = 0x42,
ci_const_f32 = 0x43,
ci_const_f64 = 0x44,
ci_ref_null = 0xD0,
ci_ref_func = 0xD2,
ci_global_get = 0x23,
ci_end = 0x0B,
};
enum limit_type {
limit_min = 0x00,
limit_min_max = 0x01,
};
struct limits {
u32 min;
u32 max;
enum limit_type type;
};
enum section_tag {
section_custom,
section_type,
section_import,
section_function,
section_table,
section_memory,
section_global,
section_export,
section_start,
section_element,
section_code,
section_data,
section_data_count,
section_name,
num_sections,
};
enum name_subsection_tag {
name_subsection_module,
name_subsection_funcs,
name_subsection_locals,
num_name_subsections,
};
enum reftype {
funcref = 0x70,
externref = 0x6F,
};
struct resulttype {
unsigned char *valtypes; /* enum valtype */
u32 num_valtypes;
};
struct functype {
struct resulttype params;
struct resulttype result;
};
struct table {
enum reftype reftype;
struct limits limits;
};
struct tablesec {
struct table *tables;
u32 num_tables;
};
enum elem_mode {
elem_mode_passive,
elem_mode_active,
elem_mode_declarative,
};
struct expr {
u8 *code;
u32 code_len;
};
struct refval {
u32 addr;
};
struct table_inst {
struct refval *refs;
enum reftype reftype;
u32 num_refs;
};
struct numval {
union {
int i32;
u32 u32;
int64_t i64;
uint64_t u64;
float f32;
double f64;
};
};
struct val {
enum valtype type;
union {
struct numval num;
struct refval ref;
};
};
struct elem_inst {
struct val val;
u16 elem;
u16 init;
};
struct elem {
struct expr offset;
u32 tableidx;
struct expr *inits;
u32 num_inits;
enum elem_mode mode;
enum reftype reftype;
struct val val;
};
struct customsec {
const char *name;
unsigned char *data;
u32 data_len;
};
struct elemsec {
struct elem *elements;
u32 num_elements;
};
struct memsec {
struct limits *mems; /* memtype */
u32 num_mems;
};
struct funcsec {
u32 *type_indices;
u32 num_indices;
};
enum mut {
mut_const,
mut_var,
};
struct globaltype {
enum valtype valtype;
enum mut mut;
};
struct globalsec {
struct global *globals;
u32 num_globals;
};
struct typesec {
struct functype *functypes;
u32 num_functypes;
};
enum import_type {
import_func,
import_table,
import_mem,
import_global,
};
struct importdesc {
enum import_type type;
union {
u32 typeidx;
struct limits tabletype;
struct limits memtype;
struct globaltype globaltype;
};
};
struct import {
const char *module_name;
const char *name;
struct importdesc desc;
int resolved_builtin;
};
struct importsec {
struct import *imports;
u32 num_imports;
};
struct global {
struct globaltype type;
struct expr init;
struct val val;
};
struct local_def {
u32 num_types;
enum valtype type;
};
/* "code" */
struct wasm_func {
struct expr code;
struct local_def *local_defs;
u32 num_local_defs;
};
enum func_type {
func_type_wasm,
func_type_builtin,
};
struct func {
union {
struct wasm_func *wasm_func;
struct builtin *builtin;
};
u32 num_locals;
struct functype *functype;
enum func_type type;
const char *name;
u32 idx;
};
struct codesec {
struct wasm_func *funcs;
u32 num_funcs;
};
enum exportdesc {
export_func,
export_table,
export_mem,
export_global,
};
struct wexport {
const char *name;
u32 index;
enum exportdesc desc;
};
struct exportsec {
struct wexport *exports;
u32 num_exports;
};
struct nameassoc {
u32 index;
const char *name;
};
struct namemap {
struct nameassoc *names;
u32 num_names;
};
struct namesec {
const char *module_name;
struct namemap func_names;
int parsed;
};
struct wsection {
enum section_tag tag;
};
enum bulk_tag {
i_memory_copy = 10,
i_memory_fill = 11,
i_table_init = 12,
i_elem_drop = 13,
i_table_copy = 14,
i_table_grow = 15,
i_table_size = 16,
i_table_fill = 17,
};
enum instr_tag {
/* control instructions */
i_unreachable = 0x00,
i_nop = 0x01,
i_block = 0x02,
i_loop = 0x03,
i_if = 0x04,
i_else = 0x05,
i_end = 0x0B,
i_br = 0x0C,
i_br_if = 0x0D,
i_br_table = 0x0E,
i_return = 0x0F,
i_call = 0x10,
i_call_indirect = 0x11,
/* parametric instructions */
i_drop = 0x1A,
i_select = 0x1B,
i_selects = 0x1C,
/* variable instructions */
i_local_get = 0x20,
i_local_set = 0x21,
i_local_tee = 0x22,
i_global_get = 0x23,
i_global_set = 0x24,
i_table_get = 0x25,
i_table_set = 0x26,
/* memory instructions */
i_i32_load = 0x28,
i_i64_load = 0x29,
i_f32_load = 0x2A,
i_f64_load = 0x2B,
i_i32_load8_s = 0x2C,
i_i32_load8_u = 0x2D,
i_i32_load16_s = 0x2E,
i_i32_load16_u = 0x2F,
i_i64_load8_s = 0x30,
i_i64_load8_u = 0x31,
i_i64_load16_s = 0x32,
i_i64_load16_u = 0x33,
i_i64_load32_s = 0x34,
i_i64_load32_u = 0x35,
i_i32_store = 0x36,
i_i64_store = 0x37,
i_f32_store = 0x38,
i_f64_store = 0x39,
i_i32_store8 = 0x3A,
i_i32_store16 = 0x3B,
i_i64_store8 = 0x3C,
i_i64_store16 = 0x3D,
i_i64_store32 = 0x3E,
i_memory_size = 0x3F,
i_memory_grow = 0x40,
/* numeric instructions */
i_i32_const = 0x41,
i_i64_const = 0x42,
i_f32_const = 0x43,
i_f64_const = 0x44,
i_i32_eqz = 0x45,
i_i32_eq = 0x46,
i_i32_ne = 0x47,
i_i32_lt_s = 0x48,
i_i32_lt_u = 0x49,
i_i32_gt_s = 0x4A,
i_i32_gt_u = 0x4B,
i_i32_le_s = 0x4C,
i_i32_le_u = 0x4D,
i_i32_ge_s = 0x4E,
i_i32_ge_u = 0x4F,
i_i64_eqz = 0x50,
i_i64_eq = 0x51,
i_i64_ne = 0x52,
i_i64_lt_s = 0x53,
i_i64_lt_u = 0x54,
i_i64_gt_s = 0x55,
i_i64_gt_u = 0x56,
i_i64_le_s = 0x57,
i_i64_le_u = 0x58,
i_i64_ge_s = 0x59,
i_i64_ge_u = 0x5A,
i_f32_eq = 0x5B,
i_f32_ne = 0x5C,
i_f32_lt = 0x5D,
i_f32_gt = 0x5E,
i_f32_le = 0x5F,
i_f32_ge = 0x60,
i_f64_eq = 0x61,
i_f64_ne = 0x62,
i_f64_lt = 0x63,
i_f64_gt = 0x64,
i_f64_le = 0x65,
i_f64_ge = 0x66,
i_i32_clz = 0x67,
i_i32_ctz = 0x68,
i_i32_popcnt = 0x69,
i_i32_add = 0x6A,
i_i32_sub = 0x6B,
i_i32_mul = 0x6C,
i_i32_div_s = 0x6D,
i_i32_div_u = 0x6E,
i_i32_rem_s = 0x6F,
i_i32_rem_u = 0x70,
i_i32_and = 0x71,
i_i32_or = 0x72,
i_i32_xor = 0x73,
i_i32_shl = 0x74,
i_i32_shr_s = 0x75,
i_i32_shr_u = 0x76,
i_i32_rotl = 0x77,
i_i32_rotr = 0x78,
i_i64_clz = 0x79,
i_i64_ctz = 0x7A,
i_i64_popcnt = 0x7B,
i_i64_add = 0x7C,
i_i64_sub = 0x7D,
i_i64_mul = 0x7E,
i_i64_div_s = 0x7F,
i_i64_div_u = 0x80,
i_i64_rem_s = 0x81,
i_i64_rem_u = 0x82,
i_i64_and = 0x83,
i_i64_or = 0x84,
i_i64_xor = 0x85,
i_i64_shl = 0x86,
i_i64_shr_s = 0x87,
i_i64_shr_u = 0x88,
i_i64_rotl = 0x89,
i_i64_rotr = 0x8A,
i_f32_abs = 0x8b,
i_f32_neg = 0x8c,
i_f32_ceil = 0x8d,
i_f32_floor = 0x8e,
i_f32_trunc = 0x8f,
i_f32_nearest = 0x90,
i_f32_sqrt = 0x91,
i_f32_add = 0x92,
i_f32_sub = 0x93,
i_f32_mul = 0x94,
i_f32_div = 0x95,
i_f32_min = 0x96,
i_f32_max = 0x97,
i_f32_copysign = 0x98,
i_f64_abs = 0x99,
i_f64_neg = 0x9a,
i_f64_ceil = 0x9b,
i_f64_floor = 0x9c,
i_f64_trunc = 0x9d,
i_f64_nearest = 0x9e,
i_f64_sqrt = 0x9f,
i_f64_add = 0xa0,
i_f64_sub = 0xa1,
i_f64_mul = 0xa2,
i_f64_div = 0xa3,
i_f64_min = 0xa4,
i_f64_max = 0xa5,
i_f64_copysign = 0xa6,
i_i32_wrap_i64 = 0xa7,
i_i32_trunc_f32_s = 0xa8,
i_i32_trunc_f32_u = 0xa9,
i_i32_trunc_f64_s = 0xaa,
i_i32_trunc_f64_u = 0xab,
i_i64_extend_i32_s = 0xac,
i_i64_extend_i32_u = 0xad,
i_i64_trunc_f32_s = 0xae,
i_i64_trunc_f32_u = 0xaf,
i_i64_trunc_f64_s = 0xb0,
i_i64_trunc_f64_u = 0xb1,
i_f32_convert_i32_s = 0xb2,
i_f32_convert_i32_u = 0xb3,
i_f32_convert_i64_s = 0xb4,
i_f32_convert_i64_u = 0xb5,
i_f32_demote_f64 = 0xb6,
i_f64_convert_i32_s = 0xb7,
i_f64_convert_i32_u = 0xb8,
i_f64_convert_i64_s = 0xb9,
i_f64_convert_i64_u = 0xba,
i_f64_promote_f32 = 0xbb,
i_i32_reinterpret_f32 = 0xbc,
i_i64_reinterpret_f64 = 0xbd,
i_f32_reinterpret_i32 = 0xbe,
i_f64_reinterpret_i64 = 0xbf,
i_i32_extend8_s = 0xc0,
i_i32_extend16_s = 0xc1,
i_i64_extend8_s = 0xc2,
i_i64_extend16_s = 0xc3,
i_i64_extend32_s = 0xc4,
i_ref_null = 0xD0,
i_ref_is_null = 0xD1,
i_ref_func = 0xD2,
i_bulk_op = 0xFC,
/* TODO: more instrs */
};
enum blocktype_tag {
blocktype_empty,
blocktype_valtype,
blocktype_index,
};
struct blocktype {
enum blocktype_tag tag;
union {
enum valtype valtype;
int type_index;
};
};
struct instrs {
unsigned char *data;
u32 len;
};
struct block {
struct blocktype type;
struct expr instrs;
};
struct memarg {
u32 offset;
u32 align;
};
struct br_table {
u32 num_label_indices;
u32 label_indices[512];
u32 default_label;
};
struct call_indirect {
u32 tableidx;
u32 typeidx;
};
struct table_init {
u32 tableidx;
u32 elemidx;
};
struct table_copy {
u32 from;
u32 to;
};
struct bulk_op {
enum bulk_tag tag;
union {
struct table_init table_init;
struct table_copy table_copy;
u32 idx;
};
};
struct select_instr {
u8 *valtypes;
u32 num_valtypes;
};
struct instr {
enum instr_tag tag;
int pos;
union {
struct br_table br_table;
struct bulk_op bulk_op;
struct call_indirect call_indirect;
struct memarg memarg;
struct select_instr select;
struct block block;
struct expr else_block;
double f64;
float f32;
int i32;
u32 u32;
int64_t i64;
u64 u64;
unsigned char memidx;
enum reftype reftype;
};
};
enum datamode {
datamode_active,
datamode_passive,
};
struct wdata_active {
u32 mem_index;
struct expr offset_expr;
};
struct wdata {
struct wdata_active active;
u8 *bytes;
u32 bytes_len;
enum datamode mode;
};
struct datasec {
struct wdata *datas;
u32 num_datas;
};
struct startsec {
u32 start_fn;
};
struct module {
unsigned int parsed;
unsigned int custom_sections;
struct func *funcs;
u32 num_funcs;
struct customsec custom_section[MAX_CUSTOM_SECTIONS];
struct typesec type_section;
struct funcsec func_section;
struct importsec import_section;
struct exportsec export_section;
struct codesec code_section;
struct tablesec table_section;
struct memsec memory_section;
struct globalsec global_section;
struct startsec start_section;
struct elemsec element_section;
struct datasec data_section;
struct namesec name_section;
};
// make sure the struct is packed so that
struct label {
u32 instr_pos; // resolved status is stored in HOB of pos
u32 jump;
};
struct callframe {
struct cursor code;
struct val *locals;
struct func *func;
u16 prev_stack_items;
};
struct resolver {
u16 label;
u8 end_tag;
u8 start_tag;
};
struct global_inst {
struct val val;
};
struct module_inst {
struct table_inst *tables;
struct global_inst *globals;
struct elem_inst *elements;
u32 num_tables;
u32 num_globals;
u32 num_elements;
int start_fn;
unsigned char *globals_init;
};
struct wasi {
int argc;
const char **argv;
int environc;
const char **environ;
};
struct wasm_interp;
struct builtin {
const char *name;
int (*fn)(struct wasm_interp *);
int (*prepare_args)(struct wasm_interp *);
};
struct wasm_interp {
struct module *module;
struct module_inst module_inst;
struct wasi wasi;
void *context;
struct builtin builtins[MAX_BUILTINS];
int num_builtins;
int prev_resolvers, quitting;
struct errors errors; /* struct error */
size_t ops;
struct cursor callframes; /* struct callframe */
struct cursor stack; /* struct val */
struct cursor mem; /* u8/mixed */
struct cursor memory; /* memory pages (65536 blocks) */
struct cursor locals; /* struct val */
struct cursor labels; /* struct labels */
struct cursor num_labels;
// resolve stack for the current function. every time a control
// instruction is encountered, the label index is pushed. When an
// instruction is popped, we can resolve the label
struct cursor resolver_stack; /* struct resolver */
struct cursor resolver_offsets; /* int */
};
struct wasm_parser {
struct module module;
struct builtin *builtins;
u32 num_builtins;
struct cursor cur;
struct cursor mem;
struct errors errs;
};
int run_wasm(unsigned char *wasm, unsigned long len, int argc, const char **argv, char **env, int *retval);
int parse_wasm(struct wasm_parser *p);
int wasm_interp_init(struct wasm_interp *interp, struct module *module);
void wasm_parser_free(struct wasm_parser *parser);
void wasm_parser_init(struct wasm_parser *p, u8 *wasm, size_t wasm_len, size_t arena_size, struct builtin *, int num_builtins);
void wasm_interp_free(struct wasm_interp *interp);
int interp_wasm_module(struct wasm_interp *interp, int *retval);
int interp_wasm_module_resume(struct wasm_interp *interp, int *retval);
void print_error_backtrace(struct errors *errors);
void setup_wasi(struct wasm_interp *interp, int argc, const char **argv, char **env);
void print_callstack(struct wasm_interp *interp);
// builtin helpers
int get_params(struct wasm_interp *interp, struct val** vals, u32 num_vals);
int get_var_params(struct wasm_interp *interp, struct val** vals, u32 *num_vals);
u8 *interp_mem_ptr(struct wasm_interp *interp, u32 ptr, int size);
static INLINE struct callframe *top_callframe(struct cursor *cur)
{
return (struct callframe*)cursor_top(cur, sizeof(struct callframe));
}
static INLINE struct cursor *interp_codeptr(struct wasm_interp *interp)
{
struct callframe *frame;
if (unlikely(!(frame = top_callframe(&interp->callframes))))
return 0;
return &frame->code;
}
static INLINE int mem_ptr_str(struct wasm_interp *interp, u32 ptr,
const char **str)
{
// still technically unsafe if the string runs over the end of memory...
if (!(*str = (const char*)interp_mem_ptr(interp, ptr, 1))) {
return interp_error(interp, "int memptr");
}
return 1;
}
static INLINE int mem_ptr_i32(struct wasm_interp *interp, u32 ptr, int **i)
{
if (!(*i = (int*)interp_mem_ptr(interp, ptr, sizeof(int))))
return interp_error(interp, "int memptr");
return 1;
}
static INLINE int cursor_pushval(struct cursor *cur, struct val *val)
{
return cursor_push(cur, (u8*)val, sizeof(*val));
}
static INLINE int cursor_push_i32(struct cursor *stack, int i)
{
struct val val;
val.type = val_i32;
val.num.i32 = i;
return cursor_pushval(stack, &val);
}
static INLINE int stack_push_i32(struct wasm_interp *interp, int i)
{
return cursor_push_i32(&interp->stack, i);
}
static INLINE struct callframe *top_callframes(struct cursor *cur, int top)
{
return (struct callframe*)cursor_topn(cur, sizeof(struct callframe), top);
}
#endif /* PROTOVERSE_WASM_H */

View File

@ -44,6 +44,7 @@
4C06670E28FDEAA000038D2A /* utf8.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670D28FDEAA000038D2A /* utf8.c */; };
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F8E280F640A000448DE /* ThreadModel.swift */; };
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; };
4C190F202A535FC200027FD5 /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */; };
4C198DEF29F88C6B004C165C /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */; };
4C198DF029F88C6B004C165C /* Readme.md in Resources */ = {isa = PBXBuildFile; fileRef = 4C198DEC29F88C6B004C165C /* Readme.md */; };
4C198DF129F88C6B004C165C /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 4C198DED29F88C6B004C165C /* License.txt */; };
@ -123,6 +124,8 @@
4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67C28FFBBA200C48A62 /* InvoicesView.swift */; };
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */; };
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C42812B298C848200DBF26F /* TranslateView.swift */; };
4C4F14A72A2A61A30045A0B9 /* NostrScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F14A62A2A61A30045A0B9 /* NostrScriptTests.swift */; };
4C4F14AC2A2A763B0045A0B9 /* primal.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C4F14AB2A2A763B0045A0B9 /* primal.wasm */; };
4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0629A540BA003E4487 /* NotificationsModel.swift */; };
4C54AA0A29A55429003E4487 /* EventGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0929A55429003E4487 /* EventGroup.swift */; };
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0B29A5543C003E4487 /* ZapGroup.swift */; };
@ -174,8 +177,11 @@
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
4C9146FC2A2A77B300DDEA40 /* NostrScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9146FB2A2A77B300DDEA40 /* NostrScript.swift */; };
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276E2A2A5D110098A105 /* wasm.c */; };
4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */; };
4C9147002A2A891E00DDEA40 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C9146FF2A2A891E00DDEA40 /* error.c */; };
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
4C9AA1482A44442E003F49FD /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */; };
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */; };
4C9BB83129C0ED4F00FC4E37 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
4C9BB83429C12D9900FC4E37 /* EventProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */; };
@ -461,6 +467,7 @@
4C06670D28FDEAA000038D2A /* utf8.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = utf8.c; sourceTree = "<group>"; };
4C0A3F8E280F640A000448DE /* ThreadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadModel.swift; sourceTree = "<group>"; };
4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = "<group>"; };
4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = "<group>"; };
4C198DEB29F88C6B004C165C /* BlurHashEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = "<group>"; };
4C198DEC29F88C6B004C165C /* Readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = "<group>"; };
4C198DED29F88C6B004C165C /* License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = License.txt; sourceTree = "<group>"; };
@ -570,6 +577,10 @@
4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoiceView.swift; sourceTree = "<group>"; };
4C42812B298C848200DBF26F /* TranslateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslateView.swift; sourceTree = "<group>"; };
4C4A3A5A288A1B2200453788 /* damus.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = damus.entitlements; sourceTree = "<group>"; };
4C4F14A62A2A61A30045A0B9 /* NostrScriptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrScriptTests.swift; sourceTree = "<group>"; };
4C4F14A82A2A71AB0045A0B9 /* nostrscript.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nostrscript.h; sourceTree = "<group>"; };
4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nostrscript.c; sourceTree = "<group>"; };
4C4F14AB2A2A763B0045A0B9 /* primal.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = primal.wasm; path = nostrscript/primal.wasm; sourceTree = SOURCE_ROOT; };
4C54AA0629A540BA003E4487 /* NotificationsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsModel.swift; sourceTree = "<group>"; };
4C54AA0929A55429003E4487 /* EventGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroup.swift; sourceTree = "<group>"; };
4C54AA0B29A5543C003E4487 /* ZapGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapGroup.swift; sourceTree = "<group>"; };
@ -625,8 +636,9 @@
4C90BD17283A9EE5008EE7EF /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
4C9146FB2A2A77B300DDEA40 /* NostrScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrScript.swift; sourceTree = "<group>"; };
4C9146FF2A2A891E00DDEA40 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = error.c; sourceTree = "<group>"; };
4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = "<group>"; };
4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = "<group>"; };
4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusModel.swift; sourceTree = "<group>"; };
4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayName.swift; sourceTree = "<group>"; };
4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfileName.swift; sourceTree = "<group>"; };
@ -635,6 +647,13 @@
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
4CA3FA0F29F593D000FDB3C3 /* ZapTypePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapTypePicker.swift; sourceTree = "<group>"; };
4CA5588229F33F5B00DC6A45 /* StringCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCodable.swift; sourceTree = "<group>"; };
4CA9276D2A2A5D110098A105 /* wasm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wasm.h; sourceTree = "<group>"; };
4CA9276E2A2A5D110098A105 /* wasm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = wasm.c; sourceTree = "<group>"; };
4CA9276F2A2A5D470098A105 /* parser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = parser.h; sourceTree = "<group>"; };
4CA927702A2A5D470098A105 /* debug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = debug.h; sourceTree = "<group>"; };
4CA927712A2A5D480098A105 /* error.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = error.h; sourceTree = "<group>"; };
4CA927742A2A5E2F0098A105 /* varint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = varint.h; sourceTree = "<group>"; };
4CA927752A2A5E2F0098A105 /* typedefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = typedefs.h; sourceTree = "<group>"; };
4CAAD8AC298851D000060CEA /* AccountDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletion.swift; sourceTree = "<group>"; };
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; };
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
@ -827,6 +846,16 @@
4C06670728FDE62900038D2A /* damus-c */ = {
isa = PBXGroup;
children = (
4C9146FF2A2A891E00DDEA40 /* error.c */,
4CA927752A2A5E2F0098A105 /* typedefs.h */,
4CA927742A2A5E2F0098A105 /* varint.h */,
4CA927702A2A5D470098A105 /* debug.h */,
4CA927712A2A5D480098A105 /* error.h */,
4CA9276F2A2A5D470098A105 /* parser.h */,
4CA9276E2A2A5D110098A105 /* wasm.c */,
4CA9276D2A2A5D110098A105 /* wasm.h */,
4C4F14A82A2A71AB0045A0B9 /* nostrscript.h */,
4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */,
4C06670928FDE64700038D2A /* damus.h */,
4C06670A28FDE64700038D2A /* damus.c */,
4C06670828FDE64700038D2A /* damus-Bridging-Header.h */,
@ -886,7 +915,7 @@
4C0A3F8D280F63FF000448DE /* Models */ = {
isa = PBXGroup;
children = (
4C9AA1462A444422003F49FD /* Zaps */,
4C190F1E2A535FC200027FD5 /* Zaps */,
4C54AA0829A55416003E4487 /* Notifications */,
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */,
4C0A3F8E280F640A000448DE /* ThreadModel.swift */,
@ -935,6 +964,14 @@
path = Models;
sourceTree = "<group>";
};
4C190F1E2A535FC200027FD5 /* Zaps */ = {
isa = PBXGroup;
children = (
4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */,
);
path = Zaps;
sourceTree = "<group>";
};
4C198DEA29F88C6B004C165C /* BlurHash */ = {
isa = PBXGroup;
children = (
@ -987,6 +1024,14 @@
path = Notifications;
sourceTree = "<group>";
};
4C4F14AA2A2A76270045A0B9 /* Fixtures */ = {
isa = PBXGroup;
children = (
4C4F14AB2A2A763B0045A0B9 /* primal.wasm */,
);
path = Fixtures;
sourceTree = "<group>";
};
4C54AA0829A55416003E4487 /* Notifications */ = {
isa = PBXGroup;
children = (
@ -1181,12 +1226,12 @@
path = Buttons;
sourceTree = "<group>";
};
4C9AA1462A444422003F49FD /* Zaps */ = {
4CA927732A2A5DCC0098A105 /* nostrscript */ = {
isa = PBXGroup;
children = (
4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */,
4C9146FB2A2A77B300DDEA40 /* NostrScript.swift */,
);
path = Zaps;
path = nostrscript;
sourceTree = "<group>";
};
4CAAD8AE29888A9B00060CEA /* Relays */ = {
@ -1313,6 +1358,7 @@
4CE6DEDA27F7A08100C66700 = {
isa = PBXGroup;
children = (
4CA927732A2A5DCC0098A105 /* nostrscript */,
4C06670728FDE62900038D2A /* damus-c */,
4CE6DEE527F7A08100C66700 /* damus */,
4CE6DEF627F7A08200C66700 /* damusTests */,
@ -1367,6 +1413,7 @@
4CE6DEF627F7A08200C66700 /* damusTests */ = {
isa = PBXGroup;
children = (
4C4F14AA2A2A76270045A0B9 /* Fixtures */,
4C7D097D2A0C58B900943473 /* WalletConnectTests.swift */,
F944F56C29EA9CB20067B3BF /* Models */,
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */,
@ -1391,6 +1438,7 @@
3AFBF3FC29FDA7CC00E79C7C /* CustomZapViewTests.swift */,
3A5E47C62A4A76C800C0D090 /* TrieTests.swift */,
3A90B1822A4EA3C600000D94 /* UserSearchCacheTests.swift */,
4C4F14A62A2A61A30045A0B9 /* NostrScriptTests.swift */,
);
path = damusTests;
sourceTree = "<group>";
@ -1669,6 +1717,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4C4F14AC2A2A763B0045A0B9 /* primal.wasm in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1705,6 +1754,7 @@
4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */,
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */,
4C190F202A535FC200027FD5 /* CustomizeZapModel.swift in Sources */,
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
F757933A29D7AECD007DEAC1 /* ImagePicker.swift in Sources */,
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
@ -1799,7 +1849,6 @@
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */,
4C9AA1482A44442E003F49FD /* CustomizeZapModel.swift in Sources */,
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */,
4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */,
@ -1809,6 +1858,7 @@
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */,
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */,
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
4C9146FC2A2A77B300DDEA40 /* NostrScript.swift in Sources */,
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
4CE879522996B68900F758CC /* RelayType.swift in Sources */,
@ -1844,6 +1894,7 @@
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */,
4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */,
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */,
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */,
@ -1920,6 +1971,7 @@
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */,
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */,
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
4C1A9A1A29DCA17E00516EAC /* ReplyCounter.swift in Sources */,
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */,
@ -1933,6 +1985,7 @@
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */,
4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */,
4C9147002A2A891E00DDEA40 /* error.c in Sources */,
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */,
501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */,
4CF0ABD42980996B00D66079 /* Report.swift in Sources */,
@ -1980,6 +2033,7 @@
3AFBF3FD29FDA7CC00E79C7C /* CustomZapViewTests.swift in Sources */,
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */,
4C4F14A72A2A61A30045A0B9 /* NostrScriptTests.swift in Sources */,
3A3040EF29A8FEE9008A0F29 /* EventDetailBarTests.swift in Sources */,
4C3EA67B28FF7B3900C48A62 /* InvoiceTests.swift in Sources */,
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */,

View File

@ -153,7 +153,7 @@ func render_blocks(blocks: [Block]) -> String {
func parse_mentions(content: String, tags: [[String]]) -> [Block] {
var out: [Block] = []
var bs = blocks()
var bs = note_blocks()
bs.num_blocks = 0;
blocks_init(&bs)

View File

@ -12,6 +12,28 @@ struct NostrSubscribe {
let sub_id: String
}
enum NostrRequestType {
case typical(NostrRequest)
case custom(String)
var is_write: Bool {
guard case .typical(let req) = self else {
return true
}
return req.is_write
}
var is_read: Bool {
guard case .typical(let req) = self else {
return true
}
return req.is_read
}
}
enum NostrRequest {
case subscribe(NostrSubscribe)
case unsubscribe(String)
@ -31,4 +53,5 @@ enum NostrRequest {
var is_read: Bool {
return !is_write
}
}

View File

@ -99,15 +99,25 @@ final class RelayConnection: ObservableObject {
isConnected = false
isConnecting = false
}
func send(_ req: NostrRequest) {
guard let req = make_nostr_req(req) else {
print("failed to encode nostr req: \(req)")
return
}
func send_raw(_ req: String) {
socket.send(.string(req))
}
func send(_ req: NostrRequestType) {
switch req {
case .typical(let req):
guard let req = make_nostr_req(req) else {
print("failed to encode nostr req: \(req)")
return
}
send_raw(req)
case .custom(let req):
send_raw(req)
}
}
private func receive(event: WebSocketEvent) {
switch event {
case .connected:

View File

@ -14,8 +14,9 @@ struct RelayHandler {
}
struct QueuedRequest {
let req: NostrRequest
let req: NostrRequestType
let relay: String
let skip_ephemeral: Bool
}
struct SeenEvent: Hashable {
@ -178,18 +179,18 @@ class RelayPool {
return c
}
func queue_req(r: NostrRequest, relay: String) {
func queue_req(r: NostrRequestType, relay: String, skip_ephemeral: Bool) {
let count = count_queued(relay: relay)
guard count <= 10 else {
print("can't queue, too many queued events for \(relay)")
return
}
print("queueing request: \(r) for \(relay)")
request_queue.append(QueuedRequest(req: r, relay: relay))
print("queueing request for \(relay)")
request_queue.append(QueuedRequest(req: r, relay: relay, skip_ephemeral: skip_ephemeral))
}
func send(_ req: NostrRequest, to: [String]? = nil, skip_ephemeral: Bool = true) {
func send_raw(_ req: NostrRequestType, to: [String]? = nil, skip_ephemeral: Bool = true) {
let relays = to.map{ get_relays($0) } ?? self.relays
for relay in relays {
@ -206,7 +207,7 @@ class RelayPool {
}
guard relay.connection.isConnected else {
queue_req(r: req, relay: relay.id)
queue_req(r: req, relay: relay.id, skip_ephemeral: skip_ephemeral)
continue
}
@ -214,6 +215,10 @@ class RelayPool {
}
}
func send(_ req: NostrRequest, to: [String]? = nil, skip_ephemeral: Bool = true) {
send_raw(.typical(req), to: to, skip_ephemeral: skip_ephemeral)
}
func get_relays(_ ids: [String]) -> [Relay] {
// don't include ephemeral relays in the default list to query
relays.filter { ids.contains($0.id) }
@ -231,7 +236,7 @@ class RelayPool {
}
print("running queueing request: \(req.req) for \(relay_id)")
self.send(req.req, to: [relay_id])
self.send_raw(req.req, to: [relay_id], skip_ephemeral: false)
}
}

View File

@ -360,7 +360,7 @@ func determine_zap_target(_ ev: NostrEvent) -> ZapTarget? {
}
func decode_bolt11(_ s: String) -> Invoice? {
var bs = blocks()
var bs = note_blocks()
bs.num_blocks = 0
blocks_init(&bs)

View File

@ -21,7 +21,7 @@ class DamusParseContentTests: XCTestCase {
}
func test_damus_parse_content_can_parse_mention_without_white_space_at_front() throws {
var bs = blocks()
var bs = note_blocks()
bs.num_blocks = 0;
blocks_init(&bs)

View File

@ -0,0 +1,83 @@
//
// NostrScriptTests.swift
// damusTests
//
// Created by William Casarin on 2023-06-02.
//
import XCTest
@testable import damus
final class NostrScriptTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func loadTestWasm() throws -> Data {
let bundle = Bundle(for: type(of: self))
guard let fileURL = bundle.url(forResource: "primal", withExtension: "wasm") else {
throw CocoaError(.fileReadNoSuchFile)
}
return try Data(contentsOf: fileURL)
}
func test_nostrscript() throws {
var data = try loadTestWasm().bytes
let pool = RelayPool()
let script = NostrScript(pool: pool)
let load_err = script.load(wasm: &data)
XCTAssertNil(load_err)
let res = script.run()
switch res {
case .finished: XCTAssert(false)
case .runtime_err: XCTAssert(false)
case .suspend:
XCTAssertEqual(script.waiting_on, .event("sidebar_trending"))
break
}
let resume_expected = XCTestExpectation(description: "we got ")
pool.register_handler(sub_id: "sidebar_trending") { (relay_id, conn) in
if script.runstate?.exited == true {
pool.disconnect()
resume_expected.fulfill()
return
}
guard case .nostr_event(let resp) = conn else {
return
}
let with: NScriptResumeWith = .event(resp)
guard let res = script.resume(with: with) else {
return
}
switch res {
case .finished: break
case .runtime_err: XCTAssert(false)
case .suspend: break
}
}
pool.connect(to: ["wss://cache3.primal.net/cache15"])
self.wait(for: [resume_expected], timeout: 10.0)
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

View File

@ -0,0 +1,361 @@
//
// NostrScript.swift
// damus
//
// Created by William Casarin on 2023-06-02.
//
import Foundation
enum NostrScriptLoadErr {
case parse
case module_init
}
enum NostrScriptRunResult {
case runtime_err([String])
case suspend
case finished(Int)
var exited: Bool {
switch self {
case .runtime_err:
return true
case .suspend:
return false
case .finished:
return true
}
}
var is_suspended: Bool {
if case .suspend = self {
return true
}
return false
}
}
enum NostrScriptLoadResult {
case err(NostrScriptLoadErr)
case loaded(wasm_interp)
}
class NostrScript {
private var interp: wasm_interp
private var parser: wasm_parser
var waiting_on: NScriptWaiting?
private(set) var runstate: NostrScriptRunResult?
private(set) var pool: RelayPool
private(set) var event: NostrResponse?
init(pool: RelayPool) {
self.interp = wasm_interp()
self.parser = wasm_parser()
self.pool = pool
self.event = nil
self.runstate = nil
}
deinit {
wasm_parser_free(&self.parser)
wasm_interp_free(&self.interp)
}
func is_suspended(on: NScriptWaiting) -> Bool {
return self.waiting_on == on
}
func can_resume(with: NScriptResumeWith) -> Bool {
guard let waiting_on else {
return false
}
switch waiting_on {
case .event(let subid):
switch with {
case .event(let resp):
return resp.subid == subid
}
}
}
func test(_ str: String) {
print("hello from \(str)")
}
func load(wasm: inout [UInt8]) -> NostrScriptLoadErr? {
switch nscript_load(&parser, &interp, &wasm, UInt(wasm.count)) {
case NSCRIPT_LOADED:
print("load num_exports \(interp.module.pointee.export_section.num_exports)")
interp.context = Unmanaged.passUnretained(self).toOpaque()
return nil
case NSCRIPT_INIT_ERR:
return .module_init
case NSCRIPT_PARSE_ERR:
return .parse
default:
return .parse
}
}
func resume(with: NScriptResumeWith) -> NostrScriptRunResult? {
guard let runstate, runstate.is_suspended, can_resume(with: with) else {
return nil
}
switch with {
case .event(let resp):
load_data(resp: resp)
}
let st = nscript_run(interp: &interp, resuming: true)
self.runstate = st
self.event = nil
return st
}
private func load_data(resp: NostrResponse) {
self.event = resp
}
func run() -> NostrScriptRunResult {
if let runstate {
return runstate
}
let st = nscript_run(interp: &interp, resuming: false)
self.runstate = st
return st
}
}
fileprivate func interp_nostrscript(interp: UnsafeMutablePointer<wasm_interp>?) -> NostrScript? {
guard let interp = interp?.pointee else {
return nil
}
return Unmanaged<NostrScript>.fromOpaque(interp.context).takeUnretainedValue()
}
fileprivate func asm_str_byteptr(cstr: UnsafePointer<UInt8>, len: Int32) -> String? {
let u16 = cstr.withMemoryRebound(to: UInt16.self, capacity: Int(len)) { p in p }
return asm_str(cstr: u16, len: len)
}
fileprivate func asm_str(cstr: UnsafePointer<UInt16>, len: Int32) -> String? {
return String(utf16CodeUnits: cstr, count: Int(len))
}
enum NScriptCommand: Int {
case pool_send = 1
case add_relay = 2
case event_await = 3
case event_get_type = 4
case event_get_note = 5
case note_get_kind = 6
case note_get_content = 7
case note_get_content_length = 8
}
enum NScriptEventType: Int {
case ok = 1
case note = 2
case notice = 3
case eose = 4
init(resp: NostrResponse) {
switch resp {
case .event:
self = .note
case .notice:
self = .notice
case .eose:
self = .eose
case .ok:
self = .ok
}
}
}
enum NScriptWaiting: Equatable {
case event(String)
var subid: String {
switch self {
case .event(let subid):
return subid
}
}
}
enum NScriptResumeWith {
case event(NostrResponse)
}
enum NScriptCmdResult {
case suspend(NScriptWaiting)
case ok
case fatal
}
@_cdecl("nscript_nostr_cmd")
public func nscript_nostr_cmd(interp: UnsafeMutablePointer<wasm_interp>?, cmd: Int32, value: UnsafePointer<UInt8>, len: Int32) -> Int32 {
guard let script = interp_nostrscript(interp: interp),
let cmd = NScriptCommand(rawValue: Int(cmd)) else {
return 0
}
print("nostr_cmd \(cmd)")
switch cmd {
case .pool_send:
guard let req = asm_str_byteptr(cstr: value, len: len) else { return 0 }
let res = nscript_pool_send(script: script, req: req)
stack_push_i32(interp, 0);
return res;
case .add_relay:
guard let relay = asm_str_byteptr(cstr: value, len: len) else { return 0 }
let ok = nscript_add_relay(script: script, relay: relay)
stack_push_i32(interp, ok ? 1 : 0)
return 1;
case .event_await:
guard let subid = asm_str_byteptr(cstr: value, len: len) else { return 0 }
nscript_event_await(script: script, subid: subid)
let ev_handle: Int32 = 1
stack_push_i32(interp, ev_handle);
return BUILTIN_SUSPEND
case .event_get_type:
guard let event = script.event else {
stack_push_i32(interp, 0);
return 1
}
let type = NScriptEventType(resp: event)
stack_push_i32(interp, Int32(type.rawValue));
return 1
case .event_get_note:
guard let event = script.event, case .event = event
else { stack_push_i32(interp, 0); return 1 }
let note_handle: Int32 = 1
stack_push_i32(interp, note_handle)
return 1
case .note_get_kind:
guard let event = script.event, case .event(_, let note) = event
else {
stack_push_i32(interp, 0);
return 1
}
stack_push_i32(interp, Int32(note.kind))
return 1
case .note_get_content:
guard let event = script.event, case .event(_, let note) = event
else { stack_push_i32(interp, 0); return 1 }
stack_push_i32(interp, Int32(note.kind))
return 1
case .note_get_content_length:
guard let event = script.event, case .event(_, let note) = event
else { stack_push_i32(interp, 0); return 1 }
stack_push_i32(interp, Int32(note.content.utf8.count))
return 1
}
}
func nscript_add_relay(script: NostrScript, relay: String) -> Bool {
guard let url = RelayURL(relay) else { return false }
let desc = RelayDescriptor(url: url, info: .rw, variant: .ephemeral)
return (try? script.pool.add_relay(desc)) != nil
}
@_cdecl("nscript_pool_send_to")
public func nscript_pool_send_to(interp: UnsafeMutablePointer<wasm_interp>?, preq: UnsafePointer<UInt16>, req_len: Int32, to: UnsafePointer<UInt16>, to_len: Int32) -> Int32 {
guard let script = interp_nostrscript(interp: interp),
let req_str = asm_str(cstr: preq, len: req_len),
let to = asm_str(cstr: to, len: to_len)
else {
return 0
}
DispatchQueue.main.async {
script.pool.send_raw(.custom(req_str), to: [to], skip_ephemeral: false)
}
return 1;
}
func nscript_pool_send(script: NostrScript, req req_str: String) -> Int32 {
script.test("pool_send: '\(req_str)'")
DispatchQueue.main.sync {
script.pool.send_raw(.custom(req_str), skip_ephemeral: false)
}
return 1;
}
func nscript_event_await(script: NostrScript, subid: String) {
script.waiting_on = .event(subid)
}
func nscript_get_error_backtrace(errors: inout errors) -> [String] {
var xs = [String]()
var errs = cursor()
var err = error()
copy_cursor(&errors.cur, &errs)
errs.p = errs.start;
while (errs.p < errors.cur.p) {
if (cursor_pull_error(&errs, &err) == 0) {
return xs
}
xs.append(String(cString: err.msg))
}
return xs
}
func nscript_run(interp: inout wasm_interp, resuming: Bool) -> NostrScriptRunResult {
var res: Int32 = 0
var retval: Int32 = 0
if (resuming) {
print("resuming nostrscript");
res = interp_wasm_module_resume(&interp, &retval);
} else {
res = interp_wasm_module(&interp, &retval);
}
if res == 0 {
print_callstack(&interp);
print_error_backtrace(&interp.errors);
let backtrace = nscript_get_error_backtrace(errors: &interp.errors)
return .runtime_err(backtrace)
}
if res == BUILTIN_SUSPEND {
return .suspend
}
//print_stack(&interp.stack);
wasm_interp_free(&interp);
return .finished(Int(retval))
}

77
nostrscript/nostr.ts Normal file
View File

@ -0,0 +1,77 @@
// these are handles not actual pointers
export type Note = i32;
export type Event = i32;
export enum EventType {
OK = 1,
NOTE = 2,
NOTICE = 3,
EOSE = 4
}
enum Command {
POOL_SEND = 1,
ADD_RELAY = 2,
EVENT_AWAIT = 3,
EVENT_GET_TYPE = 4,
EVENT_GET_NOTE = 5,
NOTE_GET_KIND = 6,
NOTE_GET_CONTENT = 7,
NOTE_GET_CONTENT_LENGTH = 8,
}
declare function nostr_log(log: string): void;
declare function nostr_cmd(cmd: i32, val: i32, len: i32): i32;
declare function nostr_pool_send_to(req: string, req_len: i32, to: string, to_len: i32): void;
export function pool_send(req: string): void {
nostr_cmd(Command.POOL_SEND, changetype<i32>(req), req.length)
}
export function pool_send_to(req: string, to: string): void {
return nostr_pool_send_to(req, req.length, to, to.length)
}
export function pool_add_relay(relay: string): boolean {
let ok = nostr_cmd(Command.ADD_RELAY, changetype<i32>(relay), relay.length)
return ok as boolean
}
export function event_await(subid: string): Event {
return nostr_cmd(Command.EVENT_AWAIT, changetype<i32>(subid), subid.length) as i32
}
export function event_get_type(ev: Event): EventType {
if (!ev) return 0;
return nostr_cmd(Command.EVENT_GET_TYPE, ev, 0) as EventType
}
export function event_get_note(ev: Event): Note {
if (!ev) return 0;
return nostr_cmd(Command.EVENT_GET_NOTE, ev, 0)
}
export function note_get_kind(note: Note): u32 {
if (!note) return 0;
return nostr_cmd(Command.NOTE_GET_KIND, note, 0);
}
function note_get_content_length(): i32 {
return nostr_cmd(Command.NOTE_GET_CONTENT_LENGTH, note, 0)
}
export function log(log: string): void {
nostr_log(log)
}
export function note_get_content(): string {
let res = nostr_cmd(Command.NOTE_GET_CONTENT, note, 0);
if (!res) return "";
let len = note_get_content_length()
let codes = TypedArray.wrap()
return String.fromCharCodes(codes)
}

38
nostrscript/primal.ts Normal file
View File

@ -0,0 +1,38 @@
import * as nostr from './nostr'
export function go(): i32 {
let subid = "sidebar_trending"
let relay = 'wss://cache3.primal.net/cache15'
var done: i32 = 0
var events: i32 = 0
nostr.pool_add_relay(relay)
nostr.pool_send_to(`["REQ","${subid}",{"cache":["explore_global_trending_24h"]}]`, relay)
while (!done) {
var ev = nostr.event_await(subid)
let type = nostr.event_get_type(ev)
switch (type) {
case nostr.EventType.OK:
nostr.log("ok")
break
case nostr.EventType.NOTE:
events++
let note = nostr.event_get_note(ev)
let kind = nostr.note_get_kind(note)
nostr.log(`type:${type} #${events} note, kind:${kind}`)
break
case nostr.EventType.EOSE:
nostr.log("eose, got " + events.toString() + " events")
done = true
break
default:
nostr.log("got event type " + type.toString())
}
}
return events
}
go()

BIN
nostrscript/primal.wasm Normal file

Binary file not shown.

View File

@ -1,5 +1,5 @@
{ pkgs ? import <nixpkgs> {} }:
with pkgs;
mkShell {
buildInputs = with python3Packages; [ Mako requests ];
buildInputs = with python3Packages; [ Mako requests wabt ];
}