Page MenuHomePhorge

No OneTemporary

Size
971 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/examples/spec/formdata.js b/examples/spec/formdata.js
index 0398408..fba2580 100644
--- a/examples/spec/formdata.js
+++ b/examples/spec/formdata.js
@@ -1,120 +1,140 @@
import { test, testThrows, testDeep, summary } from './helpers.js';
console.log('FormData constructor\n');
const fd0 = new FormData();
test('empty constructor', fd0.has('x'), false);
testThrows('requires new', () => FormData());
testThrows('rejects form element', () => new FormData(document));
console.log('\nappend / get / has / delete / set\n');
const fd = new FormData();
fd.append('name', 'Alice');
test('append then get', fd.get('name'), 'Alice');
test('has returns true', fd.has('name'), true);
test('has missing returns false', fd.has('other'), false);
fd.append('name', 'Bob');
const all = fd.getAll('name');
test('getAll length after two appends', all.length, 2);
test('getAll first', all[0], 'Alice');
test('getAll second', all[1], 'Bob');
fd.set('name', 'Carol');
test('set replaces all', fd.getAll('name').length, 1);
test('set value', fd.get('name'), 'Carol');
fd.delete('name');
test('delete removes all', fd.has('name'), false);
test('get after delete returns null', fd.get('name'), null);
console.log('\nBlob values\n');
const blob = new Blob(['hello'], { type: 'text/plain' });
const fdb = new FormData();
fdb.append('file', blob);
const got = fdb.get('file');
test('blob entry is File instance', got instanceof File, true);
test('blob entry filename default', got.name, 'blob');
test('blob entry type', got.type, 'text/plain');
fdb.append('file2', blob, 'custom.txt');
const got2 = fdb.get('file2');
test('blob with filename', got2.name, 'custom.txt');
console.log('\nFile values\n');
const file = new File(['world'], 'test.txt', { type: 'text/html' });
const fdf = new FormData();
fdf.append('upload', file);
const gotf = fdf.get('upload');
test('file entry is File instance', gotf instanceof File, true);
test('file entry name from File', gotf.name, 'test.txt');
test('file entry type', gotf.type, 'text/html');
fdf.append('upload2', file, 'override.txt');
test('file entry name overridden', fdf.get('upload2').name, 'override.txt');
console.log('\ngetAll with mixed types\n');
const fdm = new FormData();
fdm.append('x', 'str');
fdm.append('x', new Blob(['data']));
const mixed = fdm.getAll('x');
test('mixed getAll length', mixed.length, 2);
test('mixed first is string', mixed[0], 'str');
test('mixed second is File', mixed[1] instanceof File, true);
console.log('\nforEach\n');
const fdfe = new FormData();
fdfe.append('b', '2');
fdfe.append('a', '1');
const seen = [];
fdfe.forEach((val, name) => seen.push(`${name}=${val}`));
test('forEach visits all entries', seen.length, 2);
test('forEach insertion order first', seen[0], 'b=2');
test('forEach insertion order second', seen[1], 'a=1');
console.log('\nentries / keys / values\n');
const fdi = new FormData();
fdi.append('x', '1');
fdi.append('y', '2');
fdi.append('x', '3');
const keys = [...fdi.keys()];
test('keys length', keys.length, 3);
test('keys first', keys[0], 'x');
test('keys second', keys[1], 'y');
test('keys third', keys[2], 'x');
const vals = [...fdi.values()];
test('values first', vals[0], '1');
test('values second', vals[1], '2');
test('values third', vals[2], '3');
const entries = [...fdi.entries()];
test('entries length', entries.length, 3);
test('entries first name', entries[0][0], 'x');
test('entries first value', entries[0][1], '1');
console.log('\nSymbol.iterator\n');
const fds = new FormData();
fds.append('k', 'v');
const iter_result = [...fds];
test('Symbol.iterator yields entries', iter_result.length, 1);
test('Symbol.iterator entry name', iter_result[0][0], 'k');
test('Symbol.iterator entry value', iter_result[0][1], 'v');
console.log('\ntoStringTag\n');
test('toStringTag', Object.prototype.toString.call(new FormData()), '[object FormData]');
+console.log('\nmultipart parameter parsing\n');
+
+const boundary = 'spec-boundary';
+const multipartBody =
+ `--${boundary}\r\n` +
+ `Content-Disposition: form-data; name="field"\r\n\r\n` +
+ `value\r\n` +
+ `--${boundary}--\r\n`;
+
+const multipartReq = new Request('https://example.com/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': `multipart/form-data; boundaryx=wrong; boundary="${boundary}"`,
+ },
+ body: multipartBody,
+});
+
+const multipartFd = await multipartReq.formData();
+test('multipart boundary lookup skips longer prefix match', multipartFd.get('field'), 'value');
+
summary();
diff --git a/examples/spec/request.js b/examples/spec/request.js
new file mode 100644
index 0000000..ba2d60c
--- /dev/null
+++ b/examples/spec/request.js
@@ -0,0 +1,132 @@
+import { test, testThrows, summary } from './helpers.js';
+
+console.log('Request Tests\n');
+
+test('Request typeof', typeof Request, 'function');
+test('Request toStringTag', Object.prototype.toString.call(new Request('https://example.com/')), '[object Request]');
+testThrows('Request requires new', () => Request('https://example.com/'));
+
+const req0 = new Request('https://example.com/');
+test('Request method default', req0.method, 'GET');
+test('Request url', req0.url, 'https://example.com/');
+test('Request headers same object', req0.headers === req0.headers, true);
+test('Request keepalive default', req0.keepalive, false);
+test('Request duplex getter', req0.duplex, 'half');
+
+const hdrReq = new Request('https://example.com/', {
+ headers: {
+ 'Accept-Charset': 'blocked',
+ 'X-Custom': 'ok',
+ },
+});
+test('Request filters forbidden headers', hdrReq.headers.get('accept-charset'), null);
+test('Request keeps allowed headers', hdrReq.headers.get('x-custom'), 'ok');
+
+const noCorsReq = new Request('https://example.com/', {
+ mode: 'no-cors',
+ headers: {
+ 'Content-Type': 'text/plain;charset=UTF-8',
+ 'X-Blocked': 'nope',
+ },
+});
+test('Request no-cors keeps safelisted content-type', noCorsReq.headers.get('content-type'), 'text/plain;charset=UTF-8');
+test('Request no-cors strips non-safelisted header', noCorsReq.headers.get('x-blocked'), null);
+
+testThrows(
+ 'Request forbids keepalive with stream body',
+ () => new Request('https://example.com/', {
+ method: 'POST',
+ body: new ReadableStream(),
+ duplex: 'half',
+ keepalive: true,
+ })
+);
+
+testThrows(
+ 'Request requires duplex for stream body',
+ () => new Request('https://example.com/', {
+ method: 'POST',
+ body: new ReadableStream(),
+ })
+);
+
+testThrows(
+ 'Request rejects duplex full',
+ () => new Request('https://example.com/', {
+ method: 'POST',
+ body: 'hello',
+ duplex: 'full',
+ })
+);
+
+testThrows(
+ 'Request rejects forbidden method',
+ () => new Request('https://example.com/', { method: 'TRACE' })
+);
+
+testThrows(
+ 'Request rejects GET with direct body',
+ () => new Request('https://example.com/', { method: 'GET', body: 'hello' })
+);
+
+const bodyReq = new Request('https://example.com/', {
+ method: 'POST',
+ body: 'hello world',
+});
+const bodyStream = bodyReq.body;
+test('Request body is a ReadableStream', bodyStream instanceof ReadableStream, true);
+test('Request body getter is stable', bodyReq.body === bodyStream, true);
+test('Request bodyUsed before consume', bodyReq.bodyUsed, false);
+test('Request text after body access', await bodyReq.text(), 'hello world');
+test('Request bodyUsed after consume', bodyReq.bodyUsed, true);
+test('Request body object preserved after consume', bodyReq.body === bodyStream, true);
+
+const srcReq = new Request('https://example.com/', {
+ method: 'POST',
+ body: 'copied body',
+});
+const srcBody = srcReq.body;
+const copiedReq = new Request(srcReq);
+test('Request-from-Request disturbs source', srcReq.bodyUsed, true);
+test('Request-from-Request keeps source body object', srcReq.body === srcBody, true);
+test('Request-from-Request creates new body object', copiedReq.body === srcBody, false);
+test('Request-from-Request text', await copiedReq.text(), 'copied body');
+
+const failReq = new Request('https://example.com/', {
+ method: 'POST',
+ body: 'will stay unused',
+});
+testThrows(
+ 'Request GET from Request with body throws',
+ () => new Request(failReq, { method: 'GET' })
+);
+test('Request failed construction does not disturb source', failReq.bodyUsed, false);
+
+const usedReq = new Request('https://example.com/', {
+ method: 'POST',
+ body: 'replace me',
+});
+await usedReq.text();
+const replacedReq = new Request(usedReq, {
+ body: 'replacement body',
+ method: 'POST',
+});
+test('Request override from disturbed request succeeds', await replacedReq.text(), 'replacement body');
+
+const initReq = new Request('https://example.com/', {
+ method: 'POST',
+ body: 'init body',
+});
+const reqFromInitReq = new Request('https://example.com/', initReq);
+test('Request init from Request copies method', reqFromInitReq.method, 'POST');
+test('Request init from Request copies body', await reqFromInitReq.text(), 'init body');
+
+const emptyTypeReq = new Request('https://example.com/', {
+ method: 'POST',
+ headers: [['Content-Type', '']],
+ body: 'typed body',
+});
+const emptyTypeBlob = await emptyTypeReq.blob();
+test('Request empty content-type preserved on blob', emptyTypeBlob.type, '');
+
+summary();
diff --git a/include/ant.h b/include/ant.h
index bb4540a..e3c6541 100644
--- a/include/ant.h
+++ b/include/ant.h
@@ -1,179 +1,181 @@
#ifndef ANT_H
#define ANT_H
#pragma once
#define PCRE2_CODE_UNIT_WIDTH 8
#include "types.h"
#include "common.h"
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
#define STR_PROTO "__proto__"
#define STR_PROTO_LEN 9
#define ANT_STRING(s) js_mkstr(js, s, sizeof(s) - 1)
#define ANT_PTR(ptr) js_mknum((double)(uintptr_t)(ptr))
#define ANT_COPY(buf, len, s) cpy(buf, len, s, sizeof(s) - 1)
#define REMAIN(n, len) ((n) >= (len) ? 0 : (len) - (n))
#define JS_NAN ((double)NAN)
#define JS_NEG_NAN ((double)(-NAN))
#define JS_INF ((double)INFINITY)
#define JS_NEG_INF ((double)(-INFINITY))
#define js_true (NANBOX_PREFIX | ((ant_value_t)T_BOOL << NANBOX_TYPE_SHIFT) | 1)
#define js_false (NANBOX_PREFIX | ((ant_value_t)T_BOOL << NANBOX_TYPE_SHIFT))
#define js_bool(x) (js_false | (ant_value_t)!!(x))
ant_t *js_create(void *buf, size_t len);
ant_t *js_create_dynamic();
ant_value_t js_glob(ant_t *);
void js_mark_constructor(ant_value_t value, bool is_constructor);
// TODO: improve naming
ant_value_t js_eval_bytecode(ant_t *, const char *, size_t);
ant_value_t js_eval_bytecode_module(ant_t *, const char *, size_t);
ant_value_t js_eval_bytecode_eval(ant_t *, const char *, size_t);
ant_value_t js_eval_bytecode_eval_with_strict(ant_t *, const char *, size_t, bool);
ant_value_t js_eval_bytecode_repl(ant_t *, const char *, size_t);
void js_destroy(ant_t *);
bool js_truthy(ant_t *, ant_value_t);
void js_setstackbase(ant_t *, void *);
void js_setstacklimit(ant_t *, size_t);
uint32_t js_to_uint32(double d);
int32_t js_to_int32(double d);
bool js_chkargs(ant_value_t *, int, const char *);
void js_set_filename(ant_t *, const char *);
void js_stats(ant_t *, size_t *total, size_t *min, size_t *cstacksize);
ant_value_t js_mkundef(void);
ant_value_t js_mknull(void);
ant_value_t js_mknum(double);
ant_value_t js_mkpromise(ant_t *js);
ant_value_t js_getthis(ant_t *);
void js_setthis(ant_t *, ant_value_t);
ant_value_t js_getcurrentfunc(ant_t *);
ant_value_t js_get(ant_t *, ant_value_t, const char *);
ant_value_t js_getprop_proto(ant_t *, ant_value_t, const char *);
ant_value_t js_getprop_fallback(ant_t *js, ant_value_t obj, const char *name);
ant_value_t js_getprop_super(ant_t *js, ant_value_t super_obj, ant_value_t receiver, const char *name);
ant_offset_t js_arr_len(ant_t *js, ant_value_t arr);
ant_value_t js_arr_get(ant_t *js, ant_value_t arr, ant_offset_t idx);
bool js_iter(
ant_t *js,
ant_value_t iterable,
bool (*callback)(
ant_t *js,
ant_value_t value,
void *udata
),
void *udata
);
const char *js_sym_desc(ant_t *js, ant_value_t sym);
const char *js_sym_key(ant_value_t sym);
ant_value_t js_mksym_for(ant_t *, const char *key);
ant_value_t js_symbol_to_string(ant_t *js, ant_value_t sym);
ant_value_t js_get_sym(ant_t *, ant_value_t obj, ant_value_t sym);
ant_value_t js_mkobj(ant_t *);
ant_value_t js_mkobj_with_inobj_limit(ant_t *, uint8_t inobj_limit);
ant_value_t js_newobj(ant_t *);
ant_value_t js_mkarr(ant_t *);
ant_value_t js_mkstr(ant_t *, const void *, size_t);
ant_value_t js_mkstr_permanent(ant_t *, const void *, size_t);
ant_value_t js_mkbigint(ant_t *, const char *digits, size_t len, bool negative);
ant_value_t js_mksym(ant_t *, const char *desc);
ant_value_t js_mksym_well_known(ant_t *, const char *desc);
ant_value_t js_mkfun(ant_value_t (*fn)(ant_t *, ant_value_t *, int));
ant_value_t js_heavy_mkfun(ant_t *js, ant_value_t (*fn)(ant_t *, ant_value_t *, int), ant_value_t data);
ant_value_t js_mkprop_fast(ant_t *js, ant_value_t obj, const char *key, size_t len, ant_value_t v);
ant_offset_t js_mkprop_fast_off(ant_t *js, ant_value_t obj, const char *key, size_t len, ant_value_t v);
void js_set(ant_t *, ant_value_t, const char *, ant_value_t);
void js_set_sym(ant_t *, ant_value_t obj, ant_value_t sym, ant_value_t val);
void js_saveval(ant_t *js, ant_offset_t off, ant_value_t v);
void js_merge_obj(ant_t *, ant_value_t dst, ant_value_t src);
void js_arr_push(ant_t *, ant_value_t arr, ant_value_t val);
void js_set_proto(ant_value_t obj, ant_value_t proto);
void js_set_proto_wb(ant_t *js, ant_value_t obj, ant_value_t proto);
void js_set_proto_init(ant_value_t obj, ant_value_t proto);
ant_value_t js_propref_load(ant_t *js, ant_offset_t handle);
ant_value_t js_setprop(ant_t *, ant_value_t obj, ant_value_t key, ant_value_t val);
ant_value_t js_setprop_nonconfigurable(ant_t *, ant_value_t obj, const char *key, size_t keylen, ant_value_t val);
ant_value_t js_get_proto(ant_t *, ant_value_t obj);
ant_value_t js_get_ctor_proto(ant_t *, const char *name, size_t len);
ant_value_t js_tostring_val(ant_t *js, ant_value_t value);
uint8_t vtype(ant_value_t val);
size_t vdata(ant_value_t val);
ant_object_t *js_obj_ptr(ant_value_t val);
bool js_is_constructor(ant_t *js, ant_value_t value);
double js_getnum(ant_value_t val);
char *js_getstr(ant_t *js, ant_value_t val, size_t *len);
const char *js_str(ant_t *, ant_value_t val);
const char *get_str_prop(ant_t *js, ant_value_t obj, const char *key, ant_offset_t klen, ant_offset_t *out_len);
typedef struct {
void *ctx;
ant_offset_t off;
} ant_iter_t;
ant_iter_t js_prop_iter_begin(ant_t *js, ant_value_t obj);
bool js_prop_iter_next(ant_iter_t *iter, const char **key, size_t *key_len, ant_value_t *value);
void js_prop_iter_end(ant_iter_t *iter);
ant_value_t js_obj_to_func(ant_value_t obj);
ant_value_t js_obj_to_func_ex(ant_value_t obj, uint8_t flags);
ant_value_t js_mktypedarray(void *data);
void *js_gettypedarray(ant_value_t val);
ant_value_t js_mkffi(unsigned int index);
int js_getffi(ant_value_t val);
void js_resolve_promise(ant_t *js, ant_value_t promise, ant_value_t value);
void js_reject_promise(ant_t *js, ant_value_t promise, ant_value_t value);
void js_check_unhandled_rejections(ant_t *js);
void js_process_promise_handlers(ant_t *js, ant_value_t promise);
void js_setup_import_meta(ant_t *js, const char *filename);
typedef ant_value_t (*js_getter_fn)(ant_t *js, ant_value_t obj, const char *key, size_t key_len);
typedef ant_value_t (*js_keys_fn)(ant_t *js, ant_value_t obj);
typedef bool (*js_setter_fn)(ant_t *js, ant_value_t obj, const char *key, size_t key_len, ant_value_t value);
typedef bool (*js_deleter_fn)(ant_t *js, ant_value_t obj, const char *key, size_t key_len);
typedef void (*js_finalizer_fn)(ant_t *js, ant_object_t *obj);
void js_set_getter(ant_value_t obj, js_getter_fn getter);
void js_set_setter(ant_value_t obj, js_setter_fn setter);
void js_set_deleter(ant_value_t obj, js_deleter_fn deleter);
void js_set_keys(ant_value_t obj, js_keys_fn keys);
void js_set_finalizer(ant_value_t obj, js_finalizer_fn fn);
ant_value_t js_get_slot(ant_value_t obj, internal_slot_t slot);
+ant_value_t js_promise_then(ant_t *js, ant_value_t promise, ant_value_t on_fulfilled, ant_value_t on_rejected);
+
void js_set_slot(ant_value_t obj, internal_slot_t slot, ant_value_t value);
void js_set_slot_wb(ant_t *, ant_value_t obj, internal_slot_t slot, ant_value_t value);
#endif
diff --git a/include/common.h b/include/common.h
index 17cd8cd..d6474b8 100644
--- a/include/common.h
+++ b/include/common.h
@@ -1,98 +1,122 @@
#ifndef COMMON_H
#define COMMON_H
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
typedef enum {
SLOT_NONE = 0,
SLOT_PID,
SLOT_ASYNC,
SLOT_WITH,
SLOT_THIS,
SLOT_NEW_TARGET,
SLOT_FIELD_COUNT,
SLOT_FIELDS,
SLOT_STRICT,
SLOT_CODE,
SLOT_CODE_LEN,
SLOT_CFUNC,
SLOT_CORO,
SLOT_PROTO,
SLOT_FUNC_PROTO,
SLOT_ASYNC_PROTO,
SLOT_BUFFER,
SLOT_TARGET_FUNC,
SLOT_NAME,
SLOT_MAP,
SLOT_SET,
SLOT_PRIMITIVE,
SLOT_PROXY_REF,
SLOT_BUILTIN,
+ SLOT_BRAND,
SLOT_DATA,
SLOT_CTOR,
SLOT_DEFAULT,
SLOT_ERROR_BRAND,
SLOT_ERR_TYPE,
SLOT_OBSERVABLE_SUBSCRIBER,
SLOT_SUBSCRIPTION_OBSERVER,
SLOT_SUBSCRIPTION_CLEANUP,
SLOT_HOISTED_VARS,
SLOT_HOISTED_VARS_LEN,
SLOT_STRICT_ARGS,
SLOT_NO_FUNC_DECLS,
SLOT_ITER_STATE,
SLOT_BYTECODE,
SLOT_ENTRIES,
SLOT_SETTLED,
SLOT_WT_ON_MESSAGE,
SLOT_WT_ONCE_MESSAGE,
SLOT_WT_ON_EXIT,
SLOT_WT_ONCE_EXIT,
SLOT_WT_PORT_TAG,
SLOT_WT_PORT_QUEUE,
SLOT_WT_PORT_HEAD,
SLOT_WT_PORT_PEER,
SLOT_WT_PORT_CLOSED,
SLOT_WT_PORT_STARTED,
SLOT_WT_PORT_ON_MESSAGE,
SLOT_WT_PORT_ONCE_MESSAGE,
SLOT_WT_PORT_PROTO,
SLOT_WT_ENV_STORE,
SLOT_NAPI_EXTERNAL_ID,
SLOT_NAPI_WRAP_ID,
SLOT_RS_PULL,
SLOT_RS_CANCEL,
SLOT_RS_SIZE,
SLOT_RS_CLOSED,
SLOT_WS_WRITE,
SLOT_WS_CLOSE,
SLOT_WS_ABORT,
SLOT_WS_READY,
SLOT_WS_SIGNAL,
+ SLOT_HEADERS_GUARD,
+ SLOT_REQUEST_HEADERS,
+ SLOT_REQUEST_SIGNAL,
+ SLOT_REQUEST_BODY_STREAM,
SLOT_MAX = 255
} internal_slot_t;
typedef enum {
BUILTIN_NONE = 0,
BUILTIN_OBJECT = 1
} builtin_fn_id_t;
+typedef enum {
+ BRAND_NONE = 0,
+ BRAND_BLOB,
+ BRAND_FILE,
+ BRAND_HEADERS,
+ BRAND_FORMDATA,
+ BRAND_URLSEARCHPARAMS,
+ BRAND_DATAVIEW,
+ BRAND_REQUEST,
+ BRAND_READABLE_STREAM,
+ BRAND_READABLE_STREAM_READER,
+ BRAND_READABLE_STREAM_CONTROLLER,
+ BRAND_WRITABLE_STREAM,
+ BRAND_WRITABLE_STREAM_WRITER,
+ BRAND_WRITABLE_STREAM_CONTROLLER,
+ BRAND_TRANSFORM_STREAM,
+ BRAND_TRANSFORM_STREAM_CONTROLLER
+} object_brand_id_t;
+
static inline void *mantissa_chk(void *p, const char *func) {
if (!p || ((uintptr_t)p >> 47) == 0) goto ok;
fprintf(
stderr,
"FATAL: %s returned pointer %p outside 47-bit NaN-boxing range\n"
"Please report this issue with your OS/architecture details.\n", func, p
);
abort();
ok: return p;
}
#define ant_calloc(size) mantissa_chk(calloc(1, size), "calloc")
#define ant_realloc(ptr, size) mantissa_chk(realloc(ptr, size), "realloc")
#endif
diff --git a/include/modules/abort.h b/include/modules/abort.h
index 735633c..703d888 100644
--- a/include/modules/abort.h
+++ b/include/modules/abort.h
@@ -1,17 +1,19 @@
#ifndef ANT_ABORT_MODULE_H
#define ANT_ABORT_MODULE_H
#include "types.h"
#include "gc/modules.h"
void init_abort_module(void);
void gc_mark_abort(ant_t *js, gc_mark_fn mark);
void signal_do_abort(ant_t *js, ant_value_t signal, ant_value_t reason);
void abort_signal_add_listener(ant_t *js, ant_value_t signal, ant_value_t callback);
bool abort_signal_is_signal(ant_value_t signal);
bool abort_signal_is_aborted(ant_value_t signal);
+
ant_value_t abort_signal_get_reason(ant_value_t signal);
+ant_value_t abort_signal_create_dependent(ant_t *js, ant_value_t source);
#endif
diff --git a/include/modules/blob.h b/include/modules/blob.h
index 49d9d56..6cf16ed 100644
--- a/include/modules/blob.h
+++ b/include/modules/blob.h
@@ -1,24 +1,25 @@
#ifndef BLOB_H
#define BLOB_H
#include <stdint.h>
#include <stddef.h>
#include "types.h"
typedef struct {
uint8_t *data;
size_t size;
char *type;
char *name;
int64_t last_modified;
} blob_data_t;
void init_blob_module(void);
+bool blob_is_blob(ant_t *js, ant_value_t obj);
blob_data_t *blob_get_data(ant_value_t obj);
-ant_value_t blob_create(ant_t *js, const uint8_t *data, size_t size, const char *type);
+ant_value_t blob_create(ant_t *js, const uint8_t *data, size_t size, const char *type);
extern ant_value_t g_blob_proto;
extern ant_value_t g_file_proto;
#endif
diff --git a/include/modules/buffer.h b/include/modules/buffer.h
index 90bd591..12120f1 100644
--- a/include/modules/buffer.h
+++ b/include/modules/buffer.h
@@ -1,66 +1,69 @@
#ifndef BUFFER_H
#define BUFFER_H
#include <stdint.h>
#include <stddef.h>
#include "types.h"
typedef struct {
uint8_t *data;
size_t length;
size_t capacity;
int ref_count;
int is_shared;
int is_detached;
} ArrayBufferData;
typedef enum {
TYPED_ARRAY_INT8,
TYPED_ARRAY_UINT8,
TYPED_ARRAY_UINT8_CLAMPED,
TYPED_ARRAY_INT16,
TYPED_ARRAY_UINT16,
TYPED_ARRAY_INT32,
TYPED_ARRAY_UINT32,
TYPED_ARRAY_FLOAT32,
TYPED_ARRAY_FLOAT64,
TYPED_ARRAY_BIGINT64,
TYPED_ARRAY_BIGUINT64
} TypedArrayType;
typedef struct {
ArrayBufferData *buffer;
TypedArrayType type;
size_t byte_offset;
size_t byte_length;
size_t length;
} TypedArrayData;
typedef struct {
ArrayBufferData *buffer;
size_t byte_offset;
size_t byte_length;
} DataViewData;
+ant_value_t buffer_library(ant_t *js);
+
void init_buffer_module(void);
void cleanup_buffer_module(void);
void free_array_buffer_data(ArrayBufferData *data);
-size_t buffer_get_external_memory(void);
ArrayBufferData *create_array_buffer_data(size_t length);
ant_value_t create_arraybuffer_obj(ant_t *js, ArrayBufferData *buffer);
ant_value_t create_typed_array(
ant_t *js, TypedArrayType type, ArrayBufferData *buffer,
size_t byte_offset, size_t length, const char *type_name
);
ant_value_t create_typed_array_with_buffer(
ant_t *js, TypedArrayType type, ArrayBufferData *buffer,
size_t byte_offset, size_t length,
const char *type_name, ant_value_t arraybuffer_obj
);
-ant_value_t buffer_library(ant_t *js);
+size_t buffer_get_external_memory(void);
+bool buffer_is_dataview(ant_value_t obj);
+bool buffer_source_get_bytes(ant_t *js, ant_value_t value, const uint8_t **out, size_t *len);
#endif
diff --git a/include/modules/formdata.h b/include/modules/formdata.h
index f92d4a7..e6c8621 100644
--- a/include/modules/formdata.h
+++ b/include/modules/formdata.h
@@ -1,6 +1,31 @@
#ifndef FORMDATA_H
#define FORMDATA_H
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include "types.h"
+
+typedef struct fd_entry {
+ char *name;
+ bool is_file;
+ char *str_value;
+ size_t val_idx;
+ struct fd_entry *next;
+} fd_entry_t;
+
+typedef struct {
+ fd_entry_t *head;
+ fd_entry_t **tail;
+ size_t count;
+} fd_data_t;
+
void init_formdata_module(void);
+bool formdata_is_empty(ant_value_t fd);
+bool formdata_is_formdata(ant_t *js, ant_value_t obj);
+
+ant_value_t formdata_create_empty(ant_t *js);
+ant_value_t formdata_append_string(ant_t *js, ant_value_t fd, ant_value_t name_v, ant_value_t value_v);
+ant_value_t formdata_append_file(ant_t *js, ant_value_t fd, ant_value_t name_v, ant_value_t blob_v, ant_value_t filename_v);
#endif
diff --git a/include/modules/headers.h b/include/modules/headers.h
index 2637e6a..c75631e 100644
--- a/include/modules/headers.h
+++ b/include/modules/headers.h
@@ -1,12 +1,31 @@
#ifndef HEADERS_H
#define HEADERS_H
#include "types.h"
#include "modules/symbol.h"
extern ant_value_t g_headers_iter_proto;
+extern ant_value_t g_headers_proto;
+
+typedef enum {
+ HEADERS_GUARD_NONE = 0,
+ HEADERS_GUARD_REQUEST,
+ HEADERS_GUARD_REQUEST_NO_CORS
+} headers_guard_t;
+
+headers_guard_t headers_get_guard(ant_value_t hdrs);
void init_headers_module(void);
+void headers_set_guard(ant_value_t hdrs, headers_guard_t guard);
+void headers_apply_guard(ant_value_t hdrs);
+void headers_append_if_missing(ant_value_t hdrs, const char *name, const char *value);
+
+bool headers_is_headers(ant_value_t obj);
+bool headers_copy_from(ant_t *js, ant_value_t dst, ant_value_t src);
bool advance_headers(ant_t *js, struct js_iter_t *it, ant_value_t *out);
+ant_value_t headers_create_empty(ant_t *js);
+ant_value_t headers_get_value(ant_t *js, ant_value_t hdrs, const char *name);
+ant_value_t headers_append_value(ant_t *js, ant_value_t hdrs, ant_value_t name_v, ant_value_t value_v);
+
#endif
diff --git a/include/modules/multipart.h b/include/modules/multipart.h
new file mode 100644
index 0000000..fa4c1b1
--- /dev/null
+++ b/include/modules/multipart.h
@@ -0,0 +1,19 @@
+#ifndef MULTIPART_H
+#define MULTIPART_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "types.h"
+
+uint8_t *formdata_serialize_multipart(
+ ant_t *js, ant_value_t fd,
+ size_t *out_size, char **out_boundary
+);
+
+ant_value_t formdata_parse_body(
+ ant_t *js, const uint8_t *data, size_t size,
+ const char *body_type, bool has_body
+);
+
+#endif
diff --git a/include/modules/request.h b/include/modules/request.h
new file mode 100644
index 0000000..b7451d3
--- /dev/null
+++ b/include/modules/request.h
@@ -0,0 +1,43 @@
+#ifndef REQUEST_H
+#define REQUEST_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include "types.h"
+#include "modules/url.h"
+
+typedef struct {
+ char *method;
+ url_state_t url;
+ char *referrer;
+ char *referrer_policy;
+ char *mode;
+ char *credentials;
+ char *cache;
+ char *redirect;
+ char *integrity;
+ bool keepalive;
+ bool reload_navigation;
+ bool history_navigation;
+ uint8_t *body_data;
+ size_t body_size;
+ char *body_type;
+ bool body_is_stream;
+ bool has_body;
+ bool body_used;
+} request_data_t;
+
+extern ant_value_t g_request_proto;
+void init_request_module(void);
+
+request_data_t *request_get_data(ant_value_t obj);
+ant_value_t request_get_headers(ant_value_t obj);
+ant_value_t request_get_signal(ant_value_t obj);
+
+ant_value_t request_create(
+ ant_t *js, const char *method, const char *url, ant_value_t headers,
+ const uint8_t *body, size_t body_len, const char *body_type
+);
+
+#endif
diff --git a/include/modules/url.h b/include/modules/url.h
index a69845a..b88ee9f 100644
--- a/include/modules/url.h
+++ b/include/modules/url.h
@@ -1,40 +1,40 @@
#ifndef URL_H
#define URL_H
#include <stddef.h>
#include "types.h"
typedef struct {
char *protocol;
char *username;
char *password;
char *hostname;
char *port;
char *pathname;
char *search;
char *hash;
} url_state_t;
void init_url_module(void);
void url_state_clear(url_state_t *s);
void url_free_state(url_state_t *s);
+bool usp_is_urlsearchparams(ant_t *js, ant_value_t obj);
ant_value_t url_library(ant_t *js);
url_state_t *url_get_state(ant_value_t obj);
ant_value_t make_url_obj(ant_t *js, url_state_t *s);
char *build_href(const url_state_t *s);
char *usp_serialize(ant_t *js, ant_value_t usp);
-
char *form_urlencode(const char *str);
char *form_urlencode_n(const char *str, size_t len);
char *form_urldecode(const char *str);
char *url_decode_component(const char *str);
int parse_url_to_state(
const char *url_str,
const char *base_str, url_state_t *s
);
#endif
diff --git a/include/streams/pipes.h b/include/streams/pipes.h
index 13e9ac8..377620a 100644
--- a/include/streams/pipes.h
+++ b/include/streams/pipes.h
@@ -1,15 +1,16 @@
#ifndef STREAMS_PIPES_H
#define STREAMS_PIPES_H
#include "types.h"
#include <stdbool.h>
void init_pipes_proto(ant_t *js, ant_value_t rs_proto);
+ant_value_t readable_stream_tee(ant_t *js, ant_value_t source);
ant_value_t readable_stream_pipe_to(
ant_t *js, ant_value_t source, ant_value_t dest,
bool prevent_close, bool prevent_abort, bool prevent_cancel,
ant_value_t signal
);
#endif
diff --git a/include/streams/readable.h b/include/streams/readable.h
index b587014..a4b2fb8 100644
--- a/include/streams/readable.h
+++ b/include/streams/readable.h
@@ -1,78 +1,81 @@
#ifndef STREAMS_READABLE_H
#define STREAMS_READABLE_H
#include "types.h"
#include <stdbool.h>
#include <stddef.h>
typedef enum {
RS_STATE_READABLE = 0,
RS_STATE_CLOSED,
RS_STATE_ERRORED,
} rs_state_t;
typedef struct {
double *queue_sizes;
uint32_t queue_sizes_len;
uint32_t queue_sizes_cap;
double queue_total_size;
double strategy_hwm;
bool close_requested;
bool defer_close;
bool in_enqueue;
bool pull_again;
bool pulling;
bool started;
} rs_controller_t;
typedef struct {
rs_state_t state;
bool disturbed;
} rs_stream_t;
extern ant_value_t g_rs_proto;
extern ant_value_t g_reader_proto;
extern ant_value_t g_controller_proto;
void init_readable_stream_module(void);
void gc_mark_readable_streams(ant_t *js, void (*mark)(ant_t *, ant_value_t));
bool rs_is_stream(ant_value_t obj);
bool rs_is_reader(ant_value_t obj);
bool rs_is_controller(ant_value_t obj);
+bool rs_stream_locked(ant_value_t stream_obj);
+bool rs_stream_disturbed(ant_value_t stream_obj);
+bool rs_stream_unusable(ant_value_t stream_obj);
rs_stream_t *rs_get_stream(ant_value_t obj);
rs_controller_t *rs_get_controller(ant_value_t obj);
ant_offset_t rs_ctrl_queue_len(ant_t *js, ant_value_t ctrl_obj);
ant_value_t rs_ctrl_size(ant_value_t ctrl_obj);
ant_value_t rs_reader_reqs(ant_value_t reader_obj);
ant_value_t rs_stream_error(ant_value_t stream_obj);
ant_value_t rs_stream_reader(ant_value_t stream_obj);
ant_value_t rs_reader_stream(ant_value_t reader_obj);
ant_value_t rs_reader_closed(ant_value_t reader_obj);
ant_value_t rs_stream_controller(ant_t *js, ant_value_t stream_obj);
ant_value_t rs_default_reader_read(ant_t *js, ant_value_t reader_obj);
ant_value_t rs_cancel_reject(ant_t *js, ant_value_t *args, int nargs);
ant_value_t js_rs_reader_ctor(ant_t *js, ant_value_t *args, int nargs);
ant_value_t rs_cancel_resolve(ant_t *js, ant_value_t *args, int nargs);
ant_value_t readable_stream_cancel(ant_t *js, ant_value_t stream_obj, ant_value_t reason);
ant_value_t rs_create_stream(ant_t *js, ant_value_t pull_fn, ant_value_t cancel_fn, double hwm);
ant_value_t rs_controller_enqueue(ant_t *js, ant_value_t ctrl_obj, ant_value_t chunk);
void rs_controller_close(ant_t *js, ant_value_t ctrl_obj);
void rs_default_controller_clear_algorithms(ant_value_t ctrl_obj);
void rs_ctrl_queue_push(ant_t *js, ant_value_t ctrl_obj, ant_value_t value);
void rs_default_controller_call_pull_if_needed(ant_t *js, ant_value_t controller_obj);
void rs_default_reader_error_read_requests(ant_t *js, ant_value_t reader_obj, ant_value_t e);
void rs_fulfill_read_request(ant_t *js, ant_value_t stream_obj, ant_value_t chunk, bool done);
void readable_stream_close(ant_t *js, ant_value_t stream_obj);
void readable_stream_error(ant_t *js, ant_value_t stream_obj, ant_value_t e);
bool rs_reader_has_reqs(ant_t *js, ant_value_t reader_obj);
bool rs_default_controller_can_close_or_enqueue(rs_controller_t *ctrl, rs_stream_t *stream);
#endif
diff --git a/src/ant.c b/src/ant.c
index cbe527e..509677a 100644
--- a/src/ant.c
+++ b/src/ant.c
@@ -1,12647 +1,12651 @@
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC optimize("O3,inline")
#endif
#include <compat.h> // IWYU pragma: keep
#include "ant.h"
#include "utf8.h"
#include "debug.h"
#include "tokens.h"
#include "common.h"
#include "utils.h"
#include "base64.h"
#include "runtime.h"
#include "internal.h"
#include "errors.h"
#include "descriptors.h"
#include "shapes.h"
#include "gc.h"
#include "gc/objects.h"
#include "gc/roots.h"
#include "esm/remote.h"
#include "esm/loader.h"
#include "silver/lexer.h"
#include "silver/compiler.h"
#include "silver/engine.h"
#include <uv.h>
#include <oxc.h>
#include <assert.h>
#include <pcre2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <utarray.h>
#include <uthash.h>
#include <float.h>
#include <tlsuv/tlsuv.h>
#include <tlsuv/http.h>
#include <minicoro.h>
#ifdef _WIN32
#include <sys/stat.h>
#else
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/resource.h>
#endif
#include "modules/bigint.h"
#include "modules/timer.h"
#include "modules/symbol.h"
#include "modules/ffi.h"
#include "modules/date.h"
#include "modules/buffer.h"
#include "modules/blob.h"
#include "modules/collections.h"
#include "modules/lmdb.h"
#include "modules/regex.h"
#include "modules/globals.h"
#define D(x) ((double)(x))
_Static_assert(sizeof(double) == 8, "NaN-boxing requires 64-bit IEEE 754 doubles");
_Static_assert(sizeof(uint64_t) == 8, "NaN-boxing requires 64-bit integers");
_Static_assert(sizeof(double) == sizeof(uint64_t), "double and uint64_t must have same size");
#if defined(__STDC_IEC_559__) || defined(__GCC_IEC_559)
#elif defined(__FAST_MATH__)
#error "NaN-boxing is incompatible with -ffast-math"
#elif DBL_MANT_DIG != 53 || DBL_MAX_EXP != 1024
#error "NaN-boxing requires IEEE 754 binary64 doubles"
#endif
static const char *INTERN_LENGTH = NULL;
static const char *INTERN_BUFFER = NULL;
static const char *INTERN_PROTOTYPE = NULL;
static const char *INTERN_CONSTRUCTOR = NULL;
static const char *INTERN_NAME = NULL;
static const char *INTERN_MESSAGE = NULL;
static const char *INTERN_VALUE = NULL;
static const char *INTERN_GET = NULL;
static const char *INTERN_SET = NULL;
static const char *INTERN_ARGUMENTS = NULL;
static const char *INTERN_CALLEE = NULL;
static const char *INTERN_IDX[10] = {NULL};
typedef struct interned_string {
uint64_t hash;
char *str;
size_t len;
struct interned_string *next;
} interned_string_t;
typedef struct {
uint32_t id;
uint32_t flags;
const char *key;
uint32_t desc_len;
char desc[];
} ant_symbol_heap_t;
static size_t intern_count = 0;
static size_t intern_bytes = 0;
static interned_string_t **intern_buckets = NULL;
static size_t intern_bucket_count = 0;
#define INTERN_BUCKET_MIN 1024u
#define INTERN_LOAD_NUM 4u
#define INTERN_LOAD_DEN 5u
static bool intern_table_init(void) {
if (intern_buckets) return true;
intern_bucket_count = INTERN_BUCKET_MIN;
intern_buckets = ant_calloc(sizeof(*intern_buckets) * intern_bucket_count);
if (!intern_buckets) {
intern_bucket_count = 0;
return false;
}
return true;
}
static bool intern_table_rehash(size_t new_bucket_count) {
if (!intern_buckets || new_bucket_count < INTERN_BUCKET_MIN) return false;
interned_string_t **next = ant_calloc(sizeof(*next) * new_bucket_count);
if (!next) return false;
for (size_t i = 0; i < intern_bucket_count; i++) {
interned_string_t *entry = intern_buckets[i];
while (entry) {
interned_string_t *link = entry->next;
size_t bucket = (size_t)(entry->hash & (new_bucket_count - 1));
entry->next = next[bucket];
next[bucket] = entry;
entry = link;
}
}
free(intern_buckets);
intern_buckets = next;
intern_bucket_count = new_bucket_count;
return true;
}
typedef struct promise_handler {
ant_value_t onFulfilled;
ant_value_t onRejected;
ant_value_t nextPromise;
} promise_handler_t;
static const UT_icd promise_handler_icd = {
.sz = sizeof(promise_handler_t),
.init = NULL,
.copy = NULL,
.dtor = NULL,
};
static uint32_t next_promise_id = 1;
static uint32_t get_promise_id(ant_t *js, ant_value_t p);
static ant_promise_state_t *get_promise_data(ant_t *js, ant_value_t promise, bool create);
static ant_proxy_state_t *get_proxy_data(ant_value_t obj);
typedef struct {
bool has_getter;
bool has_setter;
bool writable;
bool enumerable;
bool configurable;
ant_value_t getter;
ant_value_t setter;
} prop_meta_t;
static bool lookup_symbol_prop_meta(ant_value_t cur_obj, ant_offset_t sym_off, prop_meta_t *out);
static bool lookup_string_prop_meta(ant_t *js, ant_value_t cur_obj, const char *key, size_t klen, prop_meta_t *out);
ant_value_t tov(double d) {
union { double d; ant_value_t v; } u = {d};
if (__builtin_expect(isnan(d), 0))
return (u.v > NANBOX_PREFIX)
? 0x7FF8000000000000ULL : u.v; // canonical NaN
return u.v;
}
double tod(ant_value_t v) {
union { ant_value_t v; double d; } u = {v}; return u.d;
}
static bool is_tagged(ant_value_t v) {
return v > NANBOX_PREFIX;
}
size_t vdata(ant_value_t v) {
return (size_t)(v & NANBOX_DATA_MASK);
}
ant_object_t *js_obj_ptr(ant_value_t v) {
if (!is_object_type(v)) return NULL;
ant_value_t as_obj = js_as_obj(v);
return (ant_object_t *)(uintptr_t)vdata(as_obj);
}
void js_mark_constructor(ant_value_t value, bool is_constructor) {
ant_object_t *obj = js_obj_ptr(value);
if (obj) obj->is_constructor = is_constructor ? 1u : 0u;
}
static inline ant_flat_string_t *str_flat_ptr(ant_value_t value) {
if (vtype(value) != T_STR || str_is_heap_rope(value)) return NULL;
return (ant_flat_string_t *)(uintptr_t)vdata(value);
}
static inline ant_rope_heap_t *str_rope_ptr(ant_value_t value) {
return (ant_rope_heap_t *)(uintptr_t)(vdata(value) & ~1ULL);
}
static inline ant_value_t mkrope_value(ant_rope_heap_t *rope) {
return mkval(T_STR, ((uintptr_t)rope) | 1ULL);
}
static inline ant_extra_slot_t *obj_extra_slots(ant_object_t *obj) {
return (ant_extra_slot_t *)obj->extra_slots;
}
static ant_value_t obj_extra_get(ant_object_t *obj, internal_slot_t slot) {
if (!obj || obj->extra_count == 0) return js_mkundef();
ant_extra_slot_t *entries = obj_extra_slots(obj);
for (uint8_t i = 0; i < obj->extra_count; i++) {
if ((internal_slot_t)entries[i].slot == slot) return entries[i].value;
}
return js_mkundef();
}
static bool obj_extra_set(ant_object_t *obj, internal_slot_t slot, ant_value_t value) {
if (!obj) return false;
ant_extra_slot_t *entries = obj_extra_slots(obj);
for (uint8_t i = 0; i < obj->extra_count; i++) {
if ((internal_slot_t)entries[i].slot == slot) {
entries[i].value = value;
return true;
}
}
if (obj->extra_count == UINT8_MAX) return false;
uint8_t next_count = (uint8_t)(obj->extra_count + 1);
ant_extra_slot_t *next = realloc(entries, sizeof(*next) * next_count);
if (!next) return false;
next[obj->extra_count].slot = (uint8_t)slot;
next[obj->extra_count].value = value;
obj->extra_slots = (ant_value_t *)next;
obj->extra_count = next_count;
return true;
}
static ant_offset_t propref_make(ant_t *js, ant_object_t *obj, uint32_t slot) {
if (!js || !obj) return 0;
if (js->prop_refs_len >= js->prop_refs_cap) {
ant_offset_t next_cap = js->prop_refs_cap ? js->prop_refs_cap * 2 : 256;
ant_prop_ref_t *next = realloc(js->prop_refs, sizeof(*next) * next_cap);
if (!next) return 0;
js->prop_refs = next;
js->prop_refs_cap = next_cap;
}
ant_offset_t handle = js->prop_refs_len + 1;
js->prop_refs[js->prop_refs_len++] = (ant_prop_ref_t){
.obj = obj,
.slot = slot,
.valid = true,
};
obj->propref_count++;
return handle;
}
static ant_prop_ref_t *propref_get(ant_t *js, ant_offset_t handle) {
if (!js || handle == 0 || handle > js->prop_refs_len) return NULL;
ant_prop_ref_t *ref = &js->prop_refs[handle - 1];
return ref->valid ? ref : NULL;
}
static inline ant_value_t propref_load(ant_t *js, ant_offset_t handle) {
ant_prop_ref_t *ref = propref_get(js, handle);
if (!ref || !ref->obj || ref->slot >= ref->obj->prop_count) return js_mkundef();
return ant_object_prop_get_unchecked(ref->obj, ref->slot);
}
static inline bool propref_store(ant_t *js, ant_offset_t handle, ant_value_t value) {
ant_prop_ref_t *ref = propref_get(js, handle);
if (!ref || !ref->obj || ref->slot >= ref->obj->prop_count) return false;
ant_object_prop_set_unchecked(ref->obj, ref->slot, value);
gc_write_barrier(js, ref->obj, value);
return true;
}
static void propref_adjust_after_swap_delete(ant_t *js, ant_object_t *obj, uint32_t deleted_slot, uint32_t swapped_from) {
if (!js || !obj || obj->propref_count == 0) return;
for (ant_offset_t i = js->prop_refs_len; i-- > 0;) {
ant_prop_ref_t *ref = &js->prop_refs[i];
if (!ref->valid || ref->obj != obj) continue;
if (ref->slot == deleted_slot) {
ref->valid = false;
obj->propref_count--;
if (obj->propref_count == 0) return;
} else if (ref->slot == swapped_from) ref->slot = deleted_slot;
}
}
bool js_obj_ensure_prop_capacity(ant_object_t *obj, uint32_t needed) {
if (!obj) return false;
uint32_t inobj_limit = ant_object_inobj_limit(obj);
uint32_t old_count = obj->prop_count;
if (needed <= obj->prop_count) return true;
if (needed > inobj_limit) {
uint32_t overflow_needed = needed - inobj_limit;
if (overflow_needed > obj->overflow_cap) {
uint32_t new_cap = obj->overflow_cap ? (uint32_t)obj->overflow_cap * 2 : 4;
while (new_cap < overflow_needed) new_cap *= 2;
if (new_cap > 255) new_cap = overflow_needed;
ant_value_t *next = realloc(obj->overflow_prop, sizeof(*next) * new_cap);
if (!next) return false;
obj->overflow_prop = next;
obj->overflow_cap = (uint8_t)new_cap;
}
} else if (obj->overflow_prop) {
free(obj->overflow_prop);
obj->overflow_prop = NULL;
obj->overflow_cap = 0;
}
obj->prop_count = needed;
for (uint32_t i = old_count; i < needed; i++) {
ant_object_prop_set_unchecked(obj, i, js_mkundef());
}
return true;
}
bool js_obj_ensure_unique_shape(ant_object_t *obj) {
if (!obj || !obj->shape) return false;
if (!ant_shape_is_shared(obj->shape)) return true;
ant_shape_t *copy = ant_shape_clone(obj->shape);
if (!copy) return false;
ant_shape_release(obj->shape);
obj->shape = copy;
return true;
}
static void obj_remove_prop_slot(ant_object_t *obj, uint32_t slot) {
if (!obj || slot >= obj->prop_count) return;
uint32_t last = obj->prop_count - 1;
if (slot != last) {
ant_object_prop_set_unchecked(obj, slot, ant_object_prop_get_unchecked(obj, last));
}
obj->prop_count--;
}
static ant_exotic_ops_t *obj_ensure_exotic_ops(ant_object_t *obj) {
if (!obj) return NULL;
if (!obj->exotic_ops) {
ant_exotic_ops_t *ops = calloc(1, sizeof(*ops));
if (!ops) return NULL;
obj->exotic_ops = ops;
}
return (ant_exotic_ops_t *)(void *)obj->exotic_ops;
}
static ant_object_t *obj_alloc(ant_t *js, uint8_t type_tag, uint8_t inobj_limit) {
#ifdef ANT_JIT
if (__builtin_expect(js->jit_active_depth == 0, 1))
#endif
{
size_t threshold = GC_HEAP_GROWTH(js->gc_last_live);
if (threshold < 2048) threshold = 2048;
if (js->obj_arena.live_count >= threshold) gc_run(js);
}
ant_object_t *obj = (ant_object_t *)fixed_arena_alloc(&js->obj_arena);
if (!obj) return NULL;
obj->type_tag = type_tag;
obj->proto = js_mkundef();
obj->shape = ant_shape_new_with_inobj_limit(inobj_limit);
obj->overflow_prop = NULL;
obj->overflow_cap = 0;
obj->prop_count = 0;
obj->inobj_limit = ant_shape_get_inobj_limit(obj->shape);
for (uint32_t i = 0; i < ANT_INOBJ_MAX_SLOTS; i++) obj->inobj[i] = js_mkundef();
obj->extensible = 1;
obj->frozen = 0;
obj->sealed = 0;
obj->is_exotic = 0;
obj->is_constructor = 0;
obj->fast_array = 0;
obj->exotic_ops = NULL;
obj->exotic_keys = NULL;
obj->promise_state = NULL;
obj->proxy_state = NULL;
obj->u.data.value = js_mkundef();
obj->extra_slots = NULL;
obj->extra_count = 0;
obj->gc_pending_next = NULL;
obj->gc_pending_rooted = false;
obj->generation = 0;
obj->in_remember_set = 0;
obj->next = js->objects;
js->objects = obj;
return obj;
}
static ant_value_t get_slot(ant_value_t obj, internal_slot_t slot);
static void set_slot(ant_value_t obj, internal_slot_t slot, ant_value_t value);
static ant_value_t get_proto(ant_t *js, ant_value_t obj);
static void set_proto(ant_t *js, ant_value_t obj, ant_value_t proto);
const char *typestr(uint8_t t) {
static const char *names[] = {
[T_UNDEF] = "undefined", [T_NULL] = "object", [T_BOOL] = "boolean",
[T_NUM] = "number", [T_BIGINT] = "bigint", [T_STR] = "string",
[T_SYMBOL] = "symbol", [T_OBJ] = "object", [T_ARR] = "object",
[T_FUNC] = "function", [T_CFUNC] = "function", [T_CLOSURE] = "closure",
[T_PROMISE] = "promise", [T_GENERATOR] = "generator",
[T_ERR] = "err", [T_TYPEDARRAY] = "typedarray",
[T_FFI] = "ffi", [T_NTARG] = "ntarg"
};
return (t < sizeof(names) / sizeof(names[0])) ? names[t] : "??";
}
uint8_t vtype(ant_value_t v) {
return is_tagged(v) ? ((v >> NANBOX_TYPE_SHIFT) & NANBOX_TYPE_MASK) : (uint8_t)T_NUM;
}
ant_value_t mkval(uint8_t type, uint64_t data) {
return NANBOX_PREFIX
| ((ant_value_t)(type & NANBOX_TYPE_MASK) << NANBOX_TYPE_SHIFT)
| (data & NANBOX_DATA_MASK);
}
ant_value_t js_obj_to_func_ex(ant_value_t obj, uint8_t flags) {
sv_closure_t *closure = js_closure_alloc(rt->js);
if (!closure) return mkval(T_ERR, 0);
closure->func_obj = (vtype(obj) == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
closure->bound_this = js_mkundef();
closure->bound_args = js_mkundef();
closure->super_val = js_mkundef();
closure->call_flags = flags;
ant_object_t *func_obj = js_obj_ptr(closure->func_obj);
if (func_obj) {
if (flags & SV_CALL_IS_DEFAULT_CTOR) {
func_obj->is_constructor = 1;
} else if (
!func_obj->is_constructor &&
func_obj->shape &&
INTERN_PROTOTYPE &&
vtype(obj_extra_get(func_obj, SLOT_CFUNC)) == T_CFUNC
) {
// mark native function objects as constructors when they are
// created with an explicit .prototype own property.
if (ant_shape_lookup_interned(func_obj->shape, INTERN_PROTOTYPE) >= 0)
func_obj->is_constructor = 1;
}
}
return mkval(T_FUNC, (uintptr_t)closure);
}
ant_value_t js_obj_to_func(ant_value_t obj) {
return js_obj_to_func_ex(obj, 0);
}
ant_value_t js_mktypedarray(void *data) {
return mkval(T_TYPEDARRAY, (uintptr_t)data);
}
void *js_gettypedarray(ant_value_t val) {
if (vtype(val) != T_TYPEDARRAY) return NULL;
return (void *)vdata(val);
}
ant_value_t js_get_slot(ant_value_t obj, internal_slot_t slot) {
return get_slot(js_as_obj(obj), slot);
}
ant_value_t js_mkffi(unsigned int index) {
return mkval(T_FFI, (uint64_t)index);
}
int js_getffi(ant_value_t val) {
if (vtype(val) != T_FFI) return -1;
return (int)vdata(val);
}
typedef enum {
NTARG_INVALID = 0,
NTARG_NEW_TARGET = 1
} ntarg_kind_t;
static inline bool is_unboxed_obj(ant_t *js, ant_value_t val, ant_value_t expected_proto) {
if (vtype(val) != T_OBJ) return false;
if (vtype(get_slot(val, SLOT_PRIMITIVE)) != T_UNDEF) return false;
ant_value_t proto = get_slot(val, SLOT_PROTO);
return vdata(proto) == vdata(expected_proto);
}
uint32_t js_to_uint32(double d) {
if (!isfinite(d) || d == 0) return 0;
double sign = (d < 0) ? -1.0 : 1.0;
double posInt = sign * floor(fabs(d));
double val = fmod(posInt, 4294967296.0);
if (val < 0) val += 4294967296.0;
return (uint32_t) val;
}
int32_t js_to_int32(double d) {
uint32_t uint32 = js_to_uint32(d);
if (uint32 >= 2147483648U) return (int32_t)(uint32 - 4294967296.0);
return (int32_t) uint32;
}
static size_t strstring(ant_t *js, ant_value_t value, char *buf, size_t len);
static size_t strkey(ant_t *js, ant_value_t value, char *buf, size_t len);
ant_offset_t vstrlen(ant_t *js, ant_value_t v) {
if (str_is_heap_rope(v)) {
ant_rope_heap_t *rope = str_rope_ptr(v);
return rope ? rope->len : 0;
}
ant_flat_string_t *flat = str_flat_ptr(v);
return flat ? flat->len : 0;
}
static ant_value_t proxy_read_target(ant_t *js, ant_value_t obj);
static ant_offset_t proxy_aware_length(ant_t *js, ant_value_t obj);
static ant_value_t proxy_aware_get_elem(ant_t *js, ant_value_t obj, const char *key, size_t key_len);
static ant_offset_t get_dense_buf(ant_value_t arr);
static ant_offset_t dense_capacity(ant_offset_t doff);
static ant_offset_t get_array_length(ant_t *js, ant_value_t arr);
static ant_value_t arr_get(ant_t *js, ant_value_t arr, ant_offset_t idx);
static bool arr_has(ant_t *js, ant_value_t arr, ant_offset_t idx);
static bool streq(const char *buf, size_t len, const char *p, size_t n);
static bool parse_func_params(ant_t *js, uint8_t *flags, int *out_count);
static bool try_dynamic_setter(ant_t *js, ant_value_t obj, const char *key, size_t key_len, ant_value_t value);
static uintptr_t lkp_with_setter(ant_t *js, ant_value_t obj, const char *buf, size_t len, ant_value_t *setter_out, bool *has_setter_out);
static ant_value_t call_proto_accessor(ant_t *js, ant_value_t prim, ant_value_t accessor, bool has_accessor, ant_value_t *arg, int arg_count, bool is_setter);
static ant_value_t get_prototype_for_type(ant_t *js, uint8_t type);
static inline ant_value_t lkp_val(ant_t *js, ant_value_t obj, const char *buf, size_t len);
static inline ant_value_t lkp_sym_proto_val(ant_t *js, ant_value_t obj, ant_offset_t sym_off);
static size_t tostr(ant_t *js, ant_value_t value, char *buf, size_t len);
static size_t strpromise(ant_t *js, ant_value_t value, char *buf, size_t len);
static ant_value_t js_call_valueOf(ant_t *js, ant_value_t value);
static ant_value_t js_call_toString(ant_t *js, ant_value_t value);
static ant_value_t js_call_method(ant_t *js, ant_value_t obj, const char *method, size_t method_len, ant_value_t *args, int nargs);
static inline bool is_slot_prop(ant_offset_t header);
static inline ant_offset_t next_prop(ant_offset_t header);
static ant_value_t builtin_Object(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t builtin_promise_then(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t string_split_impl(ant_t *js, ant_value_t str, ant_value_t *args, int nargs);
static ant_value_t proxy_get(ant_t *js, ant_value_t proxy, const char *key, size_t key_len);
static ant_value_t proxy_get_val(ant_t *js, ant_value_t proxy, ant_value_t key_val);
static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t value);
static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size_t key_len);
static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_val);
static ant_value_t proxy_delete(ant_t *js, ant_value_t proxy, const char *key, size_t key_len);
static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t key_val);
static ant_value_t get_prototype_for_type(ant_t *js, uint8_t type);
static ant_value_t get_ctor_proto(ant_t *js, const char *name, size_t len);
ant_offset_t lkp_interned(ant_t *js, ant_value_t obj, const char *search_intern, size_t len);
static ant_offset_t get_array_length(ant_t *js, ant_value_t arr);
static inline void array_len_set(ant_t *js, ant_value_t obj, ant_offset_t new_len);
typedef struct { ant_value_t handle; bool is_new; } ctor_t;
static ctor_t get_constructor(ant_t *js, const char *name, size_t len) {
ctor_t ctor;
ctor.handle = get_ctor_proto(js, name, len);
ctor.is_new = (vtype(js->new_target) != T_UNDEF);
return ctor;
}
ant_value_t unwrap_primitive(ant_t *js, ant_value_t val) {
if (__builtin_expect(vtype(val) != T_OBJ, 1)) return val;
ant_value_t prim = get_slot(val, SLOT_PRIMITIVE);
if (__builtin_expect(vtype(prim) == T_UNDEF, 1)) return val;
return prim;
}
static ant_value_t to_string_val(ant_t *js, ant_value_t val) {
uint8_t t = vtype(val);
if (t == T_STR) return val;
if (t == T_OBJ) {
ant_value_t prim = get_slot(val, SLOT_PRIMITIVE);
if (vtype(prim) == T_STR) return prim;
}
return js_call_toString(js, val);
}
bool js_truthy(ant_t *js, ant_value_t v) {
static const void *dispatch[] = {
[T_OBJ] = &&l_true,
[T_FUNC] = &&l_true,
[T_CFUNC] = &&l_true,
[T_ARR] = &&l_true,
[T_PROMISE] = &&l_true,
[T_SYMBOL] = &&l_true,
[T_BOOL] = &&l_bool,
[T_STR] = &&l_str,
[T_BIGINT] = &&l_bigint,
[T_NUM] = &&l_num,
};
uint8_t t = vtype(v);
if (t < sizeof(dispatch) / sizeof(*dispatch) && dispatch[t])
goto *dispatch[t];
return false;
l_true: return true;
l_bool: return vdata(v) != 0;
l_str: return vstrlen(js, v) > 0;
l_bigint: return !bigint_is_zero(js, v);
l_num: {
double d = tod(v);
return d != 0.0 && !isnan(d);
}
}
static size_t cpy(char *dst, size_t dstlen, const char *src, size_t srclen) {
if (dstlen == 0) return srclen;
size_t len = srclen < dstlen - 1 ? srclen : dstlen - 1;
memcpy(dst, src, len); dst[len] = '\0';
return srclen;
}
size_t uint_to_str(char *buf, size_t bufsize, uint64_t val) {
if (bufsize == 0) return 0;
if (val == 0) {
buf[0] = '0';
buf[1] = '\0';
return 1;
}
char temp[24];
size_t len = 0;
while (val > 0 && len < sizeof(temp)) {
temp[len++] = '0' + (val % 10);
val /= 10;
}
if (len >= bufsize) len = bufsize - 1;
for (size_t i = 0; i < len; i++) {
buf[i] = temp[len - 1 - i];
}
buf[len] = '\0';
return len;
}
static ant_value_t stringify_stack[MAX_STRINGIFY_DEPTH];
static int stringify_depth = 0;
static int stringify_indent = 0;
static ant_value_t multiref_objs[MAX_MULTIREF_OBJS];
static int multiref_ids[MAX_MULTIREF_OBJS];
static int multiref_count = 0;
static int multiref_next_id = 0;
static void scan_refs(ant_t *js, ant_value_t value);
static int find_multiref(ant_value_t obj) {
for (int i = 0; i < multiref_count; i++) {
if (multiref_objs[i] == obj) return multiref_ids[i];
}
return 0;
}
static bool is_on_stack(ant_value_t obj) {
for (int i = 0; i < stringify_depth; i++) {
if (stringify_stack[i] == obj) return true;
}
return false;
}
static void mark_multiref(ant_value_t obj) {
for (int i = 0; i < multiref_count; i++) {
if (multiref_objs[i] == obj) {
if (multiref_ids[i] == 0) multiref_ids[i] = ++multiref_next_id;
return;
}
}
if (multiref_count < MAX_MULTIREF_OBJS) {
multiref_objs[multiref_count] = obj;
multiref_ids[multiref_count] = 0;
multiref_count++;
}
}
static void scan_obj_refs(ant_t *js, ant_value_t obj) {
if (is_on_stack(obj)) {
mark_multiref(obj);
return;
}
if (stringify_depth >= MAX_STRINGIFY_DEPTH) return;
stringify_stack[stringify_depth++] = obj;
ant_object_t *ptr = js_obj_ptr(obj);
if (ptr && ptr->shape) {
uint32_t count = ant_shape_count(ptr->shape);
for (uint32_t i = 0; i < count && i < ptr->prop_count; i++) {
scan_refs(js, ant_object_prop_get_unchecked(ptr, i));
}
}
ant_value_t proto_val = get_proto(js, obj);
if (vtype(proto_val) == T_OBJ) scan_refs(js, proto_val);
stringify_depth--;
}
static void scan_arr_refs(ant_t *js, ant_value_t obj) {
if (is_on_stack(obj)) {
mark_multiref(obj);
return;
}
if (stringify_depth >= MAX_STRINGIFY_DEPTH) return;
stringify_stack[stringify_depth++] = obj;
ant_object_t *ptr = js_obj_ptr(obj);
if (ptr && ptr->shape) {
uint32_t count = ant_shape_count(ptr->shape);
for (uint32_t i = 0; i < count && i < ptr->prop_count; i++) {
scan_refs(js, ant_object_prop_get_unchecked(ptr, i));
}
}
stringify_depth--;
}
static void scan_func_refs(ant_t *js, ant_value_t value) {
ant_value_t func_obj = js_func_obj(value);
if (is_on_stack(func_obj)) {
mark_multiref(func_obj);
return;
}
if (stringify_depth >= MAX_STRINGIFY_DEPTH) return;
stringify_stack[stringify_depth++] = func_obj;
ant_object_t *ptr = js_obj_ptr(func_obj);
if (ptr && ptr->shape) {
uint32_t count = ant_shape_count(ptr->shape);
for (uint32_t i = 0; i < count && i < ptr->prop_count; i++) {
scan_refs(js, ant_object_prop_get_unchecked(ptr, i));
}
}
stringify_depth--;
}
static void scan_refs(ant_t *js, ant_value_t value) {
switch (vtype(value)) {
case T_OBJ: scan_obj_refs(js, value); break;
case T_ARR: scan_arr_refs(js, value); break;
case T_FUNC: scan_func_refs(js, value); break;
default: break;
}
}
static int get_circular_ref(ant_value_t obj) {
if (is_on_stack(obj)) {
int ref = find_multiref(obj);
return ref ? ref : -1;
}
return 0;
}
static bool is_circular(ant_value_t obj) {
return is_on_stack(obj);
}
static int get_self_ref(ant_value_t obj) {
return find_multiref(obj);
}
static void push_stringify(ant_value_t obj) {
if (stringify_depth < MAX_STRINGIFY_DEPTH) {
stringify_stack[stringify_depth++] = obj;
}
}
static void pop_stringify(void) {
if (stringify_depth > 0) stringify_depth--;
}
static size_t add_indent(char *buf, size_t len, int level) {
size_t wanted = (size_t)(level * 2);
size_t n = 0;
for (int i = 0; i < level * 2 && n < len; i++) {
buf[n++] = ' ';
}
return wanted;
}
const char *get_str_prop(ant_t *js, ant_value_t obj, const char *key, ant_offset_t klen, ant_offset_t *out_len) {
ant_value_t v = lkp_val(js, obj, key, klen);
if (vtype(v) != T_STR) return NULL;
return (const char *)(uintptr_t)(vstr(js, v, out_len));
}
static bool is_small_array(ant_t *js, ant_value_t obj, int *elem_count) {
ant_offset_t length = get_array_length(js, obj);
if (length > 64) { if (elem_count) *elem_count = (int)length; return false; }
int count = 0; bool has_nested = false;
for (ant_offset_t i = 0; i < length; i++) {
ant_value_t val = arr_get(js, obj, i); uint8_t t = vtype(val);
if (t == T_OBJ || t == T_ARR || t == T_FUNC) has_nested = true;
count++;
}
if (elem_count) *elem_count = count;
return count <= 4 && !has_nested;
}
static inline bool is_array_index(const char *key, ant_offset_t klen) {
if (klen == 0 || (klen > 1 && key[0] == '0')) return false;
for (ant_offset_t i = 0; i < klen; i++) {
if (key[i] < '0' || key[i] > '9') return false;
}
return true;
}
static inline bool parse_array_index(const char *key, size_t klen, ant_offset_t max_len, unsigned long *out_idx) {
if (klen == 0 || key[0] < '0' || key[0] > '9') return false;
unsigned long parsed_idx = 0;
for (size_t i = 0; i < klen; i++) {
if (key[i] < '0' || key[i] > '9') return false;
parsed_idx = parsed_idx * 10 + (key[i] - '0');
}
if (parsed_idx >= max_len) return false;
*out_idx = parsed_idx;
return true;
}
#define ANT_ARRAY_INDEX_EXCLUSIVE ((ant_offset_t)UINT32_MAX)
static inline ant_object_t *array_obj_ptr(ant_value_t obj) {
if (!is_object_type(obj)) return NULL;
ant_object_t *ptr = js_obj_ptr(obj);
return (ptr && ptr->type_tag == T_ARR) ? ptr : NULL;
}
static inline void array_define_or_set_index(ant_t *js, ant_value_t obj, const char *key, size_t klen) {
if (!key) return;
if (!array_obj_ptr(obj)) return;
unsigned long idx = 0;
if (!parse_array_index(key, klen, ANT_ARRAY_INDEX_EXCLUSIVE, &idx)) return;
ant_offset_t next_len = (ant_offset_t)idx + 1;
if (next_len > get_array_length(js, obj)) {
array_len_set(js, obj, next_len);
}
}
static ant_offset_t get_array_length(ant_t *js, ant_value_t arr) {
if (!is_object_type(arr)) return 0;
ant_object_t *arr_ptr = array_obj_ptr(arr);
if (arr_ptr) return (ant_offset_t)arr_ptr->u.array.len;
ant_value_t val = lkp_interned_val(js, arr, INTERN_LENGTH);
if (vtype(val) == T_NUM) return (ant_offset_t) tod(val);
return 0;
}
static ant_value_t get_obj_ctor(ant_t *js, ant_value_t obj) {
ant_value_t ctor = get_slot(obj, SLOT_CTOR);
if (vtype(ctor) == T_FUNC) return ctor;
ant_value_t proto = get_slot(obj, SLOT_PROTO);
if (vtype(proto) != T_OBJ) return js_mkundef();
return lkp_interned_val(js, proto, INTERN_CONSTRUCTOR);
}
static const char *get_func_name(ant_t *js, ant_value_t func, ant_offset_t *out_len) {
if (vtype(func) != T_FUNC) return NULL;
ant_value_t name = lkp_val(js, js_func_obj(func), "name", 4);
if (vtype(name) != T_STR) return NULL;
ant_offset_t str_off = vstr(js, name, out_len);
return (const char *)(uintptr_t)(str_off);
}
static const char *get_class_name(ant_t *js, ant_value_t obj, ant_offset_t *out_len, const char *skip) {
const char *name = get_func_name(js, get_obj_ctor(js, obj), out_len);
if (!name) return NULL;
if (skip && *out_len == (ant_offset_t)strlen(skip) && memcmp(name, skip, *out_len) == 0) return NULL;
return name;
}
static inline ant_offset_t dense_iterable_length(ant_t *js, ant_value_t obj) {
ant_offset_t doff = get_dense_buf(obj);
if (!doff) return 0;
ant_offset_t dense_len = dense_capacity(doff);
ant_offset_t semantic_len = get_array_length(js, obj);
return dense_len < semantic_len ? dense_len : semantic_len;
}
static size_t strarr(ant_t *js, ant_value_t obj, char *buf, size_t len) {
int ref = get_circular_ref(obj);
if (ref) return ref > 0 ? (size_t) snprintf(buf, len, "[Circular *%d]", ref) : cpy(buf, len, "[Circular]", 10);
push_stringify(obj);
ant_object_t *ptr = js_obj_ptr(js_as_obj(obj));
ant_offset_t length = get_array_length(js, obj);
ant_offset_t d_len = dense_iterable_length(js, obj);
ant_offset_t iter_len = (d_len >= length) ? length : d_len;
ant_offset_t class_len = 0;
const char *class_name = get_class_name(js, obj, &class_len, "Array");
int elem_count = 0;
bool inline_mode = is_small_array(js, obj, &elem_count);
size_t n = 0;
if (class_name) {
n += cpy(buf + n, REMAIN(n, len), class_name, class_len);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "(%u) ", (unsigned) length);
}
if (length == 0) {
n += cpy(buf + n, REMAIN(n, len), "[]", 2);
pop_stringify();
return n;
}
n += cpy(buf + n, REMAIN(n, len), inline_mode ? "[ " : "[\n", 2);
if (!inline_mode) stringify_indent++;
bool printed_first = false;
for (ant_offset_t i = 0; i < iter_len; i++) {
if (printed_first) n += cpy(buf + n, REMAIN(n, len), inline_mode ? ", " : ",\n", 2);
if (!inline_mode) n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
ant_value_t val = arr_get(js, obj, i); bool found = arr_has(js, obj, i);
n += found ? tostr(js, val, buf + n, REMAIN(n, len)) : cpy(buf + n, REMAIN(n, len), "undefined", 9);
printed_first = true;
}
if (ptr && ptr->shape) {
uint32_t shape_count = ant_shape_count(ptr->shape);
for (uint32_t i = 0; i < shape_count; i++) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, i);
if (!prop || prop->type == ANT_SHAPE_KEY_SYMBOL) continue;
const char *key = prop->key.interned;
ant_offset_t klen = (ant_offset_t)strlen(key);
if (is_length_key(key, klen)) continue;
if (printed_first) n += cpy(buf + n, REMAIN(n, len), inline_mode ? ", " : ",\n", 2);
if (!inline_mode) n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
ant_value_t val = (i < ptr->prop_count) ? ant_object_prop_get_unchecked(ptr, i) : js_mkundef();
if (is_array_index(key, klen)) {
n += tostr(js, val, buf + n, REMAIN(n, len));
} else {
n += cpy(buf + n, REMAIN(n, len), key, klen);
n += cpy(buf + n, REMAIN(n, len), ": ", 2);
n += tostr(js, val, buf + n, REMAIN(n, len));
}
printed_first = true;
}
}
if (!inline_mode) {
stringify_indent--;
n += cpy(buf + n, REMAIN(n, len), "\n", 1);
n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
}
n += cpy(buf + n, REMAIN(n, len), inline_mode ? " ]" : "]", inline_mode ? 2 : 1);
pop_stringify();
return n;
}
static size_t strdate(ant_t *js, ant_value_t obj, char *buf, size_t len) {
ant_value_t time_val = js_get_slot(obj, SLOT_DATA);
if (vtype(time_val) != T_NUM) return cpy(buf, len, "Invalid Date", 12);
static const date_string_spec_t kSpec = {DATE_STRING_FMT_ISO, DATE_STRING_PART_ALL};
ant_value_t iso = get_date_string(js, obj, kSpec);
if (is_err(iso) || vtype(iso) != T_STR) return cpy(buf, len, "Invalid Date", 12);
ant_offset_t slen;
ant_offset_t soff = vstr(js, iso, &slen);
return cpy(buf, len, (const char *)(uintptr_t)(soff), slen);
}
static bool is_valid_identifier(const char *str, ant_offset_t slen) {
if (slen == 0) return false;
char c = str[0];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$')) return false;
for (ant_offset_t i = 1; i < slen; i++) {
c = str[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$')) return false;
}
return true;
}
static size_t strkey(ant_t *js, ant_value_t value, char *buf, size_t len) {
ant_offset_t slen, off = vstr(js, value, &slen);
const char *str = (const char *)(uintptr_t)(off);
if (is_valid_identifier(str, slen)) {
return cpy(buf, len, str, slen);
}
return strstring(js, value, buf, len);
}
static size_t strkey_interned(ant_t *js, const char *key, size_t klen, char *buf, size_t len) {
if (is_valid_identifier(key, (ant_offset_t)klen)) {
return cpy(buf, len, key, klen);
}
ant_value_t key_str = js_mkstr(js, key, klen);
return strstring(js, key_str, buf, len);
}
static bool is_small_object(ant_t *js, ant_value_t obj, int *prop_count) {
int count = 0;
bool has_nested = false;
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
uintptr_t obj_off = (uintptr_t)vdata(as_obj);
if (ptr && ptr->shape) {
uint32_t shape_count = ant_shape_count(ptr->shape);
for (uint32_t i = 0; i < shape_count; i++) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, i);
if (!prop) continue;
if (prop->type == ANT_SHAPE_KEY_SYMBOL) {
count++;
continue;
}
if ((ant_shape_get_attrs(ptr->shape, i) & ANT_PROP_ATTR_ENUMERABLE) == 0) continue;
ant_value_t val = (i < ptr->prop_count) ? ant_object_prop_get_unchecked(ptr, i) : js_mkundef();
uint8_t t = vtype(val);
if (t == T_OBJ || t == T_ARR || t == T_FUNC) has_nested = true;
count++;
}
}
if (ptr && ptr->is_exotic) {
descriptor_entry_t *desc, *tmp;
HASH_ITER(hh, desc_registry, desc, tmp) {
if (desc->obj_off != obj_off) continue;
if (!desc->enumerable) continue;
if (!desc->has_getter && !desc->has_setter) continue;
count++;
}
}
if (prop_count) *prop_count = count;
return count <= 4 && !has_nested;
}
// todo: split into smaller functions
static size_t strobj(ant_t *js, ant_value_t obj, char *buf, size_t len) {
ant_value_t obj_proto = js_get_proto(js, obj);
ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4);
if (obj_proto == date_proto) return strdate(js, obj, buf, len);
int ref = get_circular_ref(obj);
if (ref) return ref > 0 ? (size_t) snprintf(buf, len, "[Circular *%d]", ref) : cpy(buf, len, "[Circular]", 10);
push_stringify(obj);
size_t n = 0;
int self_ref = get_self_ref(obj);
if (self_ref) {
n += (size_t) snprintf(buf + n, REMAIN(n, len), "<ref *%d> ", self_ref);
}
ant_value_t tag_sym = get_toStringTag_sym();
ant_value_t tag_val = (vtype(tag_sym) == T_SYMBOL) ? lkp_sym_proto_val(js, obj, (ant_offset_t)vdata(tag_sym)) : js_mkundef();
bool is_map = false, is_set = false, is_arraybuffer = false;
ant_offset_t tlen = 0, toff = 0;
const char *tag_str = NULL;
int prop_count = 0;
bool inline_mode = false;
if (vtype(tag_val) != T_STR) goto print_plain_object;
toff = vstr(js, tag_val, &tlen);
tag_str = (const char *)(uintptr_t)(toff);
is_map = (tlen == 3 && memcmp(tag_str, "Map", 3) == 0);
is_set = (tlen == 3 && memcmp(tag_str, "Set", 3) == 0);
is_arraybuffer = (tlen >= 11 && memcmp(tag_str + tlen - 11, "ArrayBuffer", 11) == 0);
ant_value_t ta_slot = js_get_slot(obj, SLOT_BUFFER);
if (vtype(ta_slot) == T_TYPEDARRAY) {
TypedArrayData *ta = (TypedArrayData *)vdata(ta_slot);
if (ta && ta->buffer) {
static const char *ta_type_names[] = {
"Int8Array", "Uint8Array", "Uint8ClampedArray",
"Int16Array", "Uint16Array", "Int32Array", "Uint32Array",
"Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array"
};
const char *type_name = NULL;
size_t type_len = 0;
ant_value_t proto = js_get_proto(js, obj);
ant_value_t buffer_proto = get_ctor_proto(js, "Buffer", 6);
if (vtype(proto) == T_OBJ && vtype(buffer_proto) == T_OBJ && vdata(proto) == vdata(buffer_proto)) {
type_name = "Buffer";
type_len = 6;
} else if (ta->type <= TYPED_ARRAY_BIGUINT64) {
type_name = ta_type_names[ta->type];
type_len = strlen(type_name);
} else {
type_name = "TypedArray";
type_len = 10;
}
n += cpy(buf + n, REMAIN(n, len), type_name, type_len);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "(%zu) ", ta->length);
n += cpy(buf + n, REMAIN(n, len), "[ ", 2);
uint8_t *data = ta->buffer->data + ta->byte_offset;
for (size_t i = 0; i < ta->length && i < 100; i++) {
if (i > 0) n += cpy(buf + n, REMAIN(n, len), ", ", 2);
switch (ta->type) {
case TYPED_ARRAY_INT8:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%d", (int)((int8_t*)data)[i]);
break;
case TYPED_ARRAY_UINT8:
case TYPED_ARRAY_UINT8_CLAMPED:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%u", (unsigned)data[i]);
break;
case TYPED_ARRAY_INT16:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%d", (int)((int16_t*)data)[i]);
break;
case TYPED_ARRAY_UINT16:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%u", (unsigned)((uint16_t*)data)[i]);
break;
case TYPED_ARRAY_INT32:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%d", ((int32_t*)data)[i]);
break;
case TYPED_ARRAY_UINT32:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%u", ((uint32_t*)data)[i]);
break;
case TYPED_ARRAY_FLOAT32:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%g", (double)((float*)data)[i]);
break;
case TYPED_ARRAY_FLOAT64:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%g", ((double*)data)[i]);
break;
case TYPED_ARRAY_BIGINT64:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%lldn", (long long)((int64_t*)data)[i]);
break;
case TYPED_ARRAY_BIGUINT64:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%llun", (unsigned long long)((uint64_t*)data)[i]);
break;
default:
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%u", (unsigned)data[i]);
break;
}
}
if (ta->length > 100) n += cpy(buf + n, REMAIN(n, len), ", ...", 5);
n += cpy(buf + n, REMAIN(n, len), " ]", 2);
pop_stringify();
return n;
}
}
if (is_arraybuffer) {
ant_value_t buf_val = js_get_slot(obj, SLOT_BUFFER);
if (vtype(buf_val) == T_NUM) {
ArrayBufferData *ab_data = (ArrayBufferData *)(uintptr_t)tod(buf_val);
size_t bytelen = ab_data ? ab_data->length : 0;
n += cpy(buf + n, REMAIN(n, len), tag_str, tlen);
n += cpy(buf + n, REMAIN(n, len), " {\n", 3);
n += cpy(buf + n, REMAIN(n, len), " [Uint8Contents]: <", 20);
if (ab_data && ab_data->data && bytelen > 0) {
for (size_t i = 0; i < bytelen; i++) {
if (i > 0) n += cpy(buf + n, REMAIN(n, len), " ", 1);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%02x", ab_data->data[i]);
}
}
n += cpy(buf + n, REMAIN(n, len), ">,\n", 3);
n += cpy(buf + n, REMAIN(n, len), " [byteLength]: ", 16);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%zu", bytelen);
n += cpy(buf + n, REMAIN(n, len), "\n}", 2);
pop_stringify();
return n;
}
}
bool is_dataview = (tlen == 8 && memcmp(tag_str, "DataView", 8) == 0);
if (is_dataview) {
ant_value_t dv_data_val = js_get_slot(obj, SLOT_DATA);
if (vtype(dv_data_val) == T_NUM) {
DataViewData *dv = (DataViewData *)(uintptr_t)tod(dv_data_val);
if (dv && dv->buffer) {
n += cpy(buf + n, REMAIN(n, len), "DataView {\n", 11);
n += cpy(buf + n, REMAIN(n, len), " [byteLength]: ", 16);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%zu", dv->byte_length);
n += cpy(buf + n, REMAIN(n, len), ",\n", 2);
n += cpy(buf + n, REMAIN(n, len), " [byteOffset]: ", 16);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%zu", dv->byte_offset);
n += cpy(buf + n, REMAIN(n, len), ",\n", 2);
n += cpy(buf + n, REMAIN(n, len), " [buffer]: ArrayBuffer {\n", 26);
n += cpy(buf + n, REMAIN(n, len), " [Uint8Contents]: <", 22);
if (dv->buffer->data && dv->buffer->length > 0) {
for (size_t i = 0; i < dv->buffer->length; i++) {
if (i > 0) n += cpy(buf + n, REMAIN(n, len), " ", 1);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%02x", dv->buffer->data[i]);
}
}
n += cpy(buf + n, REMAIN(n, len), ">,\n", 3);
n += cpy(buf + n, REMAIN(n, len), " [byteLength]: ", 18);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%zu", dv->buffer->length);
n += cpy(buf + n, REMAIN(n, len), "\n }\n}", 6);
pop_stringify();
return n;
}
}
}
if (is_map) {
ant_value_t map_val = js_get_slot(obj, SLOT_MAP);
if (vtype(map_val) == T_UNDEF) goto print_tagged_object;
map_entry_t **map_ptr = (map_entry_t**)(size_t)tod(map_val);
n += cpy(buf + n, REMAIN(n, len), "Map(", 4);
unsigned int count = 0;
if (map_ptr && *map_ptr) count = HASH_COUNT(*map_ptr);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%u", count);
n += cpy(buf + n, REMAIN(n, len), ") ", 2);
if (count == 0) {
n += cpy(buf + n, REMAIN(n, len), "{}", 2);
} else {
n += cpy(buf + n, REMAIN(n, len), "{\n", 2);
stringify_indent++;
bool first = true;
if (map_ptr && *map_ptr) {
map_entry_t *entry, *tmp;
HASH_ITER(hh, *map_ptr, entry, tmp) {
if (!first) n += cpy(buf + n, REMAIN(n, len), ",\n", 2);
first = false;
n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
n += tostr(js, entry->key_val, buf + n, REMAIN(n, len));
n += cpy(buf + n, REMAIN(n, len), " => ", 4);
n += tostr(js, entry->value, buf + n, REMAIN(n, len));
}
}
stringify_indent--;
n += cpy(buf + n, REMAIN(n, len), "\n", 1);
n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
n += cpy(buf + n, REMAIN(n, len), "}", 1);
}
pop_stringify();
return n;
}
if (is_set) {
ant_value_t set_val = js_get_slot(obj, SLOT_SET);
if (vtype(set_val) == T_UNDEF) goto print_tagged_object;
set_entry_t **set_ptr = (set_entry_t**)(size_t)tod(set_val);
n += cpy(buf + n, REMAIN(n, len), "Set(", 4);
unsigned int count = 0;
if (set_ptr && *set_ptr) count = HASH_COUNT(*set_ptr);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%u", count);
n += cpy(buf + n, REMAIN(n, len), ") ", 2);
if (count == 0) {
n += cpy(buf + n, REMAIN(n, len), "{}", 2);
} else {
n += cpy(buf + n, REMAIN(n, len), "{\n", 2);
stringify_indent++;
bool first = true;
if (set_ptr && *set_ptr) {
set_entry_t *entry, *tmp;
HASH_ITER(hh, *set_ptr, entry, tmp) {
if (!first) n += cpy(buf + n, REMAIN(n, len), ",\n", 2);
first = false;
n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
n += tostr(js, entry->value, buf + n, REMAIN(n, len));
}
}
stringify_indent--;
n += cpy(buf + n, REMAIN(n, len), "\n", 1);
n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
n += cpy(buf + n, REMAIN(n, len), "}", 1);
}
pop_stringify();
return n;
}
if (tag_str) {
bool is_blob = (tlen == 4 && memcmp(tag_str, "Blob", 4) == 0);
bool is_file = (tlen == 4 && memcmp(tag_str, "File", 4) == 0);
if (is_blob || is_file) {
blob_data_t *bd = blob_get_data(obj);
n += cpy(buf + n, REMAIN(n, len), is_file ? "File" : "Blob", 4);
n += cpy(buf + n, REMAIN(n, len), " { size: ", 9);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%zu", bd ? bd->size : 0);
n += cpy(buf + n, REMAIN(n, len), ", type: '", 9);
if (bd && bd->type) n += cpy(buf + n, REMAIN(n, len), bd->type, strlen(bd->type));
n += cpy(buf + n, REMAIN(n, len), "'", 1);
if (is_file) {
n += cpy(buf + n, REMAIN(n, len), ", name: '", 9);
if (bd && bd->name) n += cpy(buf + n, REMAIN(n, len), bd->name, strlen(bd->name));
n += cpy(buf + n, REMAIN(n, len), "'", 1);
n += cpy(buf + n, REMAIN(n, len), ", lastModified: ", 16);
n += (size_t) snprintf(buf + n, REMAIN(n, len), "%" PRId64, bd ? bd->last_modified : 0);
}
n += cpy(buf + n, REMAIN(n, len), " }", 2);
pop_stringify();
return n;
}}
print_tagged_object:
n += cpy(buf + n, REMAIN(n, len), "Object [", 8);
n += cpy(buf + n, REMAIN(n, len), (const char *)(uintptr_t)(toff), tlen);
n += cpy(buf + n, REMAIN(n, len), "] {\n", 4);
goto continue_object_print;
print_plain_object:
inline_mode = is_small_object(js, obj, &prop_count);
ant_value_t proto_val = js_get_proto(js, obj);
bool is_null_proto = (vtype(proto_val) == T_NULL);
bool proto_is_null_proto = false;
const char *class_name = NULL;
ant_offset_t class_name_len = 0;
do {
if (is_null_proto) break;
uint8_t pt = vtype(proto_val);
if (pt != T_OBJ && pt != T_FUNC) break;
ant_value_t proto_proto = js_get_proto(js, proto_val);
ant_value_t object_proto = get_ctor_proto(js, "Object", 6);
proto_is_null_proto = (vtype(proto_proto) == T_NULL) &&
(vdata(proto_val) != vdata(object_proto));
class_name = get_class_name(js, obj, &class_name_len, "Object");
} while (0);
if (prop_count == 0) {
if (is_null_proto) {
n += cpy(buf + n, REMAIN(n, len), "[Object: null prototype] {}", 27);
} else if (class_name && class_name_len > 0) {
n += cpy(buf + n, REMAIN(n, len), class_name, class_name_len);
if (proto_is_null_proto) {
n += cpy(buf + n, REMAIN(n, len), " <[Object: null prototype] {}> {}", 33);
} else n += cpy(buf + n, REMAIN(n, len), " {}", 3);
} else if (proto_is_null_proto) {
n += cpy(buf + n, REMAIN(n, len), "<[Object: null prototype] {}> {}", 32);
} else n += cpy(buf + n, REMAIN(n, len), "{}", 2);
pop_stringify();
return n;
}
if (is_null_proto) {
n += cpy(buf + n, REMAIN(n, len), "[Object: null prototype] ", 25);
} else if (class_name && class_name_len > 0) {
n += cpy(buf + n, REMAIN(n, len), class_name, class_name_len);
if (proto_is_null_proto) {
n += cpy(buf + n, REMAIN(n, len), " <[Object: null prototype] {}> ", 31);
} else n += cpy(buf + n, REMAIN(n, len), " ", 1);
} else if (proto_is_null_proto) {
n += cpy(buf + n, REMAIN(n, len), "<[Object: null prototype] {}> ", 30);
}
n += cpy(buf + n, REMAIN(n, len), inline_mode ? "{ " : "{\n", 2);
continue_object_print:;
if (!inline_mode) stringify_indent++;
bool first = true;
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
uintptr_t obj_off = (uintptr_t)vdata(as_obj);
uint32_t shape_count = (ptr && ptr->shape) ? ant_shape_count(ptr->shape) : 0;
for (uint32_t i = 0; i < shape_count; i++) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, i);
if (!prop) continue;
if ((ant_shape_get_attrs(ptr->shape, i) & ANT_PROP_ATTR_ENUMERABLE) == 0) continue;
ant_value_t val = (i < ptr->prop_count) ? ant_object_prop_get_unchecked(ptr, i) : js_mkundef();
if (prop->type == ANT_SHAPE_KEY_SYMBOL) {
ant_offset_t sym_off = prop->key.sym_off;
if (vtype(tag_sym) == T_SYMBOL && sym_off == (ant_offset_t)vdata(tag_sym)) continue;
if (ptr && ptr->is_exotic) {
prop_meta_t meta;
if (lookup_symbol_prop_meta(as_obj, sym_off, &meta) && !meta.enumerable) continue;
}
ant_value_t sym = mkval(T_SYMBOL, sym_off);
if (!first) n += cpy(buf + n, REMAIN(n, len), inline_mode ? ", " : ",\n", 2);
first = false;
if (!inline_mode) n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
n += cpy(buf + n, REMAIN(n, len), "[", 1);
n += tostr(js, sym, buf + n, REMAIN(n, len));
n += cpy(buf + n, REMAIN(n, len), "]: ", 3);
n += tostr(js, val, buf + n, REMAIN(n, len));
continue;
}
const char *key = prop->key.interned;
ant_offset_t klen = (ant_offset_t)strlen(key);
if (ptr && ptr->is_exotic) {
prop_meta_t meta;
if (lookup_string_prop_meta(js, as_obj, key, (size_t)klen, &meta) && !meta.enumerable) continue;
}
if (!first) n += cpy(buf + n, REMAIN(n, len), inline_mode ? ", " : ",\n", 2);
first = false;
if (!inline_mode) n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
bool is_special_global = false;
if (vtype(val) == T_UNDEF && streq(key, klen, "undefined", 9)) {
is_special_global = true;
} else if (vtype(val) == T_NUM) {
double d = tod(val);
if (isinf(d) && d > 0 && streq(key, klen, "Infinity", 8)) {
is_special_global = true;
} else if (isnan(d) && streq(key, klen, "NaN", 3)) {
is_special_global = true;
}
}
if (is_special_global) {
n += tostr(js, val, buf + n, REMAIN(n, len));
} else {
n += strkey_interned(js, key, (size_t)klen, buf + n, REMAIN(n, len));
n += cpy(buf + n, REMAIN(n, len), ": ", 2);
n += tostr(js, val, buf + n, REMAIN(n, len));
}
}
if (ptr && ptr->is_exotic) {
descriptor_entry_t *desc, *tmp;
HASH_ITER(hh, desc_registry, desc, tmp) {
if (desc->obj_off != obj_off) continue;
if (!desc->enumerable) continue;
if (!desc->has_getter && !desc->has_setter) continue;
if (!first) n += cpy(buf + n, REMAIN(n, len), inline_mode ? ", " : ",\n", 2);
first = false;
if (!inline_mode) n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
n += cpy(buf + n, REMAIN(n, len), desc->prop_name, desc->prop_len);
n += cpy(buf + n, REMAIN(n, len), ": ", 2);
if (desc->has_getter && desc->has_setter) {
n += cpy(buf + n, REMAIN(n, len), "[Getter/Setter]", 15);
} else if (desc->has_getter) {
n += cpy(buf + n, REMAIN(n, len), "[Getter]", 8);
} else n += cpy(buf + n, REMAIN(n, len), "[Setter]", 8);
}
}
if (!inline_mode) stringify_indent--;
if (inline_mode) {
n += cpy(buf + n, REMAIN(n, len), " }", 2);
} else {
if (!first) n += cpy(buf + n, REMAIN(n, len), "\n", 1);
n += add_indent(buf + n, REMAIN(n, len), stringify_indent);
n += cpy(buf + n, REMAIN(n, len), "}", 1);
}
pop_stringify();
return n;
}
static size_t fix_exponent(char *buf, size_t n) {
char *e = strchr(buf, 'e');
if (!e) return n;
char *src = e + 1;
char *dst = src;
if (*src == '+' || *src == '-') {
dst++;
src++;
}
while (*src == '0' && src[1] != '\0') src++;
if (src != dst) {
memmove(dst, src, strlen(src) + 1);
return strlen(buf);
}
return n;
}
static size_t strnum(ant_value_t value, char *buf, size_t len) {
double dv = tod(value);
if (isnan(dv)) return cpy(buf, len, "NaN", 3);
if (isinf(dv)) return cpy(buf, len, dv > 0 ? "Infinity" : "-Infinity", dv > 0 ? 8 : 9);
if (dv == 0.0) return cpy(buf, len, "0", 1);
char temp[64];
int sign = dv < 0 ? 1 : 0;
double adv = sign ? -dv : dv;
double iv;
double frac = modf(adv, &iv);
if (frac == 0.0 && adv < 9007199254740992.0) {
int result = snprintf(temp, sizeof(temp), "%.0f", dv);
fix_exponent(temp, (size_t)result);
return cpy(buf, len, temp, strlen(temp));
}
for (int prec = 1; prec <= 17; prec++) {
int n = snprintf(temp, sizeof(temp), "%.*g", prec, dv);
double parsed = strtod(temp, NULL);
if (parsed == dv) {
fix_exponent(temp, (size_t)n);
return cpy(buf, len, temp, strlen(temp));
}
(void)n;
}
int result = snprintf(temp, sizeof(temp), "%.17g", dv);
fix_exponent(temp, (size_t)result);
return cpy(buf, len, temp, strlen(temp));
}
static inline ant_offset_t assert_flat_string_len(ant_t *js, ant_value_t value, const char **out_ptr) {
(void)js;
ant_flat_string_t *flat = str_flat_ptr(value);
assert(flat != NULL);
ant_offset_t len = flat->len;
if (out_ptr) *out_ptr = flat->bytes;
return len;
}
static inline ant_rope_heap_t *assert_rope_ptr(ant_value_t value) {
assert(vtype(value) == T_STR);
assert(str_is_heap_rope(value));
ant_rope_heap_t *ptr = str_rope_ptr(value);
assert(ptr != NULL);
return ptr;
}
static inline ant_offset_t rope_len(ant_value_t value) {
ant_rope_heap_t *ptr = assert_rope_ptr(value);
return ptr->len;
}
static inline uint8_t rope_depth(ant_value_t value) {
ant_rope_heap_t *ptr = assert_rope_ptr(value);
return ptr->depth;
}
static inline ant_value_t rope_left(ant_value_t value) {
ant_rope_heap_t *ptr = assert_rope_ptr(value);
return ptr->left;
}
static inline ant_value_t rope_right(ant_value_t value) {
ant_rope_heap_t *ptr = assert_rope_ptr(value);
return ptr->right;
}
static inline ant_value_t rope_cached_flat(ant_value_t value) {
ant_rope_heap_t *ptr = assert_rope_ptr(value);
return ptr->cached;
}
static inline void rope_set_cached_flat(ant_value_t rope, ant_value_t flat) {
ant_rope_heap_t *ptr = assert_rope_ptr(rope);
ptr->cached = flat;
}
static void rope_flatten_into(ant_t *js, ant_value_t str, char *dest, ant_offset_t *pos) {
assert(vtype(str) == T_STR);
if (!str_is_heap_rope(str)) {
const char *sptr;
ant_offset_t slen = assert_flat_string_len(js, str, &sptr);
memcpy(dest + *pos, sptr, slen);
*pos += slen; return;
}
ant_value_t cached = rope_cached_flat(str);
if (vtype(cached) == T_STR && !str_is_heap_rope(cached)) {
const char *cptr;
ant_offset_t clen = assert_flat_string_len(js, cached, &cptr);
memcpy(dest + *pos, cptr, clen);
*pos += clen; return;
}
ant_value_t stack[ROPE_MAX_DEPTH + 8];
int sp = 0; stack[sp++] = str;
while (sp > 0) {
ant_value_t node = stack[--sp];
assert(vtype(node) == T_STR);
if (!str_is_heap_rope(node)) {
const char *sptr;
ant_offset_t slen = assert_flat_string_len(js, node, &sptr);
memcpy(dest + *pos, sptr, slen);
*pos += slen; continue;
}
ant_value_t c = rope_cached_flat(node);
if (vtype(c) == T_STR && !str_is_heap_rope(c)) {
const char *cptr;
ant_offset_t clen = assert_flat_string_len(js, c, &cptr);
memcpy(dest + *pos, cptr, clen);
*pos += clen; continue;
}
if (sp + 2 <= ROPE_MAX_DEPTH + 8) {
stack[sp++] = rope_right(node);
stack[sp++] = rope_left(node);
}
}
}
ant_value_t rope_flatten(ant_t *js, ant_value_t rope) {
assert(vtype(rope) == T_STR);
if (!str_is_heap_rope(rope)) return rope;
ant_value_t cached = rope_cached_flat(rope);
if (vtype(cached) == T_STR && !str_is_heap_rope(cached)) return cached;
ant_offset_t total_len = rope_len(rope);
char *buf = (char *)ant_calloc(total_len + 1);
if (!buf) return js_mkerr(js, "oom");
ant_offset_t pos = 0;
rope_flatten_into(js, rope, buf, &pos);
buf[pos] = '\0';
ant_value_t flat = js_mkstr(js, buf, pos);
free(buf);
if (!is_err(flat)) {
rope_set_cached_flat(rope, flat);
}
return flat;
}
ant_offset_t vstr(ant_t *js, ant_value_t value, ant_offset_t *len) {
if (str_is_heap_rope(value)) {
ant_value_t flat = rope_flatten(js, value);
assert(!is_err(flat));
value = flat;
}
const char *ptr = NULL;
ant_offset_t slen = assert_flat_string_len(js, value, &ptr);
if (len) *len = slen;
return (ant_offset_t)(uintptr_t)ptr;
}
static size_t strstring(ant_t *js, ant_value_t value, char *buf, size_t len) {
ant_offset_t slen, off = vstr(js, value, &slen);
const char *str = (const char *)(uintptr_t)off;
size_t n = 0;
n += cpy(buf + n, REMAIN(n, len), "'", 1);
for (ant_offset_t i = 0; i < slen && n < len - 1; i++) {
char c = str[i];
if (c == '\n') { n += cpy(buf + n, REMAIN(n, len), "\\n", 2); }
else if (c == '\r') { n += cpy(buf + n, REMAIN(n, len), "\\r", 2); }
else if (c == '\t') { n += cpy(buf + n, REMAIN(n, len), "\\t", 2); }
else if (c == '\\') { n += cpy(buf + n, REMAIN(n, len), "\\\\", 2); }
else if (c == '\'') { n += cpy(buf + n, REMAIN(n, len), "\\'", 2); }
else { if (n < len) buf[n++] = c; }
}
n += cpy(buf + n, REMAIN(n, len), "'", 1);
return n;
}
const char *intern_string(const char *str, size_t len) {
if (!intern_table_init()) return NULL;
if ((intern_count + 1) * INTERN_LOAD_DEN >= intern_bucket_count * INTERN_LOAD_NUM) {
size_t next_bucket_count = intern_bucket_count << 1;
if (next_bucket_count > intern_bucket_count) intern_table_rehash(next_bucket_count);
}
uint64_t h = hash_key(str, len);
size_t bucket = (size_t)(h & (intern_bucket_count - 1));
for (interned_string_t *e = intern_buckets[bucket]; e; e = e->next) {
if (e->hash == h && e->len == len && memcmp(e->str, str, len) == 0) return e->str;
}
size_t alloc_size = sizeof(interned_string_t) + len + 1;
interned_string_t *entry = (interned_string_t *)ant_calloc(alloc_size);
if (!entry) return NULL;
entry->str = (char *)(entry + 1);
memcpy(entry->str, str, len);
entry->str[len] = '\0';
entry->len = len;
entry->hash = h;
entry->next = intern_buckets[bucket];
intern_buckets[bucket] = entry;
intern_count++;
intern_bytes += alloc_size;
return entry->str;
}
js_intern_stats_t js_intern_stats(void) {
return (js_intern_stats_t){
.count = intern_count,
.bytes = intern_bytes
};
}
bool is_internal_prop(const char *key, ant_offset_t klen) {
if (klen < 2) return false;
if (key[0] != '_' || key[1] != '_') return false;
if (klen == STR_PROTO_LEN && memcmp(key, STR_PROTO, STR_PROTO_LEN) == 0) return false;
return true;
}
struct func_format {
const char *prefix;
size_t prefix_len;
const char *anon;
size_t anon_len;
};
static const struct func_format formats[] = {
[0] = { "[Function: ", 11, "[Function (anonymous)]", 22 },
[1] = { "[AsyncFunction: ", 16, "[AsyncFunction (anonymous)]", 27 },
};
// todo: make it work with bytecode NAME
static size_t strfunc(ant_t *js, ant_value_t value, char *buf, size_t len) {
ant_offset_t name_len = 0;
const char *name = get_func_name(js, value, &name_len);
ant_value_t func_obj = js_func_obj(value);
ant_value_t code_slot = get_slot(func_obj, SLOT_CODE);
ant_value_t builtin_slot = get_slot(func_obj, SLOT_BUILTIN);
ant_value_t async_slot = get_slot(func_obj, SLOT_ASYNC);
bool is_async = (async_slot == js_true);
bool has_code = (vtype(code_slot) == T_CFUNC);
const struct func_format *fmt = &formats[is_async];
if (vtype(builtin_slot) == T_NUM) {
if (name && name_len > 0) {
size_t n = cpy(buf, len, fmt->prefix, fmt->prefix_len);
n += cpy(buf + n, REMAIN(n, len), name, name_len);
n += cpy(buf + n, REMAIN(n, len), "]", 1);
return n;
}
return cpy(buf, len, fmt->anon, fmt->anon_len);
}
if (!has_code) {
ant_value_t cfunc_slot = get_slot(func_obj, SLOT_CFUNC);
bool is_native = (vtype(cfunc_slot) == T_CFUNC);
size_t n;
if (name && name_len > 0) {
n = cpy(buf, len, fmt->prefix, fmt->prefix_len);
n += cpy(buf + n, REMAIN(n, len), name, name_len);
n += cpy(buf + n, REMAIN(n, len), "]", 1);
} else {
n = cpy(buf, len, fmt->anon, fmt->anon_len);
}
if (!is_native) return n;
ant_value_t proto = get_slot(func_obj, SLOT_PROTO);
uint8_t pt = vtype(proto);
if (pt != T_OBJ && pt != T_FUNC) return n;
ant_value_t ctor = lkp_val(js, proto, "constructor", 11);
uint8_t ct = vtype(ctor);
if (ct != T_FUNC && ct != T_CFUNC) return n;
ant_offset_t ctor_name_len = 0;
const char *ctor_name = get_func_name(js, ctor, &ctor_name_len);
if (ctor_name && ctor_name_len > 0) {
n += cpy(buf + n, REMAIN(n, len), " ", 1);
n += cpy(buf + n, REMAIN(n, len), ctor_name, ctor_name_len);
}
return n;
}
if (name && name_len > 0) {
size_t n = cpy(buf, len, fmt->prefix, fmt->prefix_len);
n += cpy(buf + n, REMAIN(n, len), name, name_len);
n += cpy(buf + n, REMAIN(n, len), "]", 1);
return n;
}
return cpy(buf, len, fmt->anon, fmt->anon_len);
}
static size_t tostr(ant_t *js, ant_value_t value, char *buf, size_t len) {
switch (vtype(value)) {
case T_UNDEF: return ANT_COPY(buf, len, "undefined");
case T_NULL: return ANT_COPY(buf, len, "null");
case T_BOOL: {
bool b = vdata(value) & 1;
return b ? ANT_COPY(buf, len, "true") : ANT_COPY(buf, len, "false");
}
case T_ARR: return strarr(js, value, buf, len);
case T_OBJ: return strobj(js, value, buf, len);
case T_STR: return strstring(js, value, buf, len);
case T_NUM: return strnum(value, buf, len);
case T_BIGINT: return strbigint(js, value, buf, len);
case T_PROMISE: return strpromise(js, value, buf, len);
case T_FUNC: return strfunc(js, value, buf, len);
case T_CFUNC: return ANT_COPY(buf, len, "[native code]");
case T_FFI: return ANT_COPY(buf, len, "[native code (ffi)]");
case T_ERR: {
uint64_t data = vdata(value);
if (data != 0) {
ant_value_t obj = mkval(T_OBJ, data);
ant_value_t stack = js_get(js, obj, "stack");
if (vtype(stack) == T_STR) {
ant_offset_t slen;
ant_offset_t off = vstr(js, stack, &slen);
return cpy(buf, len, (const char *)(uintptr_t)(off), slen);
}
}
return ANT_COPY(buf, len, "Error");
}
case T_SYMBOL: {
const char *desc = js_sym_desc(js, value);
if (desc) return (size_t) snprintf(buf, len, "Symbol(%s)", desc);
return ANT_COPY(buf, len, "Symbol()");
}
default: return (size_t) snprintf(buf, len, "VTYPE%d", vtype(value));
}
}
static char *tostr_alloc(ant_t *js, ant_value_t value) {
size_t cap = 64;
char *buf = ant_calloc(cap);
size_t n = tostr(js, value, buf, cap);
if (n >= cap) {
free(buf);
buf = ant_calloc(n + 1);
tostr(js, value, buf, n + 1);
}
return buf;
}
js_cstr_t js_to_cstr(ant_t *js, ant_value_t value, char *stack_buf, size_t stack_size) {
js_cstr_t out = { .ptr = "", .len = 0, .needs_free = false };
if (is_err(value)) {
uint64_t data = vdata(value);
if (data != 0) {
ant_value_t obj = mkval(T_OBJ, data);
ant_value_t stack = js_get(js, obj, "stack");
if (vtype(stack) == T_STR) {
ant_offset_t slen;
ant_offset_t off = vstr(js, stack, &slen);
out.ptr = (const char *)(uintptr_t)(off);
out.len = slen;
return out;
}
}
out.ptr = "Error";
out.len = 5;
return out;
}
if (vtype(value) == T_STR) {
size_t len = 0;
char *str = js_getstr(js, value, &len);
out.ptr = str ? str : ""; out.len = len;
return out;
}
multiref_count = 0;
multiref_next_id = 0;
stringify_depth = 0;
scan_refs(js, value);
size_t capacity = stack_size;
char *buf = stack_buf;
out.needs_free = false;
if (!buf || capacity == 0) {
capacity = 64;
buf = ant_calloc(capacity);
if (!buf) return out;
out.needs_free = true;
}
for (;;) {
stringify_depth = 0;
stringify_indent = 0;
size_t len = tostr(js, value, buf, capacity);
if (len < capacity - 1) {
out.ptr = buf;
out.len = len;
return out;
}
size_t new_capacity = capacity * 2;
char *next = out.needs_free
? ant_realloc(buf, new_capacity)
: ant_calloc(new_capacity);
if (!next) {
if (out.needs_free) free(buf);
out.ptr = ""; out.len = 0;
out.needs_free = false;
return out;
}
if (!out.needs_free) {
memcpy(next, buf, capacity);
out.needs_free = true;
}
buf = next;
capacity = new_capacity;
}
}
ant_value_t js_tostring_val(ant_t *js, ant_value_t value) {
uint8_t t = vtype(value);
char *buf; size_t len, buflen;
static const void *jump_table[] = {
[T_OBJ] = &&L_OBJ, [T_STR] = &&L_STR,
[T_UNDEF] = &&L_UNDEF, [T_NULL] = &&L_NULL, [T_NUM] = &&L_NUM,
[T_BOOL] = &&L_BOOL, [T_FUNC] = &&L_OBJ, [T_CFUNC] = &&L_DEFAULT,
[T_ERR] = &&L_DEFAULT, [T_ARR] = &&L_OBJ,
[T_PROMISE] = &&L_DEFAULT, [T_TYPEDARRAY] = &&L_DEFAULT,
[T_BIGINT] = &&L_BIGINT, [T_SYMBOL] = &&L_DEFAULT,
[T_GENERATOR] = &&L_DEFAULT, [T_FFI] = &&L_DEFAULT
};
if (t < sizeof(jump_table) / sizeof(jump_table[0])) goto *jump_table[t];
goto L_DEFAULT;
L_STR: return value;
L_UNDEF: return js_mkstr(js, "undefined", 9);
L_NULL: return js_mkstr(js, "null", 4);
L_BOOL: return vdata(value) ? js_mkstr(js, "true", 4) : js_mkstr(js, "false", 5);
L_OBJ: return js_call_toString(js, value);
L_NUM: {
buf = (char *)ant_calloc(32);
len = strnum(value, buf, 32);
ant_value_t result = js_mkstr(js, buf, len);
free(buf); return result;
}
L_BIGINT: {
buflen = bigint_digits_len(js, value);
buf = (char *)ant_calloc(buflen + 2);
len = strbigint(js, value, buf, buflen + 2);
ant_value_t result = js_mkstr(js, buf, len);
free(buf); return result;
}
L_DEFAULT: {
buf = (char *)ant_calloc(64);
len = tostr(js, value, buf, 64);
ant_value_t result = js_mkstr(js, buf, len);
free(buf); return result;
}
}
const char *js_str(ant_t *js, ant_value_t value) {
if (is_err(value)) {
uint64_t data = vdata(value);
if (data != 0) {
ant_value_t obj = mkval(T_OBJ, data);
ant_value_t stack = js_get(js, obj, "stack");
if (vtype(stack) == T_STR) {
ant_offset_t slen, off = vstr(js, stack, &slen);
return (const char *)(uintptr_t)off;
}
}
return "Error";
}
multiref_count = 0;
multiref_next_id = 0;
stringify_depth = 0;
scan_refs(js, value);
size_t capacity = 4096;
char *buf = (char *)ant_calloc(capacity);
if (!buf) return "";
size_t len;
for (;;) {
stringify_depth = 0;
stringify_indent = 0;
len = tostr(js, value, buf, capacity);
if (len < capacity - 1) break;
capacity *= 2;
buf = (char *)ant_realloc(buf, capacity);
if (!buf) return "";
}
ant_value_t str = js_mkstr(js, buf, len);
free(buf);
if (is_err(str)) return "";
ant_offset_t off = vstr(js, str, NULL);
return (const char *)(uintptr_t)off;
}
static inline ant_offset_t get_dense_buf(ant_value_t arr) {
ant_object_t *ptr = js_obj_ptr(js_as_obj(arr));
if (!ptr || !ptr->fast_array || !ptr->u.array.data || ptr->u.array.cap == 0) return 0;
return (ant_offset_t)(uintptr_t)ptr;
}
static inline ant_object_t *dense_obj(ant_offset_t doff) {
if (!doff) return NULL;
ant_object_t *ptr = (ant_object_t *)(uintptr_t)doff;
if (!ptr || !ptr->fast_array || !ptr->u.array.data || ptr->u.array.cap == 0) return NULL;
return ptr;
}
static inline ant_value_t *dense_data(ant_offset_t doff) {
ant_object_t *ptr = dense_obj(doff);
return ptr ? ptr->u.array.data : NULL;
}
static inline ant_offset_t dense_capacity(ant_offset_t doff) {
ant_object_t *ptr = dense_obj(doff);
return ptr ? (ant_offset_t)ptr->u.array.cap : 0;
}
static inline ant_value_t dense_get(ant_offset_t doff, ant_offset_t idx) {
ant_object_t *ptr = dense_obj(doff);
if (!ptr || idx >= ptr->u.array.cap) return js_mkundef();
return ptr->u.array.data[idx];
}
static inline void dense_set(ant_t *js, ant_offset_t doff, ant_offset_t idx, ant_value_t val) {
ant_object_t *ptr = dense_obj(doff);
if (!ptr || idx >= ptr->u.array.cap) return;
ptr->u.array.data[idx] = val;
gc_write_barrier(js, ptr, val);
}
static ant_offset_t dense_grow(ant_t *js, ant_value_t arr, ant_offset_t needed) {
ant_object_t *obj = js_obj_ptr(js_as_obj(arr));
if (!obj) return 0;
ant_offset_t old_cap = obj->u.array.cap;
ant_offset_t new_cap = old_cap ? old_cap : MAX_DENSE_INITIAL_CAP;
while (new_cap < needed) new_cap *= 2;
ant_value_t *next = realloc(obj->u.array.data, sizeof(*next) * (size_t)new_cap);
if (!next) return 0;
for (ant_offset_t i = old_cap; i < new_cap; i++) next[i] = T_EMPTY;
obj->u.array.data = next;
obj->u.array.cap = (uint32_t)new_cap;
if (obj->u.array.len > obj->u.array.cap) obj->u.array.len = obj->u.array.cap;
obj->fast_array = 1;
return (ant_offset_t)(uintptr_t)obj;
}
// TODO: make get and set dry
static inline ant_value_t arr_get(ant_t *js, ant_value_t arr, ant_offset_t idx) {
ant_offset_t semantic_len = get_array_length(js, arr);
if (idx >= semantic_len) return js_mkundef();
ant_offset_t doff = get_dense_buf(arr);
if (doff) {
ant_offset_t len = dense_iterable_length(js, arr);
if (idx < len) {
ant_value_t v = dense_get(doff, idx);
if (!is_empty_slot(v)) return v;
}
}
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)idx);
return lkp_val(js, arr, idxstr, idxlen);
}
static inline void arr_set(ant_t *js, ant_value_t arr, ant_offset_t idx, ant_value_t val) {
ant_offset_t doff = get_dense_buf(arr);
if (doff) {
ant_offset_t len = dense_iterable_length(js, arr);
if (idx < len) {
dense_set(js, doff, idx, val);
return;
}
ant_offset_t density_limit = len > 0 ? len * 4 : 64;
if (idx >= density_limit) goto sparse;
ant_offset_t cap = dense_capacity(doff);
if (idx >= cap) {
doff = dense_grow(js, arr, idx + 1);
if (doff == 0) goto sparse;
}
for (ant_offset_t i = len; i < idx; i++) {
ant_value_t v = dense_get(doff, i);
if (!is_empty_slot(v) && vtype(v) == T_UNDEF) dense_set(js, doff, i, T_EMPTY);
}
dense_set(js, doff, idx, val);
array_len_set(js, arr, idx + 1);
return;
}
sparse:;
char idxstr[24];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)idx);
ant_value_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, arr, key, val);
}
static inline bool arr_has(ant_t *js, ant_value_t arr, ant_offset_t idx) {
ant_offset_t semantic_len = get_array_length(js, arr);
if (idx >= semantic_len) return false;
ant_offset_t doff = get_dense_buf(arr);
if (doff) {
ant_offset_t len = dense_iterable_length(js, arr);
if (idx < len) {
ant_value_t v = dense_get(doff, idx);
if (!is_empty_slot(v)) return true;
}
}
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)idx);
return lkp(js, arr, idxstr, idxlen) != 0;
}
static inline void arr_del(ant_t *js, ant_value_t arr, ant_offset_t idx) {
ant_offset_t semantic_len = get_array_length(js, arr);
if (idx >= semantic_len) return;
ant_offset_t doff = get_dense_buf(arr);
if (doff) {
ant_offset_t len = dense_iterable_length(js, arr);
if (idx < len) dense_set(js, doff, idx, T_EMPTY);
}
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)idx);
js_delete_prop(js, arr, idxstr, idxlen);
}
ant_value_t js_mkstr(ant_t *js, const void *ptr, size_t len) {
ant_flat_string_t *flat = (ant_flat_string_t *)js_type_alloc(
js, ANT_ALLOC_STRING, sizeof(*flat) + len + 1, _Alignof(ant_flat_string_t)
);
if (!flat) return js_mkerr(js, "oom");
flat->len = (ant_offset_t)len;
if (ptr && len > 0) memcpy(flat->bytes, ptr, len);
flat->bytes[len] = '\0';
return mkval(T_STR, (uintptr_t)flat);
}
ant_value_t js_mkstr_permanent(ant_t *js, const void *ptr, size_t len) {
size_t size = sizeof(ant_flat_string_t) + len + 1;
size_t align = _Alignof(ant_flat_string_t);
if (js->pool.permanent.block_size == 0)
js->pool.permanent.block_size = ANT_POOL_STRING_BLOCK_SIZE;
ant_flat_string_t *flat = (ant_flat_string_t *)pool_alloc_chain(
&js->pool.permanent.head, NULL, js->pool.permanent.block_size, size, align
);
if (!flat) return js_mkerr(js, "oom");
flat->len = (ant_offset_t)len;
if (ptr && len > 0) memcpy(flat->bytes, ptr, len);
flat->bytes[len] = '\0';
return mkval(T_STR, (uintptr_t)flat);
}
static ant_value_t js_mkrope(ant_t *js, ant_value_t left, ant_value_t right, ant_offset_t total_len, uint8_t depth) {
ant_rope_heap_t *rope = (ant_rope_heap_t *)js_type_alloc(
js, ANT_ALLOC_ROPE, sizeof(*rope), _Alignof(ant_rope_heap_t)
);
if (!rope) return js_mkerr(js, "oom");
rope->len = total_len;
rope->depth = depth;
rope->left = left;
rope->right = right;
rope->cached = js_mkundef();
return mkrope_value(rope);
}
static ant_value_t mkobj_with_inobj_limit(ant_t *js, ant_offset_t parent, uint8_t inobj_limit) {
(void)parent;
ant_object_t *obj = obj_alloc(js, T_OBJ, inobj_limit);
if (!obj) return js_mkerr(js, "oom");
return mkval(T_OBJ, (uintptr_t)obj);
}
ant_value_t mkobj(ant_t *js, ant_offset_t parent) {
return mkobj_with_inobj_limit(js, parent, (uint8_t)ANT_INOBJ_MAX_SLOTS);
}
ant_value_t js_mkobj_with_inobj_limit(ant_t *js, uint8_t inobj_limit) {
return mkobj_with_inobj_limit(js, 0, inobj_limit);
}
ant_value_t mkarr(ant_t *js) {
ant_object_t *obj = obj_alloc(js, T_ARR, (uint8_t)ANT_INOBJ_MAX_SLOTS);
if (!obj) return js_mkerr(js, "oom");
ant_value_t arr = mkval(T_ARR, (uintptr_t)obj);
ant_value_t array_proto = get_ctor_proto(js, "Array", 5);
if (vtype(array_proto) == T_OBJ) js_set_proto_init(arr, array_proto);
obj->u.array.cap = MAX_DENSE_INITIAL_CAP;
obj->u.array.len = 0;
obj->u.array.data = malloc(sizeof(*obj->u.array.data) * (size_t)obj->u.array.cap);
if (obj->u.array.data) {
for (uint32_t i = 0; i < obj->u.array.cap; i++) obj->u.array.data[i] = T_EMPTY;
obj->fast_array = 1;
} else {
obj->u.array.cap = 0;
obj->u.array.len = 0;
obj->fast_array = 0;
}
return arr;
}
ant_value_t js_mkarr(ant_t *js) {
return mkarr(js);
}
ant_value_t js_newobj(ant_t *js) {
ant_value_t obj = mkobj(js, 0);
ant_value_t proto = get_ctor_proto(js, "Object", 6);
if (vtype(proto) == T_OBJ) js_set_proto_init(obj, proto);
return obj;
}
ant_offset_t js_arr_len(ant_t *js, ant_value_t arr) {
if (!array_obj_ptr(arr)) return 0;
return get_array_length(js, arr);
}
ant_value_t js_arr_get(ant_t *js, ant_value_t arr, ant_offset_t idx) {
if (vtype(arr) != T_ARR) return js_mkundef();
return arr_get(js, arr, idx);
}
static inline bool is_const_prop(ant_t *js, ant_offset_t propoff) {
ant_prop_ref_t *ref = propref_get(js, propoff);
if (!ref) return false;
uint8_t attrs = ant_shape_get_attrs(ref->obj->shape, ref->slot);
return (attrs & ANT_PROP_ATTR_WRITABLE) == 0;
}
static inline const ant_shape_prop_t *prop_shape_meta(ant_t *js, ant_offset_t propoff) {
ant_prop_ref_t *ref = propref_get(js, propoff);
if (!ref || !ref->obj || !ref->obj->shape) return NULL;
return ant_shape_prop_at(ref->obj->shape, ref->slot);
}
static inline bool is_nonconfig_prop(ant_t *js, ant_offset_t propoff) {
ant_prop_ref_t *ref = propref_get(js, propoff);
if (!ref) return false;
uint8_t attrs = ant_shape_get_attrs(ref->obj->shape, ref->slot);
return (attrs & ANT_PROP_ATTR_CONFIGURABLE) == 0;
}
static void intern_init(void) {
if (INTERN_LENGTH) return;
INTERN_LENGTH = intern_string("length", 6);
INTERN_BUFFER = intern_string("buffer", 6);
INTERN_PROTOTYPE = intern_string("prototype", 9);
INTERN_CONSTRUCTOR = intern_string("constructor", 11);
INTERN_NAME = intern_string("name", 4);
INTERN_MESSAGE = intern_string("message", 7);
INTERN_VALUE = intern_string("value", 5);
INTERN_GET = intern_string("get", 3);
INTERN_SET = intern_string("set", 3);
INTERN_ARGUMENTS = intern_string("arguments", 9);
INTERN_CALLEE = intern_string("callee", 6);
INTERN_IDX[0] = intern_string("0", 1);
INTERN_IDX[1] = intern_string("1", 1);
INTERN_IDX[2] = intern_string("2", 1);
INTERN_IDX[3] = intern_string("3", 1);
INTERN_IDX[4] = intern_string("4", 1);
INTERN_IDX[5] = intern_string("5", 1);
INTERN_IDX[6] = intern_string("6", 1);
INTERN_IDX[7] = intern_string("7", 1);
INTERN_IDX[8] = intern_string("8", 1);
INTERN_IDX[9] = intern_string("9", 1);
}
ant_value_t mkprop(ant_t *js, ant_value_t obj, ant_value_t k, ant_value_t v, uint8_t attrs) {
obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr || !ptr->shape) return js_mkerr(js, "invalid object");
if (!attrs) attrs = ANT_PROP_ATTR_DEFAULT;
uint32_t slot = 0;
bool added = false;
if (vtype(k) == T_SYMBOL) {
ant_offset_t sym_off = (ant_offset_t)vdata(k);
int32_t found = ant_shape_lookup_symbol(ptr->shape, sym_off);
if (found >= 0) {
slot = (uint32_t)found;
if (!js_obj_ensure_unique_shape(ptr)) return js_mkerr(js, "oom");
ant_shape_set_attrs_symbol(ptr->shape, sym_off, attrs);
} else {
if (!ant_shape_add_symbol_tr(&ptr->shape, sym_off, attrs, &slot)) {
return js_mkerr(js, "oom");
}
added = true;
}
} else {
ant_offset_t klen = 0;
ant_offset_t koff = vstr(js, k, &klen);
const char *p = (const char *)(uintptr_t)(koff);
const char *interned = intern_string(p, klen);
if (!interned) return js_mkerr(js, "oom");
int32_t found = ant_shape_lookup_interned(ptr->shape, interned);
if (found >= 0) {
slot = (uint32_t)found;
if (!js_obj_ensure_unique_shape(ptr)) return js_mkerr(js, "oom");
ant_shape_set_attrs_interned(ptr->shape, interned, attrs);
} else {
if (!ant_shape_add_interned_tr(&ptr->shape, interned, attrs, &slot)) {
return js_mkerr(js, "oom");
}
added = true;
}
}
if (added && !js_obj_ensure_prop_capacity(ptr, ant_shape_count(ptr->shape))) {
return js_mkerr(js, "oom");
}
if (slot >= ptr->prop_count && !js_obj_ensure_prop_capacity(ptr, slot + 1)) {
return js_mkerr(js, "oom");
}
ant_object_prop_set_unchecked(ptr, slot, v);
gc_write_barrier(js, ptr, v);
return v;
}
ant_value_t mkprop_interned(ant_t *js, ant_value_t obj, const char *interned_key, ant_value_t v, uint8_t attrs) {
obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr || !ptr->shape || !interned_key) return js_mkerr(js, "invalid object");
if (!attrs) attrs = ANT_PROP_ATTR_DEFAULT;
uint32_t slot = 0;
bool added = false;
int32_t found = ant_shape_lookup_interned(ptr->shape, interned_key);
if (found >= 0) {
slot = (uint32_t)found;
if (!js_obj_ensure_unique_shape(ptr)) return js_mkerr(js, "oom");
ant_shape_set_attrs_interned(ptr->shape, interned_key, attrs);
} else {
if (!ant_shape_add_interned_tr(&ptr->shape, interned_key, attrs, &slot)) {
return js_mkerr(js, "oom");
}
added = true;
}
if (added && !js_obj_ensure_prop_capacity(ptr, ant_shape_count(ptr->shape))) {
return js_mkerr(js, "oom");
}
if (slot >= ptr->prop_count && !js_obj_ensure_prop_capacity(ptr, slot + 1)) {
return js_mkerr(js, "oom");
}
ant_object_prop_set_unchecked(ptr, slot, v);
gc_write_barrier(js, ptr, v);
return v;
}
ant_value_t js_mkprop_fast(ant_t *js, ant_value_t obj, const char *key, size_t len, ant_value_t v) {
ant_value_t k = js_mkstr(js, key, len);
if (is_err(k)) return k;
return mkprop(js, obj, k, v, 0);
}
ant_offset_t js_mkprop_fast_off(ant_t *js, ant_value_t obj, const char *key, size_t len, ant_value_t v) {
ant_value_t k = js_mkstr(js, key, len);
if (is_err(k)) return 0;
ant_value_t prop = mkprop(js, obj, k, v, 0);
if (is_err(prop)) return 0;
return lkp(js, obj, key, len);
}
void js_saveval(ant_t *js, ant_offset_t off, ant_value_t v) {
bool ok = propref_store(js, off, v);
assert(ok && "js_saveval expects a valid property handle");
(void)ok;
}
static void set_slot(ant_value_t obj, internal_slot_t slot, ant_value_t val) {
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr || slot < 0 || slot > SLOT_MAX) return;
if (slot == SLOT_PROTO) {
ptr->proto = val;
ant_ic_epoch_bump();
return;
}
if (slot == SLOT_DATA) {
ptr->u.data.value = val;
return;
}
(void)obj_extra_set(ptr, slot, val);
}
static void set_slot_wb(ant_t *js, ant_value_t obj, internal_slot_t slot, ant_value_t val) {
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr || slot < 0 || slot > SLOT_MAX) return;
if (slot == SLOT_PROTO) {
ptr->proto = val;
gc_write_barrier(js, ptr, val);
ant_ic_epoch_bump();
return;
}
if (slot == SLOT_DATA) {
ptr->u.data.value = val;
gc_write_barrier(js, ptr, val);
return;
}
(void)obj_extra_set(ptr, slot, val);
gc_write_barrier(js, ptr, val);
}
void js_set_slot(ant_value_t obj, internal_slot_t slot, ant_value_t value) {
set_slot(js_as_obj(obj), slot, value);
}
void js_set_slot_wb(ant_t *js, ant_value_t obj, internal_slot_t slot, ant_value_t value) {
set_slot_wb(js, js_as_obj(obj), slot, value);
}
static ant_value_t get_slot(ant_value_t obj, internal_slot_t slot) {
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr || slot < 0 || slot > SLOT_MAX) return js_mkundef();
if (slot == SLOT_PROTO) return ptr->proto;
if (slot == SLOT_DATA) {
return ptr->u.data.value;
}
return obj_extra_get(ptr, slot);
}
static void set_func_code_ptr(ant_t *js, ant_value_t func_obj, const char *code, size_t len) {
set_slot(func_obj, SLOT_CODE, mkval(T_CFUNC, (size_t)code));
set_slot(func_obj, SLOT_CODE_LEN, tov((double)len));
}
static void set_func_code(ant_t *js, ant_value_t func_obj, const char *code, size_t len) {
const char *arena_code = code_arena_alloc(code, len);
if (!arena_code) return;
set_func_code_ptr(js, func_obj, arena_code, len);
if (!memmem(code, len, "var", 3)) return;
size_t vars_buf_len;
char *vars = OXC_get_func_hoisted_vars(code, len, &vars_buf_len);
if (vars) {
set_slot(func_obj, SLOT_HOISTED_VARS, mkval(T_CFUNC, (size_t)vars));
set_slot(func_obj, SLOT_HOISTED_VARS_LEN, tov((double)vars_buf_len));
}
}
static const char *get_func_code(ant_t *js, ant_value_t func_obj, ant_offset_t *len) {
ant_value_t code_val = get_slot(func_obj, SLOT_CODE);
ant_value_t len_val = get_slot(func_obj, SLOT_CODE_LEN);
if (vtype(code_val) != T_CFUNC) {
if (len) *len = 0;
return NULL;
}
if (len) *len = (ant_offset_t)tod(len_val);
return (const char *)vdata(code_val);
}
double js_to_number(ant_t *js, ant_value_t arg) {
if (vtype(arg) == T_NUM) return tod(arg);
if (vtype(arg) == T_BOOL) return vdata(arg) ? 1.0 : 0.0;
if (vtype(arg) == T_NULL) return 0.0;
if (vtype(arg) == T_UNDEF) return JS_NAN;
if (vtype(arg) == T_STR) {
ant_offset_t len, off = vstr(js, arg, &len);
const char *s = (char *)(uintptr_t)(off), *end;
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
if (!*s) return 0.0;
double val = strtod(s, (char **)&end);
while (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r') end++;
return (end == s || *end) ? JS_NAN : val;
}
if (vtype(arg) == T_OBJ || vtype(arg) == T_ARR) {
if (vtype(arg) == T_OBJ) {
ant_value_t prim = js_call_valueOf(js, arg);
uint8_t pt = vtype(prim);
if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC) return js_to_number(js, prim);
}
ant_value_t str_val = js_tostring_val(js, arg);
if (is_err(str_val) || vtype(str_val) != T_STR) return JS_NAN;
return js_to_number(js, str_val);
}
return JS_NAN;
}
static ant_value_t setup_func_prototype(ant_t *js, ant_value_t func) {
ant_value_t proto_obj = mkobj(js, 0);
if (is_err(proto_obj)) return proto_obj;
ant_value_t object_proto = get_ctor_proto(js, "Object", 6);
if (vtype(object_proto) == T_OBJ) {
js_set_proto_init(proto_obj, object_proto);
}
ant_value_t constructor_key = js_mkstr(js, "constructor", 11);
if (is_err(constructor_key)) return constructor_key;
ant_value_t res = mkprop(js, proto_obj, constructor_key, func, 0);
if (is_err(res)) return res;
js_set_descriptor(js, proto_obj, "constructor", 11, JS_DESC_W | JS_DESC_C);
ant_value_t prototype_key = js_mkstr(js, "prototype", 9);
if (is_err(prototype_key)) return prototype_key;
res = js_setprop(js, func, prototype_key, proto_obj);
if (is_err(res)) return res;
js_set_descriptor(js, js_as_obj(func), "prototype", 9, JS_DESC_W);
js_mark_constructor(func, true);
return js_mkundef();
}
static inline bool same_object_identity(ant_value_t a, ant_value_t b) {
if (!is_object_type(a) || !is_object_type(b)) return false;
return vdata(js_as_obj(a)) == vdata(js_as_obj(b));
}
static inline ant_value_t proto_next_obj_or_null(ant_t *js, ant_value_t cur) {
if (!is_object_type(cur)) return js_mknull();
ant_value_t next = get_proto(js, cur);
return is_object_type(next) ? next : js_mknull();
}
typedef struct {
int depth;
bool overflow;
ant_value_t fast;
} proto_overflow_guard_t;
static inline void proto_overflow_guard_init(proto_overflow_guard_t *g) {
g->depth = 0;
g->overflow = false;
g->fast = js_mknull();
}
static inline bool proto_overflow_guard_hit_cycle(ant_t *js, proto_overflow_guard_t *g, ant_value_t cur) {
if (!g->overflow) {
if (++g->depth < MAX_PROTO_CHAIN_DEPTH) return false;
g->overflow = true;
g->fast = cur;
}
g->fast = proto_next_obj_or_null(js, g->fast);
g->fast = proto_next_obj_or_null(js, g->fast);
return same_object_identity(cur, g->fast);
}
static inline bool proto_walk_next(ant_t *js, ant_value_t *cur, uint8_t *t, uint8_t flags) {
uint8_t ct = *t;
if (flags & PROTO_WALK_F_OBJECT_ONLY) {
if (!is_object_type(*cur)) return false;
ant_value_t next = get_proto(js, *cur);
uint8_t nt = vtype(next);
if (nt == T_NULL || nt == T_UNDEF || !is_object_type(next)) return false;
*cur = next; *t = nt;
return true;
}
if (ct == T_OBJ || ct == T_ARR || ct == T_FUNC || ct == T_PROMISE) {
ant_value_t as_obj = js_as_obj(*cur);
ant_value_t proto = get_slot(as_obj, SLOT_PROTO);
uint8_t pt = vtype(proto);
if (pt == T_OBJ || pt == T_ARR || pt == T_FUNC) {
*cur = proto;
*t = pt;
return true;
}
if (JS_TYPE_FLAG(ct) & T_NEEDS_PROTO_FALLBACK) {
ant_value_t fallback = get_prototype_for_type(js, ct);
uint8_t ft = vtype(fallback);
if (ft == T_NULL || ft == T_UNDEF) return false;
*cur = fallback;
*t = ft;
return true;
}
return false;
}
if (ct == T_STR || ct == T_NUM || ct == T_BOOL || ct == T_BIGINT || ct == T_SYMBOL) {
ant_value_t proto = get_prototype_for_type(js, ct);
uint8_t pt = vtype(proto);
if (pt == T_NULL || pt == T_UNDEF) return false;
*cur = proto; *t = pt;
return true;
}
return false;
}
typedef struct {
int depth;
bool overflow;
bool fast_active;
ant_value_t fast_cur;
uint8_t fast_t;
} proto_walk_overflow_guard_t;
static inline void proto_walk_overflow_guard_init(proto_walk_overflow_guard_t *g) {
g->depth = 0;
g->overflow = false;
g->fast_active = false;
g->fast_cur = js_mknull();
g->fast_t = T_NULL;
}
static inline bool proto_walk_overflow_guard_hit_cycle(
ant_t *js,
proto_walk_overflow_guard_t *g,
ant_value_t cur,
uint8_t cur_t,
uint8_t flags
) {
if (!g->overflow) {
if (++g->depth < MAX_PROTO_CHAIN_DEPTH) return false;
g->overflow = true;
g->fast_active = true;
g->fast_cur = cur;
g->fast_t = cur_t;
}
if (g->fast_active && !proto_walk_next(js, &g->fast_cur, &g->fast_t, flags))
g->fast_active = false;
if (g->fast_active && !proto_walk_next(js, &g->fast_cur, &g->fast_t, flags))
g->fast_active = false;
return g->fast_active && same_object_identity(cur, g->fast_cur);
}
ant_value_t js_instance_proto_from_new_target(ant_t *js, ant_value_t fallback_proto) {
ant_value_t instance_proto = js_mkundef();
if (vtype(js->new_target) == T_FUNC || vtype(js->new_target) == T_CFUNC) {
ant_value_t nt_obj = js_as_obj(js->new_target);
ant_value_t nt_proto = lkp_interned_val(js, nt_obj, INTERN_PROTOTYPE);
if (is_object_type(nt_proto)) instance_proto = nt_proto;
}
if (!is_object_type(instance_proto) && is_object_type(fallback_proto)) {
instance_proto = fallback_proto;
} return instance_proto;
}
bool proto_chain_contains(ant_t *js, ant_value_t obj, ant_value_t proto_target) {
if (!is_object_type(obj) || !is_object_type(proto_target)) return false;
ant_value_t cur = obj;
uint8_t t = vtype(cur);
proto_overflow_guard_t guard;
proto_overflow_guard_init(&guard);
while (true) {
if (!proto_walk_next(js, &cur, &t, PROTO_WALK_F_OBJECT_ONLY)) break;
if (same_object_identity(cur, proto_target)) return true;
if (proto_overflow_guard_hit_cycle(js, &guard, cur)) break;
}
return false;
}
static inline bool is_wrapper_ctor_target(ant_t *js, ant_value_t this_val, ant_value_t expected_proto) {
if (vtype(js->new_target) == T_UNDEF) return false;
if (vtype(this_val) != T_OBJ) return false;
if (vtype(get_slot(this_val, SLOT_PRIMITIVE)) != T_UNDEF) return false;
return proto_chain_contains(js, this_val, expected_proto);
}
ant_value_t get_ctor_species_value(ant_t *js, ant_value_t ctor) {
if (!is_object_type(ctor) && vtype(ctor) != T_CFUNC) return js_mkundef();
return js_get_sym(js, ctor, get_species_sym());
}
bool same_ctor_identity(ant_t *js, ant_value_t a, ant_value_t b) {
if (vtype(a) == vtype(b) && vdata(a) == vdata(b)) return true;
if (vtype(a) == T_FUNC && vtype(b) == T_CFUNC) {
ant_value_t c = get_slot(a, SLOT_CFUNC);
return vtype(c) == T_CFUNC && vdata(c) == vdata(b);
}
if (vtype(a) == T_CFUNC && vtype(b) == T_FUNC) {
ant_value_t c = get_slot(b, SLOT_CFUNC);
return vtype(c) == T_CFUNC && vdata(c) == vdata(a);
}
if (vtype(a) == T_FUNC && vtype(b) == T_FUNC) {
ant_value_t ca = get_slot(a, SLOT_CFUNC);
ant_value_t cb = get_slot(b, SLOT_CFUNC);
if (vtype(ca) == T_CFUNC && vtype(cb) == T_CFUNC && vdata(ca) == vdata(cb)) return true;
}
return false;
}
static ant_value_t array_constructor_from_receiver(ant_t *js, ant_value_t receiver) {
if (!is_object_type(receiver)) return js_mkundef();
ant_value_t species_source = receiver;
if (is_proxy(species_source)) {
species_source = proxy_read_target(js, species_source);
}
bool receiver_is_array = (vtype(species_source) == T_ARR);
if (!receiver_is_array) {
ant_value_t array_proto = get_ctor_proto(js, "Array", 5);
if (is_object_type(array_proto) && is_object_type(species_source)) {
receiver_is_array = proto_chain_contains(js, species_source, array_proto);
}
}
if (!receiver_is_array) return js_mkundef();
ant_value_t ctor = js_getprop_fallback(js, receiver, "constructor");
if (is_err(ctor)) return ctor;
ant_value_t species = get_ctor_species_value(js, ctor);
if (is_err(species)) return species;
if (vtype(species) == T_NULL) return js_mkundef();
if (vtype(species) == T_FUNC || vtype(species) == T_CFUNC) return species;
if (vtype(ctor) != T_FUNC && vtype(ctor) != T_CFUNC) return js_mkundef();
return ctor;
}
static ant_value_t array_alloc_from_ctor_with_length(ant_t *js, ant_value_t ctor, ant_offset_t length_hint) {
if (vtype(ctor) != T_FUNC && vtype(ctor) != T_CFUNC) {
return mkarr(js);
}
ant_value_t seed = js_mkobj(js);
if (is_err(seed)) return seed;
ant_value_t proto = js_get(js, ctor, "prototype");
if (is_err(proto)) return proto;
if (is_object_type(proto)) js_set_proto_init(seed, proto);
ant_value_t ctor_args[1] = { tov((double)length_hint) };
ant_value_t saved_new_target = js->new_target;
js->new_target = ctor;
ant_value_t constructed = sv_vm_call(js->vm, js, ctor, seed, ctor_args, 1, NULL, true);
js->new_target = saved_new_target;
if (is_err(constructed)) return constructed;
ant_value_t result = is_object_type(constructed) ? constructed : seed;
set_slot(js_as_obj(result), SLOT_CTOR, ctor);
return result;
}
static inline ant_value_t array_alloc_from_ctor(ant_t *js, ant_value_t ctor) {
return array_alloc_from_ctor_with_length(js, ctor, 0);
}
static inline ant_value_t array_alloc_like(ant_t *js, ant_value_t receiver) {
ant_value_t ctor = array_constructor_from_receiver(js, receiver);
if (is_err(ctor)) return ctor;
return array_alloc_from_ctor(js, ctor);
}
static ant_value_t validate_array_length(ant_t *js, ant_value_t v) {
if (vtype(v) != T_NUM) {
return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid array length");
}
double d = tod(v);
if (d < 0 || d != (uint32_t)d || d >= 4294967296.0) {
return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid array length");
}
return js_mkundef();
}
static inline ant_value_t check_object_extensibility(ant_t *js, ant_value_t obj) {
obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr) return js_mkundef();
if (ptr->frozen) {
return sv_vm_is_strict(js->vm)
? js_mkerr(js, "cannot add property to frozen object")
: js_false;
}
if (ptr->sealed) {
return sv_vm_is_strict(js->vm)
? js_mkerr(js, "cannot add property to sealed object")
: js_false;
}
if (!ptr->extensible) {
return sv_vm_is_strict(js->vm)
? js_mkerr(js, "cannot add property to non-extensible object")
: js_false;
}
return js_mkundef();
}
static inline void array_len_set(ant_t *js, ant_value_t obj, ant_offset_t new_len) {
ant_object_t *arr_ptr = array_obj_ptr(obj);
if (arr_ptr) {
if (new_len > (ant_offset_t)UINT32_MAX) new_len = (ant_offset_t)UINT32_MAX;
arr_ptr->u.array.len = (uint32_t)new_len;
return;
}
ant_value_t new_len_val = tov((double)new_len);
ant_offset_t len_off = lkp_interned(js, obj, INTERN_LENGTH, 6);
if (len_off != 0) {
js_saveval(js, len_off, new_len_val);
} else {
js_mkprop_fast(js, obj, "length", 6, new_len_val);
}
}
static ant_value_t js_setprop_array_fast(ant_t *js, ant_value_t obj, ant_value_t k, ant_value_t v, ant_offset_t klen, const char *key) {
unsigned long idx;
if (!parse_array_index(key, klen, (ant_offset_t)-1, &idx)) return js_mkundef();
ant_offset_t cur_len = get_array_length(js, obj);
ant_offset_t doff = get_dense_buf(obj);
if (doff) {
ant_offset_t dense_len = dense_iterable_length(js, obj);
if (idx < dense_len) { dense_set(js, doff, (ant_offset_t)idx, v); return v; }
ant_offset_t density_limit = dense_len > 0 ? dense_len * 4 : 64;
if (idx >= density_limit) goto sparse;
ant_value_t extensibility_error = check_object_extensibility(js, obj);
if (is_err(extensibility_error)) return extensibility_error;
if (extensibility_error == js_false) return v;
arr_set(js, obj, (ant_offset_t)idx, v);
return v;
}
sparse:;
if (idx < cur_len) return js_mkundef();
ant_value_t extensibility_error = check_object_extensibility(js, obj);
if (is_err(extensibility_error)) return extensibility_error;
if (extensibility_error == js_false) return v;
ant_value_t result = mkprop(js, obj, k, v, 0);
if (is_err(result)) return result;
array_define_or_set_index(js, obj, key, (size_t)klen);
return v;
}
typedef enum {
PROP_META_STRING = 0,
PROP_META_SYMBOL = 1,
} prop_meta_key_t;
static inline void prop_meta_defaults(prop_meta_t *out) {
*out = (prop_meta_t){
.has_getter = false,
.has_setter = false,
.writable = true,
.enumerable = true,
.configurable = true,
.getter = js_mkundef(),
.setter = js_mkundef(),
};
}
static inline void prop_meta_from_shape(prop_meta_t *out, const ant_shape_prop_t *prop) {
out->has_getter = prop->has_getter != 0;
out->has_setter = prop->has_setter != 0;
out->writable = (prop->attrs & ANT_PROP_ATTR_WRITABLE) != 0;
out->enumerable = (prop->attrs & ANT_PROP_ATTR_ENUMERABLE) != 0;
out->configurable = (prop->attrs & ANT_PROP_ATTR_CONFIGURABLE) != 0;
out->getter = prop->getter;
out->setter = prop->setter;
}
static inline void prop_meta_from_desc(prop_meta_t *out, const descriptor_entry_t *desc) {
out->has_getter = desc->has_getter;
out->has_setter = desc->has_setter;
out->writable = desc->writable;
out->enumerable = desc->enumerable;
out->configurable = desc->configurable;
out->getter = desc->getter;
out->setter = desc->setter;
}
static bool lookup_prop_meta(
ant_t *js,
ant_value_t cur_obj,
prop_meta_key_t key_kind,
const char *key,
size_t klen,
ant_offset_t sym_off,
prop_meta_t *out
) {
if (!out || !is_object_type(cur_obj)) return false;
if (key_kind == PROP_META_STRING && !key) return false;
prop_meta_defaults(out);
ant_object_t *cur_ptr = js_obj_ptr(cur_obj);
if (!cur_ptr) return false;
if (key_kind == PROP_META_STRING && key && is_length_key(key, klen) &&
cur_ptr->type_tag == T_ARR) {
out->has_getter = false;
out->has_setter = false;
out->writable = true;
out->enumerable = false;
out->configurable = false;
out->getter = js_mkundef();
out->setter = js_mkundef();
return true;
}
if (cur_ptr->shape) {
int32_t slot = -1;
if (key_kind == PROP_META_SYMBOL) {
slot = ant_shape_lookup_symbol(cur_ptr->shape, sym_off);
} else {
const char *interned_key = intern_string(key, klen);
if (interned_key) slot = ant_shape_lookup_interned(cur_ptr->shape, interned_key);
}
if (slot >= 0) {
const ant_shape_prop_t *prop = ant_shape_prop_at(cur_ptr->shape, (uint32_t)slot);
if (!prop) return false;
prop_meta_from_shape(out, prop);
return true;
}
}
if (!cur_ptr->is_exotic) return false;
descriptor_entry_t *desc = NULL;
if (key_kind == PROP_META_SYMBOL) {
desc = lookup_sym_descriptor(cur_obj, sym_off);
} else if (js) {
desc = lookup_descriptor(cur_obj, key, klen);
}
if (!desc) return false;
prop_meta_from_desc(out, desc);
return true;
}
static inline bool lookup_symbol_prop_meta(ant_value_t cur_obj, ant_offset_t sym_off, prop_meta_t *out) {
return lookup_prop_meta(NULL, cur_obj, PROP_META_SYMBOL, NULL, 0, sym_off, out);
}
static inline bool lookup_string_prop_meta(ant_t *js, ant_value_t cur_obj, const char *key, size_t klen, prop_meta_t *out) {
return lookup_prop_meta(js, cur_obj, PROP_META_STRING, key, klen, 0, out);
}
ant_value_t js_setprop(ant_t *js, ant_value_t obj, ant_value_t k, ant_value_t v) {
uint8_t ot = vtype(obj);
if (ot == T_STR || ot == T_NUM || ot == T_BOOL) {
ant_offset_t klen; ant_offset_t koff = vstr(js, k, &klen);
const char *key = (char *)(uintptr_t)(koff);
ant_value_t proto = get_prototype_for_type(js, ot);
if (is_object_type(proto)) {
ant_value_t setter = js_mkundef();
bool has_setter = false;
lkp_with_setter(js, proto, key, klen, &setter, &has_setter);
if (has_setter && (vtype(setter) == T_FUNC || vtype(setter) == T_CFUNC)) {
call_proto_accessor(js, obj, setter, true, &v, 1, true);
return v;
}
}
if (sv_vm_is_strict(js->vm))
return js_mkerr_typed(js, JS_ERR_TYPE,
"Cannot create property '%.*s' on %s",
(int)klen, key, typestr(ot));
return v;
}
if (vtype(obj) == T_FUNC) obj = js_func_obj(obj);
if (vtype(k) == T_SYMBOL) {
ant_offset_t sym_off = (ant_offset_t)vdata(k);
ant_value_t cur = obj;
proto_overflow_guard_t guard;
proto_overflow_guard_init(&guard);
while (is_object_type(cur)) {
ant_value_t cur_obj = js_as_obj(cur);
prop_meta_t meta;
if (lookup_symbol_prop_meta(cur_obj, sym_off, &meta)) {
if (meta.has_setter) {
ant_value_t setter = meta.setter;
if (vtype(setter) == T_FUNC || vtype(setter) == T_CFUNC) {
ant_value_t result = sv_vm_call(js->vm, js, setter, obj, &v, 1, NULL, false);
if (is_err(result)) return result;
return v;
}
}
if (meta.has_getter && !meta.has_setter) {
if (sv_vm_is_strict(js->vm)) return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot set property which has only a getter");
return v;
}
if (!meta.has_getter && !meta.has_setter && !meta.writable) {
if (sv_vm_is_strict(js->vm)) return js_mkerr(js, "assignment to read-only property");
return v;
}
break;
}
ant_value_t proto = get_proto(js, cur_obj);
if (!is_object_type(proto)) break;
cur = proto;
if (proto_overflow_guard_hit_cycle(js, &guard, cur)) break;
}
ant_offset_t existing = lkp_sym(js, obj, sym_off);
if (existing > 0) {
if (is_const_prop(js, existing)) return js_mkerr(js, "assignment to constant");
js_saveval(js, existing, v);
return v;
}
{
ant_value_t extensibility_error = check_object_extensibility(js, obj);
if (is_err(extensibility_error)) return extensibility_error;
if (extensibility_error == js_false) return v;
}
return mkprop(js, obj, k, v, 0);
}
ant_offset_t klen; ant_offset_t koff = vstr(js, k, &klen);
const char *key = (char *)(uintptr_t)(koff);
if (array_obj_ptr(obj) && !is_proxy(obj) && klen > 0 && key[0] >= '0' && key[0] <= '9') {
ant_value_t result = js_setprop_array_fast(js, obj, k, v, klen, key);
if (vtype(result) != T_UNDEF) return result;
}
if (array_obj_ptr(obj) && is_length_key(key, klen)) {
ant_value_t err = validate_array_length(js, v);
if (is_err(err)) return err;
ant_offset_t doff = get_dense_buf(obj);
ant_offset_t new_len_val = (ant_offset_t) tod(v);
if (doff) {
ant_offset_t cap = dense_capacity(doff);
ant_offset_t cur_len = get_array_length(js, obj);
ant_offset_t clear_to = (cur_len < cap) ? cur_len : cap;
if (new_len_val < clear_to) {
for (ant_offset_t i = new_len_val; i < clear_to; i++)
dense_set(js, doff, i, T_EMPTY);
}
}
array_len_set(js, obj, new_len_val);
return v;
}
if (is_proxy(obj)) {
ant_value_t result = proxy_set(js, obj, key, klen, v);
if (is_err(result)) return result;
return v;
}
if (try_dynamic_setter(js, obj, key, klen, v)) return v;
ant_offset_t existing = lkp(js, obj, key, klen);
{
const char *interned_key = intern_string(key, (size_t)klen);
bool found_desc = false;
bool desc_on_receiver = false;
bool desc_has_getter = false;
bool desc_has_setter = false;
bool desc_writable = true;
ant_value_t desc_setter = js_mkundef();
ant_value_t cur = obj;
proto_overflow_guard_t guard;
proto_overflow_guard_init(&guard);
bool on_receiver = true;
while (is_object_type(cur)) {
ant_value_t cur_obj = js_as_obj(cur);
ant_object_t *cur_ptr = js_obj_ptr(cur_obj);
if (!cur_ptr) break;
bool found_here = false;
if (cur_ptr->shape && interned_key) {
int32_t slot = ant_shape_lookup_interned(cur_ptr->shape, interned_key);
if (slot >= 0) {
const ant_shape_prop_t *prop = ant_shape_prop_at(cur_ptr->shape, (uint32_t)slot);
if (prop) {
found_here = true;
desc_has_getter = prop->has_getter != 0;
desc_has_setter = prop->has_setter != 0;
desc_writable = (prop->attrs & ANT_PROP_ATTR_WRITABLE) != 0;
desc_setter = prop->setter;
}
}
}
if (!found_here && cur_ptr->is_exotic) {
descriptor_entry_t *desc = lookup_descriptor(cur_obj, key, klen);
if (desc) {
found_here = true;
desc_has_getter = desc->has_getter;
desc_has_setter = desc->has_setter;
desc_writable = desc->writable;
desc_setter = desc->setter;
}
}
if (found_here) {
found_desc = true;
desc_on_receiver = on_receiver;
break;
}
ant_value_t proto = get_proto(js, cur_obj);
if (vtype(proto) != T_OBJ && vtype(proto) != T_FUNC) break;
cur = proto;
on_receiver = false;
if (proto_overflow_guard_hit_cycle(js, &guard, cur)) break;
}
if (!found_desc) goto no_descriptor;
if (!desc_on_receiver && !desc_has_setter && !desc_has_getter && desc_writable) goto no_descriptor;
if (desc_has_setter) {
ant_value_t setter = desc_setter;
uint8_t setter_type = vtype(setter);
if (setter_type == T_FUNC || setter_type == T_CFUNC) {
js_error_site_t saved_errsite = js->errsite;
ant_value_t result = sv_vm_call(js->vm, js, setter, obj, &v, 1, NULL, false);
js->errsite = saved_errsite;
if (is_err(result)) return result;
return v;
}
}
if (desc_has_getter && !desc_has_setter) {
if (sv_vm_is_strict(js->vm)) return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot set property which has only a getter");
return v;
}
if (!desc_writable) {
if (sv_vm_is_strict(js->vm)) return js_mkerr(js, "assignment to read-only property");
return v;
}
if (existing <= 0) goto no_descriptor;
}
no_descriptor:
if (existing <= 0) goto create_new;
if (is_const_prop(js, existing)) return js_mkerr(js, "assignment to constant");
js_saveval(js, existing, v);
array_define_or_set_index(js, obj, key, (size_t)klen);
return v;
create_new:
{
ant_value_t extensibility_error = check_object_extensibility(js, obj);
if (is_err(extensibility_error)) return extensibility_error;
if (extensibility_error == js_false) return v;
}
ant_value_t result = mkprop(js, obj, k, v, 0);
if (is_err(result)) return result;
array_define_or_set_index(js, obj, key, (size_t)klen);
return v;
}
ant_value_t setprop_cstr(ant_t *js, ant_value_t obj, const char *key, size_t len, ant_value_t v) {
obj = js_as_obj(obj);
ant_value_t k = js_mkstr(js, key, len);
if (is_err(k)) return k;
return mkprop(js, obj, k, v, 0);
}
ant_value_t js_define_own_prop(ant_t *js, ant_value_t obj, const char *key, size_t klen, ant_value_t v) {
obj = js_as_obj(obj);
if (is_proxy(obj)) {
ant_value_t result = proxy_set(js, obj, key, klen, v);
if (is_err(result)) return result;
return v;
}
if (try_dynamic_setter(js, obj, key, klen, v)) return v;
ant_offset_t existing = lkp(js, obj, key, klen);
{
bool has_desc = false;
bool desc_writable = true;
bool desc_has_setter = false;
ant_value_t desc_setter = js_mkundef();
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
const char *interned_key = intern_string(key, klen);
if (ptr && ptr->shape && interned_key) {
int32_t slot = ant_shape_lookup_interned(ptr->shape, interned_key);
if (slot >= 0) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, (uint32_t)slot);
if (prop) {
has_desc = true;
desc_writable = (prop->attrs & ANT_PROP_ATTR_WRITABLE) != 0;
desc_has_setter = prop->has_setter != 0;
desc_setter = prop->setter;
}
}
}
if (!has_desc && ptr && ptr->is_exotic) {
descriptor_entry_t *desc = lookup_descriptor(as_obj, key, klen);
if (desc) {
has_desc = true;
desc_writable = desc->writable;
desc_has_setter = desc->has_setter;
desc_setter = desc->setter;
}
}
if (has_desc) {
if (!desc_writable && !desc_has_setter) {
if (sv_vm_is_strict(js->vm)) return js_mkerr(js, "assignment to read-only property");
return v;
}
if (desc_has_setter) {
ant_value_t setter = desc_setter;
uint8_t setter_type = vtype(setter);
if (setter_type == T_FUNC || setter_type == T_CFUNC) {
ant_value_t result = sv_vm_call(js->vm, js, setter, obj, &v, 1, NULL, false);
if (is_err(result)) return result;
return v;
}
}
}
}
if (existing > 0) {
if (is_const_prop(js, existing)) return js_mkerr(js, "assignment to constant");
js_saveval(js, existing, v);
array_define_or_set_index(js, obj, key, klen);
return v;
}
{
ant_value_t extensibility_error = check_object_extensibility(js, obj);
if (is_err(extensibility_error)) return extensibility_error;
if (extensibility_error == js_false) return v;
}
ant_value_t k = js_mkstr(js, key, klen);
if (is_err(k)) return k;
ant_value_t created = mkprop(js, obj, k, v, 0);
if (!is_err(created)) array_define_or_set_index(js, obj, key, klen);
return is_err(created) ? created : v;
}
ant_value_t setprop_interned(ant_t *js, ant_value_t obj, const char *key, size_t len, ant_value_t v) {
ant_value_t k = js_mkstr(js, key, len);
if (is_err(k)) return k;
return js_setprop(js, obj, k, v);
}
ant_value_t js_setprop_nonconfigurable(ant_t *js, ant_value_t obj, const char *key, size_t keylen, ant_value_t v) {
ant_value_t k = js_mkstr(js, key, keylen);
if (is_err(k)) return k;
ant_value_t result = js_setprop(js, obj, k, v);
if (is_err(result)) return result;
js_set_descriptor(js, js_as_obj(obj), key, keylen, JS_DESC_W);
return result;
}
#define SYM_FLAG_GLOBAL 1u
#define SYM_FLAG_WELL_KNOWN 2u
typedef struct sym_registry_entry {
const char *key;
ant_value_t sym;
UT_hash_handle hh;
} sym_registry_entry_t;
ant_value_t js_mksym(ant_t *js, const char *desc) {
uint32_t id = (uint32_t)(++js->sym.counter);
size_t desc_len = (desc && *desc) ? strlen(desc) : 0;
size_t total = sizeof(ant_symbol_heap_t) + (desc_len ? desc_len + 1 : 0);
ant_symbol_heap_t *sym_ptr = (ant_symbol_heap_t *)js_type_alloc(
js, ANT_ALLOC_SYMBOL, total, _Alignof(ant_symbol_heap_t)
);
if (!sym_ptr) return js_mkerr(js, "oom");
sym_ptr->id = id;
sym_ptr->flags = 0;
sym_ptr->key = NULL;
sym_ptr->desc_len = (uint32_t)desc_len;
if (desc_len) {
memcpy(sym_ptr->desc, desc, desc_len);
sym_ptr->desc[desc_len] = '\0';
}
return mkval(T_SYMBOL, (uintptr_t)sym_ptr);
}
ant_value_t js_mksym_well_known(ant_t *js, const char *desc) {
ant_value_t sym = js_mksym(js, desc);
if (is_err(sym)) return sym;
ant_symbol_heap_t *ptr = (ant_symbol_heap_t *)(uintptr_t)vdata(sym);
if (ptr) ptr->flags |= SYM_FLAG_WELL_KNOWN;
return sym;
}
static inline ant_symbol_heap_t *sym_ptr(ant_value_t v) {
return (ant_symbol_heap_t *)(uintptr_t)vdata(v);
}
static inline uint32_t sym_get_id(ant_t *js, ant_value_t v) {
(void)js;
ant_symbol_heap_t *ptr = sym_ptr(v);
return ptr ? ptr->id : 0;
}
static inline uint32_t sym_get_flags(ant_t *js, ant_value_t v) {
(void)js;
ant_symbol_heap_t *ptr = sym_ptr(v);
return ptr ? ptr->flags : 0;
}
static inline uintptr_t sym_get_key_ptr(ant_t *js, ant_value_t v) {
(void)js;
ant_symbol_heap_t *ptr = sym_ptr(v);
return ptr ? (uintptr_t)ptr->key : 0;
}
static const char *sym_get_desc(ant_t *js, ant_value_t v) {
(void)js;
ant_symbol_heap_t *ptr = sym_ptr(v);
if (!ptr || ptr->desc_len == 0) return NULL;
return ptr->desc;
}
ant_value_t js_mksym_for(ant_t *js, const char *key) {
const char *interned = intern_string(key, strlen(key));
sym_registry_entry_t *reg = js->sym.registry;
sym_registry_entry_t *found = NULL;
HASH_FIND_PTR(reg, &interned, found);
if (found) return found->sym;
ant_value_t sym = js_mksym(js, key);
if (is_err(sym)) return sym;
ant_symbol_heap_t *ptr = sym_ptr(sym);
if (!ptr) return js_mkerr(js, "oom");
ptr->flags |= SYM_FLAG_GLOBAL;
ptr->key = interned;
sym_registry_entry_t *entry = ant_calloc(sizeof(sym_registry_entry_t));
if (entry) {
entry->key = interned;
entry->sym = sym;
HASH_ADD_PTR(reg, key, entry);
js->sym.registry = reg;
}
return sym;
}
const char *js_sym_key(ant_value_t sym) {
if (vtype(sym) != T_SYMBOL) return NULL;
ant_t *js = rt->js;
uint32_t flags = sym_get_flags(js, sym);
if (!(flags & SYM_FLAG_GLOBAL) || (flags & SYM_FLAG_WELL_KNOWN)) return NULL;
return (const char *)sym_get_key_ptr(js, sym);
}
const inline char *js_sym_desc(ant_t *js, ant_value_t sym) {
return sym_get_desc(js, sym);
}
static inline bool streq(const char *buf, size_t len, const char *s, size_t n) {
return len == n && !memcmp(buf, s, n);
}
ant_offset_t lkp_interned(ant_t *js, ant_value_t obj, const char *search_intern, size_t len) {
obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!search_intern || !ptr || !ptr->shape) return 0;
int32_t shape_slot = ant_shape_lookup_interned(ptr->shape, search_intern);
(void)len;
if (shape_slot < 0) return 0;
return propref_make(js, ptr, (uint32_t)shape_slot);
}
inline ant_offset_t lkp(ant_t *js, ant_value_t obj, const char *buf, size_t len) {
const char *search_intern = intern_string(buf, len);
if (!search_intern) return 0;
return lkp_interned(js, obj, search_intern, len);
}
inline ant_value_t lkp_interned_val(ant_t *js, ant_value_t obj, const char *search_intern) {
obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!search_intern || !ptr || !ptr->shape) return js_mkundef();
int32_t slot = ant_shape_lookup_interned(ptr->shape, search_intern);
if (slot < 0) return js_mkundef();
return ant_object_prop_get_unchecked(ptr, (uint32_t)slot);
}
static inline ant_value_t lkp_val(ant_t *js, ant_value_t obj, const char *buf, size_t len) {
const char *interned = intern_string(buf, len);
if (!interned) return js_mkundef();
return lkp_interned_val(js, obj, interned);
}
ant_offset_t lkp_sym(ant_t *js, ant_value_t obj, ant_offset_t sym_off) {
obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr || !ptr->shape) return 0;
int32_t slot = ant_shape_lookup_symbol(ptr->shape, sym_off);
if (slot < 0) return 0;
return propref_make(js, ptr, (uint32_t)slot);
}
ant_offset_t lkp_sym_proto(ant_t *js, ant_value_t obj, ant_offset_t sym_off) {
ant_value_t cur = obj;
proto_overflow_guard_t guard;
proto_overflow_guard_init(&guard);
while (is_object_type(cur)) {
obj = cur;
ant_offset_t off = lkp_sym(js, obj, sym_off);
if (off != 0) return off;
ant_value_t proto = get_proto(js, js_as_obj(cur));
if (!is_object_type(proto)) break;
cur = proto;
if (proto_overflow_guard_hit_cycle(js, &guard, cur)) break;
}
return 0;
}
static inline ant_value_t lkp_sym_proto_val(ant_t *js, ant_value_t obj, ant_offset_t sym_off) {
ant_value_t cur = obj;
proto_overflow_guard_t guard;
proto_overflow_guard_init(&guard);
while (is_object_type(cur)) {
ant_value_t as_obj = js_as_obj(cur);
ant_object_t *ptr = js_obj_ptr(as_obj);
if (ptr && ptr->shape) {
int32_t slot = ant_shape_lookup_symbol(ptr->shape, sym_off);
if (slot >= 0) return ant_object_prop_get_unchecked(ptr, (uint32_t)slot);
}
ant_value_t proto = get_proto(js, as_obj);
if (!is_object_type(proto)) break;
cur = proto;
if (proto_overflow_guard_hit_cycle(js, &guard, cur)) break;
}
return js_mkundef();
}
static uintptr_t lkp_with_getter(ant_t *js, ant_value_t obj, const char *buf, size_t len, ant_value_t *getter_out, bool *has_getter_out) {
*has_getter_out = false;
*getter_out = js_mkundef();
const char *search_intern = intern_string(buf, len);
ant_value_t current = obj;
proto_overflow_guard_t guard;
proto_overflow_guard_init(&guard);
while (is_object_type(current)) {
current = js_as_obj(current);
uintptr_t current_id = (uintptr_t)vdata(current);
ant_object_t *ptr = js_obj_ptr(current);
if (ptr && ptr->shape && search_intern) {
int32_t slot = ant_shape_lookup_interned(ptr->shape, search_intern);
if (slot >= 0) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, (uint32_t)slot);
if (prop && prop->has_getter) {
*getter_out = prop->getter;
*has_getter_out = true;
return current_id;
}
return propref_make(js, ptr, (uint32_t)slot);
}
}
if (ptr && ptr->is_exotic) {
descriptor_entry_t *desc = lookup_descriptor(current, buf, len);
if (desc && desc->has_getter) {
*getter_out = desc->getter;
*has_getter_out = true;
return current_id;
}
}
ant_value_t proto = get_proto(js, current);
if (!is_object_type(proto)) break;
current = proto;
if (proto_overflow_guard_hit_cycle(js, &guard, current)) break;
}
return 0;
}
static uintptr_t lkp_with_setter(ant_t *js, ant_value_t obj, const char *buf, size_t len, ant_value_t *setter_out, bool *has_setter_out) {
*has_setter_out = false;
*setter_out = js_mkundef();
const char *search_intern = intern_string(buf, len);
ant_value_t current = obj;
proto_overflow_guard_t guard;
proto_overflow_guard_init(&guard);
while (vtype(current) == T_OBJ || vtype(current) == T_FUNC) {
current = js_as_obj(current);
uintptr_t current_id = (uintptr_t)vdata(current);
ant_object_t *ptr = js_obj_ptr(current);
if (ptr && ptr->shape && search_intern) {
int32_t slot = ant_shape_lookup_interned(ptr->shape, search_intern);
if (slot >= 0) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, (uint32_t)slot);
if (prop && prop->has_setter) {
*setter_out = prop->setter;
*has_setter_out = true;
return current_id;
}
return propref_make(js, ptr, (uint32_t)slot);
}
}
if (ptr && ptr->is_exotic) {
descriptor_entry_t *desc = lookup_descriptor(current, buf, len);
if (desc && desc->has_setter) {
*setter_out = desc->setter;
*has_setter_out = true;
return current_id;
}
}
ant_value_t proto = get_proto(js, current);
if (vtype(proto) != T_OBJ && vtype(proto) != T_FUNC) break;
current = proto;
if (proto_overflow_guard_hit_cycle(js, &guard, current)) break;
}
return 0;
}
static ant_value_t call_proto_accessor(ant_t *js, ant_value_t prim, ant_value_t accessor, bool has_accessor, ant_value_t *arg, int arg_count, bool is_setter) {
if (!has_accessor || (vtype(accessor) != T_FUNC && vtype(accessor) != T_CFUNC)) return js_mkundef();
js_error_site_t saved_errsite = js->errsite;
ant_value_t result = sv_vm_call(js->vm, js, accessor, prim, arg, arg_count, NULL, false);
bool had_throw = js->thrown_exists;
ant_value_t thrown = js->thrown_value;
js->errsite = saved_errsite;
if (had_throw) {
js->thrown_exists = true;
js->thrown_value = thrown;
}
if (is_setter) return is_err(result) ? result : (arg ? *arg : js_mkundef());
return result;
}
ant_value_t js_get_proto(ant_t *js, ant_value_t obj) {
uint8_t t = vtype(obj);
if (!is_object_type(obj)) return js_mknull();
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
ant_value_t proto = ptr ? ptr->proto : js_mkundef();
if (is_object_type(proto)) return proto;
if (t != T_OBJ) return get_prototype_for_type(js, t);
return js_mknull();
}
static ant_value_t get_proto(ant_t *js, ant_value_t obj) {
return js_get_proto(js, obj);
}
void js_set_proto(ant_value_t obj, ant_value_t proto) {
if (!is_object_type(obj)) return;
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
if (!ptr) return;
ptr->proto = proto;
ant_ic_epoch_bump();
}
void js_set_proto_init(ant_value_t obj, ant_value_t proto) {
if (!is_object_type(obj)) return;
ant_object_t *ptr = js_obj_ptr(js_as_obj(obj));
if (!ptr) return;
ptr->proto = proto;
}
static void set_proto(ant_t *js, ant_value_t obj, ant_value_t proto) {
ant_object_t *ptr = js_obj_ptr(js_as_obj(obj));
js_set_proto(obj, proto);
if (ptr) gc_write_barrier(js, ptr, proto);
}
void js_set_proto_wb(ant_t *js, ant_value_t obj, ant_value_t proto) {
set_proto(js, obj, proto);
}
ant_value_t js_get_ctor_proto(ant_t *js, const char *name, size_t len) {
const char *interned = intern_string(name, len);
ant_value_t ctor = lkp_interned_val(js, js->global, interned);
if (vtype(ctor) != T_FUNC) return js_mknull();
ant_value_t ctor_obj = js_as_obj(ctor);
ant_value_t proto = lkp_interned_val(js, ctor_obj, INTERN_PROTOTYPE);
return vtype(proto) == T_UNDEF ? js_mknull() : proto;
}
static inline ant_value_t get_ctor_proto(ant_t *js, const char *name, size_t len) {
return js_get_ctor_proto(js, name, len);
}
static ant_value_t get_prototype_for_type(ant_t *js, uint8_t type) {
switch (type) {
case T_STR: return get_ctor_proto(js, "String", 6);
case T_NUM: return get_ctor_proto(js, "Number", 6);
case T_BOOL: return get_ctor_proto(js, "Boolean", 7);
case T_ARR: return get_ctor_proto(js, "Array", 5);
case T_FUNC: return get_ctor_proto(js, "Function", 8);
case T_PROMISE: return get_ctor_proto(js, "Promise", 7);
case T_OBJ: return get_ctor_proto(js, "Object", 6);
case T_BIGINT: return get_ctor_proto(js, "BigInt", 6);
case T_SYMBOL: return get_ctor_proto(js, "Symbol", 6);
default: return js_mknull();
}}
ant_offset_t lkp_proto(ant_t *js, ant_value_t obj, const char *key, size_t len) {
uint8_t t = vtype(obj);
const char *key_intern = intern_string(key, len);
if (!key_intern) return 0;
ant_value_t cur = obj;
proto_walk_overflow_guard_t guard;
proto_walk_overflow_guard_init(&guard);
while (true) {
if (t == T_OBJ || t == T_ARR || t == T_FUNC || t == T_PROMISE) {
ant_value_t as_obj = js_as_obj(cur);
ant_offset_t off = lkp_interned(js, as_obj, key_intern, len);
if (off != 0) return off;
} else if (t == T_CFUNC) {
ant_value_t func_proto = get_ctor_proto(js, "Function", 8);
uint8_t ft = vtype(func_proto);
if (ft == T_OBJ || ft == T_ARR || ft == T_FUNC) {
ant_offset_t off = lkp(js, js_as_obj(func_proto), key, len);
if (off != 0) return off;
}
break;
} else if (t != T_STR && t != T_NUM && t != T_BOOL && t != T_BIGINT && t != T_SYMBOL) break;
if (!proto_walk_next(js, &cur, &t, PROTO_WALK_F_LOOKUP)) break;
if (proto_walk_overflow_guard_hit_cycle(js, &guard, cur, t, PROTO_WALK_F_LOOKUP)) break;
}
return 0;
}
static ant_value_t js_string_from_utf16_code_unit(ant_t *js, uint32_t code_unit) {
char buf[4];
size_t out_len = 0;
if (code_unit >= 0xD800 && code_unit <= 0xDFFF) {
buf[0] = (char)(0xE0 | (code_unit >> 12));
buf[1] = (char)(0x80 | ((code_unit >> 6) & 0x3F));
buf[2] = (char)(0x80 | (code_unit & 0x3F));
out_len = 3;
} else out_len = (size_t)utf8_encode(code_unit, buf);
return js_mkstr(js, buf, out_len);
}
static bool js_try_get_string_index(
ant_t *js, ant_value_t str,
const char *key, size_t key_len, ant_value_t *out
) {
if (!is_array_index(key, (ant_offset_t)key_len)) return false;
unsigned long idx = 0;
ant_offset_t byte_len = 0;
ant_offset_t str_off = vstr(js, str, &byte_len);
const char *str_data = (const char *)(uintptr_t)(str_off);
ant_offset_t str_len = (ant_offset_t)utf16_strlen(str_data, byte_len);
if (!parse_array_index(key, key_len, str_len, &idx)) return false;
uint32_t code_unit = utf16_code_unit_at(str_data, byte_len, (ant_offset_t)idx);
if (code_unit == 0xFFFFFFFF) return false;
*out = js_string_from_utf16_code_unit(js, code_unit);
return true;
}
static ant_value_t getprop_any(ant_t *js, ant_value_t obj, const char *key, size_t key_len) {
uint8_t t = vtype(obj);
if (t == T_STR && is_length_key(key, key_len)) {
ant_offset_t byte_len;
ant_offset_t str_off = vstr(js, obj, &byte_len);
return tov(D(utf16_strlen((const char *)(uintptr_t)(str_off), byte_len)));
}
if (t == T_STR) {
ant_value_t indexed = js_mkundef();
if (js_try_get_string_index(js, obj, key, key_len, &indexed)) return indexed;
}
if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) {
ant_offset_t off = lkp_proto(js, obj, key, key_len);
if (off != 0) return propref_load(js, off);
return js_mkundef();
}
if (t == T_OBJ || t == T_ARR || t == T_FUNC) {
ant_value_t as_obj = js_as_obj(obj);
ant_offset_t off = lkp(js, as_obj, key, key_len);
if (off != 0) return propref_load(js, off);
off = lkp_proto(js, obj, key, key_len);
if (off != 0) return propref_load(js, off);
}
return js_mkundef();
}
static ant_value_t try_dynamic_getter(ant_t *js, ant_value_t obj, const char *key, size_t key_len) {
ant_object_t *ptr = js_obj_ptr(js_as_obj(obj));
if (!ptr || !ptr->is_exotic) return js_mkundef();
if (!ptr->exotic_ops || !ptr->exotic_ops->getter) return js_mkundef();
return ptr->exotic_ops->getter(js, obj, key, key_len);
}
static bool try_dynamic_setter(ant_t *js, ant_value_t obj, const char *key, size_t key_len, ant_value_t value) {
ant_object_t *ptr = js_obj_ptr(js_as_obj(obj));
if (!ptr || !ptr->is_exotic) return false;
if (!ptr->exotic_ops || !ptr->exotic_ops->setter) return false;
return ptr->exotic_ops->setter(js, obj, key, key_len, value);
}
static bool try_dynamic_deleter(ant_t *js, ant_value_t obj, const char *key, size_t key_len) {
ant_object_t *ptr = js_obj_ptr(js_as_obj(obj));
if (!ptr || !ptr->is_exotic) return false;
if (!ptr->exotic_ops || !ptr->exotic_ops->deleter) return false;
return ptr->exotic_ops->deleter(js, obj, key, key_len);
}
static bool try_accessor_getter(ant_t *js, ant_value_t obj, const char *key, size_t key_len, ant_value_t *out) {
ant_value_t getter = js_mkundef();
bool has_getter = false;
lkp_with_getter(js, obj, key, key_len, &getter, &has_getter);
ant_value_t result = call_proto_accessor(js, obj, getter, has_getter, NULL, 0, false);
if (vtype(result) != T_UNDEF) {
*out = result;
return true;
}
return false;
}
static bool try_accessor_setter(ant_t *js, ant_value_t obj, const char *key, size_t key_len, ant_value_t val, ant_value_t *out) {
ant_value_t setter = js_mkundef();
bool has_setter = false;
lkp_with_setter(js, obj, key, key_len, &setter, &has_setter);
if (!has_setter) return false;
ant_value_t result = call_proto_accessor(js, obj, setter, has_setter, &val, 1, true);
if (is_err(result)) {
*out = result;
return true;
}
*out = val;
return true;
}
ant_value_t js_propref_load(ant_t *js, ant_offset_t handle) {
return propref_load(js, handle);
}
typedef struct {
char *buffer;
size_t capacity;
size_t size;
bool is_dynamic;
} string_builder_t;
static void string_builder_init(string_builder_t *sb, char *static_buf, size_t static_cap) {
sb->buffer = static_buf;
sb->capacity = static_cap;
sb->size = 0;
sb->is_dynamic = false;
}
static bool string_builder_append(string_builder_t *sb, const char *data, size_t len) {
if (sb->size + len > sb->capacity) {
size_t new_capacity = sb->capacity ? sb->capacity * 2 : 256;
while (new_capacity < sb->size + len) new_capacity *= 2;
char *new_buffer = (char *)ant_calloc(new_capacity);
if (!new_buffer) return false;
if (sb->size > 0) memcpy(new_buffer, sb->buffer, sb->size);
if (sb->is_dynamic) free(sb->buffer);
sb->buffer = new_buffer;
sb->capacity = new_capacity;
sb->is_dynamic = true;
}
if (len > 0) {
memcpy(sb->buffer + sb->size, data, len);
sb->size += len;
}
return true;
}
static ant_value_t string_builder_finalize(ant_t *js, string_builder_t *sb) {
ant_value_t result = js_mkstr(js, sb->buffer, sb->size);
if (sb->is_dynamic && sb->buffer) free(sb->buffer);
return result;
}
ant_offset_t str_len_fast(ant_t *js, ant_value_t str) {
if (vtype(str) != T_STR) return 0;
if (str_is_heap_rope(str)) return rope_len(str);
return assert_flat_string_len(js, str, NULL);
}
ant_value_t do_string_op(ant_t *js, uint8_t op, ant_value_t l, ant_value_t r) {
if (op == TOK_PLUS) {
ant_offset_t n1 = str_len_fast(js, l);
ant_offset_t n2 = str_len_fast(js, r);
ant_offset_t total_len = n1 + n2;
if (n2 == 0) return l;
if (n1 == 0) return r;
uint8_t left_depth = (vtype(l) == T_STR && str_is_heap_rope(l)) ? rope_depth(l) : 0;
uint8_t right_depth = (vtype(r) == T_STR && str_is_heap_rope(r)) ? rope_depth(r) : 0;
uint8_t new_depth = (left_depth > right_depth ? left_depth : right_depth) + 1;
if (new_depth >= ROPE_MAX_DEPTH || total_len >= ROPE_FLATTEN_THRESHOLD) {
ant_value_t flat_l = l, flat_r = r;
if (str_is_heap_rope(l)) flat_l = rope_flatten(js, l);
if (is_err(flat_l)) return flat_l;
if (str_is_heap_rope(r)) flat_r = rope_flatten(js, r);
if (is_err(flat_r)) return flat_r;
ant_offset_t off1, off2, len1, len2;
off1 = vstr(js, flat_l, &len1);
off2 = vstr(js, flat_r, &len2);
string_builder_t sb;
char static_buffer[512];
string_builder_init(&sb, static_buffer, sizeof(static_buffer));
if (
!string_builder_append(&sb, (char *)(uintptr_t)(off1), len1) ||
!string_builder_append(&sb, (char *)(uintptr_t)(off2), len2)
) return js_mkerr(js, "string concatenation failed");
return string_builder_finalize(js, &sb);
}
return js_mkrope(js, l, r, total_len, new_depth);
}
ant_offset_t n1, off1 = vstr(js, l, &n1);
ant_offset_t n2, off2 = vstr(js, r, &n2);
if (op == TOK_EQ) {
bool eq = n1 == n2 &&
memcmp((const void *)(uintptr_t)off1, (const void *)(uintptr_t)off2, n1) == 0;
return mkval(T_BOOL, eq ? 1 : 0);
} else if (op == TOK_NE) {
bool eq = n1 == n2 &&
memcmp((const void *)(uintptr_t)off1, (const void *)(uintptr_t)off2, n1) == 0;
return mkval(T_BOOL, eq ? 0 : 1);
} else if (op == TOK_LT || op == TOK_LE || op == TOK_GT || op == TOK_GE) {
ant_offset_t min_len = n1 < n2 ? n1 : n2;
int cmp = memcmp((const void *)(uintptr_t)off1, (const void *)(uintptr_t)off2, min_len);
if (cmp == 0) {
if (n1 == n2) {
return mkval(T_BOOL, (op == TOK_LE || op == TOK_GE) ? 1 : 0);
} else cmp = (n1 < n2) ? -1 : 1;
}
switch (op) {
case TOK_LT: return mkval(T_BOOL, cmp < 0 ? 1 : 0);
case TOK_LE: return mkval(T_BOOL, cmp <= 0 ? 1 : 0);
case TOK_GT: return mkval(T_BOOL, cmp > 0 ? 1 : 0);
case TOK_GE: return mkval(T_BOOL, cmp >= 0 ? 1 : 0);
default: return js_mkerr(js, "bad str op");
}
} else return js_mkerr(js, "bad str op");
}
typedef enum { ITER_CONTINUE, ITER_BREAK, ITER_ERROR } iter_action_t;
typedef iter_action_t (*iter_callback_t)(ant_t *js, ant_value_t value, void *ctx, ant_value_t *out);
static ant_value_t iter_foreach(ant_t *js, ant_value_t iterable, iter_callback_t cb, void *ctx);
static bool js_try_call_method(ant_t *js, ant_value_t obj, const char *method, size_t method_len, ant_value_t *args, int nargs, ant_value_t *out_result) {
ant_value_t getter = js_mkundef(); bool has_getter = false;
uintptr_t off = lkp_with_getter(js, obj, method, method_len, &getter, &has_getter);
ant_value_t fn;
if (has_getter) {
fn = call_proto_accessor(js, obj, getter, true, NULL, 0, false);
if (is_err(fn)) { *out_result = fn; return true; }
} else if (off != 0) {
fn = propref_load(js, (ant_offset_t)off);
} else return false;
uint8_t ft = vtype(fn);
if (ft != T_FUNC && ft != T_CFUNC) return false;
ant_value_t saved_this = js->this_val;
js->this_val = obj;
ant_value_t result;
if (ft == T_CFUNC) result = ((ant_value_t (*)(ant_t *, ant_value_t *, int))vdata(fn))(js, args, nargs);
else result = sv_vm_call(js->vm, js, fn, obj, args, nargs, NULL, false);
bool had_throw = js->thrown_exists;
ant_value_t thrown = js->thrown_value;
js->this_val = saved_this;
if (had_throw) {
js->thrown_exists = true;
js->thrown_value = thrown;
}
*out_result = result;
return true;
}
static ant_value_t js_call_method(ant_t *js, ant_value_t obj, const char *method, size_t method_len, ant_value_t *args, int nargs) {
ant_value_t result;
if (!js_try_call_method(js, obj, method, method_len, args, nargs, &result)) return js_mkundef();
return result;
}
static ant_value_t js_call_toString(ant_t *js, ant_value_t value) {
ant_value_t result = js_call_method(js, value, "toString", 8, NULL, 0);
if (is_err(result)) return result;
if (vtype(result) == T_STR) return result;
uint8_t rtype = vtype(result);
if (rtype == T_UNDEF) {
goto fallback;
}
if (rtype != T_OBJ && rtype != T_ARR && rtype != T_FUNC) {
char buf[256];
size_t len = tostr(js, result, buf, sizeof(buf));
return js_mkstr(js, buf, len);
}
fallback:;
char buf[4096];
size_t len = tostr(js, value, buf, sizeof(buf));
return js_mkstr(js, buf, len);
}
static ant_value_t js_call_valueOf(ant_t *js, ant_value_t value) {
ant_value_t result = js_call_method(js, value, "valueOf", 7, NULL, 0);
if (vtype(result) == T_UNDEF) return value;
return result;
}
static inline bool is_primitive(ant_value_t v) {
uint8_t t = vtype(v);
return t == T_STR || t == T_NUM || t == T_BOOL || t == T_NULL || t == T_UNDEF || t == T_SYMBOL || t == T_BIGINT;
}
static ant_value_t try_exotic_to_primitive(ant_t *js, ant_value_t value, int hint) {
ant_value_t tp_sym = get_toPrimitive_sym();
if (vtype(tp_sym) != T_SYMBOL) return mkval(T_UNDEF, 0);
ant_offset_t tp_off = lkp_sym_proto(js, value, (ant_offset_t)vdata(tp_sym));
if (tp_off == 0) return mkval(T_UNDEF, 0);
ant_value_t tp_fn = propref_load(js, tp_off);
uint8_t ft = vtype(tp_fn);
if (ft == T_UNDEF) return mkval(T_UNDEF, 0);
if (ft != T_FUNC && ft != T_CFUNC) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol.toPrimitive is not a function");
}
const char *hint_str = hint == 1 ? "string" : (hint == 2 ? "number" : "default");
ant_value_t hint_arg = js_mkstr(js, hint_str, strlen(hint_str));
ant_value_t result = sv_vm_call(js->vm, js, tp_fn, value, &hint_arg, 1, NULL, false);
if (is_err(result) || is_primitive(result)) return result;
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert object to primitive value");
}
static ant_value_t try_ordinary_to_primitive(ant_t *js, ant_value_t value, int hint) {
static const char *names[] = {"valueOf", "toString"};
static const size_t lens[] = {7, 8};
int first = (hint == 1);
ant_value_t result;
for (int i = 0; i < 2; i++) {
int idx = first ^ i;
if (js_try_call_method(js, value, names[idx], lens[idx], NULL, 0, &result))
if (is_err(result) || is_primitive(result)) return result;
}
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert object to primitive value");
}
ant_value_t js_to_primitive(ant_t *js, ant_value_t value, int hint) {
if (is_primitive(value)) return value;
if (!is_object_type(value)) return value;
ant_value_t result = try_exotic_to_primitive(js, value, hint);
if (vtype(result) != T_UNDEF) return result;
return try_ordinary_to_primitive(js, value, hint);
}
bool strict_eq_values(ant_t *js, ant_value_t l, ant_value_t r) {
uint8_t t = vtype(l);
if (t != vtype(r)) return false;
if (t == T_STR) {
ant_offset_t n1, n2, off1 = vstr(js, l, &n1), off2 = vstr(js, r, &n2);
return n1 == n2 &&
memcmp((const void *)(uintptr_t)off1, (const void *)(uintptr_t)off2, n1) == 0;
}
if (t == T_NUM) return tod(l) == tod(r);
if (t == T_BIGINT) return bigint_compare(js, l, r) == 0;
return vdata(l) == vdata(r);
}
ant_value_t coerce_to_str(ant_t *js, ant_value_t v) {
if (vtype(v) == T_STR) return v;
if (is_object_type(v)) {
ant_value_t prim = js_to_primitive(js, v, 1);
if (is_err(prim)) return prim;
if (vtype(prim) == T_STR) return prim;
return js_tostring_val(js, prim);
}
return js_tostring_val(js, v);
}
ant_value_t coerce_to_str_concat(ant_t *js, ant_value_t v) {
if (vtype(v) == T_STR) return v;
if (is_object_type(v)) {
ant_value_t prim = js_to_primitive(js, v, 0);
if (is_err(prim)) return prim;
if (vtype(prim) == T_STR) return prim;
return js_tostring_val(js, prim);
}
return js_tostring_val(js, v);
}
static ant_value_t check_frozen_sealed(ant_t *js, ant_value_t obj, const char *action) {
ant_object_t *ptr = js_obj_ptr(js_as_obj(obj));
if (!ptr) return js_mkundef();
if (ptr->frozen) {
if (sv_vm_is_strict(js->vm)) return js_mkerr(js, "cannot %s property of frozen object", action);
return js_false;
}
if (ptr->sealed) {
if (sv_vm_is_strict(js->vm)) return js_mkerr(js, "cannot %s property of sealed object", action);
return js_false;
}
return js_mkundef();
}
ant_value_t js_delete_prop(ant_t *js, ant_value_t obj, const char *key, size_t len) {
ant_value_t original_obj = obj;
obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr) return js_true;
if (is_proxy(obj)) {
ant_value_t result = proxy_delete(js, obj, key, len);
return is_err(result) ? result : js_bool(js_truthy(js, result));
}
ant_value_t err = check_frozen_sealed(js, obj, "delete");
if (vtype(err) != T_UNDEF) return err;
if (array_obj_ptr(original_obj) && is_length_key(key, len)) {
if (sv_vm_is_strict(js->vm)) return js_mkerr_typed(js, JS_ERR_TYPE, "cannot delete non-configurable property");
return js_false;
}
if (array_obj_ptr(original_obj)) {
ant_offset_t doff = get_dense_buf(original_obj);
unsigned long del_idx = 0;
if (doff && parse_array_index(key, len, get_array_length(js, original_obj), &del_idx)) {
ant_offset_t dense_len = dense_iterable_length(js, original_obj);
if ((ant_offset_t)del_idx < dense_len) dense_set(js, doff, (ant_offset_t)del_idx, T_EMPTY);
}
}
const char *interned = intern_string(key, len);
if (!interned) {
try_dynamic_deleter(js, obj, key, len);
return js_true;
}
int32_t shape_slot = ant_shape_lookup_interned(ptr->shape, interned);
if (shape_slot < 0) {
try_dynamic_deleter(js, obj, key, len);
return js_true;
}
uint8_t attrs = ant_shape_get_attrs(ptr->shape, (uint32_t)shape_slot);
if ((attrs & ANT_PROP_ATTR_CONFIGURABLE) == 0) {
if (sv_vm_is_strict(js->vm)) return js_mkerr_typed(js, JS_ERR_TYPE, "cannot delete non-configurable property");
return js_false;
}
if (ptr->is_exotic) {
descriptor_entry_t *desc = lookup_descriptor(obj, key, len);
if (desc && !desc->configurable) {
if (sv_vm_is_strict(js->vm)) return js_mkerr_typed(js, JS_ERR_TYPE, "cannot delete non-configurable property");
return js_false;
}
}
uint32_t slot = (uint32_t)shape_slot;
if (!js_obj_ensure_unique_shape(ptr)) return js_mkerr(js, "oom");
uint32_t swapped_from = slot;
if (!ant_shape_remove_slot(ptr->shape, slot, &swapped_from)) return js_true;
obj_remove_prop_slot(ptr, slot);
propref_adjust_after_swap_delete(js, ptr, slot, swapped_from);
return js_true;
}
ant_value_t js_delete_sym_prop(ant_t *js, ant_value_t obj, ant_value_t sym) {
obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr) return js_true;
if (is_proxy(obj)) {
ant_value_t result = proxy_delete_val(js, obj, sym);
return is_err(result) ? result : js_bool(js_truthy(js, result));
}
ant_value_t err = check_frozen_sealed(js, obj, "delete");
if (vtype(err) != T_UNDEF) return err;
ant_offset_t sym_off = (ant_offset_t)vdata(sym);
int32_t shape_slot = ant_shape_lookup_symbol(ptr->shape, sym_off);
if (shape_slot < 0) return js_true;
uint8_t attrs = ant_shape_get_attrs(ptr->shape, (uint32_t)shape_slot);
if ((attrs & ANT_PROP_ATTR_CONFIGURABLE) == 0) {
if (sv_vm_is_strict(js->vm)) return js_mkerr_typed(js, JS_ERR_TYPE, "cannot delete non-configurable property");
return js_false;
}
uint32_t slot = (uint32_t)shape_slot;
if (!js_obj_ensure_unique_shape(ptr)) return js_mkerr(js, "oom");
uint32_t swapped_from = slot;
if (!ant_shape_remove_slot(ptr->shape, slot, &swapped_from)) return js_true;
obj_remove_prop_slot(ptr, slot);
propref_adjust_after_swap_delete(js, ptr, slot, swapped_from);
return js_true;
}
static ant_value_t iter_call_noargs_with_this(ant_t *js, ant_value_t this_val, ant_value_t method) {
ant_value_t result = sv_vm_call(js->vm, js, method, this_val, NULL, 0, NULL, false);
return result;
}
static ant_value_t iter_close_iterator(ant_t *js, ant_value_t iterator) {
ant_offset_t return_off = lkp_proto(js, iterator, "return", 6);
if (return_off == 0) return js_mkundef();
ant_value_t return_method = propref_load(js, return_off);
if (vtype(return_method) != T_FUNC && vtype(return_method) != T_CFUNC) {
return js_mkerr(js, "iterator.return is not a function");
}
return iter_call_noargs_with_this(js, iterator, return_method);
}
static ant_value_t iter_foreach(ant_t *js, ant_value_t iterable, iter_callback_t cb, void *ctx) {
ant_value_t iter_sym = get_iterator_sym();
ant_offset_t iter_prop = (vtype(iter_sym) == T_SYMBOL) ? lkp_sym_proto(js, iterable, (ant_offset_t)vdata(iter_sym)) : 0;
if (iter_prop == 0) return js_mkerr(js, "not iterable");
ant_value_t iter_method = propref_load(js, iter_prop);
ant_value_t iterator = iter_call_noargs_with_this(js, iterable, iter_method);
if (is_err(iterator)) return iterator;
ant_value_t out = js_mkundef();
while (true) {
ant_offset_t next_off = lkp_proto(js, iterator, "next", 4);
if (next_off == 0) { return js_mkerr(js, "iterator.next is not a function"); }
ant_value_t next_method = propref_load(js, next_off);
if (vtype(next_method) != T_FUNC && vtype(next_method) != T_CFUNC) {
return js_mkerr(js, "iterator.next is not a function");
}
ant_value_t result = iter_call_noargs_with_this(js, iterator, next_method);
if (is_err(result)) { return result; }
ant_offset_t done_off = lkp(js, result, "done", 4);
ant_value_t done_val = done_off ? propref_load(js, done_off) : js_mkundef();
if (js_truthy(js, done_val)) break;
ant_offset_t value_off = lkp(js, result, "value", 5);
ant_value_t value = value_off ? propref_load(js, value_off) : js_mkundef();
iter_action_t action = cb(js, value, ctx, &out);
if (action == ITER_BREAK) {
ant_value_t close_result = iter_close_iterator(js, iterator);
if (is_err(close_result)) { return close_result; }
break;
}
if (action == ITER_ERROR) {
ant_value_t close_result = iter_close_iterator(js, iterator);
if (is_err(close_result)) return close_result;
return out;
}
}
return out;
}
ant_value_t js_symbol_to_string(ant_t *js, ant_value_t sym) {
const char *desc = js_sym_desc(js, sym);
if (!desc) return js_mkstr(js, "Symbol()", 8);
size_t desc_len = strlen(desc);
size_t total = 7 + desc_len + 1;
char stack_buf[128];
char *buf = (total + 1 <= sizeof(stack_buf)) ? stack_buf : malloc(total + 1);
if (!buf) return js_mkerr(js, "out of memory");
memcpy(buf, "Symbol(", 7);
memcpy(buf + 7, desc, desc_len);
buf[7 + desc_len] = ')';
buf[total] = '\0';
ant_value_t result = js_mkstr(js, buf, total);
if (buf != stack_buf) free(buf);
return result;
}
static ant_value_t builtin_String(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t sval;
if (nargs == 0) sval = js_mkstr(js, "", 0);
else if (vtype(args[0]) == T_STR) sval = args[0];
else if (vtype(args[0]) == T_SYMBOL) {
sval = js_symbol_to_string(js, args[0]);
if (is_err(sval)) return sval;
} else {
sval = coerce_to_str(js, args[0]);
if (is_err(sval)) return sval;
}
ant_value_t string_proto = js_get_ctor_proto(js, "String", 6);
if (is_wrapper_ctor_target(js, js->this_val, string_proto)) {
set_slot(js->this_val, SLOT_PRIMITIVE, sval);
ant_offset_t byte_len;
ant_offset_t str_off = vstr(js, sval, &byte_len);
const char *str_data = (const char *)(uintptr_t)(str_off);
js_setprop(js, js->this_val, js->length_str, tov((double)utf16_strlen(str_data, byte_len)));
js_set_descriptor(js, js_as_obj(js->this_val), "length", 6, 0);
}
return sval;
}
static ant_value_t builtin_Number_isNaN(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkval(T_BOOL, 0);
ant_value_t arg = args[0];
if (vtype(arg) != T_NUM) return mkval(T_BOOL, 0);
double val = tod(arg);
return mkval(T_BOOL, isnan(val) ? 1 : 0);
}
static ant_value_t builtin_Number_isFinite(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkval(T_BOOL, 0);
ant_value_t arg = args[0];
if (vtype(arg) != T_NUM) return mkval(T_BOOL, 0);
double val = tod(arg);
return mkval(T_BOOL, isfinite(val) ? 1 : 0);
}
static ant_value_t builtin_global_isNaN(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkval(T_BOOL, 1);
double val = js_to_number(js, args[0]);
return mkval(T_BOOL, isnan(val) ? 1 : 0);
}
static ant_value_t builtin_global_isFinite(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkval(T_BOOL, 0);
double val = js_to_number(js, args[0]);
return mkval(T_BOOL, isfinite(val) ? 1 : 0);
}
static ant_value_t builtin_eval(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_mkundef();
ant_value_t code = args[0];
if (vtype(code) != T_STR) return code;
ant_offset_t code_len = 0;
ant_offset_t code_off = vstr(js, code, &code_len);
const char *code_str = (const char *)(uintptr_t)(code_off);
return js_eval_bytecode_eval_with_strict(js, code_str, (size_t)code_len, false);
}
static ant_value_t builtin_Number_isInteger(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkval(T_BOOL, 0);
ant_value_t arg = args[0];
if (vtype(arg) != T_NUM) return mkval(T_BOOL, 0);
double val = tod(arg);
if (!isfinite(val)) return mkval(T_BOOL, 0);
return mkval(T_BOOL, (val == floor(val)) ? 1 : 0);
}
static ant_value_t builtin_Number_isSafeInteger(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkval(T_BOOL, 0);
ant_value_t arg = args[0];
if (vtype(arg) != T_NUM) return mkval(T_BOOL, 0);
double val = tod(arg);
if (!isfinite(val)) return mkval(T_BOOL, 0);
if (val != floor(val)) return mkval(T_BOOL, 0);
return mkval(T_BOOL, (val >= -9007199254740991.0 && val <= 9007199254740991.0) ? 1 : 0);
}
static ant_value_t builtin_Number(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t nval = tov(nargs > 0 ? js_to_number(js, args[0]) : 0.0);
ant_value_t number_proto = js_get_ctor_proto(js, "Number", 6);
if (is_wrapper_ctor_target(js, js->this_val, number_proto)) {
set_slot(js->this_val, SLOT_PRIMITIVE, nval);
}
return nval;
}
static ant_value_t builtin_Boolean(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t bval = mkval(T_BOOL, nargs > 0 && js_truthy(js, args[0]) ? 1 : 0);
ant_value_t boolean_proto = js_get_ctor_proto(js, "Boolean", 7);
if (is_wrapper_ctor_target(js, js->this_val, boolean_proto)) {
set_slot(js->this_val, SLOT_PRIMITIVE, bval);
}
return bval;
}
static ant_value_t builtin_Object(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0 || vtype(args[0]) == T_NULL || vtype(args[0]) == T_UNDEF) {
ant_value_t obj_proto = js_get_ctor_proto(js, "Object", 6);
if (is_unboxed_obj(js, js->this_val, obj_proto)) return js->this_val;
return js_mkobj(js);
}
ant_value_t arg = args[0];
uint8_t t = vtype(arg);
if (t == T_OBJ || t == T_ARR || t == T_FUNC) return arg;
if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) {
ant_value_t wrapper = js_mkobj(js);
if (is_err(wrapper)) return wrapper;
set_slot(wrapper, SLOT_PRIMITIVE, arg);
ant_value_t proto = get_prototype_for_type(js, t);
if (vtype(proto) == T_OBJ) js_set_proto_init(wrapper, proto);
return wrapper;
}
return arg;
}
static ant_value_t builtin_function_empty(ant_t *, ant_value_t *, int);
static ant_value_t build_dynamic_function(ant_t *js, ant_value_t *args, int nargs, bool is_async) {
if (nargs == 0) {
ant_value_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) return func_obj;
set_func_code_ptr(js, func_obj, "(){}", 4);
if (is_async) {
set_slot(func_obj, SLOT_ASYNC, js_true);
ant_value_t async_proto = get_slot(js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) js_set_proto_init(func_obj, async_proto);
} else {
ant_value_t func_proto = get_slot(js_glob(js), SLOT_FUNC_PROTO);
ant_value_t instance_proto = js_instance_proto_from_new_target(js, func_proto);
if (is_object_type(instance_proto)) js_set_proto_init(func_obj, instance_proto);
}
set_slot(func_obj, SLOT_CFUNC, js_mkfun(builtin_function_empty));
ant_value_t func = js_obj_to_func(func_obj);
ant_value_t proto_setup = setup_func_prototype(js, func);
if (is_err(proto_setup)) return proto_setup;
return func;
}
size_t total_len = 1;
for (int i = 0; i < nargs - 1; i++) {
args[i] = coerce_to_str(js, args[i]);
if (is_err(args[i])) return args[i];
total_len += vstrlen(js, args[i]);
if (i < nargs - 2) total_len += 1;
}
total_len += 2;
ant_value_t body = coerce_to_str(js, args[nargs - 1]);
if (is_err(body)) return body;
total_len += vstrlen(js, body);
total_len += 1;
char *code_buf = (char *)malloc(total_len + 1);
if (!code_buf) return js_mkerr(js, "oom");
size_t pos = 0;
code_buf[pos++] = '(';
for (int i = 0; i < nargs - 1; i++) {
ant_offset_t param_len, param_off = vstr(js, args[i], &param_len);
memcpy(code_buf + pos, (const void *)(uintptr_t)param_off, param_len);
pos += param_len;
if (i < nargs - 2) code_buf[pos++] = ',';
}
code_buf[pos++] = ')';
code_buf[pos++] = '{';
ant_offset_t body_len, body_off = vstr(js, body, &body_len);
memcpy(code_buf + pos, (const void *)(uintptr_t)body_off, body_len);
pos += body_len;
code_buf[pos++] = '}';
code_buf[pos] = '\0';
ant_value_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) { free(code_buf); return func_obj; }
sv_func_t *compiled = sv_compile_function(js, code_buf, pos, is_async);
if (!compiled) {
free(code_buf);
return js_mkerr_typed(js, JS_ERR_SYNTAX, "invalid function body");
}
sv_closure_t *closure = js_closure_alloc(js);
if (!closure) { free(code_buf); return js_mkerr(js, "oom"); }
closure->func = compiled;
closure->bound_this = js_mkundef();
closure->call_flags = 0;
closure->func_obj = func_obj;
size_t params_len = (size_t)(pos - 2) - (size_t)body_len - 2;
const char *async_prefix = is_async ? "async " : "";
size_t async_len = is_async ? 6 : 0;
size_t display_len = async_len + 19 + params_len + 5 + (size_t)body_len + 2;
char *display = (char *)malloc(display_len + 1);
if (!display) { free(code_buf); return js_mkerr(js, "oom"); }
size_t n = 0;
memcpy(display + n, async_prefix, async_len); n += async_len;
memcpy(display + n, "function anonymous(", 19); n += 19;
memcpy(display + n, code_buf + 1, params_len); n += params_len;
memcpy(display + n, "\n) {\n", 5); n += 5;
memcpy(display + n, code_buf + 1 + params_len + 2, (size_t)body_len); n += (size_t)body_len;
memcpy(display + n, "\n}", 2); n += 2;
display[n] = '\0';
set_func_code(js, func_obj, display, display_len);
free(display);
free(code_buf);
if (is_async) {
set_slot(func_obj, SLOT_ASYNC, js_true);
ant_value_t async_proto = get_slot(js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) js_set_proto_init(func_obj, async_proto);
} else {
ant_value_t func_proto = get_slot(js_glob(js), SLOT_FUNC_PROTO);
ant_value_t instance_proto = js_instance_proto_from_new_target(js, func_proto);
if (is_object_type(instance_proto)) js_set_proto_init(func_obj, instance_proto);
}
ant_value_t func = mkval(T_FUNC, (uintptr_t)closure);
ant_value_t proto_setup = setup_func_prototype(js, func);
if (is_err(proto_setup)) return proto_setup;
return func;
}
static ant_value_t builtin_Function(ant_t *js, ant_value_t *args, int nargs) {
return build_dynamic_function(js, args, nargs, false);
}
static ant_value_t builtin_AsyncFunction(ant_t *js, ant_value_t *args, int nargs) {
return build_dynamic_function(js, args, nargs, true);
}
static ant_value_t builtin_function_empty(ant_t *js, ant_value_t *args, int nargs) {
(void)js; (void)args; (void)nargs;
return js_mkundef();
}
static ant_value_t builtin_function_call(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t func = js->this_val;
if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC) {
return js_mkerr(js, "call requires a function");
}
ant_value_t this_arg = (nargs > 0) ? args[0] : js_mkundef();
ant_value_t *call_args = NULL;
int call_nargs = (nargs > 1) ? nargs - 1 : 0;
if (call_nargs > 0) {
call_args = &args[1];
}
return sv_vm_call(js->vm, js, func, this_arg, call_args, call_nargs, NULL, false);
}
static int extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_args) {
int len = (int) get_array_length(js, arr);
if (len <= 0) return 0;
ant_value_t *args_out = (ant_value_t *)ant_calloc(sizeof(ant_value_t) * len);
if (!args_out) return 0;
for (int i = 0; i < len; i++) {
args_out[i] = arr_get(js, arr, (ant_offset_t)i);
}
*out_args = args_out;
return len;
}
static ant_value_t builtin_function_toString(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t func = js->this_val;
uint8_t t = vtype(func);
if (t != T_FUNC && t != T_CFUNC) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Function.prototype.toString requires that 'this' be a Function");
}
if (t == T_CFUNC) return ANT_STRING("function() { [native code] }");
ant_value_t func_obj = js_func_obj(func);
ant_value_t cfunc_slot = get_slot(func_obj, SLOT_CFUNC);
if (vtype(cfunc_slot) == T_CFUNC) {
ant_offset_t name_len = 0;
const char *name = get_func_name(js, func, &name_len);
if (name && name_len > 0) {
size_t total = 9 + name_len + 21 + 1;
char *buf = ant_calloc(total);
size_t n = 0;
n += cpy(buf + n, total - n, "function ", 9);
n += cpy(buf + n, total - n, name, name_len);
n += cpy(buf + n, total - n, "() { [native code] }", 20);
ant_value_t result = js_mkstr(js, buf, n);
free(buf);
return result;
}
return ANT_STRING("function() { [native code] }");
}
ant_value_t code_val = get_slot(func_obj, SLOT_CODE);
ant_value_t len_val = get_slot(func_obj, SLOT_CODE_LEN);
if (vtype(code_val) == T_CFUNC && vtype(len_val) == T_NUM) {
const char *code = (const char *)(uintptr_t)vdata(code_val);
size_t code_len = (size_t)tod(len_val);
if (code && code_len > 0) {
ant_value_t async_slot = get_slot(func_obj, SLOT_ASYNC);
sv_closure_t *closure = js_func_closure(func);
bool is_async = (async_slot == js_true);
bool is_arrow = (closure->call_flags & SV_CALL_IS_ARROW) != 0;
if (is_arrow) {
const char *paren_end = memchr(code, ')', code_len);
if (!paren_end) goto fallback_arrow;
size_t params_len = paren_end - code + 1;
const char *body = paren_end + 1;
size_t body_len = code_len - params_len;
size_t len = (is_async ? 6 : 0) + params_len + 4 + body_len + 1;
char *buf = ant_calloc(len);
size_t n = 0;
if (is_async) n += cpy(buf + n, REMAIN(n, len), "async ", 6);
n += cpy(buf + n, REMAIN(n, len), code, params_len);
n += cpy(buf + n, REMAIN(n, len), " => ", 4);
n += cpy(buf + n, REMAIN(n, len), body, body_len);
ant_value_t result = js_mkstr(js, buf, n);
free(buf);
return result;
fallback_arrow:;
}
return js_mkstr(js, code, code_len);
}
}
sv_closure_t *cl = js_func_closure(func);
if (cl->func != NULL) {
sv_func_t *fn = cl->func;
if (fn && fn->source && fn->source_end > fn->source_start) {
int start = fn->source_start;
int end = fn->source_end;
if (start >= 0 && end <= fn->source_len && end > start)
return js_mkstr(js, fn->source + start, (size_t)(end - start));
}
}
char buf[256];
size_t len = strfunc(js, func, buf, sizeof(buf));
return js_mkstr(js, buf, len);
}
static ant_value_t builtin_function_apply(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t func = js->this_val;
if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Function.prototype.apply requires that 'this' be a Function");
}
ant_value_t this_arg = (nargs > 0) ? args[0] : js_mkundef();
ant_value_t *call_args = NULL;
int call_nargs = 0;
if (nargs > 1) {
ant_value_t arg_array = args[1];
uint8_t t = vtype(arg_array);
if (t == T_ARR || t == T_OBJ) {
call_nargs = extract_array_args(js, arg_array, &call_args);
} else if (t != T_UNDEF && t != T_NULL) {}
}
ant_value_t result = sv_vm_call(js->vm, js, func, this_arg, call_args, call_nargs, NULL, false);
if (call_args) free(call_args);
return result;
}
static ant_value_t builtin_function_bind(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t func = js->this_val;
if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC) {
return js_mkerr_typed(js, JS_ERR_TYPE, "bind requires a function");
}
ant_value_t this_arg = (nargs > 0) ? args[0] : js_mkundef();
int bound_argc = (nargs > 1) ? nargs - 1 : 0;
ant_value_t *bound_args = (bound_argc > 0) ? &args[1] : NULL;
int orig_length = 0;
ant_value_t target_func_obj;
if (vtype(func) == T_CFUNC) {
orig_length = 0;
} else {
target_func_obj = js_func_obj(func);
ant_value_t len_val = lkp_interned_val(js, target_func_obj, INTERN_LENGTH);
if (vtype(len_val) == T_NUM) {
orig_length = (int) tod(len_val);
}
}
int bound_length = orig_length - bound_argc;
if (bound_length < 0) bound_length = 0;
if (vtype(func) == T_CFUNC) {
ant_value_t bound_func = mkobj(js, 0);
if (is_err(bound_func)) return bound_func;
set_slot(bound_func, SLOT_CFUNC, func);
ant_value_t func_proto = get_slot(js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) js_set_proto_init(bound_func, func_proto);
ant_value_t bound = js_obj_to_func_ex(bound_func, bound_argc > 0 ? SV_CALL_HAS_BOUND_ARGS : 0);
sv_closure_t *bc = js_func_closure(bound);
bc->bound_this = this_arg;
if (bound_argc > 0) {
bc->bound_argv = malloc(sizeof(ant_value_t) * (size_t)bound_argc);
memcpy(bc->bound_argv, bound_args, sizeof(ant_value_t) * (size_t)bound_argc);
bc->bound_argc = bound_argc;
}
js_setprop(js, bound_func, js->length_str, tov((double) bound_length));
ant_value_t proto_setup = setup_func_prototype(js, bound);
if (is_err(proto_setup)) return proto_setup;
js_mark_constructor(bound_func, js_is_constructor(js, func));
return bound;
}
ant_value_t func_obj = js_func_obj(func);
ant_value_t bound_func = mkobj(js, 0);
if (is_err(bound_func)) return bound_func;
ant_value_t code_val = get_slot(func_obj, SLOT_CODE);
if (vtype(code_val) == T_STR || vtype(code_val) == T_CFUNC) {
set_slot(bound_func, SLOT_CODE, code_val);
set_slot(bound_func, SLOT_CODE_LEN, get_slot(func_obj, SLOT_CODE_LEN));
}
sv_closure_t *orig = js_func_closure(func);
sv_closure_t *bound_closure = js_closure_alloc(js);
if (!bound_closure) return js_mkerr(js, "oom");
bound_closure->func = orig->func;
bound_closure->call_flags = orig->call_flags;
bound_closure->upvalues = orig->upvalues;
if (orig->upvalues) bound_closure->call_flags |= SV_CALL_BORROWED_UPVALS;
bound_closure->bound_this = this_arg;
bound_closure->bound_args = js_mkundef();
bound_closure->super_val = orig->super_val;
bound_closure->func_obj = bound_func;
if (bound_argc > 0)
bound_closure->call_flags |= SV_CALL_HAS_BOUND_ARGS;
ant_value_t async_slot = get_slot(func_obj, SLOT_ASYNC);
if (vtype(async_slot) == T_BOOL && vdata(async_slot) == 1) {
set_slot(bound_func, SLOT_ASYNC, js_true);
}
ant_value_t target_proto = get_proto(js, func);
if (is_object_type(target_proto)) {
js_set_proto_init(bound_func, target_proto);
} else if (vtype(async_slot) == T_BOOL && vdata(async_slot) == 1) {
ant_value_t async_proto = get_slot(js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) js_set_proto_init(bound_func, async_proto);
} else {
ant_value_t func_proto = get_slot(js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) js_set_proto_init(bound_func, func_proto);
}
ant_value_t data_slot = get_slot(func_obj, SLOT_DATA);
if (vtype(data_slot) != T_UNDEF) {
set_slot(bound_func, SLOT_DATA, data_slot);
}
set_slot(bound_func, SLOT_TARGET_FUNC, func);
if (bound_argc > 0) {
ant_value_t bound_arr = mkarr(js);
for (int i = 0; i < bound_argc; i++) arr_set(js, bound_arr, (ant_offset_t)i, bound_args[i]);
bound_closure->bound_args = bound_arr;
bound_closure->bound_argv = malloc(sizeof(ant_value_t) * (size_t)bound_argc);
memcpy(bound_closure->bound_argv, bound_args, sizeof(ant_value_t) * (size_t)bound_argc);
bound_closure->bound_argc = bound_argc;
}
ant_value_t cfunc_slot = get_slot(func_obj, SLOT_CFUNC);
if (vtype(cfunc_slot) == T_CFUNC) {
set_slot(bound_func, SLOT_CFUNC, cfunc_slot);
}
js_setprop(js, bound_func, js->length_str, tov((double) bound_length));
ant_value_t bound = mkval(T_FUNC, (uintptr_t)bound_closure);
ant_value_t proto_setup = setup_func_prototype(js, bound);
if (is_err(proto_setup)) return proto_setup;
js_mark_constructor(bound_func, js_is_constructor(js, func));
return bound;
}
static ant_value_t builtin_Array(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = mkarr(js);
if (is_err(arr)) return arr;
if (nargs == 1 && vtype(args[0]) == T_NUM) {
ant_value_t err = validate_array_length(js, args[0]);
if (is_err(err)) return err;
ant_offset_t new_len = (ant_offset_t)tod(args[0]);
ant_offset_t doff = get_dense_buf(arr);
if (doff && new_len <= 1024) {
if (new_len > dense_capacity(doff)) doff = dense_grow(js, arr, new_len);
}
array_len_set(js, arr, new_len);
} else if (nargs > 0) {
for (int i = 0; i < nargs; i++) arr_set(js, arr, (ant_offset_t)i, args[i]);
}
ant_value_t array_proto = get_ctor_proto(js, "Array", 5);
ant_value_t instance_proto = js_instance_proto_from_new_target(js, array_proto);
if (is_object_type(instance_proto)) js_set_proto_init(arr, instance_proto);
if (vtype(js->new_target) == T_FUNC || vtype(js->new_target) == T_CFUNC) {
set_slot(arr, SLOT_CTOR, js->new_target);
}
return arr;
}
static ant_value_t builtin_error_isError(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_false;
ant_value_t val = args[0];
if (!is_object_type(val)) return js_false;
return get_slot(val, SLOT_ERROR_BRAND) == js_true ? js_true : js_false;
}
static ant_value_t builtin_Error(ant_t *js, ant_value_t *args, int nargs) {
bool is_new = (vtype(js->new_target) != T_UNDEF);
ant_value_t this_val = js->this_val;
ant_value_t target = is_new ? js->new_target : js->current_func;
ant_value_t name = ANT_STRING("Error");
if (vtype(target) == T_FUNC) {
ant_value_t n = lkp_val(js, js_func_obj(target), "name", 4);
if (vtype(n) != T_UNDEF) name = n;
}
if (!is_new) {
this_val = js_mkobj(js);
ant_value_t proto = lkp_interned_val(js, js_func_obj(js->current_func), INTERN_PROTOTYPE);
if (vtype(proto) != T_UNDEF) js_set_proto_init(this_val, proto);
else js_set_proto_init(this_val, get_ctor_proto(js, "Error", 5));
}
if (nargs > 0) {
ant_value_t msg = args[0];
if (vtype(msg) != T_STR) {
const char *str = js_str(js, msg);
msg = js_mkstr(js, str, strlen(str));
}
js_mkprop_fast(js, this_val, "message", 7, msg);
}
if (nargs > 1 && vtype(args[1]) == T_OBJ) {
ant_offset_t cause_off = lkp(js, args[1], "cause", 5);
if (cause_off) js_mkprop_fast(js, this_val, "cause", 5, propref_load(js, cause_off));
}
js_mkprop_fast(js, this_val, "name", 4, name);
set_slot(this_val, SLOT_ERROR_BRAND, js_true);
js_capture_stack(js, this_val);
return this_val;
}
static ant_value_t builtin_Error_toString(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t name = js_get(js, this_val, "name");
if (vtype(name) == T_UNDEF) name = js_mkstr(js, "Error", 5);
else if (vtype(name) != T_STR) {
const char *s = js_str(js, name);
name = js_mkstr(js, s, strlen(s));
}
ant_value_t msg = js_get(js, this_val, "message");
if (vtype(msg) == T_UNDEF) msg = js_mkstr(js, "", 0);
else if (vtype(msg) != T_STR) {
const char *s = js_str(js, msg);
msg = js_mkstr(js, s, strlen(s));
}
ant_offset_t name_len, msg_len;
ant_offset_t name_off = vstr(js, name, &name_len);
ant_offset_t msg_off = vstr(js, msg, &msg_len);
const char *name_str = (const char *)(uintptr_t)(name_off);
const char *msg_str = (const char *)(uintptr_t)(msg_off);
if (name_len == 0) return msg;
if (msg_len == 0) return name;
size_t total = (size_t)(name_len + 2 + msg_len);
char *buf = malloc(total + 1);
if (!buf) return js_mkerr(js, "out of memory");
memcpy(buf, name_str, (size_t)name_len);
buf[name_len] = ':';
buf[name_len + 1] = ' ';
memcpy(buf + name_len + 2, msg_str, (size_t)msg_len);
buf[total] = '\0';
ant_value_t result = js_mkstr(js, buf, total);
free(buf);
return result;
}
static ant_value_t builtin_AggregateError(ant_t *js, ant_value_t *args, int nargs) {
bool is_new = (vtype(js->new_target) != T_UNDEF);
ant_value_t this_val = js->this_val;
if (!is_new) {
this_val = js_mkobj(js);
ant_offset_t proto_off = lkp_interned(js, js_func_obj(js->current_func), INTERN_PROTOTYPE, 9);
if (proto_off) js_set_proto_init(this_val, propref_load(js, proto_off));
else js_set_proto_init(this_val, get_ctor_proto(js, "AggregateError", 14));
}
ant_value_t errors = nargs > 0 ? args[0] : mkarr(js);
if (vtype(errors) != T_ARR) errors = mkarr(js);
js_mkprop_fast(js, this_val, "errors", 6, errors);
if (nargs > 1 && vtype(args[1]) != T_UNDEF) {
ant_value_t msg = args[1];
if (vtype(msg) != T_STR) {
const char *str = js_str(js, msg);
msg = js_mkstr(js, str, strlen(str));
}
js_mkprop_fast(js, this_val, "message", 7, msg);
}
if (nargs > 2 && vtype(args[2]) == T_OBJ) {
ant_offset_t cause_off = lkp(js, args[2], "cause", 5);
if (cause_off) js_mkprop_fast(js, this_val, "cause", 5, propref_load(js, cause_off));
}
js_mkprop_fast(js, this_val, "name", 4, ANT_STRING("AggregateError"));
set_slot(this_val, SLOT_ERROR_BRAND, js_true);
return this_val;
}
typedef ant_value_t (*dynamic_kv_mapper_fn)(
ant_t *js,
ant_value_t key,
ant_value_t val
);
static ant_value_t iterate_dynamic_keys(ant_t *js, ant_value_t obj, dynamic_kv_mapper_fn mapper) {
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr || !ptr->exotic_keys || !ptr->exotic_ops || !ptr->exotic_ops->getter) return mkarr(js);
ant_value_t keys_arr = ptr->exotic_keys(js, obj);
ant_value_t arr = mkarr(js);
ant_offset_t len = get_array_length(js, keys_arr);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t key_val = arr_get(js, keys_arr, i);
if (vtype(key_val) != T_STR) continue;
ant_offset_t klen; ant_offset_t str_off = vstr(js, key_val, &klen);
const char *key = (const char *)(uintptr_t)(str_off);
ant_value_t val = ptr->exotic_ops->getter(js, obj, key, klen);
js_arr_push(js, arr, mapper ? mapper(js, key_val, val) : val);
}
return mkval(T_ARR, vdata(arr));
}
static ant_value_t builtin_object_is(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_false;
ant_value_t x = args[0];
ant_value_t y = args[1];
uint8_t tx = vtype(x);
uint8_t ty = vtype(y);
if (tx != ty) return js_false;
if (tx == T_UNDEF || tx == T_NULL) return js_true;
if (tx == T_NUM) {
double dx = tod(x);
double dy = tod(y);
if (isnan(dx) && isnan(dy)) return js_true;
if (dx == 0.0 && dy == 0.0) {
bool x_neg = (1.0 / dx) < 0;
bool y_neg = (1.0 / dy) < 0;
return x_neg == y_neg ? js_true : js_false;
}
return dx == dy ? js_true : js_false;
}
if (tx == T_BOOL) return vdata(x) == vdata(y) ? js_true : js_false;
return strict_eq_values(js, x, y) ? js_true : js_false;
}
enum obj_enum_mode {
OBJ_ENUM_KEYS,
OBJ_ENUM_VALUES,
OBJ_ENUM_ENTRIES
};
static ant_value_t map_to_entry(ant_t *js, ant_value_t key, ant_value_t val) {
ant_value_t pair = mkarr(js);
arr_set(js, pair, 0, key);
arr_set(js, pair, 1, val);
return mkval(T_ARR, vdata(pair));
}
static ant_value_t object_enum(ant_t *js, ant_value_t obj, enum obj_enum_mode mode) {
bool is_arr = (vtype(obj) == T_ARR);
if (vtype(obj) == T_FUNC) obj = js_func_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr || !ptr->shape) return mkarr(js);
if (ptr->is_exotic && ptr->exotic_keys) {
if (mode == OBJ_ENUM_KEYS && (!ptr->exotic_ops || !ptr->exotic_ops->getter)) return ptr->exotic_keys(js, obj);
if (ptr->exotic_ops && ptr->exotic_ops->getter) {
dynamic_kv_mapper_fn mapper = (mode == OBJ_ENUM_ENTRIES) ? map_to_entry : NULL;
return iterate_dynamic_keys(js, obj, mapper);
}
}
ant_value_t arr = mkarr(js);
ant_offset_t idx = 0;
if (is_arr) {
ant_offset_t doff = get_dense_buf(obj);
if (doff) {
ant_offset_t dense_len = dense_iterable_length(js, obj);
for (ant_offset_t i = 0; i < dense_len; i++) {
ant_value_t v = dense_get(doff, i);
if (is_empty_slot(v)) continue;
char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)i);
ant_value_t key_val = js_mkstr(js, idxstr, idxlen);
if (mode == OBJ_ENUM_KEYS) arr_set(js, arr, idx, key_val);
else if (mode == OBJ_ENUM_VALUES) arr_set(js, arr, idx, v);
else arr_set(js, arr, idx, map_to_entry(js, key_val, v));
idx++;
}
}
}
uint32_t shape_count = ant_shape_count(ptr->shape);
for (uint32_t i = 0; i < shape_count; i++) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, i);
if (!prop || prop->type == ANT_SHAPE_KEY_SYMBOL) continue;
if (i >= ptr->prop_count) continue;
const char *key = prop->key.interned;
ant_offset_t klen = (ant_offset_t)strlen(key);
ant_value_t val = ant_object_prop_get_unchecked(ptr, i);
if (is_internal_prop(key, klen)) continue;
if (is_arr && is_array_index(key, klen)) {
ant_offset_t doff = get_dense_buf(obj);
if (doff) {
unsigned long pidx = 0;
for (ant_offset_t ci = 0; ci < klen; ci++) pidx = pidx * 10 + (key[ci] - '0');
if (pidx < dense_iterable_length(js, obj)) continue;
}
}
bool should_include = (ant_shape_get_attrs(ptr->shape, i) & ANT_PROP_ATTR_ENUMERABLE) != 0;
if (should_include && ptr->is_exotic) {
descriptor_entry_t *desc = lookup_descriptor(js_as_obj(obj), key, (size_t)klen);
if (desc) should_include = desc->enumerable;
}
if (!should_include) continue;
ant_value_t key_val = js_mkstr(js, key, (size_t)klen);
if (mode == OBJ_ENUM_KEYS) arr_set(js, arr, idx, key_val);
else if (mode == OBJ_ENUM_VALUES) arr_set(js, arr, idx, val);
else arr_set(js, arr, idx, map_to_entry(js, key_val, val));
idx++;
}
return mkval(T_ARR, vdata(arr));
}
static ant_value_t builtin_object_keys(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
ant_value_t obj = args[0];
if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) return mkarr(js);
if (is_proxy(obj)) {
ant_proxy_state_t *data = get_proxy_data(obj);
if (!data) return mkarr(js);
if (data->revoked)
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot perform 'ownKeys' on a proxy that has been revoked");
ant_offset_t trap_off = lkp(js, data->handler, "ownKeys", 7);
if (!trap_off) return object_enum(js, data->target, OBJ_ENUM_KEYS);
ant_value_t trap = propref_load(js, trap_off);
uint8_t ft = vtype(trap);
if (ft != T_FUNC && ft != T_CFUNC) return object_enum(js, data->target, OBJ_ENUM_KEYS);
ant_value_t trap_args[1] = { data->target };
ant_value_t result = sv_vm_call(js->vm, js, trap, data->handler, trap_args, 1, NULL, false);
if (is_err(result)) return result;
if (vtype(result) != T_ARR)
return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap must return an array");
ant_offset_t len = get_array_length(js, result);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t ki = arr_get(js, result, i);
if (vtype(ki) != T_STR && vtype(ki) != T_SYMBOL)
return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must contain only strings or symbols");
ant_offset_t ki_len; ant_offset_t ki_off = vstr(js, ki, &ki_len);
for (ant_offset_t j = 0; j < i; j++) {
ant_value_t kj = arr_get(js, result, j);
ant_offset_t kj_len; ant_offset_t kj_off = vstr(js, kj, &kj_len);
if (ki_len == kj_len &&
memcmp((const void *)(uintptr_t)ki_off, (const void *)(uintptr_t)kj_off, ki_len) == 0)
return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must not contain duplicate entries");
}
}
return result;
}
return object_enum(js, obj, OBJ_ENUM_KEYS);
}
static ant_value_t for_in_keys_add(ant_t *js, ant_value_t out, ant_value_t seen, ant_value_t key) {
if (vtype(key) != T_STR) return js_mkundef();
ant_offset_t key_len = 0;
ant_offset_t key_off = vstr(js, key, &key_len);
const char *key_ptr = (const char *)(uintptr_t)(key_off);
if (is_internal_prop(key_ptr, key_len))
return js_mkundef();
if (lkp(js, seen, key_ptr, key_len) != 0)
return js_mkundef();
ant_value_t mark = setprop_cstr(js, seen, key_ptr, key_len, js_true);
if (is_err(mark)) return mark;
js_arr_push(js, out, key);
return js_mkundef();
}
static ant_value_t for_in_keys_add_string_indices(ant_t *js, ant_value_t out, ant_value_t seen, ant_value_t str) {
ant_offset_t slen = vstrlen(js, str);
for (ant_offset_t i = 0; i < slen; i++) {
char idx[16];
size_t idx_len = uint_to_str(idx, sizeof(idx), (uint64_t)i);
ant_value_t key = js_mkstr(js, idx, idx_len);
ant_value_t r = for_in_keys_add(js, out, seen, key);
if (is_err(r)) return r;
}
return js_mkundef();
}
ant_value_t js_for_in_keys(ant_t *js, ant_value_t obj) {
uint8_t t = vtype(obj);
ant_value_t out = mkarr(js);
if (t == T_NULL || t == T_UNDEF) return out;
ant_value_t seen = mkobj(js, 0);
ant_value_t result = js_mkundef();
if (t == T_STR) {
result = for_in_keys_add_string_indices(js, out, seen, obj);
if (is_err(result)) return result;
return out;
}
if (t == T_OBJ) {
ant_value_t prim = get_slot(obj, SLOT_PRIMITIVE);
if (vtype(prim) == T_STR) {
result = for_in_keys_add_string_indices(js, out, seen, prim);
if (is_err(result)) return result;
}
}
if (t == T_OBJ || t == T_ARR || t == T_FUNC) {
ant_value_t own_keys = object_enum(js, obj, OBJ_ENUM_KEYS);
if (is_err(own_keys)) return own_keys;
if (vtype(own_keys) == T_ARR) {
ant_offset_t own_len = js_arr_len(js, own_keys);
for (ant_offset_t i = 0; i < own_len; i++) {
ant_value_t key = js_arr_get(js, own_keys, i);
result = for_in_keys_add(js, out, seen, key);
if (is_err(result)) return result;
}
}
}
return out;
}
static ant_value_t builtin_object_values(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
ant_value_t obj = args[0];
if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) return mkarr(js);
return object_enum(js, obj, OBJ_ENUM_VALUES);
}
static ant_value_t builtin_object_entries(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
ant_value_t obj = args[0];
if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) return mkarr(js);
return object_enum(js, obj, OBJ_ENUM_ENTRIES);
}
static ant_value_t builtin_object_getPrototypeOf(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_mkerr(js, "Object.getPrototypeOf requires an argument");
ant_value_t obj = args[0];
uint8_t t = vtype(obj);
if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) return get_prototype_for_type(js, t);
if (is_object_type(obj)) return get_proto(js, obj);
return js_mknull();
}
static ant_value_t builtin_object_setPrototypeOf(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "Object.setPrototypeOf requires 2 arguments");
ant_value_t obj = args[0];
ant_value_t proto = args[1];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) {
return js_mkerr(js, "Object.setPrototypeOf: first argument must be an object");
}
uint8_t pt = vtype(proto);
if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC && pt != T_NULL) {
return js_mkerr(js, "Object.setPrototypeOf: prototype must be an object or null");
}
if (pt != T_NULL && proto_chain_contains(js, proto, obj))
return js_mkerr(js, "Cyclic __proto__ value");
set_proto(js, obj, proto);
return obj;
}
static ant_value_t builtin_proto_getter(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js->this_val;
uint8_t t = vtype(this_val);
if (t == T_UNDEF || t == T_NULL) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot read property '__proto__' of %s", typestr(t));
}
if (t == T_OBJ || t == T_ARR || t == T_FUNC) {
return get_proto(js, this_val);
}
return get_prototype_for_type(js, t);
}
static ant_value_t builtin_proto_setter(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js->this_val;
uint8_t t = vtype(this_val);
if (t == T_UNDEF || t == T_NULL) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot set property '__proto__' of %s", typestr(t));
}
if (t != T_OBJ && t != T_ARR && t != T_FUNC) {
return js_mkundef();
}
if (nargs == 0) return js_mkundef();
ant_value_t proto = args[0];
uint8_t pt = vtype(proto);
if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC && pt != T_NULL) {
return js_mkundef();
}
if (pt != T_NULL && proto_chain_contains(js, proto, this_val))
return js_mkundef();
set_proto(js, this_val, proto);
return js_mkundef();
}
static ant_value_t builtin_object_create(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_mkerr(js, "Object.create requires a prototype argument");
ant_value_t proto = args[0];
uint8_t pt = vtype(proto);
if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC && pt != T_NULL) {
return js_mkerr(js, "Object.create: prototype must be an object or null");
}
ant_value_t obj = js_mkobj(js);
if (pt == T_NULL) {
js_set_proto_init(obj, js_mknull());
} else js_set_proto_init(obj, proto);
if (nargs >= 2 && vtype(args[1]) == T_OBJ) {
ant_value_t props = args[1];
ant_iter_t iter = js_prop_iter_begin(js, props);
const char *key = NULL;
size_t klen = 0;
ant_value_t descriptor = js_mkundef();
while (js_prop_iter_next(&iter, &key, &klen, &descriptor)) {
if (vtype(descriptor) == T_OBJ) {
ant_offset_t val_off = lkp(js, descriptor, "value", 5);
if (val_off != 0) {
ant_value_t val = propref_load(js, val_off);
ant_value_t key_str = js_mkstr(js, key, klen);
js_setprop(js, obj, key_str, val);
}
}
}
js_prop_iter_end(&iter);
}
return obj;
}
static ant_value_t builtin_object_hasOwn(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return mkval(T_BOOL, 0);
ant_value_t obj = args[0];
uint8_t t = vtype(obj);
if (t == T_NULL || t == T_UNDEF) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert undefined or null to object");
}
ant_value_t key = args[1];
if (vtype(key) != T_STR) {
key = js_tostring_val(js, key);
if (is_err(key)) return key;
}
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return mkval(T_BOOL, 0);
ant_value_t as_obj = js_as_obj(obj);
ant_offset_t key_len, key_off = vstr(js, key, &key_len);
const char *key_str = (char *)(uintptr_t)(key_off);
ant_offset_t off = lkp(js, as_obj, key_str, key_len);
return mkval(T_BOOL, off != 0 ? 1 : 0);
}
static ant_value_t builtin_object_groupBy(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "Object.groupBy requires 2 arguments");
ant_value_t items = args[0];
ant_value_t callback = args[1];
if (vtype(callback) != T_FUNC && vtype(callback) != T_CFUNC)
return js_mkerr_typed(js, JS_ERR_TYPE, "callback is not a function");
ant_value_t result = js_mkobj(js);
js_set_proto_init(result, js_mknull());
ant_offset_t len = get_array_length(js, items);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t val = arr_get(js, items, i);
ant_value_t cb_args[2] = { val, tov((double)i) };
ant_value_t key = sv_vm_call(js->vm, js, callback, js_mkundef(), cb_args, 2, NULL, false);
if (is_err(key)) return key;
ant_value_t key_str = js_tostring_val(js, key);
if (is_err(key_str)) return key_str;
ant_offset_t klen;
ant_offset_t koff = vstr(js, key_str, &klen);
const char *kptr = (char *)(uintptr_t)(koff);
ant_offset_t grp_off = lkp(js, result, kptr, klen);
ant_value_t group;
if (grp_off) {
group = propref_load(js, grp_off);
} else {
group = mkarr(js);
js_setprop(js, result, key_str, group);
}
js_arr_push(js, group, val);
}
return result;
}
static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 3) return js_mkerr(js, "Object.defineProperty requires 3 arguments");
ant_value_t obj = args[0];
ant_value_t prop = args[1];
ant_value_t descriptor = args[2];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) {
return js_mkerr(js, "Object.defineProperty called on non-object");
}
bool sym_key = (vtype(prop) == T_SYMBOL);
if (!sym_key && vtype(prop) != T_STR) {
char buf[64];
size_t len = tostr(js, prop, buf, sizeof(buf));
prop = js_mkstr(js, buf, len);
}
if (vtype(descriptor) != T_OBJ) {
return js_mkerr(js, "Property descriptor must be an object");
}
ant_value_t as_obj = js_as_obj(obj);
ant_offset_t prop_len = 0;
const char *prop_str = NULL;
ant_offset_t sym_off = 0;
if (sym_key) {
sym_off = (ant_offset_t)vdata(prop);
const char *desc = js_sym_desc(js, prop);
prop_str = desc ? desc : "symbol";
prop_len = (ant_offset_t)strlen(prop_str);
} else {
ant_offset_t prop_off = vstr(js, prop, &prop_len);
prop_str = (char *)(uintptr_t)(prop_off);
if (streq(prop_str, prop_len, STR_PROTO, STR_PROTO_LEN)) {
return js_mkerr(js, "Cannot define " STR_PROTO " property");
}
}
bool has_value = false, has_get = false, has_set = false;
bool has_writable = false, has_enumerable = false, has_configurable = false;
ant_value_t value = js_mkundef();
bool writable = false, enumerable = false, configurable = false;
ant_offset_t value_off = lkp(js, descriptor, "value", 5);
if (value_off != 0) {
has_value = true;
value = propref_load(js, value_off);
}
ant_offset_t get_off = lkp_interned(js, descriptor, INTERN_GET, 3);
if (get_off != 0) {
has_get = true;
ant_value_t getter = propref_load(js, get_off);
if (vtype(getter) != T_FUNC && vtype(getter) != T_UNDEF) {
return js_mkerr(js, "Getter must be a function");
}
}
ant_offset_t set_off = lkp_interned(js, descriptor, INTERN_SET, 3);
if (set_off != 0) {
has_set = true;
ant_value_t setter = propref_load(js, set_off);
if (vtype(setter) != T_FUNC && vtype(setter) != T_UNDEF) {
return js_mkerr(js, "Setter must be a function");
}
}
ant_offset_t writable_off = lkp(js, descriptor, "writable", 8);
if (writable_off != 0) {
has_writable = true;
ant_value_t w_val = propref_load(js, writable_off);
writable = js_truthy(js, w_val);
}
ant_offset_t enumerable_off = lkp(js, descriptor, "enumerable", 10);
if (enumerable_off != 0) {
has_enumerable = true;
ant_value_t e_val = propref_load(js, enumerable_off);
enumerable = js_truthy(js, e_val);
}
ant_offset_t configurable_off = lkp(js, descriptor, "configurable", 12);
if (configurable_off != 0) {
has_configurable = true;
ant_value_t c_val = propref_load(js, configurable_off);
configurable = js_truthy(js, c_val);
}
if ((has_value || has_writable) && (has_get || has_set)) {
return js_mkerr(js, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute");
}
ant_object_t *arr_ptr = (!sym_key && is_length_key(prop_str, prop_len))
? array_obj_ptr(as_obj)
: NULL;
if (arr_ptr) {
if (has_get || has_set) return js_mkerr(js, "Invalid property descriptor. Cannot use accessors for array length");
if ((has_enumerable && enumerable) || (has_configurable && configurable))
return js_mkerr(js, "Cannot redefine array length attributes");
ant_offset_t new_len = (ant_offset_t)arr_ptr->u.array.len;
if (has_value) {
ant_value_t len_err = validate_array_length(js, value);
if (is_err(len_err)) return len_err;
new_len = (ant_offset_t)tod(value);
}
ant_offset_t doff = get_dense_buf(as_obj);
if (doff) {
ant_offset_t cap = dense_capacity(doff);
ant_offset_t cur_len = get_array_length(js, as_obj);
ant_offset_t clear_to = (cur_len < cap) ? cur_len : cap;
if (new_len < clear_to) {
for (ant_offset_t i = new_len; i < clear_to; i++) dense_set(js, doff, i, T_EMPTY);
}
}
array_len_set(js, as_obj, new_len);
return obj;
}
ant_offset_t existing_off = sym_key ? lkp_sym(js, as_obj, sym_off) : lkp(js, as_obj, prop_str, prop_len);
prop_meta_t existing_sym_meta;
bool has_existing_sym_meta = sym_key && lookup_symbol_prop_meta(as_obj, sym_off, &existing_sym_meta);
bool has_existing_prop = (existing_off > 0) || has_existing_sym_meta;
ant_object_t *obj_ptr = js_obj_ptr(as_obj);
if (!has_existing_prop) {
if (obj_ptr && obj_ptr->frozen)
return js_mkerr(js, "Cannot define property %.*s, object is not extensible", (int)prop_len, prop_str);
if (obj_ptr && obj_ptr->sealed)
return js_mkerr(js, "Cannot define property %.*s, object is not extensible", (int)prop_len, prop_str);
if (obj_ptr && !obj_ptr->extensible)
return js_mkerr(js, "Cannot define property %.*s, object is not extensible", (int)prop_len, prop_str);
}
if (has_existing_sym_meta) {
if (!has_writable) writable = existing_sym_meta.writable;
if (!has_enumerable) enumerable = existing_sym_meta.enumerable;
if (!has_configurable) configurable = existing_sym_meta.configurable;
} else if (existing_off > 0) {
ant_prop_ref_t *existing_ref = propref_get(js, existing_off);
if (existing_ref && existing_ref->obj && existing_ref->obj->shape) {
const ant_shape_prop_t *existing_prop =
ant_shape_prop_at(existing_ref->obj->shape, existing_ref->slot);
if (existing_prop) {
uint8_t existing_attrs = existing_prop->attrs;
if (!has_writable) writable = (existing_attrs & ANT_PROP_ATTR_WRITABLE) != 0;
if (!has_enumerable) enumerable = (existing_attrs & ANT_PROP_ATTR_ENUMERABLE) != 0;
if (!has_configurable) configurable = (existing_attrs & ANT_PROP_ATTR_CONFIGURABLE) != 0;
}
}
}
if (has_get || has_set) {
int desc_flags =
(enumerable ? JS_DESC_E : 0) |
(configurable ? JS_DESC_C : 0);
ant_value_t getter = has_get ? propref_load(js, get_off) : js_mkundef();
ant_value_t setter = has_set ? propref_load(js, set_off) : js_mkundef();
if (sym_key) {
if (has_get) js_set_sym_getter_desc(js, as_obj, prop, getter, desc_flags);
if (has_set) js_set_sym_setter_desc(js, as_obj, prop, setter, desc_flags);
} else {
if (has_get && has_set) js_set_accessor_desc(js, as_obj, prop_str, prop_len, getter, setter, desc_flags);
else if (has_get) js_set_getter_desc(js, as_obj, prop_str, prop_len, getter, desc_flags);
else js_set_setter_desc(js, as_obj, prop_str, prop_len, setter, desc_flags);
}
} else {
int desc_flags =
(writable ? JS_DESC_W : 0) |
(enumerable ? JS_DESC_E : 0) |
(configurable ? JS_DESC_C : 0);
uint8_t attrs = 0;
if (writable) attrs |= ANT_PROP_ATTR_WRITABLE;
if (enumerable) attrs |= ANT_PROP_ATTR_ENUMERABLE;
if (configurable) attrs |= ANT_PROP_ATTR_CONFIGURABLE;
if (!sym_key) js_set_descriptor(js, as_obj, prop_str, prop_len, desc_flags);
if (existing_off > 0) {
bool is_frozen = obj_ptr ? obj_ptr->frozen : false;
bool is_nonconfig = is_nonconfig_prop(js, existing_off) || is_frozen;
bool is_readonly = is_const_prop(js, existing_off) || is_frozen;
if (is_nonconfig) {
if (configurable) return js_mkerr(js,
"Cannot redefine property %.*s: cannot change configurable from false to true",
(int)prop_len, prop_str
);
if (is_readonly && has_writable && writable) return js_mkerr(js,
"Cannot redefine property %.*s: cannot change writable from false to true",
(int)prop_len, prop_str
);
}
if (is_nonconfig && is_readonly && has_value)
return js_mkerr(js, "Cannot assign to read-only property '%.*s'", (int)prop_len, prop_str);
if (has_value) js_saveval(js, existing_off, value);
ant_prop_ref_t *ref = propref_get(js, existing_off);
if (ref && ref->obj) {
if (!js_obj_ensure_unique_shape(ref->obj)) return js_mkerr(js, "oom");
if (sym_key) ant_shape_set_attrs_symbol(ref->obj->shape, sym_off, attrs);
else ant_shape_set_attrs_interned(ref->obj->shape, intern_string(prop_str, prop_len), attrs);
ant_shape_clear_accessor_slot(ref->obj->shape, ref->slot);
}
} else {
if (!has_value) value = js_mkundef();
ant_value_t prop_key = sym_key ? prop : js_mkstr(js, prop_str, prop_len);
uint8_t prop_attrs = ANT_PROP_ATTR_ENUMERABLE
| (writable ? ANT_PROP_ATTR_WRITABLE : 0)
| (configurable ? ANT_PROP_ATTR_CONFIGURABLE : 0);
mkprop(js, as_obj, prop_key, value, prop_attrs);
if (obj_ptr && obj_ptr->shape) {
if (!js_obj_ensure_unique_shape(obj_ptr)) return js_mkerr(js, "oom");
if (sym_key) {
ant_shape_set_attrs_symbol(obj_ptr->shape, sym_off, attrs);
int32_t slot = ant_shape_lookup_symbol(obj_ptr->shape, sym_off);
if (slot >= 0) ant_shape_clear_accessor_slot(obj_ptr->shape, (uint32_t)slot);
} else {
const char *interned = intern_string(prop_str, prop_len);
if (interned) {
ant_shape_set_attrs_interned(obj_ptr->shape, interned, attrs);
int32_t slot = ant_shape_lookup_interned(obj_ptr->shape, interned);
if (slot >= 0) ant_shape_clear_accessor_slot(obj_ptr->shape, (uint32_t)slot);
}
}
}
}
}
if (!sym_key) array_define_or_set_index(js, as_obj, prop_str, (size_t)prop_len);
return obj;
}
typedef struct {
bool thrown_exists;
ant_value_t thrown_value;
ant_value_t thrown_stack;
} js_exception_state_t;
static inline js_exception_state_t js_save_exception(ant_t *js) {
js_exception_state_t saved = {
.thrown_exists = js->thrown_exists,
.thrown_value = js_mkundef(),
.thrown_stack = js_mkundef(),
};
if (saved.thrown_exists) {
saved.thrown_value = js->thrown_value;
saved.thrown_stack = js->thrown_stack;
}
return saved;
}
static inline void js_restore_exception(ant_t *js, const js_exception_state_t *saved) {
js->thrown_exists = saved->thrown_exists;
js->thrown_value = saved->thrown_value;
js->thrown_stack = saved->thrown_stack;
}
ant_value_t js_define_property(ant_t *js, ant_value_t obj, ant_value_t prop, ant_value_t descriptor, bool reflect_mode) {
js_exception_state_t saved = js_save_exception(js);
ant_value_t args[3] = { obj, prop, descriptor };
ant_value_t result = builtin_object_defineProperty(js, args, 3);
if (!reflect_mode) return result;
if (is_err(result)) {
js_restore_exception(js, &saved);
return js_false;
}
return js_true;
}
static ant_value_t builtin_object_defineProperties(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "Object.defineProperties requires 2 arguments");
ant_value_t obj = args[0];
ant_value_t props = args[1];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) {
return js_mkerr(js, "Object.defineProperties called on non-object");
}
if (vtype(props) != T_OBJ) {
return js_mkerr(js, "Property descriptors must be an object");
}
ant_iter_t iter = js_prop_iter_begin(js, props);
const char *key = NULL;
size_t key_len = 0;
ant_value_t descriptor = js_mkundef();
while (js_prop_iter_next(&iter, &key, &key_len, &descriptor)) {
if (is_internal_prop(key, key_len)) continue;
ant_value_t prop_key = js_mkstr(js, key, key_len);
ant_value_t define_args[3] = { obj, prop_key, descriptor };
ant_value_t result = builtin_object_defineProperty(js, define_args, 3);
if (is_err(result)) {
js_prop_iter_end(&iter);
return result;
}
}
js_prop_iter_end(&iter);
return obj;
}
static ant_value_t builtin_object_assign(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_mkerr(js, "Object.assign requires at least 1 argument");
ant_value_t target = args[0];
uint8_t t = vtype(target);
if (t == T_NULL || t == T_UNDEF) {
return js_mkerr(js, "Cannot convert undefined or null to object");
}
if (t != T_OBJ && t != T_ARR && t != T_FUNC) {
target = js_mkobj(js);
}
ant_value_t as_obj = js_as_obj(target);
for (int i = 1; i < nargs; i++) {
ant_value_t source = args[i];
uint8_t st = vtype(source);
if (st == T_NULL || st == T_UNDEF) continue;
if (st != T_OBJ && st != T_ARR && st != T_FUNC) continue;
ant_iter_t iter = js_prop_iter_begin(js, source);
const char *key = NULL;
size_t key_len = 0;
ant_value_t val = js_mkundef();
while (js_prop_iter_next(&iter, &key, &key_len, &val)) {
if (is_internal_prop(key, key_len)) continue;
ant_object_t *source_ptr = js_obj_ptr(js_as_obj(source));
bool should_copy = true;
if (source_ptr && !source_ptr->is_exotic) {
const char *interned = intern_string(key, key_len);
if (interned && source_ptr->shape) {
int32_t slot = ant_shape_lookup_interned(source_ptr->shape, interned);
if (slot >= 0) {
should_copy = (ant_shape_get_attrs(source_ptr->shape, (uint32_t)slot) & ANT_PROP_ATTR_ENUMERABLE) != 0;
}
}
} else {
descriptor_entry_t *desc = lookup_descriptor(js_as_obj(source), key, key_len);
if (desc) should_copy = desc->enumerable;
}
if (should_copy) {
ant_value_t key_str = js_mkstr(js, key, key_len);
js_setprop(js, as_obj, key_str, val);
}
}
js_prop_iter_end(&iter);
}
return target;
}
static ant_value_t builtin_object_freeze(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_mkundef();
ant_value_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return obj;
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
if (!ptr || !ptr->shape) return obj;
if (!js_obj_ensure_unique_shape(ptr)) return js_mkerr(js, "oom");
uint32_t count = ant_shape_count(ptr->shape);
for (uint32_t i = 0; i < count; i++) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, i);
if (!prop) continue;
uint8_t attrs = ant_shape_get_attrs(ptr->shape, i);
attrs &= (uint8_t)~ANT_PROP_ATTR_WRITABLE;
attrs &= (uint8_t)~ANT_PROP_ATTR_CONFIGURABLE;
if (prop->type == ANT_SHAPE_KEY_STRING) {
const char *key = prop->key.interned;
size_t klen = strlen(key);
if (is_internal_prop(key, klen)) continue;
ant_shape_set_attrs_interned(ptr->shape, key, attrs);
} else {
ant_shape_set_attrs_symbol(ptr->shape, prop->key.sym_off, attrs);
}
}
ptr->frozen = 1;
return obj;
}
static ant_value_t builtin_object_isFrozen(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_true;
ant_value_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_true;
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
return js_bool(ptr && ptr->frozen);
}
static ant_value_t builtin_object_seal(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_mkundef();
ant_value_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return obj;
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
if (!ptr || !ptr->shape) return obj;
if (!js_obj_ensure_unique_shape(ptr)) return js_mkerr(js, "oom");
ptr->sealed = 1;
uint32_t count = ant_shape_count(ptr->shape);
for (uint32_t i = 0; i < count; i++) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, i);
if (!prop) continue;
uint8_t attrs = ant_shape_get_attrs(ptr->shape, i);
attrs &= (uint8_t)~ANT_PROP_ATTR_CONFIGURABLE;
if (prop->type == ANT_SHAPE_KEY_STRING) {
const char *key = prop->key.interned;
size_t klen = strlen(key);
if (is_internal_prop(key, klen)) continue;
ant_shape_set_attrs_interned(ptr->shape, key, attrs);
} else {
ant_shape_set_attrs_symbol(ptr->shape, prop->key.sym_off, attrs);
}
}
return obj;
}
static ant_value_t builtin_object_isSealed(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_true;
ant_value_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_true;
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
if (ptr && (ptr->sealed || ptr->frozen)) return js_true;
return js_false;
}
static ant_value_t builtin_object_fromEntries(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_mkerr(js, "Object.fromEntries requires an iterable argument");
ant_value_t iterable = args[0];
uint8_t t = vtype(iterable);
if (t != T_ARR && t != T_OBJ) {
return js_mkerr(js, "Object.fromEntries requires an iterable");
}
ant_value_t result = js_mkobj(js);
ant_offset_t len = get_array_length(js, iterable);
if (len == 0) return result;
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = arr_get(js, iterable, i);
if (vtype(entry) != T_ARR && vtype(entry) != T_OBJ) continue;
ant_value_t key = arr_get(js, entry, 0);
if (is_undefined(key)) continue;
ant_value_t val = arr_get(js, entry, 1);
if (vtype(key) != T_STR) {
char buf[64];
size_t n = tostr(js, key, buf, sizeof(buf));
key = js_mkstr(js, buf, n);
}
js_setprop(js, result, key, val);
}
return result;
}
static ant_value_t builtin_object_getOwnPropertyDescriptor(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
ant_value_t obj = args[0];
ant_value_t key = args[1];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_mkundef();
const char *key_str;
ant_offset_t key_len;
bool is_sym = (vtype(key) == T_SYMBOL);
if (is_sym) {
const char *d = js_sym_desc(js, key);
key_str = d ? d : "symbol";
key_len = (ant_offset_t)strlen(key_str);
} else if (vtype(key) == T_STR) {
ant_offset_t key_off = vstr(js, key, &key_len);
key_str = (char *)(uintptr_t)(key_off);
} else {
char buf[64];
size_t n = tostr(js, key, buf, sizeof(buf));
key = js_mkstr(js, buf, n);
ant_offset_t key_off = vstr(js, key, &key_len);
key_str = (char *)(uintptr_t)(key_off);
}
ant_value_t as_obj = js_as_obj(obj);
ant_offset_t sym_off = is_sym ? (ant_offset_t)vdata(key) : 0;
prop_meta_t sym_meta;
prop_meta_t str_meta;
bool has_sym_meta = is_sym ? lookup_symbol_prop_meta(as_obj, sym_off, &sym_meta) : false;
bool has_str_meta = is_sym ? false : lookup_string_prop_meta(js, as_obj, key_str, (size_t)key_len, &str_meta);
ant_offset_t prop_off = is_sym ? lkp_sym(js, as_obj, sym_off) : lkp(js, as_obj, key_str, key_len);
if (prop_off == 0 && !(is_sym ? has_sym_meta : has_str_meta)) {
return js_mkundef();
}
ant_value_t result = js_mkobj(js);
bool has_getter = false;
bool has_setter = false;
ant_value_t getter = js_mkundef();
ant_value_t setter = js_mkundef();
bool writable = true;
bool enumerable = true;
bool configurable = true;
if (is_sym && has_sym_meta) {
has_getter = sym_meta.has_getter;
has_setter = sym_meta.has_setter;
getter = sym_meta.getter;
setter = sym_meta.setter;
writable = sym_meta.writable;
enumerable = sym_meta.enumerable;
configurable = sym_meta.configurable;
} else if (!is_sym && has_str_meta) {
has_getter = str_meta.has_getter;
has_setter = str_meta.has_setter;
getter = str_meta.getter;
setter = str_meta.setter;
writable = str_meta.writable;
enumerable = str_meta.enumerable;
configurable = str_meta.configurable;
}
if (has_getter || has_setter) {
if (has_getter) {
js_setprop(js, result, js_mkstr(js, "get", 3), getter);
}
if (has_setter) {
js_setprop(js, result, js_mkstr(js, "set", 3), setter);
}
js_setprop(js, result, js_mkstr(js, "enumerable", 10), js_bool(enumerable));
js_setprop(js, result, js_mkstr(js, "configurable", 12), js_bool(configurable));
} else {
ant_value_t prop_val = js_mkundef();
bool has_value_out = false;
if (prop_off != 0) {
prop_val = propref_load(js, prop_off);
has_value_out = true;
} else if (!is_sym && is_length_key(key_str, key_len) && array_obj_ptr(as_obj)) {
prop_val = tov((double)get_array_length(js, as_obj));
has_value_out = true;
}
if (has_value_out) js_setprop(js, result, js_mkstr(js, "value", 5), prop_val);
js_setprop(js, result, js_mkstr(js, "writable", 8), js_bool(writable));
js_setprop(js, result, js_mkstr(js, "enumerable", 10), js_bool(enumerable));
js_setprop(js, result, js_mkstr(js, "configurable", 12), js_bool(configurable));
}
return result;
}
static inline bool own_prop_names_is_dense_shadow(
ant_t *js, ant_value_t obj,
const char *key, ant_offset_t key_len
) {
ant_offset_t doff = get_dense_buf(obj);
if (!doff) return false;
ant_offset_t dense_len = dense_iterable_length(js, obj);
if (dense_len <= 0 || !is_array_index(key, key_len)) return false;
unsigned long dense_idx = 0;
return parse_array_index(key, (size_t)key_len, dense_len, &dense_idx);
}
static ant_value_t builtin_object_getOwnPropertyNames(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
ant_value_t obj = args[0];
if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) return mkarr(js);
if (vtype(obj) == T_FUNC) obj = js_func_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr || !ptr->shape) return mkarr(js);
bool is_arr_obj = (vtype(obj) == T_ARR);
ant_value_t arr = mkarr(js);
ant_offset_t idx = 0;
if (is_arr_obj) {
for (ant_offset_t i = 0;; i++) {
ant_offset_t doff = get_dense_buf(obj);
if (!doff) break;
ant_offset_t dense_len = dense_iterable_length(js, obj);
if (i >= dense_len) break;
ant_value_t v = dense_get(doff, i);
if (is_empty_slot(v)) continue;
char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)i);
arr_set(js, arr, idx++, js_mkstr(js, idxstr, idxlen));
}}
uint32_t count = ant_shape_count(ptr->shape);
for (uint32_t i = 0; i < count; i++) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, i);
if (!prop || prop->type == ANT_SHAPE_KEY_SYMBOL) continue;
const char *key = prop->key.interned;
ant_offset_t klen = (ant_offset_t)strlen(key);
if (is_internal_prop(key, klen)) continue;
if (is_arr_obj && own_prop_names_is_dense_shadow(js, obj, key, klen)) continue;
arr_set(js, arr, idx++, js_mkstr(js, key, (size_t)klen));
}
if (is_arr_obj) arr_set(js, arr, idx++, js->length_str);
return mkval(T_ARR, vdata(arr));
}
static ant_value_t builtin_object_getOwnPropertySymbols(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
ant_value_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return mkarr(js);
if (t == T_FUNC) obj = js_func_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
ant_value_t arr = mkarr(js);
ant_offset_t idx = 0;
if (!ptr || !ptr->shape) return mkval(T_ARR, vdata(arr));
uint32_t count = ant_shape_count(ptr->shape);
for (uint32_t i = 0; i < count; i++) {
const ant_shape_prop_t *prop = ant_shape_prop_at(ptr->shape, i);
if (!prop || prop->type != ANT_SHAPE_KEY_SYMBOL) continue;
arr_set(js, arr, idx++, mkval(T_SYMBOL, prop->key.sym_off));
}
return mkval(T_ARR, vdata(arr));
}
static ant_value_t builtin_object_isExtensible(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_true;
ant_value_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_true;
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
if (!ptr) return js_true;
if (ptr->frozen || ptr->sealed) return js_false;
return js_bool(ptr->extensible);
}
static ant_value_t builtin_object_preventExtensions(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_mkundef();
ant_value_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return obj;
ant_value_t as_obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(as_obj);
if (ptr) ptr->extensible = 0;
return obj;
}
static ant_value_t builtin_object_hasOwnProperty(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return mkval(T_BOOL, 0);
ant_value_t obj = js->this_val;
ant_value_t key = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return mkval(T_BOOL, 0);
ant_value_t as_obj = js_as_obj(obj);
bool is_arr_obj = array_obj_ptr(as_obj) != NULL;
if (vtype(key) == T_SYMBOL) {
ant_offset_t sym_off = (ant_offset_t)vdata(key);
ant_offset_t off = lkp_sym(js, as_obj, sym_off);
if (off != 0) return mkval(T_BOOL, 1);
prop_meta_t meta;
return mkval(T_BOOL, lookup_symbol_prop_meta(as_obj, sym_off, &meta) ? 1 : 0);
}
const char *key_str = NULL;
ant_offset_t key_len = 0;
if (vtype(key) != T_STR) {
char buf[64];
size_t n = tostr(js, key, buf, sizeof(buf));
key = js_mkstr(js, buf, n);
}
ant_offset_t key_off = vstr(js, key, &key_len);
key_str = (char *)(uintptr_t)(key_off);
if (is_arr_obj && is_length_key(key_str, key_len)) return mkval(T_BOOL, 1);
if (is_arr_obj && is_array_index(key_str, key_len)) {
unsigned long idx;
if (parse_array_index(key_str, key_len, get_array_length(js, as_obj), &idx)) {
return mkval(T_BOOL, arr_has(js, as_obj, (ant_offset_t)idx) ? 1 : 0);
}
}
ant_offset_t off = lkp(js, as_obj, key_str, key_len);
if (off != 0) return mkval(T_BOOL, 1);
ant_object_t *ptr = js_obj_ptr(as_obj);
if (ptr && ptr->is_exotic) {
descriptor_entry_t *desc = lookup_descriptor(as_obj, key_str, key_len);
return mkval(T_BOOL, (desc && (desc->has_getter || desc->has_setter)) ? 1 : 0);
}
return mkval(T_BOOL, 0);
}
static bool proto_chain_contains_cycle_safe(ant_t *js, ant_value_t start, ant_value_t target);
bool js_is_prototype_of(ant_t *js, ant_value_t proto_obj, ant_value_t obj) {
uint8_t obj_type = vtype(obj);
if (obj_type != T_OBJ && obj_type != T_ARR && obj_type != T_FUNC) return false;
uint8_t proto_type = vtype(proto_obj);
if (proto_type != T_OBJ && proto_type != T_ARR && proto_type != T_FUNC) return false;
ant_value_t current = get_proto(js, obj);
return proto_chain_contains_cycle_safe(js, current, proto_obj);
}
ant_value_t builtin_object_isPrototypeOf(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return mkval(T_BOOL, 0);
return mkval(T_BOOL, js_is_prototype_of(js, js->this_val, args[0]) ? 1 : 0);
}
static ant_value_t builtin_object_propertyIsEnumerable(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return mkval(T_BOOL, 0);
ant_value_t obj = js->this_val;
ant_value_t key = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return mkval(T_BOOL, 0);
ant_value_t as_obj = js_as_obj(obj);
bool is_arr_obj = array_obj_ptr(as_obj) != NULL;
if (vtype(key) == T_SYMBOL) {
ant_offset_t sym_off = (ant_offset_t)vdata(key);
ant_offset_t off = lkp_sym(js, as_obj, sym_off);
if (off == 0) return mkval(T_BOOL, 0);
prop_meta_t meta;
if (lookup_symbol_prop_meta(as_obj, sym_off, &meta))
return mkval(T_BOOL, meta.enumerable ? 1 : 0);
return mkval(T_BOOL, 1);
}
const char *key_str = NULL;
ant_offset_t key_len = 0;
if (vtype(key) != T_STR) {
char buf[64];
size_t n = tostr(js, key, buf, sizeof(buf));
key = js_mkstr(js, buf, n);
}
ant_offset_t key_off = vstr(js, key, &key_len);
key_str = (char *)(uintptr_t)(key_off);
if (is_arr_obj && is_length_key(key_str, key_len)) {
return mkval(T_BOOL, 0);
}
if (is_arr_obj) {
unsigned long idx;
if (parse_array_index(key_str, key_len, get_array_length(js, as_obj), &idx)) {
return mkval(T_BOOL, arr_has(js, as_obj, (ant_offset_t)idx) ? 1 : 0);
}
}
ant_offset_t off = lkp(js, as_obj, key_str, key_len);
if (off == 0) return mkval(T_BOOL, 0);
const ant_shape_prop_t *prop_meta = prop_shape_meta(js, off);
if (prop_meta && !js_obj_ptr(as_obj)->is_exotic) {
bool enumerable = (prop_meta->attrs & ANT_PROP_ATTR_ENUMERABLE) != 0;
return mkval(T_BOOL, enumerable ? 1 : 0);
}
ant_object_t *ptr = js_obj_ptr(as_obj);
if (ptr && ptr->is_exotic) {
prop_meta_t meta;
if (lookup_string_prop_meta(js, as_obj, key_str, (size_t)key_len, &meta))
return mkval(T_BOOL, meta.enumerable ? 1 : 0);
}
return mkval(T_BOOL, 1);
}
static ant_value_t builtin_object_toString(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
ant_value_t obj = js->this_val;
uint8_t t = vtype(obj);
const char *tag = NULL;
ant_offset_t tag_len = 0;
ant_value_t tag_sym = get_toStringTag_sym();
if (vtype(tag_sym) == T_SYMBOL) {
ant_offset_t sym_off = (ant_offset_t)vdata(tag_sym);
ant_offset_t tag_off = 0;
if (is_object_type(obj)) {
tag_off = lkp_sym_proto(js, obj, sym_off);
} else {
ant_value_t proto = get_prototype_for_type(js, t);
if (is_object_type(proto)) {
tag_off = lkp_sym_proto(js, proto, sym_off);
}
}
if (tag_off != 0) {
ant_value_t tag_val = propref_load(js, tag_off);
if (vtype(tag_val) == T_STR) {
ant_offset_t str_off = vstr(js, tag_val, &tag_len);
tag = (const char *)(uintptr_t)(str_off);
}
}
}
if (!tag) {
if (is_object_type(obj) && get_slot(obj, SLOT_ERROR_BRAND) == js_true) {
tag = "Error"; tag_len = 5;
} else switch (t) {
case T_UNDEF: tag = "Undefined"; tag_len = 9; break;
case T_NULL: tag = "Null"; tag_len = 4; break;
case T_BOOL: tag = "Boolean"; tag_len = 7; break;
case T_NUM: tag = "Number"; tag_len = 6; break;
case T_STR: tag = "String"; tag_len = 6; break;
case T_ARR: tag = "Array"; tag_len = 5; break;
case T_FUNC: tag = "Function"; tag_len = 8; break;
case T_CFUNC: tag = "Function"; tag_len = 8; break;
case T_ERR: tag = "Error"; tag_len = 5; break;
case T_BIGINT: tag = "BigInt"; tag_len = 6; break;
case T_PROMISE: tag = "Promise"; tag_len = 7; break;
case T_OBJ: tag = "Object"; tag_len = 6; break;
default: tag = "Unknown"; tag_len = 7; break;
}
}
char static_buf[64];
string_builder_t sb;
string_builder_init(&sb, static_buf, sizeof(static_buf));
string_builder_append(&sb, "[object ", 8);
string_builder_append(&sb, tag, tag_len);
string_builder_append(&sb, "]", 1);
return string_builder_finalize(js, &sb);
}
static ant_value_t builtin_object_valueOf(ant_t *js, ant_value_t *args, int nargs) {
return js->this_val;
}
static ant_value_t builtin_object_toLocaleString(ant_t *js, ant_value_t *args, int nargs) {
return js_call_toString(js, js->this_val);
}
static inline ant_value_t require_callback(ant_t *js, ant_value_t *args, int nargs, const char *name) {
if (nargs == 0 || !is_callable(args[0]))
return js_mkerr(js, "%s requires a function argument", name);
return args[0];
}
static ant_value_t array_shallow_copy(ant_t *js, ant_value_t arr, ant_offset_t len) {
ant_value_t result = mkarr(js);
if (is_err(result)) return result;
ant_offset_t doff = get_dense_buf(arr);
if (doff) {
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t v = dense_get(doff, i);
arr_set(js, result, i, v);
}
return result;
}
ant_iter_t iter = js_prop_iter_begin(js, arr);
const char *key;
size_t key_len;
ant_value_t val;
while (js_prop_iter_next(&iter, &key, &key_len, &val)) {
if (key_len == 0 || key[0] > '9' || key[0] < '0') continue;
js_mkprop_fast(js, result, key, key_len, val);
}
js_prop_iter_end(&iter);
array_len_set(js, result, len);
return result;
}
static ant_value_t builtin_array_push(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "push called on non-array");
}
if (is_proxy(arr)) {
ant_offset_t off = lkp_interned(js, arr, INTERN_LENGTH, 6);
ant_offset_t len = 0;
if (off != 0) {
ant_value_t len_val = propref_load(js, off);
if (vtype(len_val) == T_NUM) len = (ant_offset_t) tod(len_val);
}
for (int i = 0; i < nargs; i++) {
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len);
ant_value_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, arr, key, args[i]);
len++;
}
ant_value_t len_val = tov((double) len);
js_setprop(js, arr, js->length_str, len_val);
return len_val;
}
ant_offset_t len = get_array_length(js, arr);
ant_offset_t doff = get_dense_buf(arr);
if (doff) {
for (int i = 0; i < nargs; i++) {
ant_offset_t cap = dense_capacity(doff);
if (len >= cap) {
doff = dense_grow(js, arr, len + 1);
if (doff == 0) return js_mkerr(js, "oom");
}
dense_set(js, doff, len, args[i]);
len++;
}
array_len_set(js, arr, len);
return tov((double) len);
}
for (int i = 0; i < nargs; i++) {
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len);
js_mkprop_fast(js, arr, idxstr, idxlen, args[i]); len++;
}
ant_value_t new_len = tov((double) len);
array_len_set(js, arr, len);
return new_len;
}
void js_arr_push(ant_t *js, ant_value_t arr, ant_value_t val) {
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) return;
ant_offset_t doff = get_dense_buf(arr);
if (doff) {
ant_offset_t len = get_array_length(js, arr);
ant_offset_t cap = dense_capacity(doff);
if (len >= cap) {
doff = dense_grow(js, arr, len + 1);
if (doff == 0) return;
}
dense_set(js, doff, len, val);
array_len_set(js, arr, len + 1);
return;
}
ant_offset_t len = get_array_length(js, arr);
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len);
js_mkprop_fast(js, arr, idxstr, idxlen, val);
array_len_set(js, arr, len + 1);
}
static ant_value_t builtin_array_pop(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "pop called on non-array");
}
if (is_proxy(arr)) {
ant_offset_t len = proxy_aware_length(js, arr);
if (len == 0) {
js_setprop(js, arr, js->length_str, tov(0.0));
return js_mkundef();
}
len--;
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len);
ant_value_t result = proxy_aware_get_elem(js, arr, idxstr, idxlen);
js_setprop(js, arr, js->length_str, tov((double) len));
return result;
}
ant_offset_t doff = get_dense_buf(arr);
if (doff) {
ant_offset_t len = get_array_length(js, arr);
ant_offset_t dense_len = dense_iterable_length(js, arr);
if (len == 0) return js_mkundef();
if (len != dense_len) goto pop_slow;
len--;
ant_value_t result = (len < dense_len) ? dense_get(doff, len) : js_mkundef();
if (is_empty_slot(result)) result = js_mkundef();
if (len < dense_len) {
dense_set(js, doff, len, T_EMPTY);
}
array_len_set(js, arr, len);
return result;
}
pop_slow:
ant_offset_t len = get_array_length(js, arr);
if (len == 0) return js_mkundef();
len--; char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len);
ant_offset_t elem_off = 0;
if (elem_off == 0) elem_off = lkp(js, arr, idxstr, idxlen);
ant_value_t result = js_mkundef();
if (elem_off != 0) result = propref_load(js, elem_off);
array_len_set(js, arr, len);
return result;
}
static ant_value_t builtin_array_slice(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "slice called on non-array");
}
ant_offset_t len = get_array_length(js, arr);
ant_offset_t start = 0, end = len;
double dlen = D(len);
if (nargs >= 1 && vtype(args[0]) == T_NUM) {
double d = tod(args[0]);
if (d < 0) {
start = (ant_offset_t) (d + dlen < 0 ? 0 : d + dlen);
} else start = (ant_offset_t) (d > dlen ? dlen : d);
}
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double d = tod(args[1]);
if (d < 0) {
end = (ant_offset_t) (d + dlen < 0 ? 0 : d + dlen);
} else {
end = (ant_offset_t) (d > dlen ? dlen : d);
}
}
if (start > end) start = end;
ant_value_t result = array_alloc_like(js, arr);
if (is_err(result)) return result;
ant_offset_t result_idx = 0;
for (ant_offset_t i = start; i < end; i++) {
ant_value_t elem = arr_get(js, arr, i);
arr_set(js, result, result_idx, elem);
result_idx++;
}
return result;
}
static ant_value_t builtin_array_join(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "join called on non-array");
}
const char *sep = ",";
ant_offset_t sep_len = 1;
if (nargs >= 1) {
if (vtype(args[0]) == T_STR) {
sep_len = 0;
ant_offset_t sep_off = vstr(js, args[0], &sep_len);
sep = (const char *)(uintptr_t)(sep_off);
} else if (vtype(args[0]) != T_UNDEF) {
const char *sep_str = js_str(js, args[0]);
sep = sep_str;
sep_len = (ant_offset_t) strlen(sep_str);
}
}
ant_offset_t len = get_array_length(js, arr);
if (len == 0) return js_mkstr(js, "", 0);
size_t capacity = 1024;
size_t result_len = 0;
char *result = (char *)ant_calloc(capacity);
if (!result) return js_mkerr(js, "oom");
for (ant_offset_t i = 0; i < len; i++) {
if (i > 0) {
if (result_len + sep_len >= capacity) {
capacity = (result_len + sep_len + 1) * 2;
char *new_result = (char *)ant_realloc(result, capacity);
if (!new_result) return js_mkerr(js, "oom");
result = new_result;
}
memcpy(result + result_len, sep, sep_len);
result_len += sep_len;
}
{
ant_value_t elem = arr_get(js, arr, i);
uint8_t et = vtype(elem);
if (et == T_NULL || et == T_UNDEF) continue;
const char *elem_str = NULL;
size_t elem_len = 0;
char numstr[64];
ant_value_t str_val = js_mkundef();
if (et == T_STR) {
ant_offset_t soff, slen;
soff = vstr(js, elem, &slen);
elem_str = (const char *)(uintptr_t)(soff);
elem_len = slen;
} else if (et == T_NUM) {
snprintf(numstr, sizeof(numstr), "%g", tod(elem));
elem_str = numstr;
elem_len = strlen(numstr);
} else if (et == T_BOOL) {
elem_str = vdata(elem) ? "true" : "false";
elem_len = strlen(elem_str);
} else if (et == T_ARR || et == T_OBJ || et == T_FUNC || et == T_BIGINT) {
str_val = to_string_val(js, elem);
if (is_err(str_val)) {
free(result);
return str_val;
}
if (vtype(str_val) == T_STR) {
ant_offset_t soff, slen;
soff = vstr(js, str_val, &slen);
elem_str = (const char *)(uintptr_t)(soff);
elem_len = slen;
}
}
if (elem_str && elem_len > 0) {
if (result_len + elem_len >= capacity) {
capacity = (result_len + elem_len + 1) * 2;
char *new_result = (char *)ant_realloc(result, capacity);
if (!new_result) { free(result); return js_mkerr(js, "oom"); }
result = new_result;
}
memcpy(result + result_len, elem_str, elem_len);
result_len += elem_len;
}
}
}
ant_value_t ret = js_mkstr(js, result, result_len);
free(result); return ret;
}
static ant_value_t builtin_array_includes(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "includes called on non-array");
if (nargs == 0) return mkval(T_BOOL, 0);
ant_value_t search = args[0];
ant_offset_t len = get_array_length(js, arr);
if (len == 0) return mkval(T_BOOL, 0);
ant_offset_t start = 0;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
int s = (int) tod(args[1]);
if (s < 0) s = (int)len + s;
if (s < 0) s = 0;
start = (ant_offset_t) s;
}
for (ant_offset_t i = start; i < len; i++) {
ant_value_t val = arr_get(js, arr, i);
if (vtype(val) == T_NUM && vtype(search) == T_NUM && isnan(tod(val)) && isnan(tod(search))) return mkval(T_BOOL, 1);
if (strict_eq_values(js, val, search)) return mkval(T_BOOL, 1);
}
return mkval(T_BOOL, 0);
}
static ant_value_t builtin_array_every(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "every called on non-array");
ant_value_t callback = require_callback(js, args, nargs, "every");
if (is_err(callback)) return callback;
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_offset_t len = get_array_length(js, arr);
for (ant_offset_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
ant_value_t val = arr_get(js, arr, i);
ant_value_t call_args[3] = { val, tov((double)i), arr };
ant_value_t result = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false);
if (is_err(result)) return result;
if (!js_truthy(js, result)) return mkval(T_BOOL, 0);
}
return mkval(T_BOOL, 1);
}
static ant_value_t builtin_array_forEach(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "forEach called on non-array");
ant_value_t callback = require_callback(js, args, nargs, "forEach");
if (is_err(callback)) return callback;
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_offset_t len = get_array_length(js, arr);
for (ant_offset_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
ant_value_t val = arr_get(js, arr, i);
ant_value_t call_args[3] = { val, tov((double)i), arr };
ant_value_t result = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false);
if (is_err(result)) return result;
}
return js_mkundef();
}
static ant_value_t builtin_array_reverse(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "reverse called on non-array");
if (is_proxy(arr)) {
ant_offset_t len = proxy_aware_length(js, arr);
if (len <= 1) return arr;
ant_value_t read_from = proxy_read_target(js, arr);
ant_offset_t lower = 0;
while (lower < len / 2) {
ant_offset_t upper_idx = len - lower - 1;
bool lower_exists = arr_has(js, read_from, lower);
bool upper_exists = arr_has(js, read_from, upper_idx);
ant_value_t lower_val = lower_exists ? arr_get(js, read_from, lower) : js_mkundef();
ant_value_t upper_val = upper_exists ? arr_get(js, read_from, upper_idx) : js_mkundef();
if (lower_exists && upper_exists) {
char s1[16]; size_t l1 = uint_to_str(s1, sizeof(s1), (unsigned)lower);
js_setprop(js, arr, js_mkstr(js, s1, l1), upper_val);
char s2[16]; size_t l2 = uint_to_str(s2, sizeof(s2), (unsigned)upper_idx);
js_setprop(js, arr, js_mkstr(js, s2, l2), lower_val);
} else if (upper_exists) {
char s[16]; size_t l = uint_to_str(s, sizeof(s), (unsigned)lower);
js_setprop(js, arr, js_mkstr(js, s, l), upper_val);
} else if (lower_exists) {
char s[16]; size_t l = uint_to_str(s, sizeof(s), (unsigned)upper_idx);
js_setprop(js, arr, js_mkstr(js, s, l), lower_val);
} lower++;
} return arr;
}
ant_offset_t len = get_array_length(js, arr);
if (len <= 1) return arr;
ant_offset_t doff = get_dense_buf(arr);
if (doff) {
for (ant_offset_t i = 0; i < len / 2; i++) {
ant_value_t a = dense_get(doff, i);
ant_value_t b = dense_get(doff, len - 1 - i);
dense_set(js, doff, i, b);
dense_set(js, doff, len - 1 - i, a);
}
return arr;
}
for (ant_offset_t lower = 0; lower < len / 2; lower++) {
ant_offset_t upper = len - lower - 1;
bool lower_exists = arr_has(js, arr, lower);
bool upper_exists = arr_has(js, arr, upper);
ant_value_t lower_val = lower_exists ? arr_get(js, arr, lower) : js_mkundef();
ant_value_t upper_val = upper_exists ? arr_get(js, arr, upper) : js_mkundef();
if (lower_exists && upper_exists) {
arr_set(js, arr, lower, upper_val);
arr_set(js, arr, upper, lower_val);
} else if (upper_exists) {
arr_set(js, arr, lower, upper_val);
arr_del(js, arr, upper);
} else if (lower_exists) {
arr_set(js, arr, upper, lower_val);
arr_del(js, arr, lower);
}
}
return arr;
}
static ant_value_t builtin_array_map(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "map called on non-array");
ant_value_t callback = require_callback(js, args, nargs, "map");
if (is_err(callback)) return callback;
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_offset_t len = get_array_length(js, arr);
ant_value_t result = array_alloc_like(js, arr);
if (is_err(result)) return result;
for (ant_offset_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
ant_value_t val = arr_get(js, arr, i);
ant_value_t call_args[3] = { val, tov((double)i), arr };
ant_value_t mapped = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false);
if (is_err(mapped)) return mapped;
arr_set(js, result, i, mapped);
}
return result;
}
static ant_value_t builtin_array_filter(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "filter called on non-array");
ant_value_t callback = require_callback(js, args, nargs, "filter");
if (is_err(callback)) return callback;
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_offset_t len = get_array_length(js, arr);
ant_value_t result = array_alloc_like(js, arr);
if (is_err(result)) return result;
ant_offset_t result_idx = 0;
for (ant_offset_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
ant_value_t val = arr_get(js, arr, i);
ant_value_t call_args[3] = { val, tov((double)i), arr };
ant_value_t test = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false);
if (is_err(test)) return test;
if (js_truthy(js, test)) { arr_set(js, result, result_idx, val); result_idx++; }
}
return result;
}
static ant_value_t builtin_array_reduce(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "reduce called on non-array");
ant_value_t callback = require_callback(js, args, nargs, "reduce");
if (is_err(callback)) return callback;
bool has_initial = (nargs >= 2);
ant_offset_t len = get_array_length(js, arr);
ant_value_t accumulator = has_initial ? args[1] : js_mkundef();
bool first = !has_initial;
for (ant_offset_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
ant_value_t val = arr_get(js, arr, i);
if (first) { accumulator = val; first = false; continue; }
ant_value_t call_args[4] = { accumulator, val, tov((double)i), arr };
accumulator = sv_vm_call(js->vm, js, callback, js_mkundef(), call_args, 4, NULL, false);
if (is_err(accumulator)) return accumulator;
}
if (first) return js_mkerr(js, "reduce of empty array with no initial value");
return accumulator;
}
static void flat_helper(ant_t *js, ant_value_t arr, ant_value_t result, ant_offset_t *result_idx, int depth) {
ant_offset_t len = get_array_length(js, arr);
if (len == 0) return;
for (ant_offset_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
ant_value_t val = arr_get(js, arr, i);
if (depth > 0 && (vtype(val) == T_ARR || vtype(val) == T_OBJ)) {
flat_helper(js, val, result, result_idx, depth - 1);
} else { arr_set(js, result, *result_idx, val); (*result_idx)++; }
}
}
static ant_value_t builtin_array_flat(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "flat called on non-array");
}
int depth = 1;
if (nargs >= 1 && vtype(args[0]) == T_NUM) {
depth = (int) tod(args[0]);
if (depth < 0) depth = 0;
}
ant_value_t result = array_alloc_like(js, arr);
if (is_err(result)) return result;
ant_offset_t result_idx = 0;
flat_helper(js, arr, result, &result_idx, depth);
return result;
}
static ant_value_t builtin_array_concat(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "concat called on non-array");
}
ant_value_t result = array_alloc_like(js, arr);
if (is_err(result)) return result;
ant_offset_t result_idx = 0;
for (int a = -1; a < nargs; a++) {
ant_value_t arg = (a < 0) ? arr : args[a];
bool spreadable = false;
if (vtype(arg) == T_ARR || vtype(arg) == T_OBJ) {
bool array_default_spreadable = (vtype(arg) == T_ARR);
if (!array_default_spreadable && is_proxy(arg)) {
ant_value_t target = proxy_read_target(js, arg);
array_default_spreadable = (vtype(target) == T_ARR);
}
ant_value_t spread_val = js_get_sym(js, arg, get_isConcatSpreadable_sym());
if (is_err(spread_val)) return spread_val;
if (vtype(spread_val) == T_UNDEF) spreadable = array_default_spreadable;
else spreadable = js_truthy(js, spread_val);
}
if (spreadable) {
ant_offset_t arg_len = 0;
ant_value_t len_val = js_get(js, arg, "length");
if (is_err(len_val)) return len_val;
if (vtype(len_val) == T_NUM && tod(len_val) > 0) arg_len = (ant_offset_t)tod(len_val);
for (ant_offset_t i = 0; i < arg_len; i++) {
char idxstr[32];
uint_to_str(idxstr, sizeof(idxstr), (uint64_t)i);
ant_value_t elem = js_get(js, arg, idxstr);
if (is_err(elem)) return elem;
arr_set(js, result, result_idx, elem);
result_idx++;
}
} else {
arr_set(js, result, result_idx, arg);
result_idx++;
}
}
return result;
}
static ant_value_t builtin_array_at(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "at called on non-array");
}
if (nargs == 0 || vtype(args[0]) != T_NUM) return js_mkundef();
ant_offset_t len = get_array_length(js, arr);
int idx = (int) tod(args[0]);
if (idx < 0) idx = (int)len + idx;
if (idx < 0 || (ant_offset_t)idx >= len) return js_mkundef();
return arr_get(js, arr, (ant_offset_t)idx);
}
static ant_value_t builtin_array_fill(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "fill called on non-array");
}
ant_value_t value = nargs >= 1 ? args[0] : js_mkundef();
ant_offset_t len = proxy_aware_length(js, arr);
ant_offset_t start = 0, end = len;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
int s = (int) tod(args[1]);
if (s < 0) s = (int)len + s;
if (s < 0) s = 0;
start = (ant_offset_t) s;
}
if (nargs >= 3 && vtype(args[2]) == T_NUM) {
int e = (int) tod(args[2]);
if (e < 0) e = (int)len + e;
if (e < 0) e = 0;
end = (ant_offset_t) e;
}
if (start > len) start = len;
if (end > len) end = len;
for (ant_offset_t i = start; i < end; i++) {
arr_set(js, arr, i, value);
}
return arr;
}
static ant_value_t array_find_impl(ant_t *js, ant_value_t *args, int nargs, bool return_index, const char *name) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "%s called on non-array", name);
ant_value_t callback = require_callback(js, args, nargs, name);
if (is_err(callback)) return callback;
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_offset_t len = get_array_length(js, arr);
if (len == 0) return return_index ? tov(-1) : js_mkundef();
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t val = arr_get(js, arr, i);
ant_value_t call_args[3] = { val, tov((double)i), arr };
ant_value_t result = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false);
if (is_err(result)) return result;
if (js_truthy(js, result)) return return_index ? tov((double)i) : val;
}
return return_index ? tov(-1) : js_mkundef();
}
static ant_value_t builtin_array_find(ant_t *js, ant_value_t *args, int nargs) {
return array_find_impl(js, args, nargs, false, "find");
}
static ant_value_t builtin_array_findIndex(ant_t *js, ant_value_t *args, int nargs) {
return array_find_impl(js, args, nargs, true, "findIndex");
}
static ant_value_t array_find_last_impl(ant_t *js, ant_value_t *args, int nargs, bool return_index, const char *name) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "%s called on non-array", name);
ant_value_t callback = require_callback(js, args, nargs, name);
if (is_err(callback)) return callback;
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_offset_t len = get_array_length(js, arr);
if (len == 0) return return_index ? tov(-1) : js_mkundef();
for (ant_offset_t i = len; i > 0; i--) {
ant_value_t val = arr_get(js, arr, i - 1);
ant_value_t call_args[3] = { val, tov((double)(i - 1)), arr };
ant_value_t result = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false);
if (is_err(result)) return result;
if (js_truthy(js, result)) return return_index ? tov((double)(i - 1)) : val;
}
return return_index ? tov(-1) : js_mkundef();
}
static ant_value_t builtin_array_findLast(ant_t *js, ant_value_t *args, int nargs) {
return array_find_last_impl(js, args, nargs, false, "findLast");
}
static ant_value_t builtin_array_findLastIndex(ant_t *js, ant_value_t *args, int nargs) {
return array_find_last_impl(js, args, nargs, true, "findLastIndex");
}
static ant_value_t builtin_array_flatMap(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "flatMap called on non-array");
}
if (nargs == 0 || vtype(args[0]) != T_FUNC) {
return js_mkerr(js, "flatMap requires a function argument");
}
ant_value_t callback = args[0];
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_offset_t len = get_array_length(js, arr);
ant_value_t result = mkarr(js);
if (is_err(result)) return result;
ant_offset_t result_idx = 0;
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t elem = arr_get(js, arr, i);
ant_value_t call_args[3] = { elem, tov((double)i), arr };
ant_value_t mapped = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false);
if (is_err(mapped)) return mapped;
if (vtype(mapped) == T_ARR || vtype(mapped) == T_OBJ) {
ant_offset_t m_len = get_array_length(js, mapped);
for (ant_offset_t j = 0; j < m_len; j++) {
ant_value_t m_elem = arr_get(js, mapped, j);
arr_set(js, result, result_idx, m_elem);
result_idx++;
}
} else {
arr_set(js, result, result_idx, mapped);
result_idx++;
}
}
return mkval(T_ARR, vdata(result));
}
static const char *js_tostring(ant_t *js, ant_value_t v) {
if (vtype(v) == T_STR) {
ant_offset_t slen, off = vstr(js, v, &slen);
return (const char *)(uintptr_t)(off);
}
return js_str(js, v);
}
static int js_compare_values(ant_t *js, ant_value_t a, ant_value_t b, ant_value_t compareFn) {
uint8_t t = vtype(compareFn);
if (t == T_FUNC || t == T_CFUNC) {
ant_value_t call_args[2] = { a, b };
ant_value_t result = sv_vm_call(js->vm, js, compareFn, js_mkundef(), call_args, 2, NULL, false);
if (vtype(result) == T_NUM) return (int)tod(result);
return 0;
}
if (vtype(a) == T_STR && vtype(b) == T_STR) {
ant_offset_t len_a, len_b;
const char *sa = (const char *)(uintptr_t)(vstr(js, a, &len_a));
const char *sb = (const char *)(uintptr_t)(vstr(js, b, &len_b));
return strcmp(sa, sb);
}
const char *sa = js_tostring(js, a);
size_t len = strlen(sa);
char *copy = alloca(len + 1);
memcpy(copy, sa, len + 1);
return strcmp(copy, js_tostring(js, b));
}
static ant_value_t builtin_array_indexOf(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "indexOf called on non-array");
}
if (nargs == 0) return tov(-1);
ant_value_t search = args[0];
ant_offset_t len = get_array_length(js, arr);
ant_offset_t start = 0;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
int s = (int) tod(args[1]);
if (s < 0) s = (int)len + s;
if (s < 0) s = 0;
start = (ant_offset_t) s;
}
for (ant_offset_t i = start; i < len; i++) {
ant_value_t elem = arr_get(js, arr, i);
if (vtype(elem) == T_UNDEF && !arr_has(js, arr, i)) continue;
if (strict_eq_values(js, elem, search)) return tov((double)i);
}
return tov(-1);
}
static ant_value_t builtin_array_lastIndexOf(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "lastIndexOf called on non-array");
}
if (nargs == 0) return tov(-1);
ant_value_t search = args[0];
ant_offset_t len = get_array_length(js, arr);
int start = (int)len - 1;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
start = (int) tod(args[1]);
if (start < 0) start = (int)len + start;
}
if (start >= (int)len) start = (int)len - 1;
for (int i = start; i >= 0; i--) {
ant_value_t elem = arr_get(js, arr, (ant_offset_t)i);
if (vtype(elem) == T_UNDEF && !arr_has(js, arr, (ant_offset_t)i)) continue;
if (strict_eq_values(js, elem, search)) return tov((double)i);
}
return tov(-1);
}
static ant_value_t builtin_array_reduceRight(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "reduceRight called on non-array");
}
if (nargs == 0 || vtype(args[0]) != T_FUNC) {
return js_mkerr(js, "reduceRight requires a function argument");
}
ant_value_t callback = args[0];
ant_offset_t len = get_array_length(js, arr);
int start_idx = (int)len - 1;
ant_value_t accumulator;
if (nargs >= 2) {
accumulator = args[1];
} else {
if (len == 0) return js_mkerr(js, "reduceRight of empty array with no initial value");
accumulator = arr_get(js, arr, len - 1);
start_idx = (int)len - 2;
}
for (int i = start_idx; i >= 0; i--) {
ant_value_t elem = arr_get(js, arr, (ant_offset_t)i);
ant_value_t call_args[4] = { accumulator, elem, tov((double)i), arr };
accumulator = sv_vm_call(js->vm, js, callback, js_mkundef(), call_args, 4, NULL, false);
if (is_err(accumulator)) return accumulator;
}
return accumulator;
}
static ant_value_t builtin_array_shift(ant_t *js, ant_value_t *args, int nargs) {
(void) args;
(void) nargs;
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "shift called on non-array");
}
ant_offset_t len = proxy_aware_length(js, arr);
if (len == 0) return js_mkundef();
ant_offset_t doff = get_dense_buf(arr);
if (doff && !is_proxy(arr)) {
ant_offset_t d_len = dense_iterable_length(js, arr);
if (len != d_len) goto shift_slow;
if (d_len == 0) return js_mkundef();
ant_value_t *d = dense_data(doff);
if (!d) return js_mkundef();
ant_value_t first = dense_get(doff, 0);
if (is_empty_slot(first)) first = js_mkundef();
memmove(&d[0], &d[1], sizeof(ant_value_t) * (size_t)(d_len - 1));
dense_set(js, doff, d_len - 1, T_EMPTY);
array_len_set(js, arr, len - 1);
return first;
}
shift_slow:
ant_value_t read_from = is_proxy(arr) ? proxy_read_target(js, arr) : arr;
ant_value_t first = arr_get(js, read_from, 0);
for (ant_offset_t i = 1; i < len; i++) {
if (arr_has(js, read_from, i)) {
ant_value_t elem = arr_get(js, read_from, i);
char dst[16];
size_t dstlen = uint_to_str(dst, sizeof(dst), (unsigned)(i - 1));
js_setprop(js, arr, js_mkstr(js, dst, dstlen), elem);
}
}
if (array_obj_ptr(arr)) array_len_set(js, arr, len - 1);
else js_setprop(js, arr, js->length_str, tov((double)(len - 1)));
return first;
}
static ant_value_t builtin_array_unshift(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "unshift called on non-array");
}
ant_offset_t len = proxy_aware_length(js, arr);
ant_offset_t doff = get_dense_buf(arr);
if (doff && !is_proxy(arr)) {
ant_offset_t d_len = dense_iterable_length(js, arr);
if (len != d_len) goto unshift_slow;
ant_offset_t new_len = len + nargs;
ant_offset_t cap = dense_capacity(doff);
if (new_len > cap) {
doff = dense_grow(js, arr, new_len);
if (doff == 0) return js_mkerr(js, "oom");
}
ant_value_t *d = dense_data(doff);
if (!d) return js_mkerr(js, "oom");
memmove(&d[nargs], &d[0], sizeof(ant_value_t) * (size_t)d_len);
for (int i = 0; i < nargs; i++)
dense_set(js, doff, (ant_offset_t)i, args[i]);
array_len_set(js, arr, new_len);
return tov((double) new_len);
}
unshift_slow:
ant_value_t read_from = is_proxy(arr) ? proxy_read_target(js, arr) : arr;
for (int i = (int)len - 1; i >= 0; i--) {
if (arr_has(js, read_from, (ant_offset_t)i)) {
ant_value_t elem = arr_get(js, read_from, (ant_offset_t)i);
char dst[16];
size_t dstlen = uint_to_str(dst, sizeof(dst), (unsigned)(i + nargs));
js_setprop(js, arr, js_mkstr(js, dst, dstlen), elem);
}
}
for (int i = 0; i < nargs; i++) {
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)i);
ant_value_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, arr, key, args[i]);
}
ant_offset_t new_len = len + nargs;
if (array_obj_ptr(arr)) array_len_set(js, arr, new_len);
else js_setprop(js, arr, js->length_str, tov((double) new_len));
return tov((double) new_len);
}
static ant_value_t builtin_array_some(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "some called on non-array");
ant_value_t callback = require_callback(js, args, nargs, "some");
if (is_err(callback)) return callback;
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_offset_t len = get_array_length(js, arr);
if (len == 0) return mkval(T_BOOL, 0);
for (ant_offset_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
ant_value_t val = arr_get(js, arr, i);
ant_value_t call_args[3] = { val, tov((double)i), arr };
ant_value_t result = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false);
if (is_err(result)) return result;
if (js_truthy(js, result)) return mkval(T_BOOL, 1);
}
return mkval(T_BOOL, 0);
}
static ant_value_t builtin_array_sort(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
ant_value_t compareFn = js_mkundef();
ant_value_t *vals = NULL, *keys = NULL, *temp_vals = NULL, *temp_keys = NULL;
ant_offset_t count = 0, undef_count = 0, len = 0;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "sort called on non-array");
if (nargs >= 1) {
uint8_t t = vtype(args[0]);
if (t == T_FUNC || t == T_CFUNC) compareFn = args[0];
else if (t != T_UNDEF) return js_mkerr_typed(js, JS_ERR_TYPE, "compareFn must be a function or undefined");
}
len = get_array_length(js, arr);
if (len == 0) return arr;
ant_offset_t doff = get_dense_buf(arr);
if (doff) {
vals = malloc(len * sizeof(ant_value_t));
if (!vals) goto oom;
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t v = dense_get(doff, i);
if (is_empty_slot(v) || vtype(v) == T_UNDEF) undef_count++;
else vals[count++] = v;
}
} else {
vals = malloc(len * sizeof(ant_value_t));
if (!vals) goto oom;
for (ant_offset_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
ant_value_t v = arr_get(js, arr, i);
if (vtype(v) == T_UNDEF) undef_count++;
else vals[count++] = v;
}
}
if (count <= 1) goto writeback;
bool use_keys = (vtype(compareFn) == T_UNDEF);
if (use_keys) {
keys = malloc(count * sizeof(ant_value_t));
if (!keys) goto oom;
for (ant_offset_t i = 0; i < count; i++) {
const char *s = js_tostring(js, vals[i]);
keys[i] = js_mkstr(js, s, strlen(s));
}
}
temp_vals = malloc(count * sizeof(ant_value_t));
if (use_keys) temp_keys = malloc(count * sizeof(ant_value_t));
if (!temp_vals || (use_keys && !temp_keys)) goto oom;
for (ant_offset_t width = 1; width < count; width *= 2) {
for (ant_offset_t left = 0; left < count; left += width * 2) {
ant_offset_t mid = left + width;
ant_offset_t right = (mid + width < count) ? mid + width : count;
if (mid >= count) break;
ant_offset_t i = left, j = mid, k = 0;
while (i < mid && j < right) {
int cmp;
if (use_keys) {
ant_offset_t len_a, len_b;
const char *sa = (const char *)(uintptr_t)(vstr(js, keys[i], &len_a));
const char *sb = (const char *)(uintptr_t)(vstr(js, keys[j], &len_b));
cmp = strcmp(sa, sb);
} else {
cmp = js_compare_values(js, vals[i], vals[j], compareFn);
}
if (cmp <= 0) {
temp_vals[k] = vals[i];
if (use_keys) temp_keys[k] = keys[i];
k++; i++;
} else {
temp_vals[k] = vals[j];
if (use_keys) temp_keys[k] = keys[j];
k++; j++;
}
}
while (i < mid) {
temp_vals[k] = vals[i];
if (use_keys) temp_keys[k] = keys[i];
k++; i++;
}
while (j < right) {
temp_vals[k] = vals[j];
if (use_keys) temp_keys[k] = keys[j];
k++; j++;
}
memcpy(&vals[left], temp_vals, k * sizeof(ant_value_t));
if (use_keys) memcpy(&keys[left], temp_keys, k * sizeof(ant_value_t));
}
}
writeback:
if (doff) {
for (ant_offset_t i = 0; i < count; i++) dense_set(js, doff, i, vals[i]);
for (ant_offset_t i = count; i < count + undef_count; i++) dense_set(js, doff, i, js_mkundef());
for (ant_offset_t i = count + undef_count; i < len; i++) dense_set(js, doff, i, T_EMPTY);
} else {
ant_offset_t out = 0;
for (; out < count; out++) arr_set(js, arr, out, vals[out]);
for (ant_offset_t i = 0; i < undef_count; i++, out++) arr_set(js, arr, out, js_mkundef());
for (; out < len; out++) arr_del(js, arr, out);
}
free(temp_keys);
free(temp_vals);
free(keys);
free(vals);
return arr;
oom:
free(temp_keys);
free(temp_vals);
free(keys);
free(vals);
return js_mkerr(js, "out of memory");
}
static ant_value_t builtin_array_splice(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "splice called on non-array");
}
ant_offset_t len = proxy_aware_length(js, arr);
ant_value_t read_from = is_proxy(arr) ? proxy_read_target(js, arr) : arr;
int start = 0;
if (nargs >= 1 && vtype(args[0]) == T_NUM) {
start = (int) tod(args[0]);
if (start < 0) start = (int)len + start;
if (start < 0) start = 0;
if (start > (int)len) start = (int)len;
}
int deleteCount = (int)len - start;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
deleteCount = (int) tod(args[1]);
if (deleteCount < 0) deleteCount = 0;
if (deleteCount > (int)len - start) deleteCount = (int)len - start;
}
int insertCount = nargs > 2 ? nargs - 2 : 0;
ant_value_t removed = array_alloc_like(js, arr);
if (is_err(removed)) return removed;
ant_offset_t doff = get_dense_buf(arr);
if (doff && !is_proxy(arr)) {
ant_offset_t d_len = dense_iterable_length(js, arr);
if (d_len != len) goto splice_slow;
for (int i = 0; i < deleteCount; i++) {
ant_value_t elem = arr_get(js, arr, (ant_offset_t)(start + i));
arr_set(js, removed, (ant_offset_t)i, elem);
}
int shift = insertCount - deleteCount;
ant_offset_t new_len = (ant_offset_t)((int)d_len + shift);
if (shift != 0) {
if (new_len > dense_capacity(doff)) {
doff = dense_grow(js, arr, new_len);
if (doff == 0) return js_mkerr(js, "oom");
}
ant_offset_t move_start = (ant_offset_t)(start + deleteCount);
ant_offset_t move_dest = (ant_offset_t)(start + insertCount);
ant_offset_t move_count = d_len - move_start;
ant_value_t *d = dense_data(doff);
if (!d) return js_mkerr(js, "oom");
if (move_count > 0) memmove(&d[move_dest], &d[move_start], sizeof(ant_value_t) * (size_t)move_count);
}
for (int i = 0; i < insertCount; i++)
dense_set(js, doff, (ant_offset_t)(start + i), args[2 + i]);
if (shift < 0) {
for (ant_offset_t i = new_len; i < d_len; i++)
dense_set(js, doff, i, T_EMPTY);
}
array_len_set(js, arr, new_len);
return removed;
}
splice_slow:
for (int i = 0; i < deleteCount; i++) {
char src[16], dst[16];
snprintf(src, sizeof(src), "%u", (unsigned)(start + i));
snprintf(dst, sizeof(dst), "%u", (unsigned) i);
ant_offset_t elem_off = lkp(js, read_from, src, strlen(src));
if (elem_off != 0) {
ant_value_t elem = propref_load(js, elem_off);
ant_value_t key = js_mkstr(js, dst, strlen(dst));
js_setprop(js, removed, key, elem);
}
}
js_setprop(js, removed, js->length_str, tov((double) deleteCount));
int shift = insertCount - deleteCount;
if (shift > 0) {
for (int i = (int)len - 1; i >= start + deleteCount; i--) {
char src[16], dst[16];
snprintf(src, sizeof(src), "%u", (unsigned) i);
snprintf(dst, sizeof(dst), "%u", (unsigned)(i + shift));
ant_offset_t elem_off = lkp(js, read_from, src, strlen(src));
ant_value_t elem = elem_off ? propref_load(js, elem_off) : js_mkundef();
ant_value_t key = js_mkstr(js, dst, strlen(dst));
js_setprop(js, arr, key, elem);
}
} else if (shift < 0) {
for (int i = start + deleteCount; i < (int)len; i++) {
char src[16], dst[16];
snprintf(src, sizeof(src), "%u", (unsigned) i);
snprintf(dst, sizeof(dst), "%u", (unsigned)(i + shift));
ant_offset_t elem_off = lkp(js, read_from, src, strlen(src));
ant_value_t elem = elem_off ? propref_load(js, elem_off) : js_mkundef();
ant_value_t key = js_mkstr(js, dst, strlen(dst));
js_setprop(js, arr, key, elem);
}
}
for (int i = 0; i < insertCount; i++) {
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(start + i));
ant_value_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, arr, key, args[2 + i]);
}
if (array_obj_ptr(arr)) array_len_set(js, arr, (ant_offset_t)((int)len + shift));
else js_setprop(js, arr, js->length_str, tov((double)((int)len + shift)));
return removed;
}
static ant_value_t builtin_array_copyWithin(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "copyWithin called on non-array");
}
ant_offset_t len = proxy_aware_length(js, arr);
ant_value_t read_from = is_proxy(arr) ? proxy_read_target(js, arr) : arr;
int target = 0, start = 0, end = (int)len;
if (nargs >= 1 && vtype(args[0]) == T_NUM) {
target = (int) tod(args[0]);
if (target < 0) target = (int)len + target;
if (target < 0) target = 0;
}
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
start = (int) tod(args[1]);
if (start < 0) start = (int)len + start;
if (start < 0) start = 0;
}
if (nargs >= 3 && vtype(args[2]) == T_NUM) {
end = (int) tod(args[2]);
if (end < 0) end = (int)len + end;
if (end < 0) end = 0;
}
if (end > (int)len) end = (int)len;
int count = end - start;
if (count > (int)len - target) count = (int)len - target;
if (count <= 0) return arr;
ant_offset_t doff = get_dense_buf(arr);
if (doff && !is_proxy(arr)) {
if (start < target) {
for (int i = count - 1; i >= 0; i--) {
ant_value_t v = dense_get(doff, (ant_offset_t)(start + i));
dense_set(js, doff, (ant_offset_t)(target + i), is_empty_slot(v) ? js_mkundef() : v);
}
} else {
for (int i = 0; i < count; i++) {
ant_value_t v = dense_get(doff, (ant_offset_t)(start + i));
dense_set(js, doff, (ant_offset_t)(target + i), is_empty_slot(v) ? js_mkundef() : v);
}
}
return arr;
}
ant_value_t *temp = (ant_value_t *)malloc(count * sizeof(ant_value_t));
for (int i = 0; i < count; i++) {
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(start + i));
ant_offset_t elem_off = lkp(js, read_from, idxstr, idxlen);
temp[i] = elem_off ? propref_load(js, elem_off) : js_mkundef();
}
for (int i = 0; i < count; i++) {
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(target + i));
ant_value_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, arr, key, temp[i]);
}
free(temp);
return arr;
}
static ant_value_t builtin_array_toSorted(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "toSorted called on non-array");
ant_value_t result = array_shallow_copy(js, arr, get_array_length(js, arr));
if (is_err(result)) return result;
ant_value_t saved_this = js->this_val;
js->this_val = result;
ant_value_t sorted = builtin_array_sort(js, args, nargs);
js->this_val = saved_this;
if (is_err(sorted)) return sorted;
return mkval(T_ARR, vdata(result));
}
static ant_value_t builtin_array_toReversed(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "toReversed called on non-array");
ant_value_t result = array_shallow_copy(js, arr, get_array_length(js, arr));
if (is_err(result)) return result;
ant_value_t saved_this = js->this_val;
js->this_val = result;
ant_value_t reversed = builtin_array_reverse(js, NULL, 0);
js->this_val = saved_this;
if (is_err(reversed)) return reversed;
return mkval(T_ARR, vdata(result));
}
static ant_value_t builtin_array_toSpliced(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "toSpliced called on non-array");
ant_value_t result = array_shallow_copy(js, arr, get_array_length(js, arr));
if (is_err(result)) return result;
ant_value_t saved_this = js->this_val;
js->this_val = result;
builtin_array_splice(js, args, nargs);
js->this_val = saved_this;
return mkval(T_ARR, vdata(result));
}
static ant_value_t builtin_array_with(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "with called on non-array");
}
if (nargs < 2) return js_mkerr(js, "with requires index and value arguments");
ant_offset_t len = get_array_length(js, arr);
int idx = (int) tod(args[0]);
if (idx < 0) idx = (int)len + idx;
if (idx < 0 || (ant_offset_t)idx >= len) return js_mkerr(js, "Invalid index");
ant_value_t result = mkarr(js);
if (is_err(result)) return result;
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t elem = ((ant_offset_t)idx == i) ? args[1] : arr_get(js, arr, i);
arr_set(js, result, i, elem);
}
return mkval(T_ARR, vdata(result));
}
static ant_value_t builtin_array_keys(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->this_val) != T_ARR && vtype(js->this_val) != T_OBJ)
return js_mkerr(js, "keys called on non-array");
return make_array_iterator(js, js->this_val, ARR_ITER_KEYS);
}
static ant_value_t builtin_array_values(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->this_val) != T_ARR && vtype(js->this_val) != T_OBJ)
return js_mkerr(js, "values called on non-array");
return make_array_iterator(js, js->this_val, ARR_ITER_VALUES);
}
static ant_value_t builtin_array_entries(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->this_val) != T_ARR && vtype(js->this_val) != T_OBJ)
return js_mkerr(js, "entries called on non-array");
return make_array_iterator(js, js->this_val, ARR_ITER_ENTRIES);
}
static ant_value_t builtin_array_toString(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js->this_val;
ant_value_t join_result;
if (js_try_call_method(js, arr, "join", 4, NULL, 0, &join_result)) {
if (is_err(join_result)) return join_result;
return join_result;
}
return builtin_object_toString(js, args, nargs);
}
static ant_value_t builtin_array_toLocaleString(ant_t *js, ant_value_t *args, int nargs) {
(void) args;
(void) nargs;
ant_value_t arr = js->this_val;
if (vtype(arr) != T_ARR) return js_mkerr(js, "toLocaleString called on non-array");
ant_offset_t len = get_array_length(js, arr);
if (len == 0) return js_mkstr(js, "", 0);
char *result = NULL;
size_t result_len = 0, result_cap = 256;
result = (char *)ant_calloc(result_cap);
if (!result) return js_mkerr(js, "oom");
for (ant_offset_t i = 0; i < len; i++) {
if (i > 0) {
if (result_len + 1 >= result_cap) {
result_cap *= 2;
char *new_result = (char *)ant_calloc(result_cap);
if (!new_result) { free(result); return js_mkerr(js, "oom"); }
memcpy(new_result, result, result_len);
free(result);
result = new_result;
}
result[result_len++] = ',';
}
if (!arr_has(js, arr, i)) continue;
ant_value_t elem = arr_get(js, arr, i);
if (vtype(elem) == T_NULL || vtype(elem) == T_UNDEF) continue;
char buf[64];
size_t elem_len = tostr(js, elem, buf, sizeof(buf));
if (result_len + elem_len >= result_cap) {
while (result_len + elem_len >= result_cap) result_cap *= 2;
char *new_result = (char *)ant_calloc(result_cap);
if (!new_result) { free(result); return js_mkerr(js, "oom"); }
memcpy(new_result, result, result_len);
free(result);
result = new_result;
}
memcpy(result + result_len, buf, elem_len);
result_len += elem_len;
}
ant_value_t ret = js_mkstr(js, result, result_len);
free(result);
return ret;
}
static ant_value_t builtin_Array_isArray(ant_t *js, ant_value_t *args, int nargs) {
(void) js;
if (nargs == 0) return mkval(T_BOOL, 0);
return mkval(T_BOOL, vtype(args[0]) == T_ARR ? 1 : 0);
}
typedef struct {
ant_value_t write_target;
ant_value_t result;
ant_value_t mapFn;
ant_value_t mapThis;
ant_offset_t index;
} array_from_iter_ctx_t;
static iter_action_t array_from_iter_cb(ant_t *js, ant_value_t value, void *ctx, ant_value_t *out) {
array_from_iter_ctx_t *fctx = (array_from_iter_ctx_t *)ctx;
ant_value_t elem = value;
if (is_callable(fctx->mapFn)) {
ant_value_t call_args[2] = { elem, tov((double)fctx->index) };
elem = sv_vm_call(js->vm, js, fctx->mapFn, fctx->mapThis, call_args, 2, NULL, false);
if (is_err(elem)) { *out = elem; return ITER_ERROR; }
}
if (vtype(fctx->write_target) == T_ARR) arr_set(js, fctx->write_target, fctx->index, elem);
else {
char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)fctx->index);
js_setprop(js, fctx->write_target, js_mkstr(js, idxstr, idxlen), elem);
}
fctx->index++;
return ITER_CONTINUE;
}
static ant_value_t builtin_Array_from(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
ant_value_t src = args[0];
ant_value_t mapFn = (nargs >= 2 && is_callable(args[1])) ? args[1] : js_mkundef();
ant_value_t mapThis = (nargs >= 3) ? args[2] : js_mkundef();
ant_value_t ctor = js->this_val;
bool use_ctor = (vtype(ctor) == T_FUNC || vtype(ctor) == T_CFUNC);
ant_value_t result = use_ctor ? array_alloc_from_ctor_with_length(js, ctor, 0) : mkarr(js);
if (is_err(result)) return result;
bool result_is_proxy = is_proxy(result);
ant_value_t write_target = result_is_proxy ? proxy_read_target(js, result) : result;
ant_value_t iter_sym = get_iterator_sym();
if (vtype(src) == T_STR) {
if (str_is_heap_rope(src)) { src = rope_flatten(js, src); if (is_err(src)) return src; }
ant_offset_t slen = str_len_fast(js, src);
array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 };
for (ant_offset_t i = 0; i < slen; ) {
ant_offset_t off = vstr(js, src, NULL);
utf8proc_int32_t cp;
ant_offset_t cb_len = (ant_offset_t)utf8_next(
(const utf8proc_uint8_t *)(uintptr_t)(off + i),
(utf8proc_ssize_t)(slen - i),
&cp
);
ant_value_t ch = js_mkstr(js, (const void *)(uintptr_t)(off + i), cb_len);
ant_value_t out;
iter_action_t act = array_from_iter_cb(js, ch, &ctx, &out);
if (act == ITER_ERROR) return out;
i += cb_len;
}
if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double)ctx.index));
} else if (vtype(src) == T_ARR) {
ant_offset_t iter_off = (vtype(iter_sym) == T_SYMBOL) ? lkp_sym_proto(js, src, (ant_offset_t)vdata(iter_sym)) : 0;
bool default_iter = iter_off != 0 && vtype(propref_load(js, iter_off)) == T_CFUNC;
if (default_iter) {
array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 };
ant_offset_t len = get_array_length(js, src);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t unused;
iter_action_t act = array_from_iter_cb(js, arr_get(js, src, i), &ctx, &unused);
if (act == ITER_ERROR) return unused;
}
if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double)len));
} else {
array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 };
ant_value_t iter_result = iter_foreach(js, src, array_from_iter_cb, &ctx);
if (is_err(iter_result)) return iter_result;
if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double)ctx.index));
}
} else {
ant_offset_t iter_prop = (vtype(iter_sym) == T_SYMBOL) ? lkp_sym_proto(js, src, (ant_offset_t)vdata(iter_sym)) : 0;
if (iter_prop != 0) {
array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 };
ant_value_t iter_result = iter_foreach(js, src, array_from_iter_cb, &ctx);
if (is_err(iter_result)) return iter_result;
if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double)ctx.index));
} else if (vtype(src) == T_OBJ) {
array_from_iter_ctx_t ctx = { write_target, result, mapFn, mapThis, 0 };
ant_offset_t len = get_array_length(js, src);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t unused;
iter_action_t act = array_from_iter_cb(js, arr_get(js, src, i), &ctx, &unused);
if (act == ITER_ERROR) return unused;
}
if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double)len));
}
}
if (!use_ctor) return mkval(T_ARR, vdata(result));
return result;
}
static ant_value_t builtin_Array_of(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ctor = js->this_val;
bool use_ctor = (vtype(ctor) == T_FUNC || vtype(ctor) == T_CFUNC);
ant_value_t arr = use_ctor ? array_alloc_from_ctor_with_length(js, ctor, (ant_offset_t)nargs) : mkarr(js);
if (is_err(arr)) return arr;
bool arr_is_proxy = is_proxy(arr);
ant_value_t write_target = arr_is_proxy ? proxy_read_target(js, arr) : arr;
for (int i = 0; i < nargs; i++) {
if (vtype(write_target) == T_ARR) arr_set(js, write_target, (ant_offset_t)i, args[i]);
else {
char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)i);
js_setprop(js, write_target, js_mkstr(js, idxstr, idxlen), args[i]);
}
}
if (vtype(arr) != T_ARR) js_setprop(js, arr, js->length_str, tov((double) nargs));
if (!use_ctor) return mkval(T_ARR, vdata(arr));
return arr;
}
static ant_value_t builtin_string_indexOf(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "indexOf called on non-string");
if (nargs == 0) return tov(-1);
ant_value_t search = args[0];
if (vtype(search) != T_STR) return tov(-1);
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
ant_offset_t search_len, search_off = vstr(js, search, &search_len);
ant_offset_t start = 0;
double dstr_len = D(str_len);
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double pos = tod(args[1]);
if (pos < 0) pos = 0;
if (pos > dstr_len) pos = dstr_len;
start = (ant_offset_t) pos;
}
if (search_len == 0) return tov(D(start));
if (start + search_len > str_len) return tov(-1);
const char *str_ptr = (char *)(uintptr_t)(str_off);
const char *search_ptr = (char *)(uintptr_t)(search_off);
for (ant_offset_t i = start; i <= str_len - search_len; i++) {
if (memcmp(str_ptr + i, search_ptr, search_len) == 0) return tov(D(i));
}
return tov(-1);
}
static ant_value_t builtin_string_substring(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "substring called on non-string");
ant_offset_t byte_len, str_off = vstr(js, str, &byte_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
size_t utf16_len = utf16_strlen(str_ptr, byte_len);
ant_offset_t start = 0, end = (ant_offset_t)utf16_len;
double dstr_len2 = D(utf16_len);
if (nargs >= 1 && vtype(args[0]) == T_NUM) {
double d = tod(args[0]);
start = (ant_offset_t) (d < 0 ? 0 : (d > dstr_len2 ? dstr_len2 : d));
}
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double d = tod(args[1]);
end = (ant_offset_t) (d < 0 ? 0 : (d > dstr_len2 ? dstr_len2 : d));
}
if (start > end) {
ant_offset_t tmp = start;
start = end;
end = tmp;
}
size_t byte_start, byte_end;
utf16_range_to_byte_range(str_ptr, byte_len, start, end, &byte_start, &byte_end);
return js_mkstr(js, str_ptr + byte_start, byte_end - byte_start);
}
static ant_value_t builtin_string_substr(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "substr called on non-string");
ant_offset_t byte_len, str_off = vstr(js, str, &byte_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
size_t utf16_len = utf16_strlen(str_ptr, byte_len);
if (nargs < 1) return js_mkstr(js, str_ptr, byte_len);
double d_start = tod(args[0]);
ant_offset_t start;
if (d_start < 0) {
start = (ant_offset_t)((double)utf16_len + d_start);
if ((int)start < 0) start = 0;
} else {
start = (ant_offset_t)d_start;
}
if (start > (ant_offset_t)utf16_len) start = (ant_offset_t)utf16_len;
ant_offset_t len = (ant_offset_t)utf16_len - start;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double d = tod(args[1]);
if (d < 0) d = 0;
len = (ant_offset_t)d;
}
if (start + len > (ant_offset_t)utf16_len) len = (ant_offset_t)utf16_len - start;
size_t byte_start, byte_end;
utf16_range_to_byte_range(str_ptr, byte_len, start, start + len, &byte_start, &byte_end);
return js_mkstr(js, str_ptr + byte_start, byte_end - byte_start);
}
static ant_value_t string_split_impl(ant_t *js, ant_value_t str, ant_value_t *args, int nargs) {
if (vtype(str) != T_STR) return js_mkerr(js, "split called on non-string");
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
ant_value_t arr = mkarr(js);
if (is_err(arr)) return arr;
uint32_t limit = UINT32_MAX;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double d = tod(args[1]);
if (d >= 0 && d <= UINT32_MAX) {
limit = (uint32_t)d;
}
}
if (limit == 0) {
return mkval(T_ARR, vdata(arr));
}
if (nargs == 0) goto return_whole;
ant_value_t sep_arg = args[0];
if (vtype(sep_arg) == T_OBJ) {
ant_offset_t source_off = lkp(js, sep_arg, "source", 6);
if (source_off == 0) goto return_whole;
ant_value_t source_val = propref_load(js, source_off);
if (vtype(source_val) != T_STR) goto return_whole;
ant_offset_t plen, poff = vstr(js, source_val, &plen);
const char *pattern_ptr = (char *)(uintptr_t)(poff);
if (plen == 0 || (plen == 4 && memcmp(pattern_ptr, "(?:)", 4) == 0)) {
ant_offset_t idx = 0;
for (ant_offset_t i = 0; i < str_len && idx < limit; i++) {
ant_value_t part = js_mkstr(js, str_ptr + i, 1);
arr_set(js, arr, idx, part);
idx++;
}
return mkval(T_ARR, vdata(arr));
}
char pcre2_pattern[512];
size_t pcre2_len = js_to_pcre2_pattern(pattern_ptr, plen, pcre2_pattern, sizeof(pcre2_pattern));
uint32_t options = PCRE2_UTF | PCRE2_UCP | PCRE2_MATCH_UNSET_BACKREF;
int errcode;
PCRE2_SIZE erroffset;
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pcre2_pattern, pcre2_len, options, &errcode, &erroffset, NULL);
if (re == NULL) goto return_whole;
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);
uint32_t capture_count;
pcre2_pattern_info(re, PCRE2_INFO_CAPTURECOUNT, &capture_count);
if (str_len == 0) {
int rc = pcre2_match(re, (PCRE2_SPTR)str_ptr, 0, 0, 0, match_data, NULL);
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return mkval(T_ARR, vdata(arr));
}
}
ant_offset_t idx = 0;
PCRE2_SIZE search_pos = 0;
PCRE2_SIZE segment_start = 0;
PCRE2_SIZE last_match_end = (PCRE2_SIZE)-1;
bool had_any_split = false;
while (idx < limit && search_pos <= str_len) {
int rc = pcre2_match(re, (PCRE2_SPTR)str_ptr, str_len, search_pos, 0, match_data, NULL);
if (rc < 0) break;
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
PCRE2_SIZE match_start = ovector[0];
PCRE2_SIZE match_end = ovector[1];
if (match_start == match_end && match_start == last_match_end) {
search_pos = match_end + 1;
continue;
}
if (match_start == match_end && capture_count > 0) {
bool is_pure_empty_capture = true;
for (uint32_t i = 1; i <= capture_count; i++) {
PCRE2_SIZE cap_start = ovector[2*i];
PCRE2_SIZE cap_end = ovector[2*i+1];
if (cap_start == PCRE2_UNSET || cap_end != cap_start) {
is_pure_empty_capture = false;
break;
}
}
if (is_pure_empty_capture) {
search_pos = match_end + 1;
continue;
}
}
had_any_split = true;
ant_value_t part = js_mkstr(js, str_ptr + segment_start, match_start - segment_start);
arr_set(js, arr, idx, part);
idx++;
for (uint32_t i = 1; i <= capture_count && idx < limit; i++) {
PCRE2_SIZE cap_start = ovector[2*i];
PCRE2_SIZE cap_end = ovector[2*i+1];
if (cap_start == PCRE2_UNSET) {
arr_set(js, arr, idx, js_mkundef());
} else {
part = js_mkstr(js, str_ptr + cap_start, cap_end - cap_start);
arr_set(js, arr, idx, part);
}
idx++;
}
last_match_end = match_end;
segment_start = match_end;
if (match_start == match_end) {
search_pos = match_end + 1;
} else {
search_pos = match_end;
}
}
if (!had_any_split) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
arr_set(js, arr, 0, js_mkstr(js, str_ptr, str_len));
return mkval(T_ARR, vdata(arr));
}
if (idx < limit) {
ant_value_t part = js_mkstr(js, str_ptr + segment_start, str_len - segment_start);
arr_set(js, arr, idx, part);
idx++;
}
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return mkval(T_ARR, vdata(arr));
}
if (vtype(sep_arg) != T_STR) goto return_whole;
ant_offset_t sep_len, sep_off = vstr(js, sep_arg, &sep_len);
const char *sep_ptr = (char *)(uintptr_t)(sep_off);
ant_offset_t idx = 0, start = 0;
if (sep_len == 0) {
for (ant_offset_t i = 0; i < str_len && idx < limit; i++) {
ant_value_t part = js_mkstr(js, str_ptr + i, 1);
arr_set(js, arr, idx, part);
idx++;
}
return mkval(T_ARR, vdata(arr));
}
for (ant_offset_t i = 0; i + sep_len <= str_len && idx < limit; i++) {
if (memcmp(str_ptr + i, sep_ptr, sep_len) != 0) continue;
ant_value_t part = js_mkstr(js, str_ptr + start, i - start);
arr_set(js, arr, idx, part);
idx++;
start = i + sep_len;
i += sep_len - 1;
}
if (idx < limit && start <= str_len) {
ant_value_t part = js_mkstr(js, str_ptr + start, str_len - start);
arr_set(js, arr, idx, part);
idx++;
}
return mkval(T_ARR, vdata(arr));
return_whole:
if (limit > 0) {
arr_set(js, arr, 0, str);
}
return mkval(T_ARR, vdata(arr));
}
static ant_value_t builtin_string_split(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "split called on non-string");
if (nargs > 0 && is_object_type(args[0])) {
bool called = false;
ant_value_t call_args[2];
int call_nargs = 1;
call_args[0] = str;
if (nargs >= 2) {
call_args[1] = args[1];
call_nargs = 2;
}
ant_value_t dispatched = maybe_call_symbol_method(
js, args[0], get_split_sym(), args[0], call_args, call_nargs, &called
);
if (is_err(dispatched)) return dispatched;
if (called) return dispatched;
}
return string_split_impl(js, str, args, nargs);
}
static ant_value_t builtin_string_slice(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_unwrapped = unwrap_primitive(js, js->this_val);
ant_value_t str = js_tostring_val(js, this_unwrapped);
if (is_err(str)) return str;
ant_offset_t byte_len, str_off = vstr(js, str, &byte_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
size_t utf16_len = utf16_strlen(str_ptr, byte_len);
ant_offset_t start = 0, end = (ant_offset_t)utf16_len;
double dstr_len = D(utf16_len);
if (nargs >= 1 && vtype(args[0]) == T_NUM) {
double d = tod(args[0]);
if (d < 0) {
start = (ant_offset_t) (d + dstr_len < 0 ? 0 : d + dstr_len);
} else start = (ant_offset_t) (d > dstr_len ? dstr_len : d);
}
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double d = tod(args[1]);
if (d < 0) {
end = (ant_offset_t) (d + dstr_len < 0 ? 0 : d + dstr_len);
} else end = (ant_offset_t) (d > dstr_len ? dstr_len : d);
}
if (start > end) start = end;
size_t byte_start, byte_end;
utf16_range_to_byte_range(str_ptr, byte_len, start, end, &byte_start, &byte_end);
return js_mkstr(js, str_ptr + byte_start, byte_end - byte_start);
}
static ant_value_t builtin_string_includes(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "includes called on non-string");
if (nargs == 0) return mkval(T_BOOL, 0);
ant_value_t search = args[0];
if (is_object_type(search)) {
ant_value_t maybe_err = reject_regexp_arg(js, search, "includes");
if (is_err(maybe_err)) return maybe_err;
}
search = js_tostring_val(js, search);
if (is_err(search)) return search;
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
ant_offset_t search_len, search_off = vstr(js, search, &search_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
const char *search_ptr = (char *)(uintptr_t)(search_off);
ant_offset_t start = 0;
if (nargs >= 2) {
double pos = tod(args[1]);
if (isnan(pos) || pos < 0) pos = 0;
if (pos > D(str_len)) return mkval(T_BOOL, 0);
start = (ant_offset_t) pos;
}
if (search_len == 0) return mkval(T_BOOL, 1);
if (start + search_len > str_len) return mkval(T_BOOL, 0);
for (ant_offset_t i = start; i <= str_len - search_len; i++) {
if (memcmp(str_ptr + i, search_ptr, search_len) == 0) return mkval(T_BOOL, 1);
}
return mkval(T_BOOL, 0);
}
static ant_value_t builtin_string_startsWith(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "startsWith called on non-string");
if (nargs == 0) return mkval(T_BOOL, 0);
ant_value_t search = args[0];
if (is_object_type(search)) {
ant_value_t maybe_err = reject_regexp_arg(js, search, "startsWith");
if (is_err(maybe_err)) return maybe_err;
}
search = js_tostring_val(js, search);
if (is_err(search)) return search;
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
ant_offset_t search_len, search_off = vstr(js, search, &search_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
const char *search_ptr = (char *)(uintptr_t)(search_off);
if (search_len > str_len) return mkval(T_BOOL, 0);
if (search_len == 0) return mkval(T_BOOL, 1);
return mkval(T_BOOL, memcmp(str_ptr, search_ptr, search_len) == 0 ? 1 : 0);
}
static ant_value_t builtin_string_endsWith(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "endsWith called on non-string");
if (nargs == 0) return mkval(T_BOOL, 0);
ant_value_t search = args[0];
if (is_object_type(search)) {
ant_value_t maybe_err = reject_regexp_arg(js, search, "endsWith");
if (is_err(maybe_err)) return maybe_err;
}
search = js_tostring_val(js, search);
if (is_err(search)) return search;
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
ant_offset_t search_len, search_off = vstr(js, search, &search_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
const char *search_ptr = (char *)(uintptr_t)(search_off);
if (search_len > str_len) return mkval(T_BOOL, 0);
if (search_len == 0) return mkval(T_BOOL, 1);
return mkval(T_BOOL, memcmp(str_ptr + str_len - search_len, search_ptr, search_len) == 0 ? 1 : 0);
}
static ant_value_t builtin_string_template(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "template called on non-string");
if (nargs < 1 || vtype(args[0]) != T_OBJ) return str;
ant_value_t data = args[0];
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
size_t result_cap = str_len + 256;
size_t result_len = 0;
char *result = (char *)ant_calloc(result_cap);
if (!result) return js_mkerr(js, "oom");
ant_offset_t i = 0;
#define ENSURE_CAP(need) do { \
if (result_len + (need) >= result_cap) { \
result_cap = (result_len + (need) + 1) * 2; \
char *nr = (char *)ant_realloc(result, result_cap); \
if (!nr) return js_mkerr(js, "oom"); \
result = nr; \
} \
} while(0)
while (i < str_len) {
if (i < str_len - 3 && str_ptr[i] == '{' && str_ptr[i + 1] == '{') {
ant_offset_t start = i + 2;
ant_offset_t end = start;
while (end < str_len - 1 && !(str_ptr[end] == '}' && str_ptr[end + 1] == '}')) {
end++;
}
if (end < str_len - 1 && str_ptr[end] == '}' && str_ptr[end + 1] == '}') {
ant_offset_t key_len = end - start;
ant_offset_t prop_off = lkp(js, data, str_ptr + start, key_len);
if (prop_off != 0) {
ant_value_t value = propref_load(js, prop_off);
if (vtype(value) == T_STR) {
ant_offset_t val_len, val_off = vstr(js, value, &val_len);
ENSURE_CAP(val_len);
memcpy(result + result_len, (const void *)(uintptr_t)val_off, val_len);
result_len += val_len;
} else if (vtype(value) == T_NUM) {
char numstr[32];
snprintf(numstr, sizeof(numstr), "%g", tod(value));
size_t num_len = strlen(numstr);
ENSURE_CAP(num_len);
memcpy(result + result_len, numstr, num_len);
result_len += num_len;
} else if (vtype(value) == T_BOOL) {
const char *boolstr = vdata(value) ? "true" : "false";
size_t bool_len = strlen(boolstr);
ENSURE_CAP(bool_len);
memcpy(result + result_len, boolstr, bool_len);
result_len += bool_len;
}
}
i = end + 2;
continue;
}
}
ENSURE_CAP(1);
result[result_len++] = str_ptr[i++];
}
ant_value_t ret = js_mkstr(js, result, result_len);
free(result);
return ret;
#undef ENSURE_CAP
}
static ant_value_t builtin_string_charCodeAt(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "charCodeAt called on non-string");
double idx_d = nargs < 1 ? 0.0 : js_to_number(js, args[0]);
if (isnan(idx_d)) idx_d = 0.0;
if (isinf(idx_d) || idx_d > (double)LONG_MAX) return tov(JS_NAN);
long idx_l = (long) idx_d;
if (idx_l < 0) return tov(JS_NAN);
ant_offset_t byte_len; ant_offset_t str_off = vstr(js, str, &byte_len);
const char *str_data = (const char *)(uintptr_t)(str_off);
uint32_t code_unit = utf16_code_unit_at(str_data, byte_len, idx_l);
if (code_unit == 0xFFFFFFFF) return tov(JS_NAN);
return tov((double) code_unit);
}
static ant_value_t builtin_string_codePointAt(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "codePointAt called on non-string");
double idx_d = nargs < 1 ? 0.0 : js_to_number(js, args[0]);
if (isnan(idx_d)) idx_d = 0.0;
if (isinf(idx_d) || idx_d > (double)LONG_MAX) return js_mkundef();
long idx_l = (long) idx_d;
if (idx_l < 0) return js_mkundef();
ant_offset_t byte_len;
ant_offset_t str_off = vstr(js, str, &byte_len);
const char *str_data = (const char *)(uintptr_t)(str_off);
uint32_t cp = utf16_codepoint_at(str_data, byte_len, idx_l);
if (cp == 0xFFFFFFFF) return js_mkundef();
return tov((double) cp);
}
static ant_value_t builtin_string_toLowerCase(ant_t *js, ant_value_t *args, int nargs) {
(void) args; (void) nargs;
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "toLowerCase called on non-string");
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
if (str_len == 0) return js_mkstr(js, "", 0);
const utf8proc_uint8_t *src = (const utf8proc_uint8_t *)str_ptr;
utf8proc_ssize_t src_len = (utf8proc_ssize_t)str_len;
ant_offset_t out_len = 0;
utf8proc_ssize_t pos = 0;
while (pos < src_len) {
utf8proc_int32_t cp;
utf8proc_ssize_t n = utf8_next(src + pos, src_len - pos, &cp);
if (cp < 0) { out_len++; pos++; continue; }
utf8proc_uint8_t tmp[4];
out_len += (ant_offset_t)utf8proc_encode_char(utf8proc_tolower(cp), tmp);
pos += n;
}
ant_value_t result = js_mkstr(js, NULL, out_len);
if (is_err(result)) return result;
ant_offset_t result_len, result_off = vstr(js, result, &result_len);
char *result_ptr = (char *)(uintptr_t)(result_off);
pos = 0;
ant_offset_t wpos = 0;
while (pos < src_len) {
utf8proc_int32_t cp;
utf8proc_ssize_t n = utf8_next(src + pos, src_len - pos, &cp);
if (cp < 0) { result_ptr[wpos++] = (char)src[pos]; pos++; continue; }
wpos += (ant_offset_t)utf8proc_encode_char(utf8proc_tolower(cp), (utf8proc_uint8_t *)(result_ptr + wpos));
pos += n;
}
return result;
}
static ant_value_t builtin_string_toUpperCase(ant_t *js, ant_value_t *args, int nargs) {
(void) args; (void) nargs;
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "toUpperCase called on non-string");
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
if (str_len == 0) return js_mkstr(js, "", 0);
const utf8proc_uint8_t *src = (const utf8proc_uint8_t *)str_ptr;
utf8proc_ssize_t src_len = (utf8proc_ssize_t)str_len;
ant_offset_t out_len = 0;
utf8proc_ssize_t pos = 0;
while (pos < src_len) {
utf8proc_int32_t cp;
utf8proc_ssize_t n = utf8_next(src + pos, src_len - pos, &cp);
if (cp < 0) { out_len++; pos++; continue; }
utf8proc_uint8_t tmp[4];
out_len += (ant_offset_t)utf8proc_encode_char(utf8proc_toupper(cp), tmp);
pos += n;
}
ant_value_t result = js_mkstr(js, NULL, out_len);
if (is_err(result)) return result;
ant_offset_t result_len, result_off = vstr(js, result, &result_len);
char *result_ptr = (char *)(uintptr_t)(result_off);
pos = 0;
ant_offset_t wpos = 0;
while (pos < src_len) {
utf8proc_int32_t cp;
utf8proc_ssize_t n = utf8_next(src + pos, src_len - pos, &cp);
if (cp < 0) { result_ptr[wpos++] = (char)src[pos]; pos++; continue; }
wpos += (ant_offset_t)utf8proc_encode_char(utf8proc_toupper(cp), (utf8proc_uint8_t *)(result_ptr + wpos));
pos += n;
}
return result;
}
static ant_value_t builtin_string_trim(ant_t *js, ant_value_t *args, int nargs) {
(void) args; (void) nargs;
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "trim called on non-string");
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
ant_offset_t start = 0, end = str_len;
while (start < end && is_space(str_ptr[start])) start++;
while (end > start && is_space(str_ptr[end - 1])) end--;
return js_mkstr(js, str_ptr + start, end - start);
}
static ant_value_t builtin_string_trimStart(ant_t *js, ant_value_t *args, int nargs) {
(void) args; (void) nargs;
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "trimStart called on non-string");
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
ant_offset_t start = 0;
while (start < str_len && is_space(str_ptr[start])) start++;
return js_mkstr(js, str_ptr + start, str_len - start);
}
static ant_value_t builtin_string_trimEnd(ant_t *js, ant_value_t *args, int nargs) {
(void) args; (void) nargs;
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "trimEnd called on non-string");
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
ant_offset_t end = str_len;
while (end > 0 && is_space(str_ptr[end - 1])) end--;
return js_mkstr(js, str_ptr, end);
}
static ant_value_t builtin_string_repeat(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "repeat called on non-string");
if (nargs < 1 || vtype(args[0]) != T_NUM) return js_mkerr(js, "repeat count required");
double count_d = tod(args[0]);
if (count_d < 0 || count_d != (double)(long)count_d) return js_mkerr(js, "invalid repeat count");
ant_offset_t count = (ant_offset_t) count_d;
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
if (count == 0 || str_len == 0) return js_mkstr(js, "", 0);
ant_value_t result = js_mkstr(js, NULL, str_len * count);
if (is_err(result)) return result;
ant_offset_t result_len, result_off = vstr(js, result, &result_len);
char *result_ptr = (char *)(uintptr_t)(result_off);
for (ant_offset_t i = 0; i < count; i++) {
memcpy(result_ptr + i * str_len, str_ptr, str_len);
}
return result;
}
static ant_value_t builtin_string_padStart(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "padStart called on non-string");
if (nargs < 1 || vtype(args[0]) != T_NUM) return str;
ant_offset_t target_len = (ant_offset_t)tod(args[0]);
if (target_len <= 0) return str;
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
size_t str_utf16_len = utf16_strlen(str_ptr, (size_t)str_len);
if ((size_t)target_len <= str_utf16_len) return str;
ant_value_t pad_val = js_mkstr(js, " ", 1);
if (nargs >= 2 && vtype(args[1]) != T_UNDEF) {
pad_val = coerce_to_str(js, args[1]);
if (is_err(pad_val)) return pad_val;
}
ant_offset_t pad_len, pad_off = vstr(js, pad_val, &pad_len);
const char *pad_str = (char *)(uintptr_t)(pad_off);
size_t pad_utf16_len = utf16_strlen(pad_str, (size_t)pad_len);
if (pad_utf16_len == 0) return str;
size_t fill_utf16_len = (size_t)target_len - str_utf16_len;
size_t full_repeats = fill_utf16_len / pad_utf16_len;
size_t rem_utf16 = fill_utf16_len % pad_utf16_len;
size_t rem_bytes = 0;
if (rem_utf16 > 0) {
int off = utf16_index_to_byte_offset(pad_str, (size_t)pad_len, rem_utf16, NULL);
if (off < 0) return str;
rem_bytes = (size_t)off;
}
size_t fill_bytes = full_repeats * (size_t)pad_len + rem_bytes;
size_t total_bytes = fill_bytes + (size_t)str_len;
ant_value_t result = js_mkstr(js, NULL, (ant_offset_t)total_bytes);
if (is_err(result)) return result;
ant_offset_t result_len, result_off = vstr(js, result, &result_len);
char *result_ptr = (char *)(uintptr_t)(result_off);
size_t pos = 0;
for (size_t i = 0; i < full_repeats; i++) {
memcpy(result_ptr + pos, pad_str, (size_t)pad_len);
pos += (size_t)pad_len;
}
if (rem_bytes > 0) {
memcpy(result_ptr + pos, pad_str, rem_bytes);
pos += rem_bytes;
}
memcpy(result_ptr + pos, str_ptr, (size_t)str_len);
return result;
}
static ant_value_t builtin_string_padEnd(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "padEnd called on non-string");
if (nargs < 1 || vtype(args[0]) != T_NUM) return str;
ant_offset_t target_len = (ant_offset_t)tod(args[0]);
if (target_len <= 0) return str;
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
size_t str_utf16_len = utf16_strlen(str_ptr, (size_t)str_len);
if ((size_t)target_len <= str_utf16_len) return str;
ant_value_t pad_val = js_mkstr(js, " ", 1);
if (nargs >= 2 && vtype(args[1]) != T_UNDEF) {
pad_val = coerce_to_str(js, args[1]);
if (is_err(pad_val)) return pad_val;
}
ant_offset_t pad_len, pad_off = vstr(js, pad_val, &pad_len);
const char *pad_str = (char *)(uintptr_t)(pad_off);
size_t pad_utf16_len = utf16_strlen(pad_str, (size_t)pad_len);
if (pad_utf16_len == 0) return str;
size_t fill_utf16_len = (size_t)target_len - str_utf16_len;
size_t full_repeats = fill_utf16_len / pad_utf16_len;
size_t rem_utf16 = fill_utf16_len % pad_utf16_len;
size_t rem_bytes = 0;
if (rem_utf16 > 0) {
int off = utf16_index_to_byte_offset(pad_str, (size_t)pad_len, rem_utf16, NULL);
if (off < 0) return str;
rem_bytes = (size_t)off;
}
size_t fill_bytes = full_repeats * (size_t)pad_len + rem_bytes;
size_t total_bytes = (size_t)str_len + fill_bytes;
ant_value_t result = js_mkstr(js, NULL, (ant_offset_t)total_bytes);
if (is_err(result)) return result;
ant_offset_t result_len, result_off = vstr(js, result, &result_len);
char *result_ptr = (char *)(uintptr_t)(result_off);
memcpy(result_ptr, str_ptr, (size_t)str_len);
size_t pos = (size_t)str_len;
for (size_t i = 0; i < full_repeats; i++) {
memcpy(result_ptr + pos, pad_str, (size_t)pad_len);
pos += (size_t)pad_len;
}
if (rem_bytes > 0) {
memcpy(result_ptr + pos, pad_str, rem_bytes);
pos += rem_bytes;
}
return result;
}
static ant_value_t builtin_string_charAt(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "charAt called on non-string");
double idx_d = nargs < 1 ? 0.0 : js_to_number(js, args[0]);
if (isnan(idx_d)) idx_d = 0;
else if (idx_d < 0) idx_d = -floor(-idx_d);
else idx_d = floor(idx_d);
if (idx_d < 0 || isinf(idx_d)) return js_mkstr(js, "", 0);
ant_offset_t idx = (ant_offset_t) idx_d;
ant_offset_t byte_len;
ant_offset_t str_off = vstr(js, str, &byte_len);
const char *str_data = (const char *)(uintptr_t)(str_off);
uint32_t code_unit = utf16_code_unit_at(str_data, byte_len, idx);
if (code_unit == 0xFFFFFFFF) return js_mkstr(js, "", 0);
return js_string_from_utf16_code_unit(js, code_unit);
}
static ant_value_t builtin_string_at(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "at called on non-string");
double idx_d = nargs < 1 ? 0.0 : js_to_number(js, args[0]);
if (isnan(idx_d) || isinf(idx_d)) return js_mkundef();
ant_offset_t byte_len; ant_offset_t str_off = vstr(js, str, &byte_len);
const char *str_data = (const char *)(uintptr_t)(str_off);
size_t utf16_len = utf16_strlen(str_data, byte_len);
long idx = (long) idx_d;
if (idx < 0) idx += (long) utf16_len;
if (idx < 0 || idx >= (long) utf16_len) return js_mkundef();
uint32_t code_unit = utf16_code_unit_at(str_data, byte_len, idx);
if (code_unit == 0xFFFFFFFF) return js_mkundef();
return js_string_from_utf16_code_unit(js, code_unit);
}
static ant_value_t builtin_string_localeCompare(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "localeCompare called on non-string");
if (nargs < 1) return tov(0);
ant_value_t that = args[0];
if (vtype(that) != T_STR) {
char buf[64];
size_t n = tostr(js, that, buf, sizeof(buf));
that = js_mkstr(js, buf, n);
}
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
ant_offset_t that_len, that_off = vstr(js, that, &that_len);
const char *str_ptr = (char *)(uintptr_t)(str_off);
const char *that_ptr = (char *)(uintptr_t)(that_off);
int result = strcoll(str_ptr, that_ptr);
if (result < 0) return tov(-1);
if (result > 0) return tov(1);
return tov(0);
}
static ant_value_t builtin_string_lastIndexOf(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "lastIndexOf called on non-string");
if (nargs == 0) return tov(-1);
ant_value_t search = args[0];
if (vtype(search) != T_STR) return tov(-1);
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
ant_offset_t search_len, search_off = vstr(js, search, &search_len);
ant_offset_t max_start = str_len;
double dstr_len = D(str_len);
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double pos = tod(args[1]);
if (isnan(pos)) pos = dstr_len;
if (pos < 0) pos = 0;
if (pos > dstr_len) pos = dstr_len;
max_start = (ant_offset_t) pos;
}
if (search_len == 0) return tov((double) (max_start > str_len ? str_len : max_start));
if (search_len > str_len) return tov(-1);
const char *str_ptr = (char *)(uintptr_t)(str_off);
const char *search_ptr = (char *)(uintptr_t)(search_off);
ant_offset_t start = (max_start + search_len > str_len) ? str_len - search_len : max_start;
for (ant_offset_t i = start + 1; i > 0; i--) {
if (memcmp(str_ptr + i - 1, search_ptr, search_len) == 0) return tov((double)(i - 1));
}
return tov(-1);
}
static ant_value_t builtin_string_concat(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_unwrapped = unwrap_primitive(js, js->this_val);
ant_value_t str = js_tostring_val(js, this_unwrapped);
if (is_err(str)) return str;
ant_offset_t total_len;
ant_offset_t base_off = vstr(js, str, &total_len);
ant_value_t *str_args = NULL;
if (nargs > 0) {
str_args = (ant_value_t *)ant_calloc(nargs * sizeof(ant_value_t));
if (!str_args) return js_mkerr(js, "oom");
for (int i = 0; i < nargs; i++) {
str_args[i] = js_tostring_val(js, args[i]);
if (is_err(str_args[i])) {
free(str_args);
return str_args[i];
}
ant_offset_t arg_len;
vstr(js, str_args[i], &arg_len);
total_len += arg_len;
}
}
char *result = (char *)ant_calloc(total_len + 1);
if (!result) {
if (str_args) free(str_args);
return js_mkerr(js, "oom");
}
ant_offset_t base_len;
base_off = vstr(js, str, &base_len);
memcpy(result, (const void *)(uintptr_t)base_off, base_len);
ant_offset_t pos = base_len;
for (int i = 0; i < nargs; i++) {
ant_offset_t arg_len, arg_off = vstr(js, str_args[i], &arg_len);
memcpy(result + pos, (const void *)(uintptr_t)arg_off, arg_len);
pos += arg_len;
}
result[pos] = '\0';
ant_value_t ret = js_mkstr(js, result, pos);
free(result); if (str_args) free(str_args);
return ret;
}
static ant_value_t builtin_string_normalize(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "normalize called on non-string");
ant_offset_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (const char *)(uintptr_t)(str_off);
if (str_len == 0) return js_mkstr(js, "", 0);
utf8proc_option_t opts = UTF8PROC_COMPOSE | UTF8PROC_STABLE;
if (nargs >= 1 && vtype(args[0]) != T_UNDEF) {
ant_value_t form_val = js_tostring_val(js, args[0]);
if (is_err(form_val)) return form_val;
ant_offset_t flen, foff = vstr(js, form_val, &flen);
const char *form = (const char *)(uintptr_t)(foff);
if (flen == 3 && memcmp(form, "NFC", 3) == 0) {
opts = UTF8PROC_COMPOSE | UTF8PROC_STABLE;
} else if (flen == 3 && memcmp(form, "NFD", 3) == 0) {
opts = UTF8PROC_DECOMPOSE | UTF8PROC_STABLE;
} else if (flen == 4 && memcmp(form, "NFKC", 4) == 0) {
opts = UTF8PROC_COMPOSE | UTF8PROC_STABLE | UTF8PROC_COMPAT;
} else if (flen == 4 && memcmp(form, "NFKD", 4) == 0) {
opts = UTF8PROC_DECOMPOSE | UTF8PROC_STABLE | UTF8PROC_COMPAT;
} else return js_mkerr_typed(js, JS_ERR_RANGE, "The normalization form should be one of NFC, NFD, NFKC, NFKD");
}
utf8proc_uint8_t *result = NULL;
utf8proc_ssize_t rlen = utf8proc_map(
(const utf8proc_uint8_t *)str_ptr, (utf8proc_ssize_t)str_len, &result, opts
);
if (rlen < 0 || !result) {
if (result) free(result);
return js_mkstr(js, str_ptr, str_len);
}
ant_value_t ret = js_mkstr(js, (const char *)result, (ant_offset_t)rlen);
free(result);
return ret;
}
static ant_value_t builtin_string_fromCharCode(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_mkstr(js, "", 0);
char *buf = (char *)ant_calloc(nargs + 1);
if (!buf) return js_mkerr(js, "oom");
for (int i = 0; i < nargs; i++) {
if (vtype(args[i]) != T_NUM) { buf[i] = 0; continue; }
int code = (int) tod(args[i]);
buf[i] = (char)(code & 0xFF);
}
buf[nargs] = '\0';
ant_value_t ret = js_mkstr(js, buf, nargs);
free(buf);
return ret;
}
static ant_value_t builtin_string_fromCodePoint(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return js_mkstr(js, "", 0);
char *buf = (char *)ant_calloc(nargs * 4 + 1);
if (!buf) return js_mkerr(js, "oom");
size_t len = 0;
for (int i = 0; i < nargs; i++) {
if (vtype(args[i]) != T_NUM) continue;
double d = tod(args[i]);
if (d < 0 || d > 0x10FFFF || d != floor(d)) {
free(buf);
return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid code point");
}
uint32_t cp = (uint32_t)d;
if (cp < 0x80) {
buf[len++] = (char)cp;
} else if (cp < 0x800) {
buf[len++] = (char)(0xC0 | (cp >> 6));
buf[len++] = (char)(0x80 | (cp & 0x3F));
} else if (cp < 0x10000) {
buf[len++] = (char)(0xE0 | (cp >> 12));
buf[len++] = (char)(0x80 | ((cp >> 6) & 0x3F));
buf[len++] = (char)(0x80 | (cp & 0x3F));
} else {
buf[len++] = (char)(0xF0 | (cp >> 18));
buf[len++] = (char)(0x80 | ((cp >> 12) & 0x3F));
buf[len++] = (char)(0x80 | ((cp >> 6) & 0x3F));
buf[len++] = (char)(0x80 | (cp & 0x3F));
}
}
buf[len] = '\0';
ant_value_t ret = js_mkstr(js, buf, len);
free(buf);
return ret;
}
static bool string_builder_append_value(
ant_t *js, char **buf,
size_t *len, size_t *cap,
ant_value_t value, ant_value_t *err
) {
ant_value_t s = js_tostring_val(js, value);
if (is_err(s)) {
if (err) *err = s;
return false;
}
ant_offset_t slen = 0;
ant_offset_t soff = vstr(js, s, &slen);
size_t need = *len + (size_t)slen + 1;
if (need > *cap) {
size_t next = (*cap == 0) ? 64 : *cap;
while (next < need) next *= 2;
char *grown = (char *)realloc(*buf, next);
if (!grown) {
if (err) *err = js_mkerr(js, "oom");
return false;
}
*buf = grown;
*cap = next;
}
if (slen > 0) memcpy(*buf + *len, (const void *)(uintptr_t)soff, (size_t)slen);
*len += (size_t)slen;
(*buf)[*len] = '\0';
return true;
}
static ant_value_t builtin_string_raw(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1 || is_null(args[0]) || is_undefined(args[0])) {
return js_mkerr_typed(js, JS_ERR_TYPE, "String.raw requires a template object");
}
ant_value_t tmpl = args[0];
if (!is_object_type(tmpl)) {
return js_mkerr_typed(js, JS_ERR_TYPE, "String.raw requires a template object");
}
ant_value_t raw = js_get(js, tmpl, "raw");
if (is_null(raw) || is_undefined(raw) || !is_object_type(raw)) {
return js_mkerr_typed(js, JS_ERR_TYPE, "String.raw requires template.raw");
}
ant_value_t raw_len_val = js_get(js, raw, "length");
double raw_len_num = js_to_number(js, raw_len_val);
if (!isfinite(raw_len_num) || raw_len_num <= 0) return js_mkstr(js, "", 0);
size_t literal_count = (size_t)raw_len_num;
if (literal_count == 0) return js_mkstr(js, "", 0);
char *buf = NULL;
size_t len = 0; size_t cap = 0;
ant_value_t err = js_mkundef();
for (size_t i = 0; i < literal_count; i++) {
ant_value_t chunk = js_mkundef();
if (vtype(raw) == T_ARR) chunk = js_arr_get(js, raw, (ant_offset_t)i);
else {
char key[32];
snprintf(key, sizeof(key), "%zu", i);
chunk = js_get(js, raw, key);
}
if (!string_builder_append_value(js, &buf, &len, &cap, chunk, &err)) {
free(buf);
return is_err(err) ? err : js_mkerr(js, "oom");
}
if (i + 1 < literal_count && (int)(i + 1) < nargs) {
if (!string_builder_append_value(js, &buf, &len, &cap, args[i + 1], &err)) {
free(buf); return is_err(err) ? err : js_mkerr(js, "oom");
}
}
}
ant_value_t out = js_mkstr(js, buf ? buf : "", len);
free(buf);
return out;
}
static ant_value_t builtin_number_toString(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t num = unwrap_primitive(js, js->this_val);
if (vtype(num) != T_NUM) return js_mkerr(js, "toString called on non-number");
int radix = 10;
if (nargs >= 1 && vtype(args[0]) == T_NUM) {
radix = (int)tod(args[0]);
if (radix < 2 || radix > 36) {
return js_mkerr(js, "radix must be between 2 and 36");
}
}
if (radix == 10) {
char buf[64];
size_t len = strnum(num, buf, sizeof(buf));
return js_mkstr(js, buf, len);
}
double val = tod(num);
if (isnan(val)) return js_mkstr(js, "NaN", 3);
if (isinf(val)) return val > 0 ? js_mkstr(js, "Infinity", 8) : js_mkstr(js, "-Infinity", 9);
char buf[128];
char *p = buf + sizeof(buf) - 1;
*p = '\0';
bool negative = val < 0;
if (negative) val = -val;
long long int_part = (long long)val;
double frac_part = val - (double)int_part;
if (int_part == 0) {
*--p = '0';
} else {
while (int_part > 0 && p > buf) {
int digit = int_part % radix;
*--p = (char)(digit < 10 ? '0' + digit : 'a' + (digit - 10));
int_part /= radix;
}
}
if (negative && p > buf) {
*--p = '-';
}
size_t int_len = strlen(p);
if (frac_part > 0.0000001) {
char frac_buf[64];
int frac_pos = 0;
frac_buf[frac_pos++] = '.';
for (int i = 0; i < 16 && frac_part > 0.0000001 && frac_pos < 63; i++) {
frac_part *= radix;
int digit = (int)frac_part;
frac_buf[frac_pos++] = (char)(digit < 10 ? '0' + digit : 'a' + (digit - 10));
frac_part -= digit;
}
frac_buf[frac_pos] = '\0';
char result[192];
snprintf(result, sizeof(result), "%s%s", p, frac_buf);
return js_mkstr(js, result, strlen(result));
}
return js_mkstr(js, p, int_len);
}
static ant_value_t builtin_number_toFixed(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t num = unwrap_primitive(js, js->this_val);
if (vtype(num) != T_NUM) return js_mkerr(js, "toFixed called on non-number");
double d = tod(num);
if (isnan(d)) return js_mkstr(js, "NaN", 3);
if (isinf(d)) return d > 0 ? js_mkstr(js, "Infinity", 8) : js_mkstr(js, "-Infinity", 9);
int digits = 0;
if (nargs >= 1 && vtype(args[0]) != T_UNDEF) {
digits = (int) tod(args[0]);
if (digits < 0 || digits > 100) {
return js_mkerr_typed(js, JS_ERR_RANGE, "toFixed() digits argument must be between 0 and 100");
}
}
bool negative = d < 0;
if (negative) d = -d;
if (d >= 1e21) {
char buf[64];
snprintf(buf, sizeof(buf), "%.0f", negative ? -d : d);
return js_mkstr(js, buf, strlen(buf));
}
double scale = pow(10, digits);
double scaled = d * scale;
double rounded = floor(scaled + 0.5);
char digit_buf[128];
snprintf(digit_buf, sizeof(digit_buf), "%.0f", rounded);
int digit_len = (int)strlen(digit_buf);
while (digit_len < digits + 1) {
memmove(digit_buf + 1, digit_buf, digit_len + 1);
digit_buf[0] = '0';
digit_len++;
}
char buf[128];
int pos = 0;
if (negative && rounded != 0) buf[pos++] = '-';
int int_digits = digit_len - digits;
if (int_digits <= 0) int_digits = 1;
for (int i = 0; i < int_digits; i++) {
buf[pos++] = digit_buf[i];
}
if (digits > 0) {
buf[pos++] = '.';
for (int i = int_digits; i < digit_len; i++) {
buf[pos++] = digit_buf[i];
}
}
buf[pos] = '\0';
return js_mkstr(js, buf, pos);
}
static ant_value_t builtin_number_toPrecision(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t num = unwrap_primitive(js, js->this_val);
if (vtype(num) != T_NUM) return js_mkerr(js, "toPrecision called on non-number");
double d = tod(num);
if (isnan(d)) return js_mkstr(js, "NaN", 3);
if (isinf(d)) return d > 0 ? js_mkstr(js, "Infinity", 8) : js_mkstr(js, "-Infinity", 9);
if (nargs < 1 || vtype(args[0]) == T_UNDEF) {
char buf[64];
size_t len = strnum(num, buf, sizeof(buf));
return js_mkstr(js, buf, len);
}
int precision = (int) tod(args[0]);
if (precision < 1 || precision > 100) {
return js_mkerr_typed(js, JS_ERR_RANGE, "toPrecision() argument must be between 1 and 100");
}
bool negative = d < 0;
if (negative) d = -d;
if (d == 0) {
char buf[128];
int pos = 0;
if (negative) buf[pos++] = '-';
buf[pos++] = '0';
if (precision > 1) {
buf[pos++] = '.';
for (int i = 1; i < precision; i++) buf[pos++] = '0';
}
buf[pos] = '\0';
return js_mkstr(js, buf, pos);
}
int exp = (int) floor(log10(d));
bool use_exp = (exp < -(precision - 1) - 1) || (exp >= precision);
if (use_exp) {
double mantissa = d / pow(10, exp);
double scale = pow(10, precision - 1);
double rounded = floor(mantissa * scale + 0.5);
if (rounded >= scale * 10) {
rounded /= 10;
exp++;
}
char digit_buf[32];
snprintf(digit_buf, sizeof(digit_buf), "%.0f", rounded);
int digit_len = (int)strlen(digit_buf);
char buf[128];
int pos = 0;
if (negative) buf[pos++] = '-';
buf[pos++] = digit_buf[0];
if (precision > 1) {
buf[pos++] = '.';
for (int i = 1; i < precision; i++) {
buf[pos++] = (i < digit_len) ? digit_buf[i] : '0';
}
}
buf[pos++] = 'e';
buf[pos++] = (exp >= 0) ? '+' : '-';
if (exp < 0) exp = -exp;
snprintf(buf + pos, sizeof(buf) - pos, "%d", exp);
return js_mkstr(js, buf, strlen(buf));
} else {
int digits_after_point = precision - exp - 1;
if (digits_after_point < 0) digits_after_point = 0;
double scale = pow(10, digits_after_point);
double rounded = floor(d * scale + 0.5);
char digit_buf[64];
snprintf(digit_buf, sizeof(digit_buf), "%.0f", rounded);
int digit_len = (int)strlen(digit_buf);
while (digit_len < digits_after_point + 1) {
memmove(digit_buf + 1, digit_buf, digit_len + 1);
digit_buf[0] = '0';
digit_len++;
}
char buf[128];
int pos = 0;
if (negative) buf[pos++] = '-';
int int_digits = digit_len - digits_after_point;
for (int i = 0; i < int_digits; i++) {
buf[pos++] = digit_buf[i];
}
if (digits_after_point > 0) {
buf[pos++] = '.';
for (int i = int_digits; i < digit_len; i++) {
buf[pos++] = digit_buf[i];
}
}
buf[pos] = '\0';
return js_mkstr(js, buf, pos);
}
}
static ant_value_t builtin_number_toExponential(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t num = unwrap_primitive(js, js->this_val);
if (vtype(num) != T_NUM) return js_mkerr(js, "toExponential called on non-number");
double d = tod(num);
if (isnan(d)) return js_mkstr(js, "NaN", 3);
if (isinf(d)) return d > 0 ? js_mkstr(js, "Infinity", 8) : js_mkstr(js, "-Infinity", 9);
int digits = -1;
if (nargs >= 1 && vtype(args[0]) != T_UNDEF) {
digits = (int) tod(args[0]);
if (digits < 0 || digits > 100) {
return js_mkerr_typed(js, JS_ERR_RANGE, "toExponential() argument must be between 0 and 100");
}
}
bool negative = d < 0;
if (negative) d = -d;
int exp = 0;
if (d != 0) {
exp = (int) floor(log10(d));
double test = d / pow(10, exp);
if (test >= 10) { exp++; test /= 10; }
if (test < 1) { exp--; test *= 10; }
}
if (digits < 0) {
char temp[32];
snprintf(temp, sizeof(temp), "%.15g", d);
int sig = 0;
for (int i = 0; temp[i] && temp[i] != 'e' && temp[i] != 'E'; i++) {
if (temp[i] == '.') continue;
if (temp[i] >= '0' && temp[i] <= '9') if (temp[i] != '0' || sig > 0) sig++;
}
digits = sig > 0 ? sig - 1 : 0;
if (digits > 20) digits = 20;
}
double mantissa = d / pow(10, exp);
double scale = pow(10, digits);
double scaled = mantissa * scale;
double rounded = floor(scaled + 0.5);
if (rounded >= scale * 10) {
rounded /= 10;
exp++;
}
char buf[64];
int pos = 0;
if (negative) buf[pos++] = '-';
char digit_buf[32];
snprintf(digit_buf, sizeof(digit_buf), "%.0f", rounded);
int digit_len = (int)strlen(digit_buf);
while (digit_len < digits + 1) {
memmove(digit_buf + 1, digit_buf, digit_len + 1);
digit_buf[0] = '0';
digit_len++;
}
buf[pos++] = digit_buf[0];
if (digits > 0) {
buf[pos++] = '.';
for (int i = 1; i <= digits; i++) {
buf[pos++] = (i < digit_len) ? digit_buf[i] : '0';
}
}
buf[pos++] = 'e';
buf[pos++] = (exp >= 0) ? '+' : '-';
if (exp < 0) exp = -exp;
snprintf(buf + pos, sizeof(buf) - pos, "%d", exp);
return js_mkstr(js, buf, strlen(buf));
}
static ant_value_t builtin_number_valueOf(ant_t *js, ant_value_t *args, int nargs) {
(void) args; (void) nargs;
ant_value_t num = unwrap_primitive(js, js->this_val);
if (vtype(num) != T_NUM) return js_mkerr(js, "valueOf called on non-number");
return num;
}
static ant_value_t builtin_number_toLocaleString(ant_t *js, ant_value_t *args, int nargs) {
(void) args; (void) nargs;
ant_value_t num = unwrap_primitive(js, js->this_val);
if (vtype(num) != T_NUM) return js_mkerr(js, "toLocaleString called on non-number");
double d = tod(num);
char raw[64];
strnum(num, raw, sizeof(raw));
if (!isfinite(d) || strchr(raw, 'e') || strchr(raw, 'E'))
return js_mkstr(js, raw, strlen(raw));
char *dot = strchr(raw, '.');
size_t int_len = dot ? (size_t)(dot - raw) : strlen(raw);
size_t start = (raw[0] == '-') ? 1 : 0;
size_t frac_len = dot ? strlen(dot) : 0;
char buf[128];
size_t pos = 0;
if (start) buf[pos++] = '-';
for (size_t i = start; i < int_len; i++) {
buf[pos++] = raw[i];
size_t remaining = int_len - 1 - i;
if (remaining > 0 && remaining % 3 == 0) buf[pos++] = ',';
}
if (frac_len) memcpy(buf + pos, dot, frac_len);
pos += frac_len;
buf[pos] = '\0';
return js_mkstr(js, buf, pos);
}
static ant_value_t builtin_string_valueOf(ant_t *js, ant_value_t *args, int nargs) {
(void) args; (void) nargs;
ant_value_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "valueOf called on non-string");
return str;
}
static ant_value_t builtin_string_toString(ant_t *js, ant_value_t *args, int nargs) {
return builtin_string_valueOf(js, args, nargs);
}
static ant_value_t builtin_boolean_valueOf(ant_t *js, ant_value_t *args, int nargs) {
(void) args; (void) nargs;
ant_value_t b = unwrap_primitive(js, js->this_val);
if (vtype(b) != T_BOOL) return js_mkerr(js, "valueOf called on non-boolean");
return b;
}
static ant_value_t builtin_boolean_toString(ant_t *js, ant_value_t *args, int nargs) {
(void) args; (void) nargs;
ant_value_t b = unwrap_primitive(js, js->this_val);
if (vtype(b) != T_BOOL) return js_mkerr(js, "toString called on non-boolean");
return vdata(b) ? js_mkstr(js, "true", 4) : js_mkstr(js, "false", 5);
}
static ant_value_t builtin_parseInt(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return tov(JS_NAN);
ant_value_t str_val = args[0];
if (vtype(str_val) != T_STR) {
const char *str = js_str(js, str_val);
str_val = js_mkstr(js, str, strlen(str));
}
ant_offset_t str_len, str_off = vstr(js, str_val, &str_len);
const char *str = (char *)(uintptr_t)(str_off);
int radix = 0;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
radix = (int) tod(args[1]);
if (radix != 0 && (radix < 2 || radix > 36)) return tov(JS_NAN);
}
ant_offset_t i = 0;
while (i < str_len && is_space(str[i])) i++;
if (i >= str_len) return tov(JS_NAN);
int sign = 1;
if (str[i] == '-') {
sign = -1;
i++;
} else if (str[i] == '+') {
i++;
}
if ((radix == 0 || radix == 16) && i + 1 < str_len && str[i] == '0' && (str[i + 1] == 'x' || str[i + 1] == 'X')) {
radix = 16;
i += 2;
}
if (radix == 0) radix = 10;
double result = 0;
bool found_digit = false;
while (i < str_len) {
char ch = str[i];
int digit = -1;
if (ch >= '0' && ch <= '9') {
digit = ch - '0';
} else if (ch >= 'a' && ch <= 'z') {
digit = ch - 'a' + 10;
} else if (ch >= 'A' && ch <= 'Z') {
digit = ch - 'A' + 10;
}
if (digit < 0 || digit >= radix) break;
result = result * radix + digit;
found_digit = true;
i++;
}
if (!found_digit) return tov(JS_NAN);
return tov(sign * result);
}
static ant_value_t builtin_parseFloat(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return tov(JS_NAN);
ant_value_t str_val = args[0];
if (vtype(str_val) != T_STR) {
const char *str = js_str(js, str_val);
str_val = js_mkstr(js, str, strlen(str));
}
ant_offset_t str_len, str_off = vstr(js, str_val, &str_len);
const char *str = (char *)(uintptr_t)(str_off);
ant_offset_t i = 0;
while (i < str_len && is_space(str[i])) i++;
if (i >= str_len) return tov(JS_NAN);
char *end;
double result = strtod(&str[i], &end);
if (end == &str[i]) return tov(JS_NAN);
return tov(result);
}
static ant_value_t builtin_btoa(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "btoa requires 1 argument");
ant_value_t str_val = args[0];
if (vtype(str_val) != T_STR) {
const char *str = js_str(js, str_val);
str_val = js_mkstr(js, str, strlen(str));
}
ant_offset_t str_len, str_off = vstr(js, str_val, &str_len);
const char *str = (char *)(uintptr_t)(str_off);
size_t out_len;
char *out = ant_base64_encode((const uint8_t *)str, str_len, &out_len);
if (!out) return js_mkerr(js, "out of memory");
ant_value_t result = js_mkstr(js, out, out_len);
free(out);
return result;
}
static ant_value_t builtin_atob(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "atob requires 1 argument");
ant_value_t str_val = args[0];
if (vtype(str_val) != T_STR) {
const char *str = js_str(js, str_val);
str_val = js_mkstr(js, str, strlen(str));
}
ant_offset_t str_len, str_off = vstr(js, str_val, &str_len);
const char *str = (char *)(uintptr_t)(str_off);
if (str_len == 0) return js_mkstr(js, "", 0);
size_t out_len;
uint8_t *out = ant_base64_decode(str, str_len, &out_len);
if (!out) return js_mkerr(js, "atob: invalid base64 string");
ant_value_t result = js_mkstr(js, (char *)out, out_len);
free(out);
return result;
}
static ant_value_t builtin_resolve_internal(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t builtin_reject_internal(ant_t *js, ant_value_t *args, int nargs);
-static void resolve_promise(ant_t *js, ant_value_t p, ant_value_t val);
-static void reject_promise(ant_t *js, ant_value_t p, ant_value_t val);
static size_t strpromise(ant_t *js, ant_value_t value, char *buf, size_t len) {
uint32_t pid = get_promise_id(js, value);
ant_promise_state_t *pd = get_promise_data(js, value, false);
const char *content;
char *allocated = NULL;
if (!pd || pd->state == 0) {
content = "<pending>";
} else if (pd->state == 2) {
char *val = tostr_alloc(js, pd->value);
allocated = ant_calloc(strlen(val) + 12);
sprintf(allocated, "<rejected> %s", val);
free(val);
content = allocated;
} else { content = allocated = tostr_alloc(js, pd->value); }
uint32_t trigger_pid = 0;
if (pd && vtype(pd->trigger_parent) == T_PROMISE) {
trigger_pid = get_promise_id(js, pd->trigger_parent);
}
size_t result = trigger_pid
? (size_t)snprintf(buf, len, "Promise {\n %s,\n Symbol(async_id): %u,\n Symbol(trigger_async_id): %u\n}", content, pid, trigger_pid)
: (size_t)snprintf(buf, len, "Promise {\n %s,\n Symbol(async_id): %u\n}", content, pid);
if (allocated) free(allocated);
return result;
}
static ant_promise_state_t *get_promise_data(ant_t *js, ant_value_t promise, bool create) {
if (vtype(promise) != T_PROMISE) return NULL;
ant_object_t *obj = js_obj_ptr(js_as_obj(promise));
if (!obj) return NULL;
if (obj->promise_state) return obj->promise_state;
if (!create) return NULL;
ant_promise_state_t *entry = (ant_promise_state_t *)calloc(1, sizeof(*entry));
if (!entry) return NULL;
entry->promise_id = next_promise_id++;
entry->trigger_parent = js_mkundef();
entry->state = 0;
entry->value = js_mkundef();
entry->has_rejection_handler = false;
entry->processing = false;
entry->unhandled_reported = false;
utarray_new(entry->handlers, &promise_handler_icd);
obj->promise_state = entry;
obj->type_tag = T_PROMISE;
return entry;
}
static uint32_t get_promise_id(ant_t *js, ant_value_t p) {
ant_promise_state_t *pd = get_promise_data(js, p, false);
return pd ? pd->promise_id : 0;
}
static ant_value_t make_data_cfunc(
ant_t *js, ant_value_t data,
ant_value_t (*fn)(ant_t *, ant_value_t *, int)
) {
GC_ROOT_SAVE(root_mark, js);
GC_ROOT_PIN(js, data);
ant_value_t obj = mkobj(js, 0);
if (is_err(obj)) {
GC_ROOT_RESTORE(js, root_mark);
return obj;
}
GC_ROOT_PIN(js, obj);
set_slot(obj, SLOT_DATA, data);
set_slot(obj, SLOT_CFUNC, js_mkfun(fn));
ant_value_t func = js_obj_to_func(obj);
GC_ROOT_RESTORE(js, root_mark);
return func;
}
-static ant_value_t mkpromise(ant_t *js) {
+ant_value_t js_mkpromise(ant_t *js) {
ant_value_t obj = mkobj(js, 0);
if (is_err(obj)) return obj;
if (!get_promise_data(js, mkval(T_PROMISE, vdata(obj)), true))
return js_mkerr(js, "out of memory");
ant_value_t promise_ctor = js_get(js, js_glob(js), "Promise");
if (vtype(promise_ctor) == T_FUNC || vtype(promise_ctor) == T_CFUNC) {
set_slot(obj, SLOT_CTOR, promise_ctor);
}
ant_value_t promise_proto = get_ctor_proto(js, "Promise", 7);
if (is_object_type(promise_proto)) {
js_set_proto_init(obj, promise_proto);
}
return mkval(T_PROMISE, vdata(obj));
}
+ant_value_t js_promise_then(ant_t *js, ant_value_t promise, ant_value_t on_fulfilled, ant_value_t on_rejected) {
+ ant_value_t args_then[2] = { on_fulfilled, on_rejected };
+ ant_value_t saved_this = js->this_val;
+
+ js->this_val = promise;
+ ant_value_t result = builtin_promise_then(js, args_then, 2);
+ js->this_val = saved_this;
+
+ return result;
+}
+
static inline void trigger_handlers(ant_t *js, ant_value_t p) {
queue_promise_trigger(p);
}
void js_process_promise_handlers(ant_t *js, ant_value_t promise) {
ant_object_t *pobj = js_obj_ptr(promise);
ant_promise_state_t *pd = get_promise_data(js, promise, false);
if (!pd) return;
int state = pd->state;
ant_value_t val = pd->value;
unsigned int len = utarray_len(pd->handlers);
if (len == 0) { return; }
gc_root_pending_promise(pobj);
pd->processing = true;
for (unsigned int i = 0; i < len; i++) {
promise_handler_t *h = (promise_handler_t *)utarray_eltptr(pd->handlers, i);
ant_value_t handler = (state == 1) ? h->onFulfilled : h->onRejected;
if (vtype(handler) == T_FUNC || vtype(handler) == T_CFUNC) {
ant_value_t res;
if (vtype(handler) == T_CFUNC) {
ant_value_t (*fn)(ant_t *, ant_value_t *, int) = (ant_value_t(*)(ant_t *, ant_value_t *, int)) vdata(handler);
res = fn(js, &val, 1);
} else {
ant_value_t call_args[] = { val };
res = sv_vm_call(js->vm, js, handler, js_mkundef(), call_args, 1, NULL, false);
}
if (is_err(res)) {
ant_value_t reject_val = js->thrown_value;
if (vtype(reject_val) == T_UNDEF) reject_val = res;
js->thrown_exists = false;
js->thrown_value = js_mkundef();
js->thrown_stack = js_mkundef();
- reject_promise(js, h->nextPromise, reject_val);
- } else resolve_promise(js, h->nextPromise, res);
+ js_reject_promise(js, h->nextPromise, reject_val);
+ } else js_resolve_promise(js, h->nextPromise, res);
} else {
- if (state == 1) resolve_promise(js, h->nextPromise, val);
- else reject_promise(js, h->nextPromise, val);
+ if (state == 1) js_resolve_promise(js, h->nextPromise, val);
+ else js_reject_promise(js, h->nextPromise, val);
}
}
pd->processing = false;
utarray_clear(pd->handlers);
gc_unroot_pending_promise(js_obj_ptr(promise));
}
-static void resolve_promise(ant_t *js, ant_value_t p, ant_value_t val) {
+void js_resolve_promise(ant_t *js, ant_value_t p, ant_value_t val) {
GC_ROOT_SAVE(root_mark, js);
GC_ROOT_PIN(js, p);
GC_ROOT_PIN(js, val);
ant_promise_state_t *pd = get_promise_data(js, p, false);
if (!pd || pd->state != 0) {
GC_ROOT_RESTORE(js, root_mark);
return;
}
if (vtype(val) == T_PROMISE) {
if (vdata(js_as_obj(val)) == vdata(js_as_obj(p))) {
ant_value_t err = js_mkerr(js, "TypeError: Chaining cycle");
GC_ROOT_PIN(js, err);
- reject_promise(js, p, err);
+ js_reject_promise(js, p, err);
GC_ROOT_RESTORE(js, root_mark);
return;
}
ant_value_t res_fn = make_data_cfunc(js, p, builtin_resolve_internal);
GC_ROOT_PIN(js, res_fn);
if (is_err(res_fn)) {
- reject_promise(js, p, res_fn);
+ js_reject_promise(js, p, res_fn);
GC_ROOT_RESTORE(js, root_mark);
return;
}
ant_value_t rej_fn = make_data_cfunc(js, p, builtin_reject_internal);
GC_ROOT_PIN(js, rej_fn);
if (is_err(rej_fn)) {
- reject_promise(js, p, rej_fn);
+ js_reject_promise(js, p, rej_fn);
GC_ROOT_RESTORE(js, root_mark);
return;
}
ant_value_t then_prop = js_get(js, val, "then");
GC_ROOT_PIN(js, then_prop);
if (vtype(then_prop) == T_FUNC || vtype(then_prop) == T_CFUNC) {
ant_value_t call_args[] = { res_fn, rej_fn };
(void)sv_vm_call(js->vm, js, then_prop, val, call_args, 2, NULL, false);
GC_ROOT_RESTORE(js, root_mark);
return;
}
}
pd->state = 1;
pd->value = val;
gc_write_barrier(js, js_obj_ptr(js_as_obj(p)), val);
trigger_handlers(js, p);
GC_ROOT_RESTORE(js, root_mark);
}
-static void reject_promise(ant_t *js, ant_value_t p, ant_value_t val) {
+void js_reject_promise(ant_t *js, ant_value_t p, ant_value_t val) {
ant_promise_state_t *pd = get_promise_data(js, p, false);
if (!pd || pd->state != 0) return;
pd->state = 2;
pd->value = val;
gc_write_barrier(js, js_obj_ptr(js_as_obj(p)), val);
pd->unhandled_reported = false;
trigger_handlers(js, p);
}
static ant_value_t builtin_resolve_internal(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t me = js->current_func;
ant_value_t p = get_slot(me, SLOT_DATA);
if (vtype(p) != T_PROMISE) return js_mkundef();
- resolve_promise(js, p, nargs > 0 ? args[0] : js_mkundef());
+ js_resolve_promise(js, p, nargs > 0 ? args[0] : js_mkundef());
return js_mkundef();
}
static ant_value_t builtin_reject_internal(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t me = js->current_func;
ant_value_t p = get_slot(me, SLOT_DATA);
if (vtype(p) != T_PROMISE) return js_mkundef();
- reject_promise(js, p, nargs > 0 ? args[0] : js_mkundef());
+ js_reject_promise(js, p, nargs > 0 ? args[0] : js_mkundef());
return js_mkundef();
}
static ant_value_t builtin_Promise(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Promise constructor cannot be invoked without 'new'");
}
if (nargs == 0 || (vtype(args[0]) != T_FUNC && vtype(args[0]) != T_CFUNC)) {
const char *val_str = nargs == 0 ? "undefined" : js_str(js, args[0]);
return js_mkerr_typed(js, JS_ERR_TYPE, "Promise resolver %s is not a function", val_str);
}
GC_ROOT_SAVE(root_mark, js);
ant_value_t executor = args[0];
GC_ROOT_PIN(js, executor);
- ant_value_t p = mkpromise(js);
+ ant_value_t p = js_mkpromise(js);
if (is_err(p)) {
GC_ROOT_RESTORE(js, root_mark);
return p;
}
GC_ROOT_PIN(js, p);
ant_value_t new_target = js->new_target;
ant_value_t p_obj = js_as_obj(p);
ant_value_t promise_proto = get_ctor_proto(js, "Promise", 7);
ant_value_t instance_proto = js_instance_proto_from_new_target(js, promise_proto);
GC_ROOT_PIN(js, instance_proto);
if (vtype(new_target) == T_FUNC || vtype(new_target) == T_CFUNC) set_slot(p_obj, SLOT_CTOR, new_target);
if (is_object_type(instance_proto)) js_set_proto_init(p_obj, instance_proto);
ant_value_t res_fn = make_data_cfunc(js, p, builtin_resolve_internal);
GC_ROOT_PIN(js, res_fn);
if (is_err(res_fn)) {
GC_ROOT_RESTORE(js, root_mark);
return res_fn;
}
ant_value_t rej_fn = make_data_cfunc(js, p, builtin_reject_internal);
GC_ROOT_PIN(js, rej_fn);
if (is_err(rej_fn)) {
GC_ROOT_RESTORE(js, root_mark);
return rej_fn;
}
ant_value_t exec_args[] = { res_fn, rej_fn };
sv_vm_call(js->vm, js, executor, js_mkundef(), exec_args, 2, NULL, false);
GC_ROOT_RESTORE(js, root_mark);
return p;
}
static ant_value_t builtin_Promise_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t val = nargs > 0 ? args[0] : js_mkundef();
if (vtype(val) == T_PROMISE) return val;
- ant_value_t p = mkpromise(js);
- resolve_promise(js, p, val);
+ ant_value_t p = js_mkpromise(js);
+ js_resolve_promise(js, p, val);
return p;
}
static ant_value_t builtin_Promise_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t val = nargs > 0 ? args[0] : js_mkundef();
- ant_value_t p = mkpromise(js);
- reject_promise(js, p, val);
+ ant_value_t p = js_mkpromise(js);
+ js_reject_promise(js, p, val);
return p;
}
static ant_value_t promise_species_noop_executor(ant_t *js, ant_value_t *args, int nargs) {
return js_mkundef();
}
static ant_value_t builtin_promise_then(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t p = js->this_val;
if (vtype(p) != T_PROMISE) return js_mkerr(js, "not a promise");
GC_ROOT_SAVE(root_mark, js);
GC_ROOT_PIN(js, p);
ant_value_t promise_ctor = js_get(js, js_glob(js), "Promise");
GC_ROOT_PIN(js, promise_ctor);
ant_value_t species_ctor = promise_ctor;
GC_ROOT_PIN(js, species_ctor);
ant_value_t p_obj = js_as_obj(p);
ant_value_t ctor = js_get(js, p_obj, "constructor");
GC_ROOT_PIN(js, ctor);
if (is_err(ctor)) {
GC_ROOT_RESTORE(js, root_mark);
return ctor;
}
if (vtype(ctor) == T_UNDEF) ctor = get_slot(p_obj, SLOT_CTOR);
ant_value_t species = get_ctor_species_value(js, ctor);
GC_ROOT_PIN(js, species);
if (is_err(species)) {
GC_ROOT_RESTORE(js, root_mark);
return species;
}
if (vtype(species) == T_FUNC || vtype(species) == T_CFUNC) {
species_ctor = species;
} else if (vtype(species) == T_NULL) {
species_ctor = promise_ctor;
} else if (vtype(species) != T_UNDEF) {
ant_value_t err = js_mkerr_typed(js, JS_ERR_TYPE, "Promise species is not a constructor");
GC_ROOT_RESTORE(js, root_mark);
return err;
}
- ant_value_t nextP = mkpromise(js);
+ ant_value_t nextP = js_mkpromise(js);
GC_ROOT_PIN(js, nextP);
if ((vtype(species_ctor) == T_FUNC || vtype(species_ctor) == T_CFUNC)
&& !(vtype(species_ctor) == vtype(promise_ctor)
&& vdata(species_ctor) == vdata(promise_ctor))) {
ant_value_t species_proto = js_get(js, species_ctor, "prototype");
if (is_object_type(species_proto))
js_set_proto_init(js_as_obj(nextP), species_proto);
set_slot(js_as_obj(nextP), SLOT_CTOR, species_ctor);
} else {
ant_value_t p_proto = get_slot(js_as_obj(p), SLOT_PROTO);
if (vtype(p_proto) == T_OBJ) {
js_set_proto_init(js_as_obj(nextP), p_proto);
ant_value_t p_ctor = get_slot(js_as_obj(p), SLOT_CTOR);
if (vtype(p_ctor) == T_FUNC) set_slot(js_as_obj(nextP), SLOT_CTOR, p_ctor);
}
}
ant_value_t onFulfilled = nargs > 0 ? args[0] : js_mkundef();
ant_value_t onRejected = nargs > 1 ? args[1] : js_mkundef();
GC_ROOT_PIN(js, onFulfilled);
GC_ROOT_PIN(js, onRejected);
ant_promise_state_t *next_pd = get_promise_data(js, nextP, false);
if (next_pd) next_pd->trigger_parent = p;
ant_promise_state_t *pd = get_promise_data(js, p, false);
if (pd) {
promise_handler_t h = { onFulfilled, onRejected, nextP };
utarray_push_back(pd->handlers, &h);
gc_write_barrier(js, js_obj_ptr(js_as_obj(p)), nextP);
gc_write_barrier(js, js_obj_ptr(js_as_obj(p)), onFulfilled);
gc_write_barrier(js, js_obj_ptr(js_as_obj(p)), onRejected);
if (vtype(onRejected) == T_FUNC || vtype(onRejected) == T_CFUNC) {
if (pd->unhandled_reported) js_fire_rejection_handled(js, p, pd->value);
pd->has_rejection_handler = true;
pd->unhandled_reported = false;
}
if (pd->state == 0)
gc_root_pending_promise(js_obj_ptr(p));
}
if (pd && pd->state != 0) trigger_handlers(js, p);
GC_ROOT_RESTORE(js, root_mark);
return nextP;
}
static ant_value_t builtin_promise_catch(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t args_then[] = { js_mkundef(), nargs > 0 ? args[0] : js_mkundef() };
return builtin_promise_then(js, args_then, 2);
}
static ant_value_t finally_value_thunk(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t me = js->current_func;
return get_slot(me, SLOT_DATA);
}
static ant_value_t finally_thrower(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t me = js->current_func;
ant_value_t reason = get_slot(me, SLOT_DATA);
ant_value_t rejected = js_mkpromise(js);
js_reject_promise(js, rejected, reason);
return rejected;
}
static ant_value_t finally_identity_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t reason = nargs > 0 ? args[0] : js_mkundef();
ant_value_t rejected = js_mkpromise(js);
js_reject_promise(js, rejected, reason);
return rejected;
}
static ant_value_t finally_fulfilled_wrapper(ant_t *js, ant_value_t *args, int nargs) {
GC_ROOT_SAVE(root_mark, js);
ant_value_t me = js->current_func;
ant_value_t callback = get_slot(me, SLOT_DATA);
ant_value_t value = nargs > 0 ? args[0] : js_mkundef();
GC_ROOT_PIN(js, callback);
GC_ROOT_PIN(js, value);
ant_value_t result = js_mkundef();
if (vtype(callback) == T_FUNC || vtype(callback) == T_CFUNC) {
result = sv_vm_call(js->vm, js, callback, js_mkundef(), NULL, 0, NULL, false);
if (is_err(result)) {
GC_ROOT_RESTORE(js, root_mark);
return result;
}
}
GC_ROOT_PIN(js, result);
if (vtype(result) == T_PROMISE || (vtype(result) == T_OBJ && vtype(js_get(js, result, "then")) == T_FUNC)) {
ant_value_t thunk_fn = make_data_cfunc(js, value, finally_value_thunk);
GC_ROOT_PIN(js, thunk_fn);
if (is_err(thunk_fn)) {
GC_ROOT_RESTORE(js, root_mark);
return thunk_fn;
}
ant_value_t identity_rej_fn = js_mkfun(finally_identity_reject);
ant_value_t then_fn = js_get(js, result, "then");
GC_ROOT_PIN(js, then_fn);
ant_value_t call_args[] = { thunk_fn, identity_rej_fn };
ant_value_t ret = sv_vm_call(js->vm, js, then_fn, result, call_args, 2, NULL, false);
GC_ROOT_RESTORE(js, root_mark);
return ret;
}
GC_ROOT_RESTORE(js, root_mark);
return value;
}
static ant_value_t finally_rejected_wrapper(ant_t *js, ant_value_t *args, int nargs) {
GC_ROOT_SAVE(root_mark, js);
ant_value_t me = js->current_func;
ant_value_t callback = get_slot(me, SLOT_DATA);
ant_value_t reason = nargs > 0 ? args[0] : js_mkundef();
GC_ROOT_PIN(js, callback);
GC_ROOT_PIN(js, reason);
ant_value_t result = js_mkundef();
if (vtype(callback) == T_FUNC || vtype(callback) == T_CFUNC) {
result = sv_vm_call(js->vm, js, callback, js_mkundef(), NULL, 0, NULL, false);
if (is_err(result)) {
GC_ROOT_RESTORE(js, root_mark);
return result;
}
}
GC_ROOT_PIN(js, result);
if (vtype(result) == T_PROMISE || (vtype(result) == T_OBJ && vtype(js_get(js, result, "then")) == T_FUNC)) {
ant_value_t thrower_fn = make_data_cfunc(js, reason, finally_thrower);
GC_ROOT_PIN(js, thrower_fn);
if (is_err(thrower_fn)) {
GC_ROOT_RESTORE(js, root_mark);
return thrower_fn;
}
ant_value_t identity_rej_fn = js_mkfun(finally_identity_reject);
ant_value_t then_prop = js_get(js, result, "then");
GC_ROOT_PIN(js, then_prop);
ant_value_t call_args[] = { thrower_fn, identity_rej_fn };
ant_value_t ret = sv_vm_call(js->vm, js, then_prop, result, call_args, 2, NULL, false);
GC_ROOT_RESTORE(js, root_mark);
return ret;
}
ant_value_t rejected = js_mkpromise(js);
GC_ROOT_PIN(js, rejected);
js_reject_promise(js, rejected, reason);
GC_ROOT_RESTORE(js, root_mark);
return rejected;
}
static ant_value_t builtin_promise_finally(ant_t *js, ant_value_t *args, int nargs) {
GC_ROOT_SAVE(root_mark, js);
ant_value_t callback = nargs > 0 ? args[0] : js_mkundef();
GC_ROOT_PIN(js, callback);
ant_value_t fulfilled_fn = make_data_cfunc(js, callback, finally_fulfilled_wrapper);
GC_ROOT_PIN(js, fulfilled_fn);
if (is_err(fulfilled_fn)) {
GC_ROOT_RESTORE(js, root_mark);
return fulfilled_fn;
}
ant_value_t rejected_fn = make_data_cfunc(js, callback, finally_rejected_wrapper);
GC_ROOT_PIN(js, rejected_fn);
if (is_err(rejected_fn)) {
GC_ROOT_RESTORE(js, root_mark);
return rejected_fn;
}
ant_value_t args_then[] = { fulfilled_fn, rejected_fn };
ant_value_t ret = builtin_promise_then(js, args_then, 2);
GC_ROOT_RESTORE(js, root_mark);
return ret;
}
static ant_value_t builtin_Promise_try(ant_t *js, ant_value_t *args, int nargs) {
if (nargs == 0) return builtin_Promise_resolve(js, args, 0);
ant_value_t fn = args[0];
ant_value_t *call_args = nargs > 1 ? &args[1] : NULL;
int call_nargs = nargs > 1 ? nargs - 1 : 0;
ant_value_t res = sv_vm_call(js->vm, js, fn, js_mkundef(), call_args, call_nargs, NULL, false);
if (is_err(res)) {
ant_value_t reject_val = js->thrown_value;
if (vtype(reject_val) == T_UNDEF) reject_val = res;
js->thrown_exists = false;
js->thrown_value = js_mkundef();
js->thrown_stack = js_mkundef();
ant_value_t rej_args[] = { reject_val };
return builtin_Promise_reject(js, rej_args, 1);
}
ant_value_t res_args[] = { res };
return builtin_Promise_resolve(js, res_args, 1);
}
static ant_value_t builtin_Promise_withResolvers(ant_t *js, ant_value_t *args, int nargs) {
GC_ROOT_SAVE(root_mark, js);
- ant_value_t p = mkpromise(js);
+ ant_value_t p = js_mkpromise(js);
GC_ROOT_PIN(js, p);
ant_value_t res_fn = make_data_cfunc(js, p, builtin_resolve_internal);
GC_ROOT_PIN(js, res_fn);
if (is_err(res_fn)) {
GC_ROOT_RESTORE(js, root_mark);
return res_fn;
}
ant_value_t rej_fn = make_data_cfunc(js, p, builtin_reject_internal);
GC_ROOT_PIN(js, rej_fn);
if (is_err(rej_fn)) {
GC_ROOT_RESTORE(js, root_mark);
return rej_fn;
}
ant_value_t result = js_newobj(js);
GC_ROOT_PIN(js, result);
js_setprop(js, result, js_mkstr(js, "promise", 7), p);
js_setprop(js, result, js_mkstr(js, "resolve", 7), res_fn);
js_setprop(js, result, js_mkstr(js, "reject", 6), rej_fn);
GC_ROOT_RESTORE(js, root_mark);
return result;
}
static ant_value_t mkpromise_with_ctor(ant_t *js, ant_value_t ctor) {
GC_ROOT_SAVE(root_mark, js);
GC_ROOT_PIN(js, ctor);
- ant_value_t p = mkpromise(js);
+ ant_value_t p = js_mkpromise(js);
GC_ROOT_PIN(js, p);
if (vtype(ctor) != T_FUNC && vtype(ctor) != T_CFUNC) {
GC_ROOT_RESTORE(js, root_mark);
return p;
}
ant_value_t proto = js_get(js, ctor, "prototype");
if (is_err(proto)) {
GC_ROOT_RESTORE(js, root_mark);
return proto;
}
if (is_object_type(proto)) {
ant_value_t p_obj = js_as_obj(p);
set_slot(p_obj, SLOT_CTOR, ctor);
js_set_proto_init(p_obj, proto);
}
GC_ROOT_RESTORE(js, root_mark);
return p;
}
static ant_value_t builtin_Promise_all_resolve_handler(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t me = js->current_func;
ant_value_t tracker = js_get(js, me, "tracker");
ant_value_t index_val = js_get(js, me, "index");
int index = (int)tod(index_val);
ant_value_t value = nargs > 0 ? args[0] : js_mkundef();
ant_value_t results = js_get(js, tracker, "results");
arr_set(js, results, (ant_offset_t)index, value);
ant_value_t remaining_val = js_get(js, tracker, "remaining");
int remaining = (int)tod(remaining_val) - 1;
js_setprop(js, tracker, js_mkstr(js, "remaining", 9), tov((double)remaining));
if (remaining == 0) {
ant_value_t result_promise = get_slot(tracker, SLOT_DATA);
- resolve_promise(js, result_promise, mkval(T_ARR, vdata(results)));
+ js_resolve_promise(js, result_promise, mkval(T_ARR, vdata(results)));
}
return js_mkundef();
}
static ant_value_t builtin_Promise_all_reject_handler(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t me = js->current_func;
ant_value_t tracker = js_get(js, me, "tracker");
ant_value_t result_promise = get_slot(tracker, SLOT_DATA);
ant_value_t reason = nargs > 0 ? args[0] : js_mkundef();
- reject_promise(js, result_promise, reason);
+ js_reject_promise(js, result_promise, reason);
return js_mkundef();
}
typedef struct {
ant_value_t tracker;
int index;
} promise_all_iter_ctx_t;
static iter_action_t promise_all_iter_cb(ant_t *js, ant_value_t value, void *ctx, ant_value_t *out) {
GC_ROOT_SAVE(root_mark, js);
promise_all_iter_ctx_t *pctx = (promise_all_iter_ctx_t *)ctx;
ant_value_t item = value;
GC_ROOT_PIN(js, item);
if (vtype(item) != T_PROMISE) {
ant_value_t wrap_args[] = { item };
item = builtin_Promise_resolve(js, wrap_args, 1);
GC_ROOT_PIN(js, item);
}
ant_value_t resolve_obj = mkobj(js, 0);
if (is_err(resolve_obj)) {
*out = resolve_obj;
GC_ROOT_RESTORE(js, root_mark);
return ITER_ERROR;
}
GC_ROOT_PIN(js, resolve_obj);
set_slot(resolve_obj, SLOT_CFUNC, js_mkfun(builtin_Promise_all_resolve_handler));
js_setprop(js, resolve_obj, js_mkstr(js, "index", 5), tov((double)pctx->index));
js_setprop(js, resolve_obj, js_mkstr(js, "tracker", 7), pctx->tracker);
ant_value_t resolve_fn = js_obj_to_func(resolve_obj);
GC_ROOT_PIN(js, resolve_fn);
ant_value_t reject_obj = mkobj(js, 0);
if (is_err(reject_obj)) {
*out = reject_obj;
GC_ROOT_RESTORE(js, root_mark);
return ITER_ERROR;
}
GC_ROOT_PIN(js, reject_obj);
set_slot(reject_obj, SLOT_CFUNC, js_mkfun(builtin_Promise_all_reject_handler));
js_setprop(js, reject_obj, js_mkstr(js, "tracker", 7), pctx->tracker);
ant_value_t reject_fn = js_obj_to_func(reject_obj);
GC_ROOT_PIN(js, reject_fn);
ant_value_t then_args[] = { resolve_fn, reject_fn };
ant_value_t saved_this = js->this_val;
GC_ROOT_PIN(js, saved_this);
js->this_val = item;
ant_value_t then_result = builtin_promise_then(js, then_args, 2);
js->this_val = saved_this;
if (is_err(then_result)) {
*out = then_result;
GC_ROOT_RESTORE(js, root_mark);
return ITER_ERROR;
}
pctx->index++;
GC_ROOT_RESTORE(js, root_mark);
return ITER_CONTINUE;
}
static ant_value_t builtin_Promise_all(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "Promise.all requires an iterable");
GC_ROOT_SAVE(root_mark, js);
ant_value_t iterable = args[0];
GC_ROOT_PIN(js, iterable);
uint8_t t = vtype(iterable);
if (t != T_ARR && t != T_OBJ) {
ant_value_t err = js_mkerr(js, "Promise.all requires an iterable");
GC_ROOT_RESTORE(js, root_mark);
return err;
}
ant_value_t ctor = js->this_val;
GC_ROOT_PIN(js, ctor);
if (vtype(ctor) != T_FUNC && vtype(ctor) != T_CFUNC) ctor = js_mkundef();
ant_value_t result_promise = mkpromise_with_ctor(js, ctor);
GC_ROOT_PIN(js, result_promise);
if (is_err(result_promise)) {
GC_ROOT_RESTORE(js, root_mark);
return result_promise;
}
ant_value_t tracker = mkobj(js, 0);
GC_ROOT_PIN(js, tracker);
ant_value_t results = mkarr(js);
GC_ROOT_PIN(js, results);
js_setprop(js, tracker, js_mkstr(js, "remaining", 9), tov(0.0));
js_setprop(js, tracker, js_mkstr(js, "results", 7), results);
set_slot(tracker, SLOT_DATA, result_promise);
promise_all_iter_ctx_t ctx = { .tracker = tracker, .index = 0 };
ant_value_t iter_result = iter_foreach(js, iterable, promise_all_iter_cb, &ctx);
if (is_err(iter_result)) {
GC_ROOT_RESTORE(js, root_mark);
return iter_result;
}
int len = ctx.index;
{
ant_offset_t doff = get_dense_buf(results);
if (doff) {
if ((ant_offset_t)len > dense_capacity(doff)) doff = dense_grow(js, results, (ant_offset_t)len);
if (doff) array_len_set(js, results, (ant_offset_t)len);
}
}
if (len == 0) {
- resolve_promise(js, result_promise, mkval(T_ARR, vdata(results)));
+ js_resolve_promise(js, result_promise, mkval(T_ARR, vdata(results)));
GC_ROOT_RESTORE(js, root_mark);
return result_promise;
}
js_setprop(js, tracker, js_mkstr(js, "remaining", 9), tov((double)len));
GC_ROOT_RESTORE(js, root_mark);
return result_promise;
}
typedef struct {
ant_value_t result_promise;
ant_value_t resolve_fn;
ant_value_t reject_fn;
bool settled;
} promise_race_iter_ctx_t;
static iter_action_t promise_race_iter_cb(ant_t *js, ant_value_t value, void *ctx, ant_value_t *out) {
GC_ROOT_SAVE(root_mark, js);
promise_race_iter_ctx_t *pctx = (promise_race_iter_ctx_t *)ctx;
ant_value_t item = value;
GC_ROOT_PIN(js, item);
if (vtype(item) != T_PROMISE) {
- resolve_promise(js, pctx->result_promise, item);
+ js_resolve_promise(js, pctx->result_promise, item);
pctx->settled = true;
GC_ROOT_RESTORE(js, root_mark);
return ITER_BREAK;
}
ant_promise_state_t *pd = get_promise_data(js, item, false);
if (pd) {
- if (pd->state == 1) {
- resolve_promise(js, pctx->result_promise, pd->value);
- pctx->settled = true;
- GC_ROOT_RESTORE(js, root_mark);
- return ITER_BREAK;
- } else if (pd->state == 2) {
- reject_promise(js, pctx->result_promise, pd->value);
- pctx->settled = true;
- GC_ROOT_RESTORE(js, root_mark);
- return ITER_BREAK;
- }
- }
+ if (pd->state == 1) {
+ js_resolve_promise(js, pctx->result_promise, pd->value);
+ pctx->settled = true;
+ GC_ROOT_RESTORE(js, root_mark);
+ return ITER_BREAK;
+ } else if (pd->state == 2) {
+ js_reject_promise(js, pctx->result_promise, pd->value);
+ pctx->settled = true;
+ GC_ROOT_RESTORE(js, root_mark);
+ return ITER_BREAK;
+ }}
ant_value_t then_args[] = { pctx->resolve_fn, pctx->reject_fn };
ant_value_t saved_this = js->this_val;
GC_ROOT_PIN(js, saved_this);
js->this_val = item;
ant_value_t then_result = builtin_promise_then(js, then_args, 2);
js->this_val = saved_this;
if (is_err(then_result)) {
*out = then_result;
GC_ROOT_RESTORE(js, root_mark);
return ITER_ERROR;
}
GC_ROOT_RESTORE(js, root_mark);
return ITER_CONTINUE;
}
static ant_value_t builtin_Promise_race(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "Promise.race requires an iterable");
GC_ROOT_SAVE(root_mark, js);
ant_value_t iterable = args[0];
GC_ROOT_PIN(js, iterable);
uint8_t t = vtype(iterable);
if (t != T_ARR && t != T_OBJ) {
ant_value_t err = js_mkerr(js, "Promise.race requires an iterable");
GC_ROOT_RESTORE(js, root_mark);
return err;
}
ant_value_t ctor = js->this_val;
GC_ROOT_PIN(js, ctor);
if (vtype(ctor) != T_FUNC && vtype(ctor) != T_CFUNC) ctor = js_mkundef();
ant_value_t result_promise = mkpromise_with_ctor(js, ctor);
GC_ROOT_PIN(js, result_promise);
if (is_err(result_promise)) {
GC_ROOT_RESTORE(js, root_mark);
return result_promise;
}
ant_value_t resolve_fn = make_data_cfunc(js, result_promise, builtin_resolve_internal);
GC_ROOT_PIN(js, resolve_fn);
if (is_err(resolve_fn)) {
GC_ROOT_RESTORE(js, root_mark);
return resolve_fn;
}
ant_value_t reject_fn = make_data_cfunc(js, result_promise, builtin_reject_internal);
GC_ROOT_PIN(js, reject_fn);
if (is_err(reject_fn)) {
GC_ROOT_RESTORE(js, root_mark);
return reject_fn;
}
promise_race_iter_ctx_t ctx = {
.result_promise = result_promise,
.resolve_fn = resolve_fn,
.reject_fn = reject_fn,
.settled = false
};
ant_value_t iter_result = iter_foreach(js, iterable, promise_race_iter_cb, &ctx);
if (is_err(iter_result)) {
GC_ROOT_RESTORE(js, root_mark);
return iter_result;
}
GC_ROOT_RESTORE(js, root_mark);
return result_promise;
}
static ant_value_t mk_aggregate_error(ant_t *js, ant_value_t errors) {
GC_ROOT_SAVE(root_mark, js);
GC_ROOT_PIN(js, errors);
ant_value_t args[] = { errors, js_mkstr(js, "All promises were rejected", 26) };
ant_offset_t off = lkp(js, js_glob(js), "AggregateError", 14);
ant_value_t ctor = off ? propref_load(js, off) : js_mkundef();
GC_ROOT_PIN(js, ctor);
ant_value_t ret = sv_vm_call(js->vm, js, ctor, js_mkundef(), args, 2, NULL, false);
GC_ROOT_RESTORE(js, root_mark);
return ret;
}
static bool promise_any_try_resolve(ant_t *js, ant_value_t tracker, ant_value_t value) {
if (js_truthy(js, js_get(js, tracker, "resolved"))) return false;
js_set(js, tracker, "resolved", js_true);
- resolve_promise(js, get_slot(tracker, SLOT_DATA), value);
+ js_resolve_promise(js, get_slot(tracker, SLOT_DATA), value);
return true;
}
static void promise_any_record_rejection(ant_t *js, ant_value_t tracker, int index, ant_value_t reason) {
ant_value_t errors = js_get(js, tracker, "errors");
arr_set(js, errors, (ant_offset_t)index, reason);
int remaining = (int)tod(js_get(js, tracker, "remaining")) - 1;
js_set(js, tracker, "remaining", tov((double)remaining));
- if (remaining == 0) reject_promise(js, get_slot(tracker, SLOT_DATA), mk_aggregate_error(js, errors));
+ if (remaining == 0) js_reject_promise(js, get_slot(tracker, SLOT_DATA), mk_aggregate_error(js, errors));
}
static ant_value_t builtin_Promise_any_resolve_handler(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t tracker = js_get(js, js->this_val, "tracker");
promise_any_try_resolve(js, tracker, nargs > 0 ? args[0] : js_mkundef());
return js_mkundef();
}
static ant_value_t builtin_Promise_any_reject_handler(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t tracker = js_get(js, js->this_val, "tracker");
if (js_truthy(js, js_get(js, tracker, "resolved"))) return js_mkundef();
int index = (int)tod(js_get(js, js->this_val, "index"));
promise_any_record_rejection(js, tracker, index, nargs > 0 ? args[0] : js_mkundef());
return js_mkundef();
}
static ant_value_t builtin_Promise_any(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "Promise.any requires an array");
GC_ROOT_SAVE(root_mark, js);
ant_value_t arr = args[0];
GC_ROOT_PIN(js, arr);
if (vtype(arr) != T_ARR) {
ant_value_t err = js_mkerr(js, "Promise.any requires an array");
GC_ROOT_RESTORE(js, root_mark);
return err;
}
int len = (int)get_array_length(js, arr);
if (len == 0) {
ant_value_t reject_args[] = { mk_aggregate_error(js, mkarr(js)) };
ant_value_t ret = builtin_Promise_reject(js, reject_args, 1);
GC_ROOT_RESTORE(js, root_mark);
return ret;
}
- ant_value_t result_promise = mkpromise(js);
+ ant_value_t result_promise = js_mkpromise(js);
GC_ROOT_PIN(js, result_promise);
ant_value_t tracker = mkobj(js, 0);
GC_ROOT_PIN(js, tracker);
ant_value_t errors = mkarr(js);
GC_ROOT_PIN(js, errors);
set_slot(tracker, SLOT_DATA, result_promise);
js_setprop(js, tracker, js_mkstr(js, "remaining", 9), tov((double)len));
js_setprop(js, tracker, js_mkstr(js, "errors", 6), errors);
js_setprop(js, tracker, js_mkstr(js, "resolved", 8), js_false);
{
ant_offset_t doff = get_dense_buf(errors);
if (doff) {
if ((ant_offset_t)len > dense_capacity(doff)) doff = dense_grow(js, errors, (ant_offset_t)len);
if (doff) array_len_set(js, errors, (ant_offset_t)len);
}
}
for (int i = 0; i < len; i++) {
ant_value_t item = arr_get(js, arr, (ant_offset_t)i);
GC_ROOT_PIN(js, item);
if (vtype(item) != T_PROMISE) {
promise_any_try_resolve(js, tracker, item);
GC_ROOT_RESTORE(js, root_mark);
return result_promise;
}
ant_promise_state_t *pd = get_promise_data(js, item, false);
if (pd) {
pd->has_rejection_handler = true;
pd->unhandled_reported = false;
if (pd->state == 1) {
promise_any_try_resolve(js, tracker, pd->value);
GC_ROOT_RESTORE(js, root_mark);
return result_promise;
} else if (pd->state == 2) {
promise_any_record_rejection(js, tracker, i, pd->value);
continue;
}
}
ant_value_t resolve_obj = mkobj(js, 0);
if (is_err(resolve_obj)) {
GC_ROOT_RESTORE(js, root_mark);
return resolve_obj;
}
GC_ROOT_PIN(js, resolve_obj);
set_slot(resolve_obj, SLOT_CFUNC, js_mkfun(builtin_Promise_any_resolve_handler));
js_setprop(js, resolve_obj, js_mkstr(js, "tracker", 7), tracker);
ant_value_t reject_obj = mkobj(js, 0);
if (is_err(reject_obj)) {
GC_ROOT_RESTORE(js, root_mark);
return reject_obj;
}
GC_ROOT_PIN(js, reject_obj);
set_slot(reject_obj, SLOT_CFUNC, js_mkfun(builtin_Promise_any_reject_handler));
js_setprop(js, reject_obj, js_mkstr(js, "index", 5), tov((double)i));
js_setprop(js, reject_obj, js_mkstr(js, "tracker", 7), tracker);
ant_value_t resolve_fn = js_obj_to_func(resolve_obj);
GC_ROOT_PIN(js, resolve_fn);
ant_value_t reject_fn = js_obj_to_func(reject_obj);
GC_ROOT_PIN(js, reject_fn);
ant_value_t then_args[] = { resolve_fn, reject_fn };
ant_value_t saved_this = js->this_val;
GC_ROOT_PIN(js, saved_this);
js->this_val = item;
ant_value_t then_result = builtin_promise_then(js, then_args, 2);
js->this_val = saved_this;
if (is_err(then_result)) {
GC_ROOT_RESTORE(js, root_mark);
return then_result;
}
}
GC_ROOT_RESTORE(js, root_mark);
return result_promise;
}
static ant_value_t handle_proxy_instanceof(ant_t *js, ant_value_t l, ant_value_t r, uint8_t ltype) {
ant_value_t target = proxy_read_target(js, r);
uint8_t ttype = vtype(target);
if (ttype != T_FUNC && ttype != T_CFUNC) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Right-hand side of 'instanceof' is not callable");
}
{
ant_value_t has_instance = js_get_sym(js, r, get_hasInstance_sym());
if (is_err(has_instance)) return has_instance;
uint8_t hit = vtype(has_instance);
if (hit == T_FUNC || hit == T_CFUNC) {
ant_value_t args[1] = { l };
ant_value_t result = sv_vm_call(js->vm, js, has_instance, r, args, 1, NULL, false);
if (is_err(result)) return result;
return js_bool(js_truthy(js, result));
}
if (hit != T_UNDEF && hit != T_NULL) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol.hasInstance is not callable");
}
}
ant_value_t proto_val = proxy_get(js, r, "prototype", 9);
uint8_t pt = vtype(proto_val);
if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC) {
return mkval(T_BOOL, 0);
}
if (ltype != T_OBJ && ltype != T_ARR && ltype != T_FUNC && ltype != T_PROMISE) {
return mkval(T_BOOL, 0);
}
ant_value_t current = get_proto(js, l);
return mkval(T_BOOL, proto_chain_contains_cycle_safe(js, current, proto_val) ? 1 : 0);
}
static ant_value_t handle_cfunc_instanceof(ant_value_t l, ant_value_t r, uint8_t ltype) {
ant_value_t (*fn)(ant_t *, ant_value_t *, int) = (ant_value_t(*)(ant_t *, ant_value_t *, int)) vdata(r);
if (fn == builtin_Object) return mkval(T_BOOL, ltype == T_OBJ ? 1 : 0);
if (fn == builtin_Function) return mkval(T_BOOL, (ltype == T_FUNC || ltype == T_CFUNC) ? 1 : 0);
if (fn == builtin_String) return mkval(T_BOOL, ltype == T_STR ? 1 : 0);
if (fn == builtin_Number) return mkval(T_BOOL, ltype == T_NUM ? 1 : 0);
if (fn == builtin_Boolean) return mkval(T_BOOL, ltype == T_BOOL ? 1 : 0);
if (fn == builtin_Array) return mkval(T_BOOL, ltype == T_ARR ? 1 : 0);
if (fn == builtin_Promise) return mkval(T_BOOL, ltype == T_PROMISE ? 1 : 0);
return mkval(T_BOOL, 0);
}
static bool proto_chain_contains_cycle_safe(ant_t *js, ant_value_t start, ant_value_t target) {
if (!is_object_type(start) || !is_object_type(target)) return false;
bool found = false;
ant_value_t slow = start;
ant_value_t fast = start;
while (is_object_type(slow)) {
if (same_object_identity(slow, target)) {
found = true;
break;
}
slow = get_proto(js, slow);
if (is_object_type(fast)) fast = get_proto(js, fast);
if (is_object_type(fast)) fast = get_proto(js, fast);
if (
is_object_type(slow) &&
is_object_type(fast) &&
same_object_identity(slow, fast)
) break;
}
return found;
}
static ant_value_t walk_prototype_chain(ant_t *js, ant_value_t l, ant_value_t ctor_proto) {
ant_value_t current = get_proto(js, l);
return mkval(T_BOOL, proto_chain_contains_cycle_safe(js, current, ctor_proto) ? 1 : 0);
}
static inline ant_object_t *cached_function_proto_obj(ant_t *js) {
static ant_object_t *cached = NULL;
if (cached) return cached;
ant_value_t proto = get_ctor_proto(js, "Function", 8);
if (!is_object_type(proto)) return NULL;
cached = js_obj_ptr(js_as_obj(proto));
return cached;
}
ant_value_t do_instanceof(ant_t *js, ant_value_t l, ant_value_t r) {
uint8_t ltype = vtype(l);
uint8_t rtype = vtype(r);
if (rtype != T_FUNC && rtype != T_CFUNC) {
if (is_proxy(r)) return handle_proxy_instanceof(js, l, r, ltype);
return js_mkerr_typed(js, JS_ERR_TYPE, "Right-hand side of 'instanceof' is not callable");
}
if (rtype == T_CFUNC) {
return handle_cfunc_instanceof(l, r, ltype);
}
ant_value_t func_obj = js_func_obj(r);
ant_offset_t has_instance_sym_off = (ant_offset_t)vdata(get_hasInstance_sym());
bool use_slow_has_instance = false;
ant_offset_t own_has_instance = lkp_sym(js, func_obj, has_instance_sym_off);
if (own_has_instance != 0) {
const ant_shape_prop_t *prop_meta = prop_shape_meta(js, own_has_instance);
if (prop_meta && (prop_meta->has_getter || prop_meta->has_setter)) {
use_slow_has_instance = true;
} else {
ant_value_t has_instance = propref_load(js, own_has_instance);
uint8_t hit = vtype(has_instance);
if (hit == T_FUNC || hit == T_CFUNC) {
ant_value_t args[1] = { l };
ant_value_t result = sv_vm_call(js->vm, js, has_instance, r, args, 1, NULL, false);
if (is_err(result)) return result;
return js_bool(js_truthy(js, result));
}
if (hit != T_UNDEF && hit != T_NULL) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol.hasInstance is not callable");
}
}
} else {
ant_object_t *func_proto_ptr = cached_function_proto_obj(js);
if (func_proto_ptr && func_proto_ptr->shape &&
ant_shape_lookup_symbol(func_proto_ptr->shape, has_instance_sym_off) >= 0) {
use_slow_has_instance = true;
} else if (func_proto_ptr && func_proto_ptr->is_exotic) {
ant_value_t func_proto_obj = mkval(T_OBJ, (uintptr_t)func_proto_ptr);
if (lookup_sym_descriptor(func_proto_obj, has_instance_sym_off))
use_slow_has_instance = true;
}
}
if (use_slow_has_instance) {
ant_value_t has_instance = js_get_sym(js, r, get_hasInstance_sym());
if (is_err(has_instance)) return has_instance;
uint8_t hit = vtype(has_instance);
if (hit == T_FUNC || hit == T_CFUNC) {
ant_value_t args[1] = { l };
ant_value_t result = sv_vm_call(js->vm, js, has_instance, r, args, 1, NULL, false);
if (is_err(result)) return result;
return js_bool(js_truthy(js, result));
}
if (hit != T_UNDEF && hit != T_NULL) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol.hasInstance is not callable");
}
}
ant_offset_t proto_off = lkp_interned(js, func_obj, INTERN_PROTOTYPE, 9);
if (proto_off == 0) return mkval(T_BOOL, 0);
ant_value_t ctor_proto = propref_load(js, proto_off);
uint8_t pt = vtype(ctor_proto);
if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC) return mkval(T_BOOL, 0);
if (ltype == T_STR || ltype == T_NUM || ltype == T_BOOL) {
ant_value_t type_proto = get_prototype_for_type(js, ltype);
return mkval(T_BOOL, vdata(ctor_proto) == vdata(type_proto) ? 1 : 0);
}
if (ltype != T_OBJ && ltype != T_ARR && ltype != T_FUNC && ltype != T_PROMISE) {
return mkval(T_BOOL, 0);
}
return walk_prototype_chain(js, l, ctor_proto);
}
ant_value_t do_in(ant_t *js, ant_value_t l, ant_value_t r) {
ant_offset_t prop_len;
const char *prop_name;
char num_buf[32];
ant_value_t key = js_to_primitive(js, l, 1);
if (is_err(key)) return key;
bool is_sym = (vtype(key) == T_SYMBOL);
if (is_sym) {
const char *d = js_sym_desc(js, key);
prop_name = d ? d : "symbol";
prop_len = (ant_offset_t)strlen(prop_name);
} else if (vtype(key) == T_NUM) {
prop_len = (ant_offset_t)strnum(key, num_buf, sizeof(num_buf));
prop_name = num_buf;
} else {
ant_value_t key_str = js_tostring_val(js, key);
if (is_err(key_str)) return key_str;
ant_offset_t prop_off = vstr(js, key_str, &prop_len);
prop_name = (char *)(uintptr_t)(prop_off);
}
if (!is_object_type(r)) {
if (vtype(r) == T_CFUNC) return mkval(T_BOOL, 0);
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot use 'in' operator to search for '%.*s' in non-object", (int)prop_len, prop_name);
}
if (is_proxy(r)) {
ant_value_t result = is_sym ? proxy_has_val(js, r, key) : proxy_has(js, r, prop_name, prop_len);
if (is_err(result)) return result;
return js_bool(js_truthy(js, result));
}
if (!is_sym && vtype(r) == T_ARR) {
unsigned long idx;
ant_offset_t arr_len = get_array_length(js, r);
if (parse_array_index(prop_name, prop_len, arr_len, &idx)) return mkval(T_BOOL, arr_has(js, r, (ant_offset_t)idx) ? 1 : 0);
if (is_length_key(prop_name, prop_len)) return mkval(T_BOOL, 1);
}
ant_offset_t found = is_sym ? lkp_sym_proto(js, r, (ant_offset_t)vdata(key)) : lkp_proto(js, r, prop_name, prop_len);
return mkval(T_BOOL, found != 0 ? 1 : 0);
}
static ant_value_t builtin_import_tla_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t me = js->current_func;
return get_slot(me, SLOT_DATA);
}
static ant_value_t builtin_import(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "import() requires a string specifier");
ant_value_t tla_promise = js_mkundef();
ant_value_t ns = js_esm_import_dynamic(js, args[0], &tla_promise);
if (is_err(ns)) return builtin_Promise_reject(js, &ns, 1);
if (vtype(tla_promise) == T_PROMISE) {
ant_value_t resolve_fn = make_data_cfunc(js, ns, builtin_import_tla_resolve);
ant_value_t saved = js->this_val;
js->this_val = tla_promise;
ant_value_t then_args[] = { resolve_fn };
ant_value_t result = builtin_promise_then(js, then_args, 1);
js->this_val = saved;
return result;
}
ant_value_t promise_args[] = { ns };
return builtin_Promise_resolve(js, promise_args, 1);
}
static ant_value_t js_get_import_meta_prop(ant_t *js) {
ant_value_t glob = js_glob(js);
ant_offset_t import_off = lkp(js, glob, "import", 6);
if (import_off == 0) return js_mkundef();
ant_value_t import_fn = propref_load(js, import_off);
if (vtype(import_fn) != T_FUNC) return js_mkundef();
return js_get(js, js_func_obj(import_fn), "meta");
}
static void js_set_import_meta_prop(ant_t *js, ant_value_t import_meta) {
ant_value_t glob = js_glob(js);
ant_offset_t import_off = lkp(js, glob, "import", 6);
if (import_off == 0) return;
ant_value_t import_fn = propref_load(js, import_off);
if (vtype(import_fn) != T_FUNC) return;
js_setprop(js, js_func_obj(import_fn), js_mkstr(js, "meta", 4), import_meta);
}
static ant_value_t js_get_current_import_meta(ant_t *js) {
ant_value_t import_meta = js_module_eval_active_import_meta(js);
if (vtype(import_meta) == T_OBJ) return import_meta;
return js_get_import_meta_prop(js);
}
static ant_value_t builtin_import_meta_resolve(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "import.meta.resolve() requires a string specifier");
ant_value_t import_meta = js_get_current_import_meta(js);
if (vtype(import_meta) == T_OBJ) {
ant_value_t filename = js_get(js, import_meta, "filename");
if (vtype(filename) == T_STR) {
ant_offset_t n = 0; ant_offset_t off = vstr(js, filename, &n);
return js_esm_resolve_specifier(js, args[0], (const char *)(uintptr_t)(off));
}
} return js_esm_resolve_specifier(js, args[0], NULL);
}
ant_value_t js_create_import_meta(ant_t *js, const char *filename, bool is_main) {
if (!filename) return js_mkundef();
ant_value_t import_meta = mkobj(js, 0);
if (is_err(import_meta)) return import_meta;
bool is_url = esm_is_url(filename);
ant_value_t url_val = is_url ? js_mkstr(js, filename, strlen(filename)) : js_esm_make_file_url(js, filename);
if (!is_err(url_val)) js_setprop(js, import_meta, js_mkstr(js, "url", 3), url_val);
ant_value_t filename_val = js_mkstr(js, filename, strlen(filename));
if (!is_err(filename_val)) js_setprop(js, import_meta, js_mkstr(js, "filename", 8), filename_val);
if (is_url) {
char *filename_copy = strdup(filename);
if (filename_copy) {
char *last_slash = strrchr(filename_copy, '/');
char *scheme_end = strstr(filename_copy, "://");
if (last_slash && scheme_end && last_slash > scheme_end + 2) {
*last_slash = '\0';
ant_value_t dirname_val = js_mkstr(js, filename_copy, strlen(filename_copy));
if (!is_err(dirname_val)) js_setprop(js, import_meta, js_mkstr(js, "dirname", 7), dirname_val);
}
free(filename_copy);
}
} else {
char *filename_copy = strdup(filename);
if (filename_copy) {
char *dir = dirname(filename_copy);
if (dir) {
ant_value_t dirname_val = js_mkstr(js, dir, strlen(dir));
if (!is_err(dirname_val)) js_setprop(js, import_meta, js_mkstr(js, "dirname", 7), dirname_val);
}
free(filename_copy);
}
}
js_setprop(js, import_meta, js_mkstr(js, "main", 4), is_main ? js_true : js_false);
ant_value_t resolve_fn = js_mkfun(builtin_import_meta_resolve);
js_setprop(js, import_meta, js_mkstr(js, "resolve", 7), resolve_fn);
return import_meta;
}
void js_setup_import_meta(ant_t *js, const char *filename) {
if (!filename) return;
ant_value_t import_meta = js_create_import_meta(js, filename, true);
if (is_err(import_meta)) return;
js_set_import_meta_prop(js, import_meta);
}
void js_module_eval_ctx_push(ant_t *js, ant_module_t *ctx) {
if (!js || !ctx) return;
ctx->prev = js->module;
ctx->prev_import_meta_prop = js_get_import_meta_prop(js);
js->module = ctx;
if (vtype(ctx->import_meta) != T_UNDEF)
js_set_import_meta_prop(js, ctx->import_meta);
}
void js_module_eval_ctx_pop(ant_t *js, ant_module_t *ctx) {
if (!js || !ctx) return;
if (js->module == ctx) {
js_set_import_meta_prop(js, ctx->prev_import_meta_prop);
js->module = ctx->prev;
}
}
static ant_proxy_state_t *get_proxy_data(ant_value_t obj) {
if (vtype(obj) != T_OBJ) return NULL;
ant_object_t *ptr = js_obj_ptr(obj);
return ptr ? ptr->proxy_state : NULL;
}
bool is_proxy(ant_value_t obj) {
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr || !ptr->is_exotic) return false;
return get_proxy_data(obj) != NULL;
}
static bool js_is_constructor_impl(ant_t *js, ant_value_t value) {
(void)js;
ant_value_t slow = value;
ant_value_t fast = value;
while (true) {
uint8_t t = vtype(slow);
if (t == T_FUNC) {
ant_object_t *obj = js_obj_ptr(js_func_obj(slow));
return obj && obj->is_constructor;
}
if (t != T_OBJ) return false;
ant_object_t *obj = js_obj_ptr(slow);
if (!obj) return false;
if (obj->is_constructor) return true;
if (!obj->is_exotic) return false;
ant_proxy_state_t *data = get_proxy_data(slow);
if (!data) return false;
slow = data->target;
for (int i = 0; i < 2; i++) {
if (vtype(fast) != T_OBJ) { fast = js_mknull(); break; }
ant_object_t *fobj = js_obj_ptr(fast);
if (!fobj || !fobj->is_exotic) { fast = js_mknull(); break; }
ant_proxy_state_t *fdata = get_proxy_data(fast);
if (!fdata) { fast = js_mknull(); break; }
fast = fdata->target;
}
if (same_object_identity(slow, fast)) return false;
}
}
bool js_is_constructor(ant_t *js, ant_value_t value) {
return js_is_constructor_impl(js, value);
}
static ant_value_t proxy_read_target(ant_t *js, ant_value_t obj) {
ant_proxy_state_t *data = get_proxy_data(obj);
return data ? data->target : obj;
}
static ant_offset_t proxy_aware_length(ant_t *js, ant_value_t obj) {
ant_value_t src = is_proxy(obj) ? proxy_read_target(js, obj) : obj;
if (vtype(src) == T_ARR) return get_array_length(js, src);
ant_offset_t off = lkp_interned(js, src, INTERN_LENGTH, 6);
if (off == 0) return 0;
ant_value_t len_val = propref_load(js, off);
return vtype(len_val) == T_NUM ? (ant_offset_t)tod(len_val) : 0;
}
static ant_value_t proxy_aware_get_elem(ant_t *js, ant_value_t obj, const char *key, size_t key_len) {
ant_value_t src = is_proxy(obj) ? proxy_read_target(js, obj) : obj;
ant_offset_t off = lkp(js, src, key, key_len);
return off ? propref_load(js, off) : js_mkundef();
}
static ant_value_t throw_proxy_error(ant_t *js, const char *message) {
return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message);
}
static bool proxy_target_is_extensible(ant_value_t obj) {
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return false;
ant_object_t *ptr = js_obj_ptr(js_as_obj(obj));
if (!ptr) return false;
if (ptr->frozen || ptr->sealed) return false;
return ptr->extensible != 0;
}
static bool proxy_target_prop_is_nonconfig(ant_t *js, ant_value_t target, ant_offset_t prop_off) {
(void)target;
return is_nonconfig_prop(js, prop_off);
}
static bool proxy_target_prop_is_const(ant_t *js, ant_value_t target, ant_offset_t prop_off) {
(void)target;
return is_const_prop(js, prop_off);
}
static ant_value_t proxy_get(ant_t *js, ant_value_t proxy, const char *key, size_t key_len) {
ant_proxy_state_t *data = get_proxy_data(proxy);
if (!data) return js_mkundef();
if (data->revoked) return throw_proxy_error(js, "Cannot perform 'get' on a proxy that has been revoked");
ant_value_t target = data->target;
ant_value_t handler = data->handler;
ant_offset_t get_trap_off = vtype(handler) == T_OBJ
? lkp_interned(js, handler, INTERN_GET, 3)
: 0;
if (get_trap_off != 0) {
ant_value_t get_trap = propref_load(js, get_trap_off);
if (vtype(get_trap) == T_FUNC || vtype(get_trap) == T_CFUNC) {
ant_value_t key_val = js_mkstr(js, key, key_len);
ant_value_t args[3] = { target, key_val, proxy };
ant_value_t result = sv_vm_call(js->vm, js, get_trap, js_mkundef(), args, 3, NULL, false);
if (is_err(result)) return result;
ant_offset_t prop_off = lkp(js, target, key, key_len);
if (prop_off != 0 && proxy_target_prop_is_nonconfig(js, target, prop_off) &&
proxy_target_prop_is_const(js, target, prop_off)) {
ant_value_t target_value = propref_load(js, prop_off);
if (!strict_eq_values(js, result, target_value))
return js_mkerr_typed(js, JS_ERR_TYPE, "'get' on proxy: trap returned invalid value for non-configurable, non-writable property");
}
prop_meta_t meta;
bool has_meta = lookup_string_prop_meta(js, js_as_obj(target), key, key_len, &meta);
if (has_meta && !meta.configurable) {
if (!meta.has_getter && !meta.has_setter && !meta.writable && prop_off != 0) {
ant_value_t target_value = propref_load(js, prop_off);
if (!strict_eq_values(js, result, target_value))
return js_mkerr_typed(js, JS_ERR_TYPE, "'get' on proxy: trap returned invalid value for non-configurable, non-writable property");
}
if ((meta.has_getter || meta.has_setter) && !meta.has_getter && vtype(result) != T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "'get' on proxy: trap returned non-undefined for property with undefined getter");
}
return result;
}
}
char key_buf[256];
size_t len = key_len < sizeof(key_buf) - 1 ? key_len : sizeof(key_buf) - 1;
memcpy(key_buf, key, len);
key_buf[len] = '\0';
ant_offset_t off = lkp(js, target, key_buf, len);
if (off != 0) return propref_load(js, off);
ant_offset_t proto_off = lkp_proto(js, target, key_buf, len);
if (proto_off != 0) return propref_load(js, proto_off);
return js_mkundef();
}
static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t value) {
ant_proxy_state_t *data = get_proxy_data(proxy);
if (!data) return js_mkundef();
if (data->revoked) return throw_proxy_error(js, "Cannot perform 'set' on a proxy that has been revoked");
ant_value_t target = data->target;
ant_value_t handler = data->handler;
ant_offset_t set_trap_off = vtype(handler) == T_OBJ ? lkp_interned(js, handler, INTERN_SET, 3) : 0;
if (set_trap_off != 0) {
ant_value_t set_trap = propref_load(js, set_trap_off);
if (vtype(set_trap) == T_FUNC || vtype(set_trap) == T_CFUNC) {
ant_value_t key_val = js_mkstr(js, key, key_len);
ant_value_t args[4] = { target, key_val, value, proxy };
ant_value_t result = sv_vm_call(js->vm, js, set_trap, js_mkundef(), args, 4, NULL, false);
if (is_err(result)) return result;
if (js_truthy(js, result)) {
ant_offset_t prop_off = lkp(js, target, key, key_len);
if (prop_off != 0 && proxy_target_prop_is_nonconfig(js, target, prop_off) &&
proxy_target_prop_is_const(js, target, prop_off)) {
ant_value_t target_value = propref_load(js, prop_off);
if (!strict_eq_values(js, value, target_value))
return js_mkerr_typed(js, JS_ERR_TYPE, "'set' on proxy: trap returned truthy for non-configurable, non-writable property with different value");
}
prop_meta_t meta;
bool has_meta = lookup_string_prop_meta(js, js_as_obj(target), key, key_len, &meta);
if (has_meta && !meta.configurable) {
if (!meta.has_getter && !meta.has_setter && !meta.writable && prop_off != 0) {
ant_value_t target_value = propref_load(js, prop_off);
if (!strict_eq_values(js, value, target_value))
return js_mkerr_typed(js, JS_ERR_TYPE, "'set' on proxy: trap returned truthy for non-configurable, non-writable property with different value");
}
if ((meta.has_getter || meta.has_setter) && !meta.has_setter)
return js_mkerr_typed(js, JS_ERR_TYPE, "'set' on proxy: trap returned truthy for property with undefined setter");
}
}
return js_true;
}
}
ant_value_t key_str = js_mkstr(js, key, key_len);
js_setprop(js, target, key_str, value);
return js_true;
}
static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size_t key_len) {
ant_proxy_state_t *data = get_proxy_data(proxy);
if (!data) return js_false;
if (data->revoked) return throw_proxy_error(js, "Cannot perform 'has' on a proxy that has been revoked");
ant_value_t target = data->target;
ant_value_t handler = data->handler;
ant_offset_t has_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "has", 3) : 0;
if (has_trap_off != 0) {
ant_value_t has_trap = propref_load(js, has_trap_off);
if (vtype(has_trap) == T_FUNC || vtype(has_trap) == T_CFUNC) {
ant_value_t key_val = js_mkstr(js, key, key_len);
ant_value_t args[2] = { target, key_val };
ant_value_t result = sv_vm_call(js->vm, js, has_trap, js_mkundef(), args, 2, NULL, false);
if (is_err(result)) return result;
if (!js_truthy(js, result)) {
ant_offset_t prop_off = lkp(js, target, key, key_len);
prop_meta_t meta;
bool has_meta = lookup_string_prop_meta(js, js_as_obj(target), key, key_len, &meta);
bool has_own = (prop_off != 0) || has_meta;
if ((prop_off != 0 && proxy_target_prop_is_nonconfig(js, target, prop_off)) || (has_meta && !meta.configurable))
return js_mkerr_typed(js, JS_ERR_TYPE, "'has' on proxy: trap returned falsy for non-configurable property");
if (has_own && !proxy_target_is_extensible(target))
return js_mkerr_typed(js, JS_ERR_TYPE, "'has' on proxy: trap returned falsy for existing property on non-extensible target");
}
return result;
}
}
char key_buf[256];
size_t len = key_len < sizeof(key_buf) - 1 ? key_len : sizeof(key_buf) - 1;
memcpy(key_buf, key, len);
key_buf[len] = '\0';
ant_offset_t off = lkp_proto(js, target, key_buf, len);
return js_bool(off != 0);
}
static ant_value_t proxy_delete(ant_t *js, ant_value_t proxy, const char *key, size_t key_len) {
ant_proxy_state_t *data = get_proxy_data(proxy);
if (!data) return js_true;
if (data->revoked) return throw_proxy_error(js, "Cannot perform 'deleteProperty' on a proxy that has been revoked");
ant_value_t target = data->target;
ant_value_t handler = data->handler;
ant_offset_t delete_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "deleteProperty", 14) : 0;
if (delete_trap_off != 0) {
ant_value_t delete_trap = propref_load(js, delete_trap_off);
if (vtype(delete_trap) == T_FUNC || vtype(delete_trap) == T_CFUNC) {
ant_value_t key_val = js_mkstr(js, key, key_len);
ant_value_t args[2] = { target, key_val };
ant_value_t result = sv_vm_call(js->vm, js, delete_trap, js_mkundef(), args, 2, NULL, false);
if (is_err(result)) return result;
if (js_truthy(js, result)) {
ant_offset_t prop_off = lkp(js, target, key, key_len);
if (prop_off != 0 && is_nonconfig_prop(js, prop_off))
return js_mkerr_typed(js, JS_ERR_TYPE, "'deleteProperty' on proxy: trap returned truthy for non-configurable property");
prop_meta_t meta;
if (lookup_string_prop_meta(js, js_as_obj(target), key, key_len, &meta) && !meta.configurable)
return js_mkerr_typed(js, JS_ERR_TYPE, "'deleteProperty' on proxy: trap returned truthy for non-configurable property");
}
return result;
}
}
ant_value_t key_str = js_mkstr(js, key, key_len);
js_setprop(js, target, key_str, js_mkundef());
return js_true;
}
static ant_value_t proxy_get_val(ant_t *js, ant_value_t proxy, ant_value_t key_val) {
ant_proxy_state_t *data = get_proxy_data(proxy);
if (!data) return js_mkundef();
if (data->revoked) return throw_proxy_error(js, "Cannot perform 'get' on a proxy that has been revoked");
ant_value_t target = data->target;
ant_value_t handler = data->handler;
ant_offset_t get_trap_off = vtype(handler) == T_OBJ
? lkp_interned(js, handler, INTERN_GET, 3) : 0;
if (get_trap_off != 0) {
ant_value_t get_trap = propref_load(js, get_trap_off);
if (vtype(get_trap) == T_FUNC || vtype(get_trap) == T_CFUNC) {
ant_value_t args[3] = { target, key_val, proxy };
return sv_vm_call(js->vm, js, get_trap, js_mkundef(), args, 3, NULL, false);
}
}
if (vtype(key_val) == T_SYMBOL) {
ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(key_val));
return off != 0 ? propref_load(js, off) : js_mkundef();
}
return proxy_get(js, proxy, "", 0);
}
static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_val) {
ant_proxy_state_t *data = get_proxy_data(proxy);
if (!data) return js_false;
if (data->revoked) return throw_proxy_error(js, "Cannot perform 'has' on a proxy that has been revoked");
ant_value_t target = data->target;
ant_value_t handler = data->handler;
ant_offset_t has_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "has", 3) : 0;
if (has_trap_off != 0) {
ant_value_t has_trap = propref_load(js, has_trap_off);
if (vtype(has_trap) == T_FUNC || vtype(has_trap) == T_CFUNC) {
ant_value_t args[2] = { target, key_val };
return sv_vm_call(js->vm, js, has_trap, js_mkundef(), args, 2, NULL, false);
}
}
if (vtype(key_val) == T_SYMBOL) {
ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(key_val));
return js_bool(off != 0);
}
return js_false;
}
static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t key_val) {
ant_proxy_state_t *data = get_proxy_data(proxy);
if (!data) return js_true;
if (data->revoked) return throw_proxy_error(js, "Cannot perform 'deleteProperty' on a proxy that has been revoked");
ant_value_t target = data->target;
ant_value_t handler = data->handler;
ant_offset_t delete_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "deleteProperty", 14) : 0;
if (delete_trap_off != 0) {
ant_value_t delete_trap = propref_load(js, delete_trap_off);
if (vtype(delete_trap) == T_FUNC || vtype(delete_trap) == T_CFUNC) {
ant_value_t args[2] = { target, key_val };
ant_value_t result = sv_vm_call(js->vm, js, delete_trap, js_mkundef(), args, 2, NULL, false);
if (is_err(result)) return result;
if (js_truthy(js, result) && vtype(key_val) == T_SYMBOL) {
ant_offset_t prop_off = lkp_sym(js, target, (ant_offset_t)vdata(key_val));
if (prop_off != 0 && is_nonconfig_prop(js, prop_off))
return js_mkerr_typed(js, JS_ERR_TYPE, "'deleteProperty' on proxy: trap returned truthy for non-configurable property");
}
return result;
}
}
if (vtype(key_val) == T_SYMBOL) {
return js_delete_sym_prop(js, target, key_val);
}
return js_true;
}
ant_value_t js_proxy_apply(ant_t *js, ant_value_t proxy, ant_value_t this_arg, ant_value_t *args, int argc) {
ant_proxy_state_t *data = get_proxy_data(proxy);
if (!data) return js_mkerr_typed(js, JS_ERR_TYPE, "object is not a function");
if (data->revoked) return throw_proxy_error(js, "Cannot perform 'apply' on a proxy that has been revoked");
ant_value_t target = data->target;
ant_value_t handler = data->handler;
uint8_t target_type = vtype(target);
if (target_type != T_FUNC && target_type != T_CFUNC && !(target_type == T_OBJ && is_proxy(target)))
return js_mkerr_typed(js, JS_ERR_TYPE, "%s is not a function", typestr(target_type));
ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "apply", 5) : 0;
if (trap_off != 0) {
ant_value_t trap = propref_load(js, trap_off);
if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) {
ant_value_t args_arr = mkarr(js);
for (int i = 0; i < argc; i++)
js_arr_push(js, args_arr, args[i]);
ant_value_t trap_args[3] = { target, this_arg, args_arr };
return sv_vm_call(js->vm, js, trap, handler, trap_args, 3, NULL, false);
}
}
return sv_vm_call(js->vm, js, target, this_arg, args, argc, NULL, false);
}
ant_value_t js_proxy_construct(ant_t *js, ant_value_t proxy, ant_value_t *args, int argc, ant_value_t new_target) {
ant_proxy_state_t *data = get_proxy_data(proxy);
if (!data) return js_mkerr_typed(js, JS_ERR_TYPE, "not a constructor");
if (data->revoked) return throw_proxy_error(js, "Cannot perform 'construct' on a proxy that has been revoked");
ant_value_t target = data->target;
ant_value_t handler = data->handler;
if (!js_is_constructor(js, target))
return js_mkerr_typed(js, JS_ERR_TYPE, "not a constructor");
if (vtype(target) == T_OBJ && is_proxy(target))
return js_proxy_construct(js, target, args, argc, new_target);
if (vtype(target) != T_FUNC)
return js_mkerr_typed(js, JS_ERR_TYPE, "not a constructor");
ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "construct", 9) : 0;
if (trap_off != 0) {
ant_value_t trap = propref_load(js, trap_off);
if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) {
ant_value_t args_arr = mkarr(js);
for (int i = 0; i < argc; i++)
js_arr_push(js, args_arr, args[i]);
ant_value_t trap_args[3] = { target, args_arr, new_target };
ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), trap_args, 3, NULL, false);
if (is_err(result)) return result;
if (!is_object_type(result))
return js_mkerr_typed(js, JS_ERR_TYPE, "'construct' on proxy: trap returned non-Object");
return result;
}
}
ant_value_t obj = mkobj(js, 0);
ant_value_t proto = js_getprop_fallback(js, target, "prototype");
if (is_object_type(proto)) js_set_proto_init(obj, proto);
ant_value_t saved = js->new_target;
js->new_target = new_target;
ant_value_t ctor_this = obj;
ant_value_t result = sv_vm_call(js->vm, js, target, obj, args, argc, &ctor_this, true);
js->new_target = saved;
if (is_err(result)) return result;
return is_object_type(result) ? result : (is_object_type(ctor_this) ? ctor_this : obj);
}
static ant_value_t mkproxy(ant_t *js, ant_value_t target, ant_value_t handler) {
ant_value_t proxy_obj = mkobj(js, 0);
ant_object_t *proxy_ptr = js_obj_ptr(proxy_obj);
if (!proxy_ptr) return js_mkerr(js, "out of memory");
ant_proxy_state_t *data = (ant_proxy_state_t *)ant_calloc(sizeof(ant_proxy_state_t));
if (!data) return js_mkerr(js, "out of memory");
data->target = target;
data->handler = handler;
data->revoked = false;
proxy_ptr->is_exotic = 1;
js_mark_constructor(proxy_obj, js_is_constructor(js, target));
proxy_ptr->proxy_state = data;
return proxy_obj;
}
static ant_value_t create_proxy_checked(ant_t *js, ant_value_t *args, int nargs, bool require_new) {
if (require_new && vtype(js->new_target) == T_UNDEF) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Proxy constructor requires 'new'");
}
if (nargs < 2) return js_mkerr(js, "Proxy requires two arguments: target and handler");
ant_value_t target = args[0];
ant_value_t handler = args[1];
uint8_t target_type = vtype(target);
if (target_type != T_OBJ && target_type != T_FUNC && target_type != T_ARR) {
return js_mkerr(js, "Proxy target must be an object");
}
uint8_t handler_type = vtype(handler);
if (handler_type != T_OBJ && handler_type != T_FUNC) {
return js_mkerr(js, "Proxy handler must be an object");
}
return mkproxy(js, target, handler);
}
static ant_value_t builtin_Proxy(ant_t *js, ant_value_t *args, int nargs) {
return create_proxy_checked(js, args, nargs, true);
}
static ant_value_t proxy_revoke_fn(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
ant_value_t func = js->current_func;
ant_value_t ref_slot = get_slot(func, SLOT_PROXY_REF);
if (vtype(ref_slot) != T_UNDEF && vdata(ref_slot) != 0) {
ant_proxy_state_t *data = get_proxy_data(ref_slot);
if (data) data->revoked = true;
}
return js_mkundef();
}
static ant_value_t builtin_Proxy_revocable(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t proxy = create_proxy_checked(js, args, nargs, false);
if (is_err(proxy)) return proxy;
ant_value_t revoke_obj = mkobj(js, 0);
set_slot(revoke_obj, SLOT_CFUNC, js_mkfun(proxy_revoke_fn));
set_slot(revoke_obj, SLOT_PROXY_REF, proxy);
ant_value_t revoke_func = js_obj_to_func(revoke_obj);
ant_value_t result = mkobj(js, 0);
js_setprop(js, result, js_mkstr(js, "proxy", 5), proxy);
js_setprop(js, result, js_mkstr(js, "revoke", 6), revoke_func);
return result;
}
ant_t *js_create(void *buf, size_t len) {
assert(
(uintptr_t)buf <= ((1ULL << 53) - 1) &&
"ANT_PTR: pointer exceeds 53-bit double-precision integer limit"
);
intern_init();
ant_t *js = NULL;
if (len < sizeof(*js)) return js;
memset(buf, 0, len);
js = (ant_t *) buf;
rt->js = js;
if (!fixed_arena_init(&js->obj_arena, sizeof(ant_object_t), offsetof(ant_object_t, mark_epoch), ANT_ARENA_MAX)) return NULL;
if (!fixed_arena_init(&js->closure_arena, sizeof(sv_closure_t), offsetof(sv_closure_t, gc_epoch), ANT_CLOSURE_ARENA_MAX)) {
fixed_arena_destroy(&js->obj_arena);
return NULL;
}
if (!fixed_arena_init(&js->upvalue_arena, sizeof(sv_upvalue_t), offsetof(sv_upvalue_t, gc_epoch), ANT_CLOSURE_ARENA_MAX)) {
fixed_arena_destroy(&js->closure_arena);
fixed_arena_destroy(&js->obj_arena);
return NULL;
}
js->c_root_cap = 64;
js->c_roots = calloc(js->c_root_cap, sizeof(*js->c_roots));
if (!js->c_roots) {
fixed_arena_destroy(&js->upvalue_arena);
fixed_arena_destroy(&js->closure_arena);
fixed_arena_destroy(&js->obj_arena);
return NULL;
}
js->global = mkobj(js, 0);
js->this_val = js->global;
js->new_target = js_mkundef();
js->length_str = ANT_STRING("length");
ant_value_t glob = js->global;
ant_value_t object_proto = js_mkobj(js);
set_proto(js, object_proto, js_mknull());
js_setprop(js, object_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_object_toString));
js_set_descriptor(js, object_proto, "toString", 8, JS_DESC_W | JS_DESC_C);
js_setprop(js, object_proto, js_mkstr(js, "valueOf", 7), js_mkfun(builtin_object_valueOf));
js_set_descriptor(js, object_proto, "valueOf", 7, JS_DESC_W | JS_DESC_C);
js_setprop(js, object_proto, js_mkstr(js, "toLocaleString", 14), js_mkfun(builtin_object_toLocaleString));
js_set_descriptor(js, object_proto, "toLocaleString", 14, JS_DESC_W | JS_DESC_C);
js_setprop(js, object_proto, js_mkstr(js, "hasOwnProperty", 14), js_mkfun(builtin_object_hasOwnProperty));
js_set_descriptor(js, object_proto, "hasOwnProperty", 14, JS_DESC_W | JS_DESC_C);
js_setprop(js, object_proto, js_mkstr(js, "isPrototypeOf", 13), js_mkfun(builtin_object_isPrototypeOf));
js_set_descriptor(js, object_proto, "isPrototypeOf", 13, JS_DESC_W | JS_DESC_C);
js_setprop(js, object_proto, js_mkstr(js, "propertyIsEnumerable", 20), js_mkfun(builtin_object_propertyIsEnumerable));
js_set_descriptor(js, object_proto, "propertyIsEnumerable", 20, JS_DESC_W | JS_DESC_C);
ant_value_t proto_getter = js_mkfun(builtin_proto_getter);
ant_value_t proto_setter = js_mkfun(builtin_proto_setter);
js_set_accessor_desc(js, object_proto, STR_PROTO, STR_PROTO_LEN, proto_getter, proto_setter, JS_DESC_C);
ant_value_t function_proto_obj = js_mkobj(js);
set_proto(js, function_proto_obj, object_proto);
set_slot(function_proto_obj, SLOT_CFUNC, js_mkfun(builtin_function_empty));
js_setprop(js, function_proto_obj, ANT_STRING("call"), js_mkfun(builtin_function_call));
js_setprop(js, function_proto_obj, ANT_STRING("apply"), js_mkfun(builtin_function_apply));
js_setprop(js, function_proto_obj, ANT_STRING("bind"), js_mkfun(builtin_function_bind));
js_setprop(js, function_proto_obj, ANT_STRING("toString"), js_mkfun(builtin_function_toString));
ant_value_t function_proto = js_obj_to_func(function_proto_obj);
set_slot(glob, SLOT_FUNC_PROTO, function_proto);
ant_value_t array_proto = js_mkobj(js);
set_proto(js, array_proto, object_proto);
js_setprop(js, array_proto, js_mkstr(js, "push", 4), js_mkfun(builtin_array_push));
js_setprop(js, array_proto, js_mkstr(js, "pop", 3), js_mkfun(builtin_array_pop));
js_setprop(js, array_proto, js_mkstr(js, "slice", 5), js_mkfun(builtin_array_slice));
js_setprop(js, array_proto, js_mkstr(js, "join", 4), js_mkfun(builtin_array_join));
js_setprop(js, array_proto, js_mkstr(js, "includes", 8), js_mkfun(builtin_array_includes));
js_setprop(js, array_proto, js_mkstr(js, "every", 5), js_mkfun(builtin_array_every));
js_setprop(js, array_proto, js_mkstr(js, "reverse", 7), js_mkfun(builtin_array_reverse));
js_setprop(js, array_proto, js_mkstr(js, "map", 3), js_mkfun(builtin_array_map));
js_setprop(js, array_proto, js_mkstr(js, "filter", 6), js_mkfun(builtin_array_filter));
js_setprop(js, array_proto, js_mkstr(js, "reduce", 6), js_mkfun(builtin_array_reduce));
js_setprop(js, array_proto, js_mkstr(js, "flat", 4), js_mkfun(builtin_array_flat));
js_setprop(js, array_proto, js_mkstr(js, "concat", 6), js_mkfun(builtin_array_concat));
js_setprop(js, array_proto, js_mkstr(js, "at", 2), js_mkfun(builtin_array_at));
js_setprop(js, array_proto, js_mkstr(js, "fill", 4), js_mkfun(builtin_array_fill));
js_setprop(js, array_proto, js_mkstr(js, "find", 4), js_mkfun(builtin_array_find));
js_setprop(js, array_proto, js_mkstr(js, "findIndex", 9), js_mkfun(builtin_array_findIndex));
js_setprop(js, array_proto, js_mkstr(js, "findLast", 8), js_mkfun(builtin_array_findLast));
js_setprop(js, array_proto, js_mkstr(js, "findLastIndex", 13), js_mkfun(builtin_array_findLastIndex));
js_setprop(js, array_proto, js_mkstr(js, "flatMap", 7), js_mkfun(builtin_array_flatMap));
js_setprop(js, array_proto, js_mkstr(js, "forEach", 7), js_mkfun(builtin_array_forEach));
js_setprop(js, array_proto, js_mkstr(js, "indexOf", 7), js_mkfun(builtin_array_indexOf));
js_setprop(js, array_proto, js_mkstr(js, "lastIndexOf", 11), js_mkfun(builtin_array_lastIndexOf));
js_setprop(js, array_proto, js_mkstr(js, "reduceRight", 11), js_mkfun(builtin_array_reduceRight));
js_setprop(js, array_proto, js_mkstr(js, "shift", 5), js_mkfun(builtin_array_shift));
js_setprop(js, array_proto, js_mkstr(js, "unshift", 7), js_mkfun(builtin_array_unshift));
js_setprop(js, array_proto, js_mkstr(js, "some", 4), js_mkfun(builtin_array_some));
js_setprop(js, array_proto, js_mkstr(js, "sort", 4), js_mkfun(builtin_array_sort));
js_setprop(js, array_proto, js_mkstr(js, "splice", 6), js_mkfun(builtin_array_splice));
js_setprop(js, array_proto, js_mkstr(js, "copyWithin", 10), js_mkfun(builtin_array_copyWithin));
js_setprop(js, array_proto, js_mkstr(js, "toReversed", 10), js_mkfun(builtin_array_toReversed));
js_setprop(js, array_proto, js_mkstr(js, "toSorted", 8), js_mkfun(builtin_array_toSorted));
js_setprop(js, array_proto, js_mkstr(js, "toSpliced", 9), js_mkfun(builtin_array_toSpliced));
js_setprop(js, array_proto, js_mkstr(js, "with", 4), js_mkfun(builtin_array_with));
js_setprop(js, array_proto, js_mkstr(js, "keys", 4), js_mkfun(builtin_array_keys));
js_setprop(js, array_proto, js_mkstr(js, "values", 6), js_mkfun(builtin_array_values));
js_setprop(js, array_proto, js_mkstr(js, "entries", 7), js_mkfun(builtin_array_entries));
js_setprop(js, array_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_array_toString));
js_setprop(js, array_proto, js_mkstr(js, "toLocaleString", 14), js_mkfun(builtin_array_toLocaleString));
ant_value_t string_proto = js_mkobj(js);
set_proto(js, string_proto, object_proto);
js_setprop(js, string_proto, js_mkstr(js, "indexOf", 7), js_mkfun(builtin_string_indexOf));
js_setprop(js, string_proto, js_mkstr(js, "substring", 9), js_mkfun(builtin_string_substring));
js_setprop(js, string_proto, js_mkstr(js, "substr", 6), js_mkfun(builtin_string_substr));
js_setprop(js, string_proto, js_mkstr(js, "split", 5), js_mkfun(builtin_string_split));
js_setprop(js, string_proto, js_mkstr(js, "slice", 5), js_mkfun(builtin_string_slice));
js_setprop(js, string_proto, js_mkstr(js, "includes", 8), js_mkfun(builtin_string_includes));
js_setprop(js, string_proto, js_mkstr(js, "startsWith", 10), js_mkfun(builtin_string_startsWith));
js_setprop(js, string_proto, js_mkstr(js, "endsWith", 8), js_mkfun(builtin_string_endsWith));
js_setprop(js, string_proto, js_mkstr(js, "template", 8), js_mkfun(builtin_string_template));
js_setprop(js, string_proto, js_mkstr(js, "charCodeAt", 10), js_mkfun(builtin_string_charCodeAt));
js_setprop(js, string_proto, js_mkstr(js, "codePointAt", 11), js_mkfun(builtin_string_codePointAt));
js_setprop(js, string_proto, js_mkstr(js, "toLowerCase", 11), js_mkfun(builtin_string_toLowerCase));
js_setprop(js, string_proto, js_mkstr(js, "toUpperCase", 11), js_mkfun(builtin_string_toUpperCase));
js_setprop(js, string_proto, js_mkstr(js, "toLocaleLowerCase", 17), js_mkfun(builtin_string_toLowerCase));
js_setprop(js, string_proto, js_mkstr(js, "toLocaleUpperCase", 17), js_mkfun(builtin_string_toUpperCase));
js_setprop(js, string_proto, js_mkstr(js, "trim", 4), js_mkfun(builtin_string_trim));
js_setprop(js, string_proto, js_mkstr(js, "trimStart", 9), js_mkfun(builtin_string_trimStart));
js_setprop(js, string_proto, js_mkstr(js, "trimEnd", 7), js_mkfun(builtin_string_trimEnd));
js_setprop(js, string_proto, js_mkstr(js, "repeat", 6), js_mkfun(builtin_string_repeat));
js_setprop(js, string_proto, js_mkstr(js, "padStart", 8), js_mkfun(builtin_string_padStart));
js_setprop(js, string_proto, js_mkstr(js, "padEnd", 6), js_mkfun(builtin_string_padEnd));
js_setprop(js, string_proto, js_mkstr(js, "charAt", 6), js_mkfun(builtin_string_charAt));
js_setprop(js, string_proto, js_mkstr(js, "at", 2), js_mkfun(builtin_string_at));
js_setprop(js, string_proto, js_mkstr(js, "lastIndexOf", 11), js_mkfun(builtin_string_lastIndexOf));
js_setprop(js, string_proto, js_mkstr(js, "concat", 6), js_mkfun(builtin_string_concat));
js_setprop(js, string_proto, js_mkstr(js, "localeCompare", 13), js_mkfun(builtin_string_localeCompare));
js_setprop(js, string_proto, js_mkstr(js, "normalize", 9), js_mkfun(builtin_string_normalize));
js_setprop(js, string_proto, js_mkstr(js, "valueOf", 7), js_mkfun(builtin_string_valueOf));
js_setprop(js, string_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_string_toString));
ant_value_t number_proto = js_mkobj(js);
set_proto(js, number_proto, object_proto);
js_setprop(js, number_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_number_toString));
js_setprop(js, number_proto, js_mkstr(js, "toFixed", 7), js_mkfun(builtin_number_toFixed));
js_setprop(js, number_proto, js_mkstr(js, "toPrecision", 11), js_mkfun(builtin_number_toPrecision));
js_setprop(js, number_proto, js_mkstr(js, "toExponential", 13), js_mkfun(builtin_number_toExponential));
js_setprop(js, number_proto, js_mkstr(js, "valueOf", 7), js_mkfun(builtin_number_valueOf));
js_setprop(js, number_proto, js_mkstr(js, "toLocaleString", 14), js_mkfun(builtin_number_toLocaleString));
ant_value_t boolean_proto = js_mkobj(js);
set_proto(js, boolean_proto, object_proto);
js_setprop(js, boolean_proto, js_mkstr(js, "valueOf", 7), js_mkfun(builtin_boolean_valueOf));
js_setprop(js, boolean_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_boolean_toString));
ant_value_t error_proto = js_mkobj(js);
set_proto(js, error_proto, object_proto);
js_setprop(js, error_proto, ANT_STRING("name"), ANT_STRING("Error"));
js_setprop(js, error_proto, ANT_STRING("message"), js_mkstr(js, "", 0));
js_setprop(js, error_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_Error_toString));
ant_value_t err_ctor_obj = mkobj(js, 0);
set_proto(js, err_ctor_obj, function_proto);
set_slot(err_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Error));
js_setprop_nonconfigurable(js, err_ctor_obj, "prototype", 9, error_proto);
js_setprop(js, err_ctor_obj, ANT_STRING("name"), ANT_STRING("Error"));
ant_value_t err_ctor_func = js_obj_to_func(err_ctor_obj);
js_setprop(js, glob, ANT_STRING("Error"), err_ctor_func);
js_setprop(js, error_proto, js_mkstr(js, "constructor", 11), err_ctor_func);
js_set_descriptor(js, error_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, err_ctor_func, ANT_STRING("isError"), js_mkfun(builtin_error_isError));
#define REGISTER_ERROR_SUBTYPE(name_str) do { \
ant_value_t proto = js_mkobj(js); \
set_proto(js, proto, error_proto); \
js_setprop(js, proto, ANT_STRING("name"), ANT_STRING(name_str)); \
ant_value_t ctor = mkobj(js, 0); \
set_proto(js, ctor, function_proto); \
set_slot(ctor, SLOT_CFUNC, js_mkfun(builtin_Error)); \
js_setprop_nonconfigurable(js, ctor, "prototype", 9, proto); \
js_setprop(js, ctor, ANT_STRING("name"), ANT_STRING(name_str)); \
ant_value_t ctor_func = js_obj_to_func(ctor); \
js_setprop(js, proto, ANT_STRING("constructor"), ctor_func); \
js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C); \
js_setprop(js, glob, ANT_STRING(name_str), ctor_func); \
} while(0)
REGISTER_ERROR_SUBTYPE("EvalError");
REGISTER_ERROR_SUBTYPE("RangeError");
REGISTER_ERROR_SUBTYPE("ReferenceError");
REGISTER_ERROR_SUBTYPE("SyntaxError");
REGISTER_ERROR_SUBTYPE("TypeError");
REGISTER_ERROR_SUBTYPE("URIError");
REGISTER_ERROR_SUBTYPE("InternalError");
#undef REGISTER_ERROR_SUBTYPE
ant_value_t proto = js_mkobj(js);
set_proto(js, proto, error_proto);
js_setprop(js, proto, ANT_STRING("name"), ANT_STRING("AggregateError"));
ant_value_t ctor = mkobj(js, 0);
set_proto(js, ctor, function_proto);
set_slot(ctor, SLOT_CFUNC, js_mkfun(builtin_AggregateError));
js_setprop_nonconfigurable(js, ctor, "prototype", 9, proto);
js_setprop(js, ctor, ANT_STRING("name"), ANT_STRING("AggregateError"));
js_setprop(js, proto, ANT_STRING("constructor"), js_obj_to_func(ctor));
js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, glob, ANT_STRING("AggregateError"), js_obj_to_func(ctor));
ant_value_t promise_proto = js_mkobj(js);
set_proto(js, promise_proto, object_proto);
js_setprop(js, promise_proto, js_mkstr(js, "then", 4), js_mkfun(builtin_promise_then));
js_setprop(js, promise_proto, js_mkstr(js, "catch", 5), js_mkfun(builtin_promise_catch));
js_setprop(js, promise_proto, js_mkstr(js, "finally", 7), js_mkfun(builtin_promise_finally));
ant_value_t obj_func_obj = mkobj(js, 0);
set_proto(js, obj_func_obj, function_proto);
set_slot(obj_func_obj, SLOT_BUILTIN, tov(BUILTIN_OBJECT));
js_mark_constructor(obj_func_obj, true);
js_setprop(js, obj_func_obj, js_mkstr(js, "keys", 4), js_mkfun(builtin_object_keys));
js_setprop(js, obj_func_obj, js_mkstr(js, "values", 6), js_mkfun(builtin_object_values));
js_setprop(js, obj_func_obj, js_mkstr(js, "entries", 7), js_mkfun(builtin_object_entries));
js_setprop(js, obj_func_obj, js_mkstr(js, "is", 2), js_mkfun(builtin_object_is));
js_setprop(js, obj_func_obj, js_mkstr(js, "getPrototypeOf", 14), js_mkfun(builtin_object_getPrototypeOf));
js_setprop(js, obj_func_obj, js_mkstr(js, "setPrototypeOf", 14), js_mkfun(builtin_object_setPrototypeOf));
js_setprop(js, obj_func_obj, js_mkstr(js, "create", 6), js_mkfun(builtin_object_create));
js_setprop(js, obj_func_obj, js_mkstr(js, "hasOwn", 6), js_mkfun(builtin_object_hasOwn));
js_setprop(js, obj_func_obj, js_mkstr(js, "groupBy", 7), js_mkfun(builtin_object_groupBy));
js_setprop(js, obj_func_obj, js_mkstr(js, "defineProperty", 14), js_mkfun(builtin_object_defineProperty));
js_setprop(js, obj_func_obj, js_mkstr(js, "defineProperties", 16), js_mkfun(builtin_object_defineProperties));
js_setprop(js, obj_func_obj, js_mkstr(js, "assign", 6), js_mkfun(builtin_object_assign));
js_setprop(js, obj_func_obj, js_mkstr(js, "freeze", 6), js_mkfun(builtin_object_freeze));
js_setprop(js, obj_func_obj, js_mkstr(js, "isFrozen", 8), js_mkfun(builtin_object_isFrozen));
js_setprop(js, obj_func_obj, js_mkstr(js, "seal", 4), js_mkfun(builtin_object_seal));
js_setprop(js, obj_func_obj, js_mkstr(js, "isSealed", 8), js_mkfun(builtin_object_isSealed));
js_setprop(js, obj_func_obj, js_mkstr(js, "fromEntries", 11), js_mkfun(builtin_object_fromEntries));
js_setprop(js, obj_func_obj, js_mkstr(js, "getOwnPropertyDescriptor", 24), js_mkfun(builtin_object_getOwnPropertyDescriptor));
js_setprop(js, obj_func_obj, js_mkstr(js, "getOwnPropertyNames", 19), js_mkfun(builtin_object_getOwnPropertyNames));
js_setprop(js, obj_func_obj, js_mkstr(js, "getOwnPropertySymbols", 21), js_mkfun(builtin_object_getOwnPropertySymbols));
js_setprop(js, obj_func_obj, js_mkstr(js, "isExtensible", 12), js_mkfun(builtin_object_isExtensible));
js_setprop(js, obj_func_obj, js_mkstr(js, "preventExtensions", 17), js_mkfun(builtin_object_preventExtensions));
js_setprop(js, obj_func_obj, ANT_STRING("name"), ANT_STRING("Object"));
js_setprop_nonconfigurable(js, obj_func_obj, "prototype", 9, object_proto);
ant_value_t obj_func = js_obj_to_func(obj_func_obj);
js_setprop(js, glob, js_mkstr(js, "Object", 6), obj_func);
ant_value_t func_ctor_obj = mkobj(js, 0);
set_proto(js, func_ctor_obj, function_proto);
set_slot(func_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Function));
js_setprop_nonconfigurable(js, func_ctor_obj, "prototype", 9, function_proto);
js_setprop(js, func_ctor_obj, js->length_str, tov(1.0));
js_set_descriptor(js, func_ctor_obj, "length", 6, JS_DESC_C);
js_setprop(js, func_ctor_obj, ANT_STRING("name"), ANT_STRING("Function"));
ant_value_t func_ctor_func = js_obj_to_func(func_ctor_obj);
js_setprop(js, glob, js_mkstr(js, "Function", 8), func_ctor_func);
ant_value_t async_func_proto_obj = js_mkobj(js);
set_proto(js, async_func_proto_obj, function_proto);
set_slot(async_func_proto_obj, SLOT_ASYNC, js_true);
ant_value_t async_func_proto = js_obj_to_func(async_func_proto_obj);
set_slot(glob, SLOT_ASYNC_PROTO, async_func_proto);
ant_value_t async_func_ctor_obj = mkobj(js, 0);
set_proto(js, async_func_ctor_obj, function_proto);
set_slot(async_func_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_AsyncFunction));
js_setprop_nonconfigurable(js, async_func_ctor_obj, "prototype", 9, async_func_proto);
js_setprop(js, async_func_ctor_obj, js->length_str, tov(1.0));
js_set_descriptor(js, async_func_ctor_obj, "length", 6, JS_DESC_C);
js_setprop(js, async_func_ctor_obj, ANT_STRING("name"), ANT_STRING("AsyncFunction"));
ant_value_t async_func_ctor = js_obj_to_func(async_func_ctor_obj);
js_setprop(js, async_func_proto_obj, js_mkstr(js, "constructor", 11), async_func_ctor);
js_set_descriptor(js, async_func_proto_obj, "constructor", 11, JS_DESC_W | JS_DESC_C);
ant_value_t str_ctor_obj = mkobj(js, 0);
set_proto(js, str_ctor_obj, function_proto);
set_slot(str_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_String));
js_setprop_nonconfigurable(js, str_ctor_obj, "prototype", 9, string_proto);
js_setprop(js, str_ctor_obj, js_mkstr(js, "fromCharCode", 12), js_mkfun(builtin_string_fromCharCode));
js_setprop(js, str_ctor_obj, js_mkstr(js, "fromCodePoint", 13), js_mkfun(builtin_string_fromCodePoint));
js_setprop(js, str_ctor_obj, js_mkstr(js, "raw", 3), js_mkfun(builtin_string_raw));
js_setprop(js, str_ctor_obj, ANT_STRING("name"), ANT_STRING("String"));
ant_value_t str_ctor_func = js_obj_to_func(str_ctor_obj);
js_setprop(js, glob, js_mkstr(js, "String", 6), str_ctor_func);
ant_value_t number_ctor_obj = mkobj(js, 0);
set_proto(js, number_ctor_obj, function_proto);
set_slot(number_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Number));
js_setprop(js, number_ctor_obj, js_mkstr(js, "isNaN", 5), js_mkfun(builtin_Number_isNaN));
js_setprop(js, number_ctor_obj, js_mkstr(js, "isFinite", 8), js_mkfun(builtin_Number_isFinite));
js_setprop(js, number_ctor_obj, js_mkstr(js, "isInteger", 9), js_mkfun(builtin_Number_isInteger));
js_setprop(js, number_ctor_obj, js_mkstr(js, "isSafeInteger", 13), js_mkfun(builtin_Number_isSafeInteger));
js_setprop(js, number_ctor_obj, js_mkstr(js, "parseInt", 8), js_mkfun(builtin_parseInt));
js_setprop(js, number_ctor_obj, js_mkstr(js, "parseFloat", 10), js_mkfun(builtin_parseFloat));
js_setprop(js, number_ctor_obj, js_mkstr(js, "MAX_VALUE", 9), tov(1.7976931348623157e+308));
js_setprop(js, number_ctor_obj, js_mkstr(js, "MIN_VALUE", 9), tov(5e-324));
js_setprop(js, number_ctor_obj, js_mkstr(js, "MAX_SAFE_INTEGER", 16), tov(9007199254740991.0));
js_setprop(js, number_ctor_obj, js_mkstr(js, "MIN_SAFE_INTEGER", 16), tov(-9007199254740991.0));
js_setprop(js, number_ctor_obj, js_mkstr(js, "POSITIVE_INFINITY", 17), tov(JS_INF));
js_setprop(js, number_ctor_obj, js_mkstr(js, "NEGATIVE_INFINITY", 17), tov(JS_NEG_INF));
js_setprop(js, number_ctor_obj, js_mkstr(js, "NaN", 3), tov(JS_NAN));
js_setprop(js, number_ctor_obj, js_mkstr(js, "EPSILON", 7), tov(2.220446049250313e-16));
js_setprop_nonconfigurable(js, number_ctor_obj, "prototype", 9, number_proto);
js_setprop(js, number_ctor_obj, ANT_STRING("name"), ANT_STRING("Number"));
ant_value_t number_ctor_func = js_obj_to_func(number_ctor_obj);
js_setprop(js, glob, js_mkstr(js, "Number", 6), number_ctor_func);
ant_value_t bool_ctor_obj = mkobj(js, 0);
set_proto(js, bool_ctor_obj, function_proto);
set_slot(bool_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Boolean));
js_setprop_nonconfigurable(js, bool_ctor_obj, "prototype", 9, boolean_proto);
js_setprop(js, bool_ctor_obj, ANT_STRING("name"), ANT_STRING("Boolean"));
ant_value_t bool_ctor_func = js_obj_to_func(bool_ctor_obj);
js_setprop(js, glob, js_mkstr(js, "Boolean", 7), bool_ctor_func);
ant_value_t arr_ctor_obj = mkobj(js, 0);
set_proto(js, arr_ctor_obj, function_proto);
set_slot(arr_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Array));
js_setprop_nonconfigurable(js, arr_ctor_obj, "prototype", 9, array_proto);
js_setprop(js, arr_ctor_obj, js_mkstr(js, "isArray", 7), js_mkfun(builtin_Array_isArray));
js_setprop(js, arr_ctor_obj, js_mkstr(js, "from", 4), js_mkfun(builtin_Array_from));
js_setprop(js, arr_ctor_obj, js_mkstr(js, "of", 2), js_mkfun(builtin_Array_of));
js_setprop(js, arr_ctor_obj, js->length_str, tov(1.0));
js_set_descriptor(js, arr_ctor_obj, "length", 6, JS_DESC_C);
js_setprop(js, arr_ctor_obj, ANT_STRING("name"), ANT_STRING("Array"));
ant_value_t arr_ctor_func = js_obj_to_func(arr_ctor_obj);
js_setprop(js, glob, js_mkstr(js, "Array", 5), arr_ctor_func);
ant_value_t proxy_ctor_obj = mkobj(js, 0);
set_proto(js, proxy_ctor_obj, function_proto);
set_slot(proxy_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Proxy));
js_mark_constructor(proxy_ctor_obj, true);
js_setprop(js, proxy_ctor_obj, js_mkstr(js, "revocable", 9), js_mkfun(builtin_Proxy_revocable));
js_setprop(js, proxy_ctor_obj, ANT_STRING("name"), ANT_STRING("Proxy"));
js_setprop(js, glob, js_mkstr(js, "Proxy", 5), js_obj_to_func(proxy_ctor_obj));
ant_value_t p_ctor_obj = mkobj(js, 0);
set_proto(js, p_ctor_obj, function_proto);
set_slot(p_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Promise));
js_setprop(js, p_ctor_obj, js_mkstr(js, "resolve", 7), js_mkfun(builtin_Promise_resolve));
js_setprop(js, p_ctor_obj, js_mkstr(js, "reject", 6), js_mkfun(builtin_Promise_reject));
js_setprop(js, p_ctor_obj, js_mkstr(js, "try", 3), js_mkfun(builtin_Promise_try));
js_setprop(js, p_ctor_obj, js_mkstr(js, "withResolvers", 13), js_mkfun(builtin_Promise_withResolvers));
js_setprop(js, p_ctor_obj, js_mkstr(js, "all", 3), js_mkfun(builtin_Promise_all));
js_setprop(js, p_ctor_obj, js_mkstr(js, "race", 4), js_mkfun(builtin_Promise_race));
js_setprop(js, p_ctor_obj, js_mkstr(js, "any", 3), js_mkfun(builtin_Promise_any));
js_setprop_nonconfigurable(js, p_ctor_obj, "prototype", 9, promise_proto);
js_setprop(js, p_ctor_obj, ANT_STRING("name"), ANT_STRING("Promise"));
js_setprop(js, glob, js_mkstr(js, "Promise", 7), js_obj_to_func(p_ctor_obj));
js_setprop(js, glob, js_mkstr(js, "parseInt", 8), js_mkfun(builtin_parseInt));
js_setprop(js, glob, js_mkstr(js, "parseFloat", 10), js_mkfun(builtin_parseFloat));
js_setprop(js, glob, js_mkstr(js, "eval", 4), js_mkfun(builtin_eval));
js_setprop(js, glob, js_mkstr(js, "isNaN", 5), js_mkfun(builtin_global_isNaN));
js_setprop(js, glob, js_mkstr(js, "isFinite", 8), js_mkfun(builtin_global_isFinite));
js_setprop(js, glob, js_mkstr(js, "btoa", 4), js_mkfun(builtin_btoa));
js_setprop(js, glob, js_mkstr(js, "atob", 4), js_mkfun(builtin_atob));
js_setprop(js, glob, js_mkstr(js, "NaN", 3), tov(JS_NAN));
js_set_descriptor(js, glob, "NaN", 3, 0);
js_setprop(js, glob, js_mkstr(js, "Infinity", 8), tov(JS_INF));
js_set_descriptor(js, glob, "Infinity", 8, 0);
js_setprop(js, glob, js_mkstr(js, "undefined", 9), js_mkundef());
js_set_descriptor(js, glob, "undefined", 9, 0);
ant_value_t import_obj = mkobj(js, 0);
set_proto(js, import_obj, function_proto);
set_slot(import_obj, SLOT_CFUNC, js_mkfun(builtin_import));
js_setprop(js, glob, js_mkstr(js, "import", 6), js_obj_to_func(import_obj));
js_setprop(js, object_proto, js_mkstr(js, "constructor", 11), obj_func);
js_set_descriptor(js, object_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, function_proto, js_mkstr(js, "constructor", 11), func_ctor_func);
js_set_descriptor(js, js_as_obj(function_proto), "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, array_proto, js_mkstr(js, "constructor", 11), arr_ctor_func);
js_set_descriptor(js, array_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, string_proto, js_mkstr(js, "constructor", 11), str_ctor_func);
js_set_descriptor(js, string_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, number_proto, js_mkstr(js, "constructor", 11), number_ctor_func);
js_set_descriptor(js, number_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, boolean_proto, js_mkstr(js, "constructor", 11), bool_ctor_func);
js_set_descriptor(js, boolean_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
set_proto(js, glob, object_proto);
js->object = object_proto;
js->owns_mem = false;
js->max_size = 0;
return js;
}
ant_t *js_create_dynamic() {
ant_t *js = (ant_t *)calloc(1, sizeof(*js));
if (js == NULL) return NULL;
if (js_create(js, sizeof(*js)) == NULL) {
free(js);
return NULL;
}
js->owns_mem = true;
js->vm = sv_vm_create(js, SV_VM_MAIN);
return js;
}
void js_destroy(ant_t *js) {
if (js == NULL) return;
if (js->vm) {
sv_vm_destroy(js->vm);
js->vm = NULL;
}
js_esm_cleanup_module_cache();
code_arena_reset();
cleanup_buffer_module();
cleanup_lmdb_module();
ant_object_t *lists[] = { js->objects, js->objects_old, js->permanent_objects };
for (int i = 0; i < 3; i++)
for (ant_object_t *obj = lists[i]; obj;) {
ant_object_t *next = obj->next;
gc_object_free(js, obj);
obj = next;
}
js->objects = NULL;
js->objects_old = NULL;
js->permanent_objects = NULL;
fixed_arena_destroy(&js->obj_arena);
fixed_arena_destroy(&js->closure_arena);
fixed_arena_destroy(&js->upvalue_arena);
free(js->prop_refs);
js->prop_refs = NULL;
js->prop_refs_len = js->prop_refs_cap = 0;
free(js->c_roots);
js->c_roots = NULL;
js->c_root_count = js->c_root_cap = 0;
js_pool_destroy(&js->pool.rope);
js_pool_destroy(&js->pool.symbol);
js_pool_destroy(&js->pool.permanent);
js_class_pool_destroy(&js->pool.bigint);
js_class_pool_destroy(&js->pool.string);
destroy_runtime(js);
if (js->owns_mem) free(js);
}
inline double js_getnum(ant_value_t value) { return tod(value); }
inline void js_setstackbase(ant_t *js, void *base) { js->cstk.base = base; js->cstk.main_base = base; }
inline void js_setstacklimit(ant_t *js, size_t max) { js->cstk.limit = max; }
inline void js_set_filename(ant_t *js, const char *filename) { js->filename = filename; }
inline ant_value_t js_mkundef(void) { return mkval(T_UNDEF, 0); }
inline ant_value_t js_mknull(void) { return mkval(T_NULL, 0); }
inline ant_value_t js_mknum(double value) { return tov(value); }
inline ant_value_t js_mkobj(ant_t *js) { return mkobj(js, 0); }
inline ant_value_t js_glob(ant_t *js) { return js->global; }
inline ant_value_t js_mkfun(ant_value_t (*fn)(ant_t *, ant_value_t *, int)) { return mkval(T_CFUNC, (size_t) (void *) fn); }
inline ant_value_t js_getthis(ant_t *js) { return js->this_val; }
inline void js_setthis(ant_t *js, ant_value_t val) { js->this_val = val; }
inline ant_value_t js_getcurrentfunc(ant_t *js) { return js->current_func; }
ant_value_t js_heavy_mkfun(ant_t *js, ant_value_t (*fn)(ant_t *, ant_value_t *, int), ant_value_t data) {
ant_value_t cfunc = js_mkfun(fn);
ant_value_t fn_obj = mkobj(js, 0);
set_slot(fn_obj, SLOT_CFUNC, cfunc);
set_slot(fn_obj, SLOT_DATA, data);
return js_obj_to_func(fn_obj);
}
void js_set(ant_t *js, ant_value_t obj, const char *key, ant_value_t val) {
size_t key_len = strlen(key);
if (vtype(obj) == T_OBJ) {
ant_offset_t existing = lkp(js, obj, key, key_len);
if (existing > 0) {
if (is_const_prop(js, existing)) {
js_mkerr(js, "assignment to constant");
return;
}
js_saveval(js, existing, val);
} else {
ant_value_t key_str = js_mkstr(js, key, key_len);
mkprop(js, obj, key_str, val, 0);
}
} else if (vtype(obj) == T_FUNC) {
ant_value_t func_obj = js_func_obj(obj);
ant_offset_t existing = lkp(js, func_obj, key, key_len);
if (existing > 0) {
if (is_const_prop(js, existing)) {
js_mkerr(js, "assignment to constant");
return;
}
js_saveval(js, existing, val);
} else {
ant_value_t key_str = js_mkstr(js, key, key_len);
mkprop(js, func_obj, key_str, val, 0);
}
}
}
void js_set_sym(ant_t *js, ant_value_t obj, ant_value_t sym, ant_value_t val) {
if (vtype(sym) != T_SYMBOL) return;
ant_offset_t sym_off = (ant_offset_t)vdata(sym);
if (vtype(obj) == T_FUNC) obj = js_func_obj(obj);
if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR) return;
ant_offset_t existing = lkp_sym(js, obj, sym_off);
if (existing > 0) {
if (is_const_prop(js, existing)) return;
js_saveval(js, existing, val);
} else mkprop(js, obj, sym, val, 0);
}
ant_value_t js_get_sym(ant_t *js, ant_value_t obj, ant_value_t sym) {
if (vtype(sym) != T_SYMBOL) return js_mkundef();
ant_offset_t sym_off = (ant_offset_t)vdata(sym);
ant_value_t receiver = obj;
if (vtype(obj) == T_FUNC) obj = js_func_obj(obj);
uint8_t ot = vtype(obj);
if (!is_object_type(obj)) {
if (ot == T_STR || ot == T_NUM || ot == T_BOOL || ot == T_BIGINT || ot == T_SYMBOL) {
ant_value_t proto = get_prototype_for_type(js, ot);
if (!is_object_type(proto)) return js_mkundef();
obj = js_as_obj(proto);
} else {
return js_mkundef();
}
} else {
obj = js_as_obj(obj);
}
if (is_proxy(obj)) return proxy_get_val(js, obj, sym);
ant_value_t cur = obj;
proto_overflow_guard_t guard;
proto_overflow_guard_init(&guard);
while (is_object_type(cur)) {
ant_value_t cur_obj = js_as_obj(cur);
prop_meta_t meta;
if (lookup_symbol_prop_meta(cur_obj, sym_off, &meta)) {
if (meta.has_getter) {
ant_value_t g = meta.getter;
if (vtype(g) == T_FUNC || vtype(g) == T_CFUNC)
return sv_vm_call(js->vm, js, g, receiver, NULL, 0, NULL, false);
return js_mkundef();
}
if (meta.has_setter && !meta.has_getter) return js_mkundef();
break;
}
ant_value_t proto = get_proto(js, cur_obj);
if (!is_object_type(proto)) break;
cur = js_as_obj(proto);
if (proto_overflow_guard_hit_cycle(js, &guard, cur)) break;
}
ant_offset_t off = lkp_sym_proto(js, obj, sym_off);
if (off == 0) return js_mkundef();
return propref_load(js, off);
}
static bool js_try_get(ant_t *js, ant_value_t obj, const char *key, ant_value_t *out) {
size_t key_len = strlen(key);
if (vtype(obj) == T_FUNC) {
if (sv_vm_is_strict(js->vm) &&
((key_len == 6 && memcmp(key, "caller", 6) == 0) ||
(key_len == 9 && memcmp(key, "arguments", 9) == 0))) {
*out = js_mkerr_typed(js, JS_ERR_TYPE,
"'%.*s' not allowed on functions in strict mode",
(int)key_len, key);
return true;
}
ant_value_t func_obj = js_func_obj(obj);
ant_value_t import_meta = js_get_current_import_meta(js);
if (key_len == 4 && memcmp(key, "meta", 4) == 0 && vtype(import_meta) != T_UNDEF) {
ant_value_t cfunc = js_get_slot(func_obj, SLOT_CFUNC);
if (vtype(cfunc) == T_CFUNC && js_as_cfunc(cfunc) == builtin_import) {
*out = import_meta;
return true;
}
}
ant_offset_t off = lkp(js, func_obj, key, key_len);
if (off == 0) {
ant_value_t accessor_result;
if (try_accessor_getter(js, obj, key, key_len, &accessor_result)) {
*out = accessor_result;
return true;
}
return false;
}
const ant_shape_prop_t *prop_meta = prop_shape_meta(js, off);
if (prop_meta && prop_meta->has_getter) {
ant_value_t accessor_result;
if (try_accessor_getter(js, obj, key, key_len, &accessor_result)) {
*out = accessor_result;
return true;
}
}
*out = propref_load(js, off);
return true;
}
if (array_obj_ptr(obj)) {
if (((key_len == 6 && memcmp(key, "callee", 6) == 0) ||
(key_len == 6 && memcmp(key, "caller", 6) == 0)) &&
vtype(get_slot(obj, SLOT_STRICT_ARGS)) != T_UNDEF) {
*out = js_mkerr_typed(js, JS_ERR_TYPE,
"'%.*s' not allowed on strict arguments",
(int)key_len, key);
return true;
}
if (is_length_key(key, key_len)) {
*out = tov((double)get_array_length(js, obj));
return true;
}
unsigned long idx;
ant_offset_t arr_len = get_array_length(js, obj);
if (parse_array_index(key, key_len, arr_len, &idx)) {
if (arr_has(js, obj, (ant_offset_t)idx)) {
*out = arr_get(js, obj, (ant_offset_t)idx);
return true;
} return false;
}
ant_value_t arr_obj = js_as_obj(obj);
ant_offset_t off = lkp(js, arr_obj, key, key_len);
if (off == 0) {
ant_value_t accessor_result;
if (try_accessor_getter(js, arr_obj, key, key_len, &accessor_result)) {
*out = accessor_result; return true;
} return false;
}
const ant_shape_prop_t *prop_meta = prop_shape_meta(js, off);
if (prop_meta && prop_meta->has_getter) {
ant_value_t accessor_result;
if (try_accessor_getter(js, arr_obj, key, key_len, &accessor_result)) {
*out = accessor_result; return true;
}
}
*out = propref_load(js, off);
return true;
}
uint8_t t = vtype(obj);
bool is_promise = (t == T_PROMISE);
if (t == T_OBJ && is_proxy(obj)) {
*out = proxy_get(js, obj, key, key_len);
return true;
}
if (t == T_STR || t == T_NUM || t == T_BOOL) {
if (t == T_STR && is_length_key(key, key_len)) {
ant_offset_t byte_len = 0; ant_offset_t str_off = vstr(js, obj, &byte_len);
const char *str_data = (const char *)(uintptr_t)(str_off);
*out = tov((double)utf16_strlen(str_data, byte_len));
return true;
}
if (t == T_STR && js_try_get_string_index(js, obj, key, key_len, out)) return true;
ant_value_t boxed = mkobj(js, 0);
js_set_slot(js_as_obj(boxed), SLOT_PRIMITIVE, obj);
obj = boxed; t = T_OBJ;
}
if (is_promise) obj = js_as_obj(obj);
else if (t != T_OBJ) return false;
ant_offset_t off = lkp(js, obj, key, key_len);
if (off == 0) {
ant_value_t result = try_dynamic_getter(js, obj, key, key_len);
if (vtype(result) != T_UNDEF) { *out = result; return true; }
}
if (off == 0 && is_promise) {
ant_value_t promise_proto = get_ctor_proto(js, "Promise", 7);
if (vtype(promise_proto) != T_UNDEF && vtype(promise_proto) != T_NULL) {
off = lkp(js, promise_proto, key, key_len);
if (off != 0) { *out = propref_load(js, off); return true; }
}
}
if (off == 0) {
ant_value_t accessor_result;
if (try_accessor_getter(js, obj, key, key_len, &accessor_result)) {
*out = accessor_result; return true;
}
return false;
}
const ant_shape_prop_t *prop_meta = prop_shape_meta(js, off);
if (prop_meta && prop_meta->has_getter) {
ant_value_t accessor_result;
if (try_accessor_getter(js, obj, key, key_len, &accessor_result)) {
*out = accessor_result; return true;
}
}
*out = propref_load(js, off);
return true;
}
ant_value_t js_get(ant_t *js, ant_value_t obj, const char *key) {
ant_value_t val;
if (js_try_get(js, obj, key, &val)) return val;
return js_mkundef();
}
ant_value_t js_getprop_proto(ant_t *js, ant_value_t obj, const char *key) {
size_t key_len = strlen(key);
ant_offset_t off = lkp_proto(js, obj, key, key_len);
return off == 0 ? js_mkundef() : propref_load(js, off);
}
ant_value_t js_getprop_fallback(ant_t *js, ant_value_t obj, const char *name) {
ant_value_t val;
if (js_try_get(js, obj, name, &val)) return val;
return js_getprop_proto(js, obj, name);
}
ant_value_t js_getprop_super(ant_t *js, ant_value_t super_obj, ant_value_t receiver, const char *name) {
if (!name) return js_mkundef();
if (vtype(super_obj) == T_FUNC) super_obj = js_func_obj(super_obj);
if (!is_object_type(super_obj)) return js_mkundef();
size_t key_len = strlen(name);
if (is_proxy(super_obj)) return proxy_get(js, super_obj, name, key_len);
const char *key_intern = intern_string(name, key_len);
if (!key_intern) return js_mkundef();
ant_value_t cur = super_obj;
proto_overflow_guard_t guard;
proto_overflow_guard_init(&guard);
while (is_object_type(cur)) {
ant_value_t cur_obj = js_as_obj(cur);
ant_object_t *cur_ptr = js_obj_ptr(cur_obj);
bool handled = false;
if (cur_ptr && cur_ptr->shape) {
int32_t slot = ant_shape_lookup_interned(cur_ptr->shape, key_intern);
if (slot >= 0) {
const ant_shape_prop_t *prop = ant_shape_prop_at(cur_ptr->shape, (uint32_t)slot);
if (prop && prop->has_getter) {
ant_value_t getter = prop->getter;
if (vtype(getter) == T_FUNC || vtype(getter) == T_CFUNC)
return sv_vm_call(js->vm, js, getter, receiver, NULL, 0, NULL, false);
return js_mkundef();
}
if (prop && prop->has_setter) return js_mkundef();
handled = true;
}
}
if (!handled && cur_ptr && cur_ptr->is_exotic) {
descriptor_entry_t *desc = lookup_descriptor(cur_obj, name, key_len);
if (desc) {
if (desc->has_getter) {
ant_value_t getter = desc->getter;
if (vtype(getter) == T_FUNC || vtype(getter) == T_CFUNC)
return sv_vm_call(js->vm, js, getter, receiver, NULL, 0, NULL, false);
return js_mkundef();
}
if (desc->has_setter) return js_mkundef();
}
}
ant_offset_t prop_off = lkp_interned(js, cur_obj, key_intern, key_len);
if (prop_off != 0) return propref_load(js, prop_off);
ant_value_t proto = get_proto(js, cur_obj);
if (!is_object_type(proto)) break;
cur = proto;
if (proto_overflow_guard_hit_cycle(js, &guard, cur)) break;
}
return js_mkundef();
}
typedef struct {
bool (*callback)(ant_t *js, ant_value_t value, void *udata);
void *udata;
} js_iter_ctx_t;
static iter_action_t js_iter_cb(ant_t *js, ant_value_t value, void *ctx, ant_value_t *out) {
js_iter_ctx_t *ictx = (js_iter_ctx_t *)ctx;
return ictx->callback(js, value, ictx->udata) ? ITER_CONTINUE : ITER_BREAK;
}
bool js_iter(ant_t *js, ant_value_t iterable, bool (*callback)(ant_t *js, ant_value_t value, void *udata), void *udata) {
js_iter_ctx_t ctx = { .callback = callback, .udata = udata };
ant_value_t result = iter_foreach(js, iterable, js_iter_cb, &ctx);
return !is_err(result);
}
char *js_getstr(ant_t *js, ant_value_t value, size_t *len) {
(void)js;
if (vtype(value) != T_STR) return NULL;
ant_offset_t n, off = vstr(js, value, &n);
if (len != NULL) *len = n;
return (char *)(uintptr_t)off;
}
void js_merge_obj(ant_t *js, ant_value_t dst, ant_value_t src) {
if (vtype(dst) != T_OBJ || vtype(src) != T_OBJ) return;
ant_value_t as_src = js_as_obj(src);
ant_object_t *src_obj = js_obj_ptr(as_src);
if (!src_obj || !src_obj->shape) return;
uint32_t count = ant_shape_count(src_obj->shape);
for (uint32_t i = 0; i < count; i++) {
const ant_shape_prop_t *prop = ant_shape_prop_at(src_obj->shape, i);
if (!prop || prop->type != ANT_SHAPE_KEY_STRING) continue;
const char *key = prop->key.interned;
ant_value_t val = (i < src_obj->prop_count) ? ant_object_prop_get_unchecked(src_obj, i) : js_mkundef();
js_setprop(js, dst, js_mkstr(js, key, strlen(key)), val);
}
}
bool js_chkargs(ant_value_t *args, int nargs, const char *spec) {
int i = 0, ok = 1;
for (; ok && i < nargs && spec[i]; i++) {
uint8_t t = vtype(args[i]), c = (uint8_t) spec[i];
ok = (c == 'b' && t == T_BOOL) || (c == 'd' && t == T_NUM) ||
(c == 's' && t == T_STR) || (c == 'j');
}
if (spec[i] != '\0' || i != nargs) ok = 0;
return ok;
}
static ant_value_t js_eval_bytecode_mode(ant_t *js, const char *buf, size_t len, sv_compile_mode_t mode, bool parse_strict) {
if (len == (size_t)~0U) len = strlen(buf);
sv_ast_t *program = sv_parse(js, buf, (ant_offset_t)len, parse_strict);
if (!program) {
if (js->thrown_exists) return mkval(T_ERR, 0);
return js_mkerr_typed(js, JS_ERR_INTERNAL | JS_ERR_NO_STACK, "Unexpected parse error");
}
sv_func_t *func = sv_compile(js, program, mode, buf, (ant_offset_t)len);
if (!func) {
if (js->thrown_exists) return mkval(T_ERR, 0);
return js_mkerr_typed(js, JS_ERR_INTERNAL | JS_ERR_NO_STACK, "Unexpected compile error");
}
js_clear_error_site(js);
ant_value_t result;
// TODO: this-newtarget-frame-migration
ant_value_t saved_this = js->this_val;
if (sv_dump_bytecode_unlikely) sv_disasm(js, func, js->filename);
if (func->is_tla) result = sv_execute_entry_tla(js, func, js->this_val);
else result = sv_execute_entry(js->vm, func, js->this_val, NULL, 0);
js->this_val = saved_this;
return result;
}
ant_value_t js_eval_bytecode(ant_t *js, const char *buf, size_t len) {
return js_eval_bytecode_mode(js, buf, len, SV_COMPILE_SCRIPT, false);
}
ant_value_t js_eval_bytecode_module(ant_t *js, const char *buf, size_t len) {
return js_eval_bytecode_mode(js, buf, len, SV_COMPILE_MODULE, false);
}
ant_value_t js_eval_bytecode_eval(ant_t *js, const char *buf, size_t len) {
return js_eval_bytecode_mode(js, buf, len, SV_COMPILE_EVAL, false);
}
ant_value_t js_eval_bytecode_eval_with_strict(ant_t *js, const char *buf, size_t len, bool inherit_strict) {
return js_eval_bytecode_mode(js, buf, len, SV_COMPILE_EVAL, inherit_strict);
}
ant_value_t js_eval_bytecode_repl(ant_t *js, const char *buf, size_t len) {
return js_eval_bytecode_mode(js, buf, len, SV_COMPILE_REPL, false);
}
ant_value_t sv_call_native(
ant_t *js, ant_value_t func, ant_value_t this_val,
ant_value_t *args, int nargs
) {
if (vtype(func) == T_FFI)
return ffi_call_by_index(js, (unsigned int)vdata(func), args, nargs);
if (vtype(func) == T_CFUNC) {
ant_value_t saved_this = js->this_val;
js->this_val = this_val;
ant_value_t (*fn)(ant_t *, ant_value_t *, int) = (ant_value_t(*)(ant_t *, ant_value_t *, int))vdata(func);
ant_value_t res = fn(js, args, nargs);
js->this_val = saved_this;
return res;
}
if (vtype(func) == T_FUNC) {
sv_closure_t *closure = js_func_closure(func);
ant_value_t func_obj = closure->func_obj;
ant_value_t cfunc_slot = get_slot(func_obj, SLOT_CFUNC);
if (vtype(cfunc_slot) == T_CFUNC) {
ant_value_t resolve_this = (vtype(closure->bound_this) != T_UNDEF) ? closure->bound_this : this_val;
int final_nargs = nargs;
ant_value_t *final_args = args;
ant_value_t *combined = NULL;
if ((closure->call_flags & SV_CALL_HAS_BOUND_ARGS) && closure->bound_argc > 0) {
combined = sv_prepend_bound_args(closure, args, nargs, &final_nargs);
if (combined) final_args = combined;
}
ant_value_t saved_func = js->current_func;
ant_value_t saved_this = js->this_val;
js->current_func = func;
js->this_val = resolve_this;
ant_value_t (*fn)(ant_t *, ant_value_t *, int) = (ant_value_t(*)(ant_t *, ant_value_t *, int))vdata(cfunc_slot);
ant_value_t res = fn(js, final_args, final_nargs);
js->current_func = saved_func;
js->this_val = saved_this;
if (combined) free(combined);
return res;
}
ant_value_t builtin_slot = get_slot(func_obj, SLOT_BUILTIN);
if (vtype(builtin_slot) == T_NUM && (int)tod(builtin_slot) == BUILTIN_OBJECT) {
ant_value_t saved_this = js->this_val;
js->this_val = this_val;
ant_value_t res = builtin_Object(js, args, nargs);
js->this_val = saved_this;
return res;
}
}
return js_mkerr_typed(js, JS_ERR_TYPE, "%s is not a function", typestr(vtype(func)));
}
ant_iter_t js_prop_iter_begin(ant_t *js, ant_value_t obj) {
typedef struct {
ant_t *js;
ant_object_t *obj;
uint32_t index;
} prop_iter_ctx_t;
ant_iter_t iter = {.ctx = NULL, .off = 0};
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return iter;
prop_iter_ctx_t *ctx = calloc(1, sizeof(*ctx));
if (!ctx) return iter;
ctx->js = js;
ctx->obj = js_obj_ptr(js_as_obj(obj));
ctx->index = 0;
if (!ctx->obj || !ctx->obj->shape) {
free(ctx);
return iter;
}
iter.ctx = ctx;
return iter;
}
bool js_prop_iter_next(ant_iter_t *iter, const char **key, size_t *key_len, ant_value_t *value) {
typedef struct {
ant_t *js;
ant_object_t *obj;
uint32_t index;
} prop_iter_ctx_t;
if (!iter || !iter->ctx) return false;
prop_iter_ctx_t *ctx = (prop_iter_ctx_t *)iter->ctx;
ant_object_t *obj = ctx->obj;
if (!obj || !obj->shape) return false;
uint32_t count = ant_shape_count(obj->shape);
while (ctx->index < count) {
uint32_t i = ctx->index++;
const ant_shape_prop_t *prop = ant_shape_prop_at(obj->shape, i);
if (!prop) continue;
if (prop->type == ANT_SHAPE_KEY_SYMBOL) continue;
if (i >= obj->prop_count) continue;
if (key) {
*key = prop->key.interned;
if (key_len) *key_len = strlen(prop->key.interned);
}
if (value) *value = ant_object_prop_get_unchecked(obj, i);
iter->off = i + 1;
return true;
}
return false;
}
void js_prop_iter_end(ant_iter_t *iter) {
if (!iter) return;
free(iter->ctx);
iter->off = 0;
iter->ctx = NULL;
}
-ant_value_t js_mkpromise(ant_t *js) { return mkpromise(js); }
-void js_resolve_promise(ant_t *js, ant_value_t promise, ant_value_t value) { resolve_promise(js, promise, value); }
-void js_reject_promise(ant_t *js, ant_value_t promise, ant_value_t value) { reject_promise(js, promise, value); }
-
static void check_unhandled_rejections_list(ant_t *js, ant_object_t *list) {
for (ant_object_t *obj = list; obj; obj = obj->next) {
ant_promise_state_t *pd = obj->promise_state;
if (!pd) continue;
if (pd->state != 2) continue;
if (pd->has_rejection_handler || pd->unhandled_reported) continue;
if (vtype(pd->trigger_parent) == T_PROMISE) {
ant_promise_state_t *parent = get_promise_data(js, pd->trigger_parent, false);
if (parent && parent->has_rejection_handler) continue;
}
if (js->fatal_error) {
js->thrown_exists = true;
js->thrown_value = pd->value;
print_uncaught_throw(js);
js_destroy(js); exit(1);
}
ant_value_t promise_val = mkval(T_PROMISE, (uintptr_t)obj);
if (!js_fire_unhandled_rejection(js, promise_val, pd->value))
print_unhandled_promise_rejection(js, pd->value);
pd->unhandled_reported = true;
}}
void js_check_unhandled_rejections(ant_t *js) {
check_unhandled_rejections_list(js, js->objects);
check_unhandled_rejections_list(js, js->objects_old);
check_unhandled_rejections_list(js, js->permanent_objects);
}
void js_set_getter(ant_value_t obj, js_getter_fn getter) {
if (!is_object_type(obj)) return;
if (vtype(obj) != T_OBJ) obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr) return;
ptr->is_exotic = 1;
ant_exotic_ops_t *ops = obj_ensure_exotic_ops(ptr);
if (!ops) return;
ops->getter = getter;
}
void js_set_setter(ant_value_t obj, js_setter_fn setter) {
if (!is_object_type(obj)) return;
if (vtype(obj) != T_OBJ) obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr) return;
ptr->is_exotic = 1;
ant_exotic_ops_t *ops = obj_ensure_exotic_ops(ptr);
if (!ops) return;
ops->setter = setter;
}
void js_set_deleter(ant_value_t obj, js_deleter_fn deleter) {
if (!is_object_type(obj)) return;
if (vtype(obj) != T_OBJ) obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr) return;
ptr->is_exotic = 1;
ant_exotic_ops_t *ops = obj_ensure_exotic_ops(ptr);
if (!ops) return;
ops->deleter = deleter;
}
void js_set_finalizer(ant_value_t obj, js_finalizer_fn fn) {
if (!is_object_type(obj)) return;
if (vtype(obj) != T_OBJ) obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr) return;
ptr->finalizer = fn;
}
void js_set_keys(ant_value_t obj, js_keys_fn keys) {
if (!is_object_type(obj)) return;
if (vtype(obj) != T_OBJ) obj = js_as_obj(obj);
ant_object_t *ptr = js_obj_ptr(obj);
if (!ptr) return;
ptr->is_exotic = 1;
ptr->exotic_keys = keys;
}
diff --git a/src/main.c b/src/main.c
index a008183..cf75da6 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,706 +1,708 @@
#include <compat.h> // IWYU pragma: keep
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <argtable3.h>
#include <crprintf.h>
#include "ant.h"
#include "gc.h"
#include "repl.h"
#include "debug.h"
#include "utils.h"
#include "watch.h"
#include "reactor.h"
#include "runtime.h"
#include "snapshot.h"
#include "esm/loader.h"
#include "esm/library.h"
#include "esm/remote.h"
#include "internal.h"
#include "silver/vm.h"
#include "messages.h"
#include "cli/pkg.h"
#include "cli/misc.h"
#include "cli/version.h"
#include "modules/builtin.h"
#include "modules/buffer.h"
#include "modules/atomics.h"
#include "modules/os.h"
#include "modules/io.h"
#include "modules/fs.h"
#include "modules/crypto.h"
#include "modules/server.h"
#include "modules/timer.h"
#include "modules/json.h"
#include "modules/fetch.h"
+#include "modules/request.h"
#include "modules/shell.h"
#include "modules/process.h"
#include "modules/tty.h"
#include "modules/path.h"
#include "modules/ffi.h"
#include "modules/events.h"
#include "modules/lmdb.h"
#include "modules/performance.h"
#include "modules/uri.h"
#include "modules/url.h"
#include "modules/reflect.h"
#include "modules/symbol.h"
#include "modules/date.h"
#include "modules/math.h"
#include "modules/bigint.h"
#include "modules/regex.h"
#include "modules/textcodec.h"
#include "modules/sessionstorage.h"
#include "modules/localstorage.h"
#include "modules/navigator.h"
#include "modules/child_process.h"
#include "modules/readline.h"
#include "modules/observable.h"
#include "modules/collections.h"
#include "modules/iterator.h"
#include "modules/module.h"
#include "modules/util.h"
#include "modules/async_hooks.h"
#include "modules/net.h"
#include "modules/dns.h"
#include "modules/assert.h"
#include "modules/domexception.h"
#include "modules/abort.h"
#include "modules/globals.h"
#include "modules/structured-clone.h"
#include "modules/v8.h"
#include "modules/worker_threads.h"
#include "modules/headers.h"
#include "modules/blob.h"
#include "modules/formdata.h"
#include "streams/queuing.h"
#include "streams/readable.h"
#include "streams/writable.h"
#include "streams/transform.h"
#include "streams/codec.h"
#include "streams/compression.h"
int js_result = EXIT_SUCCESS;
typedef int (*cmd_fn)(int argc, char **argv);
typedef struct {
const char *name;
const char *alias;
const char *desc;
cmd_fn fn;
} subcommand_t;
static const subcommand_t subcommands[] = {
{"init", NULL, "Create a new package.json", pkg_cmd_init},
{"install", "i", "Install dependencies from lockfile", pkg_cmd_install},
{"update", "up", "Re-resolve dependencies and refresh lockfile", pkg_cmd_update},
{"add", "a", "Add a package to dependencies", pkg_cmd_add},
{"remove", "rm", "Remove a package from dependencies", pkg_cmd_remove},
{"trust", NULL, "Run lifecycle scripts for packages", pkg_cmd_trust},
{"run", NULL, "Run a script from package.json", pkg_cmd_run},
{"exec", "x", "Run a command from node_modules/.bin", pkg_cmd_exec},
{"why", "explain", "Show why a package is installed", pkg_cmd_why},
{"info", NULL, "Show package information from registry", pkg_cmd_info},
{"ls", "list", "List installed packages", pkg_cmd_ls},
{"cache", NULL, "Manage the package cache", pkg_cmd_cache},
{"create", NULL, "Scaffold a project from a template", pkg_cmd_create},
{NULL, NULL, NULL, NULL}
};
static void ant_debug_apply(const char *key, const char *val) {
if (strcmp(key, "gc") == 0) {
if (strcmp(val, "disable") == 0) gc_disabled = true;
}
if (strcmp(key, "dump/crprintf") == 0) {
if (strcmp(val, "bytecode") == 0 || strcmp(val, "all") == 0) crprintf_set_debug(true);
if (strcmp(val, "hex") == 0 || strcmp(val, "all") == 0) crprintf_set_debug_hex(true);
}
else if (strcmp(key, "dump/vm") == 0) {
if (strcmp(val, "bytecode") == 0 || strcmp(val, "all") == 0) sv_debug_enable(SV_DEBUG_DUMP_BYTECODE);
if (strcmp(val, "jit") == 0 || strcmp(val, "all") == 0) sv_debug_enable(SV_DEBUG_DUMP_JIT);
if (strcmp(val, "op-warn") == 0 || strcmp(val, "all") == 0) sv_debug_enable(SV_DEBUG_JIT_WARN);
}
}
static inline void setup_console_colors(void) {
crprintf_var("version", ANT_VERSION);
crprintf_var("fatal", "<bold+red>FATAL</bold>");
crprintf_var("error", "<red>Error</red>");
crprintf_var("warn", "<yellow>Warning</yellow>");
}
static void parse_ant_debug_flags(void) {
const char *env = getenv("ANT_DEBUG");
if (!env || !*env) return;
char *buf = strdup(env);
if (!buf) return;
char *sp = NULL, *vp = NULL;
for (char *e = strtok_r(buf, " ", &sp); e; e = strtok_r(NULL, " ", &sp)) {
char *sep = strchr(e, ':');
if (!sep) { crfprintf(stderr, msg.unknown_flag_warn, e); continue; }
*sep++ = '\0';
for (char *v = strtok_r(sep, ",", &vp); v; v = strtok_r(NULL, ",", &vp))
ant_debug_apply(e, v);
}
free(buf);
}
static const subcommand_t *find_subcommand(const char *name) {
for (const subcommand_t *cmd = subcommands; cmd->name; cmd++) {
if (strcmp(name, cmd->name) == 0) return cmd;
if (cmd->alias && strcmp(name, cmd->alias) == 0) return cmd;
}
return NULL;
}
static void print_subcommands(void) {
crprintf("<bold>Commands:</>\n");
for (const subcommand_t *cmd = subcommands; cmd->name; cmd++) {
crprintf(" <pad=18>%s</pad> %s\n", cmd->name, cmd->desc);
}
crprintf(msg.ant_command_extra);
printf("\n");
}
static void print_commands(void **argtable) {
crprintf(msg.ant_help_header);
print_subcommands();
crprintf(msg.ant_help_flags);
print_flags_help(stdout, argtable);
print_flag(stdout, (flag_help_t){ .l = "verbose", .g = "enable verbose output" });
print_flag(stdout, (flag_help_t){ .l = "no-color", .g = "disable colored output" });
}
typedef struct {
int argc;
char **argv;
} argv_split_t;
static bool is_valued_flag(const char *arg) {
return
strcmp(arg, "-e") == 0 ||
strcmp(arg, "--eval") == 0 ||
strcmp(arg, "--localstorage-file") == 0;
}
static int find_argv_token_index(int argc, char **argv, const char *token) {
if (!token) return -1;
for (int i = 1; i < argc; i++) if (argv[i] == token) return i;
return -1;
}
static argv_split_t split_script_args(int *argc, char **argv) {
for (int i = 1; i < *argc; i++) {
if (strcmp(argv[i], "--") == 0) {
argv_split_t tail = { *argc - i - 1, argv + i + 1 };
*argc = i;
return tail;
}
if (argv[i][0] == '-') {
if (is_valued_flag(argv[i]) && i + 1 < *argc) i++;
continue;
}
argv_split_t tail = { *argc - i - 1, argv + i + 1 };
*argc = i + 1;
return tail;
}
return (argv_split_t){ 0, NULL };
}
static argv_split_t build_process_argv(int argc, char **argv, const char *module, argv_split_t script) {
if (!module || script.argc == 0) return (argv_split_t){ argc, argv };
int total = 2 + script.argc;
char **out = try_oom(sizeof(char*) * (total + 1));
out[0] = argv[0]; out[1] = (char *)module;
for (int i = 0; i < script.argc; i++) out[2 + i] = script.argv[i];
out[total] = NULL;
return (argv_split_t){ total, out };
}
static char *read_stdin(size_t *len) {
size_t cap = 4096;
*len = 0;
char *buf = malloc(cap);
if (!buf) return NULL;
size_t n;
while ((n = fread(buf + *len, 1, cap - *len, stdin)) > 0) {
*len += n;
if (*len == cap) {
cap *= 2; char *next = realloc(buf, cap);
if (!next) { free(buf); return NULL; }
buf = next;
}
}
buf[*len] = '\0';
return buf;
}
static char *read_file(const char *filename, size_t *len) {
FILE *fp = fopen(filename, "rb");
if (!fp) return NULL;
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buffer = malloc(size + 1);
if (!buffer) {
fclose(fp);
return NULL;
}
*len = fread(buffer, 1, size, fp);
fclose(fp);
buffer[*len] = '\0';
return buffer;
}
static void eval_code(ant_t *js, const char *script, size_t len, const char *tag, bool should_print) {
js_set_filename(js, tag);
js_setup_import_meta(js, tag);
js_set(js, js_glob(js), "__dirname", js_mkstr(js, ".", 1));
js_set(js, js_glob(js), "__filename", js_mkstr(js, tag, strlen(tag)));
ant_value_t result = js_eval_bytecode_eval(js, script, len);
js_run_event_loop(js);
if (print_uncaught_throw(js)) {
js_result = EXIT_FAILURE;
return;
}
char cbuf_stack[512]; js_cstr_t cstr = js_to_cstr(
js, result, cbuf_stack, sizeof(cbuf_stack)
);
if (vtype(result) == T_ERR) {
fprintf(stderr, "%s\n", cstr.ptr);
js_result = EXIT_FAILURE;
} else if (should_print) {
if (vtype(result) == T_STR) printf("%s\n", cstr.ptr ? cstr.ptr : "");
else if (cstr.ptr && strcmp(cstr.ptr, "undefined") != 0) {
print_value_colored(cstr.ptr, stdout); printf("\n");
}
}
if (cstr.needs_free) free((void *)cstr.ptr);
}
static int execute_module(ant_t *js, const char *filename) {
char *buffer = NULL;
size_t len = 0;
char *use_path_owned = NULL;
const char *use_path = filename;
if (esm_is_url(filename)) {
char *error = NULL;
buffer = esm_fetch_url(filename, &len, &error);
if (!buffer) {
crfprintf(stderr, msg.failed_to_fetch, filename, error ? error : "unknown error");
free(error); return EXIT_FAILURE;
}
js_set(js, js_glob(js), "__dirname", js_mkundef());
} else {
buffer = read_file(filename, &len);
if (!buffer) {
crfprintf(stderr, msg.module_not_found, filename);
return EXIT_FAILURE;
}
char *file_path = strdup(filename);
char *dir = dirname(file_path);
js_set(js, js_glob(js), "__dirname", js_mkstr(js, dir, strlen(dir)));
free(file_path);
use_path_owned = realpath(filename, NULL);
if (use_path_owned) use_path = use_path_owned;
}
size_t js_len = len;
const char *strip_detail = NULL;
int strip_result = strip_typescript_inplace(
&buffer, len, filename, &js_len, &strip_detail
);
if (strip_result < 0) {
crfprintf(stderr, msg.type_strip_failed, strip_result, strip_detail);
free(buffer); free(use_path_owned);
return EXIT_FAILURE;
}
char *js_code = buffer;
js_set(js, js_glob(js),
"__filename",
js_mkstr(js, filename, strlen(filename))
);
js_set_filename(js, use_path);
js_setup_import_meta(js, use_path);
ant_value_t result = js_esm_eval_module_source(
js, use_path, js_code,
js_len, mkobj(js, 0)
);
free(js_code);
if (print_uncaught_throw(js)) return EXIT_FAILURE;
if (vtype(result) == T_ERR) fprintf(stderr, "%s\n", js_str(js, result));
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
setup_console_colors();
parse_ant_debug_flags();
int filtered_argc = 0; int original_argc = argc;
char **original_argv = argv;
const char *binary_name = strrchr(argv[0], '/');
binary_name = binary_name ? binary_name + 1 : argv[0];
if (strcmp(binary_name, "antx") == 0) {
char **exec_argv = try_oom(sizeof(char*) * (argc + 2));
exec_argv[0] = argv[0]; exec_argv[1] = "x";
for (int i = 1; i < argc; i++) exec_argv[i + 1] = argv[i];
exec_argv[argc + 1] = NULL;
int exitcode = pkg_cmd_exec(argc, exec_argv + 1);
free(exec_argv); return exitcode;
}
char **filtered_argv = try_oom(sizeof(char*) * argc);
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "--verbose") == 0) pkg_verbose = true;
else if (strcmp(argv[i], "--no-color") == 0) { crprintf_set_color(false); io_no_color = true; }
else if (strncmp(argv[i], "--stack-size=", 13) == 0) sv_user_stack_size_kb = atoi(argv[i] + 13);
else filtered_argv[filtered_argc++] = argv[i];
}
argc = filtered_argc;
argv = filtered_argv;
argv_split_t script_tail = split_script_args(&argc, argv);
argv_split_t proc_argv = { 0, NULL };
#define ARG_ITEMS(X) \
X(struct arg_str *, eval, arg_str0("e", "eval", "<script>", "evaluate script")) \
X(struct arg_lit *, print, arg_lit0("p", "print", "evaluate script and print result")) \
X(struct arg_lit *, watch, arg_lit0("w", "watch", "restart process when entry file changes")) \
X(struct arg_lit *, no_clear_screen, arg_lit0(NULL, "no-clear-screen", "keep output when restarting in watch mode")) \
X(struct arg_file *, localstorage_file, arg_file0(NULL, "localstorage-file", "<path>", "file path for localStorage persistence")) \
X(struct arg_file *, file, arg_filen(NULL, NULL, NULL, 0, argc, NULL)) \
X(struct arg_lit *, version, arg_lit0("v", "version", "display version information and exit")) \
X(struct arg_lit *, version_raw, arg_lit0(NULL, "version-raw", "raw version number for scripts")) \
X(struct arg_lit *, help, arg_lit0("h", "help", "display this help and exit")) \
X(struct arg_end *, end, arg_end(20))
#define DECL(t, n, init) t n = init;
ARG_ITEMS(DECL)
#undef DECL
#define REF(t, n, init) n,
void *argtable[] = { ARG_ITEMS(REF) };
int nerrors = arg_parse(argc, argv, argtable);
#undef REF
#define CLEANUP_ARGS_AND_ARGV() ({ \
if (proc_argv.argv != argv) free(proc_argv.argv); \
arg_freetable(argtable, ARGTABLE_COUNT); \
free(filtered_argv); \
})
if (help->count > 0) {
print_commands(argtable);
CLEANUP_ARGS_AND_ARGV();
return EXIT_SUCCESS;
}
if (version_raw->count > 0) {
fputs(ANT_VERSION "\n", stdout);
CLEANUP_ARGS_AND_ARGV();
return EXIT_SUCCESS;
}
if (version->count > 0) {
int res = ant_version(argtable);
free(filtered_argv); return res;
}
if (nerrors > 0) {
print_errors(stdout, end);
print_commands(argtable);
CLEANUP_ARGS_AND_ARGV();
return EXIT_FAILURE;
}
if (no_clear_screen->count > 0 && watch->count == 0) {
crfprintf(stderr, msg.misuse_clear_screen);
CLEANUP_ARGS_AND_ARGV();
return EXIT_FAILURE;
}
if (eval->count == 0 && file->count > 0 && file->filename[0] != NULL) {
const char *positional = file->filename[0];
int first_pos_idx = find_argv_token_index(argc, argv, positional);
if (first_pos_idx <= 0 || first_pos_idx >= argc) {
crfprintf(stderr, msg.argument_fatal);
CLEANUP_ARGS_AND_ARGV();
return EXIT_FAILURE;
}
const subcommand_t *cmd = find_subcommand(positional);
if (cmd) {
if (watch->count > 0) {
crfprintf(stderr, msg.watch_subcommand_error);
CLEANUP_ARGS_AND_ARGV();
return EXIT_FAILURE;
}
int cmd_argc = argc - first_pos_idx;
char **cmd_argv = argv + first_pos_idx;
char **cmd_argv_full = NULL;
if (script_tail.argc > 0) {
cmd_argv_full = try_oom(sizeof(*cmd_argv_full) * (size_t)(cmd_argc + script_tail.argc + 1));
memcpy(cmd_argv_full, cmd_argv, sizeof(*cmd_argv_full) * (size_t)cmd_argc);
memcpy(cmd_argv_full + cmd_argc, script_tail.argv, sizeof(*cmd_argv_full) * (size_t)script_tail.argc);
cmd_argc += script_tail.argc;
cmd_argv_full[cmd_argc] = NULL;
cmd_argv = cmd_argv_full;
}
int exitcode = cmd->fn(cmd_argc, cmd_argv);
free(cmd_argv_full);
CLEANUP_ARGS_AND_ARGV();
return exitcode;
}
if (pkg_script_exists("package.json", positional)) {
if (watch->count > 0) {
crfprintf(stderr, msg.watch_subcommand_error);
CLEANUP_ARGS_AND_ARGV();
return EXIT_FAILURE;
}
int run_argc = argc - first_pos_idx + 1 + script_tail.argc;
char **run_argv = try_oom(sizeof(*run_argv) * (size_t)(run_argc + 1));
run_argv[0] = argv[0];
int copied = argc - first_pos_idx;
memcpy(run_argv + 1, argv + first_pos_idx, sizeof(*run_argv) * (size_t)copied);
if (script_tail.argc > 0) {
memcpy(run_argv + 1 + copied, script_tail.argv, sizeof(*run_argv) * (size_t)script_tail.argc);
}
run_argv[run_argc] = NULL;
int exitcode = pkg_cmd_run(run_argc, run_argv);
free(run_argv);
CLEANUP_ARGS_AND_ARGV();
return exitcode;
}
}
bool has_stdin = !isatty(STDIN_FILENO);
bool repl_mode = (file->count == 0 && eval->count == 0 && !has_stdin);
bool stdin_mode = (has_stdin && file->count == 0);
if (watch->count > 0 && (eval->count > 0 || repl_mode || stdin_mode)) {
crfprintf(stderr, msg.watch_module_error);
CLEANUP_ARGS_AND_ARGV();
return EXIT_FAILURE;
}
const char *module_file = (repl_mode || file->count == 0)
? NULL
: file->filename[0];
if (watch->count > 0) {
char *resolved_file = NULL;
resolved_file = resolve_js_file(module_file);
if (resolved_file) module_file = resolved_file;
if (!module_file || esm_is_url(module_file)) {
crfprintf(stderr, msg.watch_entrypoint_error);
if (resolved_file) free(resolved_file);
CLEANUP_ARGS_AND_ARGV();
return EXIT_FAILURE;
}
int res = ant_watch_run(original_argc, original_argv, module_file, no_clear_screen->count > 0);
if (resolved_file) free(resolved_file);
CLEANUP_ARGS_AND_ARGV();
return res;
}
ant_t *js;
volatile char stack_base;
if (!(js = js_create_dynamic())) {
crfprintf(stderr, msg.ant_allocation_fatal);
CLEANUP_ARGS_AND_ARGV();
return EXIT_FAILURE;
}
js_setstackbase(js, (void *)&stack_base);
js_setstacklimit(js, os_thread_stack_size() * 3 / 4);
proc_argv = build_process_argv(argc, argv, module_file, script_tail);
ant_runtime_init(js, proc_argv.argc, proc_argv.argv, localstorage_file);
init_symbol_module();
init_iterator_module();
init_timer_module();
init_domexception_module();
init_globals_module();
init_builtin_module();
init_buffer_module();
init_structured_clone_module();
init_abort_module();
init_headers_module();
init_blob_module();
init_formdata_module();
init_math_module();
init_bigint_module();
init_date_module();
init_regex_module();
init_collections_module();
init_queuing_strategies_module();
init_readable_stream_module();
init_writable_stream_module();
init_transform_stream_module();
init_codec_stream_module();
init_compression_stream_module();
init_fs_module();
init_atomics_module();
init_crypto_module();
+ init_request_module();
init_fetch_module();
init_console_module();
init_json_module();
init_server_module();
init_process_module();
init_tty_module();
init_events_module();
init_performance_module();
init_uri_module();
init_url_module();
init_reflect_module();
init_textcodec_module();
init_sessionstorage_module();
init_localstorage_module();
init_navigator_module();
init_observable_module();
ant_register_library(shell_library, "ant:shell", NULL);
ant_register_library(ffi_library, "ant:ffi", NULL);
ant_register_library(lmdb_library, "ant:lmdb", NULL);
ant_standard_library("util", util_library);
ant_standard_library("net", net_library);
ant_standard_library("dns", dns_library);
ant_standard_library("assert", assert_library);
ant_standard_library("module", module_library);
ant_standard_library("buffer", buffer_library);
ant_standard_library("path", path_library);
ant_standard_library("fs", fs_library);
ant_standard_library("fs/promises", fs_promises_library);
ant_standard_library("os", os_library);
ant_standard_library("url", url_library);
ant_standard_library("perf_hooks", perf_hooks_library);
ant_standard_library("process", process_library);
ant_standard_library("crypto", crypto_library);
ant_standard_library("events", events_library);
ant_standard_library("tty", tty_library);
ant_standard_library("readline", readline_library);
ant_standard_library("readline/promises", readline_promises_library);
ant_standard_library("child_process", child_process_library);
ant_standard_library("worker_threads", worker_threads_library);
ant_standard_library("async_hooks", async_hooks_library);
ant_standard_library("v8", v8_library);
ant_value_t snapshot_result = ant_load_snapshot(js);
if (vtype(snapshot_result) == T_ERR) {
crfprintf(stderr, msg.snapshot_warn, js_str(js, snapshot_result));
}
if (eval->count > 0) {
const char *script = eval->sval[0];
eval_code(js, script, strlen(script), "[eval]", print->count > 0);
}
else if (repl_mode) {
ant_repl_run();
}
else if (stdin_mode) {
size_t len = 0; char *buf = read_stdin(&len);
if (!buf) {
crfprintf(stderr, msg.ant_allocation_fatal);
js_result = EXIT_FAILURE; goto cleanup;
}
eval_code(js, buf, len, "[stdin]", print->count > 0); free(buf);
}
else {
for (int fi = 0; fi < file->count; fi++) {
const char *fl = file->filename[fi];
char *resolved_file = resolve_js_file(fl);
if (!resolved_file) {
crfprintf(stderr, msg.module_not_found, fl);
js_result = EXIT_FAILURE; break;
}
fl = resolved_file;
js_result = execute_module(js, fl);
js_run_event_loop(js);
free(resolved_file);
if (js_result != EXIT_SUCCESS) break;
}}
cleanup: {
js_destroy(js);
CLEANUP_ARGS_AND_ARGV();
}
#undef CLEANUP_ARGS_AND_ARGV
return js_result;
}
diff --git a/src/modules/abort.c b/src/modules/abort.c
index f5f7ea2..60a01a2 100644
--- a/src/modules/abort.c
+++ b/src/modules/abort.c
@@ -1,422 +1,437 @@
#include <stdlib.h>
#include <string.h>
#include <utarray.h>
#include <uv.h>
#include "ant.h"
#include "errors.h"
#include "internal.h"
#include "runtime.h"
#include "descriptors.h"
#include "gc/modules.h"
#include "modules/symbol.h"
#include "modules/timer.h"
#include "modules/abort.h"
#include "modules/domexception.h"
#include "silver/engine.h"
typedef struct {
ant_value_t callback;
bool once;
} abort_listener_t;
typedef struct {
bool aborted;
bool fired;
ant_value_t reason;
UT_array *listeners;
UT_array *followers;
} abort_signal_data_t;
typedef struct abort_timeout_entry {
uv_timer_t handle;
ant_t *js;
ant_value_t signal;
int closed;
struct abort_timeout_entry *next;
} abort_timeout_entry_t;
static const UT_icd abort_listener_icd = { sizeof(abort_listener_t), NULL, NULL, NULL };
static const UT_icd abort_value_icd = { sizeof(ant_value_t), NULL, NULL, NULL };
static abort_timeout_entry_t *timeout_entries = NULL;
static ant_value_t g_signal_proto = 0;
static bool g_initialized = false;
static abort_signal_data_t *get_signal_data(ant_value_t obj) {
ant_value_t slot = js_get_slot(obj, SLOT_DATA);
if (vtype(slot) != T_NUM) return NULL;
return (abort_signal_data_t *)(uintptr_t)js_getnum(slot);
}
static ant_value_t make_abort_error(ant_t *js) {
return make_dom_exception(js, "signal is aborted without reason", "AbortError");
}
static ant_value_t make_timeout_error(ant_t *js) {
return make_dom_exception(js, "signal timed out", "TimeoutError");
}
static void signal_mark_aborted(ant_t *js, ant_value_t signal_obj, ant_value_t reason) {
abort_signal_data_t *data = get_signal_data(signal_obj);
if (!data || data->aborted) return;
data->aborted = true;
data->fired = true;
data->reason = reason;
js_set(js, signal_obj, "aborted", js_true);
js_set(js, signal_obj, "reason", reason);
}
void signal_do_abort(ant_t *js, ant_value_t signal_obj, ant_value_t reason) {
abort_signal_data_t *data = get_signal_data(signal_obj);
if (!data || data->aborted) return;
UT_array *queue; utarray_new(queue, &abort_value_icd);
UT_array *to_fire; utarray_new(to_fire, &abort_value_icd);
utarray_push_back(queue, &signal_obj);
for (unsigned int qi = 0; qi < utarray_len(queue); qi++) {
ant_value_t *cur = (ant_value_t *)utarray_eltptr(queue, qi);
abort_signal_data_t *d = get_signal_data(*cur);
if (!d || d->aborted) continue;
d->aborted = true;
d->fired = true;
d->reason = reason;
js_set(js, *cur, "aborted", js_true);
js_set(js, *cur, "reason", reason);
utarray_push_back(to_fire, cur);
unsigned int nf = utarray_len(d->followers);
for (unsigned int i = 0; i < nf; i++) {
ant_value_t *sig = (ant_value_t *)utarray_eltptr(d->followers, i);
utarray_push_back(queue, sig);
}}
utarray_free(queue);
for (unsigned int qi = 0; qi < utarray_len(to_fire); qi++) {
ant_value_t *cur = (ant_value_t *)utarray_eltptr(to_fire, qi);
abort_signal_data_t *d = get_signal_data(*cur);
if (!d) continue;
ant_value_t event_obj = js_mkobj(js);
js_set(js, event_obj, "type", js_mkstr(js, "abort", 5));
js_set(js, event_obj, "target", *cur);
ant_value_t call_args[1] = { event_obj };
ant_value_t onabort = js_get(js, *cur, "onabort");
if (is_callable(onabort)) {
sv_vm_call(js->vm, js, onabort, *cur, call_args, 1, NULL, false);
process_microtasks(js);
}
unsigned int n = utarray_len(d->listeners);
for (unsigned int i = 0; i < n;) {
abort_listener_t *entry = (abort_listener_t *)utarray_eltptr(d->listeners, i);
ant_value_t cb = entry->callback;
bool once = entry->once;
if (once) { utarray_erase(d->listeners, i, 1); n--; } else i++;
if (!is_callable(cb)) continue;
sv_vm_call(js->vm, js, cb, *cur, call_args, 1, NULL, false);
process_microtasks(js);
}}
utarray_free(to_fire);
}
static ant_value_t make_new_signal(ant_t *js) {
abort_signal_data_t *data = ant_calloc(sizeof(abort_signal_data_t));
if (!data) return js_mkerr(js, "AbortSignal: out of memory");
data->aborted = false;
data->fired = false;
data->reason = js_mkundef();
utarray_new(data->listeners, &abort_listener_icd);
utarray_new(data->followers, &abort_value_icd);
ant_value_t obj = js_mkobj(js);
js_set_slot(obj, SLOT_DATA, ANT_PTR(data));
if (g_initialized) js_set_slot_wb(js, obj, SLOT_PROTO, g_signal_proto);
js_set(js, obj, "aborted", js_false);
js_set(js, obj, "reason", js_mkundef());
js_set(js, obj, "onabort", js_mkundef());
js_set_sym(js, obj, get_toStringTag_sym(), js_mkstr(js, "AbortSignal", 11));
return obj;
}
// signal.addEventListener(type, listener, options?)
static ant_value_t abort_signal_add_event_listener(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkundef();
const char *type = js_getstr(js, args[0], NULL);
if (!type || strcmp(type, "abort") != 0) return js_mkundef();
if (!is_callable(args[1])) return js_mkundef();
abort_signal_data_t *data = get_signal_data(js_getthis(js));
if (!data) return js_mkundef();
if (data->aborted) return js_mkundef();
bool once = false;
if (nargs >= 3 && vtype(args[2]) == T_OBJ) {
ant_value_t once_val = js_get(js, args[2], "once");
if (vtype(once_val) != T_UNDEF) once = js_truthy(js, once_val);
} else if (nargs >= 3 && vtype(args[2]) == T_BOOL) once = js_truthy(js, args[2]);
unsigned int n = utarray_len(data->listeners);
for (unsigned int i = 0; i < n; i++) {
abort_listener_t *e = (abort_listener_t *)utarray_eltptr(data->listeners, i);
if (e->callback == args[1] && e->once == once) return js_mkundef();
}
abort_listener_t entry = { args[1], once };
utarray_push_back(data->listeners, &entry);
return js_mkundef();
}
// signal.removeEventListener(type, listener)
static ant_value_t abort_signal_remove_event_listener(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkundef();
const char *type = js_getstr(js, args[0], NULL);
if (!type || strcmp(type, "abort") != 0) return js_mkundef();
abort_signal_data_t *data = get_signal_data(js_getthis(js));
if (!data) return js_mkundef();
unsigned int n = utarray_len(data->listeners);
for (unsigned int i = 0; i < n; i++) {
abort_listener_t *e = (abort_listener_t *)utarray_eltptr(data->listeners, i);
if (e->callback != args[1]) continue;
utarray_erase(data->listeners, i, 1);
return js_mkundef();
}
return js_mkundef();
}
// signal.dispatchEvent(event)
static ant_value_t abort_signal_dispatch_event(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_false;
const char *type = NULL;
if (vtype(args[0]) == T_OBJ) type = js_getstr(js, js_get(js, args[0], "type"), NULL);
else type = js_getstr(js, args[0], NULL);
if (!type || strcmp(type, "abort") != 0) return js_true;
abort_signal_data_t *data = get_signal_data(js_getthis(js));
if (!data || data->fired) return js_true;
signal_do_abort(js, js_getthis(js), data->reason);
return js_true;
}
// signal.throwIfAborted()
static ant_value_t abort_signal_throw_if_aborted(ant_t *js, ant_value_t *args, int nargs) {
abort_signal_data_t *data = get_signal_data(js_getthis(js));
if (!data || !data->aborted) return js_mkundef();
return js_throw(js, data->reason);
}
// AbortSignal.abort(reason?)
static ant_value_t abort_signal_static_abort(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t reason = (nargs >= 1 && vtype(args[0]) != T_UNDEF)
? args[0]
: make_abort_error(js);
ant_value_t signal = make_new_signal(js);
if (is_err(signal)) return signal;
signal_mark_aborted(js, signal, reason);
return signal;
}
// AbortSignal.any(signals)
static ant_value_t abort_signal_static_any(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1 || vtype(args[0]) != T_ARR)
return js_mkerr(js, "AbortSignal.any: argument must be an array of AbortSignal objects");
ant_value_t composite = make_new_signal(js);
if (is_err(composite)) return composite;
ant_offset_t len = js_arr_len(js, args[0]);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t sig = js_arr_get(js, args[0], i);
abort_signal_data_t *d = get_signal_data(sig);
if (d && d->aborted) {
signal_mark_aborted(js, composite, d->reason);
return composite;
}}
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t sig = js_arr_get(js, args[0], i);
abort_signal_data_t *d = get_signal_data(sig);
if (!d) continue;
utarray_push_back(d->followers, &composite);
}
return composite;
}
+ant_value_t abort_signal_create_dependent(ant_t *js, ant_value_t source) {
+ ant_value_t composite = make_new_signal(js);
+
+ if (is_err(composite)) return composite;
+ if (vtype(source) != T_OBJ && vtype(source) != T_ARR) return composite;
+
+ abort_signal_data_t *d = get_signal_data(source);
+ if (!d) return composite;
+
+ if (d->aborted) signal_mark_aborted(js, composite, d->reason);
+ else utarray_push_back(d->followers, &composite);
+
+ return composite;
+}
+
static void abort_timeout_close_cb(uv_handle_t *h) {
abort_timeout_entry_t *entry = (abort_timeout_entry_t *)h->data;
if (entry) entry->closed = 1;
}
static void abort_timeout_fire_cb(uv_timer_t *handle) {
abort_timeout_entry_t *entry = (abort_timeout_entry_t *)handle->data;
if (!entry || entry->closed) return;
ant_t *js = entry->js;
signal_do_abort(js, entry->signal, make_timeout_error(js));
process_microtasks(js);
if (!uv_is_closing((uv_handle_t *)handle))
uv_close((uv_handle_t *)handle, abort_timeout_close_cb);
}
// AbortSignal.timeout(milliseconds)
static ant_value_t abort_signal_static_timeout(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "AbortSignal.timeout: milliseconds argument required");
double ms = js_getnum(args[0]);
if (ms < 0) return js_mkerr(js, "AbortSignal.timeout: milliseconds must be non-negative");
ant_value_t signal = make_new_signal(js);
if (is_err(signal)) return signal;
abort_timeout_entry_t *entry = ant_calloc(sizeof(abort_timeout_entry_t));
if (!entry) return js_mkerr(js, "AbortSignal.timeout: out of memory");
entry->js = js;
entry->signal = signal;
entry->closed = 0;
entry->next = timeout_entries;
timeout_entries = entry;
uv_timer_init(uv_default_loop(), &entry->handle);
entry->handle.data = entry;
uv_timer_start(&entry->handle, abort_timeout_fire_cb, (uint64_t)(ms > 0 ? ms : 0), 0);
return signal;
}
// new AbortController()
static ant_value_t abort_controller_ctor(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_obj = js_getthis(js);
ant_value_t signal = make_new_signal(js);
if (is_err(signal)) return signal;
js_mkprop_fast(js, this_obj, "signal", 6, signal);
js_set_descriptor(js, this_obj, "signal", 6, 0);
js_set_sym(js, this_obj, get_toStringTag_sym(), js_mkstr(js, "AbortController", 15));
return js_mkundef();
}
// controller.abort(reason?)
static ant_value_t abort_controller_abort(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t signal = js_get(js, js_getthis(js), "signal");
abort_signal_data_t *data = get_signal_data(signal);
if (!data || data->aborted) return js_mkundef();
ant_value_t reason = (nargs >= 1 && vtype(args[0]) != T_UNDEF)
? args[0]
: make_abort_error(js);
signal_do_abort(js, signal, reason);
return js_mkundef();
}
void init_abort_module(void) {
ant_t *js = rt->js;
ant_value_t global = js_glob(js);
ant_value_t signal_proto = js_mkobj(js);
g_signal_proto = signal_proto;
g_initialized = true;
js_set(js, signal_proto, "addEventListener", js_mkfun(abort_signal_add_event_listener));
js_set(js, signal_proto, "removeEventListener", js_mkfun(abort_signal_remove_event_listener));
js_set(js, signal_proto, "dispatchEvent", js_mkfun(abort_signal_dispatch_event));
js_set(js, signal_proto, "throwIfAborted", js_mkfun(abort_signal_throw_if_aborted));
js_set_sym(js, signal_proto, get_toStringTag_sym(), js_mkstr(js, "AbortSignal", 11));
ant_value_t signal_ctor = js_mkobj(js);
js_mkprop_fast(js, signal_ctor, "prototype", 9, signal_proto);
js_mkprop_fast(js, signal_ctor, "name", 4, ANT_STRING("AbortSignal"));
js_set_descriptor(js, signal_ctor, "name", 4, 0);
ant_value_t signal_fn = js_obj_to_func_ex(signal_ctor, SV_CALL_IS_DEFAULT_CTOR);
js_set(js, signal_fn, "abort", js_mkfun(abort_signal_static_abort));
js_set(js, signal_fn, "timeout", js_mkfun(abort_signal_static_timeout));
js_set(js, signal_fn, "any", js_mkfun(abort_signal_static_any));
js_set(js, signal_proto, "constructor", signal_fn);
js_set_descriptor(js, signal_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
ant_value_t ctrl_proto = js_mkobj(js);
js_set(js, ctrl_proto, "abort", js_mkfun(abort_controller_abort));
js_set_sym(js, ctrl_proto, get_toStringTag_sym(), js_mkstr(js, "AbortController", 15));
ant_value_t ctrl_ctor = js_mkobj(js);
js_set_slot(ctrl_ctor, SLOT_CFUNC, js_mkfun(abort_controller_ctor));
js_mkprop_fast(js, ctrl_ctor, "prototype", 9, ctrl_proto);
js_mkprop_fast(js, ctrl_ctor, "name", 4, ANT_STRING("AbortController"));
js_set_descriptor(js, ctrl_ctor, "name", 4, 0);
ant_value_t ctrl_fn = js_obj_to_func(ctrl_ctor);
js_set(js, ctrl_proto, "constructor", ctrl_fn);
js_set_descriptor(js, ctrl_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_set(js, global, "AbortController", ctrl_fn);
js_set(js, global, "AbortSignal", signal_fn);
}
void gc_mark_abort(ant_t *js, gc_mark_fn mark) {
if (g_initialized) mark(js, g_signal_proto);
for (abort_timeout_entry_t *e = timeout_entries; e; e = e->next)
if (!e->closed) mark(js, e->signal);
}
bool abort_signal_is_aborted(ant_value_t signal) {
abort_signal_data_t *data = get_signal_data(signal);
return data && data->aborted;
}
bool abort_signal_is_signal(ant_value_t signal) {
return get_signal_data(signal) != NULL;
}
ant_value_t abort_signal_get_reason(ant_value_t signal) {
abort_signal_data_t *data = get_signal_data(signal);
return data ? data->reason : js_mkundef();
}
void abort_signal_add_listener(ant_t *js, ant_value_t signal, ant_value_t callback) {
abort_signal_data_t *data = get_signal_data(signal);
if (!data) return;
if (data->aborted) {
ant_value_t event_obj = js_mkobj(js);
js_set(js, event_obj, "type", js_mkstr(js, "abort", 5));
js_set(js, event_obj, "target", signal);
ant_value_t call_args[1] = { event_obj };
sv_vm_call(js->vm, js, callback, signal, call_args, 1, NULL, false);
return;
}
abort_listener_t entry = { callback, false };
utarray_push_back(data->listeners, &entry);
}
diff --git a/src/modules/blob.c b/src/modules/blob.c
index c89746f..6cb216b 100644
--- a/src/modules/blob.c
+++ b/src/modules/blob.c
@@ -1,456 +1,469 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <time.h>
#include <ctype.h>
#include <stdio.h>
#include "ant.h"
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "descriptors.h"
#include "modules/blob.h"
#include "modules/buffer.h"
#include "modules/symbol.h"
#include "streams/readable.h"
ant_value_t g_blob_proto = 0;
ant_value_t g_file_proto = 0;
+bool blob_is_blob(ant_t *js, ant_value_t obj) {
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ if (vtype(brand) != T_NUM) return false;
+ int id = (int)js_getnum(brand);
+ return id == BRAND_BLOB || id == BRAND_FILE;
+}
+
blob_data_t *blob_get_data(ant_value_t obj) {
ant_value_t slot = js_get_slot(obj, SLOT_DATA);
if (vtype(slot) != T_NUM) return NULL;
return (blob_data_t *)(uintptr_t)(size_t)js_getnum(slot);
}
static blob_data_t *blob_data_new(const uint8_t *data, size_t size, const char *type) {
blob_data_t *bd = calloc(1, sizeof(blob_data_t));
if (!bd) return NULL;
if (size > 0 && data) {
bd->data = malloc(size);
if (!bd->data) { free(bd); return NULL; }
memcpy(bd->data, data, size);
}
bd->size = size;
bd->type = type ? strdup(type) : strdup("");
return bd;
}
static char *normalize_mime_type(const char *s) {
if (!s) return strdup("");
for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
if (*p < 0x20 || *p > 0x7E) return strdup("");
}
size_t len = strlen(s);
char *out = malloc(len + 1);
if (!out) return strdup("");
for (size_t i = 0; i <= len; i++) out[i] = (char)tolower((unsigned char)s[i]);
return out;
}
typedef struct {
uint8_t *buf;
size_t size;
size_t cap;
} byte_buf_t;
static bool byte_buf_grow(byte_buf_t *b, size_t extra) {
size_t needed = b->size + extra;
if (needed <= b->cap) return true;
size_t new_cap = b->cap ? b->cap * 2 : 64;
while (new_cap < needed) new_cap *= 2;
uint8_t *p = realloc(b->buf, new_cap);
if (!p) return false;
b->buf = p;
b->cap = new_cap;
return true;
}
static bool byte_buf_append(byte_buf_t *b, const uint8_t *data, size_t len) {
if (!byte_buf_grow(b, len)) return false;
memcpy(b->buf + b->size, data, len);
b->size += len;
return true;
}
static ant_value_t process_blob_part(ant_t *js, byte_buf_t *buf, ant_value_t part) {
uint8_t t = vtype(part);
if (t == T_TYPEDARRAY) {
TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(part);
if (!ta || !ta->buffer) return js_mkundef();
if (!byte_buf_append(buf, ta->buffer->data + ta->byte_offset, ta->byte_length))
return js_mkerr(js, "out of memory");
return js_mkundef();
}
if (t == T_OBJ) {
ant_value_t buf_slot = js_get_slot(part, SLOT_BUFFER);
if (vtype(buf_slot) == T_TYPEDARRAY) {
TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(buf_slot);
if (ta && ta->buffer && !ta->buffer->is_detached) {
if (!byte_buf_append(buf, ta->buffer->data + ta->byte_offset, ta->byte_length))
return js_mkerr(js, "out of memory");
}
return js_mkundef();
}
if (vtype(buf_slot) == T_NUM) {
ArrayBufferData *abd = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(buf_slot);
if (abd && !abd->is_detached) {
if (!byte_buf_append(buf, abd->data, abd->length))
return js_mkerr(js, "out of memory");
return js_mkundef();
}
}
blob_data_t *bd = blob_get_data(part);
if (bd && bd->size > 0) {
if (!byte_buf_append(buf, bd->data, bd->size))
return js_mkerr(js, "out of memory");
return js_mkundef();
}
}
ant_value_t str = (t == T_STR) ? part : js_tostring_val(js, part);
if (is_err(str)) return str;
size_t len;
char *s = js_getstr(js, str, &len);
if (s && len > 0) {
if (!byte_buf_append(buf, (const uint8_t *)s, len))
return js_mkerr(js, "out of memory");
}
return js_mkundef();
}
static ant_value_t process_blob_parts(ant_t *js, byte_buf_t *buf, ant_value_t parts) {
uint8_t t = vtype(parts);
if (t == T_UNDEF || t == T_NULL) return js_mkundef();
if (t != T_OBJ && t != T_ARR && t != T_FUNC)
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'Blob': The provided value cannot be converted to a sequence.");
js_iter_t it;
if (!js_iter_open(js, parts, &it))
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Blob': The provided value is not of type 'BlobPart'");
ant_value_t value;
while (js_iter_next(js, &it, &value)) {
ant_value_t r = process_blob_part(js, buf, value);
if (is_err(r)) { js_iter_close(js, &it); return r; }
}
return js_mkundef();
}
static void blob_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
blob_data_t *bd = (blob_data_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
if (bd) { free(bd->data); free(bd->type); free(bd->name); free(bd); }
return;
}}
}
ant_value_t blob_create(ant_t *js, const uint8_t *data, size_t size, const char *type) {
blob_data_t *bd = blob_data_new(data, size, type);
if (!bd) return js_mkerr(js, "out of memory");
ant_value_t obj = js_mkobj(js);
+
js_set_proto_init(obj, g_blob_proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_BLOB));
js_set_slot(obj, SLOT_DATA, ANT_PTR(bd));
js_set_finalizer(obj, blob_finalize);
+
return obj;
}
static ant_value_t blob_get_size(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
blob_data_t *bd = blob_get_data(js->this_val);
return js_mknum(bd ? (double)bd->size : 0);
}
static ant_value_t blob_get_type(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
blob_data_t *bd = blob_get_data(js->this_val);
if (!bd || !bd->type) return js_mkstr(js, "", 0);
return js_mkstr(js, bd->type, strlen(bd->type));
}
static ant_value_t file_get_name(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
blob_data_t *bd = blob_get_data(js->this_val);
if (!bd || !bd->name) return js_mkstr(js, "", 0);
return js_mkstr(js, bd->name, strlen(bd->name));
}
static ant_value_t file_get_last_modified(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
blob_data_t *bd = blob_get_data(js->this_val);
return js_mknum(bd ? (double)bd->last_modified : 0);
}
static ant_value_t js_blob_text(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
blob_data_t *bd = blob_get_data(js->this_val);
ant_value_t promise = js_mkpromise(js);
ant_value_t str = (!bd || bd->size == 0)
? js_mkstr(js, "", 0)
: js_mkstr(js, (const char *)bd->data, bd->size);
js_resolve_promise(js, promise, str);
return promise;
}
static ant_value_t js_blob_array_buffer(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
blob_data_t *bd = blob_get_data(js->this_val);
ant_value_t promise = js_mkpromise(js);
size_t sz = (bd && bd->data) ? bd->size : 0;
ArrayBufferData *abd = create_array_buffer_data(sz);
if (!abd) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); return promise; }
if (sz > 0 && bd) memcpy(abd->data, bd->data, sz);
js_resolve_promise(js, promise, create_arraybuffer_obj(js, abd));
return promise;
}
static ant_value_t js_blob_bytes(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
blob_data_t *bd = blob_get_data(js->this_val);
ant_value_t promise = js_mkpromise(js);
size_t sz = (bd && bd->data) ? bd->size : 0;
ArrayBufferData *abd = create_array_buffer_data(sz);
if (!abd) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); return promise; }
if (sz > 0 && bd) memcpy(abd->data, bd->data, sz);
js_resolve_promise(js, promise,
create_typed_array(js, TYPED_ARRAY_UINT8, abd, 0, sz, "Uint8Array"));
return promise;
}
static ant_value_t js_blob_slice(ant_t *js, ant_value_t *args, int nargs) {
blob_data_t *bd = blob_get_data(js->this_val);
size_t blob_size = bd ? bd->size : 0;
ssize_t start = 0;
if (nargs >= 1 && vtype(args[0]) != T_UNDEF) {
double d = js_to_number(js, args[0]);
start = (ssize_t)d;
if (start < 0) start = (ssize_t)blob_size + start;
if (start < 0) start = 0;
if ((size_t)start > blob_size) start = (ssize_t)blob_size;
}
ssize_t end = (ssize_t)blob_size;
if (nargs >= 2 && vtype(args[1]) != T_UNDEF) {
double d = js_to_number(js, args[1]);
end = (ssize_t)d;
if (end < 0) end = (ssize_t)blob_size + end;
if (end < 0) end = 0;
if ((size_t)end > blob_size) end = (ssize_t)blob_size;
}
if (end < start) end = start;
size_t new_size = (size_t)(end - start);
const uint8_t *src = (bd && bd->data && new_size > 0) ? (bd->data + start) : NULL;
const char *new_type = (bd && bd->type) ? bd->type : "";
char *type_owned = NULL;
if (nargs >= 3 && vtype(args[2]) != T_UNDEF) {
ant_value_t tv = args[2];
if (vtype(tv) != T_STR) { tv = js_tostring_val(js, tv); if (is_err(tv)) return tv; }
type_owned = normalize_mime_type(js_getstr(js, tv, NULL));
new_type = type_owned;
}
ant_value_t result = blob_create(js, src, new_size, new_type);
free(type_owned);
return result;
}
static ant_value_t blob_stream_pull(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t blob_obj = js_get_slot(js->current_func, SLOT_DATA);
blob_data_t *bd = blob_get_data(blob_obj);
ant_value_t ctrl = (nargs > 0) ? args[0] : js_mkundef();
if (bd && bd->size > 0 && bd->data) {
ArrayBufferData *ab = create_array_buffer_data(bd->size);
if (ab) {
memcpy(ab->data, bd->data, bd->size);
rs_controller_enqueue(js, ctrl, create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, bd->size, "Uint8Array"));
}}
rs_controller_close(js, ctrl);
return js_mkundef();
}
static ant_value_t js_blob_stream(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t pull_fn = js_heavy_mkfun(js, blob_stream_pull, js->this_val);
return rs_create_stream(js, pull_fn, js_mkundef(), 1);
}
static ant_value_t js_blob_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "Blob constructor requires 'new'");
byte_buf_t buf = {NULL, 0, 0};
if (nargs >= 1 && vtype(args[0]) != T_UNDEF) {
uint8_t pt = vtype(args[0]);
if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC)
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'Blob': The provided value cannot be converted to a sequence.");
ant_value_t r = process_blob_parts(js, &buf, args[0]);
if (is_err(r)) { free(buf.buf); return r; }
}
const char *type_str = "";
char *type_owned = NULL;
if (nargs >= 2 && vtype(args[1]) != T_UNDEF && vtype(args[1]) != T_NULL) {
uint8_t ot = vtype(args[1]);
if (ot != T_OBJ && ot != T_ARR && ot != T_FUNC && ot != T_CFUNC) {
free(buf.buf);
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'Blob': The 'options' argument is not an object.");
}
// access "endings" before "type" per lexicographic order (WPT requirement)
(void)js_get(js, args[1], "endings");
ant_value_t type_v = js_get(js, args[1], "type");
if (vtype(type_v) != T_UNDEF) {
if (vtype(type_v) != T_STR) {
type_v = js_tostring_val(js, type_v);
if (is_err(type_v)) { free(buf.buf); return type_v; }
}
type_owned = normalize_mime_type(js_getstr(js, type_v, NULL));
type_str = type_owned;
}
}
blob_data_t *bd = blob_data_new(buf.buf, buf.size, type_str);
free(buf.buf); free(type_owned);
if (!bd) return js_mkerr(js, "out of memory");
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_blob_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_BLOB));
js_set_slot(obj, SLOT_DATA, ANT_PTR(bd));
js_set_finalizer(obj, blob_finalize);
+
return obj;
}
static ant_value_t js_file_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "File constructor requires 'new'");
if (nargs < 2)
return js_mkerr_typed(js, JS_ERR_TYPE, "File constructor requires at least 2 arguments");
byte_buf_t buf = {NULL, 0, 0};
if (vtype(args[0]) != T_UNDEF) {
uint8_t pt = vtype(args[0]);
if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC) {
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'File': The provided value cannot be converted to a sequence.");
}
ant_value_t r = process_blob_parts(js, &buf, args[0]);
if (is_err(r)) { free(buf.buf); return r; }
}
ant_value_t name_v = args[1];
if (vtype(name_v) != T_STR) {
name_v = js_tostring_val(js, name_v);
if (is_err(name_v)) { free(buf.buf); return name_v; }
}
const char *name_str = js_getstr(js, name_v, NULL);
const char *type_str = "";
char *type_owned = NULL;
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
int64_t last_modified = (int64_t)ts.tv_sec * 1000LL + (int64_t)(ts.tv_nsec / 1000000);
if (nargs >= 3 && vtype(args[2]) != T_UNDEF && vtype(args[2]) != T_NULL) {
ant_value_t opts = args[2];
uint8_t ot = vtype(opts);
if (ot == T_OBJ || ot == T_ARR) {
ant_value_t type_v = js_get(js, opts, "type");
if (vtype(type_v) != T_UNDEF) {
if (vtype(type_v) != T_STR) {
type_v = js_tostring_val(js, type_v);
if (is_err(type_v)) { free(buf.buf); return type_v; }
}
type_owned = normalize_mime_type(js_getstr(js, type_v, NULL));
type_str = type_owned;
}
ant_value_t lm_v = js_get(js, opts, "lastModified");
if (vtype(lm_v) != T_UNDEF) {
double d = js_to_number(js, lm_v);
if (d == d) last_modified = (int64_t)d;
}
}
}
blob_data_t *bd = blob_data_new(buf.buf, buf.size, type_str);
free(buf.buf); free(type_owned);
if (!bd) return js_mkerr(js, "out of memory");
bd->name = strdup(name_str ? name_str : "");
bd->last_modified = last_modified;
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_file_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_FILE));
js_set_slot(obj, SLOT_DATA, ANT_PTR(bd));
js_set_finalizer(obj, blob_finalize);
return obj;
}
void init_blob_module(void) {
ant_t *js = rt->js;
ant_value_t g = js_glob(js);
g_blob_proto = js_mkobj(js);
js_set_getter_desc(js, g_blob_proto, "size", 4, js_mkfun(blob_get_size), JS_DESC_C);
js_set_getter_desc(js, g_blob_proto, "type", 4, js_mkfun(blob_get_type), JS_DESC_C);
js_set(js, g_blob_proto, "text", js_mkfun(js_blob_text));
js_set(js, g_blob_proto, "arrayBuffer", js_mkfun(js_blob_array_buffer));
js_set(js, g_blob_proto, "bytes", js_mkfun(js_blob_bytes));
js_set(js, g_blob_proto, "slice", js_mkfun(js_blob_slice));
js_set(js, g_blob_proto, "stream", js_mkfun(js_blob_stream));
js_set_sym(js, g_blob_proto, get_toStringTag_sym(), js_mkstr(js, "Blob", 4));
ant_value_t blob_ctor = js_make_ctor(js, js_blob_ctor, g_blob_proto, "Blob", 4);
js_set(js, g, "Blob", blob_ctor);
js_set_descriptor(js, g, "Blob", 4, JS_DESC_W | JS_DESC_C);
g_file_proto = js_mkobj(js);
js_set_proto_init(g_file_proto, g_blob_proto);
js_set_getter_desc(js, g_file_proto, "name", 4, js_mkfun(file_get_name), JS_DESC_C);
js_set_getter_desc(js, g_file_proto, "lastModified", 12, js_mkfun(file_get_last_modified), JS_DESC_C);
js_set_sym(js, g_file_proto, get_toStringTag_sym(), js_mkstr(js, "File", 4));
ant_value_t file_ctor = js_make_ctor(js, js_file_ctor, g_file_proto, "File", 4);
js_set(js, g, "File", file_ctor);
js_set_descriptor(js, g, "File", 4, JS_DESC_W | JS_DESC_C);
}
diff --git a/src/modules/buffer.c b/src/modules/buffer.c
index f4f75bf..27d54a0 100644
--- a/src/modules/buffer.c
+++ b/src/modules/buffer.c
@@ -1,2226 +1,2264 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include "ant.h"
#include "errors.h"
#include "base64.h"
#include "internal.h"
#include "silver/engine.h"
#include "runtime.h"
#include "descriptors.h"
#include "modules/buffer.h"
#include "modules/symbol.h"
#define TA_ARENA_SIZE (16 * 1024 * 1024)
#define BUFFER_REGISTRY_INITIAL_CAP 64
static uint8_t *ta_arena = NULL;
static size_t ta_arena_offset = 0;
+static size_t buffer_registry_count = 0;
+static size_t buffer_registry_cap = 0;
+
static ArrayBufferData **buffer_registry = NULL;
static ant_value_t g_typedarray_iter_proto = 0;
-static size_t buffer_registry_count = 0;
-static size_t buffer_registry_cap = 0;
+bool buffer_is_dataview(ant_value_t obj) {
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ return vtype(brand) == T_NUM && (int)js_getnum(brand) == BRAND_DATAVIEW;
+}
+
+bool buffer_source_get_bytes(ant_t *js, ant_value_t value, const uint8_t **out, size_t *len) {
+ ant_value_t slot = js_get_slot(value, SLOT_BUFFER);
+ TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(slot);
+
+ if (ta) {
+ if (!ta->buffer || ta->buffer->is_detached) { *out = NULL; *len = 0; return true; }
+ *out = ta->buffer->data + ta->byte_offset;
+ *len = ta->byte_length;
+ return true;
+ }
+
+ if (vtype(slot) == T_NUM) {
+ ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(slot);
+ if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; }
+ *out = ab->data;
+ *len = ab->length;
+ return true;
+ }
+
+ if (buffer_is_dataview(value)) {
+ ant_value_t dv_data_val = js_get_slot(value, SLOT_DATA);
+ if (vtype(dv_data_val) != T_NUM) return false;
+ DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
+ if (!dv || !dv->buffer || dv->buffer->is_detached) { *out = NULL; *len = 0; return true; }
+ *out = dv->buffer->data + dv->byte_offset;
+ *len = dv->byte_length;
+ return true;
+ }
+
+ return false;
+}
static bool advance_typedarray(ant_t *js, js_iter_t *it, ant_value_t *out) {
ant_value_t iter = it->iterator;
ant_value_t ta_obj = js_get_slot(iter, SLOT_DATA);
ant_value_t state_v = js_get_slot(iter, SLOT_ITER_STATE);
uint32_t state = (vtype(state_v) == T_NUM) ? (uint32_t)js_getnum(state_v) : 0;
uint32_t kind = ITER_STATE_KIND(state);
uint32_t idx = ITER_STATE_INDEX(state);
ant_value_t ta_val = js_get_slot(ta_obj, SLOT_BUFFER);
TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(ta_val);
if (!ta || !ta->buffer || ta->buffer->is_detached || idx >= (uint32_t)ta->length)
return false;
uint8_t *data = ta->buffer->data + ta->byte_offset;
double value;
switch (ta->type) {
case TYPED_ARRAY_INT8: value = (double)((int8_t *)data)[idx]; break;
case TYPED_ARRAY_UINT8:
case TYPED_ARRAY_UINT8_CLAMPED: value = (double)data[idx]; break;
case TYPED_ARRAY_INT16: value = (double)((int16_t *)data)[idx]; break;
case TYPED_ARRAY_UINT16: value = (double)((uint16_t *)data)[idx]; break;
case TYPED_ARRAY_INT32: value = (double)((int32_t *)data)[idx]; break;
case TYPED_ARRAY_UINT32: value = (double)((uint32_t *)data)[idx]; break;
case TYPED_ARRAY_FLOAT32: value = (double)((float *)data)[idx]; break;
case TYPED_ARRAY_FLOAT64: value = ((double *)data)[idx]; break;
default: return false;
}
switch (kind) {
case ARR_ITER_KEYS:
*out = js_mknum((double)idx);
break;
case ARR_ITER_ENTRIES: {
ant_value_t pair = js_mkarr(js);
js_arr_push(js, pair, js_mknum((double)idx));
js_arr_push(js, pair, js_mknum(value));
*out = pair;
break;
}
default:
*out = js_mknum(value);
break;
}
js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, idx + 1)));
return true;
}
static ant_value_t ta_iter_next(ant_t *js, ant_value_t *args, int nargs) {
js_iter_t it = { .iterator = js->this_val };
ant_value_t value;
return js_iter_result(js, advance_typedarray(js, &it, &value), value);
}
static ant_value_t ta_values(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t iter = js_mkobj(js);
js_set_slot_wb(js, iter, SLOT_DATA, js->this_val);
js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(ARR_ITER_VALUES, 0)));
js_set_proto_init(iter, g_typedarray_iter_proto);
return iter;
}
static ant_value_t ta_keys(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t iter = js_mkobj(js);
js_set_slot_wb(js, iter, SLOT_DATA, js->this_val);
js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(ARR_ITER_KEYS, 0)));
js_set_proto_init(iter, g_typedarray_iter_proto);
return iter;
}
static ant_value_t ta_entries(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t iter = js_mkobj(js);
js_set_slot_wb(js, iter, SLOT_DATA, js->this_val);
js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(ARR_ITER_ENTRIES, 0)));
js_set_proto_init(iter, g_typedarray_iter_proto);
return iter;
}
static void register_buffer(ArrayBufferData *data) {
if (!data) return;
if (!buffer_registry) {
buffer_registry = calloc(BUFFER_REGISTRY_INITIAL_CAP, sizeof(ArrayBufferData *));
if (!buffer_registry) return;
buffer_registry_cap = BUFFER_REGISTRY_INITIAL_CAP;
}
if (buffer_registry_count >= buffer_registry_cap) {
size_t new_cap = buffer_registry_cap * 2;
ArrayBufferData **new_reg = realloc(buffer_registry, new_cap * sizeof(ArrayBufferData *));
if (!new_reg) return;
buffer_registry = new_reg;
buffer_registry_cap = new_cap;
}
buffer_registry[buffer_registry_count++] = data;
}
static void unregister_buffer(ArrayBufferData *data) {
if (!data || !buffer_registry) return;
for (size_t i = 0; i < buffer_registry_count; i++) {
if (buffer_registry[i] == data) {
buffer_registry[i] = buffer_registry[--buffer_registry_count];
return;
}
}
}
static inline ssize_t normalize_index(ssize_t idx, ssize_t len) {
if (idx < 0) idx += len;
if (idx < 0) return 0;
if (idx > len) return len;
return idx;
}
static void *ta_arena_alloc(size_t size) {
size = (size + 7) & ~7;
if (!ta_arena) {
#ifdef _WIN32
ta_arena = VirtualAlloc(NULL, TA_ARENA_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!ta_arena) return NULL;
#else
void *hint = (void *)0x100000;
ta_arena = mmap(
hint, TA_ARENA_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ta_arena == MAP_FAILED) {
ta_arena = mmap(
NULL, TA_ARENA_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}
if (ta_arena == MAP_FAILED) return NULL;
#endif
}
if (ta_arena_offset + size > TA_ARENA_SIZE) return NULL;
void *ptr = ta_arena + ta_arena_offset;
ta_arena_offset += size;
return ptr;
}
static ant_value_t js_arraybuffer_slice(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_typedarray_slice(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_typedarray_subarray(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_typedarray_fill(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_typedarray_at(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_typedarray_set(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_typedarray_copyWithin(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_typedarray_toReversed(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_typedarray_toSorted(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_typedarray_with(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_dataview_getUint8(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_dataview_setUint8(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_dataview_getInt16(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_dataview_setInt16(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_dataview_getInt32(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_dataview_setInt32(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_dataview_getFloat32(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_dataview_setFloat32(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_dataview_getFloat64(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_dataview_setFloat64(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_typedarray_toString(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_buffer_slice(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_buffer_toString(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_buffer_toBase64(ant_t *js, ant_value_t *args, int nargs);
static ant_value_t js_buffer_write(ant_t *js, ant_value_t *args, int nargs);
ArrayBufferData *create_array_buffer_data(size_t length) {
ArrayBufferData *data = ant_calloc(sizeof(ArrayBufferData) + length);
if (!data) return NULL;
data->data = (uint8_t *)(data + 1);
memset(data->data, 0, length);
data->length = length;
data->capacity = length;
data->ref_count = 1;
data->is_shared = 0;
data->is_detached = 0;
register_buffer(data);
return data;
}
static ArrayBufferData *create_shared_array_buffer_data(size_t length) {
ArrayBufferData *data = create_array_buffer_data(length);
if (data) data->is_shared = 1;
return data;
}
void free_array_buffer_data(ArrayBufferData *data) {
if (!data) return;
data->ref_count--;
if (data->ref_count <= 0) {
unregister_buffer(data);
free(data);
}
}
static size_t get_element_size(TypedArrayType type) {
static const void *dispatch[] = {
&&L_1, &&L_1, &&L_1, &&L_2, &&L_2,
&&L_4, &&L_4, &&L_4, &&L_8, &&L_8, &&L_8
};
if (type > TYPED_ARRAY_BIGUINT64) goto L_1;
goto *dispatch[type];
L_1: return 1;
L_2: return 2;
L_4: return 4;
L_8: return 8;
}
static const char *typedarray_type_name(TypedArrayType type) {
switch (type) {
case TYPED_ARRAY_INT8: return "Int8Array";
case TYPED_ARRAY_UINT8: return "Uint8Array";
case TYPED_ARRAY_UINT8_CLAMPED: return "Uint8ClampedArray";
case TYPED_ARRAY_INT16: return "Int16Array";
case TYPED_ARRAY_UINT16: return "Uint16Array";
case TYPED_ARRAY_INT32: return "Int32Array";
case TYPED_ARRAY_UINT32: return "Uint32Array";
case TYPED_ARRAY_FLOAT32: return "Float32Array";
case TYPED_ARRAY_FLOAT64: return "Float64Array";
case TYPED_ARRAY_BIGINT64: return "BigInt64Array";
case TYPED_ARRAY_BIGUINT64: return "BigUint64Array";
default: return "Uint8Array";
}}
static ant_value_t create_typed_array_like(
ant_t *js,
ant_value_t this_val,
TypedArrayType type,
ArrayBufferData *buffer,
size_t byte_offset,
size_t length
) {
ant_value_t ab_obj = create_arraybuffer_obj(js, buffer);
ant_value_t out = create_typed_array_with_buffer(
js,type, buffer, byte_offset,
length, typedarray_type_name(type), ab_obj
);
if (is_err(out)) return out;
ant_value_t proto = js_get_proto(js, this_val);
if (is_special_object(proto)) js_set_proto_init(out, proto);
return out;
}
static ant_value_t js_arraybuffer_constructor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF) {
return js_mkerr_typed(js, JS_ERR_TYPE, "ArrayBuffer constructor requires 'new'");
}
size_t length = 0;
if (nargs > 0 && vtype(args[0]) == T_NUM) {
length = (size_t)js_getnum(args[0]);
}
ArrayBufferData *data = create_array_buffer_data(length);
if (!data) {
return js_mkerr(js, "Failed to allocate ArrayBuffer");
}
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_get_ctor_proto(js, "ArrayBuffer", 11);
if (is_special_object(proto)) js_set_proto_init(obj, proto);
js_set_slot(obj, SLOT_BUFFER, ANT_PTR(data));
js_set(js, obj, "byteLength", js_mknum((double)length));
return obj;
}
// ArrayBuffer.prototype.slice(begin, end)
static ant_value_t js_arraybuffer_slice(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t data_val = js_get_slot(this_val, SLOT_BUFFER);
if (vtype(data_val) != T_NUM) {
return js_mkerr(js, "Not an ArrayBuffer");
}
ArrayBufferData *data = (ArrayBufferData *)(uintptr_t)js_getnum(data_val);
if (!data) return js_mkerr(js, "Invalid ArrayBuffer");
if (data->is_detached) return js_mkerr(js, "Cannot slice a detached ArrayBuffer");
ssize_t len = (ssize_t)data->length;
ssize_t begin = 0, end = len;
if (nargs > 0 && vtype(args[0]) == T_NUM) begin = (ssize_t)js_getnum(args[0]);
if (nargs > 1 && vtype(args[1]) == T_NUM) end = (ssize_t)js_getnum(args[1]);
begin = normalize_index(begin, len);
end = normalize_index(end, len);
if (end < begin) end = begin;
size_t new_length = (size_t)(end - begin);
ArrayBufferData *new_data = create_array_buffer_data(new_length);
if (!new_data) return js_mkerr(js, "Failed to allocate new ArrayBuffer");
memcpy(new_data->data, data->data + begin, new_length);
ant_value_t new_obj = js_mkobj(js);
ant_value_t proto = js_get_ctor_proto(js, "ArrayBuffer", 11);
if (is_special_object(proto)) js_set_proto_init(new_obj, proto);
js_set_slot(new_obj, SLOT_BUFFER, ANT_PTR(new_data));
js_set(js, new_obj, "byteLength", js_mknum((double)new_length));
return new_obj;
}
// ArrayBuffer.prototype.transfer(newLength)
static ant_value_t js_arraybuffer_transfer(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t data_val = js_get_slot(this_val, SLOT_BUFFER);
if (vtype(data_val) != T_NUM) {
return js_mkerr(js, "Not an ArrayBuffer");
}
ArrayBufferData *data = (ArrayBufferData *)(uintptr_t)js_getnum(data_val);
if (!data) return js_mkerr(js, "Invalid ArrayBuffer");
if (data->is_detached) {
return js_mkerr(js, "Cannot transfer a detached ArrayBuffer");
}
if (data->is_shared) {
return js_mkerr(js, "Cannot transfer a SharedArrayBuffer");
}
size_t new_length = data->length;
if (nargs > 0 && vtype(args[0]) == T_NUM) {
new_length = (size_t)js_getnum(args[0]);
}
ArrayBufferData *new_data = create_array_buffer_data(new_length);
if (!new_data) return js_mkerr(js, "Failed to allocate new ArrayBuffer");
size_t copy_length = data->length < new_length ? data->length : new_length;
memcpy(new_data->data, data->data, copy_length);
data->is_detached = 1;
data->length = 0;
js_set(js, this_val, "byteLength", js_mknum(0));
ant_value_t new_obj = js_mkobj(js);
ant_value_t proto = js_get_ctor_proto(js, "ArrayBuffer", 11);
if (is_special_object(proto)) js_set_proto_init(new_obj, proto);
js_set_slot(new_obj, SLOT_BUFFER, ANT_PTR(new_data));
js_set(js, new_obj, "byteLength", js_mknum((double)new_length));
return new_obj;
}
// ArrayBuffer.prototype.transferToFixedLength(newLength)
static ant_value_t js_arraybuffer_transferToFixedLength(ant_t *js, ant_value_t *args, int nargs) {
return js_arraybuffer_transfer(js, args, nargs);
}
// ArrayBuffer.prototype.detached getter
static ant_value_t js_arraybuffer_detached_getter(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t data_val = js_get_slot(this_val, SLOT_BUFFER);
if (vtype(data_val) != T_NUM) return js_false;
ArrayBufferData *data = (ArrayBufferData *)(uintptr_t)js_getnum(data_val);
if (!data) return js_true;
return js_bool(data->is_detached);
}
static ant_value_t typedarray_index_getter(ant_t *js, ant_value_t obj, const char *key, size_t key_len) {
if (key_len == 0 || key_len > 10) return js_mkundef();
size_t index = 0;
for (size_t i = 0; i < key_len; i++) {
char c = key[i];
if (c < '0' || c > '9') return js_mkundef();
index = index * 10 + (c - '0');
}
ant_value_t ta_val = js_get_slot(obj, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_val);
if (!ta_data || index >= ta_data->length) return js_mkundef();
if (!ta_data->buffer || ta_data->buffer->is_detached) return js_mkundef();
uint8_t *data = ta_data->buffer->data + ta_data->byte_offset;
double value;
static const void *dispatch[] = {
&&L_INT8, &&L_UINT8, &&L_UINT8, &&L_INT16, &&L_UINT16,
&&L_INT32, &&L_UINT32, &&L_FLOAT32, &&L_FLOAT64, &&L_UNDEF, &&L_UNDEF
};
if (ta_data->type > TYPED_ARRAY_BIGUINT64) goto L_UNDEF;
goto *dispatch[ta_data->type];
L_INT8: value = (double)((int8_t*)data)[index]; goto L_DONE;
L_UINT8: value = (double)data[index]; goto L_DONE;
L_INT16: value = (double)((int16_t*)data)[index]; goto L_DONE;
L_UINT16: value = (double)((uint16_t*)data)[index]; goto L_DONE;
L_INT32: value = (double)((int32_t*)data)[index]; goto L_DONE;
L_UINT32: value = (double)((uint32_t*)data)[index]; goto L_DONE;
L_FLOAT32: value = (double)((float*)data)[index]; goto L_DONE;
L_FLOAT64: value = ((double*)data)[index]; goto L_DONE;
L_UNDEF: return js_mkundef();
L_DONE: return js_mknum(value);
}
static bool typedarray_index_setter(ant_t *js, ant_value_t obj, const char *key, size_t key_len, ant_value_t value) {
if (key_len == 0 || key_len > 10) return false;
size_t index = 0;
for (size_t i = 0; i < key_len; i++) {
char c = key[i];
if (c < '0' || c > '9') return false;
index = index * 10 + (c - '0');
}
ant_value_t ta_val = js_get_slot(obj, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_val);
if (!ta_data || index >= ta_data->length) return true;
if (!ta_data->buffer || ta_data->buffer->is_detached) return true;
double num_val = vtype(value) == T_NUM ? js_getnum(value) : 0;
uint8_t *data = ta_data->buffer->data + ta_data->byte_offset;
static const void *dispatch[] = {
&&S_INT8, &&S_UINT8, &&S_UINT8, &&S_INT16, &&S_UINT16,
&&S_INT32, &&S_UINT32, &&S_FLOAT32, &&S_FLOAT64, &&S_FAIL, &&S_FAIL
};
if (ta_data->type > TYPED_ARRAY_BIGUINT64) goto S_FAIL;
goto *dispatch[ta_data->type];
S_INT8: ((int8_t*)data)[index] = (int8_t)num_val; goto S_DONE;
S_UINT8: data[index] = (uint8_t)num_val; goto S_DONE;
S_INT16: ((int16_t*)data)[index] = (int16_t)num_val; goto S_DONE;
S_UINT16: ((uint16_t*)data)[index] = (uint16_t)num_val; goto S_DONE;
S_INT32: ((int32_t*)data)[index] = (int32_t)num_val; goto S_DONE;
S_UINT32: ((uint32_t*)data)[index] = (uint32_t)num_val; goto S_DONE;
S_FLOAT32: ((float*)data)[index] = (float)num_val; goto S_DONE;
S_FLOAT64: ((double*)data)[index] = num_val; goto S_DONE;
S_FAIL: return false;
S_DONE: return true;
}
static bool typedarray_read_number(const TypedArrayData *ta_data, size_t index, double *out) {
if (!ta_data || !ta_data->buffer || ta_data->buffer->is_detached || index >= ta_data->length) return false;
uint8_t *data = ta_data->buffer->data + ta_data->byte_offset;
static const void *dispatch[] = {
&&R_INT8, &&R_UINT8, &&R_UINT8, &&R_INT16, &&R_UINT16,
&&R_INT32, &&R_UINT32, &&R_FLOAT32, &&R_FLOAT64, &&R_FAIL, &&R_FAIL
};
if (ta_data->type > TYPED_ARRAY_BIGUINT64) goto R_FAIL;
goto *dispatch[ta_data->type];
R_INT8: *out = (double)((int8_t *)data)[index]; return true;
R_UINT8: *out = (double)data[index]; return true;
R_INT16: *out = (double)((int16_t *)data)[index]; return true;
R_UINT16: *out = (double)((uint16_t *)data)[index]; return true;
R_INT32: *out = (double)((int32_t *)data)[index]; return true;
R_UINT32: *out = (double)((uint32_t *)data)[index]; return true;
R_FLOAT32: *out = (double)((float *)data)[index]; return true;
R_FLOAT64: *out = ((double *)data)[index]; return true;
R_FAIL: return false;
}
static bool typedarray_write_number(TypedArrayData *ta_data, size_t index, double value) {
if (!ta_data || !ta_data->buffer || ta_data->buffer->is_detached || index >= ta_data->length) return false;
uint8_t *data = ta_data->buffer->data + ta_data->byte_offset;
static const void *dispatch[] = {
&&W_INT8, &&W_UINT8, &&W_UINT8, &&W_INT16, &&W_UINT16,
&&W_INT32, &&W_UINT32, &&W_FLOAT32, &&W_FLOAT64, &&W_FAIL, &&W_FAIL
};
if (ta_data->type > TYPED_ARRAY_BIGUINT64) goto W_FAIL;
goto *dispatch[ta_data->type];
W_INT8: ((int8_t *)data)[index] = (int8_t)value; return true;
W_UINT8: data[index] = (uint8_t)value; return true;
W_INT16: ((int16_t *)data)[index] = (int16_t)value; return true;
W_UINT16: ((uint16_t *)data)[index] = (uint16_t)value; return true;
W_INT32: ((int32_t *)data)[index] = (int32_t)value; return true;
W_UINT32: ((uint32_t *)data)[index] = (uint32_t)value; return true;
W_FLOAT32: ((float *)data)[index] = (float)value; return true;
W_FLOAT64: ((double *)data)[index] = value; return true;
W_FAIL: return false;
}
ant_value_t create_arraybuffer_obj(ant_t *js, ArrayBufferData *buffer) {
ant_value_t ab_obj = js_mkobj(js);
ant_value_t ab_proto = js_get_ctor_proto(js, "ArrayBuffer", 11);
if (is_special_object(ab_proto)) js_set_proto_init(ab_obj, ab_proto);
js_set_slot(ab_obj, SLOT_BUFFER, js_mknum((double)(uintptr_t)buffer));
js_set(js, ab_obj, "byteLength", js_mknum((double)buffer->length));
buffer->ref_count++;
return ab_obj;
}
ant_value_t create_typed_array_with_buffer(
ant_t *js, TypedArrayType type, ArrayBufferData *buffer,
size_t byte_offset, size_t length, const char *type_name, ant_value_t arraybuffer_obj
) {
TypedArrayData *ta_data = ta_arena_alloc(sizeof(TypedArrayData));
if (!ta_data) return js_mkerr(js, "Failed to allocate TypedArray");
size_t element_size = get_element_size(type);
ta_data->buffer = buffer;
ta_data->type = type;
ta_data->byte_offset = byte_offset;
ta_data->byte_length = length * element_size;
ta_data->length = length;
buffer->ref_count++;
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_get_ctor_proto(js, type_name, strlen(type_name));
if (is_special_object(proto)) js_set_proto_init(obj, proto);
js_set_slot(obj, SLOT_BUFFER, js_mktypedarray(ta_data));
js_set(js, obj, "length", js_mknum((double)length));
js_set(js, obj, "byteLength", js_mknum((double)(length * element_size)));
js_set(js, obj, "byteOffset", js_mknum((double)byte_offset));
js_set(js, obj, "BYTES_PER_ELEMENT", js_mknum((double)element_size));
js_set(js, obj, "buffer", arraybuffer_obj);
js_set_getter(obj, typedarray_index_getter);
js_set_setter(obj, typedarray_index_setter);
return obj;
}
ant_value_t create_typed_array(
ant_t *js, TypedArrayType type, ArrayBufferData *buffer,
size_t byte_offset, size_t length, const char *type_name
) {
ant_value_t ab_obj = create_arraybuffer_obj(js, buffer);
ant_value_t result = create_typed_array_with_buffer(js, type, buffer, byte_offset, length, type_name, ab_obj);
free_array_buffer_data(buffer); return result;
}
typedef struct {
ant_value_t *values;
size_t length;
size_t capacity;
} iter_collect_ctx_t;
static bool iter_collect_callback(ant_t *js, ant_value_t value, void *udata) {
(void)js;
iter_collect_ctx_t *ctx = (iter_collect_ctx_t *)udata;
if (ctx->length >= ctx->capacity) {
ctx->capacity *= 2;
ant_value_t *new_values = realloc(ctx->values, ctx->capacity * sizeof(ant_value_t));
if (!new_values) return false;
ctx->values = new_values;
}
ctx->values[ctx->length++] = value;
return true;
}
static ant_value_t js_typedarray_constructor(ant_t *js, ant_value_t *args, int nargs, TypedArrayType type, const char *type_name) {
if (nargs == 0) {
ArrayBufferData *buffer = create_array_buffer_data(0);
return create_typed_array(js, type, buffer, 0, 0, type_name);
}
if (vtype(args[0]) == T_NUM) {
size_t length = (size_t)js_getnum(args[0]);
size_t element_size = get_element_size(type);
ArrayBufferData *buffer = create_array_buffer_data(length * element_size);
if (!buffer) return js_mkerr(js, "Failed to allocate buffer");
return create_typed_array(js, type, buffer, 0, length, type_name);
}
ant_value_t buffer_data_val = js_get_slot(args[0], SLOT_BUFFER);
if (vtype(buffer_data_val) == T_NUM) {
ArrayBufferData *buffer = (ArrayBufferData *)(uintptr_t)js_getnum(buffer_data_val);
size_t byte_offset = 0;
size_t length = buffer->length;
if (nargs > 1 && vtype(args[1]) == T_NUM) {
byte_offset = (size_t)js_getnum(args[1]);
}
size_t element_size = get_element_size(type);
if (byte_offset > buffer->length) {
return js_mkerr(js, "Start offset is outside the bounds of the buffer");
}
if (nargs > 2 && vtype(args[2]) == T_NUM) {
length = (size_t)js_getnum(args[2]);
size_t available = buffer->length - byte_offset;
if (length > available / element_size) {
return js_mkerr(js, "Invalid TypedArray length");
}
} else length = (buffer->length - byte_offset) / element_size;
return create_typed_array_with_buffer(js, type, buffer, byte_offset, length, type_name, args[0]);
}
if (is_special_object(args[0])) {
ant_value_t len_val = js_get(js, args[0], "length");
size_t length = 0; ant_value_t *values = NULL;
bool is_iterable = false;
if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val); else {
iter_collect_ctx_t ctx = { .values = NULL, .length = 0, .capacity = 16 };
ctx.values = malloc(ctx.capacity * sizeof(ant_value_t));
if (!ctx.values) return js_mkerr(js, "Failed to allocate memory");
is_iterable = js_iter(js, args[0], iter_collect_callback, &ctx);
if (is_iterable) {
values = ctx.values;
length = ctx.length;
} else free(ctx.values);
}
if (length > 0 || is_iterable || vtype(len_val) == T_NUM) {
size_t element_size = get_element_size(type);
ArrayBufferData *buffer = create_array_buffer_data(length * element_size);
if (!buffer) { if (values) free(values); return js_mkerr(js, "Failed to allocate buffer"); }
ant_value_t result = create_typed_array(js, type, buffer, 0, length, type_name);
uint8_t *data = buffer->data;
static const void *write_dispatch[] = {
&&W_INT8, &&W_UINT8, &&W_UINT8, &&W_INT16, &&W_UINT16,
&&W_INT32, &&W_UINT32, &&W_FLOAT32, &&W_FLOAT64, &&W_DONE, &&W_DONE
};
for (size_t i = 0; i < length; i++) {
ant_value_t elem;
if (values) elem = values[i]; else {
char idx_str[16];
snprintf(idx_str, sizeof(idx_str), "%zu", i);
elem = js_get(js, args[0], idx_str);
}
double val = vtype(elem) == T_NUM
? js_getnum(elem)
: js_to_number(js, elem);
if (type > TYPED_ARRAY_BIGUINT64) goto W_DONE;
goto *write_dispatch[type];
W_INT8: ((int8_t*)data)[i] = (int8_t)val; goto W_NEXT;
W_UINT8: data[i] = (uint8_t)val; goto W_NEXT;
W_INT16: ((int16_t*)data)[i] = (int16_t)val; goto W_NEXT;
W_UINT16: ((uint16_t*)data)[i] = (uint16_t)val; goto W_NEXT;
W_INT32: ((int32_t*)data)[i] = (int32_t)val; goto W_NEXT;
W_UINT32: ((uint32_t*)data)[i] = (uint32_t)val; goto W_NEXT;
W_FLOAT32: ((float*)data)[i] = (float)val; goto W_NEXT;
W_FLOAT64: ((double*)data)[i] = val; goto W_NEXT;
W_NEXT:;
}
W_DONE:
if (values) free(values);
return result;
}
}
return js_mkerr(js, "Invalid TypedArray constructor arguments");
}
// TypedArray.prototype.slice(begin, end)
// TypedArray.prototype.at(index)
static ant_value_t js_typedarray_at(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data) return js_mkerr(js, "Invalid TypedArray");
if (nargs == 0 || vtype(args[0]) != T_NUM) return js_mkundef();
ssize_t len = (ssize_t)ta_data->length;
ssize_t idx = (ssize_t)js_getnum(args[0]);
if (idx < 0) idx += len;
if (idx < 0 || idx >= len) return js_mkundef();
if (!ta_data->buffer || ta_data->buffer->is_detached) return js_mkundef();
size_t index = (size_t)idx;
uint8_t *data = ta_data->buffer->data + ta_data->byte_offset;
double value;
switch (ta_data->type) {
case TYPED_ARRAY_INT8: value = (double)((int8_t*)data)[index]; break;
case TYPED_ARRAY_UINT8:
case TYPED_ARRAY_UINT8_CLAMPED: value = (double)data[index]; break;
case TYPED_ARRAY_INT16: value = (double)((int16_t*)data)[index]; break;
case TYPED_ARRAY_UINT16: value = (double)((uint16_t*)data)[index]; break;
case TYPED_ARRAY_INT32: value = (double)((int32_t*)data)[index]; break;
case TYPED_ARRAY_UINT32: value = (double)((uint32_t*)data)[index]; break;
case TYPED_ARRAY_FLOAT32: value = (double)((float*)data)[index]; break;
case TYPED_ARRAY_FLOAT64: value = ((double*)data)[index]; break;
default: return js_mkundef();
}
return js_mknum(value);
}
static ant_value_t js_typedarray_slice(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data) return js_mkerr(js, "Invalid TypedArray");
ssize_t len = (ssize_t)ta_data->length;
ssize_t begin = 0, end = len;
if (nargs > 0 && vtype(args[0]) == T_NUM) begin = (ssize_t)js_getnum(args[0]);
if (nargs > 1 && vtype(args[1]) == T_NUM) end = (ssize_t)js_getnum(args[1]);
begin = normalize_index(begin, len);
end = normalize_index(end, len);
if (end < begin) end = begin;
size_t new_length = (size_t)(end - begin);
size_t element_size = get_element_size(ta_data->type);
ArrayBufferData *new_buffer = create_array_buffer_data(new_length * element_size);
if (!new_buffer) return js_mkerr(js, "Failed to allocate new buffer");
memcpy(
new_buffer->data,
ta_data->buffer->data + ta_data->byte_offset + (size_t)begin * element_size,
new_length * element_size
);
ant_value_t out = create_typed_array_like(
js, this_val, ta_data->type,
new_buffer, 0, new_length
); free_array_buffer_data(new_buffer);
return out;
}
// TypedArray.prototype.subarray(begin, end)
static ant_value_t js_typedarray_subarray(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data) return js_mkerr(js, "Invalid TypedArray");
ssize_t len = (ssize_t)ta_data->length;
ssize_t begin = 0, end = len;
if (nargs > 0 && vtype(args[0]) == T_NUM) begin = (ssize_t)js_getnum(args[0]);
if (nargs > 1 && vtype(args[1]) == T_NUM) end = (ssize_t)js_getnum(args[1]);
begin = normalize_index(begin, len);
end = normalize_index(end, len);
if (end < begin) end = begin;
size_t new_length = (size_t)(end - begin);
size_t element_size = get_element_size(ta_data->type);
size_t new_offset = ta_data->byte_offset + (size_t)begin * element_size;
return create_typed_array_like(
js, this_val, ta_data->type,
ta_data->buffer, new_offset, new_length
);
}
// TypedArray.prototype.fill(value, start, end)
static ant_value_t js_typedarray_fill(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data) return js_mkerr(js, "Invalid TypedArray");
double value = 0;
if (nargs > 0 && vtype(args[0]) == T_NUM) value = js_getnum(args[0]);
ssize_t len = (ssize_t)ta_data->length;
ssize_t start = 0, end = len;
if (nargs > 1 && vtype(args[1]) == T_NUM) start = (ssize_t)js_getnum(args[1]);
if (nargs > 2 && vtype(args[2]) == T_NUM) end = (ssize_t)js_getnum(args[2]);
start = normalize_index(start, len);
end = normalize_index(end, len);
if (end < start) end = start;
uint8_t *data = ta_data->buffer->data + ta_data->byte_offset;
static const void *dispatch[] = {
&&L_INT8, &&L_UINT8, &&L_UINT8, &&L_INT16, &&L_UINT16,
&&L_INT32, &&L_UINT32, &&L_FLOAT32, &&L_FLOAT64, &&L_DONE, &&L_DONE
};
if (ta_data->type > TYPED_ARRAY_BIGUINT64) goto L_DONE;
goto *dispatch[ta_data->type];
L_INT8:
for (ssize_t i = start; i < end; i++) ((int8_t*)data)[i] = (int8_t)value;
goto L_DONE;
L_UINT8:
for (ssize_t i = start; i < end; i++) data[i] = (uint8_t)value;
goto L_DONE;
L_INT16:
for (ssize_t i = start; i < end; i++) ((int16_t*)data)[i] = (int16_t)value;
goto L_DONE;
L_UINT16:
for (ssize_t i = start; i < end; i++) ((uint16_t*)data)[i] = (uint16_t)value;
goto L_DONE;
L_INT32:
for (ssize_t i = start; i < end; i++) ((int32_t*)data)[i] = (int32_t)value;
goto L_DONE;
L_UINT32:
for (ssize_t i = start; i < end; i++) ((uint32_t*)data)[i] = (uint32_t)value;
goto L_DONE;
L_FLOAT32:
for (ssize_t i = start; i < end; i++) ((float*)data)[i] = (float)value;
goto L_DONE;
L_FLOAT64:
for (ssize_t i = start; i < end; i++) ((double*)data)[i] = value;
goto L_DONE;
L_DONE:
return this_val;
}
// TypedArray.prototype.set(source, offset = 0)
static ant_value_t js_typedarray_set(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "set requires source argument");
ant_value_t this_val = js_getthis(js);
ant_value_t dst_ta_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *dst = (TypedArrayData *)js_gettypedarray(dst_ta_val);
if (!dst) return js_mkerr(js, "Invalid TypedArray");
if (!dst->buffer || dst->buffer->is_detached) return js_mkerr(js, "Cannot operate on a detached TypedArray");
ssize_t offset_i = 0;
if (nargs > 1 && vtype(args[1]) == T_NUM) offset_i = (ssize_t)js_getnum(args[1]);
if (offset_i < 0) return js_mkerr(js, "Offset out of bounds");
size_t offset = (size_t)offset_i;
if (offset > dst->length) return js_mkerr(js, "Offset out of bounds");
ant_value_t src_val = args[0];
ant_value_t src_ta_val = js_get_slot(src_val, SLOT_BUFFER);
TypedArrayData *src_ta = (TypedArrayData *)js_gettypedarray(src_ta_val);
if (src_ta && src_ta->buffer && !src_ta->buffer->is_detached) {
size_t src_len = src_ta->length;
if (offset + src_len > dst->length) return js_mkerr(js, "Source is too large");
if (src_ta->type == dst->type) {
size_t el = get_element_size(dst->type);
uint8_t *dst_data = dst->buffer->data + dst->byte_offset + offset * el;
uint8_t *src_data = src_ta->buffer->data + src_ta->byte_offset;
memmove(dst_data, src_data, src_len * el);
return js_mkundef();
}
for (size_t i = 0; i < src_len; i++) {
double value = 0;
if (!typedarray_read_number(src_ta, i, &value)) value = 0;
(void)typedarray_write_number(dst, offset + i, value);
}
return js_mkundef();
}
if (!is_special_object(src_val) && vtype(src_val) != T_STR) {
return js_mkerr(js, "set source must be array-like or TypedArray");
}
size_t src_len = 0;
if (vtype(src_val) == T_STR) {
src_len = (size_t)vstrlen(js, src_val);
} else {
ant_value_t len_val = js_get(js, src_val, "length");
src_len = vtype(len_val) == T_NUM ? (size_t)js_getnum(len_val) : 0;
}
if (offset + src_len > dst->length) return js_mkerr(js, "Source is too large");
for (size_t i = 0; i < src_len; i++) {
double value = 0;
if (vtype(src_val) == T_STR) {
ant_offset_t slen = 0;
ant_offset_t soff = vstr(js, src_val, &slen);
const unsigned char *sptr = (const unsigned char *)(uintptr_t)soff;
if (i < (size_t)slen) value = sptr[i];
} else {
char idx[24];
size_t idx_len = uint_to_str(idx, sizeof(idx), (uint64_t)i);
idx[idx_len] = '\0';
ant_value_t elem = js_get(js, src_val, idx);
value = vtype(elem) == T_NUM ? js_getnum(elem) : 0;
}
(void)typedarray_write_number(dst, offset + i, value);
}
return js_mkundef();
}
// TypedArray.prototype.copyWithin(target, start, end)
static ant_value_t js_typedarray_copyWithin(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta) return js_mkerr(js, "Invalid TypedArray");
if (!ta->buffer || ta->buffer->is_detached) return js_mkerr(js, "Cannot operate on a detached TypedArray");
ssize_t len = (ssize_t)ta->length;
if (len <= 0) return this_val;
ssize_t target = 0, start = 0, end = len;
if (nargs > 0 && vtype(args[0]) == T_NUM) target = (ssize_t)js_getnum(args[0]);
if (nargs > 1 && vtype(args[1]) == T_NUM) start = (ssize_t)js_getnum(args[1]);
if (nargs > 2 && vtype(args[2]) == T_NUM) end = (ssize_t)js_getnum(args[2]);
target = normalize_index(target, len);
start = normalize_index(start, len);
end = normalize_index(end, len);
if (end < start) end = start;
if (target >= len || start >= len || end <= start) return this_val;
size_t count = (size_t)(end - start);
size_t max_to_end = (size_t)(len - target);
if (count > max_to_end) count = max_to_end;
if (count == 0) return this_val;
size_t el = get_element_size(ta->type);
uint8_t *base = ta->buffer->data + ta->byte_offset;
memmove(base + (size_t)target * el, base + (size_t)start * el, count * el);
return this_val;
}
// TypedArray.prototype.toReversed()
static ant_value_t js_typedarray_toReversed(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
ant_value_t this_val = js_getthis(js);
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data) return js_mkerr(js, "Invalid TypedArray");
size_t length = ta_data->length;
size_t element_size = get_element_size(ta_data->type);
ArrayBufferData *new_buffer = create_array_buffer_data(length * element_size);
if (!new_buffer) return js_mkerr(js, "Failed to allocate new buffer");
uint8_t *src = ta_data->buffer->data + ta_data->byte_offset;
uint8_t *dst = new_buffer->data;
for (size_t i = 0; i < length; i++) {
memcpy(dst + i * element_size, src + (length - 1 - i) * element_size, element_size);
}
ant_value_t out = create_typed_array_like(
js, this_val, ta_data->type,
new_buffer, 0, length
); free_array_buffer_data(new_buffer);
return out;
}
// TypedArray.prototype.toSorted(comparefn)
static ant_value_t js_typedarray_toSorted(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data) return js_mkerr(js, "Invalid TypedArray");
size_t length = ta_data->length;
size_t element_size = get_element_size(ta_data->type);
ArrayBufferData *new_buffer = create_array_buffer_data(length * element_size);
if (!new_buffer) return js_mkerr(js, "Failed to allocate new buffer");
memcpy(new_buffer->data, ta_data->buffer->data + ta_data->byte_offset, length * element_size);
ant_value_t result = create_typed_array_like(js, this_val, ta_data->type, new_buffer, 0, length);
free_array_buffer_data(new_buffer);
if (is_err(result)) return result;
ant_value_t result_ta_val = js_get_slot(result, SLOT_BUFFER);
TypedArrayData *result_ta = (TypedArrayData *)js_gettypedarray(result_ta_val);
uint8_t *data = result_ta->buffer->data;
ant_value_t comparefn = (nargs > 0 && vtype(args[0]) == T_FUNC) ? args[0] : js_mkundef();
bool has_comparefn = vtype(comparefn) == T_FUNC;
for (size_t i = 1; i < length; i++) {
for (size_t j = i; j > 0; j--) {
double a_val, b_val;
int cmp;
static const void *read_dispatch[] = {
&&R_INT8, &&R_UINT8, &&R_UINT8, &&R_INT16, &&R_UINT16,
&&R_INT32, &&R_UINT32, &&R_FLOAT32, &&R_FLOAT64, &&R_DONE, &&R_DONE
};
if (ta_data->type > TYPED_ARRAY_BIGUINT64) goto R_DONE;
goto *read_dispatch[ta_data->type];
R_INT8: a_val = (double)((int8_t*)data)[j-1]; b_val = (double)((int8_t*)data)[j]; goto R_COMPARE;
R_UINT8: a_val = (double)data[j-1]; b_val = (double)data[j]; goto R_COMPARE;
R_INT16: a_val = (double)((int16_t*)data)[j-1]; b_val = (double)((int16_t*)data)[j]; goto R_COMPARE;
R_UINT16: a_val = (double)((uint16_t*)data)[j-1]; b_val = (double)((uint16_t*)data)[j]; goto R_COMPARE;
R_INT32: a_val = (double)((int32_t*)data)[j-1]; b_val = (double)((int32_t*)data)[j]; goto R_COMPARE;
R_UINT32: a_val = (double)((uint32_t*)data)[j-1]; b_val = (double)((uint32_t*)data)[j]; goto R_COMPARE;
R_FLOAT32: a_val = (double)((float*)data)[j-1]; b_val = (double)((float*)data)[j]; goto R_COMPARE;
R_FLOAT64: a_val = ((double*)data)[j-1]; b_val = ((double*)data)[j]; goto R_COMPARE;
R_DONE: goto SORT_DONE;
R_COMPARE:
if (has_comparefn) {
ant_value_t cmp_args[2] = { js_mknum(a_val), js_mknum(b_val) };
ant_value_t cmp_result = sv_vm_call(js->vm, js, comparefn, js_mkundef(), cmp_args, 2, NULL, false);
cmp = (int)js_getnum(cmp_result);
} else {
cmp = (a_val > b_val) ? 1 : ((a_val < b_val) ? -1 : 0);
}
if (cmp <= 0) break;
static const void *swap_dispatch[] = {
&&S_INT8, &&S_UINT8, &&S_UINT8, &&S_INT16, &&S_UINT16,
&&S_INT32, &&S_UINT32, &&S_FLOAT32, &&S_FLOAT64, &&S_DONE, &&S_DONE
};
if (ta_data->type > TYPED_ARRAY_BIGUINT64) goto S_DONE;
goto *swap_dispatch[ta_data->type];
S_INT8: { int8_t tmp = ((int8_t*)data)[j-1]; ((int8_t*)data)[j-1] = ((int8_t*)data)[j]; ((int8_t*)data)[j] = tmp; goto S_DONE; }
S_UINT8: { uint8_t tmp = data[j-1]; data[j-1] = data[j]; data[j] = tmp; goto S_DONE; }
S_INT16: { int16_t tmp = ((int16_t*)data)[j-1]; ((int16_t*)data)[j-1] = ((int16_t*)data)[j]; ((int16_t*)data)[j] = tmp; goto S_DONE; }
S_UINT16: { uint16_t tmp = ((uint16_t*)data)[j-1]; ((uint16_t*)data)[j-1] = ((uint16_t*)data)[j]; ((uint16_t*)data)[j] = tmp; goto S_DONE; }
S_INT32: { int32_t tmp = ((int32_t*)data)[j-1]; ((int32_t*)data)[j-1] = ((int32_t*)data)[j]; ((int32_t*)data)[j] = tmp; goto S_DONE; }
S_UINT32: { uint32_t tmp = ((uint32_t*)data)[j-1]; ((uint32_t*)data)[j-1] = ((uint32_t*)data)[j]; ((uint32_t*)data)[j] = tmp; goto S_DONE; }
S_FLOAT32: { float tmp = ((float*)data)[j-1]; ((float*)data)[j-1] = ((float*)data)[j]; ((float*)data)[j] = tmp; goto S_DONE; }
S_FLOAT64: { double tmp = ((double*)data)[j-1]; ((double*)data)[j-1] = ((double*)data)[j]; ((double*)data)[j] = tmp; goto S_DONE; }
S_DONE:;
}
}
SORT_DONE:
return result;
}
// TypedArray.prototype.with(index, value)
static ant_value_t js_typedarray_with(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "with requires index and value");
ant_value_t this_val = js_getthis(js);
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data) return js_mkerr(js, "Invalid TypedArray");
ssize_t index = (ssize_t)js_getnum(args[0]);
double value = js_getnum(args[1]);
size_t length = ta_data->length;
if (index < 0) index = (ssize_t)length + index;
if (index < 0 || (size_t)index >= length) {
return js_mkerr(js, "Index out of bounds");
}
size_t element_size = get_element_size(ta_data->type);
ArrayBufferData *new_buffer = create_array_buffer_data(length * element_size);
if (!new_buffer) return js_mkerr(js, "Failed to allocate new buffer");
memcpy(new_buffer->data, ta_data->buffer->data + ta_data->byte_offset, length * element_size);
uint8_t *data = new_buffer->data;
static const void *dispatch[] = {
&&W_INT8, &&W_UINT8, &&W_UINT8, &&W_INT16, &&W_UINT16,
&&W_INT32, &&W_UINT32, &&W_FLOAT32, &&W_FLOAT64, &&W_DONE, &&W_DONE
};
if (ta_data->type > TYPED_ARRAY_BIGUINT64) goto W_DONE;
goto *dispatch[ta_data->type];
W_INT8: ((int8_t*)data)[index] = (int8_t)value; goto W_DONE;
W_UINT8: data[index] = (uint8_t)value; goto W_DONE;
W_INT16: ((int16_t*)data)[index] = (int16_t)value; goto W_DONE;
W_UINT16: ((uint16_t*)data)[index] = (uint16_t)value; goto W_DONE;
W_INT32: ((int32_t*)data)[index] = (int32_t)value; goto W_DONE;
W_UINT32: ((uint32_t*)data)[index] = (uint32_t)value; goto W_DONE;
W_FLOAT32: ((float*)data)[index] = (float)value; goto W_DONE;
W_FLOAT64: ((double*)data)[index] = value; goto W_DONE;
W_DONE:
ant_value_t out = create_typed_array_like(
js, this_val, ta_data->type,
new_buffer, 0, length
); free_array_buffer_data(new_buffer);
return out;
}
#define DEFINE_TYPEDARRAY_CONSTRUCTOR(name, type) \
static ant_value_t js_##name##_constructor(ant_t *js, ant_value_t *args, int nargs) { \
if (vtype(js->new_target) == T_UNDEF) return js_mkerr_typed(js, JS_ERR_TYPE, #name " constructor requires 'new'"); \
return js_typedarray_constructor(js, args, nargs, type, #name); \
}
DEFINE_TYPEDARRAY_CONSTRUCTOR(Int8Array, TYPED_ARRAY_INT8)
DEFINE_TYPEDARRAY_CONSTRUCTOR(Uint8Array, TYPED_ARRAY_UINT8)
DEFINE_TYPEDARRAY_CONSTRUCTOR(Uint8ClampedArray, TYPED_ARRAY_UINT8_CLAMPED)
DEFINE_TYPEDARRAY_CONSTRUCTOR(Int16Array, TYPED_ARRAY_INT16)
DEFINE_TYPEDARRAY_CONSTRUCTOR(Uint16Array, TYPED_ARRAY_UINT16)
DEFINE_TYPEDARRAY_CONSTRUCTOR(Int32Array, TYPED_ARRAY_INT32)
DEFINE_TYPEDARRAY_CONSTRUCTOR(Uint32Array, TYPED_ARRAY_UINT32)
DEFINE_TYPEDARRAY_CONSTRUCTOR(Float32Array, TYPED_ARRAY_FLOAT32)
DEFINE_TYPEDARRAY_CONSTRUCTOR(Float64Array, TYPED_ARRAY_FLOAT64)
DEFINE_TYPEDARRAY_CONSTRUCTOR(BigInt64Array, TYPED_ARRAY_BIGINT64)
DEFINE_TYPEDARRAY_CONSTRUCTOR(BigUint64Array, TYPED_ARRAY_BIGUINT64)
static ant_value_t js_dataview_constructor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF) {
return js_mkerr_typed(js, JS_ERR_TYPE, "DataView constructor requires 'new'");
}
if (nargs < 1) {
return js_mkerr(js, "DataView requires an ArrayBuffer");
}
ant_value_t buffer_data_val = js_get_slot(args[0], SLOT_BUFFER);
if (vtype(buffer_data_val) != T_NUM) {
return js_mkerr(js, "First argument must be an ArrayBuffer");
}
ArrayBufferData *buffer = (ArrayBufferData *)(uintptr_t)js_getnum(buffer_data_val);
size_t byte_offset = 0;
size_t byte_length = buffer->length;
if (nargs > 1 && vtype(args[1]) == T_NUM) {
byte_offset = (size_t)js_getnum(args[1]);
}
if (byte_offset > buffer->length) {
return js_mkerr(js, "Start offset is outside the bounds of the buffer");
}
if (nargs > 2 && vtype(args[2]) == T_NUM) {
byte_length = (size_t)js_getnum(args[2]);
if (byte_length > buffer->length - byte_offset) {
return js_mkerr(js, "Invalid DataView length");
}
} else byte_length = buffer->length - byte_offset;
DataViewData *dv_data = ta_arena_alloc(sizeof(DataViewData));
if (!dv_data) return js_mkerr(js, "Failed to allocate DataView");
dv_data->buffer = buffer;
dv_data->byte_offset = byte_offset;
dv_data->byte_length = byte_length;
buffer->ref_count++;
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_get_ctor_proto(js, "DataView", 8);
if (is_special_object(proto)) js_set_proto_init(obj, proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_DATAVIEW));
js_set_slot(obj, SLOT_DATA, ANT_PTR(dv_data));
js_mkprop_fast(js, obj, "buffer", 6, args[0]);
js_set_descriptor(js, obj, "buffer", 6, 0);
js_set(js, obj, "byteLength", js_mknum((double)byte_length));
js_set(js, obj, "byteOffset", js_mknum((double)byte_offset));
return obj;
}
// DataView.prototype.getUint8(byteOffset)
static ant_value_t js_dataview_getUint8(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "getUint8 requires byteOffset");
ant_value_t this_val = js_getthis(js);
ant_value_t dv_data_val = js_get_slot(this_val, SLOT_DATA);
if (vtype(dv_data_val) != T_NUM) {
return js_mkerr(js, "Not a DataView");
}
DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
size_t offset = (size_t)js_getnum(args[0]);
if (offset >= dv->byte_length) {
return js_mkerr(js, "Offset out of bounds");
}
uint8_t value = dv->buffer->data[dv->byte_offset + offset];
return js_mknum((double)value);
}
// DataView.prototype.setUint8(byteOffset, value)
static ant_value_t js_dataview_setUint8(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "setUint8 requires byteOffset and value");
ant_value_t this_val = js_getthis(js);
ant_value_t dv_data_val = js_get_slot(this_val, SLOT_DATA);
if (vtype(dv_data_val) != T_NUM) {
return js_mkerr(js, "Not a DataView");
}
DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
size_t offset = (size_t)js_getnum(args[0]);
uint8_t value = (uint8_t)js_to_uint32(js_getnum(args[1]));
if (offset >= dv->byte_length) {
return js_mkerr(js, "Offset out of bounds");
}
dv->buffer->data[dv->byte_offset + offset] = value;
return js_mkundef();
}
// DataView.prototype.getInt16(byteOffset, littleEndian)
static ant_value_t js_dataview_getInt16(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "getInt16 requires byteOffset");
ant_value_t this_val = js_getthis(js);
ant_value_t dv_data_val = js_get_slot(this_val, SLOT_DATA);
if (vtype(dv_data_val) != T_NUM) {
return js_mkerr(js, "Not a DataView");
}
DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
size_t offset = (size_t)js_getnum(args[0]);
bool little_endian = (nargs > 1 && js_truthy(js, args[1]));
if (offset + 2 > dv->byte_length) {
return js_mkerr(js, "Offset out of bounds");
}
uint8_t *ptr = dv->buffer->data + dv->byte_offset + offset;
int16_t value;
if (little_endian) {
value = (int16_t)(ptr[0] | (ptr[1] << 8));
} else {
value = (int16_t)((ptr[0] << 8) | ptr[1]);
}
return js_mknum((double)value);
}
// DataView.prototype.getInt32(byteOffset, littleEndian)
static ant_value_t js_dataview_getInt32(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "getInt32 requires byteOffset");
ant_value_t this_val = js_getthis(js);
ant_value_t dv_data_val = js_get_slot(this_val, SLOT_DATA);
if (vtype(dv_data_val) != T_NUM) {
return js_mkerr(js, "Not a DataView");
}
DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
size_t offset = (size_t)js_getnum(args[0]);
bool little_endian = (nargs > 1 && js_truthy(js, args[1]));
if (offset + 4 > dv->byte_length) {
return js_mkerr(js, "Offset out of bounds");
}
uint8_t *ptr = dv->buffer->data + dv->byte_offset + offset;
int32_t value;
if (little_endian) {
value = (int32_t)(ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24));
} else {
value = (int32_t)((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]);
}
return js_mknum((double)value);
}
// DataView.prototype.getFloat32(byteOffset, littleEndian)
static ant_value_t js_dataview_getFloat32(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "getFloat32 requires byteOffset");
ant_value_t this_val = js_getthis(js);
ant_value_t dv_data_val = js_get_slot(this_val, SLOT_DATA);
if (vtype(dv_data_val) != T_NUM) {
return js_mkerr(js, "Not a DataView");
}
DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
size_t offset = (size_t)js_getnum(args[0]);
bool little_endian = (nargs > 1 && js_truthy(js, args[1]));
if (offset + 4 > dv->byte_length) {
return js_mkerr(js, "Offset out of bounds");
}
uint8_t *ptr = dv->buffer->data + dv->byte_offset + offset;
uint32_t bits;
if (little_endian) {
bits = ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24);
} else {
bits = (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
}
float value;
memcpy(&value, &bits, 4);
return js_mknum((double)value);
}
// DataView.prototype.setInt16(byteOffset, value, littleEndian)
static ant_value_t js_dataview_setInt16(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "setInt16 requires byteOffset and value");
ant_value_t this_val = js_getthis(js);
ant_value_t dv_data_val = js_get_slot(this_val, SLOT_DATA);
if (vtype(dv_data_val) != T_NUM) {
return js_mkerr(js, "Not a DataView");
}
DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
size_t offset = (size_t)js_getnum(args[0]);
int16_t value = (int16_t)js_to_int32(js_getnum(args[1]));
bool little_endian = (nargs > 2 && js_truthy(js, args[2]));
if (offset + 2 > dv->byte_length) {
return js_mkerr(js, "Offset out of bounds");
}
uint8_t *ptr = dv->buffer->data + dv->byte_offset + offset;
if (little_endian) {
ptr[0] = (uint8_t)(value & 0xFF);
ptr[1] = (uint8_t)((value >> 8) & 0xFF);
} else {
ptr[0] = (uint8_t)((value >> 8) & 0xFF);
ptr[1] = (uint8_t)(value & 0xFF);
}
return js_mkundef();
}
// DataView.prototype.setInt32(byteOffset, value, littleEndian)
static ant_value_t js_dataview_setInt32(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "setInt32 requires byteOffset and value");
ant_value_t this_val = js_getthis(js);
ant_value_t dv_data_val = js_get_slot(this_val, SLOT_DATA);
if (vtype(dv_data_val) != T_NUM) {
return js_mkerr(js, "Not a DataView");
}
DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
size_t offset = (size_t)js_getnum(args[0]);
int32_t value = js_to_int32(js_getnum(args[1]));
bool little_endian = (nargs > 2 && js_truthy(js, args[2]));
if (offset + 4 > dv->byte_length) {
return js_mkerr(js, "Offset out of bounds");
}
uint8_t *ptr = dv->buffer->data + dv->byte_offset + offset;
if (little_endian) {
ptr[0] = (uint8_t)(value & 0xFF);
ptr[1] = (uint8_t)((value >> 8) & 0xFF);
ptr[2] = (uint8_t)((value >> 16) & 0xFF);
ptr[3] = (uint8_t)((value >> 24) & 0xFF);
} else {
ptr[0] = (uint8_t)((value >> 24) & 0xFF);
ptr[1] = (uint8_t)((value >> 16) & 0xFF);
ptr[2] = (uint8_t)((value >> 8) & 0xFF);
ptr[3] = (uint8_t)(value & 0xFF);
}
return js_mkundef();
}
// DataView.prototype.setFloat32(byteOffset, value, littleEndian)
static ant_value_t js_dataview_setFloat32(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "setFloat32 requires byteOffset and value");
ant_value_t this_val = js_getthis(js);
ant_value_t dv_data_val = js_get_slot(this_val, SLOT_DATA);
if (vtype(dv_data_val) != T_NUM) {
return js_mkerr(js, "Not a DataView");
}
DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
size_t offset = (size_t)js_getnum(args[0]);
float value = (float)js_getnum(args[1]);
bool little_endian = (nargs > 2 && js_truthy(js, args[2]));
if (offset + 4 > dv->byte_length) {
return js_mkerr(js, "Offset out of bounds");
}
uint8_t *ptr = dv->buffer->data + dv->byte_offset + offset;
uint32_t bits;
memcpy(&bits, &value, 4);
if (little_endian) {
ptr[0] = (uint8_t)(bits & 0xFF);
ptr[1] = (uint8_t)((bits >> 8) & 0xFF);
ptr[2] = (uint8_t)((bits >> 16) & 0xFF);
ptr[3] = (uint8_t)((bits >> 24) & 0xFF);
} else {
ptr[0] = (uint8_t)((bits >> 24) & 0xFF);
ptr[1] = (uint8_t)((bits >> 16) & 0xFF);
ptr[2] = (uint8_t)((bits >> 8) & 0xFF);
ptr[3] = (uint8_t)(bits & 0xFF);
}
return js_mkundef();
}
// DataView.prototype.getFloat64(byteOffset, littleEndian)
static ant_value_t js_dataview_getFloat64(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "getFloat64 requires byteOffset");
ant_value_t this_val = js_getthis(js);
ant_value_t dv_data_val = js_get_slot(this_val, SLOT_DATA);
if (vtype(dv_data_val) != T_NUM) {
return js_mkerr(js, "Not a DataView");
}
DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
size_t offset = (size_t)js_getnum(args[0]);
bool little_endian = (nargs > 1 && js_truthy(js, args[1]));
if (offset + 8 > dv->byte_length) {
return js_mkerr(js, "Offset out of bounds");
}
uint8_t *ptr = dv->buffer->data + dv->byte_offset + offset;
uint64_t bits;
if (little_endian) {
bits = (uint64_t)ptr[0] | ((uint64_t)ptr[1] << 8) | ((uint64_t)ptr[2] << 16) | ((uint64_t)ptr[3] << 24) |
((uint64_t)ptr[4] << 32) | ((uint64_t)ptr[5] << 40) | ((uint64_t)ptr[6] << 48) | ((uint64_t)ptr[7] << 56);
} else {
bits = ((uint64_t)ptr[0] << 56) | ((uint64_t)ptr[1] << 48) | ((uint64_t)ptr[2] << 40) | ((uint64_t)ptr[3] << 32) |
((uint64_t)ptr[4] << 24) | ((uint64_t)ptr[5] << 16) | ((uint64_t)ptr[6] << 8) | (uint64_t)ptr[7];
}
double value;
memcpy(&value, &bits, 8);
return js_mknum(value);
}
// DataView.prototype.setFloat64(byteOffset, value, littleEndian)
static ant_value_t js_dataview_setFloat64(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "setFloat64 requires byteOffset and value");
ant_value_t this_val = js_getthis(js);
ant_value_t dv_data_val = js_get_slot(this_val, SLOT_DATA);
if (vtype(dv_data_val) != T_NUM) {
return js_mkerr(js, "Not a DataView");
}
DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val);
size_t offset = (size_t)js_getnum(args[0]);
double value = js_getnum(args[1]);
bool little_endian = (nargs > 2 && js_truthy(js, args[2]));
if (offset + 8 > dv->byte_length) {
return js_mkerr(js, "Offset out of bounds");
}
uint8_t *ptr = dv->buffer->data + dv->byte_offset + offset;
uint64_t bits;
memcpy(&bits, &value, 8);
if (little_endian) {
ptr[0] = (uint8_t)(bits & 0xFF);
ptr[1] = (uint8_t)((bits >> 8) & 0xFF);
ptr[2] = (uint8_t)((bits >> 16) & 0xFF);
ptr[3] = (uint8_t)((bits >> 24) & 0xFF);
ptr[4] = (uint8_t)((bits >> 32) & 0xFF);
ptr[5] = (uint8_t)((bits >> 40) & 0xFF);
ptr[6] = (uint8_t)((bits >> 48) & 0xFF);
ptr[7] = (uint8_t)((bits >> 56) & 0xFF);
} else {
ptr[0] = (uint8_t)((bits >> 56) & 0xFF);
ptr[1] = (uint8_t)((bits >> 48) & 0xFF);
ptr[2] = (uint8_t)((bits >> 40) & 0xFF);
ptr[3] = (uint8_t)((bits >> 32) & 0xFF);
ptr[4] = (uint8_t)((bits >> 24) & 0xFF);
ptr[5] = (uint8_t)((bits >> 16) & 0xFF);
ptr[6] = (uint8_t)((bits >> 8) & 0xFF);
ptr[7] = (uint8_t)(bits & 0xFF);
}
return js_mkundef();
}
static uint8_t *hex_decode(const char *data, size_t len, size_t *out_len) {
if (len % 2 != 0) return NULL;
size_t decoded_len = len / 2;
uint8_t *decoded = malloc(decoded_len);
if (!decoded) return NULL;
for (size_t i = 0; i < decoded_len; i++) {
unsigned int byte;
if (sscanf(data + i * 2, "%2x", &byte) != 1) {
free(decoded); return NULL;
}
decoded[i] = (uint8_t)byte;
}
*out_len = decoded_len;
return decoded;
}
typedef enum {
ENC_UTF8,
ENC_HEX,
ENC_BASE64,
ENC_ASCII,
ENC_LATIN1,
ENC_UCS2,
ENC_UNKNOWN
} BufferEncoding;
static BufferEncoding parse_encoding(const char *enc, size_t len) {
if (len == 3 && strncasecmp(enc, "hex", 3) == 0) return ENC_HEX;
if (len == 5 && strncasecmp(enc, "ascii", 5) == 0) return ENC_ASCII;
if (len == 6 && strncasecmp(enc, "base64", 6) == 0) return ENC_BASE64;
if ((len == 4 && strncasecmp(enc, "utf8", 4) == 0) || (len == 5 && strncasecmp(enc, "utf-8", 5) == 0)) return ENC_UTF8;
if ((len == 6 && strncasecmp(enc, "latin1", 6) == 0) || (len == 6 && strncasecmp(enc, "binary", 6) == 0)) return ENC_LATIN1;
if (
(len == 4 && strncasecmp(enc, "ucs2", 4) == 0) ||
(len == 5 && strncasecmp(enc, "ucs-2", 5) == 0) ||
(len == 7 && strncasecmp(enc, "utf16le", 7) == 0) ||
(len == 8 && strncasecmp(enc, "utf-16le", 8) == 0)
) return ENC_UCS2;
return ENC_UNKNOWN;
}
// Buffer.from(array/string/buffer, encoding)
static ant_value_t js_buffer_from(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "Buffer.from requires at least one argument");
if (vtype(args[0]) == T_STR) {
size_t len;
char *str = js_getstr(js, args[0], &len);
BufferEncoding encoding = ENC_UTF8;
if (nargs >= 2 && vtype(args[1]) == T_STR) {
size_t enc_len;
char *enc_str = js_getstr(js, args[1], &enc_len);
encoding = parse_encoding(enc_str, enc_len);
if (encoding == ENC_UNKNOWN) encoding = ENC_UTF8;
}
if (encoding == ENC_BASE64) {
size_t decoded_len;
uint8_t *decoded = ant_base64_decode(str, len, &decoded_len);
if (!decoded) return js_mkerr(js, "Failed to decode base64");
ArrayBufferData *buffer = create_array_buffer_data(decoded_len);
if (!buffer) { free(decoded); return js_mkerr(js, "Failed to allocate buffer"); }
memcpy(buffer->data, decoded, decoded_len);
free(decoded);
return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, decoded_len, "Buffer");
} else if (encoding == ENC_HEX) {
size_t decoded_len;
uint8_t *decoded = hex_decode(str, len, &decoded_len);
if (!decoded) return js_mkerr(js, "Failed to decode hex");
ArrayBufferData *buffer = create_array_buffer_data(decoded_len);
if (!buffer) { free(decoded); return js_mkerr(js, "Failed to allocate buffer"); }
memcpy(buffer->data, decoded, decoded_len);
free(decoded);
return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, decoded_len, "Buffer");
} else if (encoding == ENC_UCS2) {
size_t decoded_len = len * 2;
ArrayBufferData *buffer = create_array_buffer_data(decoded_len);
if (!buffer) return js_mkerr(js, "Failed to allocate buffer");
for (size_t i = 0; i < len; i++) {
buffer->data[i * 2] = (uint8_t)str[i];
buffer->data[i * 2 + 1] = 0;
}
return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, decoded_len, "Buffer");
} else {
ArrayBufferData *buffer = create_array_buffer_data(len);
if (!buffer) return js_mkerr(js, "Failed to allocate buffer");
memcpy(buffer->data, str, len);
return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, len, "Buffer");
}
}
ant_value_t length_val = js_get(js, args[0], "length");
if (vtype(length_val) == T_NUM) {
size_t len = (size_t)js_getnum(length_val);
ArrayBufferData *buffer = create_array_buffer_data(len);
if (!buffer) return js_mkerr(js, "Failed to allocate buffer");
for (size_t i = 0; i < len; i++) {
char idx_str[32];
snprintf(idx_str, sizeof(idx_str), "%zu", i);
ant_value_t elem = js_get(js, args[0], idx_str);
if (vtype(elem) == T_NUM) {
buffer->data[i] = (uint8_t)js_getnum(elem);
}
}
return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, len, "Buffer");
}
return js_mkerr(js, "Invalid argument to Buffer.from");
}
// Buffer.alloc(size)
static ant_value_t js_buffer_alloc(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) {
return js_mkerr(js, "Buffer.alloc requires a size argument");
}
size_t size = (size_t)js_getnum(args[0]);
ArrayBufferData *buffer = create_array_buffer_data(size);
if (!buffer) return js_mkerr(js, "Failed to allocate buffer");
memset(buffer->data, 0, size);
return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, size, "Buffer");
}
// Buffer.allocUnsafe(size)
static ant_value_t js_buffer_allocUnsafe(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) {
return js_mkerr(js, "Buffer.allocUnsafe requires a size argument");
}
size_t size = (size_t)js_getnum(args[0]);
ArrayBufferData *buffer = create_array_buffer_data(size);
if (!buffer) return js_mkerr(js, "Failed to allocate buffer");
return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, size, "Buffer");
}
static ant_value_t typedarray_join_with(ant_t *js, ant_value_t this_val, const char *sep, size_t sep_len) {
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data) return js_mkstr(js, "", 0);
if (!ta_data->buffer || ta_data->buffer->is_detached || ta_data->length == 0)
return js_mkstr(js, "", 0);
uint8_t *data = ta_data->buffer->data + ta_data->byte_offset;
size_t len = ta_data->length;
size_t cap = len * 12;
char *buf = malloc(cap);
if (!buf) return js_mkerr(js, "Out of memory");
size_t pos = 0;
for (size_t i = 0; i < len; i++) {
if (i > 0) {
if (pos + sep_len + 32 > cap) {
cap *= 2;
char *tmp = realloc(buf, cap);
if (!tmp) { free(buf); return js_mkerr(js, "Out of memory"); }
buf = tmp;
}
memcpy(buf + pos, sep, sep_len);
pos += sep_len;
}
if (pos + 32 > cap) {
cap *= 2;
char *tmp = realloc(buf, cap);
if (!tmp) { free(buf); return js_mkerr(js, "Out of memory"); }
buf = tmp;
}
int written = 0;
switch (ta_data->type) {
case TYPED_ARRAY_INT8: written = snprintf(buf + pos, cap - pos, "%d", ((int8_t*)data)[i]); break;
case TYPED_ARRAY_UINT8:
case TYPED_ARRAY_UINT8_CLAMPED: written = snprintf(buf + pos, cap - pos, "%u", data[i]); break;
case TYPED_ARRAY_INT16: written = snprintf(buf + pos, cap - pos, "%d", ((int16_t*)data)[i]); break;
case TYPED_ARRAY_UINT16: written = snprintf(buf + pos, cap - pos, "%u", ((uint16_t*)data)[i]); break;
case TYPED_ARRAY_INT32: written = snprintf(buf + pos, cap - pos, "%d", ((int32_t*)data)[i]); break;
case TYPED_ARRAY_UINT32: written = snprintf(buf + pos, cap - pos, "%u", ((uint32_t*)data)[i]); break;
case TYPED_ARRAY_FLOAT32: written = snprintf(buf + pos, cap - pos, "%g", (double)((float*)data)[i]); break;
case TYPED_ARRAY_FLOAT64: written = snprintf(buf + pos, cap - pos, "%g", ((double*)data)[i]); break;
case TYPED_ARRAY_BIGINT64: written = snprintf(buf + pos, cap - pos, "%lld", ((long long*)data)[i]); break;
case TYPED_ARRAY_BIGUINT64: written = snprintf(buf + pos, cap - pos, "%llu", ((unsigned long long*)data)[i]); break;
default: break;
}
if (written > 0) pos += (size_t)written;
}
ant_value_t ret = js_mkstr(js, buf, pos);
free(buf);
return ret;
}
// TypedArray.prototype.toString()
static ant_value_t js_typedarray_toString(ant_t *js, ant_value_t *args, int nargs) {
return typedarray_join_with(js, js_getthis(js), ",", 1);
}
// TypedArray.prototype.join(separator)
static ant_value_t js_typedarray_join(ant_t *js, ant_value_t *args, int nargs) {
const char *sep = ",";
size_t sep_len = 1;
if (nargs > 0 && vtype(args[0]) == T_STR)
sep = js_getstr(js, args[0], &sep_len);
return typedarray_join_with(js, js_getthis(js), sep, sep_len);
}
// Buffer.prototype.toString(encoding)
static ant_value_t js_buffer_slice(ant_t *js, ant_value_t *args, int nargs) {
return js_typedarray_subarray(js, args, nargs);
}
// Buffer.prototype.toString(encoding)
static ant_value_t js_buffer_toString(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_val = js_getthis(js);
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data) return js_mkerr(js, "Invalid Buffer");
BufferEncoding encoding = ENC_UTF8;
if (nargs > 0 && vtype(args[0]) == T_STR) {
size_t enc_len;
char *enc_str = js_getstr(js, args[0], &enc_len);
encoding = parse_encoding(enc_str, enc_len);
if (encoding == ENC_UNKNOWN) encoding = ENC_UTF8;
}
uint8_t *data = ta_data->buffer->data + ta_data->byte_offset;
size_t len = ta_data->byte_length;
if (encoding == ENC_BASE64) {
size_t out_len;
char *encoded = ant_base64_encode(data, len, &out_len);
if (!encoded) return js_mkerr(js, "Failed to encode base64");
ant_value_t result = js_mkstr(js, encoded, out_len);
free(encoded);
return result;
} else if (encoding == ENC_HEX) {
char *hex = malloc(len * 2 + 1);
if (!hex) return js_mkerr(js, "Failed to allocate hex string");
for (size_t i = 0; i < len; i++) {
snprintf(hex + i * 2, 3, "%02x", data[i]);
}
ant_value_t result = js_mkstr(js, hex, len * 2);
free(hex);
return result;
} else if (encoding == ENC_UCS2) {
size_t char_count = len / 2;
char *str = malloc(char_count + 1);
if (!str) return js_mkerr(js, "Failed to allocate string");
for (size_t i = 0; i < char_count; i++) str[i] = (char)data[i * 2];
str[char_count] = '\0';
ant_value_t result = js_mkstr(js, str, char_count);
free(str);
return result;
} else return js_mkstr(js, (char *)data, len);
}
// Buffer.prototype.toBase64()
static ant_value_t js_buffer_toBase64(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
ant_value_t encoding_arg = js_mkstr(js, "base64", 6);
ant_value_t new_args[1] = {encoding_arg};
return js_buffer_toString(js, new_args, 1);
}
// Buffer.prototype.write(string, offset, length, encoding)
static ant_value_t js_buffer_write(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "write requires a string");
ant_value_t this_val = js_getthis(js);
ant_value_t ta_data_val = js_get_slot(this_val, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
if (!ta_data) return js_mkerr(js, "Invalid Buffer");
size_t str_len;
char *str = js_getstr(js, args[0], &str_len);
size_t offset = 0;
size_t length = ta_data->byte_length;
if (nargs > 1 && vtype(args[1]) == T_NUM) {
offset = (size_t)js_getnum(args[1]);
}
if (nargs > 2 && vtype(args[2]) == T_NUM) {
length = (size_t)js_getnum(args[2]);
}
if (offset >= ta_data->byte_length) {
return js_mknum(0);
}
size_t available = ta_data->byte_length - offset;
size_t to_write = (str_len < length) ? str_len : length;
to_write = (to_write < available) ? to_write : available;
memcpy(ta_data->buffer->data + ta_data->byte_offset + offset, str, to_write);
return js_mknum((double)to_write);
}
// Buffer.isBuffer(obj)
static ant_value_t js_buffer_isBuffer(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_false;
if (!is_special_object(args[0])) return js_false;
ant_value_t proto = js_get_proto(js, args[0]);
ant_value_t buffer_proto = js_get_ctor_proto(js, "Buffer", 6);
return js_bool(proto == buffer_proto);
}
// Buffer.isEncoding(encoding)
static ant_value_t js_buffer_isEncoding(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1 || vtype(args[0]) != T_STR) return js_false;
size_t len;
char *enc = js_getstr(js, args[0], &len);
if ((len == 4 && strncasecmp(enc, "utf8", 4) == 0) ||
(len == 5 && strncasecmp(enc, "utf-8", 5) == 0) ||
(len == 3 && strncasecmp(enc, "hex", 3) == 0) ||
(len == 6 && strncasecmp(enc, "base64", 6) == 0) ||
(len == 5 && strncasecmp(enc, "ascii", 5) == 0) ||
(len == 6 && strncasecmp(enc, "latin1", 6) == 0) ||
(len == 6 && strncasecmp(enc, "binary", 6) == 0) ||
(len == 4 && strncasecmp(enc, "ucs2", 4) == 0) ||
(len == 5 && strncasecmp(enc, "ucs-2", 5) == 0) ||
(len == 7 && strncasecmp(enc, "utf16le", 7) == 0) ||
(len == 8 && strncasecmp(enc, "utf-16le", 8) == 0)) {
return js_true;
}
return js_false;
}
// Buffer.byteLength(string, encoding)
static ant_value_t js_buffer_byteLength(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mknum(0);
ant_value_t arg = args[0];
if (is_special_object(arg)) {
ant_value_t bytelen = js_get(js, arg, "byteLength");
if (vtype(bytelen) == T_NUM) return bytelen;
ant_value_t len = js_get(js, arg, "length");
if (vtype(len) == T_NUM) return len;
}
if (vtype(arg) == T_STR) {
size_t len;
js_getstr(js, arg, &len);
return js_mknum((double)len);
}
return js_mknum(0);
}
// Buffer.concat(list, totalLength)
static ant_value_t js_buffer_concat(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1 || !is_special_object(args[0])) {
return js_mkerr(js, "First argument must be an array");
}
ant_value_t list = args[0];
ant_value_t len_val = js_get(js, list, "length");
if (vtype(len_val) != T_NUM) {
return js_mkerr(js, "First argument must be an array");
}
size_t list_len = (size_t)js_getnum(len_val);
size_t total_length = 0;
if (nargs > 1 && vtype(args[1]) == T_NUM) {
total_length = (size_t)js_getnum(args[1]);
} else {
for (size_t i = 0; i < list_len; i++) {
char idx[16];
snprintf(idx, sizeof(idx), "%zu", i);
ant_value_t buf = js_get(js, list, idx);
ant_value_t buf_len = js_get(js, buf, "length");
if (vtype(buf_len) == T_NUM) total_length += (size_t)js_getnum(buf_len);
}
}
ArrayBufferData *buffer = create_array_buffer_data(total_length);
if (!buffer) return js_mkerr(js, "Failed to allocate buffer");
size_t offset = 0;
for (size_t i = 0; i < list_len && offset < total_length; i++) {
char idx[16];
snprintf(idx, sizeof(idx), "%zu", i);
ant_value_t buf = js_get(js, list, idx);
ant_value_t ta_data_val = js_get_slot(buf, SLOT_BUFFER);
TypedArrayData *ta = js_gettypedarray(ta_data_val);
if (!ta || !ta->buffer) continue;
size_t copy_len = ta->byte_length;
if (offset + copy_len > total_length) {
copy_len = total_length - offset;
}
memcpy(buffer->data + offset, ta->buffer->data + ta->byte_offset, copy_len);
offset += copy_len;
}
return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, total_length, "Buffer");
}
// Buffer.compare(buf1, buf2)
static ant_value_t js_buffer_compare(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "Buffer.compare requires two arguments");
ant_value_t ta1_val = js_get_slot(args[0], SLOT_BUFFER);
ant_value_t ta2_val = js_get_slot(args[1], SLOT_BUFFER);
TypedArrayData *ta1 = js_gettypedarray(ta1_val);
TypedArrayData *ta2 = js_gettypedarray(ta2_val);
if (!ta1 || !ta2) {
return js_mkerr(js, "Arguments must be Buffers");
}
if (!ta1 || !ta1->buffer || !ta2 || !ta2->buffer) {
return js_mkerr(js, "Invalid buffer");
}
size_t len = ta1->byte_length < ta2->byte_length ? ta1->byte_length : ta2->byte_length;
int cmp = memcmp(ta1->buffer->data + ta1->byte_offset, ta2->buffer->data + ta2->byte_offset, len);
if (cmp == 0) {
if (ta1->byte_length < ta2->byte_length) cmp = -1;
else if (ta1->byte_length > ta2->byte_length) cmp = 1;
} else cmp = cmp < 0 ? -1 : 1;
return js_mknum((double)cmp);
}
static ant_value_t js_sharedarraybuffer_constructor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF) {
return js_mkerr_typed(js, JS_ERR_TYPE, "SharedArrayBuffer constructor requires 'new'");
}
size_t length = 0;
if (nargs > 0 && vtype(args[0]) == T_NUM) {
length = (size_t)js_getnum(args[0]);
}
ArrayBufferData *data = create_shared_array_buffer_data(length);
if (!data) {
return js_mkerr(js, "Failed to allocate SharedArrayBuffer");
}
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_get_ctor_proto(js, "SharedArrayBuffer", 17);
if (is_special_object(proto)) js_set_proto_init(obj, proto);
js_set_slot(obj, SLOT_BUFFER, ANT_PTR(data));
js_set(js, obj, "byteLength", js_mknum((double)length));
return obj;
}
ant_value_t buffer_library(ant_t *js) {
return js_get(js, js_glob(js), "Buffer");
}
void init_buffer_module() {
ant_t *js = rt->js;
ant_value_t glob = js->global;
ant_value_t object_proto = js->object;
ant_value_t arraybuffer_ctor_obj = js_mkobj(js);
ant_value_t arraybuffer_proto = js_mkobj(js);
js_set_proto_init(arraybuffer_proto, object_proto);
js_set(js, arraybuffer_proto, "slice", js_mkfun(js_arraybuffer_slice));
js_set(js, arraybuffer_proto, "transfer", js_mkfun(js_arraybuffer_transfer));
js_set(js, arraybuffer_proto, "transferToFixedLength", js_mkfun(js_arraybuffer_transferToFixedLength));
js_set_getter_desc(js, arraybuffer_proto, "detached", 8, js_mkfun(js_arraybuffer_detached_getter), JS_DESC_E);
js_set_sym(js, arraybuffer_proto, get_toStringTag_sym(), js_mkstr(js, "ArrayBuffer", 11));
js_set_slot(arraybuffer_ctor_obj, SLOT_CFUNC, js_mkfun(js_arraybuffer_constructor));
js_mkprop_fast(js, arraybuffer_ctor_obj, "prototype", 9, arraybuffer_proto);
js_mkprop_fast(js, arraybuffer_ctor_obj, "name", 4, ANT_STRING("ArrayBuffer"));
js_set_descriptor(js, arraybuffer_ctor_obj, "name", 4, 0);
js_define_species_getter(js, arraybuffer_ctor_obj);
ant_value_t arraybuffer_ctor = js_obj_to_func(arraybuffer_ctor_obj);
js_set(js, arraybuffer_proto, "constructor", arraybuffer_ctor);
js_set_descriptor(js, arraybuffer_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_set(js, glob, "ArrayBuffer", arraybuffer_ctor);
ant_value_t typedarray_proto = js_mkobj(js);
js_set_proto_init(typedarray_proto, object_proto);
js_set(js, typedarray_proto, "at", js_mkfun(js_typedarray_at));
js_set(js, typedarray_proto, "set", js_mkfun(js_typedarray_set));
js_set(js, typedarray_proto, "copyWithin", js_mkfun(js_typedarray_copyWithin));
js_set(js, typedarray_proto, "slice", js_mkfun(js_typedarray_slice));
js_set(js, typedarray_proto, "subarray", js_mkfun(js_typedarray_subarray));
js_set(js, typedarray_proto, "fill", js_mkfun(js_typedarray_fill));
js_set(js, typedarray_proto, "toReversed", js_mkfun(js_typedarray_toReversed));
js_set(js, typedarray_proto, "toSorted", js_mkfun(js_typedarray_toSorted));
js_set(js, typedarray_proto, "with", js_mkfun(js_typedarray_with));
js_set(js, typedarray_proto, "toString", js_mkfun(js_typedarray_toString));
js_set(js, typedarray_proto, "join", js_mkfun(js_typedarray_join));
js_set_sym(js, typedarray_proto, get_toStringTag_sym(), js_mkstr(js, "TypedArray", 10));
g_typedarray_iter_proto = js_mkobj(js);
js_set_proto_init(g_typedarray_iter_proto, js->sym.iterator_proto);
js_set(js, g_typedarray_iter_proto, "next", js_mkfun(ta_iter_next));
js_iter_register_advance(g_typedarray_iter_proto, advance_typedarray);
ant_value_t ta_values_fn = js_mkfun(ta_values);
js_set(js, typedarray_proto, "values", ta_values_fn);
js_set(js, typedarray_proto, "keys", js_mkfun(ta_keys));
js_set(js, typedarray_proto, "entries", js_mkfun(ta_entries));
js_set_sym(js, typedarray_proto, get_iterator_sym(), ta_values_fn);
#define SETUP_TYPEDARRAY(name) \
do { \
ant_value_t name##_ctor_obj = js_mkobj(js); \
ant_value_t name##_proto = js_mkobj(js); \
js_set_proto_init(name##_proto, typedarray_proto); \
js_set_sym(js, name##_proto, get_toStringTag_sym(), js_mkstr(js, #name, sizeof(#name) - 1)); \
js_set_slot(name##_ctor_obj, SLOT_CFUNC, js_mkfun(js_##name##_constructor)); \
js_setprop(js, name##_ctor_obj, js_mkstr(js, "prototype", 9), name##_proto); \
js_mkprop_fast(js, name##_ctor_obj, "name", 4, ANT_STRING(#name)); \
js_set_descriptor(js, name##_ctor_obj, "name", 4, 0); \
js_define_species_getter(js, name##_ctor_obj); \
ant_value_t name##_ctor = js_obj_to_func(name##_ctor_obj); \
js_setprop(js, name##_proto, ANT_STRING("constructor"), name##_ctor); \
js_set_descriptor(js, name##_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); \
js_set(js, glob, #name, name##_ctor); \
} while(0)
SETUP_TYPEDARRAY(Int8Array);
SETUP_TYPEDARRAY(Uint8Array);
SETUP_TYPEDARRAY(Uint8ClampedArray);
SETUP_TYPEDARRAY(Int16Array);
SETUP_TYPEDARRAY(Uint16Array);
SETUP_TYPEDARRAY(Int32Array);
SETUP_TYPEDARRAY(Uint32Array);
SETUP_TYPEDARRAY(Float32Array);
SETUP_TYPEDARRAY(Float64Array);
SETUP_TYPEDARRAY(BigInt64Array);
SETUP_TYPEDARRAY(BigUint64Array);
ant_value_t dataview_ctor_obj = js_mkobj(js);
ant_value_t dataview_proto = js_mkobj(js);
js_set_proto_init(dataview_proto, object_proto);
js_set(js, dataview_proto, "getUint8", js_mkfun(js_dataview_getUint8));
js_set(js, dataview_proto, "setUint8", js_mkfun(js_dataview_setUint8));
js_set(js, dataview_proto, "getInt16", js_mkfun(js_dataview_getInt16));
js_set(js, dataview_proto, "setInt16", js_mkfun(js_dataview_setInt16));
js_set(js, dataview_proto, "getInt32", js_mkfun(js_dataview_getInt32));
js_set(js, dataview_proto, "setInt32", js_mkfun(js_dataview_setInt32));
js_set(js, dataview_proto, "getFloat32", js_mkfun(js_dataview_getFloat32));
js_set(js, dataview_proto, "setFloat32", js_mkfun(js_dataview_setFloat32));
js_set(js, dataview_proto, "getFloat64", js_mkfun(js_dataview_getFloat64));
js_set(js, dataview_proto, "setFloat64", js_mkfun(js_dataview_setFloat64));
js_set_sym(js, dataview_proto, get_toStringTag_sym(), js_mkstr(js, "DataView", 8));
js_set_slot(dataview_ctor_obj, SLOT_CFUNC, js_mkfun(js_dataview_constructor));
js_mkprop_fast(js, dataview_ctor_obj, "prototype", 9, dataview_proto);
js_mkprop_fast(js, dataview_ctor_obj, "name", 4, ANT_STRING("DataView"));
js_set_descriptor(js, dataview_ctor_obj, "name", 4, 0);
ant_value_t dataview_ctor = js_obj_to_func(dataview_ctor_obj);
js_set(js, dataview_proto, "constructor", dataview_ctor);
js_set_descriptor(js, dataview_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_set(js, glob, "DataView", dataview_ctor);
ant_value_t sharedarraybuffer_ctor_obj = js_mkobj(js);
ant_value_t sharedarraybuffer_proto = js_mkobj(js);
js_set_proto_init(sharedarraybuffer_proto, object_proto);
js_set(js, sharedarraybuffer_proto, "slice", js_mkfun(js_arraybuffer_slice));
js_set_sym(js, sharedarraybuffer_proto, get_toStringTag_sym(), js_mkstr(js, "SharedArrayBuffer", 17));
js_set_slot(sharedarraybuffer_ctor_obj, SLOT_CFUNC, js_mkfun(js_sharedarraybuffer_constructor));
js_mkprop_fast(js, sharedarraybuffer_ctor_obj, "prototype", 9, sharedarraybuffer_proto);
js_mkprop_fast(js, sharedarraybuffer_ctor_obj, "name", 4, ANT_STRING("SharedArrayBuffer"));
js_set_descriptor(js, sharedarraybuffer_ctor_obj, "name", 4, 0);
ant_value_t sharedarraybuffer_ctor = js_obj_to_func(sharedarraybuffer_ctor_obj);
js_set(js, sharedarraybuffer_proto, "constructor", sharedarraybuffer_ctor);
js_set_descriptor(js, sharedarraybuffer_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_set(js, glob, "SharedArrayBuffer", sharedarraybuffer_ctor);
ant_value_t buffer_ctor_obj = js_mkobj(js);
ant_value_t buffer_proto = js_mkobj(js);
ant_value_t uint8array_ctor = js_get(js, glob, "Uint8Array");
ant_value_t uint8array_proto = js_get(js, uint8array_ctor, "prototype");
if (is_special_object(uint8array_proto)) js_set_proto_init(buffer_proto, uint8array_proto);
else js_set_proto_init(buffer_proto, typedarray_proto);
js_set(js, buffer_proto, "slice", js_mkfun(js_buffer_slice));
js_set(js, buffer_proto, "toString", js_mkfun(js_buffer_toString));
js_set(js, buffer_proto, "toBase64", js_mkfun(js_buffer_toBase64));
js_set(js, buffer_proto, "write", js_mkfun(js_buffer_write));
js_set_sym(js, buffer_proto, get_toStringTag_sym(), js_mkstr(js, "Buffer", 6));
js_set_sym(js, buffer_proto, get_iterator_sym(), ta_values_fn);
js_set(js, buffer_proto, "values", ta_values_fn);
js_set(js, buffer_ctor_obj, "from", js_mkfun(js_buffer_from));
js_set(js, buffer_ctor_obj, "alloc", js_mkfun(js_buffer_alloc));
js_set(js, buffer_ctor_obj, "allocUnsafe", js_mkfun(js_buffer_allocUnsafe));
js_set(js, buffer_ctor_obj, "isBuffer", js_mkfun(js_buffer_isBuffer));
js_set(js, buffer_ctor_obj, "isEncoding", js_mkfun(js_buffer_isEncoding));
js_set(js, buffer_ctor_obj, "byteLength", js_mkfun(js_buffer_byteLength));
js_set(js, buffer_ctor_obj, "concat", js_mkfun(js_buffer_concat));
js_set(js, buffer_ctor_obj, "compare", js_mkfun(js_buffer_compare));
js_set_slot(buffer_ctor_obj, SLOT_CFUNC, js_mkfun(js_buffer_from));
js_mkprop_fast(js, buffer_ctor_obj, "prototype", 9, buffer_proto);
js_mkprop_fast(js, buffer_ctor_obj, "name", 4, ANT_STRING("Buffer"));
js_set_descriptor(js, buffer_ctor_obj, "name", 4, 0);
ant_value_t buffer_ctor = js_obj_to_func(buffer_ctor_obj);
js_set(js, buffer_proto, "constructor", buffer_ctor);
js_set_descriptor(js, buffer_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_set(js, glob, "Buffer", buffer_ctor);
}
void cleanup_buffer_module(void) {
if (buffer_registry) {
for (size_t i = 0; i < buffer_registry_count; i++) {
if (buffer_registry[i]) free(buffer_registry[i]);
}
free(buffer_registry);
buffer_registry = NULL;
buffer_registry_count = 0;
buffer_registry_cap = 0;
}
ta_arena_offset = 0;
}
size_t buffer_get_external_memory(void) {
size_t total = ta_arena ? ta_arena_offset : 0;
for (size_t i = 0; i < buffer_registry_count; i++) {
if (buffer_registry[i])
total += sizeof(ArrayBufferData) + buffer_registry[i]->capacity;
}
total += buffer_registry_cap * sizeof(ArrayBufferData *);
return total;
}
diff --git a/src/modules/formdata.c b/src/modules/formdata.c
index 6ac4917..bba1ad0 100644
--- a/src/modules/formdata.c
+++ b/src/modules/formdata.c
@@ -1,468 +1,512 @@
#include <stdlib.h>
#include <string.h>
+#include <strings.h>
#include <stdint.h>
#include <time.h>
#include "ant.h"
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "descriptors.h"
#include "silver/engine.h"
#include "modules/formdata.h"
#include "modules/blob.h"
#include "modules/symbol.h"
-typedef struct fd_entry {
- char *name;
- bool is_file;
- char *str_value;
- size_t val_idx;
- struct fd_entry *next;
-} fd_entry_t;
-
-typedef struct {
- fd_entry_t *head;
- fd_entry_t **tail;
- size_t count;
-} fd_data_t;
-
typedef struct {
size_t index;
int kind;
} fd_iter_t;
enum {
FD_ITER_ENTRIES = 0,
FD_ITER_KEYS = 1,
FD_ITER_VALUES = 2
};
static ant_value_t g_formdata_proto = 0;
static ant_value_t g_formdata_iter_proto = 0;
+static fd_data_t *get_fd_data(ant_value_t obj);
+
+bool formdata_is_formdata(ant_t *js, ant_value_t obj) {
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ return vtype(brand) == T_NUM && (int)js_getnum(brand) == BRAND_FORMDATA;
+}
+
+bool formdata_is_empty(ant_value_t fd) {
+ fd_data_t *d = get_fd_data(fd);
+ return d ? d->count == 0 : true;
+}
static fd_data_t *fd_data_new(void) {
fd_data_t *d = calloc(1, sizeof(fd_data_t));
if (!d) return NULL;
d->tail = &d->head;
return d;
}
static void fd_entry_free(fd_entry_t *e) {
if (!e) return;
free(e->name);
free(e->str_value);
free(e);
}
static void fd_data_free(fd_data_t *d) {
if (!d) return;
for (fd_entry_t *e = d->head; e; ) {
fd_entry_t *n = e->next;
fd_entry_free(e);
e = n;
}
free(d);
}
static fd_data_t *get_fd_data(ant_value_t obj) {
ant_value_t slot = js_get_slot(obj, SLOT_DATA);
if (vtype(slot) != T_NUM) return NULL;
return (fd_data_t *)(uintptr_t)(size_t)js_getnum(slot);
}
static ant_value_t get_fd_values(ant_value_t obj) {
return js_get_slot(obj, SLOT_ENTRIES);
}
static void formdata_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
fd_data_t *d = (fd_data_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
fd_data_free(d);
return;
}}
}
static bool fd_append_str(fd_data_t *d, const char *name, const char *value) {
fd_entry_t *e = calloc(1, sizeof(fd_entry_t));
if (!e) return false;
e->name = strdup(name);
e->str_value = strdup(value);
if (!e->name || !e->str_value) { fd_entry_free(e); return false; }
*d->tail = e;
d->tail = &e->next;
d->count++;
return true;
}
static bool fd_append_file(fd_data_t *d, const char *name, size_t val_idx) {
fd_entry_t *e = calloc(1, sizeof(fd_entry_t));
if (!e) return false;
e->is_file = true;
e->name = strdup(name);
e->val_idx = val_idx;
if (!e->name) { free(e); return false; }
*d->tail = e;
d->tail = &e->next;
d->count++;
return true;
}
static void fd_delete_name(fd_data_t *d, const char *name) {
fd_entry_t **pp = &d->head;
d->tail = &d->head;
while (*pp) {
if (strcmp((*pp)->name, name) == 0) {
fd_entry_t *dead = *pp;
*pp = dead->next;
d->count--;
fd_entry_free(dead);
} else {
d->tail = &(*pp)->next;
pp = &(*pp)->next;
}}
}
+ant_value_t formdata_create_empty(ant_t *js) {
+ fd_data_t *d = fd_data_new();
+ if (!d) return js_mkerr(js, "out of memory");
+
+ ant_value_t obj = js_mkobj(js);
+ js_set_proto_init(obj, g_formdata_proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_FORMDATA));
+ js_set_slot(obj, SLOT_DATA, ANT_PTR(d));
+
+ ant_value_t vals = js_mkarr(js);
+ js_set_slot_wb(js, obj, SLOT_ENTRIES, vals);
+ js_set_finalizer(obj, formdata_finalize);
+
+ return obj;
+}
+
static ant_value_t entry_to_js_value(ant_t *js, ant_value_t values_arr, fd_entry_t *e) {
if (!e->is_file)
return js_mkstr(js, e->str_value ? e->str_value : "", strlen(e->str_value ? e->str_value : ""));
return js_arr_get(js, values_arr, (ant_offset_t)e->val_idx);
}
static const char *resolve_name(ant_t *js, ant_value_t *name_v) {
if (vtype(*name_v) != T_STR) {
*name_v = js_tostring_val(js, *name_v);
if (is_err(*name_v)) return NULL;
}
return js_getstr(js, *name_v, NULL);
}
static ant_value_t extract_file_entry(
ant_t *js, fd_data_t *d, ant_value_t values_arr,
const char *name, ant_value_t val, ant_value_t filename_v, bool is_set
) {
blob_data_t *bd = blob_get_data(val);
if (!bd) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData value must be a string, Blob, or File");
bool is_file = (bd->name != NULL);
bool no_filename_override = (vtype(filename_v) == T_UNDEF);
ant_value_t stored_val;
if (is_file && no_filename_override) {
stored_val = val;
} else {
const char *fname = NULL;
char *fname_owned = NULL;
if (!no_filename_override) {
ant_value_t fv = filename_v;
if (vtype(fv) != T_STR) { fv = js_tostring_val(js, fv); if (is_err(fv)) return fv; }
fname_owned = strdup(js_getstr(js, fv, NULL));
fname = fname_owned;
}
else if (bd->name && bd->name[0]) fname = bd->name;
else fname = "blob";
int64_t last_modified = bd->last_modified;
if (!last_modified) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
last_modified = (int64_t)ts.tv_sec * 1000LL + (int64_t)(ts.tv_nsec / 1000000);
}
ant_value_t file_obj = blob_create(js, bd->data, bd->size, bd->type);
if (is_err(file_obj)) { free(fname_owned); return file_obj; }
blob_data_t *nbd = blob_get_data(file_obj);
if (nbd) {
nbd->name = strdup(fname);
nbd->last_modified = last_modified;
}
free(fname_owned);
js_set_proto_init(file_obj, g_file_proto);
stored_val = file_obj;
}
if (is_set) fd_delete_name(d, name);
size_t idx = (size_t)js_arr_len(js, values_arr);
js_arr_push(js, values_arr, stored_val);
return fd_append_file(d, name, idx) ? js_mkundef() : js_mkerr(js, "out of memory");
}
+ant_value_t formdata_append_string(ant_t *js, ant_value_t fd, ant_value_t name_v, ant_value_t value_v) {
+ fd_data_t *d = get_fd_data(fd);
+ if (!d) return js_mkerr(js, "Invalid FormData object");
+
+ const char *name = resolve_name(js, &name_v);
+ if (!name) return name_v;
+
+ if (vtype(value_v) != T_STR) {
+ value_v = js_tostring_val(js, value_v);
+ if (is_err(value_v)) return value_v;
+ }
+
+ return fd_append_str(d, name, js_getstr(js, value_v, NULL))
+ ? js_mkundef()
+ : js_mkerr(js, "out of memory"
+ );
+}
+
+ant_value_t formdata_append_file(ant_t *js, ant_value_t fd, ant_value_t name_v, ant_value_t blob_v, ant_value_t filename_v) {
+ fd_data_t *d = get_fd_data(fd);
+ if (!d) return js_mkerr(js, "Invalid FormData object");
+ const char *name = resolve_name(js, &name_v);
+ if (!name) return name_v;
+ ant_value_t values_arr = get_fd_values(fd);
+ return extract_file_entry(js, d, values_arr, name, blob_v, filename_v, false);
+}
+
static ant_value_t js_formdata_append(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData.append requires 2 arguments");
fd_data_t *d = get_fd_data(js->this_val);
if (!d) return js_mkerr(js, "Invalid FormData object");
ant_value_t name_v = args[0];
const char *name = resolve_name(js, &name_v);
if (!name) return name_v;
ant_value_t val = args[1];
if (is_object_type(val) && blob_get_data(val)) {
ant_value_t fname = (nargs >= 3) ? args[2] : js_mkundef();
ant_value_t values_arr = get_fd_values(js->this_val);
return extract_file_entry(js, d, values_arr, name, val, fname, false);
}
if (vtype(val) != T_STR) { val = js_tostring_val(js, val); if (is_err(val)) return val; }
return fd_append_str(d, name, js_getstr(js, val, NULL)) ? js_mkundef() : js_mkerr(js, "out of memory");
}
static ant_value_t js_formdata_set(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData.set requires 2 arguments");
fd_data_t *d = get_fd_data(js->this_val);
if (!d) return js_mkerr(js, "Invalid FormData object");
ant_value_t name_v = args[0];
const char *name = resolve_name(js, &name_v);
if (!name) return name_v;
ant_value_t val = args[1];
if (is_object_type(val) && blob_get_data(val)) {
ant_value_t fname = (nargs >= 3) ? args[2] : js_mkundef();
ant_value_t values_arr = get_fd_values(js->this_val);
return extract_file_entry(js, d, values_arr, name, val, fname, true);
}
if (vtype(val) != T_STR) { val = js_tostring_val(js, val); if (is_err(val)) return val; }
fd_delete_name(d, name);
return fd_append_str(d, name, js_getstr(js, val, NULL)) ? js_mkundef() : js_mkerr(js, "out of memory");
}
static ant_value_t js_formdata_get(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mknull();
fd_data_t *d = get_fd_data(js->this_val);
if (!d) return js_mknull();
ant_value_t name_v = args[0];
const char *name = resolve_name(js, &name_v);
if (!name) return name_v;
ant_value_t values_arr = get_fd_values(js->this_val);
for (fd_entry_t *e = d->head; e; e = e->next) {
if (strcmp(e->name, name) == 0)
return entry_to_js_value(js, values_arr, e);
}
return js_mknull();
}
static ant_value_t js_formdata_get_all(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t result = js_mkarr(js);
if (nargs < 1) return result;
fd_data_t *d = get_fd_data(js->this_val);
if (!d) return result;
ant_value_t name_v = args[0];
const char *name = resolve_name(js, &name_v);
if (!name) return name_v;
ant_value_t values_arr = get_fd_values(js->this_val);
for (fd_entry_t *e = d->head; e; e = e->next) {
if (strcmp(e->name, name) == 0) {
ant_value_t v = entry_to_js_value(js, values_arr, e);
if (is_err(v)) return v;
js_arr_push(js, result, v);
}}
return result;
}
static ant_value_t js_formdata_has(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_false;
fd_data_t *d = get_fd_data(js->this_val);
if (!d) return js_false;
ant_value_t name_v = args[0];
const char *name = resolve_name(js, &name_v);
if (!name) return name_v;
for (fd_entry_t *e = d->head; e; e = e->next) {
if (strcmp(e->name, name) == 0) return js_true;
}
return js_false;
}
static ant_value_t js_formdata_delete(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
fd_data_t *d = get_fd_data(js->this_val);
if (!d) return js_mkundef();
ant_value_t name_v = args[0];
const char *name = resolve_name(js, &name_v);
if (!name) return name_v;
fd_delete_name(d, name);
return js_mkundef();
}
static ant_value_t js_formdata_foreach(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1 || !is_callable(args[0]))
return js_mkerr_typed(js, JS_ERR_TYPE, "FormData.forEach requires a function");
fd_data_t *d = get_fd_data(js->this_val);
if (!d) return js_mkundef();
ant_value_t fn = args[0];
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_value_t self = js->this_val;
ant_value_t values_arr = get_fd_values(self);
for (fd_entry_t *e = d->head; e; e = e->next) {
ant_value_t val = entry_to_js_value(js, values_arr, e);
if (is_err(val)) return val;
ant_value_t name = js_mkstr(js, e->name, strlen(e->name));
ant_value_t cb_args[3] = { val, name, self };
ant_value_t r = sv_vm_call(js->vm, js, fn, this_arg, cb_args, 3, NULL, false);
if (is_err(r)) return r;
}
return js_mkundef();
}
static ant_value_t formdata_iter_next(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state_v = js_get_slot(js->this_val, SLOT_ITER_STATE);
if (vtype(state_v) != T_NUM) return js_iter_result(js, false, js_mkundef());
fd_iter_t *st = (fd_iter_t *)(uintptr_t)(size_t)js_getnum(state_v);
ant_value_t fd_obj = js_get_slot(js->this_val, SLOT_DATA);
fd_data_t *d = get_fd_data(fd_obj);
if (!d) return js_iter_result(js, false, js_mkundef());
ant_value_t values_arr = get_fd_values(fd_obj);
size_t idx = 0;
for (fd_entry_t *e = d->head; e; e = e->next, idx++) {
if (idx == st->index) {
st->index++;
ant_value_t out;
switch (st->kind) {
case FD_ITER_KEYS:
out = js_mkstr(js, e->name, strlen(e->name));
break;
case FD_ITER_VALUES:
out = entry_to_js_value(js, values_arr, e);
break;
default: {
ant_value_t v = entry_to_js_value(js, values_arr, e);
if (is_err(v)) return v;
out = js_mkarr(js);
js_arr_push(js, out, js_mkstr(js, e->name, strlen(e->name)));
js_arr_push(js, out, v);
break;
}}
if (is_err(out)) return out;
return js_iter_result(js, true, out);
}}
return js_iter_result(js, false, js_mkundef());
}
static void formdata_iter_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_ITER_STATE && vtype(entries[i].value) == T_NUM) {
fd_iter_t *st = (fd_iter_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
free(st);
return;
}}
}
static ant_value_t make_formdata_iter(ant_t *js, ant_value_t fd_obj, int kind) {
fd_iter_t *st = calloc(1, sizeof(fd_iter_t));
if (!st) return js_mkerr(js, "out of memory");
st->kind = kind;
ant_value_t iter = js_mkobj(js);
js_set_proto_init(iter, g_formdata_iter_proto);
js_set_slot(iter, SLOT_ITER_STATE, ANT_PTR(st));
js_set_slot_wb(js, iter, SLOT_DATA, fd_obj);
js_set_finalizer(iter, formdata_iter_finalize);
return iter;
}
static ant_value_t js_formdata_entries(ant_t *js, ant_value_t *args, int nargs) {
return make_formdata_iter(js, js->this_val, FD_ITER_ENTRIES);
}
static ant_value_t js_formdata_keys(ant_t *js, ant_value_t *args, int nargs) {
return make_formdata_iter(js, js->this_val, FD_ITER_KEYS);
}
static ant_value_t js_formdata_values(ant_t *js, ant_value_t *args, int nargs) {
return make_formdata_iter(js, js->this_val, FD_ITER_VALUES);
}
static ant_value_t js_formdata_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "FormData constructor requires 'new'");
if (nargs >= 1 && vtype(args[0]) != T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "FormData does not support a form element argument");
fd_data_t *d = fd_data_new();
if (!d) return js_mkerr(js, "out of memory");
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_formdata_proto);
+
if (is_object_type(proto)) js_set_proto_init(obj, proto);
-
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_FORMDATA));
js_set_slot(obj, SLOT_DATA, ANT_PTR(d));
+
ant_value_t vals = js_mkarr(js);
js_set_slot_wb(js, obj, SLOT_ENTRIES, vals);
js_set_finalizer(obj, formdata_finalize);
+
return obj;
}
void init_formdata_module(void) {
ant_t *js = rt->js;
ant_value_t g = js_glob(js);
g_formdata_proto = js_mkobj(js);
js_set(js, g_formdata_proto, "append", js_mkfun(js_formdata_append));
js_set(js, g_formdata_proto, "set", js_mkfun(js_formdata_set));
js_set(js, g_formdata_proto, "get", js_mkfun(js_formdata_get));
js_set(js, g_formdata_proto, "getAll", js_mkfun(js_formdata_get_all));
js_set(js, g_formdata_proto, "has", js_mkfun(js_formdata_has));
js_set(js, g_formdata_proto, "delete", js_mkfun(js_formdata_delete));
js_set(js, g_formdata_proto, "forEach", js_mkfun(js_formdata_foreach));
js_set(js, g_formdata_proto, "entries", js_mkfun(js_formdata_entries));
js_set(js, g_formdata_proto, "keys", js_mkfun(js_formdata_keys));
js_set(js, g_formdata_proto, "values", js_mkfun(js_formdata_values));
js_set_sym(js, g_formdata_proto, get_iterator_sym(), js_mkfun(js_formdata_entries));
js_set_sym(js, g_formdata_proto, get_toStringTag_sym(), js_mkstr(js, "FormData", 8));
ant_value_t ctor_obj = js_mkobj(js);
js_set_slot(ctor_obj, SLOT_CFUNC, js_mkfun(js_formdata_ctor));
js_mkprop_fast(js, ctor_obj, "prototype", 9, g_formdata_proto);
js_mkprop_fast(js, ctor_obj, "name", 4, js_mkstr(js, "FormData", 8));
js_set_descriptor(js, ctor_obj, "name", 4, 0);
ant_value_t ctor = js_obj_to_func(ctor_obj);
js_set(js, g_formdata_proto, "constructor", ctor);
js_set_descriptor(js, g_formdata_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_set(js, g, "FormData", ctor);
js_set_descriptor(js, g, "FormData", 8, JS_DESC_W | JS_DESC_C);
g_formdata_iter_proto = js_mkobj(js);
js_set_proto_init(g_formdata_iter_proto, js->sym.iterator_proto);
js_set(js, g_formdata_iter_proto, "next", js_mkfun(formdata_iter_next));
js_set_descriptor(js, g_formdata_iter_proto, "next", 4, JS_DESC_W | JS_DESC_E | JS_DESC_C);
js_set_sym(js, g_formdata_iter_proto, get_iterator_sym(), js_mkfun(sym_this_cb));
}
diff --git a/src/modules/headers.c b/src/modules/headers.c
index 02f57b8..75b557b 100644
--- a/src/modules/headers.c
+++ b/src/modules/headers.c
@@ -1,602 +1,875 @@
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include "ant.h"
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "descriptors.h"
#include "silver/engine.h"
#include "modules/headers.h"
#include "modules/symbol.h"
typedef struct hdr_entry {
char *name;
char *value;
struct hdr_entry *next;
} hdr_entry_t;
typedef struct {
hdr_entry_t *head;
hdr_entry_t **tail;
size_t count;
} hdr_list_t;
typedef struct {
char *name;
char *value;
} sorted_pair_t;
typedef struct {
hdr_list_t *list;
size_t index;
int kind;
} hdr_iter_t;
enum {
ITER_ENTRIES = 0,
ITER_KEYS = 1,
ITER_VALUES = 2
};
-static ant_value_t g_headers_proto = 0;
-ant_value_t g_headers_iter_proto = 0;
+ant_value_t g_headers_proto = 0;
+ant_value_t g_headers_iter_proto = 0;
static hdr_list_t *list_new(void) {
hdr_list_t *l = ant_calloc(sizeof(hdr_list_t));
if (!l) return NULL;
l->head = NULL;
l->tail = &l->head;
return l;
}
static void list_free(hdr_list_t *l) {
if (!l) return;
for (hdr_entry_t *e = l->head; e; ) {
hdr_entry_t *n = e->next;
free(e->name); free(e->value); free(e);
e = n;
}
free(l);
}
static hdr_list_t *get_list(ant_value_t obj) {
ant_value_t slot = js_get_slot(obj, SLOT_DATA);
if (vtype(slot) != T_NUM) return NULL;
return (hdr_list_t *)(uintptr_t)(size_t)js_getnum(slot);
}
+static headers_guard_t get_guard(ant_value_t obj) {
+ ant_value_t slot = js_get_slot(obj, SLOT_HEADERS_GUARD);
+ if (vtype(slot) != T_NUM) return HEADERS_GUARD_NONE;
+ return (headers_guard_t)(int)js_getnum(slot);
+}
+
+bool headers_is_headers(ant_value_t obj) {
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ return vtype(brand) == T_NUM && (int)js_getnum(brand) == BRAND_HEADERS;
+}
+
static bool is_token_char(unsigned char c) {
if (c > 127) return false;
static const char ok[] =
"!#$%&'*+-.^_`|~"
"0123456789"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return strchr(ok, (char)c) != NULL;
}
static bool is_valid_name(const char *s) {
if (!s || !*s) return false;
for (const unsigned char *p = (const unsigned char *)s; *p; p++)
if (!is_token_char(*p)) return false;
return true;
}
static bool is_valid_value(const char *s) {
if (!s) return false;
for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
unsigned char c = *p;
if (c == 0 || c == '\r' || c == '\n' || c > 127) return false;
}
return true;
}
static char *normalize_value(const char *s) {
if (!s) return strdup("");
while (*s == ' ' || *s == '\t') s++;
size_t len = strlen(s);
while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t')) len--;
char *out = malloc(len + 1);
if (!out) return NULL;
memcpy(out, s, len);
out[len] = '\0';
return out;
}
static char *lowercase_dup(const char *s) {
if (!s) return strdup("");
size_t len = strlen(s);
char *out = malloc(len + 1);
if (!out) return NULL;
for (size_t i = 0; i <= len; i++)
out[i] = (char)tolower((unsigned char)s[i]);
return out;
}
+typedef struct {
+ const char *name;
+ bool prefix;
+} header_rule_t;
+
+static const header_rule_t k_forbidden_request_headers[] = {
+ { "accept-charset", false },
+ { "accept-encoding", false },
+ { "access-control-request-headers", false },
+ { "access-control-request-method", false },
+ { "connection", false },
+ { "content-length", false },
+ { "cookie", false },
+ { "cookie2", false },
+ { "date", false },
+ { "dnt", false },
+ { "expect", false },
+ { "host", false },
+ { "keep-alive", false },
+ { "origin", false },
+ { "referer", false },
+ { "set-cookie", false },
+ { "te", false },
+ { "trailer", false },
+ { "transfer-encoding", false },
+ { "upgrade", false },
+ { "via", false },
+ { "proxy-", true },
+ { "sec-", true },
+};
+
+static const char *k_cors_safelisted_content_types[] = {
+ "application/x-www-form-urlencoded",
+ "multipart/form-data",
+ "text/plain",
+};
+
+static const char *k_no_cors_safelisted_names[] = {
+ "accept",
+ "accept-language",
+ "content-language",
+};
+
+static bool matches_rule(const char *name, const header_rule_t *rules, size_t count) {
+ for (size_t i = 0; i < count; i++) {
+ size_t len = strlen(rules[i].name);
+ if (rules[i].prefix) { if (strncmp(name, rules[i].name, len) == 0) return true; }
+ else if (strcmp(name, rules[i].name) == 0) return true;
+ }
+ return false;
+}
+
+static bool matches_string(const char *value, const char *const *list, size_t count) {
+ for (size_t i = 0; i < count; i++) {
+ if (strcmp(value, list[i]) == 0) return true;
+ }
+ return false;
+}
+
+static bool is_forbidden_request_header_name(const char *lower_name) {
+ return matches_rule(lower_name, k_forbidden_request_headers,
+ sizeof(k_forbidden_request_headers) / sizeof(k_forbidden_request_headers[0]));
+}
+
+static bool is_cors_safelisted_content_type_value(const char *value) {
+ char *lower = lowercase_dup(value ? value : "");
+ if (!lower) return false;
+ char *semi = strchr(lower, ';');
+
+ if (!semi) {
+ bool ok = matches_string(
+ lower,
+ k_cors_safelisted_content_types,
+ sizeof(k_cors_safelisted_content_types) / sizeof(k_cors_safelisted_content_types[0])
+ );
+ free(lower);
+ return ok;
+ }
+
+ *semi++ = '\0';
+ while (*semi == ' ' || *semi == '\t') semi++;
+ bool essence_ok = matches_string(
+ lower,
+ k_cors_safelisted_content_types,
+ sizeof(k_cors_safelisted_content_types) / sizeof(k_cors_safelisted_content_types[0])
+ );
+
+ bool param_ok = strcmp(semi, "charset=utf-8") == 0;
+ free(lower);
+
+ return essence_ok && param_ok;
+}
+
+static bool is_no_cors_safelisted_name_value(const char *lower_name, const char *value) {
+ if (
+ matches_string(
+ lower_name, k_no_cors_safelisted_names,
+ sizeof(k_no_cors_safelisted_names) / sizeof(k_no_cors_safelisted_names[0]))
+ ) return true;
+
+ if (strcmp(lower_name, "content-type") == 0)
+ return value && value[0] && is_cors_safelisted_content_type_value(value);
+
+ return false;
+}
+
+static bool header_allowed_for_guard(const char *lower_name, const char *value, headers_guard_t guard) {
+ if (guard == HEADERS_GUARD_NONE) return true;
+ if (is_forbidden_request_header_name(lower_name)) return false;
+ if (guard == HEADERS_GUARD_REQUEST_NO_CORS) return is_no_cors_safelisted_name_value(lower_name, value);
+ return true;
+}
+
+static void list_apply_guard(hdr_list_t *l, headers_guard_t guard) {
+ if (!l || guard == HEADERS_GUARD_NONE) return;
+
+ hdr_entry_t **pp = &l->head;
+ l->tail = &l->head;
+
+ while (*pp) {
+ hdr_entry_t *cur = *pp;
+ if (!header_allowed_for_guard(cur->name, cur->value, guard)) {
+ *pp = cur->next;
+ free(cur->name);
+ free(cur->value);
+ free(cur);
+ l->count--;
+ continue;
+ }
+
+ l->tail = &cur->next;
+ pp = &cur->next;
+ }
+}
+
static void list_append_raw(hdr_list_t *l, const char *lower_name, const char *value) {
hdr_entry_t *e = ant_calloc(sizeof(hdr_entry_t));
if (!e) return;
e->name = strdup(lower_name);
e->value = strdup(value);
*l->tail = e;
l->tail = &e->next;
l->count++;
}
static void list_delete_name(hdr_list_t *l, const char *lower_name) {
hdr_entry_t **pp = &l->head;
l->tail = &l->head;
while (*pp) {
if (strcmp((*pp)->name, lower_name) == 0) {
hdr_entry_t *dead = *pp;
*pp = dead->next;
free(dead->name); free(dead->value); free(dead);
l->count--;
} else {
l->tail = &(*pp)->next;
pp = &(*pp)->next;
}}
}
static int cmp_pairs(const void *a, const void *b) {
return strcmp(((const sorted_pair_t *)a)->name, ((const sorted_pair_t *)b)->name);
}
static sorted_pair_t *build_sorted_view(hdr_list_t *l, size_t *out) {
*out = 0;
if (!l || l->count == 0) return NULL;
sorted_pair_t *raw = malloc(l->count * sizeof(sorted_pair_t));
if (!raw) return NULL;
size_t n = 0;
for (hdr_entry_t *e = l->head; e; e = e->next) {
raw[n].name = e->name;
raw[n].value = e->value;
n++;
}
qsort(raw, n, sizeof(sorted_pair_t), cmp_pairs);
sorted_pair_t *res = malloc(n * sizeof(sorted_pair_t));
if (!res) { free(raw); return NULL; }
size_t ri = 0;
for (size_t i = 0; i < n; ) {
if (strcmp(raw[i].name, "set-cookie") == 0) {
res[ri].name = strdup(raw[i].name);
res[ri].value = strdup(raw[i].value);
ri++; i++;
} else {
size_t j = i + 1;
size_t total = strlen(raw[i].value);
while (j < n && strcmp(raw[j].name, raw[i].name) == 0) {
total += 2 + strlen(raw[j].value);
j++;
}
char *combined = malloc(total + 1);
if (!combined) combined = strdup("");
size_t pos = 0;
for (size_t k = i; k < j; k++) {
if (k > i) { combined[pos++] = ','; combined[pos++] = ' '; }
size_t vl = strlen(raw[k].value);
memcpy(combined + pos, raw[k].value, vl);
pos += vl;
}
combined[pos] = '\0';
res[ri].name = strdup(raw[i].name);
res[ri].value = combined;
ri++; i = j;
}}
free(raw);
*out = ri;
return res;
}
static void free_sorted_view(sorted_pair_t *v, size_t n) {
if (!v) return;
for (size_t i = 0; i < n; i++) { free(v[i].name); free(v[i].value); }
free(v);
}
static ant_value_t headers_append_pair(ant_t *js, hdr_list_t *l, ant_value_t name_v, ant_value_t value_v) {
if (vtype(name_v) != T_STR) {
name_v = js_tostring_val(js, name_v);
if (is_err(name_v)) return name_v;
}
if (vtype(value_v) != T_STR) {
value_v = js_tostring_val(js, value_v);
if (is_err(value_v)) return value_v;
}
const char *name = js_getstr(js, name_v, NULL);
const char *value = js_getstr(js, value_v, NULL);
if (!is_valid_name(name))
return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name: %s", name ? name : "");
char *norm = normalize_value(value);
if (!norm) return js_mkerr(js, "out of memory");
if (!is_valid_value(norm)) {
free(norm);
return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header value");
}
char *lower = lowercase_dup(name);
if (!lower) { free(norm); return js_mkerr(js, "out of memory"); }
list_append_raw(l, lower, norm);
free(lower); free(norm);
return js_mkundef();
}
+ant_value_t headers_append_value(ant_t *js, ant_value_t hdrs, ant_value_t name_v, ant_value_t value_v) {
+ hdr_list_t *l = get_list(hdrs);
+ if (!l) return js_mkerr(js, "Invalid Headers object");
+ ant_value_t r = headers_append_pair(js, l, name_v, value_v);
+ if (is_err(r)) return r;
+ list_apply_guard(l, get_guard(hdrs));
+ return js_mkundef();
+}
+
static ant_value_t init_from_sequence(ant_t *js, hdr_list_t *l, ant_value_t seq) {
js_iter_t it;
if (!js_iter_open(js, seq, &it)) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers init is not iterable");
ant_value_t pair;
while (js_iter_next(js, &it, &pair)) {
uint8_t pt = vtype(pair);
if (pt != T_ARR && pt != T_OBJ) {
js_iter_close(js, &it);
return js_mkerr_typed(js, JS_ERR_TYPE, "Each header init pair must be a sequence");
}
if (js_arr_len(js, pair) != 2) {
js_iter_close(js, &it);
return js_mkerr_typed(js, JS_ERR_TYPE, "Each header init pair must have exactly 2 elements");
}
ant_value_t r = headers_append_pair(js, l, js_arr_get(js, pair, 0), js_arr_get(js, pair, 1));
if (is_err(r)) { js_iter_close(js, &it); return r; }
}
return js_mkundef();
}
static ant_value_t init_from_record(ant_t *js, hdr_list_t *l, ant_value_t obj) {
ant_iter_t it = js_prop_iter_begin(js, obj);
const char *key;
size_t key_len;
ant_value_t val;
while (js_prop_iter_next(&it, &key, &key_len, &val)) {
ant_value_t r = headers_append_pair(js, l, js_mkstr(js, key, key_len), val);
if (is_err(r)) { js_prop_iter_end(&it); return r; }
}
js_prop_iter_end(&it);
return js_mkundef();
}
bool advance_headers(ant_t *js, js_iter_t *it, ant_value_t *out) {
ant_value_t state_val = js_get_slot(it->iterator, SLOT_ITER_STATE);
if (vtype(state_val) == T_UNDEF) return false;
hdr_iter_t *st = (hdr_iter_t *)(uintptr_t)(size_t)js_getnum(state_val);
size_t count = 0;
sorted_pair_t *view = build_sorted_view(st->list, &count);
if (st->index >= count) {
free_sorted_view(view, count);
return false;
}
sorted_pair_t *e = &view[st->index];
switch (st->kind) {
case ITER_KEYS:
*out = js_mkstr(js, e->name, strlen(e->name));
break;
case ITER_VALUES:
*out = js_mkstr(js, e->value, strlen(e->value));
break;
default: {
*out = js_mkarr(js);
js_arr_push(js, *out, js_mkstr(js, e->name, strlen(e->name)));
js_arr_push(js, *out, js_mkstr(js, e->value, strlen(e->value)));
break;
}}
free_sorted_view(view, count);
st->index++;
return true;
}
static ant_value_t headers_iter_next(ant_t *js, ant_value_t *args, int nargs) {
js_iter_t it = { .iterator = js->this_val };
ant_value_t value;
return js_iter_result(js, advance_headers(js, &it, &value), value);
}
static ant_value_t make_headers_iter(ant_t *js, ant_value_t headers_obj, int kind) {
hdr_list_t *l = get_list(headers_obj);
hdr_iter_t *st = ant_calloc(sizeof(hdr_iter_t));
if (!st) return js_mkerr(js, "out of memory");
st->list = l ? l : list_new();
st->kind = kind;
ant_value_t iter = js_mkobj(js);
js_set_proto_init(iter, g_headers_iter_proto);
js_set_slot(iter, SLOT_ITER_STATE, ANT_PTR(st));
return iter;
}
static ant_value_t js_headers_append(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.append requires 2 arguments");
hdr_list_t *l = get_list(js->this_val);
if (!l) return js_mkerr(js, "Invalid Headers object");
- return headers_append_pair(js, l, args[0], args[1]);
+ ant_value_t r = headers_append_pair(js, l, args[0], args[1]);
+ if (is_err(r)) return r;
+ list_apply_guard(l, get_guard(js->this_val));
+ return js_mkundef();
}
static ant_value_t js_headers_set(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.set requires 2 arguments");
hdr_list_t *l = get_list(js->this_val);
if (!l) return js_mkerr(js, "Invalid Headers object");
ant_value_t name_v = args[0];
ant_value_t value_v = args[1];
if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; }
if (vtype(value_v) != T_STR) { value_v = js_tostring_val(js, value_v); if (is_err(value_v)) return value_v; }
const char *name = js_getstr(js, name_v, NULL);
const char *value = js_getstr(js, value_v, NULL);
if (!is_valid_name(name))
return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name: %s", name ? name : "");
char *norm = normalize_value(value);
if (!norm) return js_mkerr(js, "out of memory");
if (!is_valid_value(norm)) { free(norm); return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header value"); }
char *lower = lowercase_dup(name);
if (!lower) { free(norm); return js_mkerr(js, "out of memory"); }
list_delete_name(l, lower);
- list_append_raw(l, lower, norm);
+ if (header_allowed_for_guard(lower, norm, get_guard(js->this_val)))
+ list_append_raw(l, lower, norm);
free(lower); free(norm);
return js_mkundef();
}
static ant_value_t js_headers_get(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.get requires 1 argument");
hdr_list_t *l = get_list(js->this_val);
if (!l) return js_mknull();
ant_value_t name_v = args[0];
if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; }
const char *name = js_getstr(js, name_v, NULL);
if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name");
char *lower = lowercase_dup(name);
if (!lower) return js_mkerr(js, "out of memory");
// set-cookie is never combined per Fetch spec
if (strcmp(lower, "set-cookie") == 0) {
for (hdr_entry_t *e = l->head; e; e = e->next) {
if (strcmp(e->name, lower) == 0) {
ant_value_t ret = js_mkstr(js, e->value, strlen(e->value));
free(lower);
return ret;
}
}
free(lower);
return js_mknull();
}
size_t total = 0;
int count = 0;
for (hdr_entry_t *e = l->head; e; e = e->next) {
if (strcmp(e->name, lower) == 0) {
if (count > 0) total += 2;
total += strlen(e->value);
count++;
}
}
if (count == 0) { free(lower); return js_mknull(); }
char *combined = malloc(total + 1);
if (!combined) { free(lower); return js_mkerr(js, "out of memory"); }
size_t pos = 0;
int seen = 0;
for (hdr_entry_t *e = l->head; e; e = e->next) {
if (strcmp(e->name, lower) == 0) {
if (seen > 0) { combined[pos++] = ','; combined[pos++] = ' '; }
size_t vl = strlen(e->value);
memcpy(combined + pos, e->value, vl);
pos += vl;
seen++;
}
}
combined[pos] = '\0';
free(lower);
ant_value_t ret = js_mkstr(js, combined, pos);
free(combined);
return ret;
}
static ant_value_t js_headers_has(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.has requires 1 argument");
hdr_list_t *l = get_list(js->this_val);
if (!l) return js_false;
ant_value_t name_v = args[0];
if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; }
const char *name = js_getstr(js, name_v, NULL);
if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name");
char *lower = lowercase_dup(name);
if (!lower) return js_mkerr(js, "out of memory");
bool found = false;
for (hdr_entry_t *e = l->head; e; e = e->next) {
if (strcmp(e->name, lower) == 0) { found = true; break; }
}
free(lower);
return js_bool(found);
}
static ant_value_t js_headers_delete(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.delete requires 1 argument");
hdr_list_t *l = get_list(js->this_val);
if (!l) return js_mkundef();
ant_value_t name_v = args[0];
if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; }
const char *name = js_getstr(js, name_v, NULL);
if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name");
char *lower = lowercase_dup(name);
if (!lower) return js_mkerr(js, "out of memory");
list_delete_name(l, lower);
free(lower);
return js_mkundef();
}
static ant_value_t js_headers_get_set_cookie(ant_t *js, ant_value_t *args, int nargs) {
(void)args; (void)nargs;
hdr_list_t *l = get_list(js->this_val);
ant_value_t arr = js_mkarr(js);
if (!l) return arr;
for (hdr_entry_t *e = l->head; e; e = e->next) {
if (strcmp(e->name, "set-cookie") == 0)
js_arr_push(js, arr, js_mkstr(js, e->value, strlen(e->value)));
}
return arr;
}
static ant_value_t js_headers_for_each(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.forEach requires 1 argument");
ant_value_t cb = args[0];
uint8_t cbt = vtype(cb);
if (cbt != T_FUNC && cbt != T_CFUNC)
return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.forEach callback must be callable");
ant_value_t this_obj = js->this_val;
hdr_list_t *l = get_list(this_obj);
if (!l) return js_mkundef();
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
size_t count = 0;
sorted_pair_t *view = build_sorted_view(l, &count);
for (size_t i = 0; i < count; i++) {
ant_value_t call_args[3] = {
js_mkstr(js, view[i].value, strlen(view[i].value)),
js_mkstr(js, view[i].name, strlen(view[i].name)),
this_obj
};
ant_value_t r = sv_vm_call(js->vm, js, cb, this_arg, call_args, 3, NULL, false);
if (is_err(r)) { free_sorted_view(view, count); return r; }
}
free_sorted_view(view, count);
return js_mkundef();
}
static ant_value_t js_headers_keys(ant_t *js, ant_value_t *args, int nargs) {
return make_headers_iter(js, js->this_val, ITER_KEYS);
}
static ant_value_t js_headers_values(ant_t *js, ant_value_t *args, int nargs) {
return make_headers_iter(js, js->this_val, ITER_VALUES);
}
static ant_value_t js_headers_entries(ant_t *js, ant_value_t *args, int nargs) {
return make_headers_iter(js, js->this_val, ITER_ENTRIES);
}
static ant_value_t js_headers_symbol_iterator(ant_t *js, ant_value_t *args, int nargs) {
return make_headers_iter(js, js->this_val, ITER_ENTRIES);
}
static ant_value_t js_headers_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "Headers constructor requires 'new'");
hdr_list_t *l = list_new();
if (!l) return js_mkerr(js, "out of memory");
ant_value_t init = (nargs >= 1) ? args[0] : js_mkundef();
if (vtype(init) != T_UNDEF) {
uint8_t t = vtype(init);
if (t == T_NULL || (t != T_OBJ && t != T_ARR && t != T_FUNC && t != T_CFUNC)) {
list_free(l);
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'Headers': The provided value is not of type 'HeadersInit'");
}
ant_value_t iter_fn = js_get_sym(js, init, get_iterator_sym());
bool has_iter = (vtype(iter_fn) == T_FUNC || vtype(iter_fn) == T_CFUNC);
ant_value_t r;
if (t == T_ARR || has_iter) r = init_from_sequence(js, l, init);
else r = init_from_record(js, l, init);
if (is_err(r)) { list_free(l); return r; }
}
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_headers_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_HEADERS));
js_set_slot(obj, SLOT_DATA, ANT_PTR(l));
+ js_set_slot(obj, SLOT_HEADERS_GUARD, js_mknum(HEADERS_GUARD_NONE));
+
return obj;
}
+ant_value_t headers_create_empty(ant_t *js) {
+ hdr_list_t *l = list_new();
+ if (!l) return js_mkerr(js, "out of memory");
+
+ ant_value_t obj = js_mkobj(js);
+ js_set_proto_init(obj, g_headers_proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_HEADERS));
+ js_set_slot(obj, SLOT_DATA, ANT_PTR(l));
+ js_set_slot(obj, SLOT_HEADERS_GUARD, js_mknum(HEADERS_GUARD_NONE));
+
+ return obj;
+}
+
+bool headers_copy_from(ant_t *js, ant_value_t dst, ant_value_t src) {
+ hdr_list_t *src_list = get_list(src);
+ hdr_list_t *dst_list = get_list(dst);
+
+ if (!dst_list) return false;
+ if (!src_list) return true;
+
+ for (hdr_entry_t *e = src_list->head; e; e = e->next)
+ list_append_raw(dst_list, e->name, e->value);
+ return true;
+}
+
+void headers_set_guard(ant_value_t hdrs, headers_guard_t guard) {
+ js_set_slot(hdrs, SLOT_HEADERS_GUARD, js_mknum(guard));
+}
+
+headers_guard_t headers_get_guard(ant_value_t hdrs) {
+ return get_guard(hdrs);
+}
+
+void headers_apply_guard(ant_value_t hdrs) {
+ list_apply_guard(get_list(hdrs), get_guard(hdrs));
+}
+
+void headers_append_if_missing(ant_value_t hdrs, const char *name, const char *value) {
+ hdr_list_t *l = get_list(hdrs);
+ if (!l || !name || !value) return;
+ char *lower = lowercase_dup(name);
+ if (!lower) return;
+ for (hdr_entry_t *e = l->head; e; e = e->next) {
+ if (strcmp(e->name, lower) == 0) { free(lower); return; }
+ }
+ list_append_raw(l, lower, value);
+ free(lower);
+}
+
+ant_value_t headers_get_value(ant_t *js, ant_value_t hdrs, const char *name) {
+ hdr_list_t *l = get_list(hdrs);
+
+ if (!l) return js_mknull();
+ if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name");
+
+ char *lower = lowercase_dup(name);
+ if (!lower) return js_mkerr(js, "out of memory");
+
+ if (strcmp(lower, "set-cookie") == 0) {
+ for (hdr_entry_t *e = l->head; e; e = e->next) {
+ if (strcmp(e->name, lower) == 0) {
+ ant_value_t ret = js_mkstr(js, e->value, strlen(e->value));
+ free(lower);
+ return ret;
+ }}
+ free(lower);
+ return js_mknull();
+ }
+
+ size_t total = 0;
+ int count = 0;
+
+ for (hdr_entry_t *e = l->head; e; e = e->next) {
+ if (strcmp(e->name, lower) == 0) {
+ if (count > 0) total += 2;
+ total += strlen(e->value);
+ count++;
+ }}
+
+ if (count == 0) {
+ free(lower);
+ return js_mknull();
+ }
+
+ char *combined = malloc(total + 1);
+ if (!combined) {
+ free(lower);
+ return js_mkerr(js, "out of memory");
+ }
+
+ size_t pos = 0;
+ int seen = 0;
+
+ for (hdr_entry_t *e = l->head; e; e = e->next) {
+ if (strcmp(e->name, lower) == 0) {
+ if (seen > 0) { combined[pos++] = ','; combined[pos++] = ' '; }
+ size_t vl = strlen(e->value);
+ memcpy(combined + pos, e->value, vl);
+ pos += vl;
+ seen++;
+ }}
+
+ combined[pos] = '\0';
+ free(lower);
+
+ ant_value_t ret = js_mkstr(js, combined, pos);
+ free(combined);
+
+ return ret;
+}
+
void init_headers_module(void) {
ant_t *js = rt->js;
ant_value_t g = js_glob(js);
g_headers_iter_proto = js_mkobj(js);
js_set_proto_init(g_headers_iter_proto, js->sym.iterator_proto);
js_set(js, g_headers_iter_proto, "next", js_mkfun(headers_iter_next));
js_set_descriptor(js, g_headers_iter_proto, "next", 4, JS_DESC_W | JS_DESC_E | JS_DESC_C);
js_set_sym(js, g_headers_iter_proto, get_iterator_sym(), js_mkfun(sym_this_cb));
js_iter_register_advance(g_headers_iter_proto, advance_headers);
g_headers_proto = js_mkobj(js);
js_set(js, g_headers_proto, "append", js_mkfun(js_headers_append));
js_set(js, g_headers_proto, "set", js_mkfun(js_headers_set));
js_set(js, g_headers_proto, "get", js_mkfun(js_headers_get));
js_set(js, g_headers_proto, "has", js_mkfun(js_headers_has));
js_set(js, g_headers_proto, "delete", js_mkfun(js_headers_delete));
js_set(js, g_headers_proto, "forEach", js_mkfun(js_headers_for_each));
js_set(js, g_headers_proto, "keys", js_mkfun(js_headers_keys));
js_set(js, g_headers_proto, "values", js_mkfun(js_headers_values));
js_set(js, g_headers_proto, "entries", js_mkfun(js_headers_entries));
js_set(js, g_headers_proto, "getSetCookie", js_mkfun(js_headers_get_set_cookie));
js_set_sym(js, g_headers_proto, get_iterator_sym(), js_mkfun(js_headers_symbol_iterator));
js_set_sym(js, g_headers_proto, get_toStringTag_sym(), js_mkstr(js, "Headers", 7));
ant_value_t ctor_obj = js_mkobj(js);
js_set_slot(ctor_obj, SLOT_CFUNC, js_mkfun(js_headers_ctor));
js_mkprop_fast(js, ctor_obj, "prototype", 9, g_headers_proto);
js_mkprop_fast(js, ctor_obj, "name", 4, js_mkstr(js, "Headers", 7));
js_set_descriptor(js, ctor_obj, "name", 4, 0);
ant_value_t ctor = js_obj_to_func(ctor_obj);
js_set(js, g_headers_proto, "constructor", ctor);
js_set_descriptor(js, g_headers_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_set(js, g, "Headers", ctor);
js_set_descriptor(js, g, "Headers", 7, JS_DESC_W | JS_DESC_C);
}
diff --git a/src/modules/multipart.c b/src/modules/multipart.c
new file mode 100644
index 0000000..608f2ce
--- /dev/null
+++ b/src/modules/multipart.c
@@ -0,0 +1,546 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#include "ant.h"
+#include "errors.h"
+#include "internal.h"
+
+#include "modules/blob.h"
+#include "modules/formdata.h"
+#include "modules/multipart.h"
+#include "modules/url.h"
+
+static bool ct_is_type(const char *ct, const char *type) {
+ size_t type_len = 0;
+
+ if (!ct || !type) return false;
+
+ while (*ct == ' ' || *ct == '\t') ct++;
+ type_len = strlen(type);
+ if (strncasecmp(ct, type, type_len) != 0) return false;
+
+ ct += type_len;
+ return *ct == '\0' || *ct == ';' || *ct == ' ' || *ct == '\t';
+}
+
+static const char *ct_find_next_param(const char *p) {
+scan:
+ if (*p == '\0') return NULL;
+ if (*p == ';') return p;
+ if (*p != '"') {
+ p++;
+ goto scan;
+ }
+
+quoted:
+ p++;
+ if (*p == '\0') return NULL;
+ if (*p == '\\') goto escaped;
+ if (*p == '"') {
+ p++;
+ goto scan;
+ }
+ goto quoted;
+
+escaped:
+ p++;
+ if (*p == '\0') return NULL;
+ p++;
+ goto quoted;
+}
+
+static char *ct_get_param_dup(const char *ct, const char *name) {
+ const char *p = NULL;
+ const char *param_name = NULL;
+ const char *param_name_end = NULL;
+
+ char *buf = NULL;
+ size_t name_len = 0;
+ size_t len = 0;
+ size_t cap = 0;
+ char ch = '\0';
+
+ if (!ct || !name) return NULL;
+
+ name_len = strlen(name);
+ if (name_len == 0) return NULL;
+
+ p = strchr(ct, ';');
+
+next_param:
+ if (!p) return NULL;
+ p++;
+
+ while (*p == ' ' || *p == '\t') p++;
+ if (*p == '\0') return NULL;
+
+ param_name = p;
+ while (*p && *p != '=' && *p != ';') p++;
+ param_name_end = p;
+
+ while (
+ param_name_end > param_name &&
+ (param_name_end[-1] == ' ' || param_name_end[-1] == '\t')
+ ) param_name_end--;
+
+ if (*p != '=') goto skip_param;
+ if ((size_t)(param_name_end - param_name) != name_len) goto skip_param;
+ if (strncasecmp(param_name, name, name_len) != 0) goto skip_param;
+
+ p++;
+ while (*p == ' ' || *p == '\t') p++;
+ if (*p == '\0') return strdup("");
+
+ if (*p == '"') {
+ p++;
+ goto quoted;
+ }
+
+unquoted:
+ if (*p == '\0' || *p == ';' || *p == ' ' || *p == '\t') {
+ buf = malloc(len + 1);
+ if (!buf) return NULL;
+ if (len != 0) memcpy(buf, p - len, len);
+ buf[len] = '\0';
+ return buf;
+ }
+ p++;
+ len++;
+ goto unquoted;
+
+quoted:
+ if (*p == '\0') goto fail;
+ if (*p == '"') goto done;
+ if (*p == '\\') goto quoted_escape;
+ ch = *p++;
+ goto append;
+
+quoted_escape:
+ p++;
+ if (*p == '\0') goto fail;
+ ch = *p++;
+ goto append;
+
+append:
+ if (len == cap) {
+ size_t next_cap = cap ? cap * 2 : 32;
+ char *next = realloc(buf, next_cap);
+ if (!next) goto fail;
+ buf = next;
+ cap = next_cap;
+ }
+ buf[len++] = ch;
+ goto quoted;
+
+done:
+ p++;
+ if (len == cap) {
+ size_t next_cap = cap ? cap + 1 : 1;
+ char *next = realloc(buf, next_cap);
+ if (!next) goto fail;
+ buf = next;
+ cap = next_cap;
+ }
+ buf[len] = '\0';
+ return buf;
+
+skip_param:
+ p = ct_find_next_param(p);
+ goto next_param;
+
+fail:
+ free(buf);
+ return NULL;
+}
+
+static ant_value_t parse_formdata_urlencoded(ant_t *js, const uint8_t *data, size_t size) {
+ ant_value_t fd = 0;
+ char *body = NULL;
+ char *cursor = NULL;
+
+ fd = formdata_create_empty(js);
+ if (is_err(fd)) return fd;
+ if (!data || size == 0) return fd;
+
+ body = strndup((const char *)data, size);
+ if (!body) return js_mkerr(js, "out of memory");
+
+ cursor = body;
+ while (cursor) {
+ ant_value_t r = 0;
+ char *amp = strchr(cursor, '&');
+ char *eq = NULL;
+ char *raw_name = NULL;
+ char *raw_value = NULL;
+ char *name = NULL;
+ char *value = NULL;
+
+ if (amp) *amp = '\0';
+
+ eq = strchr(cursor, '=');
+ raw_name = cursor;
+ raw_value = eq ? (eq + 1) : "";
+ if (eq) *eq = '\0';
+
+ name = form_urldecode(raw_name);
+ value = form_urldecode(raw_value);
+ if (!name || !value) {
+ free(name);
+ free(value);
+ free(body);
+ return js_mkerr(js, "out of memory");
+ }
+
+ r = formdata_append_string(
+ js, fd,
+ js_mkstr(js, name, strlen(name)),
+ js_mkstr(js, value, strlen(value))
+ );
+
+ free(name);
+ free(value);
+
+ if (is_err(r)) {
+ free(body);
+ return r;
+ }
+
+ cursor = amp ? (amp + 1) : NULL;
+ }
+
+ free(body);
+ return fd;
+}
+
+static const uint8_t *find_bytes(
+ const uint8_t *haystack, size_t haystack_len,
+ const uint8_t *needle, size_t needle_len
+) {
+ size_t i = 0;
+
+ if (needle_len == 0) return haystack;
+ if (haystack_len < needle_len) return NULL;
+
+ for (i = 0; i + needle_len <= haystack_len; i++) {
+ if (memcmp(haystack + i, needle, needle_len) == 0) return haystack + i;
+ }
+
+ return NULL;
+}
+
+static ant_value_t parse_formdata_multipart(
+ ant_t *js, const uint8_t *data, size_t size, const char *body_type
+) {
+ ant_value_t fd = 0;
+ char *boundary = NULL;
+ char *delim = NULL;
+
+ const uint8_t *p = data;
+ const uint8_t *end = data + size;
+ size_t delim_len = 0;
+
+ if (!data || size == 0) {
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData");
+ }
+
+ boundary = ct_get_param_dup(body_type, "boundary");
+ if (!boundary || boundary[0] == '\0') goto invalid;
+
+ fd = formdata_create_empty(js);
+ if (is_err(fd)) goto done;
+
+ delim_len = strlen(boundary) + 2;
+ delim = malloc(delim_len + 1);
+ if (!delim) {
+ fd = js_mkerr(js, "out of memory");
+ goto done;
+ }
+ snprintf(delim, delim_len + 1, "--%s", boundary);
+
+ if ((size_t)(end - p) < delim_len || memcmp(p, delim, delim_len) != 0) goto invalid;
+ p += delim_len;
+
+next_part:
+ if (p > end) goto done;
+ if ((size_t)(end - p) >= 2 && memcmp(p, "--", 2) == 0) goto done;
+ if ((size_t)(end - p) < 2 || memcmp(p, "\r\n", 2) != 0) goto invalid;
+ p += 2;
+
+ {
+ ant_value_t r = 0;
+ const uint8_t *hdr_end = find_bytes(
+ p, (size_t)(end - p), (const uint8_t *)"\r\n\r\n", 4
+ );
+
+ char *headers = NULL;
+ char *name = NULL;
+ char *filename = NULL;
+ char *part_type = NULL;
+ char *marker = NULL;
+ const uint8_t *part_end = NULL;
+
+ if (!hdr_end) goto invalid;
+
+ headers = strndup((const char *)p, (size_t)(hdr_end - p));
+ if (!headers) {
+ fd = js_mkerr(js, "out of memory");
+ goto done;
+ }
+ p = hdr_end + 4;
+
+ {
+ char *saveptr = NULL;
+ for (char *line = strtok_r(headers, "\r\n", &saveptr);
+ line;
+ line = strtok_r(NULL, "\r\n", &saveptr)) {
+ char *colon = strchr(line, ':');
+ if (!colon) continue;
+ *colon = '\0';
+
+ char *value = colon + 1;
+ while (*value == ' ' || *value == '\t') value++;
+
+ if (strcasecmp(line, "Content-Disposition") == 0) {
+ char *name_pos = strcasestr(value, "name=");
+ char *file_pos = strcasestr(value, "filename=");
+
+ if (name_pos) {
+ name_pos += 5;
+ if (*name_pos == '"') {
+ char *endq = NULL;
+ name_pos++;
+ endq = strchr(name_pos, '"');
+ if (endq) name = strndup(name_pos, (size_t)(endq - name_pos));
+ }
+ }
+
+ if (file_pos) {
+ file_pos += 9;
+ if (*file_pos == '"') {
+ char *endq = NULL;
+ file_pos++;
+ endq = strchr(file_pos, '"');
+ if (endq) filename = strndup(file_pos, (size_t)(endq - file_pos));
+ } else {
+ char *ende = file_pos;
+ while (*ende && *ende != ';') ende++;
+ filename = strndup(file_pos, (size_t)(ende - file_pos));
+ }
+ }
+ } else if (strcasecmp(line, "Content-Type") == 0) {
+ part_type = strdup(value);
+ }
+ }
+ }
+ free(headers);
+ headers = NULL;
+
+ if (!name) {
+ free(filename);
+ free(part_type);
+ goto invalid;
+ }
+
+ marker = malloc(delim_len + 3);
+ if (!marker) {
+ free(name);
+ free(filename);
+ free(part_type);
+ fd = js_mkerr(js, "out of memory");
+ goto done;
+ }
+ snprintf(marker, delim_len + 3, "\r\n%s", delim);
+ part_end = find_bytes(
+ p, (size_t)(end - p), (const uint8_t *)marker, delim_len + 2
+ );
+ free(marker);
+ marker = NULL;
+
+ if (!part_end) {
+ free(name);
+ free(filename);
+ free(part_type);
+ goto invalid;
+ }
+
+ if (filename) {
+ ant_value_t blob = blob_create(
+ js, p, (size_t)(part_end - p), part_type ? part_type : ""
+ );
+ r = is_err(blob)
+ ? blob
+ : formdata_append_file(
+ js, fd,
+ js_mkstr(js, name, strlen(name)),
+ blob,
+ js_mkstr(js, filename, strlen(filename))
+ );
+ } else {
+ r = formdata_append_string(
+ js, fd,
+ js_mkstr(js, name, strlen(name)),
+ js_mkstr(js, p, (size_t)(part_end - p))
+ );
+ }
+
+ free(name);
+ free(filename);
+ free(part_type);
+ if (is_err(r)) {
+ fd = r;
+ goto done;
+ }
+
+ p = part_end + 2 + delim_len;
+ if ((size_t)(end - p) >= 2 && memcmp(p, "--", 2) == 0) goto done;
+ }
+
+ goto next_part;
+
+invalid:
+ fd = js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData");
+
+done:
+ free(boundary);
+ free(delim);
+ return fd;
+}
+
+ant_value_t formdata_parse_body(
+ ant_t *js, const uint8_t *data, size_t size,
+ const char *body_type, bool has_body
+) {
+ if (body_type && ct_is_type(body_type, "application/x-www-form-urlencoded")) {
+ return parse_formdata_urlencoded(js, data, size);
+ }
+
+ if (body_type && ct_is_type(body_type, "multipart/form-data")) {
+ if (!has_body || !data || size == 0) {
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData");
+ }
+ return parse_formdata_multipart(js, data, size, body_type);
+ }
+
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData");
+}
+
+typedef struct {
+ uint8_t *buf;
+ size_t size;
+ size_t cap;
+} mp_buf_t;
+
+static bool mp_grow(mp_buf_t *b, size_t need) {
+ size_t nc = 0;
+ uint8_t *nb = NULL;
+
+ if (b->size + need <= b->cap) return true;
+
+ nc = b->cap ? b->cap * 2 : 4096;
+ while (nc < b->size + need) nc *= 2;
+
+ nb = realloc(b->buf, nc);
+ if (!nb) return false;
+ b->buf = nb;
+ b->cap = nc;
+ return true;
+}
+
+static bool mp_append(mp_buf_t *b, const void *data, size_t len) {
+ if (!mp_grow(b, len)) return false;
+ memcpy(b->buf + b->size, data, len);
+ b->size += len;
+ return true;
+}
+
+static bool mp_append_str(mp_buf_t *b, const char *s) {
+ return mp_append(b, s, strlen(s));
+}
+
+uint8_t *formdata_serialize_multipart(
+ ant_t *js, ant_value_t fd, size_t *out_size, char **out_boundary
+) {
+ ant_value_t values_arr = js_get_slot(fd, SLOT_ENTRIES);
+ ant_value_t data_slot = js_get_slot(fd, SLOT_DATA);
+ char boundary[49];
+ mp_buf_t b = {NULL, 0, 0};
+ fd_data_t *d = NULL;
+ if (vtype(data_slot) != T_NUM) return NULL;
+ d = (fd_data_t *)(uintptr_t)(size_t)js_getnum(data_slot);
+ if (!d) return NULL;
+
+ snprintf(
+ boundary, sizeof(boundary),
+ "----AntFormBoundary%08x%08x", (unsigned)rand(), (unsigned)rand()
+ );
+
+ if (d->count == 0) {
+ uint8_t *empty = malloc(1);
+ if (!empty) return NULL;
+ *out_size = 0;
+ *out_boundary = strdup(boundary);
+ if (!*out_boundary) {
+ free(empty);
+ return NULL;
+ }
+ return empty;
+ }
+
+ for (fd_entry_t *e = d->head; e; e = e->next) {
+ if (!mp_append_str(&b, "--") ||
+ !mp_append_str(&b, boundary) ||
+ !mp_append_str(&b, "\r\n")) {
+ goto oom;
+ }
+
+ if (!e->is_file) {
+ char disp[512];
+ const char *val = e->str_value ? e->str_value : "";
+
+ snprintf(
+ disp, sizeof(disp),
+ "Content-Disposition: form-data; name=\"%s\"\r\n\r\n",
+ e->name ? e->name : ""
+ );
+ if (!mp_append_str(&b, disp) || !mp_append_str(&b, val)) goto oom;
+ } else {
+ ant_value_t file_val = js_arr_get(js, values_arr, (ant_offset_t)e->val_idx);
+ blob_data_t *bd = blob_get_data(file_val);
+ const char *filename = (bd && bd->name) ? bd->name : "blob";
+ const char *mime = (bd && bd->type && bd->type[0])
+ ? bd->type
+ : "application/octet-stream";
+ char disp[1024];
+
+ snprintf(
+ disp, sizeof(disp),
+ "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n"
+ "Content-Type: %s\r\n\r\n",
+ e->name ? e->name : "", filename, mime
+ );
+ if (!mp_append_str(&b, disp)) goto oom;
+ if (bd && bd->data && bd->size > 0 && !mp_append(&b, bd->data, bd->size)) goto oom;
+ }
+
+ if (!mp_append_str(&b, "\r\n")) goto oom;
+ }
+
+ if (!mp_append_str(&b, "--") ||
+ !mp_append_str(&b, boundary) ||
+ !mp_append_str(&b, "--\r\n")) {
+ goto oom;
+ }
+
+ *out_size = b.size;
+ *out_boundary = strdup(boundary);
+ if (!*out_boundary) goto oom;
+ return b.buf;
+
+oom:
+ free(b.buf);
+ return NULL;
+}
diff --git a/src/modules/request.c b/src/modules/request.c
new file mode 100644
index 0000000..8c9a628
--- /dev/null
+++ b/src/modules/request.c
@@ -0,0 +1,1104 @@
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#include "ant.h"
+#include "errors.h"
+#include "runtime.h"
+#include "internal.h"
+#include "common.h"
+#include "descriptors.h"
+
+#include "modules/blob.h"
+#include "modules/buffer.h"
+#include "modules/assert.h"
+#include "modules/abort.h"
+#include "modules/formdata.h"
+#include "modules/headers.h"
+#include "modules/multipart.h"
+#include "modules/request.h"
+#include "modules/symbol.h"
+#include "modules/url.h"
+#include "modules/json.h"
+#include "streams/pipes.h"
+#include "streams/readable.h"
+
+ant_value_t g_request_proto = 0;
+
+static request_data_t *get_data(ant_value_t obj) {
+ ant_value_t slot = js_get_slot(obj, SLOT_DATA);
+ if (vtype(slot) != T_NUM) return NULL;
+ return (request_data_t *)(uintptr_t)(size_t)js_getnum(slot);
+}
+
+request_data_t *request_get_data(ant_value_t obj) {
+ return get_data(obj);
+}
+
+ant_value_t request_get_headers(ant_value_t obj) {
+ return js_get_slot(obj, SLOT_REQUEST_HEADERS);
+}
+
+ant_value_t request_get_signal(ant_value_t obj) {
+ return js_get_slot(obj, SLOT_REQUEST_SIGNAL);
+}
+
+static void data_free(request_data_t *d) {
+ if (!d) return;
+ free(d->method);
+ url_state_clear(&d->url);
+ free(d->referrer);
+ free(d->referrer_policy);
+ free(d->mode);
+ free(d->credentials);
+ free(d->cache);
+ free(d->redirect);
+ free(d->integrity);
+ free(d->body_data);
+ free(d->body_type);
+ free(d);
+}
+
+static request_data_t *data_new(void) {
+ request_data_t *d = calloc(1, sizeof(request_data_t));
+ if (!d) return NULL;
+ d->method = strdup("GET");
+ d->referrer = strdup("client");
+ d->referrer_policy = strdup("");
+ d->mode = strdup("cors");
+ d->credentials = strdup("same-origin");
+ d->cache = strdup("default");
+ d->redirect = strdup("follow");
+ d->integrity = strdup("");
+ if (!d->method || !d->referrer || !d->referrer_policy ||
+ !d->mode || !d->credentials || !d->cache || !d->redirect || !d->integrity) {
+ data_free(d);
+ return NULL;
+ }
+ return d;
+}
+
+static request_data_t *data_dup(const request_data_t *src) {
+ request_data_t *d = calloc(1, sizeof(request_data_t));
+ if (!d) return NULL;
+
+#define DUP_STR(f) do { d->f = src->f ? strdup(src->f) : NULL; } while(0)
+ DUP_STR(method);
+ DUP_STR(referrer);
+ DUP_STR(referrer_policy);
+ DUP_STR(mode);
+ DUP_STR(credentials);
+ DUP_STR(cache);
+ DUP_STR(redirect);
+ DUP_STR(integrity);
+ DUP_STR(body_type);
+#undef DUP_STR
+ url_state_t *su = (url_state_t *)&src->url;
+ url_state_t *du = &d->url;
+#define DUP_US(f) do { du->f = su->f ? strdup(su->f) : NULL; } while(0)
+ DUP_US(protocol); DUP_US(username); DUP_US(password);
+ DUP_US(hostname); DUP_US(port); DUP_US(pathname);
+ DUP_US(search); DUP_US(hash);
+#undef DUP_US
+ d->keepalive = src->keepalive;
+ d->reload_navigation = src->reload_navigation;
+ d->history_navigation = src->history_navigation;
+ d->has_body = src->has_body;
+ d->body_is_stream = src->body_is_stream;
+ d->body_used = src->body_used;
+ d->body_size = src->body_size;
+
+ if (src->body_data && src->body_size > 0) {
+ d->body_data = malloc(src->body_size);
+ if (!d->body_data) { data_free(d); return NULL; }
+ memcpy(d->body_data, src->body_data, src->body_size);
+ }
+
+ return d;
+}
+
+static bool is_token_char(unsigned char c) {
+ if (c == 0 || c > 127) return false;
+ static const char *delimiters = "(),/:;<=>?@[\\]{}\"";
+ return c > 32 && !strchr(delimiters, (char)c);
+}
+
+static bool is_valid_method(const char *m) {
+ if (!m || !*m) return false;
+ for (const unsigned char *p = (const unsigned char *)m; *p; p++)
+ if (!is_token_char(*p)) return false;
+ return true;
+}
+
+static bool is_forbidden_method(const char *m) {
+ return
+ strcasecmp(m, "CONNECT") == 0 ||
+ strcasecmp(m, "TRACE") == 0 ||
+ strcasecmp(m, "TRACK") == 0;
+}
+
+static bool is_cors_safelisted_method(const char *m) {
+ return
+ strcasecmp(m, "GET") == 0 ||
+ strcasecmp(m, "HEAD") == 0 ||
+ strcasecmp(m, "POST") == 0;
+}
+
+static void normalize_method(char *m) {
+ static const char *norm[] = {
+ "DELETE","GET","HEAD","OPTIONS","POST","PUT"
+ };
+
+ for (int i = 0; i < 6; i++) {
+ if (strcasecmp(m, norm[i]) == 0) {
+ strcpy(m, norm[i]);
+ return;
+ }}
+}
+
+static ant_value_t request_rejection_reason(ant_t *js, ant_value_t value) {
+ if (!is_err(value)) return value;
+ ant_value_t reason = js->thrown_exists ? js->thrown_value : value;
+ js->thrown_exists = false;
+ js->thrown_value = js_mkundef();
+ js->thrown_stack = js_mkundef();
+ return reason;
+}
+
+static const char *request_effective_body_type(ant_t *js, ant_value_t req_obj, request_data_t *d) {
+ ant_value_t headers = js_get_slot(req_obj, SLOT_REQUEST_HEADERS);
+ if (!headers_is_headers(headers)) return d ? d->body_type : NULL;
+ ant_value_t ct = headers_get_value(js, headers, "content-type");
+ if (vtype(ct) == T_STR) return js_getstr(js, ct, NULL);
+ return d ? d->body_type : NULL;
+}
+
+static bool extract_body(
+ ant_t *js, ant_value_t body_val,
+ uint8_t **out_data, size_t *out_size, char **out_type,
+ ant_value_t *out_stream, ant_value_t *err_out
+) {
+ *out_data = NULL;
+ *out_size = 0;
+ *out_type = NULL;
+ *out_stream = js_mkundef();
+ *err_out = js_mkundef();
+
+ uint8_t t = vtype(body_val);
+ const uint8_t *src = NULL;
+ size_t src_len = 0;
+
+ if (t == T_NULL || t == T_UNDEF) return true;
+
+ if ((t == T_TYPEDARRAY || t == T_OBJ) && buffer_source_get_bytes(js, body_val, &src, &src_len)) {
+ if (src_len > 0) {
+ uint8_t *buf = malloc(src_len);
+ if (!buf) { *err_out = js_mkerr(js, "out of memory"); return false; }
+ memcpy(buf, src, src_len);
+ *out_data = buf;
+ *out_size = src_len;
+ }
+ return true;
+ }
+
+ if (t == T_OBJ) {
+ if (rs_is_stream(body_val)) {
+ if (rs_stream_unusable(body_val)) {
+ *err_out = js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked");
+ return false;
+ }
+ *out_stream = body_val;
+ return true;
+ }
+
+ blob_data_t *bd = blob_is_blob(js, body_val) ? blob_get_data(body_val) : NULL;
+ if (bd) {
+ if (bd->size > 0) {
+ uint8_t *buf = malloc(bd->size);
+ if (!buf) { *err_out = js_mkerr(js, "out of memory"); return false; }
+ memcpy(buf, bd->data, bd->size);
+ *out_data = buf;
+ *out_size = bd->size;
+ }
+ if (bd->type && bd->type[0]) *out_type = strdup(bd->type);
+ return true;
+ }
+
+ if (usp_is_urlsearchparams(js, body_val)) {
+ char *serialized = usp_serialize(js, body_val);
+ if (serialized) {
+ *out_data = (uint8_t *)serialized;
+ *out_size = strlen(serialized);
+ *out_type = strdup("application/x-www-form-urlencoded;charset=UTF-8");
+ }
+ return true;
+ }
+
+ if (formdata_is_formdata(js, body_val)) {
+ char *boundary = NULL;
+ size_t mp_size = 0;
+ uint8_t *mp = formdata_serialize_multipart(js, body_val, &mp_size, &boundary);
+
+ if (!mp) { *err_out = js_mkerr(js, "out of memory"); return false; }
+ if (mp_size > 0) *out_data = mp;
+ else { free(mp); *out_data = NULL; }
+
+ *out_size = mp_size;
+ if (boundary) {
+ char ct[256];
+ snprintf(ct, sizeof(ct), "multipart/form-data; boundary=%s", boundary);
+ free(boundary);
+ *out_type = strdup(ct);
+ }
+
+ return true;
+ }
+ }
+
+ if (t != T_STR) {
+ body_val = js_tostring_val(js, body_val);
+ if (is_err(body_val)) {
+ *err_out = body_val;
+ return false;
+ }}
+
+ size_t len;
+ const char *s = js_getstr(js, body_val, &len);
+
+ if (len > 0) {
+ uint8_t *buf = malloc(len);
+ if (!buf) { *err_out = js_mkerr(js, "out of memory"); return false; }
+ memcpy(buf, s, len);
+ *out_data = buf;
+ *out_size = len;
+ }
+
+ *out_type = strdup("text/plain;charset=UTF-8");
+ return true;
+}
+
+enum {
+ BODY_TEXT = 0,
+ BODY_JSON,
+ BODY_ARRAYBUFFER,
+ BODY_BLOB,
+ BODY_BYTES,
+ BODY_FORMDATA
+};
+
+static void resolve_body_promise(
+ ant_t *js, ant_value_t promise,
+ const uint8_t *data, size_t size,
+ const char *body_type, int mode, bool has_body
+) {
+ switch (mode) {
+ case BODY_TEXT: {
+ ant_value_t str = (data && size > 0)
+ ? js_mkstr(js, (const char *)data, size)
+ : js_mkstr(js, "", 0);
+ js_resolve_promise(js, promise, str);
+ break;
+ }
+ case BODY_JSON: {
+ ant_value_t str = (data && size > 0)
+ ? js_mkstr(js, (const char *)data, size)
+ : js_mkstr(js, "", 0);
+ ant_value_t parsed = js_json_parse(js, &str, 1);
+ if (is_err(parsed)) js_reject_promise(js, promise, request_rejection_reason(js, parsed));
+ else js_resolve_promise(js, promise, parsed);
+ break;
+ }
+ case BODY_ARRAYBUFFER: {
+ ArrayBufferData *ab = create_array_buffer_data(size);
+ if (!ab) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); break; }
+ if (data && size > 0) memcpy(ab->data, data, size);
+ js_resolve_promise(js, promise, create_arraybuffer_obj(js, ab));
+ break;
+ }
+ case BODY_BLOB: {
+ const char *type = body_type ? body_type : "";
+ js_resolve_promise(js, promise, blob_create(js, data, size, type));
+ break;
+ }
+ case BODY_BYTES: {
+ ArrayBufferData *ab = create_array_buffer_data(size);
+ if (!ab) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); break; }
+ if (data && size > 0) memcpy(ab->data, data, size);
+ js_resolve_promise(js, promise,
+ create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, size, "Uint8Array"));
+ break;
+ }
+ case BODY_FORMDATA: {
+ ant_value_t fd = formdata_parse_body(js, data, size, body_type, has_body);
+ if (is_err(fd)) js_reject_promise(js, promise, request_rejection_reason(js, fd));
+ else js_resolve_promise(js, promise, fd);
+ break;
+ }}
+}
+
+static uint8_t *concat_chunks(ant_t *js, ant_value_t chunks, size_t *out_size) {
+ ant_offset_t n = js_arr_len(js, chunks);
+ size_t total = 0;
+
+ for (ant_offset_t i = 0; i < n; i++) {
+ ant_value_t chunk = js_arr_get(js, chunks, i);
+ if (vtype(chunk) == T_TYPEDARRAY) {
+ TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(chunk);
+ if (ta && ta->buffer && !ta->buffer->is_detached) total += ta->byte_length;
+ }}
+
+ uint8_t *buf = total > 0 ? malloc(total) : NULL;
+ if (total > 0 && !buf) return NULL;
+
+ size_t pos = 0;
+ for (ant_offset_t i = 0; i < n; i++) {
+ ant_value_t chunk = js_arr_get(js, chunks, i);
+
+ if (vtype(chunk) == T_TYPEDARRAY) {
+ TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(chunk);
+ if (ta && ta->buffer && !ta->buffer->is_detached && ta->byte_length > 0) {
+ memcpy(buf + pos, ta->buffer->data + ta->byte_offset, ta->byte_length);
+ pos += ta->byte_length;
+ }}}
+
+ *out_size = pos;
+ return buf;
+}
+
+static ant_value_t stream_body_read(ant_t *js, ant_value_t *args, int nargs);
+
+static ant_value_t stream_body_rejected(ant_t *js, ant_value_t *args, int nargs) {
+ ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
+ ant_value_t promise = js_get(js, state, "promise");
+ ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
+ js_reject_promise(js, promise, reason);
+ return js_mkundef();
+}
+
+static void stream_schedule_next_read(ant_t *js, ant_value_t state, ant_value_t read_fn, ant_value_t reader) {
+ ant_value_t next_p = rs_default_reader_read(js, reader);
+ ant_value_t fulfill = js_heavy_mkfun(js, stream_body_read, state);
+ ant_value_t reject = js_heavy_mkfun(js, stream_body_rejected, state);
+ ant_value_t then_result = js_promise_then(js, next_p, fulfill, reject);
+ promise_mark_handled(then_result);
+}
+
+static ant_value_t stream_body_read(ant_t *js, ant_value_t *args, int nargs) {
+ ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
+ ant_value_t result = (nargs > 0) ? args[0] : js_mkundef();
+ ant_value_t promise = js_get(js, state, "promise");
+ ant_value_t reader = js_get(js, state, "reader");
+ ant_value_t chunks = js_get(js, state, "chunks");
+ int mode = (int)js_getnum(js_get(js, state, "mode"));
+
+ ant_value_t done_val = js_get(js, result, "done");
+ ant_value_t value = js_get(js, result, "value");
+
+ if (vtype(done_val) == T_BOOL && done_val == js_true) {
+ size_t size = 0;
+ uint8_t *data = concat_chunks(js, chunks, &size);
+ ant_value_t type_v = js_get(js, state, "type");
+ const char *body_type = (vtype(type_v) == T_STR) ? js_getstr(js, type_v, NULL) : NULL;
+ resolve_body_promise(js, promise, data, size, body_type, mode, true);
+ free(data);
+ return js_mkundef();
+ }
+
+ if (vtype(value) != T_UNDEF && vtype(value) != T_NULL)
+ js_arr_push(js, chunks, value);
+
+ stream_schedule_next_read(js, state, js_mkundef(), reader);
+ return js_mkundef();
+}
+
+static ant_value_t consume_body_from_stream(
+ ant_t *js, ant_value_t stream,
+ ant_value_t promise, int mode,
+ const char *body_type
+) {
+ ant_value_t reader_args[1] = { stream };
+ ant_value_t saved = js->new_target;
+
+ js->new_target = g_reader_proto;
+ ant_value_t reader = js_rs_reader_ctor(js, reader_args, 1);
+ js->new_target = saved;
+
+ if (is_err(reader)) {
+ js_reject_promise(js, promise, reader);
+ return promise;
+ }
+
+ ant_value_t state = js_mkobj(js);
+ js_set(js, state, "promise", promise);
+ js_set(js, state, "reader", reader);
+ js_set(js, state, "chunks", js_mkarr(js));
+ js_set(js, state, "mode", js_mknum(mode));
+ js_set(js, state, "type", body_type ? js_mkstr(js, body_type, strlen(body_type)) : js_mkundef());
+
+ stream_schedule_next_read(js, state, js_mkundef(), reader);
+ return promise;
+}
+
+static ant_value_t consume_body(ant_t *js, int mode) {
+ ant_value_t this = js_getthis(js);
+ request_data_t *d = get_data(this);
+ ant_value_t promise = js_mkpromise(js);
+
+ if (!d) {
+ js_reject_promise(js, promise, request_rejection_reason(js,
+ js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Request object")));
+ return promise;
+ }
+
+ if (!d->has_body) {
+ resolve_body_promise(js, promise, NULL, 0, request_effective_body_type(js, this, d), mode, false);
+ return promise;
+ }
+
+ if (d->body_used) {
+ js_reject_promise(js, promise, request_rejection_reason(js,
+ js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked")));
+ return promise;
+ }
+
+ d->body_used = true;
+ ant_value_t stream = js_get_slot(this, SLOT_REQUEST_BODY_STREAM);
+ if (rs_is_stream(stream) && d->body_is_stream)
+ return consume_body_from_stream(js, stream, promise, mode, request_effective_body_type(js, this, d));
+ resolve_body_promise(js, promise, d->body_data, d->body_size, request_effective_body_type(js, this, d), mode, true);
+
+ return promise;
+}
+
+static ant_value_t js_req_text(ant_t *js, ant_value_t *args, int nargs) {
+ return consume_body(js, BODY_TEXT);
+}
+
+static ant_value_t js_req_json(ant_t *js, ant_value_t *args, int nargs) {
+ return consume_body(js, BODY_JSON);
+}
+
+static ant_value_t js_req_array_buffer(ant_t *js, ant_value_t *args, int nargs) {
+ return consume_body(js, BODY_ARRAYBUFFER);
+}
+
+static ant_value_t js_req_blob(ant_t *js, ant_value_t *args, int nargs) {
+ return consume_body(js, BODY_BLOB);
+}
+
+static ant_value_t js_req_bytes(ant_t *js, ant_value_t *args, int nargs) {
+ return consume_body(js, BODY_BYTES);
+}
+
+static ant_value_t js_req_form_data(ant_t *js, ant_value_t *args, int nargs) {
+ return consume_body(js, BODY_FORMDATA);
+}
+
+#define REQ_GETTER_START(name) \
+ static ant_value_t js_req_get_##name(ant_t *js, ant_value_t *args, int nargs) { \
+ ant_value_t this = js_getthis(js); \
+ request_data_t *d = get_data(this); \
+ if (!d) return js_mkundef();
+
+#define REQ_GETTER_END }
+
+REQ_GETTER_START(method)
+ return js_mkstr(js, d->method, strlen(d->method));
+REQ_GETTER_END
+
+REQ_GETTER_START(url)
+ char *href = build_href(&d->url);
+ if (!href) return js_mkstr(js, "", 0);
+ ant_value_t ret = js_mkstr(js, href, strlen(href));
+ free(href);
+ return ret;
+REQ_GETTER_END
+
+REQ_GETTER_START(headers)
+ return js_get_slot(this, SLOT_REQUEST_HEADERS);
+REQ_GETTER_END
+
+REQ_GETTER_START(destination)
+ (void)d;
+ return js_mkstr(js, "", 0);
+REQ_GETTER_END
+
+REQ_GETTER_START(referrer)
+ if (!d->referrer || strcmp(d->referrer, "no-referrer") == 0)
+ return js_mkstr(js, "", 0);
+ if (strcmp(d->referrer, "client") == 0)
+ return js_mkstr(js, "about:client", 12);
+ return js_mkstr(js, d->referrer, strlen(d->referrer));
+REQ_GETTER_END
+
+REQ_GETTER_START(referrer_policy)
+ const char *p = d->referrer_policy ? d->referrer_policy : "";
+ return js_mkstr(js, p, strlen(p));
+REQ_GETTER_END
+
+REQ_GETTER_START(mode)
+ return js_mkstr(js, d->mode, strlen(d->mode));
+REQ_GETTER_END
+
+REQ_GETTER_START(credentials)
+ return js_mkstr(js, d->credentials, strlen(d->credentials));
+REQ_GETTER_END
+
+REQ_GETTER_START(cache)
+ return js_mkstr(js, d->cache, strlen(d->cache));
+REQ_GETTER_END
+
+REQ_GETTER_START(redirect)
+ return js_mkstr(js, d->redirect, strlen(d->redirect));
+REQ_GETTER_END
+
+REQ_GETTER_START(integrity)
+ const char *ig = d->integrity ? d->integrity : "";
+ return js_mkstr(js, ig, strlen(ig));
+REQ_GETTER_END
+
+REQ_GETTER_START(keepalive)
+ return js_bool(d->keepalive);
+REQ_GETTER_END
+
+REQ_GETTER_START(is_reload_navigation)
+ return js_bool(d->reload_navigation);
+REQ_GETTER_END
+
+REQ_GETTER_START(is_history_navigation)
+ return js_bool(d->history_navigation);
+REQ_GETTER_END
+
+REQ_GETTER_START(signal)
+ return js_get_slot(this, SLOT_REQUEST_SIGNAL);
+REQ_GETTER_END
+
+REQ_GETTER_START(duplex)
+ return js_mkstr(js, "half", 4);
+REQ_GETTER_END
+
+static ant_value_t req_body_pull(ant_t *js, ant_value_t *args, int nargs) {
+ ant_value_t req_obj = js_get_slot(js->current_func, SLOT_DATA);
+ request_data_t *d = get_data(req_obj);
+ ant_value_t ctrl = (nargs > 0) ? args[0] : js_mkundef();
+
+ if (d && d->body_data && d->body_size > 0) {
+ ArrayBufferData *ab = create_array_buffer_data(d->body_size);
+ if (ab) {
+ memcpy(ab->data, d->body_data, d->body_size);
+ rs_controller_enqueue(js, ctrl,
+ create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, d->body_size, "Uint8Array"));
+ }}
+
+ rs_controller_close(js, ctrl);
+ return js_mkundef();
+}
+
+REQ_GETTER_START(body)
+ if (!d->has_body) return js_mknull();
+ ant_value_t stored_stream = js_get_slot(this, SLOT_REQUEST_BODY_STREAM);
+ if (rs_is_stream(stored_stream)) return stored_stream;
+ if (d->body_used) return js_mknull();
+ ant_value_t pull = js_heavy_mkfun(js, req_body_pull, this);
+ ant_value_t stream = rs_create_stream(js, pull, js_mkundef(), 1.0);
+ if (!is_err(stream)) js_set_slot_wb(js, this, SLOT_REQUEST_BODY_STREAM, stream);
+ return stream;
+REQ_GETTER_END
+
+REQ_GETTER_START(body_used)
+ return js_bool(d->body_used);
+REQ_GETTER_END
+
+#undef REQ_GETTER_START
+#undef REQ_GETTER_END
+
+static ant_value_t js_request_clone(ant_t *js, ant_value_t *args, int nargs) {
+ ant_value_t this = js_getthis(js);
+ request_data_t *d = get_data(this);
+
+ if (!d) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Request object");
+ if (d->body_used)
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot clone a Request whose body is unusable");
+
+ request_data_t *nd = data_dup(d);
+ if (!nd) return js_mkerr(js, "out of memory");
+
+ ant_value_t src_headers = js_get_slot(this, SLOT_REQUEST_HEADERS);
+ ant_value_t src_signal = js_get_slot(this, SLOT_REQUEST_SIGNAL);
+
+ ant_value_t new_headers = headers_create_empty(js);
+ if (is_err(new_headers)) { data_free(nd); return new_headers; }
+
+ headers_copy_from(js, new_headers, src_headers);
+ headers_set_guard(new_headers,
+ strcmp(nd->mode, "no-cors") == 0
+ ? HEADERS_GUARD_REQUEST_NO_CORS
+ : HEADERS_GUARD_REQUEST
+ );
+ headers_apply_guard(new_headers);
+
+ ant_value_t new_signal = abort_signal_create_dependent(js, src_signal);
+ if (is_err(new_signal)) { data_free(nd); return new_signal; }
+
+ ant_value_t obj = js_mkobj(js);
+ js_set_proto_init(obj, g_request_proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST));
+ js_set_slot(obj, SLOT_DATA, ANT_PTR(nd));
+
+ js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, new_headers);
+ js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, new_signal);
+
+ ant_value_t src_stream = js_get_slot(this, SLOT_REQUEST_BODY_STREAM);
+ if (rs_is_stream(src_stream)) {
+ ant_value_t branches = readable_stream_tee(js, src_stream);
+ if (!is_err(branches) && vtype(branches) == T_ARR) {
+ ant_value_t b1 = js_arr_get(js, branches, 0);
+ ant_value_t b2 = js_arr_get(js, branches, 1);
+ js_set_slot_wb(js, this, SLOT_REQUEST_BODY_STREAM, b1);
+ js_set_slot_wb(js, obj, SLOT_REQUEST_BODY_STREAM, b2);
+ }}
+
+ return obj;
+}
+
+static const char *init_str(ant_t *js, ant_value_t init, const char *key, size_t klen, ant_value_t *err_out) {
+ ant_value_t v = js_get(js, init, key);
+ if (vtype(v) == T_UNDEF) return NULL;
+ if (vtype(v) != T_STR) {
+ v = js_tostring_val(js, v);
+ if (is_err(v)) { *err_out = v; return NULL; }
+ }
+ return js_getstr(js, v, NULL);
+}
+
+static ant_value_t js_request_ctor(ant_t *js, ant_value_t *args, int nargs) {
+ if (vtype(js->new_target) == T_UNDEF)
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Request constructor requires 'new'");
+ if (nargs < 1)
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Request constructor requires at least 1 argument");
+
+ ant_value_t input = args[0];
+ ant_value_t init = (nargs >= 2 && vtype(args[1]) != T_UNDEF) ? args[1] : js_mkundef();
+ bool init_provided = (vtype(init) == T_OBJ || vtype(init) == T_ARR);
+
+ request_data_t *req = NULL;
+ ant_value_t input_signal = js_mkundef();
+
+ request_data_t *src = NULL;
+ if (vtype(input) == T_OBJ) src = get_data(input);
+
+ if (!src) {
+ if (vtype(input) != T_STR) {
+ input = js_tostring_val(js, input);
+ if (is_err(input)) return input;
+ }
+ size_t ulen;
+ const char *url_str = js_getstr(js, input, &ulen);
+ url_state_t parsed = {0};
+ if (parse_url_to_state(url_str, NULL, &parsed) != 0) {
+ url_state_clear(&parsed);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Invalid URL");
+ }
+ if ((parsed.username && parsed.username[0]) || (parsed.password && parsed.password[0])) {
+ url_state_clear(&parsed);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': URL includes credentials");
+ }
+ req = data_new();
+ if (!req) { url_state_clear(&parsed); return js_mkerr(js, "out of memory"); }
+ req->url = parsed;
+ } else if (vtype(input) == T_OBJ) {
+ req = data_dup(src);
+ if (!req) return js_mkerr(js, "out of memory");
+ req->body_used = false;
+ input_signal = js_get_slot(input, SLOT_REQUEST_SIGNAL);
+ }
+
+ ant_value_t err = js_mkundef();
+
+ if (init_provided) {
+ ant_value_t win = js_get(js, init, "window");
+ if (vtype(win) != T_UNDEF && vtype(win) != T_NULL) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': 'window' must be null");
+ }
+
+ if (strcmp(req->mode, "navigate") == 0) {
+ free(req->mode); req->mode = strdup("same-origin");
+ }
+ req->reload_navigation = false;
+ req->history_navigation = false;
+ free(req->referrer); req->referrer = strdup("client");
+ free(req->referrer_policy); req->referrer_policy = strdup("");
+
+ const char *ref = init_str(js, init, "referrer", 8, &err);
+ if (is_err(err)) { data_free(req); return err; }
+
+ if (ref) {
+ if (ref[0] == '\0') {
+ free(req->referrer);
+ req->referrer = strdup("no-referrer");
+ } else {
+ url_state_t rs = {0};
+ if (parse_url_to_state(ref, NULL, &rs) == 0) {
+ char *href = build_href(&rs);
+ url_state_clear(&rs);
+ free(req->referrer);
+ req->referrer = href ? href : strdup("client");
+ } else {
+ url_state_clear(&rs);
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Invalid referrer URL");
+ }
+ }}
+
+ const char *rp = init_str(js, init, "referrerPolicy", 14, &err);
+ if (is_err(err)) { data_free(req); return err; }
+ if (rp) { free(req->referrer_policy); req->referrer_policy = strdup(rp); }
+
+ const char *mode_val = init_str(js, init, "mode", 4, &err);
+ if (is_err(err)) { data_free(req); return err; }
+ if (mode_val) {
+ if (strcmp(mode_val, "navigate") == 0) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': mode 'navigate' is not allowed");
+ }
+ free(req->mode); req->mode = strdup(mode_val);
+ }
+
+ const char *cred = init_str(js, init, "credentials", 11, &err);
+ if (is_err(err)) { data_free(req); return err; }
+ if (cred) { free(req->credentials); req->credentials = strdup(cred); }
+
+ const char *cache_val = init_str(js, init, "cache", 5, &err);
+ if (is_err(err)) { data_free(req); return err; }
+ if (cache_val) {
+ free(req->cache); req->cache = strdup(cache_val);
+ if (
+ strcmp(req->cache, "only-if-cached") == 0 &&
+ strcmp(req->mode, "same-origin") != 0
+ ) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE,
+ "Failed to construct 'Request': cache mode 'only-if-cached' requires mode 'same-origin'");
+ }}
+
+ const char *redir = init_str(js, init, "redirect", 8, &err);
+ if (is_err(err)) { data_free(req); return err; }
+ if (redir) { free(req->redirect); req->redirect = strdup(redir); }
+
+ const char *integ = init_str(js, init, "integrity", 9, &err);
+ if (is_err(err)) { data_free(req); return err; }
+ if (integ) { free(req->integrity); req->integrity = strdup(integ); }
+
+ ant_value_t ka = js_get(js, init, "keepalive");
+ if (vtype(ka) != T_UNDEF) req->keepalive = js_truthy(js, ka);
+
+ const char *method_val = init_str(js, init, "method", 6, &err);
+ if (is_err(err)) { data_free(req); return err; }
+ if (method_val) {
+ if (!is_valid_method(method_val)) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Invalid method");
+ }
+ if (is_forbidden_method(method_val)) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Forbidden method");
+ }
+ free(req->method);
+ req->method = strdup(method_val);
+ normalize_method(req->method);
+ }
+
+ ant_value_t sig_val = js_get(js, init, "signal");
+ if (vtype(sig_val) != T_UNDEF) {
+ if (vtype(sig_val) == T_NULL) input_signal = js_mkundef();
+ else if (abort_signal_is_signal(sig_val)) input_signal = sig_val; else {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': signal must be an AbortSignal");
+ }}
+ }
+
+ if (
+ strcmp(req->mode, "no-cors") == 0 &&
+ !is_cors_safelisted_method(req->method)
+ ) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE,
+ "Failed to construct 'Request': method must be one of GET, HEAD, POST for no-cors mode");
+ }
+
+ ant_value_t obj = js_mkobj(js);
+ ant_value_t proto = js_instance_proto_from_new_target(js, g_request_proto);
+
+ if (is_object_type(proto)) js_set_proto_init(obj, proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST));
+ js_set_slot(obj, SLOT_DATA, ANT_PTR(req));
+
+ ant_value_t signal = abort_signal_create_dependent(js, input_signal);
+ if (is_err(signal)) { data_free(req); return signal; }
+ js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, signal);
+
+ ant_value_t headers;
+ if (vtype(input) == T_OBJ) {
+ ant_value_t src_hdrs = js_get_slot(input, SLOT_REQUEST_HEADERS);
+ headers = headers_create_empty(js);
+ if (is_err(headers)) { data_free(req); return headers; }
+ headers_copy_from(js, headers, src_hdrs);
+ } else {
+ headers = headers_create_empty(js);
+ if (is_err(headers)) { data_free(req); return headers; }
+ }
+
+ if (init_provided) {
+ ant_value_t init_headers = js_get(js, init, "headers");
+
+ if (vtype(init_headers) != T_UNDEF) {
+ ant_value_t new_hdrs = headers_create_empty(js);
+ if (is_err(new_hdrs)) { data_free(req); return new_hdrs; }
+
+ uint8_t ht = vtype(init_headers);
+ if (headers_is_headers(init_headers)) {
+ headers_copy_from(js, new_hdrs, init_headers);
+ } else if (ht == T_ARR) {
+ ant_offset_t len = js_arr_len(js, init_headers);
+
+ for (ant_offset_t i = 0; i < len; i++) {
+ ant_value_t pair = js_arr_get(js, init_headers, i);
+ if (js_arr_len(js, pair) < 2) continue;
+ ant_value_t r = headers_append_value(js, new_hdrs, js_arr_get(js, pair, 0), js_arr_get(js, pair, 1));
+ if (is_err(r)) { data_free(req); return r; }
+ }
+ } else if (ht == T_OBJ) {
+ ant_iter_t it = js_prop_iter_begin(js, init_headers);
+ const char *key; size_t key_len; ant_value_t val;
+
+ while (js_prop_iter_next(&it, &key, &key_len, &val)) {
+ ant_value_t r = headers_append_value(js, new_hdrs, js_mkstr(js, key, key_len), val);
+ if (is_err(r)) { js_prop_iter_end(&it); data_free(req); return r; }
+ } js_prop_iter_end(&it);
+ } headers = new_hdrs;
+ }}
+
+ headers_set_guard(headers,
+ strcmp(req->mode, "no-cors") == 0
+ ? HEADERS_GUARD_REQUEST_NO_CORS
+ : HEADERS_GUARD_REQUEST
+ );
+ headers_apply_guard(headers);
+ js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, headers);
+
+ if (init_provided) {
+ ant_value_t duplex_val = js_get(js, init, "duplex");
+ bool duplex_provided = vtype(duplex_val) != T_UNDEF;
+ if (duplex_provided) {
+ ant_value_t duplex_str_v = duplex_val;
+ if (vtype(duplex_str_v) != T_STR) {
+ duplex_str_v = js_tostring_val(js, duplex_str_v);
+ if (is_err(duplex_str_v)) { data_free(req); return duplex_str_v; }
+ }
+ const char *duplex_str = js_getstr(js, duplex_str_v, NULL);
+ if (!duplex_str || strcmp(duplex_str, "half") != 0) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE,
+ "Failed to construct 'Request': duplex must be 'half'");
+ }}
+
+ ant_value_t body_val = js_get(js, init, "body");
+ bool init_body_present = vtype(body_val) != T_UNDEF;
+ bool input_body_present = src && src->has_body;
+ bool effective_body_present = (init_body_present && vtype(body_val) != T_NULL)
+
+ || (input_body_present && (!init_body_present || vtype(body_val) == T_NULL));
+ if ((strcmp(req->method, "GET") == 0 || strcmp(req->method, "HEAD") == 0) &&
+ effective_body_present) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE,
+ "Failed to construct 'Request': Request with GET/HEAD method cannot have body");
+ }
+ if (vtype(body_val) != T_UNDEF) {
+ if (vtype(body_val) != T_NULL) {
+ request_data_t *init_req = get_data(init);
+ ant_value_t body_err = js_mkundef();
+ uint8_t *bd = NULL; size_t bs = 0; char *bt = NULL;
+ ant_value_t body_stream = js_mkundef();
+ if (init_req && !init_req->body_used && !init_req->body_is_stream && init_req->has_body) {
+ bd = malloc(init_req->body_size);
+ if (init_req->body_size > 0 && !bd) {
+ data_free(req);
+ return js_mkerr(js, "out of memory");
+ }
+ if (init_req->body_size > 0) memcpy(bd, init_req->body_data, init_req->body_size);
+ bs = init_req->body_size;
+ bt = init_req->body_type ? strdup(init_req->body_type) : NULL;
+ } else if (!extract_body(js, body_val, &bd, &bs, &bt, &body_stream, &body_err)) {
+ data_free(req);
+ return is_err(body_err) ? body_err : js_mkerr(js, "Failed to extract body");
+ }
+ free(req->body_data); free(req->body_type);
+ req->body_data = bd;
+ req->body_size = bs;
+ req->body_type = bt;
+ req->body_is_stream = rs_is_stream(body_stream);
+ req->has_body = true;
+ if (rs_is_stream(body_stream)) {
+ if (req->keepalive) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE,
+ "Failed to construct 'Request': keepalive cannot be used with a ReadableStream body");
+ }
+ if (!duplex_provided) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE,
+ "Failed to construct 'Request': duplex must be provided for a ReadableStream body");
+ }
+ js_set_slot_wb(js, obj, SLOT_REQUEST_BODY_STREAM, body_stream);
+ }
+
+ if (bt && bt[0])
+ headers_append_if_missing(headers, "content-type", bt);
+ } else {
+ free(req->body_data); req->body_data = NULL;
+ req->body_size = 0;
+ free(req->body_type); req->body_type = NULL;
+ req->body_is_stream = false;
+ req->has_body = false;
+ js_set_slot_wb(js, obj, SLOT_REQUEST_BODY_STREAM, js_mkundef());
+ }
+ }
+ } else if (src) {
+ if (src && src->body_used) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot construct Request with unusable body");
+ }
+ ant_value_t src_stream = js_get_slot(input, SLOT_REQUEST_BODY_STREAM);
+ if (rs_is_stream(src_stream) && rs_stream_unusable(src_stream)) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked");
+ }
+ if (!src->body_is_stream) {
+ req->body_data = malloc(src->body_size);
+ if (src->body_size > 0 && !req->body_data) { data_free(req); return js_mkerr(js, "out of memory"); }
+ if (src->body_size > 0) memcpy(req->body_data, src->body_data, src->body_size);
+ req->body_size = src->body_size;
+ req->body_type = src->body_type ? strdup(src->body_type) : NULL;
+ req->body_is_stream = false;
+ req->has_body = true;
+ } else if (rs_is_stream(src_stream)) {
+ if (rs_stream_unusable(src_stream)) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked");
+ }
+ ant_value_t branches = readable_stream_tee(js, src_stream);
+ if (is_err(branches)) { data_free(req); return branches; }
+ if (vtype(branches) != T_ARR) {
+ data_free(req);
+ return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': tee() did not return branches");
+ }
+ ant_value_t b2 = js_arr_get(js, branches, 1);
+ js_set_slot_wb(js, obj, SLOT_REQUEST_BODY_STREAM, b2);
+
+ req->body_is_stream = true;
+ req->has_body = true;
+ }
+ }
+
+ if (src && src->has_body && !src->body_used)
+ src->body_used = true;
+
+ return obj;
+}
+
+ant_value_t request_create(ant_t *js,
+ const char *method, const char *url,
+ ant_value_t headers_obj, const uint8_t *body, size_t body_len,
+ const char *body_type) {
+ request_data_t *req = data_new();
+ if (!req) return js_mkerr(js, "out of memory");
+
+ free(req->method);
+ req->method = strdup(method ? method : "GET");
+ free(req->mode);
+ req->mode = strdup("same-origin");
+
+ url_state_t parsed = {0};
+ if (url && parse_url_to_state(url, NULL, &parsed) == 0) req->url = parsed;
+ else url_state_clear(&parsed);
+
+ if (body) req->has_body = true;
+
+ if (body && body_len > 0) {
+ req->body_data = malloc(body_len);
+ if (!req->body_data) { data_free(req); return js_mkerr(js, "out of memory"); }
+ memcpy(req->body_data, body, body_len);
+ req->body_size = body_len;
+ req->body_type = body_type ? strdup(body_type) : NULL;
+ }
+ req->body_is_stream = false;
+
+ ant_value_t obj = js_mkobj(js);
+ js_set_proto_init(obj, g_request_proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST));
+ js_set_slot(obj, SLOT_DATA, ANT_PTR(req));
+
+ ant_value_t hdrs = is_object_type(headers_obj)
+ ? headers_obj
+ : headers_create_empty(js);
+
+ headers_set_guard(hdrs,
+ strcmp(req->mode, "no-cors") == 0
+ ? HEADERS_GUARD_REQUEST_NO_CORS
+ : HEADERS_GUARD_REQUEST
+ );
+
+ headers_apply_guard(hdrs);
+ js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, hdrs);
+
+ ant_value_t sig = abort_signal_create_dependent(js, js_mkundef());
+ js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, sig);
+
+ return obj;
+}
+
+void init_request_module(void) {
+ ant_t *js = rt->js;
+ ant_value_t g = js_glob(js);
+ g_request_proto = js_mkobj(js);
+
+ js_set(js, g_request_proto, "text", js_mkfun(js_req_text));
+ js_set(js, g_request_proto, "json", js_mkfun(js_req_json));
+ js_set(js, g_request_proto, "arrayBuffer", js_mkfun(js_req_array_buffer));
+ js_set(js, g_request_proto, "blob", js_mkfun(js_req_blob));
+ js_set(js, g_request_proto, "formData", js_mkfun(js_req_form_data));
+ js_set(js, g_request_proto, "bytes", js_mkfun(js_req_bytes));
+ js_set(js, g_request_proto, "clone", js_mkfun(js_request_clone));
+
+#define GETTER(prop, fn) \
+ js_set_getter_desc(js, g_request_proto, prop, sizeof(prop)-1, js_mkfun(js_req_get_##fn), JS_DESC_C)
+ GETTER("method", method);
+ GETTER("url", url);
+ GETTER("headers", headers);
+ GETTER("destination", destination);
+ GETTER("referrer", referrer);
+ GETTER("referrerPolicy", referrer_policy);
+ GETTER("mode", mode);
+ GETTER("credentials", credentials);
+ GETTER("cache", cache);
+ GETTER("redirect", redirect);
+ GETTER("integrity", integrity);
+ GETTER("keepalive", keepalive);
+ GETTER("isReloadNavigation",is_reload_navigation);
+ GETTER("isHistoryNavigation",is_history_navigation);
+ GETTER("signal", signal);
+ GETTER("duplex", duplex);
+ GETTER("body", body);
+ GETTER("bodyUsed", body_used);
+#undef GETTER
+
+ js_set_sym(js, g_request_proto, get_toStringTag_sym(), js_mkstr(js, "Request", 7));
+ ant_value_t ctor = js_make_ctor(js, js_request_ctor, g_request_proto, "Request", 7);
+
+ js_set(js, g, "Request", ctor);
+ js_set_descriptor(js, g, "Request", 7, JS_DESC_W | JS_DESC_C);
+}
diff --git a/src/modules/textcodec.c b/src/modules/textcodec.c
index 4652acb..ad79333 100644
--- a/src/modules/textcodec.c
+++ b/src/modules/textcodec.c
@@ -1,501 +1,464 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "ant.h"
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "descriptors.h"
#include "utf8.h"
#include "modules/textcodec.h"
#include "modules/buffer.h"
#include "modules/symbol.h"
static ant_value_t g_textencoder_proto = 0;
static ant_value_t g_textdecoder_proto = 0;
td_state_t *td_state_new(td_encoding_t enc, bool fatal, bool ignore_bom) {
td_state_t *st = calloc(1, sizeof(td_state_t));
if (!st) return NULL;
st->encoding = enc;
st->fatal = fatal;
st->ignore_bom = ignore_bom;
return st;
}
static td_state_t *td_get_state(ant_value_t obj) {
ant_value_t s = js_get_slot(obj, SLOT_DATA);
if (vtype(s) != T_NUM) return NULL;
return (td_state_t *)(uintptr_t)(size_t)js_getnum(s);
}
static void td_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
free((td_state_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
return;
}}
}
static int resolve_encoding(const char *s, size_t len) {
static const struct { const char *label; uint8_t len; td_encoding_t enc; } map[] = {
{"unicode-1-1-utf-8", 17, TD_ENC_UTF8}, {"unicode11utf8", 13, TD_ENC_UTF8},
{"unicode20utf8", 13, TD_ENC_UTF8}, {"utf-8", 5, TD_ENC_UTF8},
{"utf8", 4, TD_ENC_UTF8}, {"x-unicode20utf8",17, TD_ENC_UTF8},
{"windows-1252", 12, TD_ENC_WINDOWS_1252}, {"ascii", 5, TD_ENC_WINDOWS_1252},
{"unicodefffe", 11, TD_ENC_UTF16BE}, {"utf-16be", 8, TD_ENC_UTF16BE},
{"csunicode", 9, TD_ENC_UTF16LE}, {"iso-10646-ucs-2",16, TD_ENC_UTF16LE},
{"ucs-2", 5, TD_ENC_UTF16LE}, {"unicode", 7, TD_ENC_UTF16LE},
{"unicodefeff", 11, TD_ENC_UTF16LE}, {"utf-16", 6, TD_ENC_UTF16LE},
{"utf-16le", 8, TD_ENC_UTF16LE},
{"iso-8859-2", 10, TD_ENC_ISO_8859_2},
{NULL, 0, 0}
};
for (int i = 0; map[i].label; i++) {
if (len == map[i].len && strncasecmp(s, map[i].label, len) == 0) return (int)map[i].enc;
}
return -1;
}
static const char *encoding_name(td_encoding_t enc) {
switch (enc) {
case TD_ENC_UTF16LE: return "utf-16le";
case TD_ENC_UTF16BE: return "utf-16be";
case TD_ENC_WINDOWS_1252: return "windows-1252";
case TD_ENC_ISO_8859_2: return "iso-8859-2";
default: return "utf-8";
}}
static const char *trim_label(const char *s, size_t len, size_t *out_len) {
while (len > 0 && (unsigned char)*s <= 0x20) { s++; len--; }
while (len > 0 && (unsigned char)s[len - 1] <= 0x20) { len--; }
*out_len = len;
return s;
}
-static bool get_buffer_source(ant_t *js, ant_value_t arg, const uint8_t **out, size_t *len) {
- ant_value_t slot = js_get_slot(arg, SLOT_BUFFER);
- TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(slot);
- if (ta) {
- if (!ta->buffer || ta->buffer->is_detached) { *out = NULL; *len = 0; return true; }
- *out = ta->buffer->data + ta->byte_offset;
- *len = ta->byte_length;
- return true;
- }
-
- if (vtype(slot) == T_NUM) {
- ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(slot);
- if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; }
- *out = ab->data;
- *len = ab->length;
- return true;
- }
-
- ant_value_t buf_prop = js_get(js, arg, "buffer");
- if (is_object_type(buf_prop)) {
- ant_value_t buf_slot = js_get_slot(buf_prop, SLOT_BUFFER);
-
- if (vtype(buf_slot) == T_NUM) {
- ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(buf_slot);
- if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; }
- ant_value_t off_v = js_get(js, arg, "byteOffset");
- ant_value_t len_v = js_get(js, arg, "byteLength");
- size_t off = (vtype(off_v) == T_NUM) ? (size_t)js_getnum(off_v) : 0;
- size_t blen = (vtype(len_v) == T_NUM) ? (size_t)js_getnum(len_v) : ab->length - off;
- *out = ab->data + off;
- *len = blen;
- return true;
- }}
-
- return false;
-}
-
static ant_value_t js_textencoder_get_encoding(ant_t *js, ant_value_t *args, int nargs) {
return js_mkstr(js, "utf-8", 5);
}
ant_value_t te_encode(ant_t *js, const char *str, size_t str_len) {
ArrayBufferData *ab = create_array_buffer_data(str_len);
if (!ab) return js_mkerr(js, "out of memory");
if (str_len > 0) {
const uint8_t *s = (const uint8_t *)str;
uint8_t *d = ab->data; size_t i = 0;
while (i < str_len) {
if (s[i] == 0xED && i + 2 < str_len && s[i+1] >= 0xA0 && s[i+1] <= 0xBF) {
d[i] = 0xEF; d[i+1] = 0xBF; d[i+2] = 0xBD;
i += 3;
} else { d[i] = s[i]; i++; }}
}
return create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, str_len, "Uint8Array");
}
static ant_value_t js_textencoder_encode(ant_t *js, ant_value_t *args, int nargs) {
size_t str_len = 0;
const char *str = "";
if (nargs > 0 && vtype(args[0]) == T_STR) {
str = js_getstr(js, args[0], &str_len);
if (!str) { str = ""; str_len = 0; }
} else if (nargs > 0 && vtype(args[0]) != T_UNDEF) {
ant_value_t sv = js_tostring_val(js, args[0]);
if (is_err(sv)) return sv;
str = js_getstr(js, sv, &str_len);
if (!str) { str = ""; str_len = 0; }
}
return te_encode(js, str, str_len);
}
static ant_value_t js_textencoder_encode_into(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "encodeInto requires 2 arguments");
size_t str_len = 0;
const char *str = "";
if (vtype(args[0]) == T_STR) {
str = js_getstr(js, args[0], &str_len);
if (!str) { str = ""; str_len = 0; }
} else if (vtype(args[0]) != T_UNDEF) {
ant_value_t sv = js_tostring_val(js, args[0]);
if (is_err(sv)) return sv;
str = js_getstr(js, sv, &str_len);
if (!str) { str = ""; str_len = 0; }
}
TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(js_get_slot(args[1], SLOT_BUFFER));
if (!ta) return js_mkerr_typed(js, JS_ERR_TYPE, "Second argument must be a Uint8Array");
uint8_t *dest = (ta->buffer && !ta->buffer->is_detached)
? ta->buffer->data + ta->byte_offset : NULL;
size_t available = ta->byte_length;
const utf8proc_uint8_t *src = (const utf8proc_uint8_t *)str;
utf8proc_ssize_t src_len = (utf8proc_ssize_t)str_len;
utf8proc_ssize_t pos = 0;
size_t written = 0;
size_t read_units = 0;
while (pos < src_len) {
utf8proc_int32_t cp;
utf8proc_ssize_t n = utf8_next(src + pos, src_len - pos, &cp);
utf8proc_uint8_t tmp[4];
utf8proc_ssize_t enc_len;
if (cp >= 0xD800 && cp <= 0xDFFF) {
tmp[0] = 0xEF; tmp[1] = 0xBF; tmp[2] = 0xBD;
enc_len = 3;
} else {
enc_len = (cp >= 0) ? utf8proc_encode_char(cp, tmp) : 0;
if (enc_len <= 0) { tmp[0] = 0xEF; tmp[1] = 0xBF; tmp[2] = 0xBD; enc_len = 3; }
}
if (written + (size_t)enc_len > available) break;
if (dest) memcpy(dest + written, tmp, (size_t)enc_len);
written += (size_t)enc_len;
pos += n;
read_units += (cp >= 0x10000 && cp <= 0x10FFFF) ? 2 : 1;
}
ant_value_t result = js_mkobj(js);
js_set(js, result, "read", js_mknum((double)read_units));
js_set(js, result, "written", js_mknum((double)written));
return result;
}
static ant_value_t js_textencoder_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "TextEncoder constructor requires 'new'");
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_textencoder_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
return obj;
}
static ant_value_t js_textdecoder_get_encoding(ant_t *js, ant_value_t *args, int nargs) {
td_state_t *st = td_get_state(js->this_val);
const char *name = encoding_name(st ? st->encoding : TD_ENC_UTF8);
return js_mkstr(js, name, strlen(name));
}
static ant_value_t js_textdecoder_get_fatal(ant_t *js, ant_value_t *args, int nargs) {
td_state_t *st = td_get_state(js->this_val);
return (st && st->fatal) ? js_true : js_false;
}
static ant_value_t js_textdecoder_get_ignore_bom(ant_t *js, ant_value_t *args, int nargs) {
td_state_t *st = td_get_state(js->this_val);
return (st && st->ignore_bom) ? js_true : js_false;
}
static inline uint16_t u16_read(const uint8_t *p, bool be) {
return be
? (uint16_t)((uint16_t)p[0] << 8 | p[1])
: (uint16_t)((uint16_t)p[1] << 8 | p[0]);
}
static inline size_t u8_emit(char *out, size_t o, utf8proc_int32_t cp) {
utf8proc_ssize_t n = utf8proc_encode_char(cp, (utf8proc_uint8_t *)(out + o));
return n > 0 ? o + (size_t)n : o;
}
static inline size_t u8_fffd(char *out, size_t o) {
out[o] = (char)0xEF; out[o+1] = (char)0xBF; out[o+2] = (char)0xBD;
return o + 3;
}
#define U16_IS_HIGH(cu) ((cu) >= 0xD800 && (cu) <= 0xDBFF)
#define U16_IS_LOW(cu) ((cu) >= 0xDC00 && (cu) <= 0xDFFF)
#define U16_PAIR(hi,lo) (0x10000 + ((uint32_t)((hi) - 0xD800) << 10) + ((lo) - 0xDC00))
static uint32_t decode_windows_1252_byte(uint8_t byte) {
static const uint16_t specials[32] = {
0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F,
0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178,
};
if (byte < 0x80) return byte;
if (byte < 0xA0) return specials[byte - 0x80];
return byte;
}
static uint32_t decode_iso_8859_2_byte(uint8_t byte) {
static const uint16_t upper[96] = {
0x00A0, 0x0104, 0x02D8, 0x0141, 0x00A4, 0x013D, 0x015A, 0x00A7,
0x00A8, 0x0160, 0x015E, 0x0164, 0x0179, 0x00AD, 0x017D, 0x017B,
0x00B0, 0x0105, 0x02DB, 0x0142, 0x00B4, 0x013E, 0x015B, 0x02C7,
0x00B8, 0x0161, 0x015F, 0x0165, 0x017A, 0x02DD, 0x017E, 0x017C,
0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7,
0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E,
0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7,
0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF,
0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7,
0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F,
0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7,
0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9,
};
if (byte < 0xA0) return byte;
return upper[byte - 0xA0];
}
static utf8proc_ssize_t decode_single_byte(td_state_t *st, const uint8_t *src, size_t len, char *out) {
size_t o = 0;
for (size_t i = 0; i < len; i++) {
uint32_t cp = (st->encoding == TD_ENC_WINDOWS_1252)
? decode_windows_1252_byte(src[i])
: decode_iso_8859_2_byte(src[i]);
o = u8_emit(out, o, (utf8proc_int32_t)cp);
}
return (utf8proc_ssize_t)o;
}
static utf8proc_ssize_t utf16_decode(td_state_t *st, const uint8_t *src, size_t len, char *out, bool stream) {
bool be = (st->encoding == TD_ENC_UTF16BE);
size_t i = 0, o = 0;
size_t avail;
if (!st->bom_seen) {
if (len < 2) goto pend_tail;
if (u16_read(src, be) == 0xFEFF && !st->ignore_bom) i = 2;
st->bom_seen = true;
}
while (i < len) {
avail = len - i;
if (avail < 2) goto pend_tail;
uint16_t cu = u16_read(src + i, be);
i += 2;
if (!U16_IS_HIGH(cu) && !U16_IS_LOW(cu)) {
o = u8_emit(out, o, (utf8proc_int32_t)cu);
continue;
}
if (U16_IS_LOW(cu)) goto err;
avail = len - i;
if (avail < 2) goto pend_hi;
uint16_t lo = u16_read(src + i, be);
if (U16_IS_LOW(lo)) { i += 2; o = u8_emit(out, o, (utf8proc_int32_t)U16_PAIR(cu, lo)); continue; }
goto err;
pend_tail:
if (stream) { st->pending[0] = src[i]; st->pending_len = 1; }
else { if (st->fatal) return -1; o = u8_fffd(out, o); }
break;
pend_hi:
if (stream) { st->pending_len = (int)(len - (i - 2)); memcpy(st->pending, src + i - 2, (size_t)st->pending_len); }
else { if (st->fatal) return -1; o = u8_fffd(out, o); if (avail == 1) o = u8_fffd(out, o); }
break;
err:
if (st->fatal) return -1;
o = u8_fffd(out, o);
continue;
}
return (utf8proc_ssize_t)o;
}
#undef U16_IS_HIGH
#undef U16_IS_LOW
#undef U16_PAIR
ant_value_t td_decode(ant_t *js, td_state_t *st, const uint8_t *input, size_t input_len, bool stream_mode) {
size_t total = (size_t)st->pending_len + input_len;
if (total == 0) {
if (!stream_mode) st->bom_seen = false;
return js_mkstr(js, "", 0);
}
uint8_t *work = NULL;
const uint8_t *src;
if (st->pending_len > 0) {
work = malloc(total);
if (!work) return js_mkerr(js, "out of memory");
memcpy(work, st->pending, (size_t)st->pending_len);
if (input && input_len > 0) memcpy(work + st->pending_len, input, input_len);
src = work;
} else src = input;
st->pending_len = 0;
char *out = malloc(total * 3 + 1);
if (!out) { free(work); return js_mkerr(js, "out of memory"); }
utf8proc_ssize_t n;
if (st->encoding == TD_ENC_UTF16LE || st->encoding == TD_ENC_UTF16BE) {
n = utf16_decode(st, src, total, out, stream_mode);
} else if (st->encoding == TD_ENC_WINDOWS_1252 || st->encoding == TD_ENC_ISO_8859_2) {
n = decode_single_byte(st, src, total, out);
st->pending_len = 0;
st->bom_seen = false;
} else {
utf8_dec_t dec = { .ignore_bom = st->ignore_bom, .bom_seen = st->bom_seen };
n = utf8_whatwg_decode(&dec, src, total, out, st->fatal, stream_mode);
st->pending_len = dec.pend_pos;
memcpy(st->pending, dec.pend_buf, (size_t)dec.pend_pos);
st->bom_seen = stream_mode ? dec.bom_seen : false;
}
if (n < 0) {
free(work); free(out);
return js_mkerr_typed(js, JS_ERR_TYPE, "The encoded data was not valid.");
}
if (st->encoding != TD_ENC_UTF8) {
if (!stream_mode) st->bom_seen = false;
}
ant_value_t result = js_mkstr(js, out, (size_t)n);
free(work);
free(out);
return result;
}
static ant_value_t js_textdecoder_decode(ant_t *js, ant_value_t *args, int nargs) {
td_state_t *st = td_get_state(js->this_val);
if (!st) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TextDecoder");
bool stream_mode = false;
if (nargs > 1 && is_object_type(args[1])) {
ant_value_t sv = js_get(js, args[1], "stream");
stream_mode = js_truthy(js, sv);
}
const uint8_t *input = NULL;
size_t input_len = 0;
if (nargs > 0 && is_object_type(args[0]))
- get_buffer_source(js, args[0], &input, &input_len);
+ buffer_source_get_bytes(js, args[0], &input, &input_len);
return td_decode(js, st, input, input_len, stream_mode);
}
static ant_value_t js_textdecoder_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "TextDecoder constructor requires 'new'");
td_encoding_t enc = TD_ENC_UTF8;
if (nargs > 0 && !is_undefined(args[0])) {
ant_value_t label = (vtype(args[0]) == T_STR) ? args[0] : coerce_to_str(js, args[0]);
if (is_err(label)) return label;
size_t llen;
const char *raw = js_getstr(js, label, &llen);
if (raw) {
size_t tlen;
const char *trimmed = trim_label(raw, llen, &tlen);
int resolved = resolve_encoding(trimmed, tlen);
if (resolved < 0) return js_mkerr_typed(
js, JS_ERR_RANGE, "Failed to construct 'TextDecoder': The encoding label provided ('%.*s') is invalid.",
(int)tlen, trimmed
);
enc = (td_encoding_t)resolved;
}}
bool fatal = false;
bool ignore_bom = false;
if (nargs > 1 && is_object_type(args[1])) {
ant_value_t fv = js_getprop_fallback(js, args[1], "fatal");
if (is_err(fv)) return fv;
if (vtype(fv) != T_UNDEF) fatal = js_truthy(js, fv);
ant_value_t bv = js_getprop_fallback(js, args[1], "ignoreBOM");
if (is_err(bv)) return bv;
if (vtype(bv) != T_UNDEF) ignore_bom = js_truthy(js, bv);
}
td_state_t *st = td_state_new(enc, fatal, ignore_bom);
if (!st) return js_mkerr(js, "out of memory");
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_textdecoder_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
js_set_slot(obj, SLOT_DATA, ANT_PTR(st));
js_set_finalizer(obj, td_finalize);
return obj;
}
void init_textcodec_module(void) {
ant_t *js = rt->js;
ant_value_t g = js_glob(js);
g_textencoder_proto = js_mkobj(js);
js_set_getter_desc(js, g_textencoder_proto, "encoding", 8, js_mkfun(js_textencoder_get_encoding), JS_DESC_C);
js_set(js, g_textencoder_proto, "encode", js_mkfun(js_textencoder_encode));
js_set(js, g_textencoder_proto, "encodeInto", js_mkfun(js_textencoder_encode_into));
js_set_sym(js, g_textencoder_proto, get_toStringTag_sym(), js_mkstr(js, "TextEncoder", 11));
ant_value_t te_ctor = js_make_ctor(js, js_textencoder_ctor, g_textencoder_proto, "TextEncoder", 11);
js_set(js, g, "TextEncoder", te_ctor);
js_set_descriptor(js, g, "TextEncoder", 11, JS_DESC_W | JS_DESC_C);
g_textdecoder_proto = js_mkobj(js);
js_set_getter_desc(js, g_textdecoder_proto, "encoding", 8, js_mkfun(js_textdecoder_get_encoding), JS_DESC_C);
js_set_getter_desc(js, g_textdecoder_proto, "fatal", 5, js_mkfun(js_textdecoder_get_fatal), JS_DESC_C);
js_set_getter_desc(js, g_textdecoder_proto, "ignoreBOM", 9, js_mkfun(js_textdecoder_get_ignore_bom), JS_DESC_C);
js_set(js, g_textdecoder_proto, "decode", js_mkfun(js_textdecoder_decode));
js_set_sym(js, g_textdecoder_proto, get_toStringTag_sym(), js_mkstr(js, "TextDecoder", 11));
ant_value_t td_ctor = js_make_ctor(js, js_textdecoder_ctor, g_textdecoder_proto, "TextDecoder", 11);
js_set(js, g, "TextDecoder", td_ctor);
js_set_descriptor(js, g, "TextDecoder", 11, JS_DESC_W | JS_DESC_C);
}
diff --git a/src/modules/url.c b/src/modules/url.c
index ccc2ed5..d4f50d5 100644
--- a/src/modules/url.c
+++ b/src/modules/url.c
@@ -1,1323 +1,1331 @@
#include <compat.h> // IWYU pragma: keep
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <uriparser/Uri.h>
#include "ant.h"
#include "errors.h"
#include "internal.h"
#include "runtime.h"
#include "descriptors.h"
#include "silver/engine.h"
#include "modules/url.h"
#include "modules/symbol.h"
static ant_value_t g_url_proto = 0;
static ant_value_t g_usp_proto = 0;
static ant_value_t g_usp_iter_proto = 0;
enum {
USP_ITER_ENTRIES = 0,
USP_ITER_KEYS = 1,
USP_ITER_VALUES = 2
};
url_state_t *url_get_state(ant_value_t obj) {
ant_value_t slot = js_get_slot(obj, SLOT_DATA);
if (vtype(slot) != T_NUM) return NULL;
return (url_state_t *)(uintptr_t)(size_t)js_getnum(slot);
}
+bool usp_is_urlsearchparams(ant_t *js, ant_value_t obj) {
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ return vtype(brand) == T_NUM && (int)js_getnum(brand) == BRAND_URLSEARCHPARAMS;
+}
+
void url_state_clear(url_state_t *s) {
free(s->protocol); free(s->username); free(s->password);
free(s->hostname); free(s->port); free(s->pathname);
free(s->search); free(s->hash);
}
void url_free_state(url_state_t *s) {
if (!s) return;
url_state_clear(s);
free(s);
}
static void url_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *slots = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (slots[i].slot == SLOT_DATA && vtype(slots[i].value) == T_NUM) {
url_free_state((url_state_t *)(uintptr_t)(size_t)js_getnum(slots[i].value));
return;
}}
}
static int default_port_for(const char *proto) {
if (!proto) return -1;
if (strcmp(proto, "http:") == 0 || strcmp(proto, "ws:") == 0) return 80;
if (strcmp(proto, "https:") == 0 || strcmp(proto, "wss:") == 0) return 443;
if (strcmp(proto, "ftp:") == 0) return 21;
return -1;
}
static bool is_special_scheme(const char *proto) {
if (!proto) return false;
return
strcmp(proto, "http:") == 0 || strcmp(proto, "https:") == 0 ||
strcmp(proto, "ftp:") == 0 || strcmp(proto, "ws:") == 0 ||
strcmp(proto, "wss:") == 0;
}
char *form_urlencode_n(const char *str, size_t len) {
if (!str) return strdup("");
char *out = malloc(len * 3 + 1);
if (!out) return strdup("");
size_t j = 0;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)str[i];
if (isalnum(c) || c == '*' || c == '-' || c == '.' || c == '_') out[j++] = (char)c;
else if (c == ' ') out[j++] = '+';
else {
snprintf(out + j, 4, "%%%02X", c);
j += 3;
}}
out[j] = '\0';
return out;
}
char *form_urlencode(const char *str) {
if (!str) return strdup("");
return form_urlencode_n(str, strlen(str));
}
char *form_urldecode(const char *str) {
if (!str) return strdup("");
size_t len = strlen(str);
char *out = malloc(len + 1);
if (!out) return strdup("");
size_t j = 0;
for (size_t i = 0; i < len; i++) {
if (str[i] == '+') out[j++] = ' ';
else if (
str[i] == '%' && i + 2 < len &&
isxdigit((unsigned char)str[i+1]) &&
isxdigit((unsigned char)str[i+2])
) {
int hi = isdigit((unsigned char)str[i+1]) ? str[i+1]-'0' : tolower((unsigned char)str[i+1])-'a'+10;
int lo = isdigit((unsigned char)str[i+2]) ? str[i+2]-'0' : tolower((unsigned char)str[i+2])-'a'+10;
out[j++] = (char)((hi << 4) | lo);
i += 2;
} else out[j++] = str[i];
}
out[j] = '\0';
return out;
}
char *url_decode_component(const char *str) {
if (!str) return strdup("");
size_t len = strlen(str);
char *out = malloc(len + 1);
if (!out) return strdup("");
size_t j = 0;
for (size_t i = 0; i < len; i++) {
if (
str[i] == '%' && i + 2 < len &&
isxdigit((unsigned char)str[i+1]) &&
isxdigit((unsigned char)str[i+2])
) {
int hi = isdigit((unsigned char)str[i+1]) ? str[i+1]-'0' : tolower((unsigned char)str[i+1])-'a'+10;
int lo = isdigit((unsigned char)str[i+2]) ? str[i+2]-'0' : tolower((unsigned char)str[i+2])-'a'+10;
out[j++] = (char)((hi << 4) | lo);
i += 2;
} else out[j++] = str[i];
}
out[j] = '\0';
return out;
}
static char *userinfo_encode(const char *str) {
if (!str) return strdup("");
size_t len = strlen(str);
char *out = malloc(len * 3 + 1);
if (!out) return strdup("");
size_t j = 0;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)str[i];
if (
isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~' ||
c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
c == '=' || c == ':'
) out[j++] = (char)c; else { snprintf(out + j, 4, "%%%02X", c); j += 3; }
}
out[j] = '\0';
return out;
}
static char *uri_range_dup(const UriTextRangeA *r) {
if (!r->first || !r->afterLast) return strdup("");
return strndup(r->first, (size_t)(r->afterLast - r->first));
}
static void uri_to_state(const UriUriA *uri, url_state_t *s) {
char *scheme = uri_range_dup(&uri->scheme);
size_t slen = strlen(scheme);
for (size_t i = 0; i < slen; i++) scheme[i] = (char)tolower((unsigned char)scheme[i]);
s->protocol = malloc(slen + 2);
memcpy(s->protocol, scheme, slen);
s->protocol[slen] = ':';
s->protocol[slen + 1] = '\0';
free(scheme);
char *userinfo = uri_range_dup(&uri->userInfo);
char *colon = strchr(userinfo, ':');
if (colon) {
*colon = '\0';
s->username = strdup(userinfo);
s->password = strdup(colon + 1);
} else {
s->username = strdup(userinfo);
s->password = strdup("");
}
free(userinfo);
s->hostname = uri_range_dup(&uri->hostText);
char *port = uri_range_dup(&uri->portText);
int def = default_port_for(s->protocol);
if (def > 0 && *port && atoi(port) == def) {
free(port);
port = strdup("");
} s->port = port;
size_t path_cap = 2;
for (UriPathSegmentA *seg = uri->pathHead; seg; seg = seg->next)
path_cap += (size_t)(seg->text.afterLast - seg->text.first) + 1;
char *path = malloc(path_cap + 1);
size_t pos = 0;
for (UriPathSegmentA *seg = uri->pathHead; seg; seg = seg->next) {
path[pos++] = '/';
size_t seglen = (size_t)(seg->text.afterLast - seg->text.first);
memcpy(path + pos, seg->text.first, seglen);
pos += seglen;
}
if (pos == 0) path[pos++] = '/';
path[pos] = '\0';
s->pathname = path;
char *query = uri_range_dup(&uri->query);
if (*query) {
size_t qlen = strlen(query);
s->search = malloc(qlen + 2);
s->search[0] = '?';
memcpy(s->search + 1, query, qlen + 1);
} else s->search = strdup("");
free(query);
char *frag = uri_range_dup(&uri->fragment);
if (*frag) {
size_t flen = strlen(frag);
s->hash = malloc(flen + 2);
s->hash[0] = '#';
memcpy(s->hash + 1, frag, flen + 1);
} else s->hash = strdup("");
free(frag);
}
int parse_url_to_state(const char *url_str, const char *base_str, url_state_t *s) {
memset(s, 0, sizeof(*s));
const char *errpos;
if (base_str) {
UriUriA base_uri, ref_uri, resolved;
if (uriParseSingleUriA(&base_uri, base_str, &errpos) != URI_SUCCESS) return -1;
char *base_scheme = uri_range_dup(&base_uri.scheme);
size_t bslen = strlen(base_scheme);
for (size_t i = 0; i < bslen; i++) base_scheme[i] = (char)tolower((unsigned char)base_scheme[i]);
char proto_buf[bslen + 2];
memcpy(proto_buf, base_scheme, bslen);
proto_buf[bslen] = ':';
proto_buf[bslen + 1] = '\0';
free(base_scheme);
bool base_special = is_special_scheme(proto_buf);
if (!base_special) {
const char *after_colon = strchr(base_str, ':');
if (after_colon) {
after_colon++;
bool is_opaque = (*after_colon != '/' && *after_colon != '\0');
if (is_opaque) { uriFreeUriMembersA(&base_uri); return -1; }
}}
if (uriParseSingleUriA(&ref_uri, url_str, &errpos) != URI_SUCCESS) {
uriFreeUriMembersA(&base_uri);
return -1;
}
if (uriAddBaseUriA(&resolved, &ref_uri, &base_uri) != URI_SUCCESS) {
uriFreeUriMembersA(&base_uri);
uriFreeUriMembersA(&ref_uri);
return -1;
}
uriNormalizeSyntaxA(&resolved);
if (!resolved.scheme.first || resolved.scheme.first == resolved.scheme.afterLast) {
uriFreeUriMembersA(&resolved);
uriFreeUriMembersA(&ref_uri);
uriFreeUriMembersA(&base_uri);
return -1;
}
uri_to_state(&resolved, s);
uriFreeUriMembersA(&resolved);
uriFreeUriMembersA(&ref_uri);
uriFreeUriMembersA(&base_uri);
return 0;
}
UriUriA uri;
if (uriParseSingleUriA(&uri, url_str, &errpos) != URI_SUCCESS) return -1;
if (!uri.scheme.first || uri.scheme.first == uri.scheme.afterLast) {
uriFreeUriMembersA(&uri);
return -1;
}
uriNormalizeSyntaxA(&uri);
uri_to_state(&uri, s);
uriFreeUriMembersA(&uri);
return 0;
}
char *build_href(const url_state_t *s) {
size_t len = strlen(s->protocol) + 2;
len += strlen(s->hostname) + strlen(s->pathname) + strlen(s->search) + strlen(s->hash) + 32;
if (s->username && *s->username) len += strlen(s->username) + 1;
if (s->password && *s->password) len += strlen(s->password) + 1;
if (s->port && *s->port) len += strlen(s->port) + 1;
char *href = malloc(len);
if (!href) return strdup("");
size_t pos = 0;
pos += (size_t)sprintf(href + pos, "%s//", s->protocol);
if (s->username && *s->username) {
pos += (size_t)sprintf(href + pos, "%s", s->username);
if (s->password && *s->password)
pos += (size_t)sprintf(href + pos, ":%s", s->password);
href[pos++] = '@';
}
pos += (size_t)sprintf(href + pos, "%s", s->hostname);
if (s->port && *s->port)
pos += (size_t)sprintf(href + pos, ":%s", s->port);
pos += (size_t)sprintf(href + pos, "%s%s%s", s->pathname, s->search, s->hash);
href[pos] = '\0';
return href;
}
static const char *coerce_to_string(ant_t *js, ant_value_t val, size_t *len) {
if (vtype(val) == T_STR) return js_getstr(js, val, len);
if (is_object_type(val)) {
ant_value_t href = js_getprop_fallback(js, val, "href");
if (vtype(href) == T_STR) return js_getstr(js, href, len);
}
return NULL;
}
static ant_value_t parse_query_to_arr(ant_t *js, const char *query) {
ant_value_t arr = js_mkarr(js);
if (!query || !*query) return arr;
const char *p = query;
while (*p) {
const char *amp = strchr(p, '&');
size_t plen = amp ? (size_t)(amp - p) : strlen(p);
if (plen == 0) { p = amp ? amp + 1 : p + 1; continue; }
char *pair = strndup(p, plen);
if (!pair) { p = amp ? amp + 1 : p + plen; continue; }
char *eq = strchr(pair, '=');
char *raw_v = eq ? ((*eq = '\0'), eq + 1) : "";
char *k = form_urldecode(pair);
char *v = form_urldecode(raw_v);
ant_value_t entry = js_mkarr(js);
js_arr_push(js, entry, js_mkstr(js, k, strlen(k)));
js_arr_push(js, entry, js_mkstr(js, v, strlen(v)));
js_arr_push(js, arr, entry);
free(pair); free(k); free(v);
p = amp ? amp + 1 : p + plen;
}
return arr;
}
char *usp_serialize(ant_t *js, ant_value_t usp) {
ant_value_t entries = js_get_slot(usp, SLOT_ENTRIES);
ant_offset_t len = is_special_object(entries) ? js_arr_len(js, entries) : 0;
size_t cap = 256;
char *buf = malloc(cap);
if (!buf) return strdup("");
size_t pos = 0;
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
size_t klen = 0, vlen = 0;
char *k = js_getstr(js, js_arr_get(js, entry, 0), &klen);
char *v = js_getstr(js, js_arr_get(js, entry, 1), &vlen);
char *ek = k ? form_urlencode_n(k, klen) : strdup("");
char *ev = v ? form_urlencode_n(v, vlen) : strdup("");
size_t needed = strlen(ek) + strlen(ev) + 3;
if (pos + needed >= cap) {
cap = cap * 2 + needed;
buf = realloc(buf, cap);
if (!buf) { free(ek); free(ev); return strdup(""); }
}
if (pos > 0) buf[pos++] = '&';
pos += (size_t)sprintf(buf + pos, "%s=%s", ek, ev);
free(ek); free(ev);
}
buf[pos] = '\0';
return buf;
}
static void usp_push_to_url(ant_t *js, ant_value_t usp) {
ant_value_t url_obj = js_get_slot(usp, SLOT_DATA);
if (!is_special_object(url_obj)) return;
url_state_t *s = url_get_state(url_obj);
if (!s) return;
char *qs = usp_serialize(js, usp);
free(s->search);
if (*qs) {
size_t qlen = strlen(qs);
s->search = malloc(qlen + 2);
s->search[0] = '?';
memcpy(s->search + 1, qs, qlen + 1);
} else s->search = strdup("");
free(qs);
}
static void url_sync_usp(ant_t *js, ant_value_t url_obj, const char *query) {
ant_value_t usp = js_get_slot(url_obj, SLOT_ENTRIES);
if (!is_special_object(usp)) return;
ant_value_t new_entries = parse_query_to_arr(js, query);
js_set_slot_wb(js, usp, SLOT_ENTRIES, new_entries);
}
static ant_value_t make_usp_for_url(ant_t *js, ant_value_t url_obj, const char *query) {
ant_value_t usp = js_mkobj(js);
js_set_proto_init(usp, g_usp_proto);
+ js_set_slot(usp, SLOT_BRAND, js_mknum(BRAND_URLSEARCHPARAMS));
js_set_slot_wb(js, usp, SLOT_DATA, url_obj);
js_set_slot(usp, SLOT_ENTRIES, parse_query_to_arr(js, query));
return usp;
}
static ant_value_t url_get_href(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
char *href = build_href(s);
ant_value_t ret = js_mkstr(js, href, strlen(href));
free(href);
return ret;
}
static ant_value_t url_get_protocol(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->protocol, strlen(s->protocol));
}
static ant_value_t url_get_username(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->username, strlen(s->username));
}
static ant_value_t url_get_password(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->password, strlen(s->password));
}
static ant_value_t url_get_host(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
if (s->port && *s->port) {
size_t len = strlen(s->hostname) + strlen(s->port) + 2;
char *host = malloc(len);
snprintf(host, len, "%s:%s", s->hostname, s->port);
ant_value_t ret = js_mkstr(js, host, strlen(host));
free(host);
return ret;
}
return js_mkstr(js, s->hostname, strlen(s->hostname));
}
static ant_value_t url_get_hostname(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->hostname, strlen(s->hostname));
}
static ant_value_t url_get_port(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->port, strlen(s->port));
}
static ant_value_t url_get_pathname(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "/", 1);
return js_mkstr(js, s->pathname, strlen(s->pathname));
}
static ant_value_t url_get_search(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->search, strlen(s->search));
}
static ant_value_t url_get_hash(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->hash, strlen(s->hash));
}
static ant_value_t url_get_origin(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s || !is_special_scheme(s->protocol)) return js_mkstr(js, "null", 4);
size_t proto_len = strlen(s->protocol) - 1;
size_t host_len = strlen(s->hostname);
size_t port_len = (s->port && *s->port) ? strlen(s->port) + 1 : 0;
size_t total = proto_len + 3 + host_len + port_len + 1;
char *origin = malloc(total);
size_t pos = 0;
memcpy(origin + pos, s->protocol, proto_len); pos += proto_len;
memcpy(origin + pos, "://", 3); pos += 3;
memcpy(origin + pos, s->hostname, host_len); pos += host_len;
if (s->port && *s->port) {
origin[pos++] = ':';
memcpy(origin + pos, s->port, strlen(s->port));
pos += strlen(s->port);
}
origin[pos] = '\0';
ant_value_t ret = js_mkstr(js, origin, pos);
free(origin);
return ret;
}
static ant_value_t url_get_searchParams(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t usp = js_get_slot(js->this_val, SLOT_ENTRIES);
if (vtype(usp) == T_OBJ) return usp;
return js_mkundef();
}
static ant_value_t url_set_href(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkerr(js, "TypeError: Invalid URL");
url_state_t tmp;
if (parse_url_to_state(val, NULL, &tmp) != 0)
return js_mkerr(js, "TypeError: Invalid URL");
free(s->protocol); s->protocol = tmp.protocol;
free(s->username); s->username = tmp.username;
free(s->password); s->password = tmp.password;
free(s->hostname); s->hostname = tmp.hostname;
free(s->port); s->port = tmp.port;
free(s->pathname); s->pathname = tmp.pathname;
free(s->search); s->search = tmp.search;
free(s->hash); s->hash = tmp.hash;
const char *q = (s->search[0] == '?') ? s->search + 1 : "";
url_sync_usp(js, js->this_val, q);
return js_mkundef();
}
static ant_value_t url_set_protocol(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val || !*val) return js_mkundef();
const char *colon = strchr(val, ':');
size_t slen = colon ? (size_t)(colon - val) : strlen(val);
if (!slen || !isalpha((unsigned char)val[0])) return js_mkundef();
for (size_t i = 1; i < slen; i++) {
unsigned char c = (unsigned char)val[i];
if (!isalnum(c) && c != '+' && c != '-' && c != '.') return js_mkundef();
}
free(s->protocol);
s->protocol = malloc(slen + 2);
for (size_t i = 0; i < slen; i++) s->protocol[i] = (char)tolower((unsigned char)val[i]);
s->protocol[slen] = ':';
s->protocol[slen + 1] = '\0';
if (s->port && *s->port) {
int def = default_port_for(s->protocol);
if (def > 0 && atoi(s->port) == def) { free(s->port); s->port = strdup(""); }
}
return js_mkundef();
}
static ant_value_t url_set_username(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
free(s->username);
s->username = userinfo_encode(val);
return js_mkundef();
}
static ant_value_t url_set_password(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
free(s->password);
s->password = userinfo_encode(val);
return js_mkundef();
}
static ant_value_t url_set_host(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
const char *colon = strchr(val, ':');
if (colon) {
free(s->hostname);
s->hostname = strndup(val, (size_t)(colon - val));
const char *port_str = colon + 1;
free(s->port);
if (*port_str) {
int p = atoi(port_str);
int def = default_port_for(s->protocol);
s->port = (def > 0 && p == def) ? strdup("") : strdup(port_str);
} else s->port = strdup("");
} else {
free(s->hostname);
s->hostname = strdup(val);
}
return js_mkundef();
}
static ant_value_t url_set_hostname(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
free(s->hostname);
const char *colon = strchr(val, ':');
s->hostname = colon ? strndup(val, (size_t)(colon - val)) : strdup(val);
return js_mkundef();
}
static ant_value_t url_set_port(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
free(s->port);
if (!*val) { s->port = strdup(""); return js_mkundef(); }
int p = atoi(val);
if (p < 0 || p > 65535) { s->port = strdup(""); return js_mkundef(); }
int def = default_port_for(s->protocol);
if (def > 0 && p == def) s->port = strdup(""); else {
char buf[8];
snprintf(buf, sizeof(buf), "%d", p);
s->port = strdup(buf);
}
return js_mkundef();
}
static ant_value_t url_set_pathname(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
free(s->pathname);
if (is_special_scheme(s->protocol) && val[0] != '/') {
size_t vlen = strlen(val);
s->pathname = malloc(vlen + 2);
s->pathname[0] = '/';
memcpy(s->pathname + 1, val, vlen + 1);
} else s->pathname = strdup(val);
return js_mkundef();
}
static ant_value_t url_set_search(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
const char *q = (val[0] == '?') ? val + 1 : val;
free(s->search);
if (*q) {
size_t qlen = strlen(q);
s->search = malloc(qlen + 2);
s->search[0] = '?';
memcpy(s->search + 1, q, qlen + 1);
} else s->search = strdup("");
url_sync_usp(js, js->this_val, *q ? q : "");
return js_mkundef();
}
static ant_value_t url_set_hash(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
const char *h = (val[0] == '#') ? val + 1 : val;
free(s->hash);
if (*h) {
size_t hlen = strlen(h);
s->hash = malloc(hlen + 2);
s->hash[0] = '#';
memcpy(s->hash + 1, h, hlen + 1);
} else s->hash = strdup("");
return js_mkundef();
}
static ant_value_t url_toString(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
char *href = build_href(s);
ant_value_t ret = js_mkstr(js, href, strlen(href));
free(href);
return ret;
}
static ant_value_t js_URL(ant_t *js, ant_value_t *args, int nargs) {
if (is_undefined(js->new_target))
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'URL': Please use the 'new' operator.");
if (nargs < 1)
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': 1 argument required.");
ant_value_t url_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(url_sv))
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': Invalid URL.");
const char *url_str = js_getstr(js, url_sv, NULL);
if (!url_str)
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': Invalid URL.");
const char *base_str = NULL;
if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
base_str = coerce_to_string(js, args[1], NULL);
url_state_t *s = calloc(1, sizeof(url_state_t));
if (!s) return js_mkerr(js, "out of memory");
if (parse_url_to_state(url_str, base_str, s) != 0) {
free(s);
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': Invalid URL.");
}
ant_value_t obj = js_mkobj(js);
js_set_proto_init(obj, g_url_proto);
js_set_slot(obj, SLOT_DATA, ANT_PTR(s));
js_set_finalizer(obj, url_finalize);
const char *query = (s->search && s->search[0] == '?') ? s->search + 1 : "";
ant_value_t usp = make_usp_for_url(js, obj, query);
js_set_slot_wb(js, obj, SLOT_ENTRIES, usp);
return obj;
}
ant_value_t make_url_obj(ant_t *js, url_state_t *s) {
ant_value_t obj = js_mkobj(js);
js_set_proto_init(obj, g_url_proto);
js_set_slot(obj, SLOT_DATA, ANT_PTR(s));
js_set_finalizer(obj, url_finalize);
const char *query = (s->search && s->search[0] == '?') ? s->search + 1 : "";
ant_value_t usp = make_usp_for_url(js, obj, query);
js_set_slot_wb(js, obj, SLOT_ENTRIES, usp);
return obj;
}
static ant_value_t url_canParse(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_false;
ant_value_t url_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(url_sv)) return js_false;
const char *url_str = js_getstr(js, url_sv, NULL);
if (!url_str) return js_false;
const char *base_str = NULL;
if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
base_str = coerce_to_string(js, args[1], NULL);
url_state_t s;
if (parse_url_to_state(url_str, base_str, &s) != 0) return js_false;
url_state_clear(&s);
return js_true;
}
static ant_value_t url_parse(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mknull();
ant_value_t url_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(url_sv)) return js_mknull();
const char *url_str = js_getstr(js, url_sv, NULL);
if (!url_str) return js_mknull();
const char *base_str = NULL;
if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
base_str = coerce_to_string(js, args[1], NULL);
url_state_t *s = calloc(1, sizeof(url_state_t));
if (!s) return js_mknull();
if (parse_url_to_state(url_str, base_str, s) != 0) {
free(s);
return js_mknull();
}
return make_url_obj(js, s);
}
static ant_value_t usp_get(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mknull();
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return js_mknull();
const char *key = js_getstr(js, key_sv, NULL);
if (!key) return js_mknull();
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_mknull();
ant_offset_t len = js_arr_len(js, entries);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
if (ek && strcmp(ek, key) == 0) return js_arr_get(js, entry, 1);
}
return js_mknull();
}
static ant_value_t usp_getAll(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t result = js_mkarr(js);
if (nargs < 1) return result;
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return result;
const char *key = js_getstr(js, key_sv, NULL);
if (!key) return result;
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return result;
ant_offset_t len = js_arr_len(js, entries);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
if (ek && strcmp(ek, key) == 0)
js_arr_push(js, result, js_arr_get(js, entry, 1));
}
return result;
}
static ant_value_t usp_has(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_false;
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return js_false;
const char *key = js_getstr(js, key_sv, NULL);
if (!key) return js_false;
const char *match_val = NULL;
if (nargs >= 2 && !is_undefined(args[1])) {
ant_value_t mv_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
if (!is_err(mv_sv)) match_val = js_getstr(js, mv_sv, NULL);
}
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_false;
ant_offset_t len = js_arr_len(js, entries);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
if (!ek || strcmp(ek, key) != 0) continue;
if (!match_val) return js_true;
const char *ev = js_getstr(js, js_arr_get(js, entry, 1), NULL);
if (ev && strcmp(ev, match_val) == 0) return js_true;
}
return js_false;
}
static ant_value_t usp_set(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkundef();
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return js_mkundef();
ant_value_t val_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
if (is_err(val_sv)) return js_mkundef();
const char *key = js_getstr(js, key_sv, NULL);
if (!key) return js_mkundef();
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
ant_offset_t len = is_special_object(entries) ? js_arr_len(js, entries) : 0;
ant_value_t new_entries = js_mkarr(js);
int found = 0;
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
if (ek && strcmp(ek, key) == 0) {
if (!found) {
ant_value_t ne = js_mkarr(js);
js_arr_push(js, ne, key_sv);
js_arr_push(js, ne, val_sv);
js_arr_push(js, new_entries, ne);
found = 1;
}
} else js_arr_push(js, new_entries, entry); }
if (!found) {
ant_value_t ne = js_mkarr(js);
js_arr_push(js, ne, key_sv);
js_arr_push(js, ne, val_sv);
js_arr_push(js, new_entries, ne);
}
js_set_slot_wb(js, js->this_val, SLOT_ENTRIES, new_entries);
usp_push_to_url(js, js->this_val);
return js_mkundef();
}
static ant_value_t usp_append(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkundef();
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return js_mkundef();
ant_value_t val_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
if (is_err(val_sv)) return js_mkundef();
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_mkundef();
ant_value_t entry = js_mkarr(js);
js_arr_push(js, entry, key_sv);
js_arr_push(js, entry, val_sv);
js_arr_push(js, entries, entry);
usp_push_to_url(js, js->this_val);
return js_mkundef();
}
static ant_value_t usp_delete(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return js_mkundef();
const char *key = js_getstr(js, key_sv, NULL);
if (!key) return js_mkundef();
const char *match_val = NULL;
if (nargs >= 2 && !is_undefined(args[1])) {
ant_value_t mv_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
if (!is_err(mv_sv)) match_val = js_getstr(js, mv_sv, NULL);
}
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
ant_offset_t len = is_special_object(entries) ? js_arr_len(js, entries) : 0;
ant_value_t new_entries = js_mkarr(js);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
if (ek && strcmp(ek, key) == 0) {
if (!match_val) continue;
const char *ev = js_getstr(js, js_arr_get(js, entry, 1), NULL);
if (ev && strcmp(ev, match_val) == 0) continue;
}
js_arr_push(js, new_entries, entry);
}
js_set_slot_wb(js, js->this_val, SLOT_ENTRIES, new_entries);
usp_push_to_url(js, js->this_val);
return js_mkundef();
}
static ant_value_t usp_toString(ant_t *js, ant_value_t *args, int nargs) {
char *s = usp_serialize(js, js->this_val);
ant_value_t ret = js_mkstr(js, s, strlen(s));
free(s);
return ret;
}
static ant_value_t usp_forEach(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1 || !is_callable(args[0])) return js_mkundef();
ant_value_t cb = args[0];
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_value_t self = js->this_val;
ant_value_t entries = js_get_slot(self, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_mkundef();
ant_offset_t len = js_arr_len(js, entries);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
ant_value_t k = js_arr_get(js, entry, 0);
ant_value_t v = js_arr_get(js, entry, 1);
ant_value_t cb_args[3] = { v, k, self };
ant_value_t r = sv_vm_call(js->vm, js, cb, this_arg, cb_args, 3, NULL, false);
if (is_err(r)) return r;
}
return js_mkundef();
}
static ant_value_t usp_size_get(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_mknum(0);
return js_mknum((double)js_arr_len(js, entries));
}
static ant_value_t usp_sort(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_mkundef();
ant_offset_t len = js_arr_len(js, entries);
if (len <= 1) return js_mkundef();
ant_value_t *arr = malloc(sizeof(ant_value_t) * (size_t)len);
if (!arr) return js_mkundef();
for (ant_offset_t i = 0; i < len; i++) arr[i] = js_arr_get(js, entries, i);
for (ant_offset_t i = 1; i < len; i++) {
ant_value_t cur = arr[i];
const char *ck = js_getstr(js, js_arr_get(js, cur, 0), NULL);
ant_offset_t j = i - 1;
while (j >= 0) {
const char *jk = js_getstr(js, js_arr_get(js, arr[j], 0), NULL);
if (strcmp(jk ? jk : "", ck ? ck : "") <= 0) break;
arr[j + 1] = arr[j]; j--;
}
arr[j + 1] = cur;
}
ant_value_t new_entries = js_mkarr(js);
for (ant_offset_t i = 0; i < len; i++) js_arr_push(js, new_entries, arr[i]);
free(arr);
js_set_slot_wb(js, js->this_val, SLOT_ENTRIES, new_entries);
usp_push_to_url(js, js->this_val);
return js_mkundef();
}
static ant_value_t usp_iter_next(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state_v = js_get_slot(js->this_val, SLOT_ITER_STATE);
if (vtype(state_v) != T_NUM) return js_iter_result(js, false, js_mkundef());
uint32_t state = (uint32_t)js_getnum(state_v);
uint32_t kind = ITER_STATE_KIND(state);
uint32_t idx = ITER_STATE_INDEX(state);
ant_value_t usp = js_get_slot(js->this_val, SLOT_DATA);
ant_value_t entries = js_get_slot(usp, SLOT_ENTRIES);
if (!is_special_object(entries) || (ant_offset_t)idx >= js_arr_len(js, entries))
return js_iter_result(js, false, js_mkundef());
js_set_slot(js->this_val, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, idx + 1)));
ant_value_t entry = js_arr_get(js, entries, (ant_offset_t)idx);
ant_value_t k = js_arr_get(js, entry, 0);
ant_value_t v = js_arr_get(js, entry, 1);
ant_value_t out;
switch (kind) {
case USP_ITER_KEYS: out = k; break;
case USP_ITER_VALUES: out = v; break;
default: {
out = js_mkarr(js);
js_arr_push(js, out, k);
js_arr_push(js, out, v);
break;
}}
return js_iter_result(js, true, out);
}
static ant_value_t make_usp_iter(ant_t *js, ant_value_t usp, int kind) {
ant_value_t iter = js_mkobj(js);
js_set_proto_init(iter, g_usp_iter_proto);
js_set_slot_wb(js, iter, SLOT_DATA, usp);
js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, 0)));
return iter;
}
static ant_value_t usp_entries_fn(ant_t *js, ant_value_t *args, int nargs) {
return make_usp_iter(js, js->this_val, USP_ITER_ENTRIES);
}
static ant_value_t usp_keys_fn(ant_t *js, ant_value_t *args, int nargs) {
return make_usp_iter(js, js->this_val, USP_ITER_KEYS);
}
static ant_value_t usp_values_fn(ant_t *js, ant_value_t *args, int nargs) {
return make_usp_iter(js, js->this_val, USP_ITER_VALUES);
}
static ant_value_t js_URLSearchParams(ant_t *js, ant_value_t *args, int nargs) {
if (is_undefined(js->new_target))
return js_mkerr_typed(js, JS_ERR_TYPE,
- "Failed to construct 'URLSearchParams': Please use the 'new' operator.");
+ "Failed to construct 'URLSearchParams': Please use the 'new' operator.");
ant_value_t obj = js_mkobj(js);
js_set_proto_init(obj, g_usp_proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_URLSEARCHPARAMS));
js_set_slot(obj, SLOT_DATA, js_mkundef());
+
ant_value_t entries = js_mkarr(js);
js_set_slot(obj, SLOT_ENTRIES, entries);
if (nargs < 1 || is_undefined(args[0]) || is_null(args[0])) return obj;
ant_value_t init = args[0];
uint8_t t = vtype(init);
if (t == T_STR) {
const char *s = js_getstr(js, init, NULL);
if (s) {
const char *q = (s[0] == '?') ? s + 1 : s;
js_set_slot(obj, SLOT_ENTRIES, parse_query_to_arr(js, q));
}
return obj;
}
if (t == T_ARR) {
ant_offset_t len = js_arr_len(js, init);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t pair = js_arr_get(js, init, i);
if (vtype(pair) != T_ARR)
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'URLSearchParams': Each element must be an array.");
ant_offset_t plen = js_arr_len(js, pair);
if (plen != 2)
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'URLSearchParams': Each pair must have exactly 2 elements.");
ant_value_t pk = js_arr_get(js, pair, 0);
ant_value_t pv = js_arr_get(js, pair, 1);
ant_value_t ksv = (vtype(pk) == T_STR) ? pk : js_tostring_val(js, pk);
if (is_err(ksv)) return ksv;
ant_value_t vsv = (vtype(pv) == T_STR) ? pv : js_tostring_val(js, pv);
if (is_err(vsv)) return vsv;
ant_value_t entry = js_mkarr(js);
js_arr_push(js, entry, ksv);
js_arr_push(js, entry, vsv);
js_arr_push(js, entries, entry);
}
return obj;
}
if (is_special_object(init)) {
ant_value_t src = js_get_slot(init, SLOT_ENTRIES);
if (vtype(src) == T_ARR) {
ant_offset_t len = js_arr_len(js, src);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, src, i);
ant_value_t ne = js_mkarr(js);
js_arr_push(js, ne, js_arr_get(js, entry, 0));
js_arr_push(js, ne, js_arr_get(js, entry, 1));
js_arr_push(js, entries, ne);
}
return obj;
}
ant_iter_t it = js_prop_iter_begin(js, init);
const char *key;
size_t key_len;
ant_value_t val;
while (js_prop_iter_next(&it, &key, &key_len, &val)) {
ant_value_t sv = (vtype(val) == T_STR) ? val : js_tostring_val(js, val);
if (is_err(sv)) { js_prop_iter_end(&it); return sv; }
ant_value_t entry = js_mkarr(js);
js_arr_push(js, entry, js_mkstr(js, key, key_len));
js_arr_push(js, entry, sv);
js_arr_push(js, entries, entry);
}
js_prop_iter_end(&it);
}
return obj;
}
void init_url_module(void) {
ant_t *js = rt->js;
ant_value_t glob = js->global;
g_usp_iter_proto = js_mkobj(js);
js_set_proto_init(g_usp_iter_proto, js->sym.iterator_proto);
js_set(js, g_usp_iter_proto, "next", js_mkfun(usp_iter_next));
js_set_descriptor(js, g_usp_iter_proto, "next", 4, JS_DESC_W | JS_DESC_E | JS_DESC_C);
js_set_sym(js, g_usp_iter_proto, get_iterator_sym(), js_mkfun(sym_this_cb));
g_usp_proto = js_mkobj(js);
js_set(js, g_usp_proto, "get", js_mkfun(usp_get));
js_set(js, g_usp_proto, "getAll", js_mkfun(usp_getAll));
js_set(js, g_usp_proto, "has", js_mkfun(usp_has));
js_set(js, g_usp_proto, "set", js_mkfun(usp_set));
js_set(js, g_usp_proto, "append", js_mkfun(usp_append));
js_set(js, g_usp_proto, "delete", js_mkfun(usp_delete));
js_set(js, g_usp_proto, "sort", js_mkfun(usp_sort));
js_set(js, g_usp_proto, "toString", js_mkfun(usp_toString));
js_set(js, g_usp_proto, "forEach", js_mkfun(usp_forEach));
js_set_getter_desc(js, g_usp_proto, "size", 4, js_mkfun(usp_size_get), JS_DESC_C);
ant_value_t entries_fn = js_mkfun(usp_entries_fn);
js_set(js, g_usp_proto, "entries", entries_fn);
js_set(js, g_usp_proto, "keys", js_mkfun(usp_keys_fn));
js_set(js, g_usp_proto, "values", js_mkfun(usp_values_fn));
js_set_sym(js, g_usp_proto, get_iterator_sym(), entries_fn);
js_set_sym(js, g_usp_proto, get_toStringTag_sym(), js_mkstr(js, "URLSearchParams", 15));
ant_value_t usp_ctor = js_make_ctor(js, js_URLSearchParams, g_usp_proto, "URLSearchParams", 15);
js_set(js, glob, "URLSearchParams", usp_ctor);
g_url_proto = js_mkobj(js);
js_set_accessor_desc(js, g_url_proto, "href", 4, js_mkfun(url_get_href), js_mkfun(url_set_href), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "protocol", 8, js_mkfun(url_get_protocol), js_mkfun(url_set_protocol), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "username", 8, js_mkfun(url_get_username), js_mkfun(url_set_username), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "password", 8, js_mkfun(url_get_password), js_mkfun(url_set_password), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "host", 4, js_mkfun(url_get_host), js_mkfun(url_set_host), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "hostname", 8, js_mkfun(url_get_hostname), js_mkfun(url_set_hostname), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "port", 4, js_mkfun(url_get_port), js_mkfun(url_set_port), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "pathname", 8, js_mkfun(url_get_pathname), js_mkfun(url_set_pathname), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "search", 6, js_mkfun(url_get_search), js_mkfun(url_set_search), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "hash", 4, js_mkfun(url_get_hash), js_mkfun(url_set_hash), JS_DESC_C);
js_set_getter_desc(js, g_url_proto, "origin", 6, js_mkfun(url_get_origin), JS_DESC_C);
js_set_getter_desc(js, g_url_proto, "searchParams", 12, js_mkfun(url_get_searchParams), JS_DESC_C);
js_set(js, g_url_proto, "toString", js_mkfun(url_toString));
js_set(js, g_url_proto, "toJSON", js_mkfun(url_toString));
js_set_sym(js, g_url_proto, get_toStringTag_sym(), js_mkstr(js, "URL", 3));
ant_value_t url_ctor = js_make_ctor(js, js_URL, g_url_proto, "URL", 3);
js_set(js, url_ctor, "canParse", js_mkfun(url_canParse));
js_set(js, url_ctor, "parse", js_mkfun(url_parse));
js_set(js, glob, "URL", url_ctor);
}
static ant_value_t builtin_fileURLToPath(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "fileURLToPath requires a string or URL argument");
size_t len;
const char *str = coerce_to_string(js, args[0], &len);
if (!str) return js_mkerr(js, "fileURLToPath requires a string or URL argument");
url_state_t s;
if (parse_url_to_state(str, NULL, &s) != 0)
return js_mkerr(js, "Invalid URL");
if (strcmp(s.protocol, "file:") != 0) {
url_state_clear(&s);
return js_mkerr(js, "fileURLToPath requires a file: URL");
}
char *decoded = url_decode_component(s.pathname);
url_state_clear(&s);
if (!decoded) return js_mkerr(js, "allocation failure");
ant_value_t ret = js_mkstr(js, decoded, strlen(decoded));
free(decoded);
return ret;
}
static ant_value_t builtin_pathToFileURL(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1 || vtype(args[0]) != T_STR)
return js_mkerr(js, "pathToFileURL requires a string argument");
size_t len;
const char *path = js_getstr(js, args[0], &len);
size_t total = 7 + len;
char *buf = malloc(total + 1);
if (!buf) return js_mkerr(js, "allocation failure");
memcpy(buf, "file://", 7);
memcpy(buf + 7, path, len);
buf[total] = '\0';
url_state_t *s = calloc(1, sizeof(url_state_t));
if (!s) { free(buf); return js_mkerr(js, "allocation failure"); }
if (parse_url_to_state(buf, NULL, s) != 0) {
free(buf); free(s);
return js_mkerr(js, "Invalid file URL");
}
free(buf);
return make_url_obj(js, s);
}
ant_value_t url_library(ant_t *js) {
ant_value_t lib = js_mkobj(js);
ant_value_t glob = js_glob(js);
js_set(js, lib, "URL", js_get(js, glob, "URL"));
js_set(js, lib, "URLSearchParams",js_get(js, glob, "URLSearchParams"));
js_set(js, lib, "fileURLToPath", js_mkfun(builtin_fileURLToPath));
js_set(js, lib, "pathToFileURL", js_mkfun(builtin_pathToFileURL));
js_set(js, lib, "default", lib);
return lib;
}
diff --git a/src/streams/codec.c b/src/streams/codec.c
index 1ae0d59..dd4f2ca 100644
--- a/src/streams/codec.c
+++ b/src/streams/codec.c
@@ -1,498 +1,462 @@
#include <stdlib.h>
#include <string.h>
#include "ant.h"
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "descriptors.h"
#include "modules/symbol.h"
#include "modules/buffer.h"
#include "modules/textcodec.h"
#include "streams/codec.h"
#include "streams/transform.h"
ant_value_t g_tes_proto;
ant_value_t g_tds_proto;
typedef struct {
uint8_t pending[3];
uint8_t pending_len;
} tes_state_t;
static ant_value_t tes_get_ts(ant_value_t obj) {
return js_get_slot(obj, SLOT_ENTRIES);
}
static ant_value_t tds_get_ts(ant_value_t obj) {
return js_get_slot(obj, SLOT_ENTRIES);
}
bool tes_is_stream(ant_value_t obj) {
return is_object_type(obj)
&& vtype(js_get_slot(obj, SLOT_DATA)) == T_NUM
&& ts_is_stream(tes_get_ts(obj));
}
bool tds_is_stream(ant_value_t obj) {
return is_object_type(obj)
&& vtype(js_get_slot(obj, SLOT_DATA)) == T_NUM
&& ts_is_stream(tds_get_ts(obj));
}
ant_value_t tes_stream_readable(ant_value_t obj) {
return ts_stream_readable(tes_get_ts(obj));
}
ant_value_t tes_stream_writable(ant_value_t obj) {
return ts_stream_writable(tes_get_ts(obj));
}
ant_value_t tds_stream_readable(ant_value_t obj) {
return ts_stream_readable(tds_get_ts(obj));
}
ant_value_t tds_stream_writable(ant_value_t obj) {
return ts_stream_writable(tds_get_ts(obj));
}
static void tes_state_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
free((tes_state_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
return;
}}
}
static void tds_state_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
free((td_state_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
return;
}}
}
-static bool get_chunk_bytes(ant_t *js, ant_value_t chunk, const uint8_t **out, size_t *len) {
- ant_value_t slot = js_get_slot(chunk, SLOT_BUFFER);
- TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(slot);
- if (ta) {
- if (!ta->buffer || ta->buffer->is_detached) { *out = NULL; *len = 0; return true; }
- *out = ta->buffer->data + ta->byte_offset;
- *len = ta->byte_length;
- return true;
- }
-
- if (vtype(slot) == T_NUM) {
- ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(slot);
- if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; }
- *out = ab->data;
- *len = ab->length;
- return true;
- }
-
- ant_value_t buf_prop = js_get(js, chunk, "buffer");
- if (is_object_type(buf_prop)) {
- ant_value_t buf_slot = js_get_slot(buf_prop, SLOT_BUFFER);
- if (vtype(buf_slot) == T_NUM) {
- ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(buf_slot);
- if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; }
- ant_value_t off_v = js_get(js, chunk, "byteOffset");
- ant_value_t len_v = js_get(js, chunk, "byteLength");
- size_t off = (vtype(off_v) == T_NUM) ? (size_t)js_getnum(off_v) : 0;
- size_t blen = (vtype(len_v) == T_NUM) ? (size_t)js_getnum(len_v) : ab->length - off;
- *out = ab->data + off;
- *len = blen;
- return true;
- }}
-
- return false;
-}
-
static ant_value_t codec_transform_controller(ant_value_t *args, int nargs) {
return (nargs > 1) ? args[1] : js_mkundef();
}
static ant_value_t codec_flush_controller(ant_value_t *args, int nargs) {
return (nargs > 0) ? args[0] : js_mkundef();
}
static ant_value_t tes_transform(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
tes_state_t *st = (tes_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
ant_value_t ctrl_obj = codec_transform_controller(args, nargs);
ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
size_t str_len = 0;
const char *str = NULL;
if (vtype(chunk) == T_STR) {
str = js_getstr(js, chunk, &str_len);
} else {
ant_value_t sv = js_tostring_val(js, chunk);
if (is_err(sv)) return sv;
str = js_getstr(js, sv, &str_len);
}
if (!str) { str = ""; str_len = 0; }
const uint8_t *s = (const uint8_t *)str;
size_t total = st->pending_len + str_len;
if (total == 0) return js_mkundef();
uint8_t *buf = malloc(total);
if (!buf) return js_mkerr(js, "out of memory");
size_t off = 0;
if (st->pending_len > 0) {
memcpy(buf, st->pending, st->pending_len);
off = st->pending_len;
st->pending_len = 0;
}
memcpy(buf + off, s, str_len);
size_t out_len = total;
if (out_len >= 3) {
uint8_t b0 = buf[out_len - 3], b1 = buf[out_len - 2], b2 = buf[out_len - 1];
if (b0 == 0xED && b1 >= 0xA0 && b1 <= 0xAF) {
st->pending[0] = b0;
st->pending[1] = b1;
st->pending[2] = b2;
st->pending_len = 3;
out_len -= 3;
}}
if (out_len == 0) {
free(buf);
return js_mkundef();
}
uint8_t *out = malloc(out_len * 4 / 3 + 4);
if (!out) { free(buf); return js_mkerr(js, "out of memory"); }
size_t i = 0, o = 0;
while (i < out_len) {
if (i + 5 < out_len &&
buf[i] == 0xED && buf[i+1] >= 0xA0 && buf[i+1] <= 0xAF &&
buf[i+3] == 0xED && buf[i+4] >= 0xB0 && buf[i+4] <= 0xBF) {
uint32_t hi_cp = ((uint32_t)0x0D << 12) | ((uint32_t)(buf[i+1] & 0x3F) << 6) | (buf[i+2] & 0x3F);
uint32_t lo_cp = ((uint32_t)0x0D << 12) | ((uint32_t)(buf[i+4] & 0x3F) << 6) | (buf[i+5] & 0x3F);
uint32_t cp = 0x10000 + ((hi_cp - 0xD800) << 10) + (lo_cp - 0xDC00);
out[o++] = (uint8_t)(0xF0 | (cp >> 18));
out[o++] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
out[o++] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
out[o++] = (uint8_t)(0x80 | (cp & 0x3F));
i += 6;
} else if (buf[i] == 0xED && i + 2 < out_len && buf[i+1] >= 0xA0 && buf[i+1] <= 0xBF) {
out[o++] = 0xEF; out[o++] = 0xBF; out[o++] = 0xBD;
i += 3;
} else out[o++] = buf[i++]; }
free(buf);
ArrayBufferData *ab = create_array_buffer_data(o);
if (!ab) { free(out); return js_mkerr(js, "out of memory"); }
memcpy(ab->data, out, o);
free(out);
ant_value_t result = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, o, "Uint8Array");
return ts_is_controller(ctrl_obj)
? ts_ctrl_enqueue(js, ctrl_obj, result)
: js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
}
static ant_value_t tes_flush(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
tes_state_t *st = (tes_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
ant_value_t ctrl_obj = codec_flush_controller(args, nargs);
if (st->pending_len > 0) {
uint8_t fffd[3] = { 0xEF, 0xBF, 0xBD };
ArrayBufferData *ab = create_array_buffer_data(3);
if (!ab) return js_mkerr(js, "out of memory");
memcpy(ab->data, fffd, 3);
st->pending_len = 0;
ant_value_t result = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, 3, "Uint8Array");
if (!ts_is_controller(ctrl_obj))
return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
return ts_ctrl_enqueue(js, ctrl_obj, result);
}
return js_mkundef();
}
static ant_value_t js_tes_get_encoding(ant_t *js, ant_value_t *args, int nargs) {
return js_mkstr(js, "utf-8", 5);
}
static ant_value_t js_tes_get_readable(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = tes_get_ts(js->this_val);
if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TextEncoderStream");
return ts_stream_readable(ts_obj);
}
static ant_value_t js_tes_get_writable(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = tes_get_ts(js->this_val);
if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TextEncoderStream");
return ts_stream_writable(ts_obj);
}
static ant_value_t js_tes_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "TextEncoderStream constructor requires 'new'");
tes_state_t *st = calloc(1, sizeof(tes_state_t));
if (!st) return js_mkerr(js, "out of memory");
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_tes_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
js_set_slot(obj, SLOT_DATA, ANT_PTR(st));
js_set_finalizer(obj, tes_state_finalize);
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, ANT_PTR(st));
ant_value_t transformer = js_mkobj(js);
ant_value_t transform_fn = js_heavy_mkfun(js, tes_transform, wrapper);
ant_value_t flush_fn = js_heavy_mkfun(js, tes_flush, wrapper);
js_set(js, transformer, "transform", transform_fn);
js_set(js, transformer, "flush", flush_fn);
ant_value_t ctor_args[1] = { transformer };
ant_value_t saved_new_target = js->new_target;
ant_value_t saved_this = js->this_val;
js->new_target = js_mknum(1);
ant_value_t ts_obj = js_ts_ctor(js, ctor_args, 1);
js->new_target = saved_new_target;
js->this_val = saved_this;
if (is_err(ts_obj)) { free(st); return ts_obj; }
js_set_slot(obj, SLOT_ENTRIES, ts_obj);
js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
return obj;
}
static ant_value_t tds_transform(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
td_state_t *st = (td_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
ant_value_t ctrl_obj = codec_transform_controller(args, nargs);
ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
const uint8_t *input = NULL;
size_t input_len = 0;
- if (!is_object_type(chunk) || !get_chunk_bytes(js, chunk, &input, &input_len))
+ if (!is_object_type(chunk) || !buffer_source_get_bytes(js, chunk, &input, &input_len))
return js_mkerr_typed(js, JS_ERR_TYPE, "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'");
ant_value_t result = td_decode(js, st, input, input_len, true);
if (is_err(result)) return result;
size_t slen = 0;
const char *sval = js_getstr(js, result, &slen);
if (sval && slen > 0) {
if (!ts_is_controller(ctrl_obj))
return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
return ts_ctrl_enqueue(js, ctrl_obj, result);
}
return js_mkundef();
}
static ant_value_t tds_flush(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
td_state_t *st = (td_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
ant_value_t ctrl_obj = codec_flush_controller(args, nargs);
ant_value_t result = td_decode(js, st, NULL, 0, false);
if (is_err(result)) return result;
size_t slen = 0;
const char *sval = js_getstr(js, result, &slen);
if (sval && slen > 0) {
if (!ts_is_controller(ctrl_obj))
return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
return ts_ctrl_enqueue(js, ctrl_obj, result);
}
return js_mkundef();
}
static ant_value_t js_tds_get_encoding(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state_val = js_get_slot(js->this_val, SLOT_DATA);
td_state_t *st = (td_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
if (!st) return js_mkstr(js, "utf-8", 5);
switch (st->encoding) {
case TD_ENC_UTF16LE: return js_mkstr(js, "utf-16le", 8);
case TD_ENC_UTF16BE: return js_mkstr(js, "utf-16be", 8);
case TD_ENC_WINDOWS_1252: return js_mkstr(js, "windows-1252", 12);
case TD_ENC_ISO_8859_2: return js_mkstr(js, "iso-8859-2", 10);
default: return js_mkstr(js, "utf-8", 5);
}
}
static ant_value_t js_tds_get_fatal(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state_val = js_get_slot(js->this_val, SLOT_DATA);
td_state_t *st = (td_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
return (st && st->fatal) ? js_true : js_false;
}
static ant_value_t js_tds_get_ignore_bom(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state_val = js_get_slot(js->this_val, SLOT_DATA);
td_state_t *st = (td_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
return (st && st->ignore_bom) ? js_true : js_false;
}
static ant_value_t js_tds_get_readable(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = tds_get_ts(js->this_val);
if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TextDecoderStream");
return ts_stream_readable(ts_obj);
}
static ant_value_t js_tds_get_writable(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = tds_get_ts(js->this_val);
if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TextDecoderStream");
return ts_stream_writable(ts_obj);
}
static const char *tds_trim_label(const char *s, size_t len, size_t *out_len) {
while (len > 0 && (unsigned char)*s <= 0x20) { s++; len--; }
while (len > 0 && (unsigned char)s[len - 1] <= 0x20) { len--; }
*out_len = len;
return s;
}
static int tds_resolve_encoding(const char *s, size_t len) {
static const struct { const char *label; uint8_t label_len; td_encoding_t enc; } map[] = {
{"unicode-1-1-utf-8", 17, TD_ENC_UTF8}, {"unicode11utf8", 13, TD_ENC_UTF8},
{"unicode20utf8", 13, TD_ENC_UTF8}, {"utf-8", 5, TD_ENC_UTF8},
{"utf8", 4, TD_ENC_UTF8}, {"x-unicode20utf8",17, TD_ENC_UTF8},
{"windows-1252", 12, TD_ENC_WINDOWS_1252}, {"ascii", 5, TD_ENC_WINDOWS_1252},
{"unicodefffe", 11, TD_ENC_UTF16BE}, {"utf-16be", 8, TD_ENC_UTF16BE},
{"csunicode", 9, TD_ENC_UTF16LE}, {"iso-10646-ucs-2",16, TD_ENC_UTF16LE},
{"ucs-2", 5, TD_ENC_UTF16LE}, {"unicode", 7, TD_ENC_UTF16LE},
{"unicodefeff", 11, TD_ENC_UTF16LE}, {"utf-16", 6, TD_ENC_UTF16LE},
{"utf-16le", 8, TD_ENC_UTF16LE},
{"iso-8859-2", 10, TD_ENC_ISO_8859_2},
{NULL, 0, 0}
};
for (int i = 0; map[i].label; i++) {
if (len == map[i].label_len && strncasecmp(s, map[i].label, len) == 0) return (int)map[i].enc;
}
return -1;
}
static ant_value_t js_tds_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "TextDecoderStream constructor requires 'new'");
td_encoding_t enc = TD_ENC_UTF8;
if (nargs > 0 && !is_undefined(args[0])) {
ant_value_t label = (vtype(args[0]) == T_STR) ? args[0] : coerce_to_str(js, args[0]);
if (is_err(label)) return label;
size_t llen;
const char *raw = js_getstr(js, label, &llen);
if (raw) {
size_t tlen;
const char *trimmed = tds_trim_label(raw, llen, &tlen);
int resolved = tds_resolve_encoding(trimmed, tlen);
if (resolved < 0) return js_mkerr_typed(
js, JS_ERR_RANGE, "Failed to construct 'TextDecoderStream': The encoding label provided ('%.*s') is invalid.",
(int)tlen, trimmed
);
enc = (td_encoding_t)resolved;
}
}
bool fatal = false;
bool ignore_bom = false;
if (nargs > 1 && is_object_type(args[1])) {
ant_value_t fv = js_getprop_fallback(js, args[1], "fatal");
if (is_err(fv)) return fv;
if (vtype(fv) != T_UNDEF) fatal = js_truthy(js, fv);
ant_value_t bv = js_getprop_fallback(js, args[1], "ignoreBOM");
if (is_err(bv)) return bv;
if (vtype(bv) != T_UNDEF) ignore_bom = js_truthy(js, bv);
}
td_state_t *st = td_state_new(enc, fatal, ignore_bom);
if (!st) return js_mkerr(js, "out of memory");
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_tds_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
js_set_slot(obj, SLOT_DATA, ANT_PTR(st));
js_set_finalizer(obj, tds_state_finalize);
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, ANT_PTR(st));
ant_value_t transformer = js_mkobj(js);
ant_value_t transform_fn = js_heavy_mkfun(js, tds_transform, wrapper);
ant_value_t flush_fn = js_heavy_mkfun(js, tds_flush, wrapper);
js_set(js, transformer, "transform", transform_fn);
js_set(js, transformer, "flush", flush_fn);
ant_value_t ctor_args[1] = { transformer };
ant_value_t saved_new_target = js->new_target;
ant_value_t saved_this = js->this_val;
js->new_target = js_mknum(1);
ant_value_t ts_obj = js_ts_ctor(js, ctor_args, 1);
js->new_target = saved_new_target;
js->this_val = saved_this;
if (is_err(ts_obj)) { free(st); return ts_obj; }
js_set_slot(obj, SLOT_ENTRIES, ts_obj);
js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
return obj;
}
void init_codec_stream_module(void) {
ant_t *js = rt->js;
ant_value_t g = js_glob(js);
g_tes_proto = js_mkobj(js);
js_set_getter_desc(js, g_tes_proto, "encoding", 8, js_mkfun(js_tes_get_encoding), JS_DESC_C);
js_set_getter_desc(js, g_tes_proto, "readable", 8, js_mkfun(js_tes_get_readable), JS_DESC_C);
js_set_getter_desc(js, g_tes_proto, "writable", 8, js_mkfun(js_tes_get_writable), JS_DESC_C);
js_set_sym(js, g_tes_proto, get_toStringTag_sym(), js_mkstr(js, "TextEncoderStream", 17));
ant_value_t tes_ctor = js_make_ctor(js, js_tes_ctor, g_tes_proto, "TextEncoderStream", 17);
js_set(js, g, "TextEncoderStream", tes_ctor);
js_set_descriptor(js, g, "TextEncoderStream", 17, JS_DESC_W | JS_DESC_C);
g_tds_proto = js_mkobj(js);
js_set_getter_desc(js, g_tds_proto, "encoding", 8, js_mkfun(js_tds_get_encoding), JS_DESC_C);
js_set_getter_desc(js, g_tds_proto, "fatal", 5, js_mkfun(js_tds_get_fatal), JS_DESC_C);
js_set_getter_desc(js, g_tds_proto, "ignoreBOM", 9, js_mkfun(js_tds_get_ignore_bom), JS_DESC_C);
js_set_getter_desc(js, g_tds_proto, "readable", 8, js_mkfun(js_tds_get_readable), JS_DESC_C);
js_set_getter_desc(js, g_tds_proto, "writable", 8, js_mkfun(js_tds_get_writable), JS_DESC_C);
js_set_sym(js, g_tds_proto, get_toStringTag_sym(), js_mkstr(js, "TextDecoderStream", 17));
ant_value_t tds_ctor = js_make_ctor(js, js_tds_ctor, g_tds_proto, "TextDecoderStream", 17);
js_set(js, g, "TextDecoderStream", tds_ctor);
js_set_descriptor(js, g, "TextDecoderStream", 17, JS_DESC_W | JS_DESC_C);
}
void gc_mark_codec_streams(ant_t *js, void (*mark)(ant_t *, ant_value_t)) {
mark(js, g_tes_proto);
mark(js, g_tds_proto);
}
diff --git a/src/streams/compression.c b/src/streams/compression.c
index 5f0f4e2..2dc5f7a 100644
--- a/src/streams/compression.c
+++ b/src/streams/compression.c
@@ -1,468 +1,432 @@
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#include "ant.h"
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "descriptors.h"
#include "modules/symbol.h"
#include "modules/buffer.h"
#include "streams/brotli.h"
#include "streams/compression.h"
#include "streams/transform.h"
ant_value_t g_cs_proto;
ant_value_t g_ds_proto;
typedef struct {
z_stream strm;
zformat_t format;
bool initialized;
} zstate_t;
static int zfmt_window_bits(zformat_t fmt, bool decompress) {
switch (fmt) {
case ZFMT_GZIP: return decompress ? (15 + 32) : (15 + 16);
case ZFMT_DEFLATE: return 15;
case ZFMT_DEFLATE_RAW: return -15;
case ZFMT_BROTLI: return 15;
}
return 15;
}
static int parse_format(ant_t *js, ant_value_t arg, zformat_t *out) {
if (vtype(arg) != T_STR) return -1;
size_t len;
const char *s = js_getstr(js, arg, &len);
if (!s) return -1;
if (len == 4 && !memcmp(s, "gzip", 4)) { *out = ZFMT_GZIP; return 0; }
if (len == 7 && !memcmp(s, "deflate", 7)) { *out = ZFMT_DEFLATE; return 0; }
if (len == 11 && !memcmp(s, "deflate-raw", 11)) { *out = ZFMT_DEFLATE_RAW; return 0; }
if (len == 6 && !memcmp(s, "brotli", 6)) { *out = ZFMT_BROTLI; return 0; }
return -1;
}
static ant_value_t get_ts(ant_value_t obj) {
return js_get_slot(obj, SLOT_ENTRIES);
}
static zformat_t get_wrapper_format(ant_value_t wrapper) {
ant_value_t fmt = js_get_slot(wrapper, SLOT_CTOR);
return (zformat_t)(int)js_getnum(fmt);
}
bool cs_is_stream(ant_value_t obj) {
return is_object_type(obj)
&& vtype(js_get_slot(obj, SLOT_DATA)) == T_NUM
&& ts_is_stream(get_ts(obj));
}
bool ds_is_stream(ant_value_t obj) {
return is_object_type(obj)
&& vtype(js_get_slot(obj, SLOT_DATA)) == T_NUM
&& ts_is_stream(get_ts(obj));
}
ant_value_t cs_stream_readable(ant_value_t obj) {
return ts_stream_readable(get_ts(obj));
}
ant_value_t cs_stream_writable(ant_value_t obj) {
return ts_stream_writable(get_ts(obj));
}
ant_value_t ds_stream_readable(ant_value_t obj) {
return ts_stream_readable(get_ts(obj));
}
ant_value_t ds_stream_writable(ant_value_t obj) {
return ts_stream_writable(get_ts(obj));
}
static void zstate_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
zstate_t *st = (zstate_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
if (st->initialized) deflateEnd(&st->strm);
free(st);
return;
}}
}
static void zstate_inflate_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
zstate_t *st = (zstate_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
if (st->initialized) inflateEnd(&st->strm);
free(st);
return;
}}
}
static void brotli_state_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
brotli_stream_state_t *st =
(brotli_stream_state_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
brotli_stream_state_destroy(st);
return;
}}
}
-static bool get_chunk_bytes(ant_t *js, ant_value_t chunk, const uint8_t **out, size_t *len) {
- ant_value_t slot = js_get_slot(chunk, SLOT_BUFFER);
- TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(slot);
- if (ta) {
- if (!ta->buffer || ta->buffer->is_detached) { *out = NULL; *len = 0; return true; }
- *out = ta->buffer->data + ta->byte_offset;
- *len = ta->byte_length;
- return true;
- }
-
- if (vtype(slot) == T_NUM) {
- ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(slot);
- if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; }
- *out = ab->data;
- *len = ab->length;
- return true;
- }
-
- ant_value_t buf_prop = js_get(js, chunk, "buffer");
- if (is_object_type(buf_prop)) {
- ant_value_t buf_slot = js_get_slot(buf_prop, SLOT_BUFFER);
- if (vtype(buf_slot) == T_NUM) {
- ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(buf_slot);
- if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; }
- ant_value_t off_v = js_get(js, chunk, "byteOffset");
- ant_value_t len_v = js_get(js, chunk, "byteLength");
- size_t off = (vtype(off_v) == T_NUM) ? (size_t)js_getnum(off_v) : 0;
- size_t blen = (vtype(len_v) == T_NUM) ? (size_t)js_getnum(len_v) : ab->length - off;
- *out = ab->data + off;
- *len = blen;
- return true;
- }}
-
- return false;
-}
-
static ant_value_t enqueue_buffer(ant_t *js, ant_value_t ctrl_obj, const uint8_t *data, size_t len) {
ArrayBufferData *ab = create_array_buffer_data(len);
if (!ab) return js_mkerr(js, "out of memory");
memcpy(ab->data, data, len);
ant_value_t arr = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Uint8Array");
if (!ts_is_controller(ctrl_obj))
return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStreamDefaultController");
return ts_ctrl_enqueue(js, ctrl_obj, arr);
}
#define ZCHUNK_SIZE 16384
static ant_value_t cs_transform(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
zstate_t *st = (zstate_t *)(uintptr_t)(size_t)js_getnum(state_val);
ant_value_t ctrl_obj = (nargs > 1) ? args[1] : js_mkundef();
ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
const uint8_t *input = NULL;
size_t input_len = 0;
- if (!is_object_type(chunk) || !get_chunk_bytes(js, chunk, &input, &input_len))
+ if (!is_object_type(chunk) || !buffer_source_get_bytes(js, chunk, &input, &input_len))
return js_mkerr_typed(js, JS_ERR_TYPE, "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'");
if (!input || input_len == 0) return js_mkundef();
if (get_wrapper_format(wrapper) == ZFMT_BROTLI) {
brotli_stream_state_t *brotli_st =
(brotli_stream_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
return brotli_stream_transform(js, brotli_st, ctrl_obj, input, input_len);
}
st->strm.next_in = (Bytef *)input;
st->strm.avail_in = (uInt)input_len;
uint8_t out_buf[ZCHUNK_SIZE];
do {
st->strm.next_out = out_buf;
st->strm.avail_out = ZCHUNK_SIZE;
int ret = deflate(&st->strm, Z_NO_FLUSH);
if (ret == Z_STREAM_ERROR)
return js_mkerr_typed(js, JS_ERR_TYPE, "Compression failed");
size_t have = ZCHUNK_SIZE - st->strm.avail_out;
if (have > 0) {
ant_value_t r = enqueue_buffer(js, ctrl_obj, out_buf, have);
if (is_err(r)) return r;
}
} while (st->strm.avail_out == 0);
return js_mkundef();
}
static ant_value_t cs_flush(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ctrl_obj = (nargs > 0) ? args[0] : js_mkundef();
if (get_wrapper_format(wrapper) == ZFMT_BROTLI) {
brotli_stream_state_t *brotli_st =
(brotli_stream_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
return brotli_stream_flush(js, brotli_st, ctrl_obj);
} zstate_t *st = (zstate_t *)(uintptr_t)(size_t)js_getnum(state_val);
st->strm.next_in = NULL;
st->strm.avail_in = 0;
uint8_t out_buf[ZCHUNK_SIZE];
int ret;
do {
st->strm.next_out = out_buf;
st->strm.avail_out = ZCHUNK_SIZE;
ret = deflate(&st->strm, Z_FINISH);
size_t have = ZCHUNK_SIZE - st->strm.avail_out;
if (have > 0) {
ant_value_t r = enqueue_buffer(js, ctrl_obj, out_buf, have);
if (is_err(r)) return r;
}
} while (ret != Z_STREAM_END);
return js_mkundef();
}
static ant_value_t js_cs_get_readable(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = get_ts(js->this_val);
if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid CompressionStream");
return ts_stream_readable(ts_obj);
}
static ant_value_t js_cs_get_writable(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = get_ts(js->this_val);
if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid CompressionStream");
return ts_stream_writable(ts_obj);
}
static ant_value_t js_cs_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "CompressionStream constructor requires 'new'");
if (nargs < 1)
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'CompressionStream': 1 argument required");
zformat_t fmt;
if (parse_format(js, args[0], &fmt) < 0)
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'CompressionStream': Unsupported compression format");
zstate_t *st = calloc(1, sizeof(zstate_t));
brotli_stream_state_t *brotli = NULL;
if (fmt == ZFMT_BROTLI) {
brotli = brotli_stream_state_new(false);
if (!brotli) return js_mkerr(js, "Failed to initialize compression");
} else {
if (!st) return js_mkerr(js, "out of memory");
st->format = fmt;
int ret = deflateInit2(&st->strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
zfmt_window_bits(fmt, false), 8, Z_DEFAULT_STRATEGY);
if (ret != Z_OK) { free(st); return js_mkerr(js, "Failed to initialize compression"); }
st->initialized = true;
}
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_cs_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
js_set_slot(obj, SLOT_DATA, fmt == ZFMT_BROTLI ? ANT_PTR(brotli) : ANT_PTR(st));
js_set_finalizer(obj, fmt == ZFMT_BROTLI ? brotli_state_finalize : zstate_finalize);
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, fmt == ZFMT_BROTLI ? ANT_PTR(brotli) : ANT_PTR(st));
js_set_slot(wrapper, SLOT_CTOR, js_mknum((double)fmt));
ant_value_t transformer = js_mkobj(js);
ant_value_t transform_fn = js_heavy_mkfun(js, cs_transform, wrapper);
ant_value_t flush_fn = js_heavy_mkfun(js, cs_flush, wrapper);
js_set(js, transformer, "transform", transform_fn);
js_set(js, transformer, "flush", flush_fn);
ant_value_t ctor_args[1] = { transformer };
ant_value_t saved_new_target = js->new_target;
ant_value_t saved_this = js->this_val;
js->new_target = js_mknum(1);
ant_value_t ts_obj = js_ts_ctor(js, ctor_args, 1);
js->new_target = saved_new_target;
js->this_val = saved_this;
if (is_err(ts_obj)) {
if (fmt == ZFMT_BROTLI) brotli_stream_state_destroy(brotli);
else { deflateEnd(&st->strm); free(st); }
return ts_obj;
}
js_set_slot(obj, SLOT_ENTRIES, ts_obj);
js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
return obj;
}
static ant_value_t ds_transform(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
zstate_t *st = (zstate_t *)(uintptr_t)(size_t)js_getnum(state_val);
ant_value_t ctrl_obj = (nargs > 1) ? args[1] : js_mkundef();
ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
const uint8_t *input = NULL;
size_t input_len = 0;
- if (!is_object_type(chunk) || !get_chunk_bytes(js, chunk, &input, &input_len))
+ if (!is_object_type(chunk) || !buffer_source_get_bytes(js, chunk, &input, &input_len))
return js_mkerr_typed(js, JS_ERR_TYPE, "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'");
if (!input || input_len == 0) return js_mkundef();
if (get_wrapper_format(wrapper) == ZFMT_BROTLI) {
brotli_stream_state_t *brotli_st =
(brotli_stream_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
return brotli_stream_transform(js, brotli_st, ctrl_obj, input, input_len);
}
st->strm.next_in = (Bytef *)input;
st->strm.avail_in = (uInt)input_len;
uint8_t out_buf[ZCHUNK_SIZE];
do {
st->strm.next_out = out_buf;
st->strm.avail_out = ZCHUNK_SIZE;
int ret = inflate(&st->strm, Z_NO_FLUSH);
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
return js_mkerr_typed(js, JS_ERR_TYPE, "Decompression failed");
size_t have = ZCHUNK_SIZE - st->strm.avail_out;
if (have > 0) {
ant_value_t r = enqueue_buffer(js, ctrl_obj, out_buf, have);
if (is_err(r)) return r;
}
if (ret == Z_STREAM_END) break;
} while (st->strm.avail_out == 0);
return js_mkundef();
}
static ant_value_t ds_flush(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t state_val = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ctrl_obj = (nargs > 0) ? args[0] : js_mkundef();
if (get_wrapper_format(wrapper) == ZFMT_BROTLI) {
brotli_stream_state_t *st =
(brotli_stream_state_t *)(uintptr_t)(size_t)js_getnum(state_val);
return brotli_stream_flush(js, st, ctrl_obj);
}
return js_mkundef();
}
static ant_value_t js_ds_get_readable(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = get_ts(js->this_val);
if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid DecompressionStream");
return ts_stream_readable(ts_obj);
}
static ant_value_t js_ds_get_writable(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = get_ts(js->this_val);
if (!ts_is_stream(ts_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid DecompressionStream");
return ts_stream_writable(ts_obj);
}
static ant_value_t js_ds_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "DecompressionStream constructor requires 'new'");
if (nargs < 1)
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'DecompressionStream': 1 argument required");
zformat_t fmt;
if (parse_format(js, args[0], &fmt) < 0)
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'DecompressionStream': Unsupported compression format");
zstate_t *st = calloc(1, sizeof(zstate_t));
brotli_stream_state_t *brotli = NULL;
if (fmt == ZFMT_BROTLI) {
brotli = brotli_stream_state_new(true);
if (!brotli) return js_mkerr(js, "Failed to initialize decompression");
} else {
if (!st) return js_mkerr(js, "out of memory");
st->format = fmt;
int ret = inflateInit2(&st->strm, zfmt_window_bits(fmt, true));
if (ret != Z_OK) { free(st); return js_mkerr(js, "Failed to initialize decompression"); }
st->initialized = true;
}
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_ds_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
js_set_slot(obj, SLOT_DATA, fmt == ZFMT_BROTLI ? ANT_PTR(brotli) : ANT_PTR(st));
js_set_finalizer(obj, fmt == ZFMT_BROTLI ? brotli_state_finalize : zstate_inflate_finalize);
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, fmt == ZFMT_BROTLI ? ANT_PTR(brotli) : ANT_PTR(st));
js_set_slot(wrapper, SLOT_CTOR, js_mknum((double)fmt));
ant_value_t transformer = js_mkobj(js);
ant_value_t transform_fn = js_heavy_mkfun(js, ds_transform, wrapper);
ant_value_t flush_fn = js_heavy_mkfun(js, ds_flush, wrapper);
js_set(js, transformer, "transform", transform_fn);
js_set(js, transformer, "flush", flush_fn);
ant_value_t ctor_args[1] = { transformer };
ant_value_t saved_new_target = js->new_target;
ant_value_t saved_this = js->this_val;
js->new_target = js_mknum(1);
ant_value_t ts_obj = js_ts_ctor(js, ctor_args, 1);
js->new_target = saved_new_target;
js->this_val = saved_this;
if (is_err(ts_obj)) {
if (fmt == ZFMT_BROTLI) brotli_stream_state_destroy(brotli);
else { inflateEnd(&st->strm); free(st); }
return ts_obj;
}
js_set_slot(obj, SLOT_ENTRIES, ts_obj);
js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
return obj;
}
void init_compression_stream_module(void) {
ant_t *js = rt->js;
ant_value_t g = js_glob(js);
g_cs_proto = js_mkobj(js);
js_set_getter_desc(js, g_cs_proto, "readable", 8, js_mkfun(js_cs_get_readable), JS_DESC_C);
js_set_getter_desc(js, g_cs_proto, "writable", 8, js_mkfun(js_cs_get_writable), JS_DESC_C);
js_set_sym(js, g_cs_proto, get_toStringTag_sym(), js_mkstr(js, "CompressionStream", 17));
ant_value_t cs_ctor = js_make_ctor(js, js_cs_ctor, g_cs_proto, "CompressionStream", 17);
js_set(js, g, "CompressionStream", cs_ctor);
js_set_descriptor(js, g, "CompressionStream", 17, JS_DESC_W | JS_DESC_C);
g_ds_proto = js_mkobj(js);
js_set_getter_desc(js, g_ds_proto, "readable", 8, js_mkfun(js_ds_get_readable), JS_DESC_C);
js_set_getter_desc(js, g_ds_proto, "writable", 8, js_mkfun(js_ds_get_writable), JS_DESC_C);
js_set_sym(js, g_ds_proto, get_toStringTag_sym(), js_mkstr(js, "DecompressionStream", 19));
ant_value_t ds_ctor = js_make_ctor(js, js_ds_ctor, g_ds_proto, "DecompressionStream", 19);
js_set(js, g, "DecompressionStream", ds_ctor);
js_set_descriptor(js, g, "DecompressionStream", 19, JS_DESC_W | JS_DESC_C);
}
void gc_mark_compression_streams(ant_t *js, void (*mark)(ant_t *, ant_value_t)) {
mark(js, g_cs_proto);
mark(js, g_ds_proto);
}
diff --git a/src/streams/pipes.c b/src/streams/pipes.c
index ebd1e02..d877a03 100644
--- a/src/streams/pipes.c
+++ b/src/streams/pipes.c
@@ -1,784 +1,794 @@
#include <string.h>
#include "ant.h"
#include "errors.h"
#include "internal.h"
#include "descriptors.h"
#include "silver/engine.h"
#include "modules/assert.h"
#include "modules/abort.h"
#include "streams/pipes.h"
#include "streams/readable.h"
#include "streams/writable.h"
static void pipes_chain_promise(
ant_t *js, ant_value_t value,
ant_value_t on_resolve, ant_value_t on_reject
) {
ant_value_t promise = value;
if (vtype(promise) != T_PROMISE) {
promise = js_mkpromise(js);
js_resolve_promise(js, promise, value);
}
ant_value_t then_fn = js_get(js, promise, "then");
if (!is_callable(then_fn)) return;
ant_value_t then_args[2] = { on_resolve, on_reject };
sv_vm_call(js->vm, js, then_fn, promise, then_args, 2, NULL, false);
}
typedef struct {
bool settled;
bool shutting_down;
bool in_flight;
bool prevent_close;
bool prevent_abort;
bool prevent_cancel;
} pipe_state_t;
static void pipe_state_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
free((pipe_state_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
return;
}}
}
static pipe_state_t *pipe_get_state(ant_value_t state) {
ant_value_t s = js_get_slot(state, SLOT_DATA);
if (vtype(s) != T_NUM) return NULL;
return (pipe_state_t *)(uintptr_t)(size_t)js_getnum(s);
}
static ant_value_t pipe_state_source(ant_value_t state) {
return js_get_slot(state, SLOT_ENTRIES);
}
static ant_value_t pipe_state_dest(ant_value_t state) {
return js_get_slot(state, SLOT_CTOR);
}
static ant_value_t pipe_state_reader(ant_value_t state) {
return js_get_slot(state, SLOT_BUFFER);
}
static ant_value_t pipe_state_writer(ant_value_t state) {
return js_get_slot(state, SLOT_DEFAULT);
}
static ant_value_t pipe_state_promise(ant_value_t state) {
return js_get_slot(state, SLOT_RS_PULL);
}
static void pipes_release_reader(ant_t *js, ant_value_t reader_obj) {
ant_value_t stream_obj = rs_reader_stream(reader_obj);
if (!rs_is_stream(stream_obj)) return;
if (rs_reader_has_reqs(js, reader_obj)) {
ant_value_t release_err = js_make_error_silent(js, JS_ERR_TYPE, "Reader was released");
rs_default_reader_error_read_requests(js, reader_obj, release_err);
}
ant_value_t old_closed = rs_reader_closed(reader_obj);
ant_value_t new_closed = js_mkpromise(js);
ant_value_t release_err = js_make_error_silent(js, JS_ERR_TYPE, "Reader was released");
rs_stream_t *rs = rs_get_stream(stream_obj);
if (rs && rs->state == RS_STATE_READABLE) {
js_reject_promise(js, old_closed, release_err);
promise_mark_handled(old_closed);
}
js_reject_promise(js, new_closed, release_err);
promise_mark_handled(new_closed);
js_set_slot(reader_obj, SLOT_RS_CLOSED, new_closed);
js_set_slot(stream_obj, SLOT_CTOR, js_mkundef());
js_set_slot(reader_obj, SLOT_ENTRIES, js_mkundef());
}
static void pipes_release_writer(ant_t *js, ant_value_t writer_obj) {
ant_value_t ws_obj = js_get_slot(writer_obj, SLOT_ENTRIES);
if (!ws_is_stream(ws_obj)) return;
ant_value_t rel_err = js_make_error_silent(js, JS_ERR_TYPE, "Writer was released");
ant_value_t ready = js_mkpromise(js);
js_reject_promise(js, ready, rel_err);
promise_mark_handled(ready);
js_set_slot(writer_obj, SLOT_WS_READY, ready);
ant_value_t closed = js_mkpromise(js);
js_reject_promise(js, closed, rel_err);
promise_mark_handled(closed);
js_set_slot(writer_obj, SLOT_RS_CLOSED, closed);
js_set_slot(ws_obj, SLOT_CTOR, js_mkundef());
js_set_slot(writer_obj, SLOT_ENTRIES, js_mkundef());
}
static void pipes_release_locks(ant_t *js, ant_value_t state) {
ant_value_t reader = pipe_state_reader(state);
if (rs_is_reader(reader)) {
pipes_release_reader(js, reader);
js_set_slot(state, SLOT_BUFFER, js_mkundef());
}
ant_value_t writer = pipe_state_writer(state);
if (ws_is_writer(writer)) {
pipes_release_writer(js, writer);
js_set_slot(state, SLOT_DEFAULT, js_mkundef());
}
}
static void pipes_settle(ant_t *js, ant_value_t state, bool ok, ant_value_t value) {
pipe_state_t *pst = pipe_get_state(state);
if (!pst || pst->settled) return;
pst->settled = true;
pst->shutting_down = true;
pipes_release_locks(js, state);
ant_value_t promise = pipe_state_promise(state);
if (ok) js_resolve_promise(js, promise, value);
else js_reject_promise(js, promise, value);
}
static void pipes_shutdown_from_source_error(ant_t *js, ant_value_t state, ant_value_t error) {
pipe_state_t *pst = pipe_get_state(state);
if (!pst || pst->settled || pst->shutting_down) return;
pst->shutting_down = true;
pst->in_flight = false;
if (!pst->prevent_abort) {
ant_value_t result = writable_stream_abort(js, pipe_state_dest(state), error);
promise_mark_handled(result);
}
pipes_settle(js, state, false, error);
}
static void pipes_shutdown_from_dest_error(ant_t *js, ant_value_t state, ant_value_t error) {
pipe_state_t *pst = pipe_get_state(state);
if (!pst || pst->settled || pst->shutting_down) return;
pst->shutting_down = true;
pst->in_flight = false;
if (!pst->prevent_cancel) {
ant_value_t result = readable_stream_cancel(js, pipe_state_source(state), error);
promise_mark_handled(result);
}
pipes_settle(js, state, false, error);
}
static void pipes_shutdown_from_abort(ant_t *js, ant_value_t state, ant_value_t reason) {
pipe_state_t *pst = pipe_get_state(state);
if (!pst || pst->settled || pst->shutting_down) return;
pst->shutting_down = true;
pst->in_flight = false;
if (!pst->prevent_abort) {
ant_value_t result = writable_stream_abort(js, pipe_state_dest(state), reason);
promise_mark_handled(result);
}
if (!pst->prevent_cancel) {
ant_value_t result = readable_stream_cancel(js, pipe_state_source(state), reason);
promise_mark_handled(result);
}
pipes_settle(js, state, false, reason);
}
static void pipes_pump(ant_t *js, ant_value_t state);
static ant_value_t pipe_write_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
pipe_state_t *pst = pipe_get_state(state);
if (!pst) return js_mkundef();
pst->in_flight = false;
if (pst->settled || pst->shutting_down)
return js_mkundef();
pipes_pump(js, state);
return js_mkundef();
}
static ant_value_t pipe_dest_error(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t error = (nargs > 0) ? args[0] : js_mkundef();
pipes_shutdown_from_dest_error(js, state, error);
return js_mkundef();
}
static ant_value_t pipe_close_dest_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
pipes_settle(js, state, true, js_mkundef());
return js_mkundef();
}
static ant_value_t pipe_close_dest_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t error = (nargs > 0) ? args[0] : js_mkundef();
pipes_settle(js, state, false, error);
return js_mkundef();
}
static ant_value_t pipe_ignore(ant_t *js, ant_value_t *args, int nargs) {
return js_mkundef();
}
static ant_value_t pipe_read_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
pipe_state_t *pst = pipe_get_state(state);
if (!pst || pst->settled || pst->shutting_down) {
if (pst) pst->in_flight = false;
return js_mkundef();
}
ant_value_t result = (nargs > 0) ? args[0] : js_mkundef();
bool done = js_truthy(js, js_get(js, result, "done"));
if (done) {
pst->in_flight = false;
if (pst->prevent_close) {
pipes_settle(js, state, true, js_mkundef());
return js_mkundef();
}
ant_value_t close_promise = writable_stream_close(js, pipe_state_dest(state));
ant_value_t on_resolve = js_heavy_mkfun(js, pipe_close_dest_resolve, state);
ant_value_t on_reject = js_heavy_mkfun(js, pipe_close_dest_reject, state);
pipes_chain_promise(js, close_promise, on_resolve, on_reject);
return js_mkundef();
}
ant_value_t value = js_get(js, result, "value");
ant_value_t write_promise = ws_writer_write(js, pipe_state_writer(state), value);
ant_value_t on_resolve = js_heavy_mkfun(js, pipe_write_resolve, state);
ant_value_t on_reject = js_heavy_mkfun(js, pipe_dest_error, state);
pipes_chain_promise(js, write_promise, on_resolve, on_reject);
return js_mkundef();
}
static ant_value_t pipe_source_error(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t error = (nargs > 0) ? args[0] : js_mkundef();
pipes_shutdown_from_source_error(js, state, error);
return js_mkundef();
}
static ant_value_t pipe_ready_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
pipe_state_t *pst = pipe_get_state(state);
if (!pst || pst->settled || pst->shutting_down) {
if (pst) pst->in_flight = false;
return js_mkundef();
}
ant_value_t read_promise = rs_default_reader_read(js, pipe_state_reader(state));
ant_value_t on_resolve = js_heavy_mkfun(js, pipe_read_resolve, state);
ant_value_t on_reject = js_heavy_mkfun(js, pipe_source_error, state);
pipes_chain_promise(js, read_promise, on_resolve, on_reject);
return js_mkundef();
}
static ant_value_t pipe_abort_listener(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
pipe_state_t *pst = pipe_get_state(state);
if (!pst || pst->settled || pst->shutting_down)
return js_mkundef();
ant_value_t signal = js_get_slot(state, SLOT_RS_CANCEL);
pipes_shutdown_from_abort(js, state, abort_signal_get_reason(signal));
return js_mkundef();
}
static void pipes_pump(ant_t *js, ant_value_t state) {
pipe_state_t *pst = pipe_get_state(state);
if (!pst || pst->settled || pst->shutting_down || pst->in_flight) return;
pst->in_flight = true;
ant_value_t writer = pipe_state_writer(state);
ant_value_t ready = ws_writer_ready(writer);
ant_value_t on_resolve = js_heavy_mkfun(js, pipe_ready_resolve, state);
ant_value_t on_reject = js_heavy_mkfun(js, pipe_dest_error, state);
pipes_chain_promise(js, ready, on_resolve, on_reject);
}
static ant_value_t pipe_create_rejected(ant_t *js, ant_value_t error) {
ant_value_t promise = js_mkpromise(js);
js_reject_promise(js, promise, error);
return promise;
}
static void pipes_parse_options(
ant_t *js, ant_value_t options,
bool *prevent_close, bool *prevent_abort, bool *prevent_cancel,
ant_value_t *signal
) {
*prevent_close = false;
*prevent_abort = false;
*prevent_cancel = false;
*signal = js_mkundef();
if (!is_object_type(options)) return;
*prevent_close = js_truthy(js, js_get(js, options, "preventClose"));
*prevent_abort = js_truthy(js, js_get(js, options, "preventAbort"));
*prevent_cancel = js_truthy(js, js_get(js, options, "preventCancel"));
*signal = js_get(js, options, "signal");
}
ant_value_t readable_stream_pipe_to(
ant_t *js, ant_value_t source, ant_value_t dest,
bool prevent_close, bool prevent_abort, bool prevent_cancel,
ant_value_t signal
) {
rs_stream_t *rs = rs_get_stream(source);
ws_stream_t *ws = ws_get_stream(dest);
if (!rs || !ws) {
js_mkerr_typed(js, JS_ERR_TYPE, "pipeTo requires a ReadableStream and WritableStream");
return pipe_create_rejected(js, js->thrown_value);
}
if (rs_is_reader(rs_stream_reader(source))) {
js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStream is already locked");
return pipe_create_rejected(js, js->thrown_value);
}
if (ws_is_writer(ws_stream_writer(dest))) {
js_mkerr_typed(js, JS_ERR_TYPE, "WritableStream is already locked");
return pipe_create_rejected(js, js->thrown_value);
}
if (!is_undefined(signal) && !abort_signal_is_signal(signal)) {
js_mkerr_typed(js, JS_ERR_TYPE, "pipeTo option 'signal' must be an AbortSignal");
return pipe_create_rejected(js, js->thrown_value);
}
ant_value_t reader_args[1] = { source };
ant_value_t saved = js->new_target;
js->new_target = g_reader_proto;
ant_value_t reader = js_rs_reader_ctor(js, reader_args, 1);
js->new_target = saved;
if (is_err(reader)) return pipe_create_rejected(js, js->thrown_value);
ant_value_t writer = ws_acquire_writer(js, dest);
if (is_err(writer)) {
pipes_release_reader(js, reader);
return pipe_create_rejected(js, js->thrown_value);
}
pipe_state_t *pst = calloc(1, sizeof(pipe_state_t));
if (!pst) return js_mkerr(js, "out of memory");
pst->prevent_close = prevent_close;
pst->prevent_abort = prevent_abort;
pst->prevent_cancel = prevent_cancel;
ant_value_t promise = js_mkpromise(js);
ant_value_t state = js_mkobj(js);
js_set_slot(state, SLOT_DATA, ANT_PTR(pst));
js_set_slot(state, SLOT_ENTRIES, source);
js_set_slot(state, SLOT_CTOR, dest);
js_set_slot(state, SLOT_BUFFER, reader);
js_set_slot(state, SLOT_DEFAULT, writer);
js_set_slot(state, SLOT_RS_PULL, promise);
js_set_finalizer(state, pipe_state_finalize);
js_set_slot(state, SLOT_RS_CANCEL, signal);
promise_mark_handled(rs_reader_closed(reader));
promise_mark_handled(js_get_slot(writer, SLOT_RS_CLOSED));
promise_mark_handled(js_get_slot(writer, SLOT_WS_READY));
ant_value_t ignore_fn = js_heavy_mkfun(js, pipe_ignore, state);
ant_value_t source_closed_reject = js_heavy_mkfun(js, pipe_source_error, state);
ant_value_t dest_closed_reject = js_heavy_mkfun(js, pipe_dest_error, state);
pipes_chain_promise(js, rs_reader_closed(reader), ignore_fn, source_closed_reject);
pipes_chain_promise(js, js_get_slot(writer, SLOT_RS_CLOSED), ignore_fn, dest_closed_reject);
if (abort_signal_is_signal(signal) && abort_signal_is_aborted(signal)) {
pipes_shutdown_from_abort(js, state, abort_signal_get_reason(signal));
return promise;
}
if (abort_signal_is_signal(signal)) {
ant_value_t listener = js_heavy_mkfun(js, pipe_abort_listener, state);
abort_signal_add_listener(js, signal, listener);
}
pipes_pump(js, state);
return promise;
}
static ant_value_t js_rs_pipe_to(ant_t *js, ant_value_t *args, int nargs) {
if (!rs_is_stream(js->this_val)) {
js_mkerr_typed(js, JS_ERR_TYPE, "Invalid ReadableStream");
return pipe_create_rejected(js, js->thrown_value);
}
ant_value_t dest = (nargs > 0) ? args[0] : js_mkundef();
bool prevent_close, prevent_abort, prevent_cancel;
ant_value_t signal;
pipes_parse_options(js, nargs > 1 ? args[1] : js_mkundef(),
&prevent_close, &prevent_abort, &prevent_cancel, &signal);
return readable_stream_pipe_to(js, js->this_val, dest,
prevent_close, prevent_abort, prevent_cancel, signal);
}
static ant_value_t js_rs_pipe_through(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t source = js->this_val;
if (!rs_is_stream(source)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid ReadableStream");
if (rs_is_reader(rs_stream_reader(source)))
return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStream is already locked");
if (nargs < 1 || !is_object_type(args[0]))
return js_mkerr_typed(js, JS_ERR_TYPE, "pipeThrough requires a transform object");
ant_value_t transform = args[0];
ant_value_t writable = js_get(js, transform, "writable");
ant_value_t readable = js_get(js, transform, "readable");
if (!ws_is_stream(writable))
return js_mkerr_typed(js, JS_ERR_TYPE, "pipeThrough transform.writable must be a WritableStream");
if (!rs_is_stream(readable))
return js_mkerr_typed(js, JS_ERR_TYPE, "pipeThrough transform.readable must be a ReadableStream");
if (ws_is_writer(ws_stream_writer(writable)))
return js_mkerr_typed(js, JS_ERR_TYPE, "WritableStream is already locked");
bool prevent_close, prevent_abort, prevent_cancel;
ant_value_t signal;
pipes_parse_options(
js, nargs > 1 ? args[1] : js_mkundef(),
&prevent_close, &prevent_abort, &prevent_cancel, &signal
);
ant_value_t pipe_promise = readable_stream_pipe_to(
js, source, writable,
prevent_close, prevent_abort, prevent_cancel, signal
);
promise_mark_handled(pipe_promise);
return readable;
}
typedef struct {
bool pulling;
bool done;
bool canceled1;
bool canceled2;
} tee_state_t;
static void tee_state_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
free((tee_state_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
return;
}}
}
static tee_state_t *tee_get_state(ant_value_t state) {
ant_value_t s = js_get_slot(state, SLOT_DATA);
if (vtype(s) != T_NUM) return NULL;
return (tee_state_t *)(uintptr_t)(size_t)js_getnum(s);
}
static ant_value_t tee_state_reader(ant_value_t state) {
return js_get_slot(state, SLOT_BUFFER);
}
static void tee_release_reader(ant_t *js, ant_value_t state) {
ant_value_t reader = tee_state_reader(state);
if (!rs_is_reader(reader)) return;
pipes_release_reader(js, reader);
js_set_slot(state, SLOT_BUFFER, js_mkundef());
}
static void tee_resolve_cancel_promises(ant_t *js, ant_value_t state) {
ant_value_t p1 = js_get_slot(state, SLOT_RS_CLOSED);
ant_value_t p2 = js_get_slot(state, SLOT_RS_SIZE);
if (vtype(p1) == T_PROMISE) {
js_resolve_promise(js, p1, js_mkundef());
js_set_slot(state, SLOT_RS_CLOSED, js_mkundef());
}
if (vtype(p2) == T_PROMISE) {
js_resolve_promise(js, p2, js_mkundef());
js_set_slot(state, SLOT_RS_SIZE, js_mkundef());
}
}
static void tee_reject_cancel_promises(ant_t *js, ant_value_t state, ant_value_t error) {
ant_value_t p1 = js_get_slot(state, SLOT_RS_CLOSED);
ant_value_t p2 = js_get_slot(state, SLOT_RS_SIZE);
if (vtype(p1) == T_PROMISE) {
js_reject_promise(js, p1, error);
js_set_slot(state, SLOT_RS_CLOSED, js_mkundef());
}
if (vtype(p2) == T_PROMISE) {
js_reject_promise(js, p2, error);
js_set_slot(state, SLOT_RS_SIZE, js_mkundef());
}
}
static void tee_finalize(ant_t *js, ant_value_t state) {
tee_state_t *st = tee_get_state(state);
if (!st || st->done) return;
st->done = true;
tee_release_reader(js, state);
}
static void tee_close_branch(ant_t *js, ant_value_t branch_stream) {
ant_value_t ctrl = rs_stream_controller(js, branch_stream);
rs_controller_t *c = rs_get_controller(ctrl);
if (!c || c->close_requested) return;
c->close_requested = true;
if (rs_ctrl_queue_len(js, ctrl) == 0) {
rs_default_controller_clear_algorithms(ctrl);
readable_stream_close(js, branch_stream);
}
}
static void tee_enqueue_branch(ant_t *js, ant_value_t branch_stream, ant_value_t value) {
ant_value_t ctrl = rs_stream_controller(js, branch_stream);
rs_controller_t *c = rs_get_controller(ctrl);
if (!c || !rs_default_controller_can_close_or_enqueue(c, rs_get_stream(branch_stream)))
return;
ant_value_t r = rs_stream_reader(branch_stream);
if (rs_is_reader(r) && rs_reader_has_reqs(js, r)) {
rs_fulfill_read_request(js, branch_stream, value, false);
rs_default_controller_call_pull_if_needed(js, ctrl);
return;
}
double chunk_size = 1;
ant_value_t size_fn = rs_ctrl_size(ctrl);
if (is_callable(size_fn)) {
ant_value_t sa[1] = { value };
ant_value_t sr = sv_vm_call(js->vm, js, size_fn, js_mkundef(), sa, 1, NULL, false);
if (!is_err(sr))
chunk_size = vtype(sr) == T_NUM ? js_getnum(sr) : js_to_number(js, sr);
}
rs_ctrl_queue_push(js, ctrl, value);
if (c->queue_sizes_len >= c->queue_sizes_cap) {
uint32_t nc = c->queue_sizes_cap ? c->queue_sizes_cap * 2 : 8;
double *ns = realloc(c->queue_sizes, nc * sizeof(double));
if (ns) { c->queue_sizes = ns; c->queue_sizes_cap = nc; }
}
if (c->queue_sizes_len < c->queue_sizes_cap)
c->queue_sizes[c->queue_sizes_len++] = chunk_size;
c->queue_total_size += chunk_size;
rs_default_controller_call_pull_if_needed(js, ctrl);
}
static void tee_error_branch(ant_t *js, ant_value_t branch_stream, ant_value_t error) {
readable_stream_error(js, branch_stream, error);
}
static void tee_pull(ant_t *js, ant_value_t state);
static ant_value_t tee_cancel_both_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
tee_resolve_cancel_promises(js, state);
tee_finalize(js, state);
return js_mkundef();
}
static ant_value_t tee_cancel_both_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t error = (nargs > 0) ? args[0] : js_mkundef();
tee_reject_cancel_promises(js, state, error);
tee_finalize(js, state);
return js_mkundef();
}
static ant_value_t tee_read_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
tee_state_t *st = tee_get_state(state);
if (!st) return js_mkundef();
st->pulling = false;
if (st->done) return js_mkundef();
ant_value_t result = (nargs > 0) ? args[0] : js_mkundef();
bool done = js_truthy(js, js_get(js, result, "done"));
ant_value_t branch1 = js_get_slot(state, SLOT_CTOR);
ant_value_t branch2 = js_get_slot(state, SLOT_DEFAULT);
if (done) {
if (!st->canceled1) tee_close_branch(js, branch1);
if (!st->canceled2) tee_close_branch(js, branch2);
tee_resolve_cancel_promises(js, state);
tee_finalize(js, state);
return js_mkundef();
}
ant_value_t value = js_get(js, result, "value");
if (!st->canceled1) tee_enqueue_branch(js, branch1, value);
if (!st->canceled2) tee_enqueue_branch(js, branch2, value);
return js_mkundef();
}
static ant_value_t tee_read_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
tee_state_t *st = tee_get_state(state);
if (!st) return js_mkundef();
ant_value_t error = (nargs > 0) ? args[0] : js_mkundef();
st->pulling = false;
if (st->done) return js_mkundef();
ant_value_t branch1 = js_get_slot(state, SLOT_CTOR);
ant_value_t branch2 = js_get_slot(state, SLOT_DEFAULT);
if (!st->canceled1) tee_error_branch(js, branch1, error);
if (!st->canceled2) tee_error_branch(js, branch2, error);
tee_resolve_cancel_promises(js, state);
tee_finalize(js, state);
return js_mkundef();
}
static void tee_pull(ant_t *js, ant_value_t state) {
tee_state_t *st = tee_get_state(state);
if (!st || st->done || st->pulling) return;
if (st->canceled1 && st->canceled2) return;
st->pulling = true;
ant_value_t read_promise = rs_default_reader_read(js, tee_state_reader(state));
ant_value_t on_resolve = js_heavy_mkfun(js, tee_read_resolve, state);
ant_value_t on_reject = js_heavy_mkfun(js, tee_read_reject, state);
pipes_chain_promise(js, read_promise, on_resolve, on_reject);
}
static ant_value_t tee_branch_pull(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
tee_pull(js, state);
ant_value_t promise = js_mkpromise(js);
js_resolve_promise(js, promise, js_mkundef());
return promise;
}
static ant_value_t tee_branch_cancel(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t state = js_get_slot(wrapper, SLOT_DATA);
int branch = (int)js_getnum(js_get_slot(wrapper, SLOT_ENTRIES));
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
tee_state_t *st = tee_get_state(state);
if (!st) return js_mkundef();
bool is_b1 = (branch == 1);
internal_slot_t reason_slot = is_b1 ? SLOT_RS_PULL : SLOT_RS_CANCEL;
internal_slot_t promise_slot = is_b1 ? SLOT_RS_CLOSED : SLOT_RS_SIZE;
bool already_canceled = is_b1 ? st->canceled1 : st->canceled2;
if (already_canceled) {
ant_value_t existing = js_get_slot(state, promise_slot);
if (vtype(existing) == T_PROMISE) return existing;
ant_value_t resolved = js_mkpromise(js);
js_resolve_promise(js, resolved, js_mkundef());
return resolved;
}
if (is_b1) st->canceled1 = true;
else st->canceled2 = true;
js_set_slot(state, reason_slot, reason);
ant_value_t promise = js_mkpromise(js);
js_set_slot(state, promise_slot, promise);
if (st->done) {
js_resolve_promise(js, promise, js_mkundef());
js_set_slot(state, promise_slot, js_mkundef());
return promise;
}
if (st->canceled1 && st->canceled2) {
ant_value_t reasons = js_mkarr(js);
js_arr_push(js, reasons, js_get_slot(state, SLOT_RS_PULL));
js_arr_push(js, reasons, js_get_slot(state, SLOT_RS_CANCEL));
ant_value_t orig_stream = js_get_slot(state, SLOT_ENTRIES);
ant_value_t cancel_promise = readable_stream_cancel(js, orig_stream, reasons);
ant_value_t on_resolve = js_heavy_mkfun(js, tee_cancel_both_resolve, state);
ant_value_t on_reject = js_heavy_mkfun(js, tee_cancel_both_reject, state);
pipes_chain_promise(js, cancel_promise, on_resolve, on_reject);
}
return promise;
}
static ant_value_t js_rs_tee(ant_t *js, ant_value_t *args, int nargs) {
if (!rs_is_stream(js->this_val)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid ReadableStream");
if (rs_is_reader(rs_stream_reader(js->this_val)))
return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStream is already locked");
ant_value_t reader_args[1] = { js->this_val };
ant_value_t saved = js->new_target;
js->new_target = g_reader_proto;
ant_value_t reader = js_rs_reader_ctor(js, reader_args, 1);
js->new_target = saved;
if (is_err(reader)) return reader;
tee_state_t *st = calloc(1, sizeof(tee_state_t));
if (!st) return js_mkerr(js, "out of memory");
ant_value_t state = js_mkobj(js);
js_set_slot(state, SLOT_DATA, ANT_PTR(st));
js_set_slot(state, SLOT_ENTRIES, js->this_val);
js_set_slot(state, SLOT_BUFFER, reader);
js_set_slot(state, SLOT_RS_PULL, js_mkundef());
js_set_slot(state, SLOT_RS_CANCEL, js_mkundef());
js_set_slot(state, SLOT_RS_CLOSED, js_mkundef());
js_set_slot(state, SLOT_RS_SIZE, js_mkundef());
js_set_finalizer(state, tee_state_finalize);
ant_value_t pull1 = js_heavy_mkfun(js, tee_branch_pull, state);
ant_value_t pull2 = js_heavy_mkfun(js, tee_branch_pull, state);
ant_value_t cancel1_wrap = js_mkobj(js);
js_set_slot(cancel1_wrap, SLOT_DATA, state);
js_set_slot(cancel1_wrap, SLOT_ENTRIES, js_mknum(1));
ant_value_t cancel1 = js_heavy_mkfun(js, tee_branch_cancel, cancel1_wrap);
ant_value_t cancel2_wrap = js_mkobj(js);
js_set_slot(cancel2_wrap, SLOT_DATA, state);
js_set_slot(cancel2_wrap, SLOT_ENTRIES, js_mknum(2));
ant_value_t cancel2 = js_heavy_mkfun(js, tee_branch_cancel, cancel2_wrap);
ant_value_t branch1 = rs_create_stream(js, pull1, cancel1, 1);
ant_value_t branch2 = rs_create_stream(js, pull2, cancel2, 1);
if (is_err(branch1) || is_err(branch2)) {
tee_release_reader(js, state);
return is_err(branch1) ? branch1 : branch2;
}
js_set_slot(state, SLOT_CTOR, branch1);
js_set_slot(state, SLOT_DEFAULT, branch2);
ant_value_t result = js_mkarr(js);
js_arr_push(js, result, branch1);
js_arr_push(js, result, branch2);
return result;
}
+ant_value_t readable_stream_tee(ant_t *js, ant_value_t source) {
+ ant_value_t saved_this = js->this_val;
+ js->this_val = source;
+
+ ant_value_t result = js_rs_tee(js, NULL, 0);
+ js->this_val = saved_this;
+
+ return result;
+}
+
void init_pipes_proto(ant_t *js, ant_value_t rs_proto) {
js_set(js, rs_proto, "pipeTo", js_mkfun(js_rs_pipe_to));
js_set_descriptor(js, rs_proto, "pipeTo", 6, JS_DESC_W | JS_DESC_C);
js_set(js, rs_proto, "pipeThrough", js_mkfun(js_rs_pipe_through));
js_set_descriptor(js, rs_proto, "pipeThrough", 11, JS_DESC_W | JS_DESC_C);
js_set(js, rs_proto, "tee", js_mkfun(js_rs_tee));
js_set_descriptor(js, rs_proto, "tee", 3, JS_DESC_W | JS_DESC_C);
}
diff --git a/src/streams/readable.c b/src/streams/readable.c
index 9190fb2..1edf011 100644
--- a/src/streams/readable.c
+++ b/src/streams/readable.c
@@ -1,987 +1,1015 @@
#include <stdlib.h>
#include <string.h>
#include "ant.h"
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "descriptors.h"
#include "silver/engine.h"
#include "modules/symbol.h"
#include "modules/assert.h"
#include "streams/readable.h"
#include "streams/pipes.h"
ant_value_t g_rs_proto;
ant_value_t g_reader_proto;
ant_value_t g_controller_proto;
bool rs_is_stream(ant_value_t obj) {
- return is_object_type(obj) && rs_get_stream(obj) != NULL;
+ if (!is_object_type(obj)) return false;
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ return vtype(brand) == T_NUM
+ && (int)js_getnum(brand) == BRAND_READABLE_STREAM
+ && rs_get_stream(obj) != NULL;
}
bool rs_is_reader(ant_value_t obj) {
- return is_object_type(obj)
+ if (!is_object_type(obj)) return false;
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ return vtype(brand) == T_NUM
+ && (int)js_getnum(brand) == BRAND_READABLE_STREAM_READER
&& vtype(js_get_slot(obj, SLOT_RS_CLOSED)) == T_PROMISE
&& vtype(js_get_slot(obj, SLOT_BUFFER)) == T_ARR;
}
bool rs_is_controller(ant_value_t obj) {
- return is_object_type(obj)
+ if (!is_object_type(obj)) return false;
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ return vtype(brand) == T_NUM
+ && (int)js_getnum(brand) == BRAND_READABLE_STREAM_CONTROLLER
&& rs_get_controller(obj) != NULL
&& rs_is_stream(js_get_slot(obj, SLOT_ENTRIES));
}
+bool rs_stream_locked(ant_value_t stream_obj) {
+ return rs_is_stream(stream_obj) && rs_is_reader(rs_stream_reader(stream_obj));
+}
+
+bool rs_stream_disturbed(ant_value_t stream_obj) {
+ rs_stream_t *stream = rs_get_stream(stream_obj);
+ return stream && stream->disturbed;
+}
+
+bool rs_stream_unusable(ant_value_t stream_obj) {
+ return rs_stream_locked(stream_obj) || rs_stream_disturbed(stream_obj);
+}
+
rs_stream_t *rs_get_stream(ant_value_t obj) {
ant_value_t s = js_get_slot(obj, SLOT_DATA);
if (vtype(s) != T_NUM) return NULL;
return (rs_stream_t *)(uintptr_t)(size_t)js_getnum(s);
}
rs_controller_t *rs_get_controller(ant_value_t obj) {
ant_value_t s = js_get_slot(obj, SLOT_DATA);
if (vtype(s) != T_NUM) return NULL;
return (rs_controller_t *)(uintptr_t)(size_t)js_getnum(s);
}
static void rs_stream_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
free((rs_stream_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
return;
}}
}
static void rs_controller_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
rs_controller_t *ctrl = (rs_controller_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
free(ctrl->queue_sizes);
free(ctrl);
return;
}}
}
ant_value_t rs_stream_controller(ant_t *js, ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_ENTRIES);
}
ant_value_t rs_stream_reader(ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_CTOR);
}
ant_value_t rs_stream_error(ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_BUFFER);
}
static inline ant_value_t rs_ctrl_stream(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_ENTRIES);
}
static inline ant_value_t rs_ctrl_pull(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_RS_PULL);
}
static inline ant_value_t rs_ctrl_cancel(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_RS_CANCEL);
}
ant_value_t rs_ctrl_size(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_RS_SIZE);
}
static inline ant_value_t rs_ctrl_queue(ant_t *js, ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_BUFFER);
}
ant_value_t rs_reader_stream(ant_value_t reader_obj) {
return js_get_slot(reader_obj, SLOT_ENTRIES);
}
ant_value_t rs_reader_closed(ant_value_t reader_obj) {
return js_get_slot(reader_obj, SLOT_RS_CLOSED);
}
ant_value_t rs_reader_reqs(ant_value_t reader_obj) {
return js_get_slot(reader_obj, SLOT_BUFFER);
}
bool rs_reader_has_reqs(ant_t *js, ant_value_t reader_obj) {
ant_value_t arr = rs_reader_reqs(reader_obj);
return vtype(arr) == T_ARR && js_arr_len(js, arr) > 0;
}
void rs_ctrl_queue_push(ant_t *js, ant_value_t ctrl_obj, ant_value_t value) {
ant_value_t arr = rs_ctrl_queue(js, ctrl_obj);
if (vtype(arr) == T_ARR) js_arr_push(js, arr, value);
}
static ant_value_t rs_ctrl_queue_shift(ant_t *js, ant_value_t ctrl_obj) {
ant_value_t arr = rs_ctrl_queue(js, ctrl_obj);
if (vtype(arr) != T_ARR) return js_mkundef();
ant_object_t *aobj = js_obj_ptr(arr);
if (aobj->u.array.len == 0) return js_mkundef();
ant_value_t val = aobj->u.array.data[0];
uint32_t new_len = aobj->u.array.len - 1;
for (uint32_t i = 0; i < new_len; i++)
aobj->u.array.data[i] = aobj->u.array.data[i + 1];
aobj->u.array.len = new_len;
return val;
}
ant_offset_t rs_ctrl_queue_len(ant_t *js, ant_value_t ctrl_obj) {
ant_value_t arr = rs_ctrl_queue(js, ctrl_obj);
if (vtype(arr) != T_ARR) return 0;
return js_arr_len(js, arr);
}
static void rs_reader_reqs_push(ant_t *js, ant_value_t reader_obj, ant_value_t promise) {
ant_value_t arr = rs_reader_reqs(reader_obj);
if (vtype(arr) == T_ARR) js_arr_push(js, arr, promise);
}
static ant_value_t rs_reader_reqs_shift(ant_t *js, ant_value_t reader_obj) {
ant_value_t arr = rs_reader_reqs(reader_obj);
if (vtype(arr) != T_ARR) return js_mkundef();
ant_object_t *aobj = js_obj_ptr(arr);
if (aobj->u.array.len == 0) return js_mkundef();
ant_value_t val = aobj->u.array.data[0];
uint32_t new_len = aobj->u.array.len - 1;
for (uint32_t i = 0; i < new_len; i++)
aobj->u.array.data[i] = aobj->u.array.data[i + 1];
aobj->u.array.len = new_len;
return val;
}
void rs_default_controller_call_pull_if_needed(ant_t *js, ant_value_t controller_obj);
bool rs_default_controller_can_close_or_enqueue(rs_controller_t *ctrl, rs_stream_t *stream);
void rs_default_controller_clear_algorithms(ant_value_t ctrl_obj) {
js_set_slot(ctrl_obj, SLOT_RS_PULL, js_mkundef());
js_set_slot(ctrl_obj, SLOT_RS_CANCEL, js_mkundef());
js_set_slot(ctrl_obj, SLOT_RS_SIZE, js_mkundef());
}
static double rs_default_controller_get_desired_size(rs_controller_t *ctrl, rs_stream_t *stream) {
if (!stream) return 0;
if (stream->state == RS_STATE_ERRORED) return -1;
if (stream->state == RS_STATE_CLOSED) return 0;
return ctrl->strategy_hwm - ctrl->queue_total_size;
}
static bool rs_default_controller_should_call_pull(ant_t *js, rs_controller_t *ctrl, rs_stream_t *stream, ant_value_t ctrl_obj) {
if (!rs_default_controller_can_close_or_enqueue(ctrl, stream)) return false;
if (!ctrl->started) return false;
ant_value_t stream_obj = rs_ctrl_stream(ctrl_obj);
ant_value_t reader_obj = rs_stream_reader(stream_obj);
if (rs_is_reader(reader_obj) && rs_reader_has_reqs(js, reader_obj)) return true;
double desired = rs_default_controller_get_desired_size(ctrl, stream);
return desired > 0;
}
bool rs_default_controller_can_close_or_enqueue(rs_controller_t *ctrl, rs_stream_t *stream) {
if (!ctrl || !stream) return false;
if (ctrl->close_requested) return false;
if (stream->state != RS_STATE_READABLE) return false;
return true;
}
void rs_fulfill_read_request(ant_t *js, ant_value_t stream_obj, ant_value_t chunk, bool done) {
ant_value_t reader_obj = rs_stream_reader(stream_obj);
if (!rs_is_reader(reader_obj)) return;
ant_value_t promise = rs_reader_reqs_shift(js, reader_obj);
if (vtype(promise) == T_UNDEF) return;
ant_value_t result = js_iter_result(js, !done, chunk);
js_resolve_promise(js, promise, result);
}
void rs_default_reader_error_read_requests(ant_t *js, ant_value_t reader_obj, ant_value_t e) {
ant_value_t arr = rs_reader_reqs(reader_obj);
if (vtype(arr) != T_ARR) return;
ant_offset_t len = js_arr_len(js, arr);
for (ant_offset_t i = 0; i < len; i++)
js_reject_promise(js, js_arr_get(js, arr, i), e);
ant_object_t *aobj = js_obj_ptr(arr);
aobj->u.array.len = 0;
}
void readable_stream_close(ant_t *js, ant_value_t stream_obj) {
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!stream) return;
stream->state = RS_STATE_CLOSED;
ant_value_t reader_obj = rs_stream_reader(stream_obj);
if (rs_is_reader(reader_obj)) {
ant_value_t arr = rs_reader_reqs(reader_obj);
if (vtype(arr) == T_ARR) {
ant_offset_t len = js_arr_len(js, arr);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t result = js_iter_result(js, false, js_mkundef());
js_resolve_promise(js, js_arr_get(js, arr, i), result);
}
ant_object_t *aobj = js_obj_ptr(arr);
aobj->u.array.len = 0;
}
js_resolve_promise(js, rs_reader_closed(reader_obj), js_mkundef());
}
}
void readable_stream_error(ant_t *js, ant_value_t stream_obj, ant_value_t e) {
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!stream || stream->state != RS_STATE_READABLE) return;
stream->state = RS_STATE_ERRORED;
js_set_slot(stream_obj, SLOT_BUFFER, e);
ant_value_t reader_obj = rs_stream_reader(stream_obj);
if (rs_is_reader(reader_obj)) {
js_reject_promise(js, rs_reader_closed(reader_obj), e);
rs_default_reader_error_read_requests(js, reader_obj, e);
}
}
ant_value_t rs_cancel_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t p = js_get_slot(js->current_func, SLOT_DATA);
js_resolve_promise(js, p, js_mkundef());
return js_mkundef();
}
ant_value_t rs_cancel_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t p = js_get_slot(js->current_func, SLOT_DATA);
js_reject_promise(js, p, nargs > 0 ? args[0] : js_mkundef());
return js_mkundef();
}
ant_value_t readable_stream_cancel(ant_t *js, ant_value_t stream_obj, ant_value_t reason) {
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!stream) return js_mkundef();
stream->disturbed = true;
if (stream->state == RS_STATE_CLOSED) {
ant_value_t p = js_mkpromise(js);
js_resolve_promise(js, p, js_mkundef());
return p;
}
if (stream->state == RS_STATE_ERRORED) {
ant_value_t p = js_mkpromise(js);
js_reject_promise(js, p, rs_stream_error(stream_obj));
return p;
}
readable_stream_close(js, stream_obj);
ant_value_t ctrl_obj = rs_stream_controller(js, stream_obj);
ant_value_t cancel_fn = rs_ctrl_cancel(ctrl_obj);
ant_value_t result = js_mkundef();
if (is_callable(cancel_fn)) {
ant_value_t cancel_args[1] = { reason };
result = sv_vm_call(js->vm, js, cancel_fn, ctrl_obj, cancel_args, 1, NULL, false);
}
rs_default_controller_clear_algorithms(ctrl_obj);
ant_value_t p = js_mkpromise(js);
if (is_err(result)) {
ant_value_t thrown = js->thrown_value;
js_reject_promise(js, p, is_object_type(thrown) ? thrown : result);
} else if (vtype(result) == T_PROMISE) {
ant_value_t res_fn = js_heavy_mkfun(js, rs_cancel_resolve, p);
ant_value_t rej_fn = js_heavy_mkfun(js, rs_cancel_reject, p);
ant_value_t then_fn = js_get(js, result, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { res_fn, rej_fn };
sv_vm_call(js->vm, js, then_fn, result, then_args, 2, NULL, false);
}
} else js_resolve_promise(js, p, js_mkundef());
return p;
}
static ant_value_t rs_pull_resolve_handler(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ctrl_obj = js_get_slot(js->current_func, SLOT_DATA);
rs_controller_t *ctrl = rs_get_controller(ctrl_obj);
if (!ctrl) return js_mkundef();
ctrl->pulling = false;
if (ctrl->pull_again) {
ctrl->pull_again = false;
rs_default_controller_call_pull_if_needed(js, ctrl_obj);
}
return js_mkundef();
}
static ant_value_t rs_pull_reject_handler(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ctrl_obj = js_get_slot(js->current_func, SLOT_DATA);
rs_controller_t *ctrl = rs_get_controller(ctrl_obj);
if (!ctrl) return js_mkundef();
ant_value_t stream_obj = rs_ctrl_stream(ctrl_obj);
rs_stream_t *stream = rs_get_stream(stream_obj);
if (stream && stream->state == RS_STATE_READABLE)
readable_stream_error(js, stream_obj, nargs > 0 ? args[0] : js_mkundef());
return js_mkundef();
}
void rs_default_controller_call_pull_if_needed(ant_t *js, ant_value_t controller_obj) {
rs_controller_t *ctrl = rs_get_controller(controller_obj);
if (!ctrl) return;
ant_value_t stream_obj = rs_ctrl_stream(controller_obj);
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!stream) return;
if (!rs_default_controller_should_call_pull(js, ctrl, stream, controller_obj)) return;
if (ctrl->pulling) { ctrl->pull_again = true; return; }
ctrl->pulling = true;
ant_value_t pull_fn = rs_ctrl_pull(controller_obj);
if (is_callable(pull_fn)) {
ant_value_t args[1] = { controller_obj };
ant_value_t result = sv_vm_call(js->vm, js, pull_fn, js_mkundef(), args, 1, NULL, false);
if (vtype(result) == T_PROMISE) {
ant_value_t resolve_fn = js_heavy_mkfun(js, rs_pull_resolve_handler, controller_obj);
ant_value_t reject_fn = js_heavy_mkfun(js, rs_pull_reject_handler, controller_obj);
ant_value_t then_fn = js_get(js, result, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { resolve_fn, reject_fn };
sv_vm_call(js->vm, js, then_fn, result, then_args, 2, NULL, false);
}
} else if (is_err(result)) {
if (stream->state == RS_STATE_READABLE) {
ant_value_t thrown = js->thrown_value;
readable_stream_error(js, stream_obj, is_object_type(thrown) ? thrown : result);
}
} else {
ant_value_t resolved = js_mkpromise(js);
js_resolve_promise(js, resolved, js_mkundef());
ant_value_t resolve_fn = js_heavy_mkfun(js, rs_pull_resolve_handler, controller_obj);
ant_value_t reject_fn = js_heavy_mkfun(js, rs_pull_reject_handler, controller_obj);
ant_value_t then_fn = js_get(js, resolved, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { resolve_fn, reject_fn };
sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false);
}
}
} else ctrl->pulling = false;
}
ant_value_t rs_default_reader_read(ant_t *js, ant_value_t reader_obj) {
ant_value_t stream_obj = rs_reader_stream(reader_obj);
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!stream) return js_mkerr_typed(js, JS_ERR_TYPE, "Reader has no stream");
stream->disturbed = true;
if (stream->state == RS_STATE_CLOSED) {
ant_value_t p = js_mkpromise(js);
js_resolve_promise(js, p, js_iter_result(js, false, js_mkundef()));
return p;
}
if (stream->state == RS_STATE_ERRORED) {
ant_value_t p = js_mkpromise(js);
js_reject_promise(js, p, rs_stream_error(stream_obj));
return p;
}
ant_value_t ctrl_obj = rs_stream_controller(js, stream_obj);
rs_controller_t *ctrl = rs_get_controller(ctrl_obj);
if (ctrl && rs_ctrl_queue_len(js, ctrl_obj) > 0) {
ant_value_t chunk = rs_ctrl_queue_shift(js, ctrl_obj);
double chunk_size = 1;
if (ctrl->queue_sizes_len > 0) {
chunk_size = ctrl->queue_sizes[0];
ctrl->queue_sizes_len--;
memmove(ctrl->queue_sizes, ctrl->queue_sizes + 1, ctrl->queue_sizes_len * sizeof(double));
}
ctrl->queue_total_size -= chunk_size;
if (ctrl->queue_total_size < 0) ctrl->queue_total_size = 0;
bool should_close = ctrl->close_requested && rs_ctrl_queue_len(js, ctrl_obj) == 0;
ant_value_t p = js_mkpromise(js);
js_resolve_promise(js, p, js_iter_result(js, true, chunk));
if (should_close) {
rs_default_controller_clear_algorithms(ctrl_obj);
readable_stream_close(js, stream_obj);
} else rs_default_controller_call_pull_if_needed(js, ctrl_obj);
return p;
}
ant_value_t p = js_mkpromise(js);
rs_reader_reqs_push(js, reader_obj, p);
rs_default_controller_call_pull_if_needed(js, ctrl_obj);
return p;
}
static ant_value_t js_rs_controller_get_desired_size(ant_t *js, ant_value_t *args, int nargs) {
rs_controller_t *ctrl = rs_get_controller(js->this_val);
if (!ctrl) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid ReadableStreamDefaultController");
ant_value_t stream_obj = rs_ctrl_stream(js->this_val);
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!stream || stream->state == RS_STATE_ERRORED) return js_mknull();
return js_mknum(rs_default_controller_get_desired_size(ctrl, stream));
}
static ant_value_t js_rs_controller_close(ant_t *js, ant_value_t *args, int nargs) {
rs_controller_t *ctrl = rs_get_controller(js->this_val);
if (!ctrl) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid ReadableStreamDefaultController");
ant_value_t stream_obj = rs_ctrl_stream(js->this_val);
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!rs_default_controller_can_close_or_enqueue(ctrl, stream))
return js_mkerr_typed(js, JS_ERR_TYPE, "The stream is not in a state that permits close");
ctrl->close_requested = true;
if (rs_ctrl_queue_len(js, js->this_val) == 0 && !(ctrl->in_enqueue && ctrl->defer_close)) {
rs_default_controller_clear_algorithms(js->this_val);
readable_stream_close(js, stream_obj);
}
return js_mkundef();
}
static ant_value_t js_rs_controller_enqueue(ant_t *js, ant_value_t *args, int nargs) {
rs_controller_t *ctrl = rs_get_controller(js->this_val);
if (!ctrl) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid ReadableStreamDefaultController");
ant_value_t stream_obj = rs_ctrl_stream(js->this_val);
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!rs_default_controller_can_close_or_enqueue(ctrl, stream))
return js_mkerr_typed(js, JS_ERR_TYPE, "The stream is not in a state that permits enqueue");
ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
ant_value_t reader_obj = rs_stream_reader(stream_obj);
if (rs_is_reader(reader_obj) && rs_reader_has_reqs(js, reader_obj)) {
rs_fulfill_read_request(js, stream_obj, chunk, false);
rs_default_controller_call_pull_if_needed(js, js->this_val);
return js_mkundef();
}
double chunk_size = 1;
ant_value_t size_fn = rs_ctrl_size(js->this_val);
ctrl->in_enqueue = true;
if (is_callable(size_fn)) {
ant_value_t size_args[1] = { chunk };
ant_value_t size_result = sv_vm_call(js->vm, js, size_fn, js_mkundef(), size_args, 1, NULL, false);
if (is_err(size_result)) {
ant_value_t thrown = js->thrown_value;
ant_value_t err = is_object_type(thrown) ? thrown : size_result;
if (stream && stream->state == RS_STATE_ERRORED) err = rs_stream_error(stream_obj);
ctrl->in_enqueue = false;
readable_stream_error(js, stream_obj, err);
return js_throw(js, err);
}
if (vtype(size_result) == T_NUM) chunk_size = js_getnum(size_result);
else chunk_size = js_to_number(js, size_result);
}
ctrl->in_enqueue = false;
if (chunk_size < 0 || chunk_size != chunk_size || chunk_size == (double)INFINITY) {
js_mkerr_typed(js, JS_ERR_RANGE,
"The return value of a queuing strategy's size function must be a finite, non-NaN, non-negative number");
ant_value_t err = is_object_type(js->thrown_value) ? js->thrown_value : js_mkundef();
readable_stream_error(js, stream_obj, err);
return js_throw(js, err);
}
rs_ctrl_queue_push(js, js->this_val, chunk);
if (ctrl->queue_sizes_len >= ctrl->queue_sizes_cap) {
uint32_t new_cap = ctrl->queue_sizes_cap ? ctrl->queue_sizes_cap * 2 : 8;
double *ns = realloc(ctrl->queue_sizes, new_cap * sizeof(double));
if (ns) { ctrl->queue_sizes = ns; ctrl->queue_sizes_cap = new_cap; }
}
if (ctrl->queue_sizes_len < ctrl->queue_sizes_cap)
ctrl->queue_sizes[ctrl->queue_sizes_len++] = chunk_size;
ctrl->queue_total_size += chunk_size;
rs_default_controller_call_pull_if_needed(js, js->this_val);
return js_mkundef();
}
static ant_value_t js_rs_controller_error(ant_t *js, ant_value_t *args, int nargs) {
rs_controller_t *ctrl = rs_get_controller(js->this_val);
if (!ctrl) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid ReadableStreamDefaultController");
ant_value_t stream_obj = rs_ctrl_stream(js->this_val);
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!stream || stream->state != RS_STATE_READABLE) return js_mkundef();
ant_value_t e = (nargs > 0) ? args[0] : js_mkundef();
ctrl->queue_total_size = 0;
ctrl->queue_sizes_len = 0;
rs_default_controller_clear_algorithms(js->this_val);
readable_stream_error(js, stream_obj, e);
return js_mkundef();
}
ant_value_t rs_controller_enqueue(ant_t *js, ant_value_t ctrl_obj, ant_value_t chunk) {
rs_controller_t *ctrl = rs_get_controller(ctrl_obj);
if (!ctrl) return js_mkundef();
ant_value_t stream_obj = rs_ctrl_stream(ctrl_obj);
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!rs_default_controller_can_close_or_enqueue(ctrl, stream))
return js_mkerr_typed(js, JS_ERR_TYPE, "The stream is not in a state that permits enqueue");
ant_value_t reader_obj = rs_stream_reader(stream_obj);
if (rs_is_reader(reader_obj) && rs_reader_has_reqs(js, reader_obj)) {
rs_fulfill_read_request(js, stream_obj, chunk, false);
rs_default_controller_call_pull_if_needed(js, ctrl_obj);
return js_mkundef();
}
double chunk_size = 1;
ant_value_t size_fn = rs_ctrl_size(ctrl_obj);
ctrl->in_enqueue = true;
if (is_callable(size_fn)) {
ant_value_t size_args[1] = { chunk };
ant_value_t size_result = sv_vm_call(js->vm, js, size_fn, js_mkundef(), size_args, 1, NULL, false);
if (is_err(size_result)) {
ant_value_t thrown = js->thrown_value;
ant_value_t err = is_object_type(thrown) ? thrown : size_result;
if (stream && stream->state == RS_STATE_ERRORED)
err = rs_stream_error(stream_obj);
ctrl->in_enqueue = false;
js->thrown_exists = false;
js->thrown_value = js_mkundef();
js->thrown_stack = js_mkundef();
readable_stream_error(js, stream_obj, err);
return js_throw(js, err);
}
if (vtype(size_result) == T_NUM) chunk_size = js_getnum(size_result);
else chunk_size = js_to_number(js, size_result);
}
ctrl->in_enqueue = false;
if (chunk_size < 0 || chunk_size != chunk_size || chunk_size == (double)INFINITY) {
ant_value_t err = js_make_error_silent(js, JS_ERR_RANGE,
"The return value of a queuing strategy's size function must be a finite, non-NaN, non-negative number");
readable_stream_error(js, stream_obj, err);
return js_throw(js, err);
}
rs_ctrl_queue_push(js, ctrl_obj, chunk);
if (ctrl->queue_sizes_len >= ctrl->queue_sizes_cap) {
uint32_t new_cap = ctrl->queue_sizes_cap ? ctrl->queue_sizes_cap * 2 : 4;
double *new_sizes = realloc(ctrl->queue_sizes, new_cap * sizeof(double));
if (new_sizes) { ctrl->queue_sizes = new_sizes; ctrl->queue_sizes_cap = new_cap; }
}
if (ctrl->queue_sizes_len < ctrl->queue_sizes_cap)
ctrl->queue_sizes[ctrl->queue_sizes_len++] = chunk_size;
ctrl->queue_total_size += chunk_size;
rs_default_controller_call_pull_if_needed(js, ctrl_obj);
return js_mkundef();
}
void rs_controller_close(ant_t *js, ant_value_t ctrl_obj) {
rs_controller_t *ctrl = rs_get_controller(ctrl_obj);
if (!ctrl) return;
ant_value_t stream_obj = rs_ctrl_stream(ctrl_obj);
rs_stream_t *stream = rs_get_stream(stream_obj);
if (!rs_default_controller_can_close_or_enqueue(ctrl, stream)) return;
ctrl->close_requested = true;
if (rs_ctrl_queue_len(js, ctrl_obj) == 0 && !(ctrl->in_enqueue && ctrl->defer_close)) {
rs_default_controller_clear_algorithms(ctrl_obj);
readable_stream_close(js, stream_obj);
}
}
static ant_value_t js_rs_reader_get_closed(ant_t *js, ant_value_t *args, int nargs) {
return rs_reader_closed(js->this_val);
}
static ant_value_t js_rs_reader_read(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t stream_obj = rs_reader_stream(js->this_val);
if (!rs_is_stream(stream_obj))
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot read from a released reader");
return rs_default_reader_read(js, js->this_val);
}
static ant_value_t js_rs_reader_release_lock(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t stream_obj = rs_reader_stream(js->this_val);
if (!rs_is_stream(stream_obj)) return js_mkundef();
rs_stream_t *stream = rs_get_stream(stream_obj);
if (rs_reader_has_reqs(js, js->this_val)) {
ant_value_t release_err = js_make_error_silent(js, JS_ERR_TYPE, "Reader was released");
rs_default_reader_error_read_requests(js, js->this_val, release_err);
}
ant_value_t new_closed = js_mkpromise(js);
ant_value_t release_err = js_make_error_silent(js, JS_ERR_TYPE, "Reader was released");
if (stream->state == RS_STATE_READABLE) {
ant_value_t old_closed = rs_reader_closed(js->this_val);
js_reject_promise(js, old_closed, release_err);
promise_mark_handled(old_closed);
}
js_reject_promise(js, new_closed, release_err);
promise_mark_handled(new_closed);
js_set_slot(js->this_val, SLOT_RS_CLOSED, new_closed);
js_set_slot(stream_obj, SLOT_CTOR, js_mkundef());
js_set_slot(js->this_val, SLOT_ENTRIES, js_mkundef());
return js_mkundef();
}
static ant_value_t js_rs_reader_cancel(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t stream_obj = rs_reader_stream(js->this_val);
if (!rs_is_stream(stream_obj)) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Cannot cancel a released reader");
js_reject_promise(js, p, js->thrown_value);
return p;
}
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
return readable_stream_cancel(js, stream_obj, reason);
}
ant_value_t js_rs_reader_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStreamDefaultReader constructor requires 'new'");
if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStreamDefaultReader requires a stream argument");
ant_value_t stream_obj = args[0];
if (!rs_is_stream(stream_obj))
return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStreamDefaultReader argument must be a ReadableStream");
rs_stream_t *stream = rs_get_stream(stream_obj);
if (rs_is_reader(rs_stream_reader(stream_obj)))
return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStream is already locked to a reader");
ant_value_t closed = js_mkpromise(js);
promise_mark_handled(closed);
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_reader_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM_READER));
js_set_slot(obj, SLOT_ENTRIES, stream_obj);
js_set_slot(obj, SLOT_RS_CLOSED, closed);
js_set_slot(obj, SLOT_BUFFER, js_mkarr(js));
js_set_slot(stream_obj, SLOT_CTOR, obj);
if (stream->state == RS_STATE_CLOSED)
js_resolve_promise(js, closed, js_mkundef());
else if (stream->state == RS_STATE_ERRORED)
js_reject_promise(js, closed, rs_stream_error(stream_obj));
return obj;
}
static ant_value_t js_rs_get_locked(ant_t *js, ant_value_t *args, int nargs) {
rs_stream_t *stream = rs_get_stream(js->this_val);
if (!stream) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid ReadableStream");
return js_bool(rs_is_reader(rs_stream_reader(js->this_val)));
}
static ant_value_t js_rs_cancel(ant_t *js, ant_value_t *args, int nargs) {
rs_stream_t *stream = rs_get_stream(js->this_val);
if (!stream) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid ReadableStream");
if (rs_is_reader(rs_stream_reader(js->this_val))) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Cannot cancel a locked ReadableStream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
return readable_stream_cancel(js, js->this_val, reason);
}
static ant_value_t js_rs_get_reader(ant_t *js, ant_value_t *args, int nargs) {
rs_stream_t *stream = rs_get_stream(js->this_val);
if (!stream) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid ReadableStream");
if (nargs > 0 && is_object_type(args[0])) {
ant_value_t mode = js_get(js, args[0], "mode");
if (!is_undefined(mode)) {
ant_value_t mode_str_v = mode;
if (vtype(mode) != T_STR) {
mode_str_v = js_tostring_val(js, mode);
if (is_err(mode_str_v)) return mode_str_v;
}
size_t mode_len;
const char *mode_str = js_getstr(js, mode_str_v, &mode_len);
if (mode_str && mode_len == 4 && memcmp(mode_str, "byob", 4) == 0)
return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStreamBYOBReader is not yet implemented");
return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid reader mode");
}
}
ant_value_t reader_args[1] = { js->this_val };
ant_value_t saved_new_target = js->new_target;
js->new_target = g_reader_proto;
ant_value_t reader = js_rs_reader_ctor(js, reader_args, 1);
js->new_target = saved_new_target;
return reader;
}
static ant_value_t js_rs_values(ant_t *js, ant_value_t *args, int nargs) {
return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStream async iteration is not yet implemented");
}
static ant_value_t rs_start_resolve_handler(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ctrl_obj = js_get_slot(js->current_func, SLOT_DATA);
rs_controller_t *ctrl = rs_get_controller(ctrl_obj);
if (!ctrl) return js_mkundef();
ctrl->started = true;
ctrl->pulling = false;
ctrl->pull_again = false;
rs_default_controller_call_pull_if_needed(js, ctrl_obj);
return js_mkundef();
}
static ant_value_t rs_start_reject_handler(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ctrl_obj = js_get_slot(js->current_func, SLOT_DATA);
rs_controller_t *ctrl = rs_get_controller(ctrl_obj);
if (!ctrl) return js_mkundef();
ant_value_t stream_obj = rs_ctrl_stream(ctrl_obj);
rs_stream_t *stream = rs_get_stream(stream_obj);
if (stream && stream->state == RS_STATE_READABLE)
readable_stream_error(js, stream_obj, nargs > 0 ? args[0] : js_mkundef());
return js_mkundef();
}
static ant_value_t setup_default_controller(
ant_t *js, ant_value_t stream_obj,
ant_value_t pull_fn, ant_value_t cancel_fn, ant_value_t size_fn,
double hwm
) {
rs_controller_t *ctrl = calloc(1, sizeof(rs_controller_t));
if (!ctrl) return js_mkerr(js, "out of memory");
ctrl->strategy_hwm = hwm;
ant_value_t ctrl_obj = js_mkobj(js);
js_set_proto_init(ctrl_obj, g_controller_proto);
+ js_set_slot(ctrl_obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM_CONTROLLER));
js_set_slot(ctrl_obj, SLOT_DATA, ANT_PTR(ctrl));
js_set_slot(ctrl_obj, SLOT_ENTRIES, stream_obj);
js_set_slot(ctrl_obj, SLOT_RS_PULL, pull_fn);
js_set_slot(ctrl_obj, SLOT_RS_CANCEL, cancel_fn);
js_set_slot(ctrl_obj, SLOT_RS_SIZE, size_fn);
js_set_slot(ctrl_obj, SLOT_BUFFER, js_mkarr(js));
js_set_finalizer(ctrl_obj, rs_controller_finalize);
js_set_slot(stream_obj, SLOT_ENTRIES, ctrl_obj);
return ctrl_obj;
}
static ant_value_t js_rs_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStream constructor requires 'new'");
ant_value_t underlying_source = js_mkundef();
if (nargs > 0 && !is_undefined(args[0])) {
if (is_null(args[0]))
return js_mkerr_typed(js, JS_ERR_TYPE, "The underlying source cannot be null");
underlying_source = args[0];
}
if (is_object_type(underlying_source)) {
ant_value_t type_val = js_get(js, underlying_source, "type");
if (!is_undefined(type_val)) {
ant_value_t coerced = type_val;
if (vtype(type_val) != T_STR) {
coerced = js_tostring_val(js, type_val);
if (is_err(coerced)) return coerced;
}
size_t tlen;
const char *tstr = js_getstr(js, coerced, &tlen);
if (tstr && tlen == 5 && memcmp(tstr, "bytes", 5) == 0)
return js_mkerr_typed(js, JS_ERR_RANGE, "ReadableStream byte sources are not yet implemented");
return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid type is specified");
}}
ant_value_t strategy = js_mkundef();
if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
strategy = args[1];
double hwm = 1;
if (is_object_type(strategy)) {
ant_value_t hwm_val = js_get(js, strategy, "highWaterMark");
if (is_err(hwm_val)) return hwm_val;
if (!is_undefined(hwm_val)) {
hwm = js_to_number(js, hwm_val);
if (hwm != hwm || hwm < 0) return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid highWaterMark");
}
}
ant_value_t size_fn = js_mkundef();
if (is_object_type(strategy)) {
ant_value_t s = js_get(js, strategy, "size");
if (is_err(s)) return s;
if (!is_undefined(s)) {
if (!is_callable(s)) return js_mkerr_typed(js, JS_ERR_TYPE, "size must be a function");
size_fn = s;
}
}
rs_stream_t *st = calloc(1, sizeof(rs_stream_t));
if (!st) return js_mkerr(js, "out of memory");
st->state = RS_STATE_READABLE;
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_rs_proto);
+
if (is_object_type(proto)) js_set_proto_init(obj, proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM));
js_set_slot(obj, SLOT_DATA, ANT_PTR(st));
js_set_finalizer(obj, rs_stream_finalize);
ant_value_t pull_fn = js_mkundef();
ant_value_t cancel_fn = js_mkundef();
ant_value_t start_fn = js_mkundef();
if (is_object_type(underlying_source)) {
ant_value_t pv = js_get(js, underlying_source, "pull");
if (is_err(pv)) return pv;
if (!is_undefined(pv)) {
if (!is_callable(pv)) return js_mkerr_typed(js, JS_ERR_TYPE, "pull must be a function");
pull_fn = pv;
}
ant_value_t cv = js_get(js, underlying_source, "cancel");
if (is_err(cv)) return cv;
if (!is_undefined(cv)) {
if (!is_callable(cv)) return js_mkerr_typed(js, JS_ERR_TYPE, "cancel must be a function");
cancel_fn = cv;
}
ant_value_t sv = js_get(js, underlying_source, "start");
if (is_err(sv)) return sv;
if (!is_undefined(sv)) {
if (!is_callable(sv)) return js_mkerr_typed(js, JS_ERR_TYPE, "start must be a function");
start_fn = sv;
}
}
ant_value_t ctrl_obj = setup_default_controller(js, obj, pull_fn, cancel_fn, size_fn, hwm);
if (is_err(ctrl_obj)) return ctrl_obj;
if (is_callable(start_fn)) {
ant_value_t start_args[1] = { ctrl_obj };
ant_value_t start_result = sv_vm_call(js->vm, js, start_fn, underlying_source, start_args, 1, NULL, false);
if (is_err(start_result)) return start_result;
if (vtype(start_result) == T_PROMISE) {
ant_value_t resolve_fn = js_heavy_mkfun(js, rs_start_resolve_handler, ctrl_obj);
ant_value_t reject_fn = js_heavy_mkfun(js, rs_start_reject_handler, ctrl_obj);
ant_value_t then_fn = js_get(js, start_result, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { resolve_fn, reject_fn };
sv_vm_call(js->vm, js, then_fn, start_result, then_args, 2, NULL, false);
}
}
if (vtype(start_result) != T_PROMISE) {
ant_value_t resolved = js_mkpromise(js);
js_resolve_promise(js, resolved, js_mkundef());
ant_value_t res_fn = js_heavy_mkfun(js, rs_start_resolve_handler, ctrl_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, rs_start_reject_handler, ctrl_obj);
ant_value_t then_fn = js_get(js, resolved, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { res_fn, rej_fn };
sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false);
}
}
} else {
ant_value_t resolved = js_mkpromise(js);
js_resolve_promise(js, resolved, js_mkundef());
ant_value_t res_fn = js_heavy_mkfun(js, rs_start_resolve_handler, ctrl_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, rs_start_reject_handler, ctrl_obj);
ant_value_t then_fn = js_get(js, resolved, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { res_fn, rej_fn };
sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false);
}
}
return obj;
}
ant_value_t rs_create_stream(ant_t *js, ant_value_t pull_fn, ant_value_t cancel_fn, double hwm) {
rs_stream_t *st = calloc(1, sizeof(rs_stream_t));
if (!st) return js_mkerr(js, "out of memory");
st->state = RS_STATE_READABLE;
ant_value_t obj = js_mkobj(js);
js_set_proto_init(obj, g_rs_proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM));
js_set_slot(obj, SLOT_DATA, ANT_PTR(st));
js_set_finalizer(obj, rs_stream_finalize);
ant_value_t ctrl_obj = setup_default_controller(js, obj, pull_fn, cancel_fn, js_mkundef(), hwm);
if (is_err(ctrl_obj)) return ctrl_obj;
ant_value_t resolved = js_mkpromise(js);
js_resolve_promise(js, resolved, js_mkundef());
ant_value_t res_fn = js_heavy_mkfun(js, rs_start_resolve_handler, ctrl_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, rs_start_reject_handler, ctrl_obj);
ant_value_t then_fn = js_get(js, resolved, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { res_fn, rej_fn };
sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false);
}
return obj;
}
static ant_value_t js_rs_controller_ctor(ant_t *js, ant_value_t *args, int nargs) {
return js_mkerr_typed(js, JS_ERR_TYPE, "ReadableStreamDefaultController cannot be constructed directly");
}
void gc_mark_readable_streams(ant_t *js, void (*mark)(ant_t *, ant_value_t)) {
mark(js, g_rs_proto);
mark(js, g_reader_proto);
mark(js, g_controller_proto);
}
void init_readable_stream_module(void) {
ant_t *js = rt->js;
ant_value_t g = js_glob(js);
g_controller_proto = js_mkobj(js);
js_set_getter_desc(js, g_controller_proto, "desiredSize", 11, js_mkfun(js_rs_controller_get_desired_size), JS_DESC_C);
js_set(js, g_controller_proto, "close", js_mkfun(js_rs_controller_close));
js_set_descriptor(js, g_controller_proto, "close", 5, JS_DESC_W | JS_DESC_C);
js_set(js, g_controller_proto, "enqueue", js_mkfun(js_rs_controller_enqueue));
js_set_descriptor(js, g_controller_proto, "enqueue", 7, JS_DESC_W | JS_DESC_C);
js_set(js, g_controller_proto, "error", js_mkfun(js_rs_controller_error));
js_set_descriptor(js, g_controller_proto, "error", 5, JS_DESC_W | JS_DESC_C);
js_set_sym(js, g_controller_proto, get_toStringTag_sym(), js_mkstr(js, "ReadableStreamDefaultController", 31));
ant_value_t ctrl_ctor = js_make_ctor(js, js_rs_controller_ctor, g_controller_proto, "ReadableStreamDefaultController", 31);
js_set(js, g, "ReadableStreamDefaultController", ctrl_ctor);
js_set_descriptor(js, g, "ReadableStreamDefaultController", 31, JS_DESC_W | JS_DESC_C);
g_reader_proto = js_mkobj(js);
js_set_getter_desc(js, g_reader_proto, "closed", 6, js_mkfun(js_rs_reader_get_closed), JS_DESC_C);
js_set(js, g_reader_proto, "read", js_mkfun(js_rs_reader_read));
js_set_descriptor(js, g_reader_proto, "read", 4, JS_DESC_W | JS_DESC_C);
js_set(js, g_reader_proto, "releaseLock", js_mkfun(js_rs_reader_release_lock));
js_set_descriptor(js, g_reader_proto, "releaseLock", 11, JS_DESC_W | JS_DESC_C);
js_set(js, g_reader_proto, "cancel", js_mkfun(js_rs_reader_cancel));
js_set_descriptor(js, g_reader_proto, "cancel", 6, JS_DESC_W | JS_DESC_C);
js_set_sym(js, g_reader_proto, get_toStringTag_sym(), js_mkstr(js, "ReadableStreamDefaultReader", 27));
ant_value_t reader_ctor = js_make_ctor(js, js_rs_reader_ctor, g_reader_proto, "ReadableStreamDefaultReader", 27);
js_set(js, g, "ReadableStreamDefaultReader", reader_ctor);
js_set_descriptor(js, g, "ReadableStreamDefaultReader", 27, JS_DESC_W | JS_DESC_C);
g_rs_proto = js_mkobj(js);
js_set_getter_desc(js, g_rs_proto, "locked", 6, js_mkfun(js_rs_get_locked), JS_DESC_C);
js_set(js, g_rs_proto, "cancel", js_mkfun(js_rs_cancel));
js_set_descriptor(js, g_rs_proto, "cancel", 6, JS_DESC_W | JS_DESC_C);
js_set(js, g_rs_proto, "getReader", js_mkfun(js_rs_get_reader));
js_set_descriptor(js, g_rs_proto, "getReader", 9, JS_DESC_W | JS_DESC_C);
init_pipes_proto(js, g_rs_proto);
js_set(js, g_rs_proto, "values", js_mkfun(js_rs_values));
js_set_descriptor(js, g_rs_proto, "values", 6, JS_DESC_W | JS_DESC_C);
js_set_sym(js, g_rs_proto, get_toStringTag_sym(), js_mkstr(js, "ReadableStream", 14));
ant_value_t rs_ctor = js_make_ctor(js, js_rs_ctor, g_rs_proto, "ReadableStream", 14);
js_set(js, g, "ReadableStream", rs_ctor);
js_set_descriptor(js, g, "ReadableStream", 14, JS_DESC_W | JS_DESC_C);
}
diff --git a/src/streams/transform.c b/src/streams/transform.c
index aa2b3c2..e6083dc 100644
--- a/src/streams/transform.c
+++ b/src/streams/transform.c
@@ -1,1084 +1,1095 @@
#include <stdlib.h>
#include <string.h>
#include "ant.h"
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "descriptors.h"
#include "silver/engine.h"
#include "modules/assert.h"
#include "modules/symbol.h"
#include "streams/transform.h"
#include "streams/readable.h"
#include "streams/writable.h"
ant_value_t g_ts_proto;
ant_value_t g_ts_ctrl_proto;
bool ts_is_controller(ant_value_t obj) {
if (!is_object_type(obj)) return false;
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ if (vtype(brand) != T_NUM || (int)js_getnum(brand) != BRAND_TRANSFORM_STREAM_CONTROLLER)
+ return false;
ant_value_t ts_obj = js_get_slot(obj, SLOT_DATA);
return is_object_type(ts_obj)
- && vtype(js_get_slot(ts_obj, SLOT_DATA)) == T_NUM
+ && ts_is_stream(ts_obj)
&& js_get_slot(ts_obj, SLOT_DEFAULT) == obj;
}
bool ts_is_stream(ant_value_t obj) {
return is_object_type(obj)
+ && vtype(js_get_slot(obj, SLOT_BRAND)) == T_NUM
+ && (int)js_getnum(js_get_slot(obj, SLOT_BRAND)) == BRAND_TRANSFORM_STREAM
&& vtype(js_get_slot(obj, SLOT_DATA)) == T_NUM
&& rs_is_stream(js_get_slot(obj, SLOT_ENTRIES))
&& ws_is_stream(js_get_slot(obj, SLOT_CTOR))
&& ts_is_controller(js_get_slot(obj, SLOT_DEFAULT));
}
ant_value_t ts_stream_readable(ant_value_t ts_obj) {
return js_get_slot(ts_obj, SLOT_ENTRIES);
}
ant_value_t ts_stream_writable(ant_value_t ts_obj) {
return js_get_slot(ts_obj, SLOT_CTOR);
}
static void ts_ws_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
free((ws_stream_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
return;
}}
}
static void ts_ws_ctrl_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
ws_controller_t *ctrl = (ws_controller_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
free(ctrl->queue_sizes);
free(ctrl);
return;
}}
}
static void ts_rs_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
free((rs_stream_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
return;
}}
}
static void ts_rs_ctrl_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
rs_controller_t *ctrl = (rs_controller_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
free(ctrl->queue_sizes);
free(ctrl);
return;
}}
}
static inline bool ts_get_backpressure(ant_value_t ts_obj) {
return js_getnum(js_get_slot(ts_obj, SLOT_DATA)) != 0;
}
static inline void ts_set_bp_flag(ant_value_t ts_obj, bool bp) {
js_set_slot(ts_obj, SLOT_DATA, js_mknum(bp ? 1 : 0));
}
static inline ant_value_t ts_readable(ant_value_t ts_obj) {
return ts_stream_readable(ts_obj);
}
static inline ant_value_t ts_writable(ant_value_t ts_obj) {
return ts_stream_writable(ts_obj);
}
static inline ant_value_t ts_bp_promise(ant_value_t ts_obj) {
return js_get_slot(ts_obj, SLOT_BUFFER);
}
ant_value_t ts_stream_controller(ant_value_t ts_obj) {
return js_get_slot(ts_obj, SLOT_DEFAULT);
}
static inline ant_value_t ts_controller(ant_value_t ts_obj) {
return ts_stream_controller(ts_obj);
}
static inline ant_value_t ts_ctrl_transform_fn(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_ENTRIES);
}
static inline ant_value_t ts_ctrl_flush_fn(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_CTOR);
}
static inline ant_value_t ts_ctrl_cancel_fn(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_BUFFER);
}
static inline ant_value_t ts_ctrl_transformer(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_SETTLED);
}
static inline ant_value_t ts_ctrl_stream(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_DATA);
}
static inline ant_value_t ts_ctrl_finish_promise(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_RS_PULL);
}
static inline ant_value_t ts_writable_stored_error(ant_value_t ts_obj) {
return js_get_slot(ts_writable(ts_obj), SLOT_BUFFER);
}
static inline ant_value_t ts_cancel_promise(ant_value_t ts_obj) {
return js_get_slot(ts_obj, SLOT_RS_CANCEL);
}
static inline bool ts_cancel_started_by_abort(ant_value_t ts_obj) {
return js_get_slot(ts_obj, SLOT_WS_ABORT) == js_true;
}
static inline bool ts_is_flushing(ant_value_t ts_obj) {
return js_get_slot(ts_obj, SLOT_WS_CLOSE) == js_true;
}
static inline ant_value_t ts_cancel_settle_error(ant_value_t ts_obj) {
return js_get_slot(ts_obj, SLOT_RS_SIZE);
}
static inline bool ts_cancel_joined_abort(ant_value_t ts_obj) {
return js_get_slot(ts_obj, SLOT_WS_WRITE) == js_true;
}
static inline bool ts_cancel_has_user_handler(ant_value_t ts_obj) {
return js_get_slot(ts_obj, SLOT_WS_SIGNAL) == js_true;
}
static inline void ts_set_flushing(ant_value_t ts_obj, bool flushing) {
js_set_slot(ts_obj, SLOT_WS_CLOSE, flushing ? js_true : js_false);
}
static inline void ts_set_cancel_state(ant_value_t ts_obj, ant_value_t promise, bool started_by_abort, bool has_user_handler) {
js_set_slot(ts_obj, SLOT_RS_CANCEL, promise);
js_set_slot(ts_obj, SLOT_WS_ABORT, started_by_abort ? js_true : js_false);
js_set_slot(ts_obj, SLOT_RS_SIZE, js_mkundef());
js_set_slot(ts_obj, SLOT_WS_WRITE, js_false);
js_set_slot(ts_obj, SLOT_WS_SIGNAL, has_user_handler ? js_true : js_false);
}
static inline void ts_clear_cancel_state(ant_value_t ts_obj, ant_value_t promise) {
if (ts_cancel_promise(ts_obj) == promise) {
js_set_slot(ts_obj, SLOT_RS_CANCEL, js_mkundef());
js_set_slot(ts_obj, SLOT_WS_ABORT, js_false);
js_set_slot(ts_obj, SLOT_WS_SIGNAL, js_false);
}}
static void ts_ctrl_clear_algorithms(ant_value_t ctrl_obj) {
js_set_slot(ctrl_obj, SLOT_ENTRIES, js_mkundef());
js_set_slot(ctrl_obj, SLOT_CTOR, js_mkundef());
js_set_slot(ctrl_obj, SLOT_BUFFER, js_mkundef());
}
static ant_value_t ts_take_thrown_or(ant_t *js, ant_value_t fallback) {
ant_value_t thrown = js->thrown_exists ? js->thrown_value : js_mkundef();
ant_value_t err = is_object_type(thrown) ? thrown : fallback;
js->thrown_exists = false;
js->thrown_value = js_mkundef();
js->thrown_stack = js_mkundef();
return err;
}
static void ts_chain_promise(ant_t *js, ant_value_t val, ant_value_t res_fn, ant_value_t rej_fn) {
ant_value_t promise = val;
if (vtype(promise) != T_PROMISE) {
promise = js_mkpromise(js);
js_resolve_promise(js, promise, val);
}
ant_value_t then_fn = js_get(js, promise, "then");
if (!is_callable(then_fn)) return;
ant_value_t then_args[2] = { res_fn, rej_fn };
ant_value_t then_result = sv_vm_call(js->vm, js, then_fn, promise, then_args, 2, NULL, false);
promise_mark_handled(then_result);
}
static void ts_error(ant_t *js, ant_value_t ts_obj, ant_value_t e) {
ant_value_t readable = ts_readable(ts_obj);
ant_value_t rs_ctrl = rs_stream_controller(js, readable);
rs_controller_t *rc = rs_get_controller(rs_ctrl);
if (rc) {
rc->queue_total_size = 0;
rc->queue_sizes_len = 0;
rs_default_controller_clear_algorithms(rs_ctrl);
}
readable_stream_error(js, readable, e);
if (ts_get_backpressure(ts_obj)) {
ant_value_t bp = ts_bp_promise(ts_obj);
if (vtype(bp) == T_PROMISE) js_resolve_promise(js, bp, js_mkundef());
ts_set_bp_flag(ts_obj, false);
}
}
static void ts_error_writable_and_unblock_write(ant_t *js, ant_value_t ts_obj, ant_value_t e) {
ant_value_t ctrl_obj = ts_controller(ts_obj);
ts_ctrl_clear_algorithms(ctrl_obj);
ant_value_t writable = ts_writable(ts_obj);
ws_stream_t *ws = ws_get_stream(writable);
if (ws && ws->state == WS_STATE_WRITABLE)
ws_default_controller_error(js, ws_stream_controller(writable), e);
if (ts_get_backpressure(ts_obj)) {
ant_value_t bp = ts_bp_promise(ts_obj);
if (vtype(bp) == T_PROMISE) js_resolve_promise(js, bp, js_mkundef());
ts_set_bp_flag(ts_obj, false);
}
}
static void ts_set_backpressure(ant_t *js, ant_value_t ts_obj, bool backpressure) {
if (ts_get_backpressure(ts_obj)) {
ant_value_t bp = ts_bp_promise(ts_obj);
if (vtype(bp) == T_PROMISE) js_resolve_promise(js, bp, js_mkundef());
}
ant_value_t new_bp = js_mkpromise(js);
js_set_slot(ts_obj, SLOT_BUFFER, new_bp);
ts_set_bp_flag(ts_obj, backpressure);
}
ant_value_t ts_ctrl_enqueue(ant_t *js, ant_value_t ctrl_obj, ant_value_t chunk) {
ant_value_t ts_obj = ts_ctrl_stream(ctrl_obj);
ant_value_t readable = ts_readable(ts_obj);
ant_value_t rs_ctrl = rs_stream_controller(js, readable);
rs_stream_t *rs = rs_get_stream(readable);
if (!rs || rs->state != RS_STATE_READABLE)
return js_mkerr_typed(js, JS_ERR_TYPE, "Readable side is not in a readable state");
ant_value_t enqueue_result = rs_controller_enqueue(js, rs_ctrl, chunk);
if (is_err(enqueue_result)) {
ant_value_t err = ts_take_thrown_or(js, enqueue_result);
ts_error_writable_and_unblock_write(js, ts_obj, err);
return js_throw(js, err);
}
rs_controller_t *rc = rs_get_controller(rs_ctrl);
bool bp = rc && ((rc->strategy_hwm - rc->queue_total_size) <= 0);
if (bp != ts_get_backpressure(ts_obj)) ts_set_backpressure(js, ts_obj, bp);
return js_mkundef();
}
void ts_ctrl_error(ant_t *js, ant_value_t ctrl_obj, ant_value_t e) {
ant_value_t ts_obj = ts_ctrl_stream(ctrl_obj);
if (vtype(ts_cancel_promise(ts_obj)) == T_PROMISE && ts_cancel_has_user_handler(ts_obj))
js_set_slot(ts_obj, SLOT_RS_SIZE, e);
ts_error(js, ts_obj, e);
ts_error_writable_and_unblock_write(js, ts_obj, e);
}
void ts_ctrl_terminate(ant_t *js, ant_value_t ctrl_obj) {
ant_value_t ts_obj = ts_ctrl_stream(ctrl_obj);
ant_value_t readable = ts_readable(ts_obj);
ant_value_t rs_ctrl = rs_stream_controller(js, readable);
rs_controller_close(js, rs_ctrl);
ant_value_t writable = ts_writable(ts_obj);
ws_stream_t *ws = ws_get_stream(writable);
if (ws && ws->state == WS_STATE_WRITABLE) {
ant_value_t err = js_make_error_silent(js, JS_ERR_TYPE, "TransformStream readable side terminated");
ts_error_writable_and_unblock_write(js, ts_obj, err);
}
}
static ant_value_t ts_transform_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t p = js_get_slot(js->current_func, SLOT_DATA);
js_resolve_promise(js, p, js_mkundef());
return js_mkundef();
}
static ant_value_t ts_transform_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t e = (nargs > 0) ? args[0] : js_mkundef();
ts_error(js, ts_obj, e);
js_reject_promise(js, p, e);
return js_mkundef();
}
static ant_value_t ts_ctrl_perform_transform(ant_t *js, ant_value_t ctrl_obj, ant_value_t chunk) {
ant_value_t transform_fn = ts_ctrl_transform_fn(ctrl_obj);
ant_value_t ts_obj = ts_ctrl_stream(ctrl_obj);
ant_value_t p = js_mkpromise(js);
promise_mark_handled(p);
if (is_callable(transform_fn)) {
ant_value_t call_args[2] = { chunk, ctrl_obj };
ant_value_t result = sv_vm_call(js->vm, js, transform_fn, ts_ctrl_transformer(ctrl_obj), call_args, 2, NULL, false);
if (is_err(result)) {
ant_value_t err = ts_take_thrown_or(js, result);
ts_error(js, ts_obj, err);
js_reject_promise(js, p, err);
return p;
}
ant_value_t res_fn = js_heavy_mkfun(js, ts_transform_resolve, p);
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, p);
js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, ts_transform_reject, wrapper);
ts_chain_promise(js, result, res_fn, rej_fn);
} else {
ant_value_t enqueue_result = ts_ctrl_enqueue(js, ctrl_obj, chunk);
if (is_err(enqueue_result)) {
ant_value_t err = ts_take_thrown_or(js, enqueue_result);
js_reject_promise(js, p, err);
return p;
}
js_resolve_promise(js, p, js_mkundef());
}
return p;
}
static ant_value_t ts_cancel_base_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ts_clear_cancel_state(ts_obj, p);
js_resolve_promise(js, p, js_mkundef());
return js_mkundef();
}
static ant_value_t ts_cancel_base_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t err = (nargs > 0) ? args[0] : js_mkundef();
ts_clear_cancel_state(ts_obj, p);
js_reject_promise(js, p, err);
return js_mkundef();
}
static ant_value_t ts_run_cancel_algorithm(ant_t *js, ant_value_t ts_obj, ant_value_t cancel_fn, ant_value_t reason, bool started_by_abort) {
ant_value_t existing = ts_cancel_promise(ts_obj);
if (vtype(existing) == T_PROMISE) return existing;
ant_value_t p = js_mkpromise(js);
promise_mark_handled(p);
ts_set_cancel_state(ts_obj, p, started_by_abort, is_callable(cancel_fn));
ts_ctrl_clear_algorithms(ts_controller(ts_obj));
ant_value_t result = js_mkundef();
if (is_callable(cancel_fn)) {
ant_value_t cancel_args[1] = { reason };
result = sv_vm_call(js->vm, js, cancel_fn, ts_ctrl_transformer(ts_controller(ts_obj)), cancel_args, 1, NULL, false);
}
if (is_err(result)) {
ant_value_t err = ts_take_thrown_or(js, result);
ts_clear_cancel_state(ts_obj, p);
js_reject_promise(js, p, err);
return p;
}
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, p);
js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
ant_value_t res_fn = js_heavy_mkfun(js, ts_cancel_base_resolve, wrapper);
ant_value_t rej_fn = js_heavy_mkfun(js, ts_cancel_base_reject, wrapper);
ts_chain_promise(js, result, res_fn, rej_fn);
return p;
}
static ant_value_t ts_source_cancel_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t reason = js_get_slot(wrapper, SLOT_BUFFER);
ant_value_t readable = ts_readable(ts_obj);
rs_stream_t *rs = rs_get_stream(readable);
ant_value_t settle_error = ts_cancel_settle_error(ts_obj);
if (is_object_type(settle_error)) {
js_reject_promise(js, p, settle_error);
return js_mkundef();
}
if (rs && rs->state == RS_STATE_ERRORED) {
js_reject_promise(js, p, rs_stream_error(readable));
return js_mkundef();
}
ant_value_t writable = ts_writable(ts_obj);
ws_stream_t *ws = ws_get_stream(writable);
if (ws && ws->state == WS_STATE_WRITABLE)
ts_error_writable_and_unblock_write(js, ts_obj, reason);
js_resolve_promise(js, p, js_mkundef());
return js_mkundef();
}
static ant_value_t ts_source_cancel_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t err = (nargs > 0) ? args[0] : js_mkundef();
ts_error_writable_and_unblock_write(js, ts_obj, err);
js_reject_promise(js, p, err);
return js_mkundef();
}
static ant_value_t ts_abort_cancel_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t reason = js_get_slot(wrapper, SLOT_BUFFER);
ant_value_t readable = ts_readable(ts_obj);
rs_stream_t *rs = rs_get_stream(readable);
if (rs && rs->state == RS_STATE_ERRORED) {
js_reject_promise(js, p, rs_stream_error(readable));
return js_mkundef();
}
if (ts_cancel_joined_abort(ts_obj)) js_set_slot(ts_obj, SLOT_RS_SIZE, reason);
ts_error(js, ts_obj, reason);
if (ts_cancel_joined_abort(ts_obj)) js_reject_promise(js, p, reason);
else js_resolve_promise(js, p, js_mkundef());
return js_mkundef();
}
static ant_value_t ts_abort_cancel_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t err = (nargs > 0) ? args[0] : js_mkundef();
ts_error(js, ts_obj, err);
js_reject_promise(js, p, err);
return js_mkundef();
}
static ant_value_t ts_close_cancel_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t readable = ts_readable(ts_obj);
rs_stream_t *rs = rs_get_stream(readable);
ant_value_t settle_error = ts_cancel_settle_error(ts_obj);
if (is_object_type(settle_error)) {
js_reject_promise(js, p, settle_error);
return js_mkundef();
}
if (rs && rs->state == RS_STATE_ERRORED) {
js_reject_promise(js, p, rs_stream_error(readable));
return js_mkundef();
}
js_resolve_promise(js, p, js_mkundef());
return js_mkundef();
}
static ant_value_t ts_close_cancel_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t err = (nargs > 0) ? args[0] : js_mkundef();
js_reject_promise(js, p, err);
return js_mkundef();
}
static ant_value_t ts_sink_write_bp_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t ctrl_obj = js_get_slot(wrapper, SLOT_DATA);
ant_value_t chunk = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_CTOR);
ws_stream_t *ws = ws_get_stream(ts_writable(ts_obj));
if (ws && ws->state == WS_STATE_ERRORING) {
ant_value_t err = ts_writable_stored_error(ts_obj);
if (!is_object_type(err))
err = js_make_error_silent(js, JS_ERR_TYPE, "WritableStream is in erroring state");
ant_value_t fp = ts_ctrl_finish_promise(ctrl_obj);
if (vtype(fp) == T_PROMISE) js_reject_promise(js, fp, err);
return js_mkundef();
}
ant_value_t transform_p = ts_ctrl_perform_transform(js, ctrl_obj, chunk);
ant_value_t fp = ts_ctrl_finish_promise(ctrl_obj);
if (vtype(fp) == T_PROMISE) {
ant_value_t resolve_fn = js_heavy_mkfun(js, ts_transform_resolve, fp);
ant_value_t rej_wrapper = js_mkobj(js);
js_set_slot(rej_wrapper, SLOT_DATA, fp);
js_set_slot(rej_wrapper, SLOT_ENTRIES, ts_obj);
ant_value_t reject_fn = js_heavy_mkfun(js, ts_transform_reject, rej_wrapper);
ts_chain_promise(js, transform_p, resolve_fn, reject_fn);
}
return js_mkundef();
}
static ant_value_t ts_sink_write(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
ant_value_t ctrl_obj = ts_controller(ts_obj);
ant_value_t finish_p = js_mkpromise(js);
promise_mark_handled(finish_p);
js_set_slot(ctrl_obj, SLOT_RS_PULL, finish_p);
if (ts_get_backpressure(ts_obj)) {
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, ctrl_obj);
js_set_slot(wrapper, SLOT_ENTRIES, chunk);
js_set_slot(wrapper, SLOT_CTOR, ts_obj);
ant_value_t res_fn = js_heavy_mkfun(js, ts_sink_write_bp_resolve, wrapper);
ant_value_t rej_wrapper = js_mkobj(js);
js_set_slot(rej_wrapper, SLOT_DATA, finish_p);
js_set_slot(rej_wrapper, SLOT_ENTRIES, ts_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, ts_transform_reject, rej_wrapper);
ant_value_t bp = ts_bp_promise(ts_obj);
ts_chain_promise(js, bp, res_fn, rej_fn);
} else {
ant_value_t transform_p = ts_ctrl_perform_transform(js, ctrl_obj, chunk);
ant_value_t resolve_fn = js_heavy_mkfun(js, ts_transform_resolve, finish_p);
ant_value_t rej_wrapper = js_mkobj(js);
js_set_slot(rej_wrapper, SLOT_DATA, finish_p);
js_set_slot(rej_wrapper, SLOT_ENTRIES, ts_obj);
ant_value_t reject_fn = js_heavy_mkfun(js, ts_transform_reject, rej_wrapper);
ts_chain_promise(js, transform_p, resolve_fn, reject_fn);
}
return finish_p;
}
static ant_value_t ts_sink_abort(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t ctrl_obj = ts_controller(ts_obj);
ant_value_t cancel_fn = ts_ctrl_cancel_fn(ctrl_obj);
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
bool joined_source_cancel = vtype(ts_cancel_promise(ts_obj)) == T_PROMISE && !ts_cancel_started_by_abort(ts_obj);
if (joined_source_cancel && ts_cancel_has_user_handler(ts_obj))
js_set_slot(ts_obj, SLOT_WS_WRITE, js_true);
ant_value_t p = js_mkpromise(js);
ant_value_t base = ts_run_cancel_algorithm(js, ts_obj, cancel_fn, reason, true);
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, p);
js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
js_set_slot(wrapper, SLOT_BUFFER, reason);
js_set_slot(wrapper, SLOT_CTOR, ts_cancel_started_by_abort(ts_obj) ? js_true : js_false);
ant_value_t res_fn = js_heavy_mkfun(js, ts_abort_cancel_resolve, wrapper);
ant_value_t rej_fn = js_heavy_mkfun(js, ts_abort_cancel_reject, wrapper);
ts_chain_promise(js, base, res_fn, rej_fn);
return p;
}
static ant_value_t ts_sink_close_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t readable = ts_readable(ts_obj);
rs_stream_t *rs = rs_get_stream(readable);
ant_value_t rs_ctrl = rs_stream_controller(js, readable);
ts_set_flushing(ts_obj, false);
if (rs && rs->state == RS_STATE_READABLE) {
rs_controller_close(js, rs_ctrl);
js_resolve_promise(js, p, js_mkundef());
} else if (rs && rs->state == RS_STATE_CLOSED) {
js_resolve_promise(js, p, js_mkundef());
} else if (rs && rs->state == RS_STATE_ERRORED) {
js_reject_promise(js, p, rs_stream_error(readable));
} else {
js_reject_promise(js, p, js_make_error_silent(js, JS_ERR_TYPE, "TransformStream readable side is not in a readable state"));
}
return js_mkundef();
}
static ant_value_t ts_sink_close_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t ts_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t e = (nargs > 0) ? args[0] : js_mkundef();
ts_set_flushing(ts_obj, false);
ts_error(js, ts_obj, e);
js_reject_promise(js, p, e);
return js_mkundef();
}
static ant_value_t ts_sink_close(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t ctrl_obj = ts_controller(ts_obj);
ant_value_t readable = ts_readable(ts_obj);
ant_value_t flush_fn = ts_ctrl_flush_fn(ctrl_obj);
ant_value_t p = js_mkpromise(js);
promise_mark_handled(p);
ant_value_t cancel_p = ts_cancel_promise(ts_obj);
if (vtype(cancel_p) == T_PROMISE) {
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, p);
js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
ant_value_t res_fn = js_heavy_mkfun(js, ts_close_cancel_resolve, wrapper);
ant_value_t rej_fn = js_heavy_mkfun(js, ts_close_cancel_reject, wrapper);
ts_chain_promise(js, cancel_p, res_fn, rej_fn);
return p;
}
ts_ctrl_clear_algorithms(ctrl_obj);
ts_set_flushing(ts_obj, true);
if (is_callable(flush_fn)) {
ant_value_t flush_args[1] = { ctrl_obj };
ant_value_t result = sv_vm_call(js->vm, js, flush_fn, ts_ctrl_transformer(ctrl_obj), flush_args, 1, NULL, false);
if (is_err(result)) {
ant_value_t err = ts_take_thrown_or(js, result);
ts_error(js, ts_obj, err);
ts_set_flushing(ts_obj, false);
js_reject_promise(js, p, err);
return p;
}
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, p);
js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
ant_value_t res_fn = js_heavy_mkfun(js, ts_sink_close_resolve, wrapper);
ant_value_t rej_fn = js_heavy_mkfun(js, ts_sink_close_reject, wrapper);
ts_chain_promise(js, result, res_fn, rej_fn);
} else {
rs_stream_t *rs = rs_get_stream(readable);
if (rs && rs->state == RS_STATE_READABLE) {
ant_value_t rs_ctrl = rs_stream_controller(js, readable);
rs_controller_t *rc = rs_get_controller(rs_ctrl);
if (rc) rc->defer_close = true;
rs_controller_close(js, rs_ctrl);
if (rc) rc->defer_close = false;
}
ts_set_flushing(ts_obj, false);
js_resolve_promise(js, p, js_mkundef());
}
return p;
}
static ant_value_t ts_source_pull_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t p = js_get_slot(js->current_func, SLOT_DATA);
js_resolve_promise(js, p, js_mkundef());
return js_mkundef();
}
static ant_value_t ts_source_pull(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = js_get_slot(js->current_func, SLOT_DATA);
if (ts_get_backpressure(ts_obj)) {
ts_set_backpressure(js, ts_obj, false);
ant_value_t p = js_mkpromise(js);
js_resolve_promise(js, p, js_mkundef());
return p;
}
ant_value_t p = js_mkpromise(js);
js_resolve_promise(js, p, js_mkundef());
return p;
}
static ant_value_t ts_source_cancel(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t ctrl_obj = ts_controller(ts_obj);
ant_value_t cancel_fn = ts_ctrl_cancel_fn(ctrl_obj);
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
if (ts_is_flushing(ts_obj)) {
ant_value_t p = js_mkpromise(js);
js_resolve_promise(js, p, js_mkundef());
return p;
}
ant_value_t p = js_mkpromise(js);
ant_value_t base = ts_run_cancel_algorithm(js, ts_obj, cancel_fn, reason, false);
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, p);
js_set_slot(wrapper, SLOT_ENTRIES, ts_obj);
js_set_slot(wrapper, SLOT_BUFFER, reason);
js_set_slot(wrapper, SLOT_CTOR, ts_cancel_started_by_abort(ts_obj) ? js_true : js_false);
ant_value_t res_fn = js_heavy_mkfun(js, ts_source_cancel_resolve, wrapper);
ant_value_t rej_fn = js_heavy_mkfun(js, ts_source_cancel_reject, wrapper);
ts_chain_promise(js, base, res_fn, rej_fn);
return p;
}
static ant_value_t js_ts_ctrl_get_desired_size(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = ts_ctrl_stream(js->this_val);
ant_value_t readable = ts_readable(ts_obj);
ant_value_t rs_ctrl = rs_stream_controller(js, readable);
rs_controller_t *rc = rs_get_controller(rs_ctrl);
rs_stream_t *rs = rs_get_stream(readable);
if (!rc || !rs) return js_mknull();
if (rs->state == RS_STATE_ERRORED) return js_mknull();
if (rs->state == RS_STATE_CLOSED) return js_mknum(0);
return js_mknum(rc->strategy_hwm - rc->queue_total_size);
}
static ant_value_t js_ts_ctrl_enqueue(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = ts_ctrl_stream(js->this_val);
ant_value_t readable = ts_readable(ts_obj);
rs_stream_t *rs = rs_get_stream(readable);
if (!rs || rs->state != RS_STATE_READABLE)
return js_mkerr_typed(js, JS_ERR_TYPE, "Readable side is not in a readable state");
ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
return ts_ctrl_enqueue(js, js->this_val, chunk);
}
static ant_value_t js_ts_ctrl_error(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t e = (nargs > 0) ? args[0] : js_mkundef();
ts_ctrl_error(js, js->this_val, e);
return js_mkundef();
}
static ant_value_t js_ts_ctrl_terminate(ant_t *js, ant_value_t *args, int nargs) {
ts_ctrl_terminate(js, js->this_val);
return js_mkundef();
}
static ant_value_t js_ts_get_readable(ant_t *js, ant_value_t *args, int nargs) {
if (!ts_is_stream(js->this_val)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStream");
return ts_readable(js->this_val);
}
static ant_value_t js_ts_get_writable(ant_t *js, ant_value_t *args, int nargs) {
if (!ts_is_stream(js->this_val)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TransformStream");
return ts_writable(js->this_val);
}
static ant_value_t ts_start_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t writable = ts_writable(ts_obj);
ant_value_t ws_ctrl = ws_stream_controller(writable);
ws_controller_t *wc = ws_get_controller(ws_ctrl);
if (wc) wc->started = true;
ws_default_controller_advance_queue_if_needed(js, ws_ctrl);
ant_value_t readable = ts_readable(ts_obj);
ant_value_t rs_ctrl = rs_stream_controller(js, readable);
rs_controller_t *rc = rs_get_controller(rs_ctrl);
if (rc) {
rc->started = true;
rc->pulling = false;
rc->pull_again = false;
rs_default_controller_call_pull_if_needed(js, rs_ctrl);
}
return js_mkundef();
}
static ant_value_t ts_start_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ts_obj = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t e = (nargs > 0) ? args[0] : js_mkundef();
ant_value_t writable = ts_writable(ts_obj);
ant_value_t ws_ctrl = ws_stream_controller(writable);
ws_controller_t *wc = ws_get_controller(ws_ctrl);
if (wc) wc->started = true;
ant_value_t readable = ts_readable(ts_obj);
ant_value_t rs_ctrl = rs_stream_controller(js, readable);
rs_controller_t *rc = rs_get_controller(rs_ctrl);
if (rc) rc->started = true;
readable_stream_error(js, readable, e);
ws_stream_t *ws = ws_get_stream(writable);
if (ws && ws->state == WS_STATE_WRITABLE)
ws_default_controller_error(js, ws_ctrl, e);
else if (ws && ws->state == WS_STATE_ERRORING)
writable_stream_finish_erroring(js, writable);
return js_mkundef();
}
ant_value_t js_ts_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "TransformStream constructor requires 'new'");
ant_value_t transformer = js_mkundef();
if (nargs > 0 && !is_undefined(args[0]))
transformer = args[0];
if (is_object_type(transformer)) {
ant_value_t rt_val = js_getprop_fallback(js, transformer, "readableType");
if (!is_undefined(rt_val))
return js_mkerr_typed(js, JS_ERR_RANGE, "readableType is not supported");
ant_value_t wt_val = js_getprop_fallback(js, transformer, "writableType");
if (!is_undefined(wt_val))
return js_mkerr_typed(js, JS_ERR_RANGE, "writableType is not supported");
}
ant_value_t writable_strategy = js_mkundef();
if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
writable_strategy = args[1];
ant_value_t readable_strategy = js_mkundef();
if (nargs > 2 && !is_undefined(args[2]) && !is_null(args[2]))
readable_strategy = args[2];
double writable_hwm = 1;
ant_value_t writable_size_fn = js_mkundef();
if (is_object_type(writable_strategy)) {
ant_value_t hwm_val = js_getprop_fallback(js, writable_strategy, "highWaterMark");
if (is_err(hwm_val)) return hwm_val;
if (!is_undefined(hwm_val)) {
writable_hwm = js_to_number(js, hwm_val);
if (writable_hwm != writable_hwm || writable_hwm < 0)
return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid highWaterMark");
}
ant_value_t s = js_getprop_fallback(js, writable_strategy, "size");
if (is_err(s)) return s;
if (!is_undefined(s)) {
if (!is_callable(s)) return js_mkerr_typed(js, JS_ERR_TYPE, "size must be a function");
writable_size_fn = s;
}
}
double readable_hwm = 0;
ant_value_t readable_size_fn = js_mkundef();
if (is_object_type(readable_strategy)) {
ant_value_t hwm_val = js_getprop_fallback(js, readable_strategy, "highWaterMark");
if (is_err(hwm_val)) return hwm_val;
if (!is_undefined(hwm_val)) {
readable_hwm = js_to_number(js, hwm_val);
if (readable_hwm != readable_hwm || readable_hwm < 0)
return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid highWaterMark");
}
ant_value_t s = js_getprop_fallback(js, readable_strategy, "size");
if (is_err(s)) return s;
if (!is_undefined(s)) {
if (!is_callable(s)) return js_mkerr_typed(js, JS_ERR_TYPE, "size must be a function");
readable_size_fn = s;
}
}
ant_value_t transform_fn = js_mkundef();
ant_value_t flush_fn = js_mkundef();
ant_value_t cancel_fn = js_mkundef();
ant_value_t start_fn = js_mkundef();
if (is_object_type(transformer)) {
ant_value_t tv = js_getprop_fallback(js, transformer, "transform");
if (is_err(tv)) return tv;
if (!is_undefined(tv)) {
if (!is_callable(tv)) return js_mkerr_typed(js, JS_ERR_TYPE, "transform must be a function");
transform_fn = tv;
}
ant_value_t fv = js_getprop_fallback(js, transformer, "flush");
if (is_err(fv)) return fv;
if (!is_undefined(fv)) {
if (!is_callable(fv)) return js_mkerr_typed(js, JS_ERR_TYPE, "flush must be a function");
flush_fn = fv;
}
ant_value_t cv = js_getprop_fallback(js, transformer, "cancel");
if (is_err(cv)) return cv;
if (!is_undefined(cv)) {
if (!is_callable(cv)) return js_mkerr_typed(js, JS_ERR_TYPE, "cancel must be a function");
cancel_fn = cv;
}
ant_value_t sv = js_getprop_fallback(js, transformer, "start");
if (is_err(sv)) return sv;
if (!is_undefined(sv)) {
if (!is_callable(sv)) return js_mkerr_typed(js, JS_ERR_TYPE, "start must be a function");
start_fn = sv;
}
}
ant_value_t ts_obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_ts_proto);
if (is_object_type(proto)) js_set_proto_init(ts_obj, proto);
+ js_set_slot(ts_obj, SLOT_BRAND, js_mknum(BRAND_TRANSFORM_STREAM));
js_set_slot(ts_obj, SLOT_DATA, js_mknum(0));
ant_value_t ctrl_obj = js_mkobj(js);
js_set_proto_init(ctrl_obj, g_ts_ctrl_proto);
+ js_set_slot(ctrl_obj, SLOT_BRAND, js_mknum(BRAND_TRANSFORM_STREAM_CONTROLLER));
js_set_slot(ctrl_obj, SLOT_DATA, ts_obj);
js_set_slot(ctrl_obj, SLOT_ENTRIES, transform_fn);
js_set_slot(ctrl_obj, SLOT_CTOR, flush_fn);
js_set_slot(ctrl_obj, SLOT_BUFFER, cancel_fn);
js_set_slot(ctrl_obj, SLOT_SETTLED, transformer);
js_set_slot(ctrl_obj, SLOT_RS_PULL, js_mkundef());
js_set_slot(ts_obj, SLOT_DEFAULT, ctrl_obj);
js_set_slot(ts_obj, SLOT_RS_CANCEL, js_mkundef());
js_set_slot(ts_obj, SLOT_WS_ABORT, js_false);
js_set_slot(ts_obj, SLOT_WS_CLOSE, js_false);
js_set_slot(ts_obj, SLOT_RS_SIZE, js_mkundef());
js_set_slot(ts_obj, SLOT_WS_WRITE, js_false);
js_set_slot(ts_obj, SLOT_WS_SIGNAL, js_false);
ant_value_t sink_write = js_heavy_mkfun(js, ts_sink_write, ts_obj);
ant_value_t sink_abort = js_heavy_mkfun(js, ts_sink_abort, ts_obj);
ant_value_t sink_close = js_heavy_mkfun(js, ts_sink_close, ts_obj);
ant_value_t source_pull = js_heavy_mkfun(js, ts_source_pull, ts_obj);
ant_value_t source_cancel = js_heavy_mkfun(js, ts_source_cancel, ts_obj);
rs_stream_t *rst = calloc(1, sizeof(rs_stream_t));
if (!rst) return js_mkerr(js, "out of memory");
rst->state = RS_STATE_READABLE;
ant_value_t rs_obj = js_mkobj(js);
js_set_proto_init(rs_obj, g_rs_proto);
+ js_set_slot(rs_obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM));
js_set_slot(rs_obj, SLOT_DATA, ANT_PTR(rst));
js_set_finalizer(rs_obj, ts_rs_finalize);
rs_controller_t *rcc = calloc(1, sizeof(rs_controller_t));
if (!rcc) { free(rst); return js_mkerr(js, "out of memory"); }
rcc->strategy_hwm = readable_hwm;
ant_value_t rs_ctrl_obj = js_mkobj(js);
js_set_proto_init(rs_ctrl_obj, g_controller_proto);
+ js_set_slot(rs_ctrl_obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM_CONTROLLER));
js_set_slot(rs_ctrl_obj, SLOT_DATA, ANT_PTR(rcc));
js_set_slot(rs_ctrl_obj, SLOT_ENTRIES, rs_obj);
js_set_slot(rs_ctrl_obj, SLOT_RS_PULL, source_pull);
js_set_slot(rs_ctrl_obj, SLOT_RS_CANCEL, source_cancel);
js_set_slot(rs_ctrl_obj, SLOT_RS_SIZE, readable_size_fn);
js_set_slot(rs_ctrl_obj, SLOT_BUFFER, js_mkarr(js));
js_set_finalizer(rs_ctrl_obj, ts_rs_ctrl_finalize);
js_set_slot(rs_obj, SLOT_ENTRIES, rs_ctrl_obj);
js_set_slot(ts_obj, SLOT_ENTRIES, rs_obj);
ws_stream_t *wst = calloc(1, sizeof(ws_stream_t));
if (!wst) return js_mkerr(js, "out of memory");
wst->state = WS_STATE_WRITABLE;
ant_value_t ws_obj = js_mkobj(js);
js_set_proto_init(ws_obj, g_ws_proto);
+ js_set_slot(ws_obj, SLOT_BRAND, js_mknum(BRAND_WRITABLE_STREAM));
js_set_slot(ws_obj, SLOT_DATA, ANT_PTR(wst));
js_set_slot(ws_obj, SLOT_SETTLED, js_mkarr(js));
js_set_finalizer(ws_obj, ts_ws_finalize);
js_set_slot(ts_obj, SLOT_CTOR, ws_obj);
ant_value_t bp_promise = js_mkpromise(js);
js_set_slot(ts_obj, SLOT_BUFFER, bp_promise);
ts_set_backpressure(js, ts_obj, true);
ws_controller_t *wc = calloc(1, sizeof(ws_controller_t));
if (!wc) { free(wst); return js_mkerr(js, "out of memory"); }
wc->strategy_hwm = writable_hwm;
ant_value_t ws_ctrl_obj = js_mkobj(js);
js_set_proto_init(ws_ctrl_obj, g_ws_controller_proto);
+ js_set_slot(ws_ctrl_obj, SLOT_BRAND, js_mknum(BRAND_WRITABLE_STREAM_CONTROLLER));
js_set_slot(ws_ctrl_obj, SLOT_DATA, ANT_PTR(wc));
js_set_slot(ws_ctrl_obj, SLOT_ENTRIES, ws_obj);
js_set_slot(ws_ctrl_obj, SLOT_WS_WRITE, sink_write);
js_set_slot(ws_ctrl_obj, SLOT_WS_CLOSE, sink_close);
js_set_slot(ws_ctrl_obj, SLOT_WS_ABORT, sink_abort);
js_set_slot(ws_ctrl_obj, SLOT_RS_SIZE, writable_size_fn);
js_set_slot(ws_ctrl_obj, SLOT_CTOR, js_mkundef());
js_set_slot(ws_ctrl_obj, SLOT_BUFFER, js_mkarr(js));
js_set_finalizer(ws_ctrl_obj, ts_ws_ctrl_finalize);
js_set_slot(ws_obj, SLOT_ENTRIES, ws_ctrl_obj);
if (is_callable(start_fn)) {
ant_value_t start_args[1] = { ctrl_obj };
ant_value_t start_result = sv_vm_call(js->vm, js, start_fn, transformer, start_args, 1, NULL, false);
if (is_err(start_result)) { return start_result; }
if (vtype(start_result) == T_PROMISE) {
ant_value_t resolve_fn = js_heavy_mkfun(js, ts_start_resolve, ts_obj);
ant_value_t reject_fn = js_heavy_mkfun(js, ts_start_reject, ts_obj);
ant_value_t then_fn = js_get(js, start_result, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { resolve_fn, reject_fn };
sv_vm_call(js->vm, js, then_fn, start_result, then_args, 2, NULL, false);
}
}
if (vtype(start_result) != T_PROMISE) {
ant_value_t resolved = js_mkpromise(js);
js_resolve_promise(js, resolved, js_mkundef());
ant_value_t res_fn = js_heavy_mkfun(js, ts_start_resolve, ts_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, ts_start_reject, ts_obj);
ant_value_t then_fn = js_get(js, resolved, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { res_fn, rej_fn };
sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false);
}
}
} else {
ant_value_t resolved = js_mkpromise(js);
js_resolve_promise(js, resolved, js_mkundef());
ant_value_t res_fn = js_heavy_mkfun(js, ts_start_resolve, ts_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, ts_start_reject, ts_obj);
ant_value_t then_fn = js_get(js, resolved, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { res_fn, rej_fn };
sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false);
}
}
return ts_obj;
}
static ant_value_t js_ts_ctrl_ctor(ant_t *js, ant_value_t *args, int nargs) {
return js_mkerr_typed(js, JS_ERR_TYPE, "TransformStreamDefaultController cannot be constructed directly");
}
void init_transform_stream_module(void) {
ant_t *js = rt->js;
ant_value_t g = js_glob(js);
g_ts_ctrl_proto = js_mkobj(js);
js_set_getter_desc(js, g_ts_ctrl_proto, "desiredSize", 11, js_mkfun(js_ts_ctrl_get_desired_size), JS_DESC_C);
js_set(js, g_ts_ctrl_proto, "enqueue", js_mkfun(js_ts_ctrl_enqueue));
js_set_descriptor(js, g_ts_ctrl_proto, "enqueue", 7, JS_DESC_W | JS_DESC_C);
js_set(js, g_ts_ctrl_proto, "error", js_mkfun(js_ts_ctrl_error));
js_set_descriptor(js, g_ts_ctrl_proto, "error", 5, JS_DESC_W | JS_DESC_C);
js_set(js, g_ts_ctrl_proto, "terminate", js_mkfun(js_ts_ctrl_terminate));
js_set_descriptor(js, g_ts_ctrl_proto, "terminate", 9, JS_DESC_W | JS_DESC_C);
js_set_sym(js, g_ts_ctrl_proto, get_toStringTag_sym(), js_mkstr(js, "TransformStreamDefaultController", 32));
ant_value_t ctrl_ctor = js_make_ctor(js, js_ts_ctrl_ctor, g_ts_ctrl_proto, "TransformStreamDefaultController", 32);
js_set(js, g, "TransformStreamDefaultController", ctrl_ctor);
js_set_descriptor(js, g, "TransformStreamDefaultController", 32, JS_DESC_W | JS_DESC_C);
g_ts_proto = js_mkobj(js);
js_set_getter_desc(js, g_ts_proto, "readable", 8, js_mkfun(js_ts_get_readable), JS_DESC_C);
js_set_getter_desc(js, g_ts_proto, "writable", 8, js_mkfun(js_ts_get_writable), JS_DESC_C);
js_set_sym(js, g_ts_proto, get_toStringTag_sym(), js_mkstr(js, "TransformStream", 15));
ant_value_t ts_ctor = js_make_ctor(js, js_ts_ctor, g_ts_proto, "TransformStream", 15);
js_set(js, g, "TransformStream", ts_ctor);
js_set_descriptor(js, g, "TransformStream", 15, JS_DESC_W | JS_DESC_C);
}
void gc_mark_transform_streams(ant_t *js, void (*mark)(ant_t *, ant_value_t)) {
mark(js, g_ts_proto);
mark(js, g_ts_ctrl_proto);
}
diff --git a/src/streams/writable.c b/src/streams/writable.c
index ee533f4..2d8cc91 100644
--- a/src/streams/writable.c
+++ b/src/streams/writable.c
@@ -1,1296 +1,1311 @@
#include <stdlib.h>
#include <string.h>
#include "ant.h"
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "descriptors.h"
#include "silver/engine.h"
#include "modules/symbol.h"
#include "modules/assert.h"
#include "modules/abort.h"
#include "streams/writable.h"
ant_value_t g_ws_proto;
ant_value_t g_ws_writer_proto;
ant_value_t g_ws_controller_proto;
static ant_value_t g_close_sentinel;
bool ws_is_stream(ant_value_t obj) {
- return is_object_type(obj) && ws_get_stream(obj) != NULL;
+ if (!is_object_type(obj)) return false;
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ return vtype(brand) == T_NUM
+ && (int)js_getnum(brand) == BRAND_WRITABLE_STREAM
+ && ws_get_stream(obj) != NULL;
}
bool ws_is_writer(ant_value_t obj) {
- return is_object_type(obj)
+ if (!is_object_type(obj)) return false;
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ return vtype(brand) == T_NUM
+ && (int)js_getnum(brand) == BRAND_WRITABLE_STREAM_WRITER
&& vtype(js_get_slot(obj, SLOT_RS_CLOSED)) == T_PROMISE
&& vtype(js_get_slot(obj, SLOT_WS_READY)) == T_PROMISE;
}
bool ws_is_controller(ant_value_t obj) {
- return is_object_type(obj)
+ if (!is_object_type(obj)) return false;
+ ant_value_t brand = js_get_slot(obj, SLOT_BRAND);
+ return vtype(brand) == T_NUM
+ && (int)js_getnum(brand) == BRAND_WRITABLE_STREAM_CONTROLLER
&& ws_get_controller(obj) != NULL
&& ws_is_stream(js_get_slot(obj, SLOT_ENTRIES));
}
ws_stream_t *ws_get_stream(ant_value_t obj) {
ant_value_t s = js_get_slot(obj, SLOT_DATA);
if (vtype(s) != T_NUM) return NULL;
return (ws_stream_t *)(uintptr_t)(size_t)js_getnum(s);
}
ws_controller_t *ws_get_controller(ant_value_t obj) {
ant_value_t s = js_get_slot(obj, SLOT_DATA);
if (vtype(s) != T_NUM) return NULL;
return (ws_controller_t *)(uintptr_t)(size_t)js_getnum(s);
}
static void ws_stream_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
free((ws_stream_t *)(uintptr_t)(size_t)js_getnum(entries[i].value));
return;
}}
}
static void ws_controller_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
ws_controller_t *ctrl = (ws_controller_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
free(ctrl->queue_sizes);
free(ctrl);
return;
}}
}
ant_value_t ws_stream_controller(ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_ENTRIES);
}
ant_value_t ws_stream_writer(ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_CTOR);
}
static inline ant_value_t ws_stream_stored_error(ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_BUFFER);
}
static inline ant_value_t ws_stream_write_requests(ant_t *js, ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_SETTLED);
}
static inline ant_value_t ws_stream_in_flight_write(ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_DEFAULT);
}
static inline ant_value_t ws_stream_close_request(ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_WS_CLOSE);
}
static inline ant_value_t ws_stream_in_flight_close(ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_WS_ABORT);
}
static inline ant_value_t ws_stream_pending_abort_promise(ant_value_t stream_obj) {
return js_get_slot(stream_obj, SLOT_WS_READY);
}
static inline ant_value_t ws_ctrl_stream(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_ENTRIES);
}
static inline ant_value_t ws_ctrl_write_fn(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_WS_WRITE);
}
static inline ant_value_t ws_ctrl_close_fn(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_WS_CLOSE);
}
static inline ant_value_t ws_ctrl_abort_fn(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_WS_ABORT);
}
static inline ant_value_t ws_ctrl_size_fn(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_RS_SIZE);
}
static inline ant_value_t ws_ctrl_sink(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_CTOR);
}
static inline ant_value_t ws_ctrl_queue(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_BUFFER);
}
static inline ant_value_t ws_ctrl_signal(ant_value_t ctrl_obj) {
return js_get_slot(ctrl_obj, SLOT_WS_SIGNAL);
}
static inline ant_value_t ws_writer_stream(ant_value_t writer_obj) {
return js_get_slot(writer_obj, SLOT_ENTRIES);
}
static inline ant_value_t ws_writer_closed(ant_value_t writer_obj) {
return js_get_slot(writer_obj, SLOT_RS_CLOSED);
}
ant_value_t ws_writer_ready(ant_value_t writer_obj) {
return js_get_slot(writer_obj, SLOT_WS_READY);
}
static void ws_ctrl_queue_push(ant_t *js, ant_value_t ctrl_obj, ant_value_t value) {
ant_value_t arr = ws_ctrl_queue(ctrl_obj);
if (vtype(arr) == T_ARR) js_arr_push(js, arr, value);
}
static ant_value_t ws_ctrl_queue_shift(ant_t *js, ant_value_t ctrl_obj) {
ant_value_t arr = ws_ctrl_queue(ctrl_obj);
if (vtype(arr) != T_ARR) return js_mkundef();
ant_object_t *aobj = js_obj_ptr(arr);
if (aobj->u.array.len == 0) return js_mkundef();
ant_value_t val = aobj->u.array.data[0];
uint32_t new_len = aobj->u.array.len - 1;
for (uint32_t i = 0; i < new_len; i++)
aobj->u.array.data[i] = aobj->u.array.data[i + 1];
aobj->u.array.len = new_len;
return val;
}
static ant_value_t ws_ctrl_queue_peek(ant_t *js, ant_value_t ctrl_obj) {
ant_value_t arr = ws_ctrl_queue(ctrl_obj);
if (vtype(arr) != T_ARR) return js_mkundef();
ant_object_t *aobj = js_obj_ptr(arr);
if (aobj->u.array.len == 0) return js_mkundef();
return aobj->u.array.data[0];
}
static bool ws_ctrl_queue_empty(ant_value_t ctrl_obj) {
ant_value_t arr = ws_ctrl_queue(ctrl_obj);
if (vtype(arr) != T_ARR) return true;
ant_object_t *aobj = js_obj_ptr(arr);
return aobj->u.array.len == 0;
}
static void ws_write_reqs_push(ant_t *js, ant_value_t stream_obj, ant_value_t promise) {
ant_value_t arr = ws_stream_write_requests(js, stream_obj);
if (vtype(arr) == T_ARR) js_arr_push(js, arr, promise);
}
static ant_value_t ws_write_reqs_shift(ant_t *js, ant_value_t stream_obj) {
ant_value_t arr = ws_stream_write_requests(js, stream_obj);
if (vtype(arr) != T_ARR) return js_mkundef();
ant_object_t *aobj = js_obj_ptr(arr);
if (aobj->u.array.len == 0) return js_mkundef();
ant_value_t val = aobj->u.array.data[0];
uint32_t new_len = aobj->u.array.len - 1;
for (uint32_t i = 0; i < new_len; i++)
aobj->u.array.data[i] = aobj->u.array.data[i + 1];
aobj->u.array.len = new_len;
return val;
}
static void ws_chain_promise(ant_t *js, ant_value_t val, ant_value_t res_fn, ant_value_t rej_fn) {
ant_value_t promise = val;
if (vtype(promise) != T_PROMISE) {
promise = js_mkpromise(js);
js_resolve_promise(js, promise, val);
}
ant_value_t then_fn = js_get(js, promise, "then");
if (!is_callable(then_fn)) return;
ant_value_t then_args[2] = { res_fn, rej_fn };
ant_value_t then_result = sv_vm_call(js->vm, js, then_fn, promise, then_args, 2, NULL, false);
promise_mark_handled(then_result);
}
static void ws_default_controller_clear_algorithms(ant_value_t ctrl_obj) {
js_set_slot(ctrl_obj, SLOT_WS_WRITE, js_mkundef());
js_set_slot(ctrl_obj, SLOT_WS_CLOSE, js_mkundef());
js_set_slot(ctrl_obj, SLOT_WS_ABORT, js_mkundef());
js_set_slot(ctrl_obj, SLOT_RS_SIZE, js_mkundef());
}
static double ws_default_controller_get_desired_size(ws_controller_t *ctrl) {
return ctrl->strategy_hwm - ctrl->queue_total_size;
}
static bool ws_default_controller_get_backpressure(ws_controller_t *ctrl) {
return ws_default_controller_get_desired_size(ctrl) <= 0;
}
bool writable_stream_close_queued_or_in_flight(ant_value_t stream_obj) {
ant_value_t cr = ws_stream_close_request(stream_obj);
ant_value_t icr = ws_stream_in_flight_close(stream_obj);
return !is_undefined(cr) || !is_undefined(icr);
}
static bool writable_stream_has_operation_in_flight(ant_value_t stream_obj) {
ant_value_t iw = ws_stream_in_flight_write(stream_obj);
ant_value_t ic = ws_stream_in_flight_close(stream_obj);
return !is_undefined(iw) || !is_undefined(ic);
}
static void ws_writer_replace_ready_promise_rejected(ant_t *js, ant_value_t writer_obj, ant_value_t error) {
ant_value_t ready = js_mkpromise(js);
js_reject_promise(js, ready, error);
promise_mark_handled(ready);
js_set_slot(writer_obj, SLOT_WS_READY, ready);
}
static void ws_writer_replace_closed_promise_rejected(ant_t *js, ant_value_t writer_obj, ant_value_t error) {
ant_value_t closed = js_mkpromise(js);
js_reject_promise(js, closed, error);
promise_mark_handled(closed);
js_set_slot(writer_obj, SLOT_RS_CLOSED, closed);
}
static void ws_writer_reject_ready_promise(ant_t *js, ant_value_t writer_obj, ant_value_t error) {
ant_value_t ready = ws_writer_ready(writer_obj);
if (!is_undefined(ready)) js_reject_promise(js, ready, error);
}
static void ws_writer_reject_closed_promise(ant_t *js, ant_value_t writer_obj, ant_value_t error) {
ant_value_t closed = ws_writer_closed(writer_obj);
if (!is_undefined(closed)) js_reject_promise(js, closed, error);
}
static void writable_stream_start_erroring(ant_t *js, ant_value_t stream_obj, ant_value_t reason) {
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream || stream->state != WS_STATE_WRITABLE) return;
ant_value_t ctrl_obj = ws_stream_controller(stream_obj);
ws_controller_t *ctrl = ws_get_controller(ctrl_obj);
stream->state = WS_STATE_ERRORING;
js_set_slot(stream_obj, SLOT_BUFFER, reason);
ant_value_t signal_ac = ws_ctrl_signal(ctrl_obj);
if (is_object_type(signal_ac)) {
ant_value_t signal = js_get(js, signal_ac, "signal");
if (abort_signal_is_signal(signal))
signal_do_abort(js, signal, reason);
}
ant_value_t writer_obj = ws_stream_writer(stream_obj);
if (ws_is_writer(writer_obj))
ws_writer_reject_ready_promise(js, writer_obj, reason);
if (!writable_stream_has_operation_in_flight(stream_obj) && ctrl && ctrl->started)
writable_stream_finish_erroring(js, stream_obj);
}
static void ws_reject_close_and_closed(ant_t *js, ant_value_t stream_obj) {
ant_value_t stored_error = ws_stream_stored_error(stream_obj);
ant_value_t cr = ws_stream_close_request(stream_obj);
if (!is_undefined(cr)) {
js_reject_promise(js, cr, stored_error);
js_set_slot(stream_obj, SLOT_WS_CLOSE, js_mkundef());
}
ant_value_t writer_obj = ws_stream_writer(stream_obj);
if (ws_is_writer(writer_obj))
ws_writer_reject_closed_promise(js, writer_obj, stored_error);
}
static ant_value_t ws_finish_erroring_abort_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t stream_obj = js_get_slot(wrapper, SLOT_ENTRIES);
js_resolve_promise(js, p, js_mkundef());
ws_reject_close_and_closed(js, stream_obj);
return js_mkundef();
}
static ant_value_t ws_finish_erroring_abort_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t stream_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
js_reject_promise(js, p, reason);
ws_reject_close_and_closed(js, stream_obj);
return js_mkundef();
}
void writable_stream_finish_erroring(ant_t *js, ant_value_t stream_obj) {
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream || stream->state != WS_STATE_ERRORING) return;
stream->state = WS_STATE_ERRORED;
ant_value_t ctrl_obj = ws_stream_controller(stream_obj);
ws_controller_t *ctrl = ws_get_controller(ctrl_obj);
ant_value_t saved_abort_fn = ws_ctrl_abort_fn(ctrl_obj);
ant_value_t saved_sink = ws_ctrl_sink(ctrl_obj);
if (ctrl) {
ctrl->queue_total_size = 0;
ctrl->queue_sizes_len = 0;
}
ws_default_controller_clear_algorithms(ctrl_obj);
ant_value_t stored_error = ws_stream_stored_error(stream_obj);
ant_value_t wr_arr = ws_stream_write_requests(js, stream_obj);
if (vtype(wr_arr) == T_ARR) {
ant_offset_t len = js_arr_len(js, wr_arr);
for (ant_offset_t i = 0; i < len; i++)
js_reject_promise(js, js_arr_get(js, wr_arr, i), stored_error);
ant_object_t *aobj = js_obj_ptr(wr_arr);
aobj->u.array.len = 0;
}
if (stream->has_pending_abort) {
stream->has_pending_abort = false;
ant_value_t abort_promise = ws_stream_pending_abort_promise(stream_obj);
js_set_slot(stream_obj, SLOT_WS_READY, js_mkundef());
if (stream->pending_abort_was_already_erroring) {
js_reject_promise(js, abort_promise, stored_error);
ws_reject_close_and_closed(js, stream_obj);
return;
}
ant_value_t result = js_mkundef();
if (is_callable(saved_abort_fn)) {
ant_value_t abort_args[1] = { stored_error };
result = sv_vm_call(js->vm, js, saved_abort_fn, saved_sink, abort_args, 1, NULL, false);
}
if (is_err(result)) {
ant_value_t thrown = js->thrown_value;
js_reject_promise(js, abort_promise, is_object_type(thrown) ? thrown : result);
ws_reject_close_and_closed(js, stream_obj);
} else {
ant_value_t wrapper = js_mkobj(js);
js_set_slot(wrapper, SLOT_DATA, abort_promise);
js_set_slot(wrapper, SLOT_ENTRIES, stream_obj);
ant_value_t res_fn = js_heavy_mkfun(js, ws_finish_erroring_abort_resolve, wrapper);
ant_value_t rej_fn = js_heavy_mkfun(js, ws_finish_erroring_abort_reject, wrapper);
ws_chain_promise(js, result, res_fn, rej_fn);
}
return;
}
ws_reject_close_and_closed(js, stream_obj);
}
static void writable_stream_deal_with_rejection(ant_t *js, ant_value_t stream_obj, ant_value_t error) {
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream) return;
if (stream->state == WS_STATE_WRITABLE) {
writable_stream_start_erroring(js, stream_obj, error);
return;
}
writable_stream_finish_erroring(js, stream_obj);
}
static void writable_stream_update_backpressure(ant_t *js, ant_value_t stream_obj, bool backpressure) {
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream) return;
ant_value_t writer_obj = ws_stream_writer(stream_obj);
if (ws_is_writer(writer_obj) && stream->backpressure != backpressure) {
if (backpressure) {
ant_value_t ready = js_mkpromise(js);
promise_mark_handled(ready);
js_set_slot(writer_obj, SLOT_WS_READY, ready);
} else {
ant_value_t ready = ws_writer_ready(writer_obj);
if (!is_undefined(ready)) js_resolve_promise(js, ready, js_mkundef());
}}
stream->backpressure = backpressure;
}
static void writable_stream_finish_in_flight_write(ant_t *js, ant_value_t stream_obj) {
ant_value_t p = ws_stream_in_flight_write(stream_obj);
if (!is_undefined(p)) js_resolve_promise(js, p, js_mkundef());
js_set_slot(stream_obj, SLOT_DEFAULT, js_mkundef());
}
static void writable_stream_finish_in_flight_write_with_error(ant_t *js, ant_value_t stream_obj, ant_value_t error) {
ant_value_t p = ws_stream_in_flight_write(stream_obj);
if (!is_undefined(p)) js_reject_promise(js, p, error);
js_set_slot(stream_obj, SLOT_DEFAULT, js_mkundef());
writable_stream_deal_with_rejection(js, stream_obj, error);
}
static void writable_stream_finish_in_flight_close(ant_t *js, ant_value_t stream_obj) {
ant_value_t p = ws_stream_in_flight_close(stream_obj);
if (!is_undefined(p)) js_resolve_promise(js, p, js_mkundef());
js_set_slot(stream_obj, SLOT_WS_ABORT, js_mkundef());
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream) return;
if (stream->state == WS_STATE_ERRORING) {
js_set_slot(stream_obj, SLOT_BUFFER, js_mkundef());
if (stream->has_pending_abort) {
ant_value_t ap = ws_stream_pending_abort_promise(stream_obj);
js_resolve_promise(js, ap, js_mkundef());
stream->has_pending_abort = false;
js_set_slot(stream_obj, SLOT_WS_READY, js_mkundef());
}
}
stream->state = WS_STATE_CLOSED;
ant_value_t writer_obj = ws_stream_writer(stream_obj);
if (ws_is_writer(writer_obj)) {
ant_value_t closed = ws_writer_closed(writer_obj);
if (!is_undefined(closed)) js_resolve_promise(js, closed, js_mkundef());
}
}
static void writable_stream_finish_in_flight_close_with_error(ant_t *js, ant_value_t stream_obj, ant_value_t error) {
ant_value_t p = ws_stream_in_flight_close(stream_obj);
if (!is_undefined(p)) js_reject_promise(js, p, error);
js_set_slot(stream_obj, SLOT_WS_ABORT, js_mkundef());
writable_stream_deal_with_rejection(js, stream_obj, error);
}
static void writable_stream_mark_first_write_in_flight(ant_t *js, ant_value_t stream_obj) {
ant_value_t wr = ws_write_reqs_shift(js, stream_obj);
js_set_slot(stream_obj, SLOT_DEFAULT, wr);
}
static void writable_stream_mark_close_in_flight(ant_value_t stream_obj) {
ant_value_t cr = ws_stream_close_request(stream_obj);
js_set_slot(stream_obj, SLOT_WS_ABORT, cr);
js_set_slot(stream_obj, SLOT_WS_CLOSE, js_mkundef());
}
static ant_value_t ws_process_write_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ctrl_obj = js_get_slot(js->current_func, SLOT_DATA);
ws_controller_t *ctrl = ws_get_controller(ctrl_obj);
if (!ctrl) return js_mkundef();
ant_value_t stream_obj = ws_ctrl_stream(ctrl_obj);
writable_stream_finish_in_flight_write(js, stream_obj);
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream) return js_mkundef();
ws_ctrl_queue_shift(js, ctrl_obj);
if (ctrl->queue_sizes_len > 0) {
double sz = ctrl->queue_sizes[0];
ctrl->queue_sizes_len--;
memmove(ctrl->queue_sizes, ctrl->queue_sizes + 1, ctrl->queue_sizes_len * sizeof(double));
ctrl->queue_total_size -= sz;
if (ctrl->queue_total_size < 0) ctrl->queue_total_size = 0;
}
if (!writable_stream_close_queued_or_in_flight(stream_obj) && stream->state == WS_STATE_WRITABLE) {
bool bp = ws_default_controller_get_backpressure(ctrl);
writable_stream_update_backpressure(js, stream_obj, bp);
}
ws_default_controller_advance_queue_if_needed(js, ctrl_obj);
return js_mkundef();
}
static ant_value_t ws_process_write_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ctrl_obj = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t stream_obj = ws_ctrl_stream(ctrl_obj);
ws_stream_t *stream = ws_get_stream(stream_obj);
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
if (stream && stream->state == WS_STATE_WRITABLE)
ws_default_controller_clear_algorithms(ctrl_obj);
writable_stream_finish_in_flight_write_with_error(js, stream_obj, reason);
return js_mkundef();
}
static void ws_default_controller_process_write(ant_t *js, ant_value_t ctrl_obj, ant_value_t chunk) {
ant_value_t stream_obj = ws_ctrl_stream(ctrl_obj);
writable_stream_mark_first_write_in_flight(js, stream_obj);
ant_value_t write_fn = ws_ctrl_write_fn(ctrl_obj);
ant_value_t sink = ws_ctrl_sink(ctrl_obj);
ant_value_t result = js_mkundef();
if (is_callable(write_fn)) {
ant_value_t write_args[2] = { chunk, ctrl_obj };
result = sv_vm_call(js->vm, js, write_fn, sink, write_args, 2, NULL, false);
}
if (is_err(result)) {
ws_stream_t *stream = ws_get_stream(stream_obj);
ant_value_t thrown = js->thrown_value;
ant_value_t err = is_object_type(thrown) ? thrown : result;
if (stream && stream->state == WS_STATE_WRITABLE)
ws_default_controller_clear_algorithms(ctrl_obj);
writable_stream_finish_in_flight_write_with_error(js, stream_obj, err);
} else {
ant_value_t res_fn = js_heavy_mkfun(js, ws_process_write_resolve, ctrl_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, ws_process_write_reject, ctrl_obj);
ws_chain_promise(js, result, res_fn, rej_fn);
}
}
static ant_value_t ws_process_close_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t stream_obj = js_get_slot(js->current_func, SLOT_DATA);
writable_stream_finish_in_flight_close(js, stream_obj);
return js_mkundef();
}
static ant_value_t ws_process_close_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t stream_obj = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
writable_stream_finish_in_flight_close_with_error(js, stream_obj, reason);
return js_mkundef();
}
static void ws_default_controller_process_close(ant_t *js, ant_value_t ctrl_obj) {
ant_value_t stream_obj = ws_ctrl_stream(ctrl_obj);
writable_stream_mark_close_in_flight(stream_obj);
ws_ctrl_queue_shift(js, ctrl_obj);
ws_controller_t *ctrl = ws_get_controller(ctrl_obj);
if (ctrl && ctrl->queue_sizes_len > 0) {
ctrl->queue_sizes_len--;
memmove(ctrl->queue_sizes, ctrl->queue_sizes + 1, ctrl->queue_sizes_len * sizeof(double));
}
ant_value_t close_fn = ws_ctrl_close_fn(ctrl_obj);
ant_value_t sink = ws_ctrl_sink(ctrl_obj);
ws_default_controller_clear_algorithms(ctrl_obj);
ant_value_t result = js_mkundef();
if (is_callable(close_fn))
result = sv_vm_call(js->vm, js, close_fn, sink, NULL, 0, NULL, false);
if (is_err(result)) {
ant_value_t thrown = js->thrown_value;
ant_value_t err = is_object_type(thrown) ? thrown : result;
writable_stream_finish_in_flight_close_with_error(js, stream_obj, err);
} else {
ant_value_t res_fn = js_heavy_mkfun(js, ws_process_close_resolve, stream_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, ws_process_close_reject, stream_obj);
ws_chain_promise(js, result, res_fn, rej_fn);
}
}
void ws_default_controller_advance_queue_if_needed(ant_t *js, ant_value_t ctrl_obj) {
ws_controller_t *ctrl = ws_get_controller(ctrl_obj);
if (!ctrl || !ctrl->started) return;
ant_value_t stream_obj = ws_ctrl_stream(ctrl_obj);
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream) return;
if (!is_undefined(ws_stream_in_flight_write(stream_obj))) return;
if (stream->state == WS_STATE_ERRORING) {
writable_stream_finish_erroring(js, stream_obj);
return;
}
if (ws_ctrl_queue_empty(ctrl_obj)) return;
ant_value_t value = ws_ctrl_queue_peek(js, ctrl_obj);
if (value == g_close_sentinel) ws_default_controller_process_close(js, ctrl_obj);
else ws_default_controller_process_write(js, ctrl_obj, value);
}
static void ws_default_controller_close(ant_t *js, ant_value_t ctrl_obj) {
ws_controller_t *ctrl = ws_get_controller(ctrl_obj);
if (!ctrl) return;
ws_ctrl_queue_push(js, ctrl_obj, g_close_sentinel);
if (ctrl->queue_sizes_len >= ctrl->queue_sizes_cap) {
uint32_t new_cap = ctrl->queue_sizes_cap ? ctrl->queue_sizes_cap * 2 : 8;
double *ns = realloc(ctrl->queue_sizes, new_cap * sizeof(double));
if (ns) { ctrl->queue_sizes = ns; ctrl->queue_sizes_cap = new_cap; }
}
if (ctrl->queue_sizes_len < ctrl->queue_sizes_cap)
ctrl->queue_sizes[ctrl->queue_sizes_len++] = 0;
ws_default_controller_advance_queue_if_needed(js, ctrl_obj);
}
static void ws_default_controller_write(ant_t *js, ant_value_t ctrl_obj, ant_value_t chunk, double chunk_size) {
ws_controller_t *ctrl = ws_get_controller(ctrl_obj);
if (!ctrl) return;
ws_ctrl_queue_push(js, ctrl_obj, chunk);
if (ctrl->queue_sizes_len >= ctrl->queue_sizes_cap) {
uint32_t new_cap = ctrl->queue_sizes_cap ? ctrl->queue_sizes_cap * 2 : 8;
double *ns = realloc(ctrl->queue_sizes, new_cap * sizeof(double));
if (ns) { ctrl->queue_sizes = ns; ctrl->queue_sizes_cap = new_cap; }
}
if (ctrl->queue_sizes_len < ctrl->queue_sizes_cap)
ctrl->queue_sizes[ctrl->queue_sizes_len++] = chunk_size;
ctrl->queue_total_size += chunk_size;
ant_value_t stream_obj = ws_ctrl_stream(ctrl_obj);
if (!writable_stream_close_queued_or_in_flight(stream_obj)) {
ws_stream_t *stream = ws_get_stream(stream_obj);
if (stream && stream->state == WS_STATE_WRITABLE) {
bool bp = ws_default_controller_get_backpressure(ctrl);
writable_stream_update_backpressure(js, stream_obj, bp);
}
}
ws_default_controller_advance_queue_if_needed(js, ctrl_obj);
}
void ws_default_controller_error(ant_t *js, ant_value_t ctrl_obj, ant_value_t error) {
ant_value_t stream_obj = ws_ctrl_stream(ctrl_obj);
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream || stream->state != WS_STATE_WRITABLE) return;
ws_default_controller_clear_algorithms(ctrl_obj);
writable_stream_start_erroring(js, stream_obj, error);
}
ant_value_t writable_stream_close(ant_t *js, ant_value_t stream_obj) {
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream) return js_mkundef();
if (stream->state == WS_STATE_CLOSED || stream->state == WS_STATE_ERRORED) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Cannot close a stream that is already closed or errored");
js_reject_promise(js, p, js->thrown_value);
return p;
}
if (writable_stream_close_queued_or_in_flight(stream_obj)) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Cannot close an already-closing stream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
ant_value_t promise = js_mkpromise(js);
js_set_slot(stream_obj, SLOT_WS_CLOSE, promise);
ant_value_t writer_obj = ws_stream_writer(stream_obj);
if (ws_is_writer(writer_obj) && stream->backpressure && stream->state == WS_STATE_WRITABLE) {
ant_value_t ready = ws_writer_ready(writer_obj);
if (!is_undefined(ready)) js_resolve_promise(js, ready, js_mkundef());
}
ant_value_t ctrl_obj = ws_stream_controller(stream_obj);
ws_default_controller_close(js, ctrl_obj);
return promise;
}
static ant_value_t ws_abort_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t stream_obj = js_get_slot(wrapper, SLOT_ENTRIES);
js_resolve_promise(js, p, js_mkundef());
ws_stream_t *stream = ws_get_stream(stream_obj);
if (stream) stream->has_pending_abort = false;
js_set_slot(stream_obj, SLOT_WS_READY, js_mkundef());
ant_value_t stored_error = ws_stream_stored_error(stream_obj);
ant_value_t cr = ws_stream_close_request(stream_obj);
if (!is_undefined(cr)) {
js_reject_promise(js, cr, stored_error);
js_set_slot(stream_obj, SLOT_WS_CLOSE, js_mkundef());
}
ant_value_t writer_obj = ws_stream_writer(stream_obj);
if (ws_is_writer(writer_obj)) ws_writer_reject_closed_promise(js, writer_obj, stored_error);
return js_mkundef();
}
static ant_value_t ws_abort_reject(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t wrapper = js_get_slot(js->current_func, SLOT_DATA);
ant_value_t p = js_get_slot(wrapper, SLOT_DATA);
ant_value_t stream_obj = js_get_slot(wrapper, SLOT_ENTRIES);
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
js_reject_promise(js, p, reason);
ws_stream_t *stream = ws_get_stream(stream_obj);
if (stream) stream->has_pending_abort = false;
js_set_slot(stream_obj, SLOT_WS_READY, js_mkundef());
ant_value_t stored_error = ws_stream_stored_error(stream_obj);
ant_value_t cr = ws_stream_close_request(stream_obj);
if (!is_undefined(cr)) {
js_reject_promise(js, cr, stored_error);
js_set_slot(stream_obj, SLOT_WS_CLOSE, js_mkundef());
}
ant_value_t writer_obj = ws_stream_writer(stream_obj);
if (ws_is_writer(writer_obj))
ws_writer_reject_closed_promise(js, writer_obj, stored_error);
return js_mkundef();
}
ant_value_t writable_stream_abort(ant_t *js, ant_value_t stream_obj, ant_value_t reason) {
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream) return js_mkundef();
if (stream->state == WS_STATE_CLOSED || stream->state == WS_STATE_ERRORED) {
ant_value_t p = js_mkpromise(js);
js_resolve_promise(js, p, js_mkundef());
return p;
}
if (stream->has_pending_abort) {
return ws_stream_pending_abort_promise(stream_obj);
}
bool was_already_erroring = (stream->state == WS_STATE_ERRORING);
ant_value_t promise = js_mkpromise(js);
stream->has_pending_abort = true;
stream->pending_abort_was_already_erroring = was_already_erroring;
js_set_slot(stream_obj, SLOT_WS_READY, promise);
if (!was_already_erroring)
writable_stream_start_erroring(js, stream_obj, reason);
return promise;
}
ant_value_t ws_writer_write(ant_t *js, ant_value_t writer_obj, ant_value_t chunk) {
ant_value_t stream_obj = ws_writer_stream(writer_obj);
if (!ws_is_stream(stream_obj)) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Writer has no stream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WritableStream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
if (stream->state == WS_STATE_CLOSED) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Cannot write to a closed WritableStream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
if (stream->state == WS_STATE_ERRORED) {
ant_value_t p = js_mkpromise(js);
js_reject_promise(js, p, ws_stream_stored_error(stream_obj));
return p;
}
if (writable_stream_close_queued_or_in_flight(stream_obj)) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Cannot write to a closing WritableStream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
if (stream->state == WS_STATE_ERRORING) {
ant_value_t p = js_mkpromise(js);
js_reject_promise(js, p, ws_stream_stored_error(stream_obj));
return p;
}
ant_value_t ctrl_obj = ws_stream_controller(stream_obj);
double chunk_size = 1;
ant_value_t size_fn = ws_ctrl_size_fn(ctrl_obj);
if (is_callable(size_fn)) {
ant_value_t size_args[1] = { chunk };
ant_value_t size_result = sv_vm_call(js->vm, js, size_fn, js_mkundef(), size_args, 1, NULL, false);
if (is_err(size_result)) {
ant_value_t thrown = js->thrown_value;
ant_value_t err = is_object_type(thrown) ? thrown : size_result;
ws_default_controller_error(js, ctrl_obj, err);
ant_value_t p = js_mkpromise(js);
js_reject_promise(js, p, err);
return p;
}
if (vtype(size_result) == T_NUM) chunk_size = js_getnum(size_result);
else chunk_size = js_to_number(js, size_result);
}
if (chunk_size < 0 || chunk_size != chunk_size || chunk_size == (double)INFINITY) {
js_mkerr_typed(js, JS_ERR_RANGE,
"The return value of a queuing strategy's size function must be a finite, non-NaN, non-negative number");
ant_value_t err = is_object_type(js->thrown_value) ? js->thrown_value : js_mkundef();
ws_default_controller_error(js, ctrl_obj, err);
ant_value_t p = js_mkpromise(js);
js_reject_promise(js, p, err);
return p;
}
ant_value_t p = js_mkpromise(js);
ws_write_reqs_push(js, stream_obj, p);
ws_default_controller_write(js, ctrl_obj, chunk, chunk_size);
return p;
}
static ant_value_t ws_start_resolve_handler(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ctrl_obj = js_get_slot(js->current_func, SLOT_DATA);
ws_controller_t *ctrl = ws_get_controller(ctrl_obj);
if (!ctrl) return js_mkundef();
ctrl->started = true;
ws_default_controller_advance_queue_if_needed(js, ctrl_obj);
return js_mkundef();
}
static ant_value_t ws_start_reject_handler(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t ctrl_obj = js_get_slot(js->current_func, SLOT_DATA);
ws_controller_t *ctrl = ws_get_controller(ctrl_obj);
if (!ctrl) return js_mkundef();
ctrl->started = true;
ant_value_t stream_obj = ws_ctrl_stream(ctrl_obj);
writable_stream_deal_with_rejection(js, stream_obj, nargs > 0 ? args[0] : js_mkundef());
return js_mkundef();
}
static ant_value_t js_ws_controller_get_signal(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t signal_ctrl = ws_ctrl_signal(js->this_val);
if (!is_object_type(signal_ctrl)) return js_mkundef();
return js_get(js, signal_ctrl, "signal");
}
static ant_value_t js_ws_controller_error(ant_t *js, ant_value_t *args, int nargs) {
ws_controller_t *ctrl = ws_get_controller(js->this_val);
if (!ctrl) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WritableStreamDefaultController");
ant_value_t stream_obj = ws_ctrl_stream(js->this_val);
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream || stream->state != WS_STATE_WRITABLE) return js_mkundef();
ant_value_t e = (nargs > 0) ? args[0] : js_mkundef();
ws_default_controller_error(js, js->this_val, e);
return js_mkundef();
}
static ant_value_t js_ws_writer_get_closed(ant_t *js, ant_value_t *args, int nargs) {
return ws_writer_closed(js->this_val);
}
static ant_value_t js_ws_writer_get_ready(ant_t *js, ant_value_t *args, int nargs) {
return ws_writer_ready(js->this_val);
}
static ant_value_t js_ws_writer_get_desired_size(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t stream_obj = ws_writer_stream(js->this_val);
if (!ws_is_stream(stream_obj))
return js_mkerr_typed(js, JS_ERR_TYPE, "Writer has no stream");
ws_stream_t *stream = ws_get_stream(stream_obj);
if (!stream) return js_mknull();
if (stream->state == WS_STATE_ERRORED || stream->state == WS_STATE_ERRORING) return js_mknull();
if (stream->state == WS_STATE_CLOSED) return js_mknum(0);
ant_value_t ctrl_obj = ws_stream_controller(stream_obj);
ws_controller_t *ctrl = ws_get_controller(ctrl_obj);
if (!ctrl) return js_mknull();
return js_mknum(ws_default_controller_get_desired_size(ctrl));
}
static ant_value_t js_ws_writer_abort(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t stream_obj = ws_writer_stream(js->this_val);
if (!ws_is_stream(stream_obj)) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Writer has no stream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
return writable_stream_abort(js, stream_obj, reason);
}
static ant_value_t js_ws_writer_close(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t stream_obj = ws_writer_stream(js->this_val);
if (!ws_is_stream(stream_obj)) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Writer has no stream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
if (writable_stream_close_queued_or_in_flight(stream_obj)) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Cannot close an already-closing stream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
return writable_stream_close(js, stream_obj);
}
static ant_value_t js_ws_writer_release_lock(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t stream_obj = ws_writer_stream(js->this_val);
if (!ws_is_stream(stream_obj)) return js_mkundef();
ant_value_t release_err = js_make_error_silent(js, JS_ERR_TYPE, "Writer was released");
ws_writer_reject_ready_promise(js, js->this_val, release_err);
ws_writer_reject_closed_promise(js, js->this_val, release_err);
promise_mark_handled(ws_writer_ready(js->this_val));
promise_mark_handled(ws_writer_closed(js->this_val));
ws_writer_replace_ready_promise_rejected(js, js->this_val, release_err);
ws_writer_replace_closed_promise_rejected(js, js->this_val, release_err);
js_set_slot(stream_obj, SLOT_CTOR, js_mkundef());
js_set_slot(js->this_val, SLOT_ENTRIES, js_mkundef());
return js_mkundef();
}
static ant_value_t js_ws_writer_write(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t stream_obj = ws_writer_stream(js->this_val);
if (!ws_is_stream(stream_obj)) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Writer has no stream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
ant_value_t chunk = (nargs > 0) ? args[0] : js_mkundef();
return ws_writer_write(js, js->this_val, chunk);
}
ant_value_t js_ws_writer_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "WritableStreamDefaultWriter constructor requires 'new'");
if (nargs < 1)
return js_mkerr_typed(js, JS_ERR_TYPE, "WritableStreamDefaultWriter requires a stream argument");
ant_value_t stream_obj = args[0];
if (!ws_is_stream(stream_obj))
return js_mkerr_typed(js, JS_ERR_TYPE, "WritableStreamDefaultWriter argument must be a WritableStream");
ws_stream_t *stream = ws_get_stream(stream_obj);
if (ws_is_writer(ws_stream_writer(stream_obj)))
return js_mkerr_typed(js, JS_ERR_TYPE, "WritableStream is already locked to a writer");
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_ws_writer_proto);
+
if (is_object_type(proto)) js_set_proto_init(obj, proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_WRITABLE_STREAM_WRITER));
ant_value_t closed = js_mkpromise(js);
ant_value_t ready = js_mkpromise(js);
promise_mark_handled(closed);
promise_mark_handled(ready);
js_set_slot(obj, SLOT_ENTRIES, stream_obj);
js_set_slot(obj, SLOT_RS_CLOSED, closed);
js_set_slot(obj, SLOT_WS_READY, ready);
js_set_slot(stream_obj, SLOT_CTOR, obj);
if (stream->state == WS_STATE_WRITABLE) {
if (writable_stream_close_queued_or_in_flight(stream_obj) || !stream->backpressure) js_resolve_promise(js, ready, js_mkundef());
} else if (stream->state == WS_STATE_ERRORING) {
ant_value_t stored_error = ws_stream_stored_error(stream_obj);
js_reject_promise(js, ready, stored_error);
} else if (stream->state == WS_STATE_CLOSED) {
js_resolve_promise(js, ready, js_mkundef());
js_resolve_promise(js, closed, js_mkundef());
} else {
ant_value_t stored_error = ws_stream_stored_error(stream_obj);
js_reject_promise(js, ready, stored_error);
js_reject_promise(js, closed, stored_error);
}
return obj;
}
ant_value_t ws_acquire_writer(ant_t *js, ant_value_t stream_obj) {
ant_value_t writer_args[1] = { stream_obj };
ant_value_t saved = js->new_target;
js->new_target = g_ws_writer_proto;
ant_value_t writer = js_ws_writer_ctor(js, writer_args, 1);
js->new_target = saved;
return writer;
}
static ant_value_t js_ws_get_locked(ant_t *js, ant_value_t *args, int nargs) {
ws_stream_t *stream = ws_get_stream(js->this_val);
if (!stream) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WritableStream");
return js_bool(ws_is_writer(ws_stream_writer(js->this_val)));
}
static ant_value_t js_ws_abort(ant_t *js, ant_value_t *args, int nargs) {
ws_stream_t *stream = ws_get_stream(js->this_val);
if (!stream) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WritableStream");
if (ws_is_writer(ws_stream_writer(js->this_val))) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Cannot abort a locked WritableStream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
return writable_stream_abort(js, js->this_val, reason);
}
static ant_value_t js_ws_close(ant_t *js, ant_value_t *args, int nargs) {
ws_stream_t *stream = ws_get_stream(js->this_val);
if (!stream) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WritableStream");
if (ws_is_writer(ws_stream_writer(js->this_val))) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Cannot close a locked WritableStream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
if (writable_stream_close_queued_or_in_flight(js->this_val)) {
ant_value_t p = js_mkpromise(js);
js_mkerr_typed(js, JS_ERR_TYPE, "Cannot close an already-closing stream");
js_reject_promise(js, p, js->thrown_value);
return p;
}
return writable_stream_close(js, js->this_val);
}
static ant_value_t js_ws_get_writer(ant_t *js, ant_value_t *args, int nargs) {
ws_stream_t *stream = ws_get_stream(js->this_val);
if (!stream) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WritableStream");
ant_value_t writer_args[1] = { js->this_val };
ant_value_t saved_new_target = js->new_target;
js->new_target = g_ws_writer_proto;
ant_value_t writer = js_ws_writer_ctor(js, writer_args, 1);
js->new_target = saved_new_target;
return writer;
}
static ant_value_t setup_ws_default_controller(
ant_t *js, ant_value_t stream_obj, ant_value_t underlying_sink,
ant_value_t write_fn, ant_value_t close_fn, ant_value_t abort_fn,
ant_value_t size_fn, double hwm
) {
ws_controller_t *ctrl = calloc(1, sizeof(ws_controller_t));
if (!ctrl) return js_mkerr(js, "out of memory");
ctrl->strategy_hwm = hwm;
ant_value_t ctrl_obj = js_mkobj(js);
js_set_proto_init(ctrl_obj, g_ws_controller_proto);
+ js_set_slot(ctrl_obj, SLOT_BRAND, js_mknum(BRAND_WRITABLE_STREAM_CONTROLLER));
js_set_slot(ctrl_obj, SLOT_DATA, ANT_PTR(ctrl));
js_set_slot(ctrl_obj, SLOT_ENTRIES, stream_obj);
js_set_slot(ctrl_obj, SLOT_WS_WRITE, write_fn);
js_set_slot(ctrl_obj, SLOT_WS_CLOSE, close_fn);
js_set_slot(ctrl_obj, SLOT_WS_ABORT, abort_fn);
js_set_slot(ctrl_obj, SLOT_RS_SIZE, size_fn);
js_set_slot(ctrl_obj, SLOT_CTOR, underlying_sink);
js_set_slot(ctrl_obj, SLOT_BUFFER, js_mkarr(js));
js_set_finalizer(ctrl_obj, ws_controller_finalize);
ant_value_t ac_ctor = js_get(js, js_glob(js), "AbortController");
ant_value_t ac = js_mkundef();
if (is_callable(ac_ctor)) {
ant_value_t ac_proto = js_get(js, ac_ctor, "prototype");
ac = js_mkobj(js);
if (is_object_type(ac_proto)) js_set_proto_init(ac, ac_proto);
ant_value_t saved = js->new_target;
js->new_target = ac_ctor;
ant_value_t result = sv_vm_call(js->vm, js, ac_ctor, ac, NULL, 0, NULL, false);
js->new_target = saved;
if (is_err(result)) ac = js_mkundef();
}
js_set_slot(ctrl_obj, SLOT_WS_SIGNAL, ac);
js_set_slot(stream_obj, SLOT_ENTRIES, ctrl_obj);
bool backpressure = ws_default_controller_get_backpressure(ctrl);
writable_stream_update_backpressure(js, stream_obj, backpressure);
return ctrl_obj;
}
static ant_value_t js_ws_ctor(ant_t *js, ant_value_t *args, int nargs) {
if (vtype(js->new_target) == T_UNDEF)
return js_mkerr_typed(js, JS_ERR_TYPE, "WritableStream constructor requires 'new'");
ant_value_t underlying_sink = js_mkundef();
if (nargs > 0 && !is_undefined(args[0])) {
if (is_null(args[0]))
return js_mkerr_typed(js, JS_ERR_TYPE, "The underlying sink cannot be null");
underlying_sink = args[0];
}
if (is_object_type(underlying_sink)) {
ant_value_t type_val = js_get(js, underlying_sink, "type");
if (!is_undefined(type_val))
return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid type is specified");
}
ant_value_t strategy = js_mkundef();
if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
strategy = args[1];
double hwm = 1;
if (is_object_type(strategy)) {
ant_value_t hwm_val = js_get(js, strategy, "highWaterMark");
if (is_err(hwm_val)) return hwm_val;
if (!is_undefined(hwm_val)) {
hwm = js_to_number(js, hwm_val);
if (hwm != hwm || hwm < 0) return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid highWaterMark");
}
}
ant_value_t size_fn = js_mkundef();
if (is_object_type(strategy)) {
ant_value_t s = js_get(js, strategy, "size");
if (is_err(s)) return s;
if (!is_undefined(s)) {
if (!is_callable(s)) return js_mkerr_typed(js, JS_ERR_TYPE, "size must be a function");
size_fn = s;
}
}
ws_stream_t *st = calloc(1, sizeof(ws_stream_t));
if (!st) return js_mkerr(js, "out of memory");
st->state = WS_STATE_WRITABLE;
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, g_ws_proto);
+
if (is_object_type(proto)) js_set_proto_init(obj, proto);
+ js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_WRITABLE_STREAM));
js_set_slot(obj, SLOT_DATA, ANT_PTR(st));
js_set_slot(obj, SLOT_SETTLED, js_mkarr(js));
js_set_finalizer(obj, ws_stream_finalize);
ant_value_t write_fn = js_mkundef();
ant_value_t close_fn = js_mkundef();
ant_value_t abort_fn = js_mkundef();
ant_value_t start_fn = js_mkundef();
if (is_object_type(underlying_sink)) {
ant_value_t wv = js_getprop_fallback(js, underlying_sink, "write");
if (is_err(wv)) return wv;
if (!is_undefined(wv)) {
if (!is_callable(wv)) return js_mkerr_typed(js, JS_ERR_TYPE, "write must be a function");
write_fn = wv;
}
ant_value_t cv = js_getprop_fallback(js, underlying_sink, "close");
if (is_err(cv)) return cv;
if (!is_undefined(cv)) {
if (!is_callable(cv)) return js_mkerr_typed(js, JS_ERR_TYPE, "close must be a function");
close_fn = cv;
}
ant_value_t av = js_getprop_fallback(js, underlying_sink, "abort");
if (is_err(av)) return av;
if (!is_undefined(av)) {
if (!is_callable(av)) return js_mkerr_typed(js, JS_ERR_TYPE, "abort must be a function");
abort_fn = av;
}
ant_value_t sv = js_getprop_fallback(js, underlying_sink, "start");
if (is_err(sv)) return sv;
if (!is_undefined(sv)) {
if (!is_callable(sv)) return js_mkerr_typed(js, JS_ERR_TYPE, "start must be a function");
start_fn = sv;
}
}
ant_value_t ctrl_obj = setup_ws_default_controller(js, obj, underlying_sink, write_fn, close_fn, abort_fn, size_fn, hwm);
if (is_err(ctrl_obj)) return ctrl_obj;
if (is_callable(start_fn)) {
ant_value_t start_args[1] = { ctrl_obj };
ant_value_t start_result = sv_vm_call(js->vm, js, start_fn, underlying_sink, start_args, 1, NULL, false);
if (is_err(start_result)) return start_result;
if (vtype(start_result) == T_PROMISE) {
ant_value_t resolve_fn = js_heavy_mkfun(js, ws_start_resolve_handler, ctrl_obj);
ant_value_t reject_fn = js_heavy_mkfun(js, ws_start_reject_handler, ctrl_obj);
ant_value_t then_fn = js_get(js, start_result, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { resolve_fn, reject_fn };
sv_vm_call(js->vm, js, then_fn, start_result, then_args, 2, NULL, false);
}
}
if (vtype(start_result) != T_PROMISE) {
ant_value_t resolved = js_mkpromise(js);
js_resolve_promise(js, resolved, js_mkundef());
ant_value_t res_fn = js_heavy_mkfun(js, ws_start_resolve_handler, ctrl_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, ws_start_reject_handler, ctrl_obj);
ant_value_t then_fn = js_get(js, resolved, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { res_fn, rej_fn };
sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false);
}
}
} else {
ant_value_t resolved = js_mkpromise(js);
js_resolve_promise(js, resolved, js_mkundef());
ant_value_t res_fn = js_heavy_mkfun(js, ws_start_resolve_handler, ctrl_obj);
ant_value_t rej_fn = js_heavy_mkfun(js, ws_start_reject_handler, ctrl_obj);
ant_value_t then_fn = js_get(js, resolved, "then");
if (is_callable(then_fn)) {
ant_value_t then_args[2] = { res_fn, rej_fn };
sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false);
}
}
return obj;
}
static ant_value_t js_ws_controller_ctor(ant_t *js, ant_value_t *args, int nargs) {
return js_mkerr_typed(js, JS_ERR_TYPE, "WritableStreamDefaultController cannot be constructed directly");
}
void gc_mark_writable_streams(ant_t *js, void (*mark)(ant_t *, ant_value_t)) {
mark(js, g_ws_proto);
mark(js, g_ws_writer_proto);
mark(js, g_ws_controller_proto);
mark(js, g_close_sentinel);
}
void init_writable_stream_module(void) {
ant_t *js = rt->js;
ant_value_t g = js_glob(js);
g_close_sentinel = js_mkobj(js);
g_ws_controller_proto = js_mkobj(js);
js_set_getter_desc(js, g_ws_controller_proto, "signal", 6, js_mkfun(js_ws_controller_get_signal), JS_DESC_C);
js_set(js, g_ws_controller_proto, "error", js_mkfun(js_ws_controller_error));
js_set_descriptor(js, g_ws_controller_proto, "error", 5, JS_DESC_W | JS_DESC_C);
js_set_sym(js, g_ws_controller_proto, get_toStringTag_sym(), js_mkstr(js, "WritableStreamDefaultController", 31));
ant_value_t ctrl_ctor = js_make_ctor(js, js_ws_controller_ctor, g_ws_controller_proto, "WritableStreamDefaultController", 31);
js_set(js, g, "WritableStreamDefaultController", ctrl_ctor);
js_set_descriptor(js, g, "WritableStreamDefaultController", 31, JS_DESC_W | JS_DESC_C);
g_ws_writer_proto = js_mkobj(js);
js_set_getter_desc(js, g_ws_writer_proto, "closed", 6, js_mkfun(js_ws_writer_get_closed), JS_DESC_C);
js_set_getter_desc(js, g_ws_writer_proto, "desiredSize", 11, js_mkfun(js_ws_writer_get_desired_size), JS_DESC_C);
js_set_getter_desc(js, g_ws_writer_proto, "ready", 5, js_mkfun(js_ws_writer_get_ready), JS_DESC_C);
js_set(js, g_ws_writer_proto, "abort", js_mkfun(js_ws_writer_abort));
js_set_descriptor(js, g_ws_writer_proto, "abort", 5, JS_DESC_W | JS_DESC_C);
js_set(js, g_ws_writer_proto, "close", js_mkfun(js_ws_writer_close));
js_set_descriptor(js, g_ws_writer_proto, "close", 5, JS_DESC_W | JS_DESC_C);
js_set(js, g_ws_writer_proto, "releaseLock", js_mkfun(js_ws_writer_release_lock));
js_set_descriptor(js, g_ws_writer_proto, "releaseLock", 11, JS_DESC_W | JS_DESC_C);
js_set(js, g_ws_writer_proto, "write", js_mkfun(js_ws_writer_write));
js_set_descriptor(js, g_ws_writer_proto, "write", 5, JS_DESC_W | JS_DESC_C);
js_set_sym(js, g_ws_writer_proto, get_toStringTag_sym(), js_mkstr(js, "WritableStreamDefaultWriter", 27));
ant_value_t writer_ctor = js_make_ctor(js, js_ws_writer_ctor, g_ws_writer_proto, "WritableStreamDefaultWriter", 27);
js_set(js, g, "WritableStreamDefaultWriter", writer_ctor);
js_set_descriptor(js, g, "WritableStreamDefaultWriter", 27, JS_DESC_W | JS_DESC_C);
g_ws_proto = js_mkobj(js);
js_set_getter_desc(js, g_ws_proto, "locked", 6, js_mkfun(js_ws_get_locked), JS_DESC_C);
js_set(js, g_ws_proto, "abort", js_mkfun(js_ws_abort));
js_set_descriptor(js, g_ws_proto, "abort", 5, JS_DESC_W | JS_DESC_C);
js_set(js, g_ws_proto, "close", js_mkfun(js_ws_close));
js_set_descriptor(js, g_ws_proto, "close", 5, JS_DESC_W | JS_DESC_C);
js_set(js, g_ws_proto, "getWriter", js_mkfun(js_ws_get_writer));
js_set_descriptor(js, g_ws_proto, "getWriter", 9, JS_DESC_W | JS_DESC_C);
js_set_sym(js, g_ws_proto, get_toStringTag_sym(), js_mkstr(js, "WritableStream", 14));
ant_value_t ws_ctor = js_make_ctor(js, js_ws_ctor, g_ws_proto, "WritableStream", 14);
js_set(js, g, "WritableStream", ws_ctor);
js_set_descriptor(js, g, "WritableStream", 14, JS_DESC_W | JS_DESC_C);
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jun 17, 12:03 PM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
575324
Default Alt Text
(971 KB)

Event Timeline