add dmon and nota into source tree, out of subprojects; build on macos
All checks were successful
Build and Deploy / build-linux (push) Successful in 1m9s
Build and Deploy / build-windows (CLANG64) (push) Successful in 33m33s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped

This commit is contained in:
2025-02-23 08:49:49 -06:00
parent 96ef8ccba3
commit fb10c63882
12 changed files with 2725 additions and 19 deletions

View File

@@ -39,7 +39,24 @@ deps = []
if host_machine.system() == 'darwin'
add_project_arguments('-x', 'objective-c', language: 'c')
fworks = ['foundation', 'metal', 'audiotoolbox', 'metalkit', 'avfoundation', 'quartzcore', 'cocoa']
fworks = [
'foundation',
'metal',
'audiotoolbox',
'metalkit',
'avfoundation',
'quartzcore',
'cocoa',
'coreaudio',
'coremedia',
'gamecontroller',
'forcefeedback',
'iokit',
'corefoundation',
'corehaptics',
'carbon',
'uniformtypeidentifiers'
]
foreach fkit : fworks
deps += dependency('appleframeworks', modules: fkit)
endforeach
@@ -106,7 +123,6 @@ if storefront == 'steam'
endif
deps += dependency('qjs-layout',static:true)
deps += dependency('qjs-nota',static:true)
deps += dependency('qjs-miniz',static:true)
deps += dependency('qjs-soloud',static:true)
deps += dependency('physfs', static:true)
@@ -127,7 +143,7 @@ if get_option('enet')
endif
sources = []
src += ['anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','prosperon.c', 'wildmatch.c', 'sprite.c', 'rtree.c']
src += ['anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','prosperon.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_dmon.c', 'qjs_nota.c']
imsrc = ['GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp','imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp','implot_items.cpp','implot.cpp', 'imgui_impl_sdlrenderer3.cpp', 'imgui_impl_sdl3.cpp', 'imgui_impl_sdlgpu3.cpp']
@@ -146,8 +162,6 @@ if get_option('editor')
foreach imgui : imsrc
sources += tp / 'imgui' / imgui
endforeach
# sub_dmon = subproject('qjs-dmon')
# dmon_dep = sub_dmon.get_variable('qjs_dmon_dep')
endif
includers = []

1748
source/dmon.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -7584,8 +7584,12 @@ MISTUSE(video)
MISTUSE(event)
MISTUSE(camera)
MISTUSE(debug)
JSValue js_imgui_use(JSContext *js);
#include "qjs_dmon.h"
#include "qjs_nota.h"
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
void ffi_load(JSContext *js, int argc, char **argv) {
@@ -7607,6 +7611,9 @@ void ffi_load(JSContext *js, int argc, char **argv) {
arrput(module_registry, MISTLINE(imgui));
arrput(module_registry, MISTLINE(camera));
arrput(module_registry, MISTLINE(debug));
arrput(module_registry, MISTLINE(dmon));
arrput(module_registry, MISTLINE(nota));
#ifdef TRACY_ENABLE
arrput(module_registry, MISTLINE(tracy));
#endif

122
source/kim.h Executable file
View File

@@ -0,0 +1,122 @@
#ifndef KIM_H
#define KIM_H
// write number of runes from a kim stream int a utf8 stream
void utf8_to_kim(const char **utf, char **kim);
// write number of runes from a kim stream int a utf8 stream
void kim_to_utf8(char **kim, char **utf, int runes);
// Return the number of bytes a given utf-8 rune will have
int utf8_bytes(char c);
// Return the number of runes in a utf8 string
int utf8_count(const char *utf8);
#ifdef KIM_IMPLEMENTATION
#define KIM_CONT 0x80
#define KIM_DATA 0x7f
#define CONTINUE(CHAR) (CHAR>>7)
int decode_utf8(char **s);
void encode_utf8(char **s, int code);
static void encode_kim(char **s, int code);
int decode_kim(char **s);
int utf8_bytes(char c)
{
int bytes = __builtin_clz(~(c));
if (!bytes) return 1;
return bytes-24;
}
int utf8_count(const char *utf8)
{
int count = 0;
while(*utf8) {
count++;
utf8 += utf8_bytes(*utf8);
}
return count;
}
// decode and advance s, returning the character rune
int decode_utf8(char **s) {
int k = **s ? __builtin_clz(~(**s << 24)) : 0; // Count # of leading 1 bits.
int mask = (1 << (8 - k)) - 1; // All 1's with k leading 0's.
int value = **s & mask;
for (++(*s), --k; k > 0 && **s; --k, ++(*s)) { // Note that k = #total bytes, or 0.
value <<= 6;
value += (**s & 0x3F);
}
return value;
}
// Write and advance s with rune in utf-8
void encode_utf8(char **s, int rune) {
char val[4];
int lead_byte_max = 0x7F;
int val_index = 0;
while (rune > lead_byte_max) {
val[val_index++] = (rune & 0x3F) | 0x80;
rune >>= 6;
lead_byte_max >>= (val_index == 1 ? 2 : 1);
}
val[val_index++] = (rune & lead_byte_max) | (~lead_byte_max << 1);
while (val_index--) {
**s = val[val_index];
(*s)++;
}
}
// write and advance s with rune in kim
static inline void encode_kim(char **s, int rune)
{
if (rune < KIM_CONT) {
**s = 0 | (KIM_DATA & rune);
(*s)++;
return;
}
int bits = ((32 - __builtin_clz(rune) + 6) / 7) * 7;
while (bits > 7) {
bits -= 7;
**s = KIM_CONT | KIM_DATA & (rune >> bits);
(*s)++;
}
**s = KIM_DATA & rune;
(*s)++;
}
// decode and advance s, returning the character rune
int decode_kim(char **s)
{
int rune = **s & KIM_DATA;
while (CONTINUE(**s)) {
rune <<= 7;
(*s)++;
rune |= **s & KIM_DATA;
}
(*s)++;
return rune;
}
void utf8_to_kim(const char **utf, char **kim)
{
char * str = *utf;
while (*str)
encode_kim(kim, decode_utf8(&str));
}
void kim_to_utf8(char **kim, char **utf, int runes)
{
for (int i = 0; i < runes; i++)
encode_utf8(utf, decode_kim(kim));
}
#endif
#endif

357
source/nota.h Executable file
View File

@@ -0,0 +1,357 @@
#ifndef NOTA_H
#define NOTA_H
#define NOTA_BLOB 0x00 // C 0 0 0
#define NOTA_TEXT 0x10 //
#define NOTA_ARR 0x20 // 0 1 0
#define NOTA_REC 0x30 // C 0 1 1
#define NOTA_FLOAT 0x40 // C 1 0
#define NOTA_INT 0x60 // C 1 1 0
#define NOTA_SYM 0x70 // C 1 1 1
#define NOTA_FALSE 0x00
#define NOTA_TRUE 0x01
#define NOTA_NULL 0x02
#define NOTA_INF 0x03
#define NOTA_PRIVATE 0x08
#define NOTA_SYSTEM 0x09
// Returns the type NOTA_ of the byte at *nota
int nota_type(char *nota);
// Functions take a pointer to a buffer *nota, read or write the value, and then return a pointer to the next byte of the stream
// Pass NULL into the read in variable to skip over it
char *nota_read_blob(long long *len, char *nota);
// ALLOCATES! Uses strdup to return it via the text pointer
char *nota_read_text(char **text, char *nota);
char *nota_read_array(long long *len, char *nota);
char *nota_read_record(long long *len, char *nota);
char *nota_read_float(double *d, char *nota);
char *nota_read_int(long long *l, char *nota);
char *nota_read_sym(int *sym, char *nota);
char *nota_write_blob(unsigned long long n, char *nota);
char *nota_write_text(const char *s, char *nota);
char *nota_write_array(unsigned long long n, char *nota);
char *nota_write_record(unsigned long long n, char *nota);
char *nota_write_number(double n, char *nota);
char *nota_write_sym(int sym, char *nota);
void print_nota_hex(char *nota);
#ifdef NOTA_IMPLEMENTATION
#include "stdio.h"
#include "math.h"
#include "string.h"
#include "stdlib.h"
#include "limits.h"
#include "kim.h"
#define NOTA_CONT 0x80
#define NOTA_DATA 0x7f
#define NOTA_INT_DATA 0x07
#define NOTA_INT_SIGN(CHAR) (CHAR & (1<<3))
#define NOTA_SIG_SIGN(CHAR) (CHAR & (1<<3))
#define NOTA_EXP_SIGN(CHAR) (CHAR & (1<<4))
#define NOTA_TYPE 0x70
#define NOTA_HEAD_DATA 0x0f
#define CONTINUE(CHAR) (CHAR>>7)
#define UTF8_DATA 0x3f
/* define this to use native string instead of kim. Bytes are encoded instead of runes */
#define NOTA_UTF8
int nota_type(char *nota) { return *nota & NOTA_TYPE; }
char *nota_skip(char *nota)
{
while (CONTINUE(*nota))
nota++;
return nota+1;
}
char *nota_read_num(long long *n, char *nota)
{
if (!n)
return nota_skip(nota);
*n = 0;
*n |= (*nota) & NOTA_HEAD_DATA;
while (CONTINUE(*(nota++)))
*n = (*n<<7) | (*nota) & NOTA_DATA;
return nota;
}
// Given a number n, and bits used in the first char sb, how many bits are needed
int nota_bits(long long n, int sb)
{
if (n == 0) return sb;
int bits = sizeof(n)*CHAR_BIT - __builtin_clzll(n);
bits-=sb;
int needed = ((bits + 6) / 7)*7 + sb;
return needed;
}
// write a number from n into *nota, with sb bits in the first char
char *nota_continue_num(long long n, char *nota, int sb)
{
int bits = nota_bits(n, sb);
bits -= sb;
if (bits > 0)
nota[0] |= NOTA_CONT;
else
nota[0] &= ~NOTA_CONT;
int shex = (~0) << sb;
nota[0] &= shex; /* clear shex bits */
nota[0] |= (~shex) & (n>>bits);
int i = 1;
while (bits > 0) {
bits -= 7;
int head = bits == 0 ? 0 : NOTA_CONT;
nota[i] = head | (NOTA_DATA & (n >> bits));
i++;
}
return &nota[i];
}
void print_nota_hex(char *nota)
{
do {
printf("%02X ", (unsigned char)(*nota));
} while(CONTINUE(*(nota++)));
printf("\n");
return;
long long chars = 0;
if (!((*nota>>4 & 0x07) ^ NOTA_TEXT>>4))
nota_read_num(&chars, nota);
if ((*nota>>5) == 2 || (*nota>>5) == 6)
chars = 1;
for (int i = 0; i < chars+1; i++) {
do {
printf("%02X ", (unsigned char)(*nota));
} while(CONTINUE(*(nota++)));
}
printf("\n");
}
char *nota_write_int(long long n, char *nota)
{
char sign = 0;
if (n < 0) {
sign = 0x08;
n *= -1;
}
*nota = NOTA_INT | sign;
return nota_continue_num(n, nota, 3);
}
#define NOTA_DBL_PREC 6
#define xstr(s) str(s)
#define str(s) #s
#include <stdio.h>
#include <math.h>
void extract_mantissa_coefficient(double num, long *mantissa, long* coefficient) {
char buf[64];
char *p, *dec_point;
int exp = 0, coeff = 0;
// Convert double to string with maximum precision
snprintf(buf, sizeof(buf), "%.17g", num);
// Find if 'e' or 'E' is present (scientific notation)
p = strchr(buf, 'e');
if (!p) p = strchr(buf, 'E');
if (p) {
// There is an exponent part
exp = atol(p + 1);
*p = '\0'; // Remove exponent part from the string
}
// Find decimal point
dec_point = strchr(buf, '.');
if (dec_point) {
// Count number of digits after decimal point
int digits_after_point = strlen(dec_point + 1);
coeff = digits_after_point;
// Remove decimal point by shifting characters
memmove(dec_point, dec_point + 1, strlen(dec_point));
} else
coeff = 0;
// Adjust coefficient with exponent from scientific notation
coeff -= exp;
// Copy the mantissa
*mantissa = atol(buf);
// Set coefficient
*coefficient = coeff;
}
char *nota_write_float(double n, char *nota)
{
int neg = n < 0;
long digits;
long coef;
extract_mantissa_coefficient(n, &digits, &coef);
printf("Values of %g are %ld e %ld\n", n, digits, coef);
if (coef == 0)
return nota_write_int(coef * (neg ? -1 : 1), nota);
int expsign = coef < 0 ? ~0 : 0;
coef = llabs(coef);
nota[0] = NOTA_FLOAT;
nota[0] |= 0x10 & expsign;
nota[0] |= 0x08 & neg;
char *c = nota_continue_num(coef, nota, 3);
return nota_continue_num(digits, c, 7);
}
char *nota_read_float(double *d, char *nota)
{
long long sig = 0;
long long e = 0;
char *c = nota;
e = (*c) & NOTA_INT_DATA; /* first three bits */
while (CONTINUE(*c)) {
e = (e<<7) | (*c) & NOTA_DATA;
c++;
}
c++;
do
sig = (sig<<7) | *c & NOTA_DATA;
while (CONTINUE(*(c++)));
if (NOTA_SIG_SIGN(*nota)) sig *= -1;
if (NOTA_EXP_SIGN(*nota)) e *= -1;
*d = (double)sig * pow(10.0, e);
return c;
}
char *nota_write_number(double n, char *nota)
{
if (n < (double)INT64_MIN || n > (double)INT64_MAX) return nota_write_float(n, nota);
if (floor(n) == n)
return nota_write_int(n, nota);
return nota_write_float(n, nota);
}
char *nota_read_int(long long *n, char *nota)
{
if (!n)
return nota_skip(nota);
*n = 0;
char *c = nota;
*n |= (*c) & NOTA_INT_DATA; /* first three bits */
while (CONTINUE(*(c++)))
*n = (*n<<7) | (*c) & NOTA_DATA;
if (NOTA_INT_SIGN(*nota)) *n *= -1;
return c;
}
/* n is the number of bits */
char *nota_write_blob(unsigned long long n, char *nota)
{
nota[0] = NOTA_BLOB;
return nota_continue_num(n, nota, 4);
}
char *nota_write_array(unsigned long long n, char *nota)
{
nota[0] = NOTA_ARR;
return nota_continue_num(n, nota, 4);
}
char *nota_read_array(long long *len, char *nota)
{
if (!len) return nota;
return nota_read_num(len, nota);
}
char *nota_read_record(long long *len, char *nota)
{
if (!len) return nota;
return nota_read_num(len, nota);
}
char *nota_read_blob(long long *len, char *nota)
{
if (!len) return nota;
return nota_read_num(len, nota);
}
char *nota_write_record(unsigned long long n, char *nota)
{
nota[0] = NOTA_REC;
return nota_continue_num(n, nota, 4);
}
char *nota_write_sym(int sym, char *nota)
{
*nota = NOTA_SYM | sym;
return nota+1;
}
char *nota_read_sym(int *sym, char *nota)
{
if (*sym) *sym = (*nota) & 0x0f;
return nota+1;
}
char *nota_read_text(char **text, char *nota)
{
long long chars;
nota = nota_read_num(&chars, nota);
char utf[chars*4]; // enough for the worst case scenario
char *pp = utf;
kim_to_utf8(&nota, &pp, chars);
*pp = 0;
*text = strdup(utf);
return nota;
}
char *nota_write_text(const char *s, char *nota)
{
nota[0] = NOTA_TEXT;
long long n = utf8_count(s);
nota = nota_continue_num(n,nota,4);
utf8_to_kim(&s, &nota);
return nota;
}
#endif
#endif

150
source/qjs_dmon.c Normal file
View File

@@ -0,0 +1,150 @@
#include "quickjs.h"
#define DMON_IMPL
#include "dmon.h"
// Define the file event structure and completion queue
typedef struct {
dmon_action action;
char rootdir[256];
char filepath[256];
char oldfilepath[256];
} FileEvent;
typedef struct EventNode {
FileEvent event;
struct EventNode *next;
} EventNode;
typedef struct {
EventNode *head;
EventNode *tail;
} CompletionQueue;
CompletionQueue completionQueue = { NULL, NULL };
// Helper functions for the completion queue
void enqueue_event(FileEvent event) {
EventNode *node = malloc(sizeof(EventNode));
node->event = event;
node->next = NULL;
if (completionQueue.tail) {
completionQueue.tail->next = node;
} else {
completionQueue.head = node;
}
completionQueue.tail = node;
}
int dequeue_event(FileEvent *event) {
if (!completionQueue.head) {
return 0; // No event
}
EventNode *node = completionQueue.head;
*event = node->event;
completionQueue.head = node->next;
if (!completionQueue.head) {
completionQueue.tail = NULL;
}
free(node);
return 1;
}
void watch_cb(dmon_watch_id id, dmon_action action, const char *rootdir, const char *filepath, const char *oldfilepath, void *user)
{
FileEvent event;
event.action = action;
strncpy(event.rootdir, rootdir, sizeof(event.rootdir) - 1);
strncpy(event.filepath, filepath, sizeof(event.filepath) - 1);
if (oldfilepath) {
strncpy(event.oldfilepath, oldfilepath, sizeof(event.oldfilepath) - 1);
} else {
event.oldfilepath[0] = '\0';
}
enqueue_event(event); // Add event to completion queue
}
static dmon_watch_id watched = {0};
JSValue js_dmon_watch(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (watched.id)
return JS_ThrowReferenceError(js, "Already watching a directory.");
const char *dir = JS_ToCString(js,argv[0]);
watched = dmon_watch(dir,watch_cb, DMON_WATCHFLAGS_RECURSIVE, NULL);
return JS_UNDEFINED;
}
JSValue js_dmon_unwatch(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (!watched.id)
return JS_ThrowReferenceError(js, "Not watching a directory.");
dmon_unwatch(watched);
watched.id = 0;
return JS_UNDEFINED;
}
JSValue js_dmon_poll(JSContext *js, JSValueConst this_val, int argc, JSValueConst *argv) {
FileEvent event;
while (dequeue_event(&event)) {
JSValue jsevent = JS_NewObject(js);
JSValue action;
switch(event.action) {
case DMON_ACTION_CREATE:
action = JS_NewAtomString(js, "create");
break;
case DMON_ACTION_DELETE:
action = JS_NewAtomString(js, "delete");
break;
case DMON_ACTION_MODIFY:
action = JS_NewAtomString(js, "modify");
break;
case DMON_ACTION_MOVE:
action = JS_NewAtomString(js, "move");
break;
}
JS_SetPropertyStr(js, jsevent, "action", action);
JS_SetPropertyStr(js, jsevent, "root", JS_NewString(js, event.rootdir));
JS_SetPropertyStr(js, jsevent, "file", JS_NewString(js, event.filepath));
JS_SetPropertyStr(js, jsevent, "old", JS_NewString(js, event.oldfilepath));
JS_Call(js, argv[0], JS_UNDEFINED, 1, &jsevent);
}
return JS_UNDEFINED;
}
static const JSCFunctionListEntry js_dmon_funcs[] = {
JS_CFUNC_DEF("watch", 1, js_dmon_watch),
JS_CFUNC_DEF("unwatch", 0, js_dmon_unwatch),
JS_CFUNC_DEF("poll", 1, js_dmon_poll)
};
JSValue js_dmon_use(JSContext *js)
{
JSValue export = JS_NewObject(js);
JS_SetPropertyFunctionList(js, export, js_dmon_funcs, sizeof(js_dmon_funcs)/sizeof(JSCFunctionListEntry));
dmon_init();
return export;
}
static int js_dmon_init(JSContext *js, JSModuleDef *m) {
JS_SetModuleExport(js, m, "default",js_dmon_use(js));
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_dmon
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *js, const char *module_name) {
JSModuleDef *m = JS_NewCModule(js, module_name, js_dmon_init);
if (!m) return NULL;
JS_AddModuleExport(js, m, "default");
return m;
}

8
source/qjs_dmon.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_DMON_H
#define QJS_DMON_H
#include "quickjs.h"
JSValue js_dmon_use(JSContext *js);
#endif

180
source/qjs_nota.c Executable file
View File

@@ -0,0 +1,180 @@
#include "quickjs.h"
#define KIM_IMPLEMENTATION
#define NOTA_IMPLEMENTATION
#include "nota.h"
char *js_do_nota_decode(JSContext *js, JSValue *tmp, char *nota)
{
int type = nota_type(nota);
JSValue ret2;
long long n;
double d;
int b;
char *str;
switch(type) {
case NOTA_BLOB:
break;
case NOTA_TEXT:
nota = nota_read_text(&str, nota);
*tmp = JS_NewString(js, str);
/* TODO: Avoid malloc and free here */
free(str);
break;
case NOTA_ARR:
nota = nota_read_array(&n, nota);
*tmp = JS_NewArray(js);
for (int i = 0; i < n; i++) {
nota = js_do_nota_decode(js, &ret2, nota);
JS_SetPropertyInt64(js, *tmp, i, ret2);
}
break;
case NOTA_REC:
nota = nota_read_record(&n, nota);
*tmp = JS_NewObject(js);
for (int i = 0; i < n; i++) {
nota = nota_read_text(&str, nota);
nota = js_do_nota_decode(js, &ret2, nota);
JS_SetPropertyStr(js, *tmp, str, ret2);
free(str);
}
break;
case NOTA_INT:
nota = nota_read_int(&n, nota);
*tmp = JS_NewInt64(js,n);
break;
case NOTA_SYM:
nota = nota_read_sym(&b, nota);
if (b == NOTA_NULL) *tmp = JS_UNDEFINED;
else
*tmp = JS_NewBool(js,b);
break;
default:
case NOTA_FLOAT:
nota = nota_read_float(&d, nota);
*tmp = JS_NewFloat64(js,d);
break;
}
return nota;
}
// Writers the JSValue v into the buffer of char *nota, returning a pointer to the next byte in nota to be written
char *js_do_nota_encode(JSContext *js, JSValue v, char *nota)
{
int tag = JS_VALUE_GET_TAG(v);
const char *str = NULL;
JSPropertyEnum *ptab;
uint32_t plen;
int n;
double nval;
JSValue val;
switch(tag) {
case JS_TAG_FLOAT64:
case JS_TAG_INT:
JS_ToFloat64(js, &nval, v);
return nota_write_number(nval, nota);
case JS_TAG_STRING:
str = JS_ToCString(js, v);
nota = nota_write_text(str, nota);
JS_FreeCString(js, str);
return nota;
case JS_TAG_BOOL:
return nota_write_sym(JS_VALUE_GET_BOOL(v), nota);
case JS_TAG_UNDEFINED:
return nota_write_sym(NOTA_NULL, nota);
case JS_TAG_NULL:
return nota_write_sym(NOTA_NULL, nota);
case JS_TAG_OBJECT:
if (JS_IsArray(js, v)) {
int n;
JS_ToInt32(js, &n, JS_GetPropertyStr(js, v, "length"));
nota = nota_write_array(n, nota);
for (int i = 0; i < n; i++)
nota = js_do_nota_encode(js, JS_GetPropertyUint32(js, v, i), nota);
return nota;
}
n = JS_GetOwnPropertyNames(js, &ptab, &plen, v, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK);
nota = nota_write_record(plen, nota);
for (int i = 0; i < plen; i++) {
val = JS_GetProperty(js,v,ptab[i].atom);
str = JS_AtomToCString(js, ptab[i].atom);
JS_FreeAtom(js, ptab[i].atom);
nota = nota_write_text(str, nota);
JS_FreeCString(js, str);
nota = js_do_nota_encode(js, val, nota);
JS_FreeValue(js,val);
}
js_free(js, ptab);
return nota;
default:
return nota;
}
return nota;
}
JSValue js_nota_encode(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (argc < 1)
JS_ThrowInternalError(js, "Expected at least one argument to encode.");
JSValue obj = argv[0];
char nota[1024*1024];
char *e = js_do_nota_encode(js, obj, nota);
return JS_NewArrayBufferCopy(js, (unsigned char*)nota, e-nota);
}
JSValue js_nota_decode(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (argc < 1) return JS_UNDEFINED;
size_t len;
unsigned char *nota = JS_GetArrayBuffer(js, &len, argv[0]);
JSValue ret;
js_do_nota_decode(js, &ret, (char*)nota);
return ret;
}
JSValue js_nota_hex(JSContext *js, JSValue self, int argc, JSValue *argv)
{
size_t len;
unsigned char *nota = JS_GetArrayBuffer(js, &len, argv[0]);
print_nota_hex(nota);
return JS_UNDEFINED;
}
static const JSCFunctionListEntry js_nota_funcs[] = {
JS_CFUNC_DEF("encode", 1, js_nota_encode),
JS_CFUNC_DEF("decode", 1, js_nota_decode),
JS_CFUNC_DEF("hex", 1, js_nota_hex),
};
static int js_nota_init(JSContext *ctx, JSModuleDef *m) {
JS_SetModuleExportList(ctx, m, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
return 0;
}
JSValue js_nota_use(JSContext *js)
{
JSValue export = JS_NewObject(js);
JS_SetPropertyFunctionList(js, export, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
return export;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_nota
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) {
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_nota_init);
if (!m) return NULL;
JS_AddModuleExportList(ctx, m, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
return m;
}

8
source/qjs_nota.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_NOTA_H
#define QJS_NOTA_H
#include "quickjs.h"
JSValue js_nota_use(JSContext*);
#endif

View File

@@ -1,7 +0,0 @@
[wrap-git]
url = https://github.com/johnalanbrook/qjs-dmon.git
revision = head
depth = 1
[provide]
qjs-dmon = qjs_dmon_dep

View File

@@ -1,7 +0,0 @@
[wrap-git]
url = https://github.com/johnalanbrook/cnota.git
revision = head
depth = 1
[provide]
qjs-nota = qjs_nota_dep

126
tests/nota.js Normal file
View File

@@ -0,0 +1,126 @@
var nota = use('nota');
var os = use('os');
// Helper function to convert hex string to ArrayBuffer
function hexToBuffer(hex) {
let bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i/2] = parseInt(hex.substr(i, 2), 16);
}
return bytes.buffer;
}
// Helper function to convert ArrayBuffer to hex string
function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
.toLowerCase();
}
// Test cases
var testCases = [
{ input: 0, expectedHex: "60" },
{ input: 2023, expectedHex: "e08f67" },
{ input: -1, expectedHex: "69" },
{ input: true, expectedHex: "71" },
{ input: false, expectedHex: "72" },
{ input: null, expectedHex: "73" },
{ input: -1.01, expectedHex: "5a65" },
{ input: 98.6, expectedHex: "51875a" },
{ input: "", expectedHex: "10" },
{ input: "cat", expectedHex: "13636174" },
{ input: [], expectedHex: "20" },
{ input: [1, 2, 3], expectedHex: "23616263" },
{ input: {}, expectedHex: "30" },
{ input: {a: 1, b: 2}, expectedHex: "32116161116262" },
{ input: new Uint8Array([0xFF, 0xAA]).buffer, expectedHex: "020280ffaa" },
{ input: {
num: 42,
arr: [1, -1, 2.5],
str: "test",
obj: {x: true}
},
expectedHex: "34216e756d622a2173747214746573742161727223616965235840216f626a21117873"
}
];
// Run tests and collect results
let testCount = 0;
let failedCount = 0;
let results = [];
for (let test of testCases) {
testCount++;
let testName = `Test ${testCount}: ${JSON.stringify(test.input)}`;
let passed = true;
let messages = [];
console.log(`Running ${testName}`);
// Test encoding
let encoded = nota.encode(test.input);
if (!(encoded instanceof ArrayBuffer)) {
passed = false;
messages.push("Encode should return ArrayBuffer");
} else {
let encodedHex = bufferToHex(encoded);
if (encodedHex !== test.expectedHex.toLowerCase()) {
passed = false;
messages.push(
`Encoding failed\n` +
`Expected: ${test.expectedHex}\n` +
`Got: ${encodedHex}`
);
}
// Test decoding
let decoded = nota.decode(encoded);
let inputStr = JSON.stringify(test.input instanceof ArrayBuffer ?
Array.from(new Uint8Array(test.input)) : test.input);
let decodedStr = JSON.stringify(decoded);
if (inputStr !== decodedStr) {
passed = false;
messages.push(
`Decoding failed\n` +
`Expected: ${inputStr}\n` +
`Got: ${decodedStr}`
);
}
}
// Record result
let status = passed ? "PASSED" : "FAILED";
results.push({ testName, status, messages });
if (!passed) failedCount++;
// Print immediate feedback
console.log(`${testName} - ${status}`);
if (!passed) {
console.log(messages.join("\n"));
}
console.log(""); // Empty line between tests
}
// Summary
console.log("Test Summary:");
console.log(`Total tests: ${testCount}`);
console.log(`Passed: ${testCount - failedCount}`);
console.log(`Failed: ${failedCount}`);
console.log("\nDetailed Results:");
results.forEach(result => {
console.log(`${result.testName} - ${result.status}`);
if (result.messages.length > 0) {
console.log(result.messages.join("\n"));
console.log("");
}
});
if (failedCount > 0) {
console.log("Overall result: FAILED");
os.exit(1);
} else {
console.log("Overall result: PASSED");
os.exit(0);
}