Page MenuHomePhorge

No OneTemporary

Size
930 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/examples/embed/embed.c b/examples/embed/embed.c
index 609a740..82cca43 100644
--- a/examples/embed/embed.c
+++ b/examples/embed/embed.c
@@ -1,480 +1,480 @@
/**
* Ant JavaScript Engine - Embedding Example
* This demonstrates how to embed the Ant JS engine in a C application.
*
* to build:
* ./libant/build.sh
* ./libant/example.sh
*
* to run:
* ./libant/dist/embed
*/
#include <ant.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SEPARATOR "════════════════════════════════════════════════════════════"
#define SUBSEP "────────────────────────────────────────────────────────────"
static void print_header(int num, const char *title) {
printf("\n%s\n", SEPARATOR);
printf(" Example %d: %s\n", num, title);
printf("%s\n\n", SUBSEP);
}
static struct js *create_js_runtime(void *stack_base) {
- struct js *js = js_create_dynamic(0, 0);
+ struct js *js = js_create_dynamic();
if (!js) {
fprintf(stderr, "Failed to create JS runtime\n");
return NULL;
}
js_setstackbase(js, stack_base);
static char *default_argv[] = { "embed_example", NULL };
ant_runtime_init(js, 1, default_argv, NULL);
return js;
}
static void example_basic_eval(void) {
print_header(1, "Basic Evaluation");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
js_mkscope(js);
const char *code = "1 + 2 * 3";
jsval_t result = js_eval(js, code, strlen(code));
if (vtype(result) == T_NUM) {
printf(" Result: %g\n", js_getnum(result));
} else if (vtype(result) == T_ERR) {
printf(" Error: %s\n", js_str(js, result));
}
js_destroy(js);
}
static jsval_t my_add(struct js *js, jsval_t *args, int nargs) {
if (!js_chkargs(args, nargs, "dd")) {
return js_mkerr(js, "add() expects two numbers");
}
double a = js_getnum(args[0]);
double b = js_getnum(args[1]);
return js_mknum(a + b);
}
static jsval_t my_greet(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1 || vtype(args[0]) != T_STR) {
return js_mkerr(js, "greet() expects a string");
}
size_t len;
char *name = js_getstr(js, args[0], &len);
char buf[256];
snprintf(buf, sizeof(buf), "Hello, %s!", name);
return js_mkstr(js, buf, strlen(buf));
}
static jsval_t my_create_point(struct js *js, jsval_t *args, int nargs) {
if (!js_chkargs(args, nargs, "dd")) {
return js_mkerr(js, "createPoint() expects two numbers");
}
jsval_t obj = js_mkobj(js);
js_set(js, obj, "x", args[0]);
js_set(js, obj, "y", args[1]);
return obj;
}
static void example_c_functions(void) {
print_header(2, "C Functions");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
js_mkscope(js);
jsval_t global = js_glob(js);
js_set(js, global, "add", js_mkfun(my_add));
js_set(js, global, "greet", js_mkfun(my_greet));
js_set(js, global, "createPoint", js_mkfun(my_create_point));
const char *code1 = "add(10, 32)";
jsval_t r1 = js_eval(js, code1, strlen(code1));
printf(" add(10, 32) = %g\n", js_getnum(r1));
const char *code2 = "greet('World')";
jsval_t r2 = js_eval(js, code2, strlen(code2));
printf(" greet('World') = %s\n", js_str(js, r2));
const char *code3 = "let p = createPoint(3, 4); p.x * p.x + p.y * p.y";
jsval_t r3 = js_eval(js, code3, strlen(code3));
printf(" distance² = %g\n", js_getnum(r3));
js_destroy(js);
}
static void example_objects_arrays(void) {
print_header(3, "Objects and Arrays");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
js_mkscope(js);
jsval_t global = js_glob(js);
jsval_t config = js_mkobj(js);
js_set(js, config, "debug", js_true);
js_set(js, config, "version", js_mknum(1.0));
js_set(js, config, "name", js_mkstr(js, "MyApp", 5));
js_set(js, global, "config", config);
jsval_t arr = js_mkarr(js);
js_arr_push(js, arr, js_mknum(10));
js_arr_push(js, arr, js_mknum(20));
js_arr_push(js, arr, js_mknum(30));
js_set(js, global, "numbers", arr);
const char *code = "config.name + ' v' + config.version + ' - sum: ' + numbers.reduce((a,b) => a+b, 0)";
jsval_t result = js_eval(js, code, strlen(code));
printf(" Result: %s\n", js_str(js, result));
jsval_t name_val = js_get(js, config, "name");
printf(" config.name: %s\n", js_str(js, name_val));
jsval_t debug_val = js_get(js, config, "debug");
printf(" config.debug: %s\n", js_str(js, debug_val));
js_destroy(js);
}
static void example_error_handling(void) {
print_header(4, "Error Handling");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
js_mkscope(js);
const char *bad_code = "let x = {";
jsval_t r1 = js_eval(js, bad_code, strlen(bad_code));
if (vtype(r1) == T_ERR) {
printf(" Syntax error: %s\n", js_str(js, r1));
}
const char *ref_err = "undefinedVariable + 1";
jsval_t r2 = js_eval(js, ref_err, strlen(ref_err));
if (vtype(r2) == T_ERR) {
printf(" Reference error: %s\n", js_str(js, r2));
}
jsval_t global = js_glob(js);
js_set(js, global, "add", js_mkfun(my_add));
const char *type_err = "add('not', 'numbers')";
jsval_t r3 = js_eval(js, type_err, strlen(type_err));
if (vtype(r3) == T_ERR) {
printf(" Type error: %s\n", js_str(js, r3));
}
js_destroy(js);
}
static void example_call_js_from_c(void) {
print_header(5, "Calling JS from C");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
jsval_t scope = js_mkscope(js);
const char *code =
"function multiply(a, b) {"
" return a * b;"
"}"
"function formatName(first, last) {"
" return last + ', ' + first;"
"}";
js_eval(js, code, strlen(code));
jsval_t multiply_fn = js_get(js, scope, "multiply");
jsval_t format_fn = js_get(js, scope, "formatName");
jsval_t args1[] = { js_mknum(6), js_mknum(7) };
jsval_t result1 = js_call(js, multiply_fn, args1, 2);
printf(" multiply(6, 7) = %g\n", js_getnum(result1));
jsval_t args2[] = {
js_mkstr(js, "John", 4),
js_mkstr(js, "Doe", 3)
};
jsval_t result2 = js_call(js, format_fn, args2, 2);
printf(" formatName('John', 'Doe') = %s\n", js_str(js, result2));
js_destroy(js);
}
static void example_iterate_properties(void) {
print_header(6, "Iterating Properties");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
js_mkscope(js);
const char *code = "({ name: 'Alice', age: 30, city: 'NYC' })";
jsval_t obj = js_eval(js, code, strlen(code));
ant_iter_t iter = js_prop_iter_begin(js, obj);
const char *key;
size_t key_len;
jsval_t value;
printf(" Object properties:\n");
while (js_prop_iter_next(&iter, &key, &key_len, &value)) {
printf(" • %.*s = %s\n", (int)key_len, key, js_str(js, value));
}
js_prop_iter_end(&iter);
js_destroy(js);
}
static jsval_t method_get_full_name(struct js *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
jsval_t this_obj = js_getthis(js);
jsval_t first = js_get(js, this_obj, "firstName");
jsval_t last = js_get(js, this_obj, "lastName");
size_t first_len, last_len;
char *first_str = js_getstr(js, first, &first_len);
char *last_str = js_getstr(js, last, &last_len);
char buf[256];
snprintf(buf, sizeof(buf), "%s %s", first_str, last_str);
return js_mkstr(js, buf, strlen(buf));
}
static void example_this_context(void) {
print_header(7, "'this' Context");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
js_mkscope(js);
jsval_t person = js_mkobj(js);
js_set(js, person, "firstName", js_mkstr(js, "Jane", 4));
js_set(js, person, "lastName", js_mkstr(js, "Smith", 5));
js_set(js, person, "getFullName", js_mkfun(method_get_full_name));
js_set(js, js_glob(js), "person", person);
const char *code = "person.getFullName()";
jsval_t result = js_eval(js, code, strlen(code));
printf(" person.getFullName() = %s\n", js_str(js, result));
js_destroy(js);
}
static void example_stateful_session(void) {
print_header(8, "Stateful Session");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
js_mkscope(js);
const char *scripts[] = {
"let counter = 0;",
"function increment() { return ++counter; }",
"function getCount() { return counter; }",
"increment(); increment(); increment();",
"getCount()"
};
jsval_t result = js_mkundef();
for (int i = 0; i < 5; i++) {
result = js_eval(js, scripts[i], strlen(scripts[i]));
if (vtype(result) == T_ERR) {
printf(" Error in script %d: %s\n", i, js_str(js, result));
break;
}
}
printf(" Final count: %g\n", js_getnum(result));
js_destroy(js);
}
static void example_async_event_loop(void) {
print_header(9, "Async & Event Loop");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
init_symbol_module();
init_builtin_module();
init_timer_module();
jsval_t scope = js_mkscope(js);
const char *code =
"let results = [];"
""
"setTimeout(() => {"
" results.push('timer 1 (50ms)');"
"}, 50);"
""
"setTimeout(() => {"
" results.push('timer 2 (10ms)');"
"}, 10);"
""
"Promise.resolve('promise 1').then(v => {"
" results.push(v);"
"});"
""
"queueMicrotask(() => {"
" results.push('microtask');"
"});"
""
"results.push('sync');";
jsval_t result = js_eval(js, code, strlen(code));
if (vtype(result) == T_ERR) {
printf(" Error: %s\n", js_str(js, result));
js_destroy(js);
return;
}
js_run_event_loop(js);
jsval_t results = js_get(js, scope, "results");
printf(" Execution order:\n");
jsoff_t len = js_arr_len(js, results);
for (jsoff_t i = 0; i < len; i++) {
jsval_t item = js_arr_get(js, results, i);
printf(" %llu. %s\n", (unsigned long long)i + 1, js_str(js, item));
}
js_destroy(js);
}
static void example_console_logging(void) {
print_header(10, "Console Logging");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
init_console_module();
js_mkscope(js);
const char *code =
"console.log('Hello from JavaScript!');"
"console.log('Number:', 42, 'Boolean:', true);"
"console.log('Object:', { name: 'test', value: 123 });"
"console.log('Array:', [1, 2, 3]);"
"console.warn('This is a warning');"
"console.error('This is an error');"
"'done'";
jsval_t result = js_eval(js, code, strlen(code));
if (vtype(result) == T_ERR) {
printf(" Error: %s\n", js_str(js, result));
}
js_destroy(js);
}
static void example_global_this(void) {
print_header(11, "GlobalThis");
volatile char stack_base;
struct js *js = create_js_runtime((void *)&stack_base);
if (!js) return;
init_console_module();
js_mkscope(js);
jsval_t global = js_glob(js);
js_set(js, global, "myNumber", js_mknum(42));
js_set(js, global, "myString", js_mkstr(js, "hello from C", 12));
js_set(js, global, "myBool", js_true);
jsval_t myObj = js_mkobj(js);
js_set(js, myObj, "a", js_mknum(1));
js_set(js, myObj, "b", js_mknum(2));
js_set(js, global, "myObject", myObj);
const char *code =
"console.log('globalThis.myNumber:', globalThis.myNumber);"
"console.log('globalThis.myString:', globalThis.myString);"
"console.log('globalThis.myBool:', globalThis.myBool);"
"console.log('globalThis.myObject:', globalThis.myObject);"
""
"globalThis.addedFromJS = 'I was added from JavaScript';"
"console.log('globalThis.addedFromJS:', globalThis.addedFromJS);"
""
"console.log('\\nAll custom globals:');"
"console.log(' myNumber:', myNumber);"
"console.log(' myString:', myString);"
"console.log(' myBool:', myBool);"
"console.log(' myObject:', myObject);"
"console.log(' addedFromJS:', addedFromJS);"
"console.log(this)";
jsval_t result = js_eval(js, code, strlen(code));
if (vtype(result) == T_ERR) {
printf(" Error: %s\n", js_str(js, result));
}
jsval_t added = js_get(js, global, "addedFromJS");
printf("\n Read from C: addedFromJS = %s\n", js_str(js, added));
js_destroy(js);
}
int main(void) {
printf("\n%s\n", SEPARATOR);
printf(" Ant JavaScript Engine - Embedding Examples\n");
printf("%s\n", SEPARATOR);
example_basic_eval();
example_c_functions();
example_objects_arrays();
example_error_handling();
example_call_js_from_c();
example_iterate_properties();
example_this_context();
example_stateful_session();
example_async_event_loop();
example_console_logging();
example_global_this();
return EXIT_SUCCESS;
}
\ No newline at end of file
diff --git a/include/ant.h b/include/ant.h
index c043f6e..f2345db 100644
--- a/include/ant.h
+++ b/include/ant.h
@@ -1,193 +1,193 @@
#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_LIMIT_SIZE_CACHE 16384
#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 | ((jsval_t)T_BOOL << NANBOX_TYPE_SHIFT) | 1)
#define js_false (NANBOX_PREFIX | ((jsval_t)T_BOOL << NANBOX_TYPE_SHIFT))
#define js_bool(x) (js_false | (jsval_t)!!(x))
#define JS_DESC_W (1 << 0)
#define JS_DESC_E (1 << 1)
#define JS_DESC_C (1 << 2)
ant_t *js_create(void *buf, size_t len);
-ant_t *js_create_dynamic(size_t initial_size, size_t max_size);
+ant_t *js_create_dynamic();
jsval_t js_glob(ant_t *);
jsval_t js_mkscope(ant_t *);
jsval_t js_getscope(ant_t *);
jsval_t js_eval(ant_t *, const char *, size_t);
jsval_t js_eval_cached(ant_t *, const char *, size_t);
void js_destroy(ant_t *);
void js_delscope(ant_t *);
bool js_truthy(ant_t *, jsval_t);
void js_setstacklimit(ant_t *, size_t);
void js_setstackbase(ant_t *, void *);
uint32_t js_to_uint32(double d);
int32_t js_to_int32(double d);
bool js_chkargs(jsval_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);
size_t js_getbrk(ant_t *);
jshdl_t js_root(ant_t *, jsval_t);
jsval_t js_deref(ant_t *, jshdl_t);
void js_unroot(ant_t *, jshdl_t);
void js_root_update(ant_t *, jshdl_t, jsval_t);
jsval_t js_mkundef(void);
jsval_t js_mknull(void);
jsval_t js_mknum(double);
jsval_t js_getthis(ant_t *);
void js_setthis(ant_t *, jsval_t);
jsval_t js_getcurrentfunc(ant_t *);
jsval_t js_get(ant_t *, jsval_t, const char *);
jsval_t js_getprop_proto(ant_t *, jsval_t, const char *);
jsval_t js_getprop_fallback(ant_t *js, jsval_t obj, const char *name);
jsoff_t js_arr_len(struct js *js, jsval_t arr);
jsval_t js_arr_get(struct js *js, jsval_t arr, jsoff_t idx);
bool js_iter(
ant_t *js,
jsval_t iterable,
bool (*callback)(
ant_t *js,
jsval_t value,
void *udata
),
void *udata
);
uint64_t js_sym_id(jsval_t sym);
const char *js_sym_desc(ant_t *js, jsval_t sym);
jsval_t js_mksym_for(ant_t *, const char *key);
jsval_t js_symbol_to_string(struct js *js, jsval_t sym);
const char *js_sym_key(jsval_t sym);
jsval_t js_mkobj(ant_t *);
jsval_t js_newobj(ant_t *);
jsval_t js_mkarr(ant_t *);
jsval_t js_mkstr(ant_t *, const void *, size_t);
jsval_t js_mkbigint(ant_t *, const char *digits, size_t len, bool negative);
jsval_t js_mksym(ant_t *, const char *desc);
jsval_t js_mkfun(jsval_t (*fn)(ant_t *, jsval_t *, int));
jsval_t js_heavy_mkfun(ant_t *js, jsval_t (*fn)(ant_t *, jsval_t *, int), jsval_t data);
jsval_t js_mkprop_fast(ant_t *js, jsval_t obj, const char *key, size_t len, jsval_t v);
jsoff_t js_mkprop_fast_off(ant_t *js, jsval_t obj, const char *key, size_t len, jsval_t v);
jsval_t js_call(ant_t *js, jsval_t func, jsval_t *args, int nargs);
jsval_t js_call_with_this(ant_t *js, jsval_t func, jsval_t this_val, jsval_t *args, int nargs);
void js_set(ant_t *, jsval_t, const char *, jsval_t);
void js_saveval(ant_t *js, jsoff_t off, jsval_t v);
bool js_del(ant_t *, jsval_t obj, const char *key);
void js_merge_obj(ant_t *, jsval_t dst, jsval_t src);
void js_arr_push(ant_t *, jsval_t arr, jsval_t val);
jsval_t js_setprop(ant_t *, jsval_t obj, jsval_t key, jsval_t val);
jsval_t js_setprop_nonconfigurable(ant_t *, jsval_t obj, const char *key, size_t keylen, jsval_t val);
void js_set_proto(ant_t *, jsval_t obj, jsval_t proto);
jsval_t js_get_proto(ant_t *, jsval_t obj);
jsval_t js_get_ctor_proto(ant_t *, const char *name, size_t len);
jsval_t js_tostring_val(ant_t *js, jsval_t value);
uint8_t vtype(jsval_t val);
size_t vdata(jsval_t val);
double js_getnum(jsval_t val);
char *js_getstr(ant_t *js, jsval_t val, size_t *len);
const char *js_str(ant_t *, jsval_t val);
const char *get_str_prop(struct js *js, jsval_t obj, const char *key, jsoff_t klen, jsoff_t *out_len);
typedef struct {
void *ctx;
jsoff_t off;
} ant_iter_t;
ant_iter_t js_prop_iter_begin(ant_t *js, jsval_t obj);
bool js_prop_iter_next(ant_iter_t *iter, const char **key, size_t *key_len, jsval_t *value);
void js_prop_iter_end(ant_iter_t *iter);
jsval_t js_obj_to_func(jsval_t obj);
jsval_t js_mkpromise(ant_t *js);
jsval_t js_mktypedarray(void *data);
void *js_gettypedarray(jsval_t val);
jsval_t js_mkffi(unsigned int index);
int js_getffi(jsval_t val);
void js_resolve_promise(ant_t *js, jsval_t promise, jsval_t value);
void js_reject_promise(ant_t *js, jsval_t promise, jsval_t value);
void js_check_unhandled_rejections(ant_t *js);
void js_process_promise_handlers(ant_t *js, uint32_t promise_id);
void js_setup_import_meta(ant_t *js, const char *filename);
typedef jsval_t (*ant_library_init_fn)(ant_t *js);
void ant_register_library(ant_library_init_fn init_fn, const char *name, ...);
#define ant_standard_library(name, lib) \
ant_register_library(lib, name, "ant:" name, "node:" name, NULL)
typedef jsval_t (*js_getter_fn)(ant_t *js, jsval_t obj, const char *key, size_t key_len);
typedef bool (*js_setter_fn)(ant_t *js, jsval_t obj, const char *key, size_t key_len, jsval_t value);
typedef bool (*js_deleter_fn)(ant_t *js, jsval_t obj, const char *key, size_t key_len);
typedef jsval_t (*js_keys_fn)(ant_t *js, jsval_t obj);
void js_set_getter(ant_t *js, jsval_t obj, js_getter_fn getter);
void js_set_setter(ant_t *js, jsval_t obj, js_setter_fn setter);
void js_set_deleter(ant_t *js, jsval_t obj, js_deleter_fn deleter);
void js_set_keys(ant_t *js, jsval_t obj, js_keys_fn keys);
void js_set_descriptor(ant_t *js, jsval_t obj, const char *key, size_t klen, int flags);
void js_set_getter_desc(ant_t *js, jsval_t obj, const char *key, size_t klen, jsval_t getter, int flags);
void js_set_setter_desc(ant_t *js, jsval_t obj, const char *key, size_t klen, jsval_t setter, int flags);
void js_set_accessor_desc(ant_t *js, jsval_t obj, const char *key, size_t klen, jsval_t getter, jsval_t setter, int flags);
jsval_t js_get_slot(ant_t *js, jsval_t obj, internal_slot_t slot);
void js_set_slot(ant_t *js, jsval_t obj, internal_slot_t slot, jsval_t value);
bool js_is_slot_prop(jsoff_t header);
jsoff_t js_next_prop(jsoff_t header);
jsoff_t js_loadoff(ant_t *js, jsoff_t off);
#endif
\ No newline at end of file
diff --git a/include/cli/misc.h b/include/cli/misc.h
new file mode 100644
index 0000000..9e6f92f
--- /dev/null
+++ b/include/cli/misc.h
@@ -0,0 +1,16 @@
+#ifndef MISC_H
+#define MISC_H
+#include <stdio.h>
+
+typedef struct {
+ const char *s;
+ const char *l;
+ const char *d;
+ const char *g;
+ int opt;
+} flag_help_t;
+
+void print_flags_help(FILE *fp, void **argtable);
+void print_flag(FILE *fp, flag_help_t f);
+
+#endif
\ No newline at end of file
diff --git a/include/cli/version.h b/include/cli/version.h
new file mode 100644
index 0000000..b4b169e
--- /dev/null
+++ b/include/cli/version.h
@@ -0,0 +1,7 @@
+#ifndef VERSION_H
+#define VERSION_H
+
+const char *ant_semver(void);
+int ant_version(void *argtable[]);
+
+#endif
\ No newline at end of file
diff --git a/include/utils.h b/include/utils.h
index e342cec..4debe5e 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -1,40 +1,38 @@
#ifndef ANT_UTILS_H
#define ANT_UTILS_H
-#define ARGTABLE_COUNT 10
+#define ARGTABLE_COUNT 8
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
char *ptr;
char *heap;
} cstr_buf_t;
-const char *ant_semver(void);
char *resolve_js_file(const char *filename);
uint64_t hash_key(const char *key, size_t len);
int hex_digit(char c);
int is_typescript_file(const char *filename);
-int ant_version(void *argtable[]);
void *try_oom(size_t size);
void cstr_free(cstr_buf_t *buf);
char *cstr_init(
cstr_buf_t *buf,
char *stack,
size_t stack_size,
const char *src,
size_t len
);
#define CSTR_BUF(name, size) \
char name##_stack[size]; \
cstr_buf_t name = {0}
#define CSTR_INIT(buf, src, len) \
cstr_init(&(buf), buf##_stack, sizeof(buf##_stack), (src), (len))
#endif
\ No newline at end of file
diff --git a/meson.build b/meson.build
index dc3a1ba..3d2019a 100644
--- a/meson.build
+++ b/meson.build
@@ -1,104 +1,106 @@
project('ant', 'c', default_options: [
'buildtype=release',
'optimization=3',
'c_std=gnu23',
'default_library=static',
'b_lto=true',
'b_lto_threads=8',
'strip=true',
'warning_level=2'
], subproject_dir: 'vendor')
src_root = meson.project_source_root()
vendor_dir = 'vendor'
is_static = get_option('static_link')
subdir('meson')
module_files = run_command('sh', '-c',
'cd "$MESON_SOURCE_ROOT" && ls src/modules/*.c',
check: true
).stdout().strip().split()
lib_sources = files(
'src/roots.c',
'src/utils.c',
'src/utf8.c',
'src/escape.c',
'src/reactor.c',
'src/sugar.c',
'src/ant.c',
'src/errors.c',
'src/stack.c',
'src/gc.c',
'src/repl.c',
'src/runtime.c',
'src/snapshot.c',
'src/esm/remote.c',
'src/cli/pkg.c',
+ 'src/cli/misc.c',
+ 'src/cli/version.c',
'src/cli/cprintf.c',
) + files(module_files)
include = include_directories('include')
build_include = include_directories('.')
strip_include = include_directories('src/strip')
libant = static_library(
'ant',
lib_sources + [snapshot_h],
include_directories: [
include, version_include,
build_include, strip_include
],
dependencies: ant_deps,
install: true
)
pkg_lib = custom_target(
'pkg_zig',
output: 'libpkg.a',
command: [
'sh', '-c',
'"$ZIG" build --build-file "$PKG_ZIG_DIR/build.zig" --prefix "$PKG_BUILD_DIR" && cp "$PKG_BUILD_DIR/lib/$PKG_LIB_NAME" "@OUTPUT@"'
],
env: {
'ZIG': zig.full_path(),
'ANT_VERSION': ant_version,
'PKG_ZIG_DIR': pkg_zig_dir, 'PKG_BUILD_DIR': pkg_build_dir,
'LMDB_INCLUDE': lmdb_include_path, 'ZLIB_INCLUDE': zlib_include_path,
'LIBUV_INCLUDE': libuv_include_path, 'YYJSON_INCLUDE': yyjson_include_path,
'PKG_TARGET': host_machine.cpu_family() + '-' + host_machine.system(),
'PKG_LIB_NAME': host_machine.system() == 'windows' ? 'pkg.lib' : 'libpkg.a',
},
build_by_default: true,
build_always_stale: true
)
pkg_dep = declare_dependency(
link_with: [pkg_lib],
include_directories: include,
dependencies: [lmdb_dep, tlsuv_dep, libuv_dep, nghttp2_dep]
)
libant_dep = declare_dependency(
link_with: libant,
include_directories: [
include, version_include, build_include
],
dependencies: ant_deps + [oxc_dep, pkg_dep]
)
link_args = []
if get_option('static_link')
link_args += ['-static']
endif
executable(
'ant',
files('src/main.c') + [snapshot_h],
include_directories: [strip_include],
dependencies: libant_dep,
link_args: link_args
)
\ No newline at end of file
diff --git a/src/ant.c b/src/ant.c
index 8a6a969..f8043b8 100644
--- a/src/ant.c
+++ b/src/ant.c
@@ -1,23962 +1,23959 @@
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC optimize("O3,inline")
#endif
#include <compat.h> // IWYU pragma: keep
#include "ant.h"
#include "gc.h"
#include "tokens.h"
#include "common.h"
#include "arena.h"
#include "utils.h"
#include "runtime.h"
#include "internal.h"
#include "sugar.h"
#include "stack.h"
#include "errors.h"
#include "utf8.h"
#include "escape.h"
#include "esm/remote.h"
#include <uv.h>
#include <oxc.h>
#include <assert.h>
#include <pcre2.h>
#include <stdarg.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/fs.h"
#include "modules/timer.h"
#include "modules/fetch.h"
#include "modules/symbol.h"
#include "modules/ffi.h"
#include "modules/child_process.h"
#include "modules/readline.h"
#include "modules/process.h"
#include "modules/json.h"
#include "modules/buffer.h"
#include "modules/collections.h"
#include "modules/navigator.h"
#include "modules/server.h"
#include "modules/events.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
typedef struct {
jsval_t *stack;
int depth;
int capacity;
} this_stack_t;
static this_stack_t global_this_stack = {NULL, 0, 0};
const UT_icd jsoff_icd = {
.sz = sizeof(jsoff_t),
.init = NULL,
.copy = NULL,
.dtor = NULL,
};
const UT_icd jsval_icd = {
.sz = sizeof(jsval_t),
.init = NULL,
.copy = NULL,
.dtor = NULL,
};
UT_array *global_scope_stack = NULL;
UT_array *saved_scope_stack = NULL;
typedef struct {
const char *name;
jsoff_t name_len;
bool is_loop;
bool is_block;
} label_entry_t;
static const UT_icd label_entry_icd = {
.sz = sizeof(label_entry_t),
.init = NULL,
.copy = NULL,
.dtor = NULL,
};
static UT_array *label_stack = NULL;
static const char *break_target_label = NULL;
static jsoff_t break_target_label_len = 0;
static const char *continue_target_label = NULL;
static jsoff_t continue_target_label_len = 0;
#define F_BREAK_LABEL 1U
#define F_CONTINUE_LABEL 2U
static uint8_t label_flags = 0;
typedef struct esm_module {
char *path;
char *resolved_path;
jsval_t namespace_obj;
jsval_t default_export;
bool is_loaded;
bool is_loading;
bool is_json;
bool is_text;
bool is_image;
bool is_url;
char *url_content;
size_t url_content_len;
struct esm_module *next;
UT_hash_handle hh;
} esm_module_t;
typedef struct {
esm_module_t *modules;
int count;
} esm_module_cache_t;
typedef struct ant_library {
char name[256];
ant_library_init_fn init_fn;
UT_hash_handle hh;
} ant_library_t;
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;
static size_t intern_count = 0;
static size_t intern_bytes = 0;
static interned_string_t *intern_buckets[ANT_LIMIT_SIZE_CACHE];
typedef struct {
jsoff_t obj_off;
const char *intern_ptr;
jsoff_t prop_off;
jsoff_t tail;
uint64_t generation;
} intern_prop_cache_entry_t;
typedef struct {
const char *code_ptr;
jsoff_t code_off;
jsoff_t cached_key_off;
uint64_t generation;
} dot_ic_entry_t;
static uint64_t intern_prop_cache_gen = 1;
static dot_ic_entry_t dot_ic_table[ANT_LIMIT_SIZE_CACHE];
static intern_prop_cache_entry_t intern_prop_cache[ANT_LIMIT_SIZE_CACHE];
static inline size_t dot_ic_slot(const char *code, jsoff_t off) {
uint64_t h = (uintptr_t)code ^ (off * 0x9E3779B97F4A7C15ULL);
return (size_t)((h ^ (h >> 32)) & (ANT_LIMIT_SIZE_CACHE - 1));
}
typedef struct promise_handler {
jsval_t onFulfilled;
jsval_t onRejected;
jsval_t nextPromise;
} promise_handler_t;
static const UT_icd promise_handler_icd = {
.sz = sizeof(promise_handler_t),
.init = NULL,
.copy = NULL,
.dtor = NULL,
};
typedef struct promise_data_entry {
jsval_t value;
UT_array *handlers;
uint32_t promise_id;
uint32_t trigger_pid;
jsoff_t obj_offset;
int state;
bool has_rejection_handler;
bool processing;
UT_hash_handle hh;
UT_hash_handle hh_unhandled;
} promise_data_entry_t;
static promise_data_entry_t *promise_registry = NULL;
static promise_data_entry_t *unhandled_rejections = NULL;
static uint32_t next_promise_id = 1;
static promise_data_entry_t *get_promise_data(uint32_t promise_id, bool create);
static uint32_t get_promise_id(struct js *js, jsval_t p);
static bool js_try_grow_memory(struct js *js, size_t needed);
typedef struct proxy_data {
jsoff_t obj_offset;
jsval_t target;
jsval_t handler;
bool revoked;
UT_hash_handle hh;
} proxy_data_t;
typedef struct dynamic_accessors {
jsoff_t obj_offset;
js_getter_fn getter;
js_setter_fn setter;
js_deleter_fn deleter;
js_keys_fn keys;
UT_hash_handle hh;
} dynamic_accessors_t;
typedef struct descriptor_entry {
uint64_t key;
jsoff_t obj_off;
char *prop_name;
size_t prop_len;
bool writable;
bool enumerable;
bool configurable;
bool has_getter;
bool has_setter;
jsval_t getter;
jsval_t setter;
UT_hash_handle hh;
} descriptor_entry_t;
typedef struct {
jsoff_t obj_off;
jsoff_t key_off;
} propref_data_t;
static const UT_icd propref_icd = { sizeof(propref_data_t), NULL, NULL, NULL };
static UT_array *propref_stack = NULL;
typedef struct parsed_param {
jsoff_t name_off;
jsoff_t name_len;
jsoff_t default_start;
jsoff_t default_len;
jsoff_t pattern_off;
jsoff_t pattern_len;
bool is_destruct;
} parsed_param_t;
typedef struct cached_token {
uint8_t tok;
uint8_t had_newline;
jsoff_t toff;
jsoff_t tlen;
jsoff_t pos;
jsval_t tval;
} cached_token_t;
typedef struct token_stream {
cached_token_t *tokens;
int count;
int capacity;
} token_stream_t;
typedef struct parsed_func {
uint64_t code_hash;
jsoff_t body_start;
jsoff_t body_len;
int param_count;
bool has_rest;
bool is_strict;
bool is_expr;
bool uses_arguments;
bool has_tail_calls;
jsoff_t rest_param_start;
jsoff_t rest_param_len;
UT_array *params;
token_stream_t *tokens;
UT_hash_handle hh;
} parsed_func_t;
static const UT_icd parsed_param_icd = {
.sz = sizeof(parsed_param_t),
.init = NULL,
.copy = NULL,
.dtor = NULL,
};
static parsed_func_t *func_parse_cache = NULL;
static ant_library_t *library_registry = NULL;
static esm_module_cache_t global_module_cache = {NULL, 0};
static proxy_data_t *proxy_registry = NULL;
static dynamic_accessors_t *accessor_registry = NULL;
static descriptor_entry_t *desc_registry = NULL;
void ant_register_library(ant_library_init_fn init_fn, const char *name, ...) {
va_list args;
const char *alias = name;
va_start(args, name);
while (alias != NULL) {
ant_library_t *lib = (ant_library_t *)ant_calloc(sizeof(ant_library_t));
if (!lib) break;
strncpy(lib->name, alias, sizeof(lib->name) - 1);
lib->name[sizeof(lib->name) - 1] = '\0';
lib->init_fn = init_fn;
HASH_ADD_STR(library_registry, name, lib);
alias = va_arg(args, const char *);
}
va_end(args);
}
static ant_library_t* find_library(const char *specifier, size_t spec_len) {
ant_library_t *lib = NULL;
char key[256];
if (spec_len >= sizeof(key)) return NULL;
memcpy(key, specifier, spec_len);
key[spec_len] = '\0';
HASH_FIND_STR(library_registry, key, lib);
return lib;
}
static const char *typestr_raw(uint8_t t) {
const char *names[] = {
"object", "prop", "string", "undefined", "null", "number",
"boolean", "function", "coderef", "cfunc", "err", "array",
"promise", "typedarray", "bigint", "propref", "symbol", "generator", "ffi"
};
return (t < sizeof(names) / sizeof(names[0])) ? names[t] : "??";
}
jsval_t tov(double d) {
union { double d; jsval_t v; } u = {d}; return u.v;
}
double tod(jsval_t v) {
union { jsval_t v; double d; } u = {v}; return u.d;
}
static bool is_tagged(jsval_t v) {
return (v >> 53) == NANBOX_PREFIX_CHK;
}
size_t vdata(jsval_t v) {
return (size_t)(v & NANBOX_DATA_MASK);
}
#define PROPREF_STACK_FLAG 0x800000000000ULL
#define PROPREF_PRIM_FLAG 0x400000000000ULL
#define PROPREF_INDEX_MASK 0x3FFFFFFFFFFFULL
#define PROPREF_OFF_MASK 0x7FFFFFULL
#define PROPREF_PAYLOAD 0x7FFFFFULL
#define PROPREF_SAFE_MASK 0x7FFFFFULL
#define PROPREF_KEY_SHIFT 23U
typedef struct {
jsval_t prim_val;
jsoff_t key_off;
} prim_propref_data_t;
static UT_icd prim_propref_icd = { sizeof(prim_propref_data_t), NULL, NULL, NULL };
static UT_array *prim_propref_stack = NULL;
static jsoff_t coderefoff(jsval_t v) { return v & PROPREF_OFF_MASK; }
static jsoff_t codereflen(jsval_t v) { return (v >> PROPREF_KEY_SHIFT) & PROPREF_OFF_MASK; }
static jsval_t get_slot(struct js *js, jsval_t obj, internal_slot_t slot);
static void set_slot(struct js *js, jsval_t obj, internal_slot_t slot, jsval_t value);
static jsval_t get_proto(struct js *js, jsval_t obj);
static void set_proto(struct js *js, jsval_t obj, jsval_t proto);
enum tail_scan { TAIL_NONE, TAIL_OK, TAIL_UNSAFE };
static enum tail_scan scan_tail_span(const char *code, jsoff_t clen, jsoff_t start, jsoff_t end);
static void clear_break_label(void) {
break_target_label = NULL;
break_target_label_len = 0;
label_flags &= ~F_BREAK_LABEL;
}
static void clear_continue_label(void) {
continue_target_label = NULL;
continue_target_label_len = 0;
label_flags &= ~F_CONTINUE_LABEL;
}
static inline propref_data_t *propref_get_entry(jsval_t v) {
uint64_t data = v & NANBOX_DATA_MASK;
if (!(data & PROPREF_STACK_FLAG)) return NULL;
int idx = (int)(data & PROPREF_INDEX_MASK);
if (!propref_stack || idx < 0 || idx >= (int)utarray_len(propref_stack)) return NULL;
return (propref_data_t *)utarray_eltptr(propref_stack, (unsigned)idx);
}
static jsoff_t propref_obj(jsval_t v) {
propref_data_t *entry = propref_get_entry(v);
if (entry) return entry->obj_off;
uint64_t data = v & NANBOX_DATA_MASK;
return (data & PROPREF_STACK_FLAG) ? 0 : (data & PROPREF_OFF_MASK);
}
static jsoff_t propref_key(jsval_t v) {
propref_data_t *entry = propref_get_entry(v);
if (entry) return entry->key_off;
uint64_t data = v & NANBOX_DATA_MASK;
return (data & PROPREF_STACK_FLAG) ? 0 : ((data >> PROPREF_KEY_SHIFT) & PROPREF_OFF_MASK);
}
static inline jsoff_t offtolen(jsoff_t off) { return (off >> 3) - 1; }
static inline jsoff_t align64(jsoff_t v) { return (v + 7) & ~7ULL; }
static void saveoff(struct js *js, jsoff_t off, jsoff_t val) {
memcpy(&js->mem[off], &val, sizeof(val));
}
static void saveval(struct js *js, jsoff_t off, jsval_t val) {
memcpy(&js->mem[off], &val, sizeof(val));
}
static const char *typestr(uint8_t t) {
if (t == T_CFUNC) return "function";
if (t == T_ARR) return "object";
if (t == T_NULL) return "object";
return typestr_raw(t);
}
uint8_t vtype(jsval_t v) {
return is_tagged(v) ? ((v >> NANBOX_TYPE_SHIFT) & NANBOX_TYPE_MASK) : (uint8_t)T_NUM;
}
jsval_t mkval(uint8_t type, uint64_t data) {
return NANBOX_PREFIX | ((jsval_t)(type & NANBOX_TYPE_MASK) << NANBOX_TYPE_SHIFT) | (data & NANBOX_DATA_MASK);
}
jsval_t js_obj_to_func(jsval_t obj) {
return mkval(T_FUNC, vdata(obj));
}
jsval_t js_mktypedarray(void *data) {
return mkval(T_TYPEDARRAY, (uintptr_t)data);
}
void *js_gettypedarray(jsval_t val) {
if (vtype(val) != T_TYPEDARRAY) return NULL;
return (void *)vdata(val);
}
jsval_t js_get_slot(struct js *js, jsval_t obj, internal_slot_t slot) {
return get_slot(js, obj, slot);
}
void js_set_slot(struct js *js, jsval_t obj, internal_slot_t slot, jsval_t value) {
set_slot(js, obj, slot, value);
}
jsval_t js_mkffi(unsigned int index) {
return mkval(T_FFI, (uint64_t)index);
}
int js_getffi(jsval_t val) {
if (vtype(val) != T_FFI) return -1;
return (int)vdata(val);
}
static jsval_t mkcoderef(jsval_t off, jsoff_t len) {
return mkval(T_CODEREF, (off & PROPREF_OFF_MASK) | ((jsval_t)(len & PROPREF_OFF_MASK) << PROPREF_KEY_SHIFT));
}
static jsval_t mkpropref(jsoff_t obj_off, jsoff_t key_off) {
if (obj_off <= PROPREF_SAFE_MASK && key_off <= PROPREF_SAFE_MASK) return mkval(
T_PROPREF, (obj_off & PROPREF_OFF_MASK) |
((jsval_t)(key_off & PROPREF_OFF_MASK) << PROPREF_KEY_SHIFT)
);
if (!propref_stack) utarray_new(propref_stack, &propref_icd);
static unsigned propref_next_slot = 0;
static unsigned propref_high_water = 0;
if (propref_next_slot == 0 && propref_high_water > 0) {
assert(propref_high_water <= PROPREF_STACK_SIZE && "propref_stack overflow");
}
propref_data_t entry = { obj_off, key_off };
if (propref_next_slot < utarray_len(propref_stack)) {
propref_data_t *slot = (propref_data_t *)utarray_eltptr(propref_stack, propref_next_slot);
*slot = entry;
} else utarray_push_back(propref_stack, &entry);
int idx = (int)propref_next_slot;
propref_next_slot = (propref_next_slot + 1) % PROPREF_STACK_SIZE;
if ((unsigned)idx > propref_high_water) propref_high_water = (unsigned)idx;
return mkval(T_PROPREF, PROPREF_STACK_FLAG | (uint64_t)idx);
}
static jsval_t mkprim_propref(jsval_t prim_val, jsoff_t key_off) {
if (!prim_propref_stack) utarray_new(prim_propref_stack, &prim_propref_icd);
static unsigned prim_propref_next_slot = 0;
static unsigned prim_propref_high_water = 0;
if (prim_propref_next_slot == 0 && prim_propref_high_water > 0) {
assert(prim_propref_high_water <= PRIM_PROPREF_STACK_SIZE && "prim_propref_stack overflow");
}
prim_propref_data_t entry = { prim_val, key_off };
if (prim_propref_next_slot < utarray_len(prim_propref_stack)) {
prim_propref_data_t *slot = (prim_propref_data_t *)utarray_eltptr(prim_propref_stack, prim_propref_next_slot);
*slot = entry;
} else utarray_push_back(prim_propref_stack, &entry);
int idx = (int)prim_propref_next_slot;
prim_propref_next_slot = (prim_propref_next_slot + 1) % PRIM_PROPREF_STACK_SIZE;
if ((unsigned)idx > prim_propref_high_water) prim_propref_high_water = (unsigned)idx;
return mkval(T_PROPREF, PROPREF_PRIM_FLAG | (uint64_t)idx);
}
static inline bool is_prim_propref(jsval_t v) {
if (vtype(v) != T_PROPREF) return false;
uint64_t data = v & NANBOX_DATA_MASK;
return (data & PROPREF_PRIM_FLAG) != 0;
}
static inline prim_propref_data_t *prim_propref_get(jsval_t v) {
uint64_t data = v & NANBOX_DATA_MASK;
if (!(data & PROPREF_PRIM_FLAG)) return NULL;
int idx = (int)(data & PROPREF_INDEX_MASK);
if (!prim_propref_stack || idx < 0 || idx >= (int)utarray_len(prim_propref_stack)) return NULL;
return (prim_propref_data_t *)utarray_eltptr(prim_propref_stack, (unsigned)idx);
}
inline size_t js_getbrk(struct js *js) {
return (size_t) js->brk;
}
static inline int is_body_end_tok(int tok) {
return body_end_tok[tok];
}
static inline bool is_assign(uint8_t tok) {
return tok >= TOK_ASSIGN && tok <= TOK_NULLISH_ASSIGN;
}
static inline bool is_identifier_like(uint8_t tok) {
return tok >= TOK_IDENTIFIER && tok < TOK_IDENT_LIKE_END;
}
static inline bool is_keyword_propname(uint8_t tok) {
return (tok >= TOK_ASYNC && tok <= TOK_GLOBAL_THIS) || tok == TOK_TYPEOF;
}
static inline bool is_contextual_keyword(uint8_t tok) {
return tok == TOK_FROM || tok == TOK_OF || tok == TOK_AS || tok == TOK_ASYNC;
}
static inline bool js_stack_overflow(struct js *js) {
volatile char marker;
uintptr_t curr = (uintptr_t)&marker;
mco_coro *coro = mco_running();
if (coro != NULL) {
uintptr_t stack_top = (uintptr_t)coro->stack_base + coro->stack_size;
size_t limit = coro->stack_size / 2;
size_t used = (stack_top > curr) ? (stack_top - curr) : (curr - stack_top);
return used > limit;
}
if (js->stack_limit == 0 || js->cstk == NULL) return false;
uintptr_t base = (uintptr_t)js->cstk;
size_t used = (base > curr) ? (base - curr) : (curr - base);
return used > js->stack_limit;
}
static inline bool is_valid_param_name(uint8_t tok) {
return tok == TOK_IDENTIFIER || is_contextual_keyword(tok);
}
static bool is_valid_arrow_param_tok(uint8_t tok) {
static const uint64_t bits[4] = {
0x000C000000000FCCull,
0x041CC0100BC00808ull,
0x0000000000800100ull,
0x0000000000000000ull
};
return (bits[tok >> 6] >> (tok & 63)) & 1;
}
static inline bool is_block_tok(uint8_t tok) {
static const uint64_t bits[4] = {
0x2108000000000110ull,
0x0000000000124075ull, 0, 0
};
return (bits[tok >> 6] >> (tok & 63)) & 1;
}
static inline bool is_asi_ok_tok(uint8_t tok) {
static const uint64_t bits[4] = {
0x0000000000000212ull, 0, 0, 0
};
return (bits[tok >> 6] >> (tok & 63)) & 1;
}
static inline bool is_unboxed_obj(struct js *js, jsval_t val, jsval_t expected_proto) {
if (vtype(val) != T_OBJ) return false;
if (vtype(get_slot(js, val, SLOT_PRIMITIVE)) != T_UNDEF) return false;
jsval_t proto = get_slot(js, 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;
}
typedef struct {
const char *code;
jsoff_t clen, pos;
uint8_t tok, consumed;
int token_stream_pos;
void *token_stream;
const char *token_stream_code;
} js_parse_state_t;
#define JS_SAVE_STATE(js, state) do { \
(state).code = (js)->code; \
(state).clen = (js)->clen; \
(state).pos = (js)->pos; \
(state).tok = (js)->tok; \
(state).consumed = (js)->consumed; \
(state).token_stream_pos = (js)->token_stream_pos; \
(state).token_stream = (js)->token_stream; \
(state).token_stream_code = (js)->token_stream_code; \
} while(0)
#define JS_RESTORE_STATE(js, state) do { \
(js)->code = (state).code; \
(js)->clen = (state).clen; \
(js)->pos = (state).pos; \
(js)->tok = (state).tok; \
(js)->consumed = (state).consumed; \
(js)->token_stream_pos = (state).token_stream_pos; \
(js)->token_stream = (state).token_stream; \
(js)->token_stream_code = (state).token_stream_code; \
} while(0)
static size_t strstring(struct js *js, jsval_t value, char *buf, size_t len);
static size_t strkey(struct js *js, jsval_t value, char *buf, size_t len);
static inline jsoff_t loadoff(struct js *js, jsoff_t off) {
assert(off + sizeof(jsoff_t) <= js->brk); jsoff_t val;
memcpy(&val, &js->mem[off], sizeof(val)); return val;
}
static bool is_arr_off(struct js *js, jsoff_t off) {
return (loadoff(js, off) & ARRMASK) != 0;
}
static bool is_func_off(struct js *js, jsoff_t off) {
jsval_t obj = mkval(T_OBJ, off);
jsval_t code = get_slot(js, obj, SLOT_CODE);
jsval_t cfunc = get_slot(js, obj, SLOT_CFUNC);
return vtype(code) != T_UNDEF || vtype(cfunc) != T_UNDEF;
}
static inline jsval_t loadval(struct js *js, jsoff_t off) {
return *(jsval_t *)(&js->mem[off]);
}
static jsval_t upper(struct js *js, jsval_t scope) {
return mkval(T_OBJ, loadoff(js, (jsoff_t) (vdata(scope) + sizeof(jsoff_t))));
}
static jsoff_t vstrlen(struct js *js, jsval_t v) {
jsoff_t off = (jsoff_t) vdata(v);
jsoff_t header = loadoff(js, off);
if (header & ROPE_FLAG) {
return offtolen(header & ~(ROPE_FLAG | (ROPE_DEPTH_MASK << ROPE_DEPTH_SHIFT)));
}
return offtolen(header);
}
#define EXPECT(_tok, ...) \
if (next(js) != _tok) { \
__VA_ARGS__; \
return js_mkerr_typed(js, JS_ERR_SYNTAX, "parse error"); \
} else js->consumed = 1
#define EXPECT_IDENT(...) \
if (!is_valid_param_name(next(js))) { \
__VA_ARGS__; \
return js_mkerr_typed(js, JS_ERR_SYNTAX, "identifier expected"); \
} else js->consumed = 1
static bool is_digit(int c);
static bool is_proxy(struct js *js, jsval_t obj);
static jsval_t proxy_read_target(struct js *js, jsval_t obj);
static jsoff_t proxy_aware_length(struct js *js, jsval_t obj);
static jsval_t proxy_aware_get_elem(struct js *js, jsval_t obj, const char *key, size_t key_len);
static bool bigint_is_zero(struct js *js, jsval_t v);
static jsoff_t get_dense_buf(struct js *js, jsval_t arr);
static jsoff_t dense_length(struct js *js, jsoff_t doff);
static jsoff_t get_array_length(struct js *js, jsval_t arr);
static jsval_t arr_get(struct js *js, jsval_t arr, jsoff_t idx);
static bool arr_has(struct js *js, jsval_t arr, jsoff_t idx);
static bool streq(const char *buf, size_t len, const char *p, size_t n);
static bool is_this_loop_continue_target(int depth_at_entry);
static bool code_has_function_decl(const char *code, size_t len);
static bool parse_func_params(struct js *js, uint8_t *flags, int *out_count);
static bool try_dynamic_setter(struct js *js, jsval_t obj, const char *key, size_t key_len, jsval_t value);
static size_t strbigint(struct js *js, jsval_t value, char *buf, size_t len);
static size_t tostr(struct js *js, jsval_t value, char *buf, size_t len);
static size_t strpromise(struct js *js, jsval_t value, char *buf, size_t len);
static size_t js_to_pcre2_pattern(const char *src, size_t src_len, char *dst, size_t dst_size);
static double js_to_number(struct js *js, jsval_t arg);
static jsval_t js_stmt_impl(struct js *js);
static jsval_t js_expr(struct js *js);
static jsval_t js_call_valueOf(struct js *js, jsval_t value);
static jsval_t js_call_toString(struct js *js, jsval_t value);
static jsval_t js_eval_slice(struct js *js, jsoff_t off, jsoff_t len);
static jsval_t js_eval_str(struct js *js, const char *code, jsoff_t len);
static jsval_t js_stmt(struct js *js);
static jsval_t js_assignment(struct js *js);
static jsval_t js_arrow_func(struct js *js, jsoff_t params_start, jsoff_t params_end, bool is_async);
static jsval_t js_async_arrow_paren(struct js *js);
static jsval_t js_var_decl(struct js *js);
static jsval_t do_op(struct js *, uint8_t op, jsval_t l, jsval_t r);
static jsval_t do_instanceof(struct js *js, jsval_t l, jsval_t r);
static jsval_t do_in(struct js *js, jsval_t l, jsval_t r);
static inline bool is_slot_prop(jsoff_t header);
static inline jsoff_t next_prop(jsoff_t header);
static jsval_t js_import_stmt(struct js *js);
static jsval_t js_export_stmt(struct js *js);
static jsval_t builtin_Object(struct js *js, jsval_t *args, int nargs);
static jsval_t builtin_promise_then(struct js *js, jsval_t *args, int nargs);
static jsval_t proxy_get(struct js *js, jsval_t proxy, const char *key, size_t key_len);
static jsval_t proxy_set(struct js *js, jsval_t proxy, const char *key, size_t key_len, jsval_t value);
static jsval_t proxy_has(struct js *js, jsval_t proxy, const char *key, size_t key_len);
static jsval_t proxy_delete(struct js *js, jsval_t proxy, const char *key, size_t key_len);
static uint8_t next_raw(struct js *js);
static inline bool push_this(jsval_t this_value);
static inline jsval_t pop_this(void);
static jsval_t get_prototype_for_type(struct js *js, uint8_t type);
static jsval_t get_ctor_proto(struct js *js, const char *name, size_t len);
static jsoff_t lkp_interned(struct js *js, jsval_t obj, const char *search_intern, size_t len);
static descriptor_entry_t *lookup_descriptor(struct js *js, jsoff_t obj_off, const char *key, size_t klen);
static const char *bigint_digits(struct js *js, jsval_t v, size_t *len);
typedef struct { jsval_t handle; bool is_new; } ctor_t;
static ctor_t get_constructor(struct js *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;
}
static jsval_t unwrap_primitive(struct js *js, jsval_t val) {
if (vtype(val) != T_OBJ) return val;
jsval_t prim = get_slot(js, val, SLOT_PRIMITIVE);
if (vtype(prim) == T_UNDEF) return val;
return prim;
}
static jsval_t to_string_val(struct js *js, jsval_t val) {
uint8_t t = vtype(val);
if (t == T_STR) return val;
if (t == T_OBJ) {
jsval_t prim = get_slot(js, val, SLOT_PRIMITIVE);
if (vtype(prim) == T_STR) return prim;
}
return js_call_toString(js, val);
}
bool js_truthy(struct js *js, jsval_t v) {
static const void *dispatch[] = {
[T_OBJ] = &&l_true,
[T_FUNC] = &&l_true,
[T_ARR] = &&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 jsval_t bigint_from_u64(struct js *js, uint64_t value) {
char buf[32];
size_t len = uint_to_str(buf, sizeof(buf), value);
return js_mkbigint(js, buf, len, false);
}
static jsval_t stringify_stack[MAX_STRINGIFY_DEPTH];
static int stringify_depth = 0;
static int stringify_indent = 0;
static jsval_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(struct js *js, jsval_t value);
static int find_multiref(jsval_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(jsval_t obj) {
for (int i = 0; i < stringify_depth; i++) {
if (stringify_stack[i] == obj) return true;
}
return false;
}
static void mark_multiref(jsval_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(struct js *js, jsval_t obj) {
if (is_on_stack(obj)) {
mark_multiref(obj);
return;
}
if (stringify_depth >= MAX_STRINGIFY_DEPTH) return;
stringify_stack[stringify_depth++] = obj;
jsoff_t next = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (!is_slot_prop(header)) {
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsval_t val = loadval(js, next + (jsoff_t) (sizeof(next) + sizeof(koff)));
scan_refs(js, val);
}
next = next_prop(header);
}
jsval_t proto_val = get_slot(js, obj, SLOT_PROTO);
if (vtype(proto_val) == T_OBJ) scan_refs(js, proto_val);
stringify_depth--;
}
static void scan_arr_refs(struct js *js, jsval_t obj) {
if (is_on_stack(obj)) {
mark_multiref(obj);
return;
}
if (stringify_depth >= MAX_STRINGIFY_DEPTH) return;
stringify_stack[stringify_depth++] = obj;
jsoff_t next = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (!is_slot_prop(header)) {
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsval_t val = loadval(js, next + (jsoff_t) (sizeof(next) + sizeof(koff)));
scan_refs(js, val);
}
next = next_prop(header);
}
stringify_depth--;
}
static void scan_func_refs(struct js *js, jsval_t value) {
jsval_t func_obj = mkval(T_OBJ, vdata(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;
jsoff_t next = loadoff(js, (jsoff_t) vdata(func_obj)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (!is_slot_prop(header)) {
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsval_t val = loadval(js, next + (jsoff_t) (sizeof(next) + sizeof(koff)));
scan_refs(js, val);
}
next = next_prop(header);
}
stringify_depth--;
}
static void scan_refs(struct js *js, jsval_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(jsval_t obj) {
if (is_on_stack(obj)) {
int ref = find_multiref(obj);
return ref ? ref : -1;
}
return 0;
}
static bool is_circular(jsval_t obj) {
return is_on_stack(obj);
}
static int get_self_ref(jsval_t obj) {
return find_multiref(obj);
}
static void push_stringify(jsval_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;
}
static inline jsoff_t get_prop_koff(struct js *js, jsoff_t prop) {
return loadoff(js, prop + (jsoff_t) sizeof(prop));
}
static void get_prop_key(struct js *js, jsoff_t prop, const char **key, jsoff_t *klen) {
jsoff_t koff = get_prop_koff(js, prop);
*klen = offtolen(loadoff(js, koff));
*key = (char *) &js->mem[koff + sizeof(koff)];
}
static jsval_t get_prop_val(struct js *js, jsoff_t prop) {
jsoff_t koff = get_prop_koff(js, prop);
return loadval(js, prop + (jsoff_t) (sizeof(prop) + sizeof(koff)));
}
const char *get_str_prop(struct js *js, jsval_t obj, const char *key, jsoff_t klen, jsoff_t *out_len) {
jsoff_t off = lkp(js, obj, key, klen);
if (off <= 0) return NULL;
jsval_t v = resolveprop(js, mkval(T_PROP, off));
if (vtype(v) != T_STR) return NULL;
return (const char *)&js->mem[vstr(js, v, out_len)];
}
static bool is_small_array(struct js *js, jsval_t obj, int *elem_count) {
jsoff_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 (jsoff_t i = 0; i < length; i++) {
jsval_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, jsoff_t klen) {
if (klen == 0 || (klen > 1 && key[0] == '0')) return false;
for (jsoff_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, jsoff_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;
}
static jsoff_t get_array_length(struct js *js, jsval_t arr) {
jsoff_t dense_len = 0;
jsoff_t doff = get_dense_buf(js, arr);
if (doff) dense_len = dense_length(js, doff);
jsoff_t prop_len = 0;
jsoff_t off = lkp_interned(js, arr, INTERN_LENGTH, 6);
if (off) {
jsval_t val = resolveprop(js, mkval(T_PROP, off));
if (vtype(val) == T_NUM) prop_len = (jsoff_t) tod(val);
}
return dense_len > prop_len ? dense_len : prop_len;
}
static jsval_t get_obj_ctor(struct js *js, jsval_t obj) {
jsval_t ctor = get_slot(js, obj, SLOT_CTOR);
if (vtype(ctor) == T_FUNC) return ctor;
jsval_t proto = get_slot(js, obj, SLOT_PROTO);
if (vtype(proto) != T_OBJ) return js_mkundef();
jsoff_t off = lkp_interned(js, proto, INTERN_CONSTRUCTOR, 11);
return off ? resolveprop(js, mkval(T_PROP, off)) : js_mkundef();
}
static const char *get_func_name(struct js *js, jsval_t func, jsoff_t *out_len) {
if (vtype(func) != T_FUNC) return NULL;
jsoff_t off = lkp(js, mkval(T_OBJ, vdata(func)), "name", 4);
if (!off) return NULL;
jsval_t name = resolveprop(js, mkval(T_PROP, off));
if (vtype(name) != T_STR) return NULL;
jsoff_t str_off = vstr(js, name, out_len);
return (const char *) &js->mem[str_off];
}
static const char *get_class_name(struct js *js, jsval_t obj, jsoff_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 == (jsoff_t)strlen(skip) && memcmp(name, skip, *out_len) == 0) return NULL;
return name;
}
static inline jsoff_t dense_iterable_length(struct js *js, jsval_t obj) {
jsoff_t doff = get_dense_buf(js, obj);
return doff ? dense_length(js, doff) : 0;
}
static size_t strarr(struct js *js, jsval_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);
jsoff_t first = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | FLAGMASK);
jsoff_t length = get_array_length(js, obj);
jsoff_t d_len = dense_iterable_length(js, obj);
jsoff_t iter_len = (d_len >= length) ? length : d_len;
jsoff_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 (jsoff_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);
jsval_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;
}
for (jsoff_t p = first; p < js->brk && p != 0; p = next_prop(loadoff(js, p))) {
jsoff_t header = loadoff(js, p);
if (is_slot_prop(header)) continue;
const char *key; jsoff_t klen;
get_prop_key(js, p, &key, &klen);
if (streq(key, klen, "length", 6)) 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);
if (is_array_index(key, klen)) {
n += tostr(js, get_prop_val(js, p), 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, get_prop_val(js, p), 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 array_to_string(struct js *js, jsval_t obj, char *buf, size_t len) {
if (is_circular(obj)) return cpy(buf, len, "", 0);
push_stringify(obj); size_t n = 0;
jsoff_t length = get_array_length(js, obj);
jsoff_t d_len = dense_iterable_length(js, obj);
jsoff_t iter_len = (d_len >= length) ? length : d_len;
for (jsoff_t i = 0; i < iter_len; i++) {
if (i > 0) n += cpy(buf + n, REMAIN(n, len), ",", 1);
jsval_t val = arr_get(js, obj, i);
if (arr_has(js, obj, i)) {
uint8_t vt = vtype(val);
if (vt == T_STR) {
jsoff_t slen, soff = vstr(js, val, &slen);
n += cpy(buf + n, REMAIN(n, len), (const char *)&js->mem[soff], slen);
} else if (vt != T_UNDEF && vt != T_NULL) n += tostr(js, val, buf + n, REMAIN(n, len));
}
}
jsoff_t first = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | FLAGMASK);
for (jsoff_t p = first; p < js->brk && p != 0; p = next_prop(loadoff(js, p))) {
jsoff_t header = loadoff(js, p);
if (is_slot_prop(header)) continue;
const char *key; jsoff_t klen;
get_prop_key(js, p, &key, &klen);
if (streq(key, klen, "length", 6)) continue;
if (!is_array_index(key, klen)) continue;
if (n > 0) n += cpy(buf + n, REMAIN(n, len), ",", 1);
jsval_t val = get_prop_val(js, p);
uint8_t vt = vtype(val);
if (vt == T_STR) {
jsoff_t slen, soff = vstr(js, val, &slen);
n += cpy(buf + n, REMAIN(n, len), (const char *)&js->mem[soff], slen);
} else if (vt != T_UNDEF && vt != T_NULL) n += tostr(js, val, buf + n, REMAIN(n, len));
}
pop_stringify();
return n;
}
static size_t strdate(struct js *js, jsval_t obj, char *buf, size_t len) {
jsval_t time_val = js_get_slot(js, obj, SLOT_DATA);
if (vtype(time_val) != T_NUM) return cpy(buf, len, "Invalid Date", 12);
double timestamp_ms = tod(time_val);
time_t timestamp_sec = (time_t)(timestamp_ms / 1000.0);
struct tm *tm_local = localtime(&timestamp_sec);
if (!tm_local) return cpy(buf, len, "Invalid Date", 12);
char date_part[64];
strftime(date_part, sizeof(date_part), "%a %b %d %Y %H:%M:%S", tm_local);
time_t now = timestamp_sec;
struct tm *gm = gmtime(&now);
struct tm local_copy = *tm_local;
time_t local_time = mktime(&local_copy);
time_t gmt_time = mktime(gm);
long offset_sec = (long)difftime(local_time, gmt_time);
int offset_hours = (int)(offset_sec / 3600);
int offset_mins = (int)(labs(offset_sec) % 3600) / 60;
char tz_name[64];
strftime(tz_name, sizeof(tz_name), "%Z", tm_local);
return (size_t) snprintf(buf, len, "%s GMT%+03d%02d (%s)", date_part, offset_hours, offset_mins, tz_name);
}
static bool is_valid_identifier(const char *str, jsoff_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 (jsoff_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(struct js *js, jsval_t value, char *buf, size_t len) {
jsoff_t slen, off = vstr(js, value, &slen);
const char *str = (const char *) &js->mem[off];
const char *sym_desc = get_symbol_description_from_key(str, slen);
if (sym_desc) {
size_t n = 0;
n += cpy(buf + n, REMAIN(n, len), "[", 1);
n += cpy(buf + n, REMAIN(n, len), sym_desc, strlen(sym_desc));
n += cpy(buf + n, REMAIN(n, len), "]", 1);
return n;
}
if (is_valid_identifier(str, slen)) {
return cpy(buf, len, str, slen);
}
return strstring(js, value, buf, len);
}
static bool is_small_object(struct js *js, jsval_t obj, int *prop_count) {
int count = 0;
bool has_nested = false;
jsoff_t obj_off = (jsoff_t)vdata(obj);
jsoff_t next = loadoff(js, obj_off) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (is_slot_prop(header)) { next = next_prop(header); continue; }
const char *key; jsoff_t klen;
get_prop_key(js, next, &key, &klen);
const char *tag_sym_key = get_toStringTag_sym_key();
bool should_hide = streq(key, klen, STR_PROTO, STR_PROTO_LEN) || streq(key, klen, tag_sym_key, strlen(tag_sym_key));
if (!should_hide) {
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, key, klen);
if (desc && !desc->enumerable) should_hide = true;
}
if (!should_hide) {
jsval_t val = get_prop_val(js, next);
uint8_t t = vtype(val);
if (t == T_OBJ || t == T_ARR || t == T_FUNC) has_nested = true;
count++;
}
next = next_prop(header);
}
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(struct js *js, jsval_t obj, char *buf, size_t len) {
jsval_t obj_proto = js_get_proto(js, obj);
jsval_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);
}
const char *tostring_tag_key = get_toStringTag_sym_key();
jsoff_t tag_off = lkp_proto(js, obj, tostring_tag_key, strlen(tostring_tag_key));
bool is_map = false, is_set = false, is_arraybuffer = false;
jsoff_t tlen = 0, toff = 0;
const char *tag_str = NULL;
int prop_count = 0;
bool inline_mode = false;
if (tag_off == 0) goto print_plain_object;
jsval_t tag_val = resolveprop(js, mkval(T_PROP, tag_off));
if (vtype(tag_val) != T_STR) goto print_plain_object;
toff = vstr(js, tag_val, &tlen);
tag_str = (const char *) &js->mem[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);
jsval_t ta_slot = js_get_slot(js, 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;
jsval_t proto = js_get_proto(js, obj);
jsval_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) {
jsval_t buf_val = js_get_slot(js, 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) {
jsval_t dv_data_val = js_get_slot(js, 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) {
jsval_t map_val = js_get_slot(js, 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);
size_t key_len = strlen(entry->key);
n += cpy(buf + n, REMAIN(n, len), "'", 1);
n += cpy(buf + n, REMAIN(n, len), entry->key, key_len);
n += cpy(buf + n, REMAIN(n, len), "'", 1);
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) {
jsval_t set_val = js_get_slot(js, 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;
}
print_tagged_object:
n += cpy(buf + n, REMAIN(n, len), "Object [", 8);
n += cpy(buf + n, REMAIN(n, len), (const char *) &js->mem[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);
jsval_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;
jsoff_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;
jsval_t proto_proto = js_get_proto(js, proto_val);
jsval_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++;
jsoff_t next = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | FLAGMASK);
bool first = true;
jsoff_t obj_off = (jsoff_t)vdata(obj);
int prop_capacity = 64;
jsoff_t *prop_offsets = malloc(prop_capacity * sizeof(jsoff_t));
int num_props = 0;
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (is_slot_prop(header)) { next = next_prop(header); continue; }
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *) &js->mem[koff + sizeof(koff)];
const char *tag_sym_key = get_toStringTag_sym_key();
bool should_hide = streq(key, klen, STR_PROTO, STR_PROTO_LEN) || streq(key, klen, tag_sym_key, strlen(tag_sym_key));
if (!should_hide) {
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, key, klen);
if (desc && !desc->enumerable) should_hide = true;
}
if (!should_hide) {
if (num_props >= prop_capacity) {
prop_capacity *= 2;
prop_offsets = realloc(prop_offsets, prop_capacity * sizeof(jsoff_t));
}
prop_offsets[num_props++] = next;
}
next = next_prop(header);
}
for (int i = num_props - 1; i >= 0; i--) {
jsoff_t prop = prop_offsets[i];
jsoff_t koff = loadoff(js, prop + (jsoff_t) sizeof(prop));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *) &js->mem[koff + sizeof(koff)];
jsval_t val = loadval(js, prop + (jsoff_t) (sizeof(prop) + sizeof(koff)));
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(js, mkval(T_STR, koff), buf + n, REMAIN(n, len));
n += cpy(buf + n, REMAIN(n, len), ": ", 2);
n += tostr(js, val, buf + n, REMAIN(n, len));
}
}
free(prop_offsets);
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(jsval_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 bool is_rope(struct js *js, jsval_t value) {
jsoff_t off = (jsoff_t) vdata(value);
jsoff_t header = loadoff(js, off);
return (header & ROPE_FLAG) != 0;
}
static inline jsoff_t rope_len(struct js *js, jsval_t value) {
jsoff_t off = (jsoff_t) vdata(value);
jsoff_t header = loadoff(js, off);
return offtolen(header & ~(ROPE_FLAG | (ROPE_DEPTH_MASK << ROPE_DEPTH_SHIFT)));
}
static inline uint8_t rope_depth(struct js *js, jsval_t value) {
jsoff_t off = (jsoff_t) vdata(value);
jsoff_t header = loadoff(js, off);
return (uint8_t)((header >> ROPE_DEPTH_SHIFT) & ROPE_DEPTH_MASK);
}
static inline jsval_t rope_left(struct js *js, jsval_t value) {
jsoff_t off = (jsoff_t) vdata(value);
return loadval(js, off + offsetof(rope_node_t, left));
}
static inline jsval_t rope_right(struct js *js, jsval_t value) {
jsoff_t off = (jsoff_t) vdata(value);
return loadval(js, off + offsetof(rope_node_t, right));
}
static inline jsval_t rope_cached_flat(struct js *js, jsval_t value) {
jsoff_t off = (jsoff_t) vdata(value);
return loadval(js, off + offsetof(rope_node_t, cached));
}
static inline void rope_set_cached_flat(struct js *js, jsval_t rope, jsval_t flat) {
jsoff_t off = (jsoff_t) vdata(rope);
saveval(js, off + offsetof(rope_node_t, cached), flat);
}
static void rope_flatten_into(struct js *js, jsval_t str, char *dest, jsoff_t *pos) {
if (vtype(str) != T_STR) return;
if (!is_rope(js, str)) {
jsoff_t slen;
jsoff_t soff = (jsoff_t) vdata(str);
slen = offtolen(loadoff(js, soff));
memcpy(dest + *pos, &js->mem[soff + sizeof(jsoff_t)], slen);
*pos += slen; return;
}
jsval_t cached = rope_cached_flat(js, str);
if (vtype(cached) == T_STR && !is_rope(js, cached)) {
jsoff_t clen;
jsoff_t coff = (jsoff_t) vdata(cached);
clen = offtolen(loadoff(js, coff));
memcpy(dest + *pos, &js->mem[coff + sizeof(jsoff_t)], clen);
*pos += clen; return;
}
jsval_t stack[ROPE_MAX_DEPTH + 8];
int sp = 0; stack[sp++] = str;
while (sp > 0) {
jsval_t node = stack[--sp];
if (vtype(node) != T_STR) continue;
if (!is_rope(js, node)) {
jsoff_t slen;
jsoff_t soff = (jsoff_t) vdata(node);
slen = offtolen(loadoff(js, soff));
memcpy(dest + *pos, &js->mem[soff + sizeof(jsoff_t)], slen);
*pos += slen; continue;
}
jsval_t c = rope_cached_flat(js, node);
if (vtype(c) == T_STR && !is_rope(js, c)) {
jsoff_t clen;
jsoff_t coff = (jsoff_t) vdata(c);
clen = offtolen(loadoff(js, coff));
memcpy(dest + *pos, &js->mem[coff + sizeof(jsoff_t)], clen);
*pos += clen; continue;
}
if (sp + 2 <= ROPE_MAX_DEPTH + 8) {
stack[sp++] = rope_right(js, node);
stack[sp++] = rope_left(js, node);
}
}
}
static jsval_t rope_flatten(struct js *js, jsval_t rope) {
if (!is_rope(js, rope)) return rope;
jsval_t cached = rope_cached_flat(js, rope);
if (vtype(cached) == T_STR && !is_rope(js, cached)) return cached;
jsoff_t total_len = rope_len(js, rope);
char *buf = (char *)ant_calloc(total_len + 1);
if (!buf) return js_mkerr(js, "oom");
jsoff_t pos = 0;
rope_flatten_into(js, rope, buf, &pos);
buf[pos] = '\0';
jsval_t flat = js_mkstr(js, buf, pos);
free(buf);
if (!is_err(flat)) {
rope_set_cached_flat(js, rope, flat);
}
return flat;
}
jsoff_t vstr(struct js *js, jsval_t value, jsoff_t *len) {
jsoff_t off = (jsoff_t) vdata(value);
jsoff_t header = loadoff(js, off);
if (header & ROPE_FLAG) {
jsval_t flat = rope_flatten(js, value);
if (is_err(flat)) {
if (len) *len = 0;
return 0;
}
off = (jsoff_t) vdata(flat);
header = loadoff(js, off);
}
if (len) *len = offtolen(header);
return (jsoff_t) (off + sizeof(off));
}
static size_t strstring(struct js *js, jsval_t value, char *buf, size_t len) {
jsoff_t slen, off = vstr(js, value, &slen);
const char *str = (const char *) &js->mem[off];
size_t n = 0;
n += cpy(buf + n, REMAIN(n, len), "'", 1);
for (jsoff_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;
}
static const char *intern_string(const char *str, size_t len) {
uint64_t h = hash_key(str, len);
uint32_t bucket = (uint32_t)(h & (ANT_LIMIT_SIZE_CACHE - 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, jsoff_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;
if (klen >= 9 && key[2] == 's' && key[3] == 'y' && key[4] == 'm' && key[5] == '_' && key[klen-1] == '_' && key[klen-2] == '_') return true;
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 },
};
static size_t strfunc(struct js *js, jsval_t value, char *buf, size_t len) {
jsoff_t name_len = 0;
const char *name = get_func_name(js, value, &name_len);
jsval_t func_obj = mkval(T_OBJ, vdata(value));
jsval_t code_slot = get_slot(js, func_obj, SLOT_CODE);
jsval_t builtin_slot = get_slot(js, func_obj, SLOT_BUILTIN);
jsval_t async_slot = get_slot(js, 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) {
jsval_t cfunc_slot = get_slot(js, 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;
jsval_t proto = get_slot(js, func_obj, SLOT_PROTO);
uint8_t pt = vtype(proto);
if (pt != T_OBJ && pt != T_FUNC) return n;
jsoff_t ctor_off = lkp(js, proto, "constructor", 11);
if (ctor_off == 0) return n;
jsval_t ctor = resolveprop(js, mkval(T_PROP, ctor_off));
uint8_t ct = vtype(ctor);
if (ct != T_FUNC && ct != T_CFUNC) return n;
jsoff_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(struct js *js, jsval_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: {
const char *msg = js->errmsg ? js->errmsg : "Error";
size_t mlen = strlen(msg);
return cpy(buf, len, msg, mlen);
}
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()");
}
case T_PROP: return (size_t) snprintf(buf, len, "PROP@%lu", (unsigned long) vdata(value));
default: return (size_t) snprintf(buf, len, "VTYPE%d", vtype(value));
}
}
static char *tostr_alloc(struct js *js, jsval_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(struct js *js, jsval_t value, char *stack_buf, size_t stack_size) {
js_cstr_t out = { .ptr = "", .len = 0, .needs_free = false };
if (is_err(value)) {
out.ptr = js->errmsg ? js->errmsg : "";
out.len = strlen(out.ptr);
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;
}
}
jsval_t js_tostring_val(struct js *js, jsval_t value) {
uint8_t t = vtype(value);
char *buf; size_t len, buflen;
static const void *jump_table[] = {
[T_OBJ] = &&L_OBJ, [T_PROP] = &&L_DEFAULT, [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_CODEREF] = &&L_DEFAULT,
[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_PROPREF] = &&L_DEFAULT,
[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);
jsval_t result = js_mkstr(js, buf, len);
free(buf); return result;
}
L_BIGINT: {
bigint_digits(js, value, &buflen);
buf = (char *)ant_calloc(buflen + 2);
len = strbigint(js, value, buf, buflen + 2);
jsval_t result = js_mkstr(js, buf, len);
free(buf); return result;
}
L_DEFAULT: {
buf = (char *)ant_calloc(64);
len = tostr(js, value, buf, 64);
jsval_t result = js_mkstr(js, buf, len);
free(buf); return result;
}
}
const char *js_str(struct js *js, jsval_t value) {
if (is_err(value)) return js->errmsg;
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 "";
}
jsval_t str = js_mkstr(js, buf, len);
free(buf);
if (is_err(str)) return "";
return (const char *)&js->mem[vdata(str) + sizeof(jsoff_t)];
}
static bool js_try_grow_memory(struct js *js, size_t needed) {
if (!js->owns_mem) return false;
if (js->max_size == 0) return false;
size_t current = (size_t)js->size;
size_t required = current + needed;
size_t new_mem_size = ((required + ARENA_GROW_INCREMENT - 1) / ARENA_GROW_INCREMENT) * ARENA_GROW_INCREMENT;
if (new_mem_size > (size_t)js->max_size) new_mem_size = (size_t)js->max_size;
if (new_mem_size <= current) return false;
if (ant_arena_commit(js->mem, js->size, new_mem_size) != 0) return false;
js->size = (jsoff_t)(new_mem_size / 8U * 8U);
return true;
}
static inline bool js_has_space(struct js *js, size_t size) {
return js->brk + size <= js->size;
}
static bool js_ensure_space(struct js *js, size_t size) {
if (js_has_space(js, size)) return true;
if (js_try_grow_memory(js, size) && js_has_space(js, size)) return true;
js->needs_gc = true;
if (js_has_space(js, size)) return true;
if (js_try_grow_memory(js, size) && js_has_space(js, size)) return true;
return false;
}
static void js_track_allocation(struct js *js, size_t size) {
js->brk += (jsoff_t) size;
js->gc_alloc_since += (jsoff_t) size;
jsoff_t threshold = js->brk / 2;
if (threshold < 4 * 1024 * 1024) threshold = 4 * 1024 * 1024;
if (js->gc_alloc_since > threshold) js->needs_gc = true;
}
static inline jsoff_t js_alloc(struct js *js, size_t size) {
size = align64((jsoff_t) size);
if (!js_ensure_space(js, size)) return ~(jsoff_t) 0;
jsoff_t ofs = js->brk;
js_track_allocation(js, size);
return ofs;
}
static jsoff_t dense_alloc(struct js *js, jsoff_t capacity) {
jsoff_t size = sizeof(jsoff_t) * 2 + sizeof(jsval_t) * capacity;
jsoff_t off = js_alloc(js, size);
if (off == (jsoff_t)~0) return 0;
saveoff(js, off, capacity);
saveoff(js, off + sizeof(jsoff_t), 0);
for (jsoff_t i = 0; i < capacity; i++) saveval(
js, off + sizeof(jsoff_t) * 2 + sizeof(jsval_t) * i, T_EMPTY
);
return off;
}
static inline jsoff_t get_dense_buf(struct js *js, jsval_t arr) {
jsval_t slot = get_slot(js, arr, SLOT_DENSE_BUF);
if (vtype(slot) == T_UNDEF) return 0;
return (jsoff_t) tod(slot);
}
static inline jsoff_t get_dense_buf_off(struct js *js, jsoff_t obj_off) {
return get_dense_buf(js, mkval(T_ARR, (uint64_t)obj_off));
}
static inline jsoff_t dense_capacity(struct js *js, jsoff_t doff) {
return loadoff(js, doff);
}
static inline jsoff_t dense_length(struct js *js, jsoff_t doff) {
return loadoff(js, doff + sizeof(jsoff_t));
}
static inline void dense_set_length(struct js *js, jsoff_t doff, jsoff_t len) {
saveoff(js, doff + sizeof(jsoff_t), len);
}
static inline jsval_t dense_get(struct js *js, jsoff_t doff, jsoff_t idx) {
return loadval(js, doff + sizeof(jsoff_t) * 2 + sizeof(jsval_t) * idx);
}
static inline void dense_set(struct js *js, jsoff_t doff, jsoff_t idx, jsval_t val) {
saveval(js, doff + sizeof(jsoff_t) * 2 + sizeof(jsval_t) * idx, val);
}
static jsoff_t dense_grow(struct js *js, jsval_t arr, jsoff_t needed) {
jsoff_t doff = get_dense_buf(js, arr);
jsoff_t old_cap = doff ? dense_capacity(js, doff) : 0;
jsoff_t old_len = doff ? dense_length(js, doff) : 0;
jsoff_t new_cap = old_cap ? old_cap : JS_DENSE_INITIAL_CAP;
while (new_cap < needed) new_cap *= 2;
jsoff_t new_doff = dense_alloc(js, new_cap);
if (new_doff == 0) return 0;
if (doff && old_len > 0) memcpy(
&js->mem[new_doff + sizeof(jsoff_t) * 2],
&js->mem[doff + sizeof(jsoff_t) * 2],
sizeof(jsval_t) * old_len
);
dense_set_length(js, new_doff, old_len);
set_slot(js, arr, SLOT_DENSE_BUF, tov((double)new_doff));
return new_doff;
}
static inline jsval_t arr_get(struct js *js, jsval_t arr, jsoff_t idx) {
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
jsoff_t len = dense_length(js, doff);
if (idx < len) {
jsval_t v = dense_get(js, doff, idx);
if (!is_empty_slot(v)) return v;
return js_mkundef();
}
}
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)idx);
jsoff_t prop = lkp(js, arr, idxstr, idxlen);
return prop ? resolveprop(js, mkval(T_PROP, prop)) : js_mkundef();
}
static inline void arr_set(struct js *js, jsval_t arr, jsoff_t idx, jsval_t val) {
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
jsoff_t len = dense_length(js, doff);
if (idx < len) {
dense_set(js, doff, idx, val);
return;
}
jsoff_t density_limit = len > 0 ? len * 4 : 64;
if (idx >= density_limit) goto sparse;
jsoff_t cap = dense_capacity(js, doff);
if (idx >= cap) {
doff = dense_grow(js, arr, idx + 1);
if (doff == 0) goto sparse;
}
for (jsoff_t i = len; i < idx; i++) {
jsval_t v = dense_get(js, doff, i);
if (!is_empty_slot(v) && vtype(v) == T_UNDEF) dense_set(js, doff, i, T_EMPTY);
}
dense_set(js, doff, idx, val);
dense_set_length(js, doff, idx + 1);
return;
}
sparse:;
char idxstr[24];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)idx);
jsval_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, arr, key, val);
}
static inline bool arr_has(struct js *js, jsval_t arr, jsoff_t idx) {
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
jsoff_t len = dense_length(js, doff);
if (idx < len) return !is_empty_slot(dense_get(js, doff, idx));
}
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(struct js *js, jsval_t arr, jsoff_t idx) {
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
jsoff_t len = dense_length(js, doff);
if (idx < len) dense_set(js, doff, idx, T_EMPTY);
return;
}
char idxstr[16];
uint_to_str(idxstr, sizeof(idxstr), (unsigned)idx);
js_del(js, arr, idxstr);
}
static inline jsoff_t dense_arr_length(struct js *js, jsval_t arr) {
jsoff_t doff = get_dense_buf(js, arr);
if (doff) return dense_length(js, doff);
return 0;
}
static jsval_t mkentity(struct js *js, jsoff_t b, const void *buf, size_t len) {
jsoff_t ofs = js_alloc(js, len + sizeof(b));
if (ofs == (jsoff_t) ~0) return js_mkerr(js, "oom");
memcpy(&js->mem[ofs], &b, sizeof(b));
if (buf != NULL) {
size_t copy_len = ((b & 3) == T_STR && len > 0) ? len - 1 : len;
memmove(&js->mem[ofs + sizeof(b)], buf, copy_len);
}
if ((b & 3) == T_STR) js->mem[ofs + sizeof(b) + len - 1] = 0;
return mkval(b & 3, ofs);
}
jsval_t js_mkstr(struct js *js, const void *ptr, size_t len) {
jsoff_t n = (jsoff_t) (len + 1);
return mkentity(js, (jsoff_t) ((n << 3) | T_STR), ptr, n);
}
static jsval_t js_mkrope(struct js *js, jsval_t left, jsval_t right, jsoff_t total_len, uint8_t depth) {
jsoff_t ofs = js_alloc(js, sizeof(rope_node_t));
if (ofs == (jsoff_t) ~0) return js_mkerr(js, "oom");
jsoff_t header = ((total_len + 1) << 3) | T_STR | ROPE_FLAG | ((jsoff_t)depth << ROPE_DEPTH_SHIFT);
jsval_t undef = js_mkundef();
memcpy(&js->mem[ofs + offsetof(rope_node_t, header)], &header, sizeof(header));
memcpy(&js->mem[ofs + offsetof(rope_node_t, left)], &left, sizeof(left));
memcpy(&js->mem[ofs + offsetof(rope_node_t, right)], &right, sizeof(right));
memcpy(&js->mem[ofs + offsetof(rope_node_t, cached)], &undef, sizeof(undef));
return mkval(T_STR, ofs);
}
static bool bigint_parse_abs_u64(struct js *js, jsval_t value, uint64_t *out) {
size_t len = 0; const char *digits = bigint_digits(js, value, &len);
uint64_t acc = 0;
for (size_t i = 0; i < len; i++) {
char c = digits[i];
if (!is_digit(c)) return false;
uint64_t digit = (uint64_t)(c - '0');
if (acc > UINT64_MAX / 10 || (acc == UINT64_MAX / 10 && digit > (UINT64_MAX % 10))) {
return false;
} acc = acc * 10 + digit;
}
*out = acc;
return true;
}
static bool bigint_IsNegative(struct js *js, jsval_t v) {
jsoff_t ofs = (jsoff_t) vdata(v);
return js->mem[ofs + sizeof(jsoff_t)] == 1;
}
static bool bigint_parse_u64(struct js *js, jsval_t value, uint64_t *out) {
if (bigint_IsNegative(js, value)) return false;
return bigint_parse_abs_u64(js, value, out);
}
jsval_t js_mkbigint(struct js *js, const char *digits, size_t len, bool negative) {
size_t total = len + 2;
jsoff_t ofs = js_alloc(js, total + sizeof(jsoff_t));
if (ofs == (jsoff_t) ~0) return js_mkerr(js, "oom");
jsoff_t header = (jsoff_t) (total << 4);
memcpy(&js->mem[ofs], &header, sizeof(header));
js->mem[ofs + sizeof(header)] = negative ? 1 : 0;
if (digits) memcpy(&js->mem[ofs + sizeof(header) + 1], digits, len);
js->mem[ofs + sizeof(header) + 1 + len] = 0;
return mkval(T_BIGINT, ofs);
}
static const char *bigint_digits(struct js *js, jsval_t v, size_t *len) {
jsoff_t ofs = (jsoff_t) vdata(v);
jsoff_t header = loadoff(js, ofs);
size_t total = (header >> 4) - 2;
if (len) *len = total;
return (const char *)&js->mem[ofs + sizeof(jsoff_t) + 1];
}
static int bigint_cmp_abs(const char *a, size_t alen, const char *b, size_t blen) {
while (alen > 1 && a[0] == '0') { a++; alen--; }
while (blen > 1 && b[0] == '0') { b++; blen--; }
if (alen != blen) return alen > blen ? 1 : -1;
for (size_t i = 0; i < alen; i++) {
if (a[i] != b[i]) return a[i] > b[i] ? 1 : -1;
}
return 0;
}
static char *bigint_add_abs(const char *a, size_t alen, const char *b, size_t blen, size_t *rlen) {
size_t maxlen = (alen > blen ? alen : blen) + 1;
char *result = (char *)malloc(maxlen + 1);
if (!result) return NULL;
int carry = 0;
size_t ri = 0;
for (size_t i = 0; i < maxlen; i++) {
int da = (i < alen) ? (a[alen - 1 - i] - '0') : 0;
int db = (i < blen) ? (b[blen - 1 - i] - '0') : 0;
int sum = da + db + carry;
carry = sum / 10;
result[ri++] = (char)('0' + (sum % 10));
}
while (ri > 1 && result[ri - 1] == '0') ri--;
for (size_t i = 0; i < ri / 2; i++) {
char tmp = result[i]; result[i] = result[ri - 1 - i]; result[ri - 1 - i] = tmp;
}
result[ri] = 0;
*rlen = ri;
return result;
}
static char *bigint_sub_abs(const char *a, size_t alen, const char *b, size_t blen, size_t *rlen) {
char *result = (char *)malloc(alen + 1);
if (!result) return NULL;
int borrow = 0;
size_t ri = 0;
for (size_t i = 0; i < alen; i++) {
int da = a[alen - 1 - i] - '0';
int db = (i < blen) ? (b[blen - 1 - i] - '0') : 0;
int diff = da - db - borrow;
if (diff < 0) { diff += 10; borrow = 1; } else { borrow = 0; }
result[ri++] = (char)('0' + diff);
}
while (ri > 1 && result[ri - 1] == '0') ri--;
for (size_t i = 0; i < ri / 2; i++) {
char tmp = result[i]; result[i] = result[ri - 1 - i]; result[ri - 1 - i] = tmp;
}
result[ri] = 0;
*rlen = ri;
return result;
}
static char *bigint_mul_abs(const char *a, size_t alen, const char *b, size_t blen, size_t *rlen) {
size_t reslen = alen + blen;
int *temp = (int *)calloc(reslen, sizeof(int));
if (!temp) return NULL;
for (size_t i = 0; i < alen; i++) {
for (size_t j = 0; j < blen; j++) {
temp[i + j] += (a[alen - 1 - i] - '0') * (b[blen - 1 - j] - '0');
}
}
for (size_t i = 0; i < reslen - 1; i++) {
temp[i + 1] += temp[i] / 10;
temp[i] %= 10;
}
size_t start = reslen - 1;
while (start > 0 && temp[start] == 0) start--;
char *result = (char *)malloc(start + 2);
if (!result) { free(temp); return NULL; }
for (size_t i = 0; i <= start; i++) result[i] = (char)('0' + temp[start - i]);
result[start + 1] = 0;
*rlen = start + 1;
free(temp);
return result;
}
static char *bigint_div_abs(const char *a, size_t alen, const char *b, size_t blen, size_t *rlen, char **rem, size_t *remlen) {
if (blen == 1 && b[0] == '0') return NULL;
if (bigint_cmp_abs(a, alen, b, blen) < 0) {
char *result = (char *)malloc(2); result[0] = '0'; result[1] = 0; *rlen = 1;
if (rem) { *rem = (char *)malloc(alen + 1); memcpy(*rem, a, alen); (*rem)[alen] = 0; *remlen = alen; }
return result;
}
char *current = (char *)calloc(alen + 1, 1);
char *result = (char *)calloc(alen + 1, 1);
if (!current || !result) { free(current); free(result); return NULL; }
size_t curlen = 0, reslen = 0;
for (size_t i = 0; i < alen; i++) {
if (curlen == 1 && current[0] == '0') curlen = 0;
current[curlen++] = a[i]; current[curlen] = 0;
int count = 0;
while (bigint_cmp_abs(current, curlen, b, blen) >= 0) {
size_t sublen;
char *sub = bigint_sub_abs(current, curlen, b, blen, &sublen);
if (!sub) break;
memcpy(current, sub, sublen + 1); curlen = sublen;
free(sub); count++;
}
result[reslen++] = (char)('0' + count);
}
size_t start = 0;
while (start < reslen - 1 && result[start] == '0') start++;
memmove(result, result + start, reslen - start + 1);
*rlen = reslen - start;
if (rem) { *rem = current; *remlen = curlen; } else free(current);
return result;
}
static jsval_t bigint_add(struct js *js, jsval_t a, jsval_t b) {
bool aneg = bigint_IsNegative(js, a), bneg = bigint_IsNegative(js, b);
size_t alen, blen;
const char *ad = bigint_digits(js, a, &alen), *bd = bigint_digits(js, b, &blen);
char *result; size_t rlen; bool rneg;
if (aneg == bneg) {
result = bigint_add_abs(ad, alen, bd, blen, &rlen); rneg = aneg;
} else {
int cmp = bigint_cmp_abs(ad, alen, bd, blen);
if (cmp >= 0) { result = bigint_sub_abs(ad, alen, bd, blen, &rlen); rneg = aneg; }
else { result = bigint_sub_abs(bd, blen, ad, alen, &rlen); rneg = bneg; }
}
if (!result) return js_mkerr(js, "oom");
if (rlen == 1 && result[0] == '0') rneg = false;
jsval_t r = js_mkbigint(js, result, rlen, rneg);
free(result);
return r;
}
static jsval_t bigint_sub(struct js *js, jsval_t a, jsval_t b) {
bool aneg = bigint_IsNegative(js, a), bneg = bigint_IsNegative(js, b);
size_t alen, blen;
const char *ad = bigint_digits(js, a, &alen), *bd = bigint_digits(js, b, &blen);
char *result; size_t rlen; bool rneg;
if (aneg != bneg) {
result = bigint_add_abs(ad, alen, bd, blen, &rlen); rneg = aneg;
} else {
int cmp = bigint_cmp_abs(ad, alen, bd, blen);
if (cmp >= 0) { result = bigint_sub_abs(ad, alen, bd, blen, &rlen); rneg = aneg; }
else { result = bigint_sub_abs(bd, blen, ad, alen, &rlen); rneg = !aneg; }
}
if (!result) return js_mkerr(js, "oom");
if (rlen == 1 && result[0] == '0') rneg = false;
jsval_t r = js_mkbigint(js, result, rlen, rneg);
free(result);
return r;
}
static jsval_t bigint_mul(struct js *js, jsval_t a, jsval_t b) {
bool aneg = bigint_IsNegative(js, a), bneg = bigint_IsNegative(js, b);
size_t alen, blen;
const char *ad = bigint_digits(js, a, &alen), *bd = bigint_digits(js, b, &blen);
size_t rlen;
char *result = bigint_mul_abs(ad, alen, bd, blen, &rlen);
if (!result) return js_mkerr(js, "oom");
bool rneg = (aneg != bneg) && !(rlen == 1 && result[0] == '0');
jsval_t r = js_mkbigint(js, result, rlen, rneg);
free(result);
return r;
}
static jsval_t bigint_div(struct js *js, jsval_t a, jsval_t b) {
bool aneg = bigint_IsNegative(js, a), bneg = bigint_IsNegative(js, b);
size_t alen, blen;
const char *ad = bigint_digits(js, a, &alen), *bd = bigint_digits(js, b, &blen);
if (blen == 1 && bd[0] == '0') return js_mkerr(js, "Division by zero");
size_t rlen;
char *result = bigint_div_abs(ad, alen, bd, blen, &rlen, NULL, NULL);
if (!result) return js_mkerr(js, "oom");
bool rneg = (aneg != bneg) && !(rlen == 1 && result[0] == '0');
jsval_t r = js_mkbigint(js, result, rlen, rneg);
free(result);
return r;
}
static jsval_t bigint_mod(struct js *js, jsval_t a, jsval_t b) {
bool aneg = bigint_IsNegative(js, a);
size_t alen, blen;
const char *ad = bigint_digits(js, a, &alen), *bd = bigint_digits(js, b, &blen);
if (blen == 1 && bd[0] == '0') return js_mkerr(js, "Division by zero");
size_t rlen, remlen; char *rem;
char *result = bigint_div_abs(ad, alen, bd, blen, &rlen, &rem, &remlen);
if (!result) return js_mkerr(js, "oom");
free(result);
bool rneg = aneg && !(remlen == 1 && rem[0] == '0');
jsval_t r = js_mkbigint(js, rem, remlen, rneg);
free(rem);
return r;
}
static jsval_t bigint_neg(struct js *js, jsval_t a) {
size_t len;
const char *digits = bigint_digits(js, a, &len);
bool neg = bigint_IsNegative(js, a);
if (len == 1 && digits[0] == '0') return js_mkbigint(js, digits, len, false);
return js_mkbigint(js, digits, len, !neg);
}
static jsval_t bigint_exp(struct js *js, jsval_t base, jsval_t exp) {
if (bigint_IsNegative(js, exp)) return js_mkerr(js, "Exponent must be positive");
size_t explen;
const char *expd = bigint_digits(js, exp, &explen);
if (explen == 1 && expd[0] == '0') return js_mkbigint(js, "1", 1, false);
jsval_t result = js_mkbigint(js, "1", 1, false);
jsval_t b = base;
jsval_t e = exp;
jsval_t two = js_mkbigint(js, "2", 1, false);
while (true) {
size_t elen;
const char *ed = bigint_digits(js, e, &elen);
if (elen == 1 && ed[0] == '0') break;
int last_digit = ed[elen - 1] - '0';
if (last_digit % 2 == 1) {
result = bigint_mul(js, result, b);
if (is_err(result)) return result;
}
b = bigint_mul(js, b, b);
if (is_err(b)) return b;
e = bigint_div(js, e, two);
if (is_err(e)) return e;
}
return result;
}
static inline jsval_t bigint_pow2(struct js *js, uint64_t bits) {
jsval_t two = js_mkbigint(js, "2", 1, false);
if (is_err(two)) return two;
jsval_t exp = bigint_from_u64(js, bits);
if (is_err(exp)) return exp;
return bigint_exp(js, two, exp);
}
static jsval_t bigint_shift_left(struct js *js, jsval_t value, uint64_t shift) {
if (shift == 0) return value;
if (shift > 18446744073709551615ULL) return js_mkerr(js, "Shift count too large");
size_t digits_len; const char *digits = bigint_digits(js, value, &digits_len);
if (digits_len == 1 && digits[0] == '0') return js_mkbigint(js, "0", 1, false);
uint64_t u64 = 0;
if (!bigint_IsNegative(js, value) && shift < 64 && bigint_parse_u64(js, value, &u64)) {
if (u64 <= (UINT64_MAX >> shift)) return bigint_from_u64(js, u64 << shift);
}
jsval_t pow = bigint_pow2(js, shift);
if (is_err(pow)) return pow;
return bigint_mul(js, value, pow);
}
static jsval_t bigint_shift_right(struct js *js, jsval_t value, uint64_t shift) {
if (shift == 0) return value;
if (shift > 18446744073709551615ULL) return js_mkerr(js, "Shift count too large");
size_t digits_len; const char *digits = bigint_digits(js, value, &digits_len);
if (digits_len == 1 && digits[0] == '0') return js_mkbigint(js, "0", 1, false);
uint64_t u64 = 0;
if (!bigint_IsNegative(js, value) && bigint_parse_u64(js, value, &u64)) {
if (shift >= 64) return js_mkbigint(js, "0", 1, false);
return bigint_from_u64(js, u64 >> shift);
}
if (bigint_parse_abs_u64(js, value, &u64)) {
if (shift >= 64) return js_mkbigint(
js, bigint_IsNegative(js, value) ? "1" : "0", 1,
bigint_IsNegative(js, value)
);
uint64_t shifted = u64 >> shift;
if (bigint_IsNegative(js, value)) {
if ((u64 & ((1ULL << shift) - 1)) != 0) shifted += 1;
jsval_t pos = bigint_from_u64(js, shifted);
if (is_err(pos)) return pos;
return bigint_neg(js, pos);
}
return bigint_from_u64(js, shifted);
}
jsval_t pow = bigint_pow2(js, shift);
if (is_err(pow)) return pow;
return bigint_div(js, value, pow);
}
static inline jsval_t bigint_shift_right_logical(struct js *js, jsval_t value, uint64_t shift) {
return js_mkerr_typed(js, JS_ERR_TYPE, "BigInts have no unsigned right shift, use >> instead");
}
static int bigint_compare(struct js *js, jsval_t a, jsval_t b) {
bool aneg = bigint_IsNegative(js, a), bneg = bigint_IsNegative(js, b);
size_t alen, blen;
const char *ad = bigint_digits(js, a, &alen), *bd = bigint_digits(js, b, &blen);
if (aneg && !bneg) return -1;
if (!aneg && bneg) return 1;
int cmp = bigint_cmp_abs(ad, alen, bd, blen);
return aneg ? -cmp : cmp;
}
static bool bigint_is_zero(struct js *js, jsval_t v) {
size_t len;
const char *digits = bigint_digits(js, v, &len);
return len == 1 && digits[0] == '0';
}
static size_t strbigint(struct js *js, jsval_t value, char *buf, size_t len) {
bool neg = bigint_IsNegative(js, value);
size_t dlen;
const char *digits = bigint_digits(js, value, &dlen);
size_t n = 0;
if (neg) n += cpy(buf + n, REMAIN(n, len), "-", 1);
n += cpy(buf + n, REMAIN(n, len), digits, dlen);
return n;
}
static jsval_t builtin_BigInt(struct js *js, jsval_t *args, int nargs) {
if (vtype(js->new_target) != T_UNDEF) return js_mkerr_typed(js, JS_ERR_TYPE, "BigInt is not a constructor");
if (nargs < 1) return js_mkbigint(js, "0", 1, false);
jsval_t arg = args[0];
if (vtype(arg) == T_BIGINT) return arg;
if (vtype(arg) == T_NUM) {
double d = tod(arg);
if (!isfinite(d)) return js_mkerr(js, "Cannot convert Infinity or NaN to BigInt");
if (d != trunc(d)) return js_mkerr(js, "Cannot convert non-integer to BigInt");
bool neg = d < 0;
if (neg) d = -d;
char buf[64];
snprintf(buf, sizeof(buf), "%.0f", d);
return js_mkbigint(js, buf, strlen(buf), neg);
}
if (vtype(arg) == T_STR) {
jsoff_t slen, off = vstr(js, arg, &slen);
const char *str = (const char *)&js->mem[off];
bool neg = false;
size_t i = 0;
if (slen > 0 && str[0] == '-') { neg = true; i++; }
else if (slen > 0 && str[0] == '+') { i++; }
while (i < slen && str[i] == '0') i++;
if (i >= slen) return js_mkbigint(js, "0", 1, false);
for (size_t j = i; j < slen; j++) {
if (!is_digit(str[j])) return js_mkerr(js, "Cannot convert string to BigInt");
}
return js_mkbigint(js, str + i, slen - i, neg);
}
if (vtype(arg) == T_BOOL) {
return js_mkbigint(js, vdata(arg) ? "1" : "0", 1, false);
}
return js_mkerr(js, "Cannot convert to BigInt");
}
static jsval_t bigint_to_u64(struct js *js, jsval_t value, uint64_t *out) {
if (!bigint_parse_u64(js, value, out)) {
return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid bits");
}
return js_mkundef();
}
static jsval_t bigint_asint_bits(struct js *js, jsval_t arg, uint64_t *bits_out) {
if (vtype(arg) == T_BIGINT) {
return bigint_to_u64(js, arg, bits_out);
}
double bits = js_to_number(js, arg);
if (!isfinite(bits) || bits < 0 || bits != floor(bits)) {
return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid bits");
}
if (bits > 18446744073709551615.0) {
return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid bits");
}
*bits_out = (uint64_t)bits;
return js_mkundef();
}
static jsval_t builtin_BigInt_asIntN(struct js *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "BigInt.asIntN requires 2 arguments");
uint64_t bits = 0;
jsval_t err = bigint_asint_bits(js, args[0], &bits);
if (is_err(err)) return err;
if (vtype(args[1]) != T_BIGINT) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert to BigInt");
}
if (bits == 0) return js_mkbigint(js, "0", 1, false);
jsval_t mod = bigint_pow2(js, bits);
if (is_err(mod)) return mod;
jsval_t res = bigint_mod(js, args[1], mod);
if (is_err(res)) return res;
if (bigint_IsNegative(js, res)) {
jsval_t adj = bigint_add(js, res, mod);
if (is_err(adj)) return adj;
res = adj;
}
jsval_t threshold = bigint_pow2(js, bits - 1);
if (is_err(threshold)) return threshold;
if (bigint_compare(js, res, threshold) >= 0) {
jsval_t adj = bigint_sub(js, res, mod);
if (is_err(adj)) return adj;
res = adj;
}
return res;
}
static jsval_t builtin_BigInt_asUintN(struct js *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "BigInt.asUintN requires 2 arguments");
uint64_t bits = 0;
jsval_t err = bigint_asint_bits(js, args[0], &bits);
if (is_err(err)) return err;
if (vtype(args[1]) != T_BIGINT) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert to BigInt");
}
if (bits == 0) return js_mkbigint(js, "0", 1, false);
jsval_t mod = bigint_pow2(js, bits);
if (is_err(mod)) return mod;
jsval_t res = bigint_mod(js, args[1], mod);
if (is_err(res)) return res;
if (bigint_IsNegative(js, res)) {
jsval_t adj = bigint_add(js, res, mod);
if (is_err(adj)) return adj;
res = adj;
}
return res;
}
static jsval_t builtin_bigint_toString(struct js *js, jsval_t *args, int nargs) {
jsval_t val = js->this_val;
if (vtype(val) != T_BIGINT) return js_mkerr(js, "toString called on non-BigInt");
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");
}
bool neg = bigint_IsNegative(js, val);
size_t dlen; const char *digits = bigint_digits(js, val, &dlen);
if (radix == 10) {
size_t buflen = dlen + 2;
char *buf = (char *)ant_calloc(buflen);
if (!buf) return js_mkerr(js, "oom");
size_t n = 0; if (neg) buf[n++] = '-';
memcpy(buf + n, digits, dlen); n += dlen;
jsval_t ret = js_mkstr(js, buf, n); free(buf);
return ret;
}
const uint32_t base = 1000000000U;
size_t result_cap = dlen * 4 + 16;
char *result = (char *)ant_calloc(result_cap);
if (!result) return js_mkerr(js, "oom");
size_t rpos = result_cap - 1;
result[rpos] = '\0';
size_t limb_cap = (dlen + 8) / 9 + 1;
uint32_t *limbs = (uint32_t *)ant_calloc(limb_cap * sizeof(uint32_t));
if (!limbs) { free(result); return js_mkerr(js, "oom"); }
size_t limb_len = 1;
for (size_t i = 0; i < dlen; i++) {
uint64_t carry = (uint64_t)(digits[i] - '0');
for (size_t j = 0; j < limb_len; j++) {
uint64_t cur = (uint64_t)limbs[j] * 10 + carry;
limbs[j] = (uint32_t)(cur % base);
carry = cur / base;
}
if (carry != 0) {
if (limb_len == limb_cap) {
size_t new_cap = limb_cap * 2;
uint32_t *new_limbs = (uint32_t *)ant_realloc(limbs, new_cap * sizeof(uint32_t));
if (!new_limbs) { free(limbs); free(result); return js_mkerr(js, "oom"); }
limbs = new_limbs;
limb_cap = new_cap;
}
limbs[limb_len++] = (uint32_t)carry;
}
}
static const char digit_map[] = "0123456789abcdefghijklmnopqrstuvwxyz";
while (limb_len > 0 && !(limb_len == 1 && limbs[0] == 0)) {
uint64_t remainder = 0;
for (size_t i = limb_len; i-- > 0;) {
uint64_t cur = (uint64_t)limbs[i] + remainder * base;
limbs[i] = (uint32_t)(cur / (uint64_t)radix);
remainder = cur % (uint64_t)radix;
}
while (limb_len > 0 && limbs[limb_len - 1] == 0) limb_len--;
if (rpos == 0) {
size_t new_cap = result_cap * 2;
char *new_result = (char *)ant_calloc(new_cap);
if (!new_result) { free(limbs); free(result); return js_mkerr(js, "oom"); }
size_t used = result_cap - rpos;
memcpy(new_result + new_cap - used, result + rpos, used);
free(result);
result = new_result;
rpos = new_cap - used;
result_cap = new_cap;
}
result[--rpos] = digit_map[remainder];
}
free(limbs);
if (rpos == result_cap - 1) result[--rpos] = '0';
if (neg) result[--rpos] = '-';
jsval_t ret = js_mkstr(js, result + rpos, result_cap - 1 - rpos);
free(result); return ret;
}
static jsval_t mkobj(struct js *js, jsoff_t parent) {
jsoff_t buf[2] = { parent, 0 };
return mkentity(js, 0 | T_OBJ, buf, sizeof(buf));
}
jsval_t mkarr(struct js *js) {
jsval_t arr = mkobj(js, 0);
jsoff_t off = (jsoff_t) vdata(arr);
jsoff_t header = loadoff(js, off);
saveoff(js, off, header | ARRMASK);
jsval_t array_proto = get_ctor_proto(js, "Array", 5);
if (vtype(array_proto) == T_OBJ) set_proto(js, arr, array_proto);
jsval_t arr_val = mkval(T_ARR, vdata(arr));
jsoff_t doff = dense_alloc(js, JS_DENSE_INITIAL_CAP);
if (doff) set_slot(js, arr_val, SLOT_DENSE_BUF, tov((double)doff));
return arr_val;
}
jsval_t js_mkarr(struct js *js) {
return mkarr(js);
}
jsval_t js_newobj(struct js *js) {
jsval_t obj = mkobj(js, 0);
jsval_t proto = get_ctor_proto(js, "Object", 6);
if (vtype(proto) == T_OBJ) set_proto(js, obj, proto);
return obj;
}
jsoff_t js_arr_len(struct js *js, jsval_t arr) {
if (vtype(arr) != T_ARR) return 0;
jsoff_t doff = get_dense_buf(js, arr);
if (doff) return dense_length(js, doff);
jsoff_t max_idx = 0;
bool found_length_prop = false;
jsoff_t length_prop_val = 0;
jsoff_t scan = loadoff(js, (jsoff_t) vdata(arr)) & ~(3U | FLAGMASK);
while (scan < js->brk && scan != 0) {
const char *key; jsoff_t klen;
get_prop_key(js, scan, &key, &klen);
if (streq(key, klen, "length", 6)) {
jsval_t val = get_prop_val(js, scan);
if (vtype(val) == T_NUM) {
found_length_prop = true;
length_prop_val = (jsoff_t) tod(val);
}
} else if (klen > 0 && key[0] >= '0' && key[0] <= '9') {
char *endptr;
unsigned long idx = strtoul(key, &endptr, 10);
if (endptr == key + klen && idx + 1 > max_idx) max_idx = (jsoff_t)(idx + 1);
}
scan = loadoff(js, scan) & ~(3U | FLAGMASK);
}
if (found_length_prop) return length_prop_val;
return max_idx;
}
jsval_t js_arr_get(struct js *js, jsval_t arr, jsoff_t idx) {
if (vtype(arr) != T_ARR) return js_mkundef();
return arr_get(js, arr, idx);
}
static inline bool is_const_prop(struct js *js, jsoff_t propoff) {
jsoff_t v = loadoff(js, propoff);
return (v & CONSTMASK) != 0;
}
static inline bool is_nonconfig_prop(struct js *js, jsoff_t propoff) {
jsoff_t v = loadoff(js, propoff);
return (v & NONCONFIGMASK) != 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);
}
static void invalidate_prop_cache(struct js *js, jsoff_t obj_off, jsoff_t prop_off) {
jsoff_t koff = loadoff(js, prop_off + sizeof(jsoff_t));
jsoff_t klen = (loadoff(js, koff) >> 3) - 1;
const char *key = (char *)&js->mem[koff + sizeof(jsoff_t)];
const char *interned = intern_string(key, klen);
if (!interned) return;
uint32_t cache_slot = (((uintptr_t)interned >> 3) ^ obj_off) & (ANT_LIMIT_SIZE_CACHE - 1);
intern_prop_cache_entry_t *ce = &intern_prop_cache[cache_slot];
if (ce->obj_off == obj_off && ce->intern_ptr == interned) ce->generation = 0;
}
static jsval_t mkprop(struct js *js, jsval_t obj, jsval_t k, jsval_t v, jsoff_t flags) {
jsoff_t klen; jsoff_t koff = vstr(js, k, &klen);
jsoff_t koff_entity = koff - sizeof(jsoff_t);
jsoff_t head = (jsoff_t) vdata(obj);
char buf[sizeof(koff_entity) + sizeof(v)];
jsoff_t header = loadoff(js, head);
jsoff_t first_prop = header & ~(3U | FLAGMASK);
jsoff_t tail = loadoff(js, head + sizeof(jsoff_t) + sizeof(jsoff_t));
memcpy(buf, &koff_entity, sizeof(koff_entity));
memcpy(buf + sizeof(koff_entity), &v, sizeof(v));
const char *p = (char *) &js->mem[koff];
(void)intern_string(p, klen);
jsoff_t new_prop_off = js->brk;
jsval_t prop = mkentity(js, 0 | T_PROP | flags, buf, sizeof(buf));
if (is_err(prop)) return prop;
if (first_prop == 0) {
jsoff_t new_header = new_prop_off | (header & (3U | FLAGMASK));
saveoff(js, head, new_header);
} else {
jsoff_t tail_header = loadoff(js, tail);
jsoff_t new_tail_header = new_prop_off | (tail_header & (3U | FLAGMASK));
saveoff(js, tail, new_tail_header);
}
saveoff(js, head + sizeof(jsoff_t) + sizeof(jsoff_t), new_prop_off);
return prop;
}
static inline jsval_t mkprop_fast(struct js *js, jsval_t obj, jsval_t k, jsval_t v, jsoff_t flags) {
jsoff_t klen; jsoff_t koff = vstr(js, k, &klen);
jsoff_t koff_entity = koff - sizeof(jsoff_t);
jsoff_t head = (jsoff_t) vdata(obj);
char buf[sizeof(koff_entity) + sizeof(v)];
jsoff_t header = loadoff(js, head);
jsoff_t first_prop = header & ~(3U | FLAGMASK);
jsoff_t tail = loadoff(js, head + sizeof(jsoff_t) + sizeof(jsoff_t));
memcpy(buf, &koff_entity, sizeof(koff_entity));
memcpy(buf + sizeof(koff_entity), &v, sizeof(v));
jsoff_t new_prop_off = js->brk;
jsval_t prop = mkentity(js, 0 | T_PROP | flags, buf, sizeof(buf));
if (is_err(prop)) return prop;
if (first_prop == 0) {
jsoff_t new_header = new_prop_off | (header & (3U | FLAGMASK));
saveoff(js, head, new_header);
} else {
jsoff_t tail_header = loadoff(js, tail);
jsoff_t new_tail_header = new_prop_off | (tail_header & (3U | FLAGMASK));
saveoff(js, tail, new_tail_header);
}
saveoff(js, head + sizeof(jsoff_t) + sizeof(jsoff_t), new_prop_off);
return prop;
}
jsval_t js_mkprop_fast(struct js *js, jsval_t obj, const char *key, size_t len, jsval_t v) {
jsval_t k = js_mkstr(js, key, len);
if (is_err(k)) return k;
return mkprop_fast(js, obj, k, v, 0);
}
jsoff_t js_mkprop_fast_off(struct js *js, jsval_t obj, const char *key, size_t len, jsval_t v) {
jsval_t k = js_mkstr(js, key, len);
if (is_err(k)) return 0;
jsoff_t prop_off = js->brk;
mkprop_fast(js, obj, k, v, 0);
return prop_off + sizeof(jsoff_t) * 2;
}
void js_saveval(struct js *js, jsoff_t off, jsval_t v) { saveval(js, off, v); }
static jsval_t mkslot(struct js *js, jsval_t obj, internal_slot_t slot, jsval_t v) {
jsoff_t head = (jsoff_t) vdata(obj);
char buf[sizeof(jsoff_t) + sizeof(v)];
jsoff_t header = loadoff(js, head);
jsoff_t first_prop = header & ~(3U | FLAGMASK);
jsoff_t slot_key = (jsoff_t)slot;
memcpy(buf, &slot_key, sizeof(slot_key));
memcpy(buf + sizeof(slot_key), &v, sizeof(v));
jsoff_t new_prop_off = js->brk;
jsval_t prop = mkentity(js, first_prop | T_PROP | SLOTMASK, buf, sizeof(buf));
if (is_err(prop)) return prop;
jsoff_t new_header = new_prop_off | (header & (3U | FLAGMASK));
saveoff(js, head, new_header);
if (first_prop == 0) {
saveoff(js, head + sizeof(jsoff_t) + sizeof(jsoff_t), new_prop_off);
}
return prop;
}
static inline jsoff_t search_slot(struct js *js, jsval_t obj, internal_slot_t slot) {
jsoff_t off = (jsoff_t) vdata(obj);
if (__builtin_expect(off >= js->brk, 0)) return 0;
jsoff_t next = loadoff(js, off) & ~(3U | FLAGMASK);
jsoff_t header, koff;
check:
if (__builtin_expect(next == 0 || next >= js->brk, 0)) return 0;
header = loadoff(js, next);
if ((header & SLOTMASK) == 0) return 0;
koff = loadoff(js, next + sizeof(jsoff_t));
if (koff == (jsoff_t)slot) return next;
next = header & ~(3U | FLAGMASK);
goto check;
}
static void set_slot(struct js *js, jsval_t obj, internal_slot_t slot, jsval_t val) {
jsoff_t existing = search_slot(js, obj, slot);
if (existing > 0) {
saveval(js, existing + sizeof(jsoff_t) * 2, val);
} else mkslot(js, obj, slot, val);
}
static jsval_t get_slot(struct js *js, jsval_t obj, internal_slot_t slot) {
jsoff_t off = search_slot(js, obj, slot);
if (off == 0) return js_mkundef();
return loadval(js, off + sizeof(jsoff_t) * 2);
}
static void set_func_code_ptr(struct js *js, jsval_t func_obj, const char *code, size_t len) {
set_slot(js, func_obj, SLOT_CODE, mkval(T_CFUNC, (size_t)code));
set_slot(js, func_obj, SLOT_CODE_LEN, tov((double)len));
}
static void set_func_code(struct js *js, jsval_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 (!code_has_function_decl(code, len)) set_slot(js, func_obj, SLOT_NO_FUNC_DECLS, js_true);
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(js, func_obj, SLOT_HOISTED_VARS, mkval(T_CFUNC, (size_t)vars));
set_slot(js, func_obj, SLOT_HOISTED_VARS_LEN, tov((double)vars_buf_len));
}
}
static const char *get_func_code(struct js *js, jsval_t func_obj, jsoff_t *len) {
jsval_t code_val = get_slot(js, func_obj, SLOT_CODE);
jsval_t len_val = get_slot(js, func_obj, SLOT_CODE_LEN);
if (vtype(code_val) != T_CFUNC) {
if (len) *len = 0;
return NULL;
}
if (len) *len = (jsoff_t)tod(len_val);
return (const char *)vdata(code_val);
}
static inline bool is_slot_prop(jsoff_t header) {
return (header & SLOTMASK) != 0;
}
static inline jsoff_t next_prop(jsoff_t header) {
return header & ~(3U | FLAGMASK);
}
static double js_to_number(struct js *js, jsval_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) {
jsoff_t len, off = vstr(js, arg, &len);
const char *s = (char *)&js->mem[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) {
jsval_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);
}
jsval_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 jsval_t setup_func_prototype(struct js *js, jsval_t func) {
jsval_t proto_obj = mkobj(js, 0);
if (is_err(proto_obj)) return proto_obj;
jsval_t object_proto = get_ctor_proto(js, "Object", 6);
if (vtype(object_proto) == T_OBJ) {
set_proto(js, proto_obj, object_proto);
}
jsval_t constructor_key = js_mkstr(js, "constructor", 11);
if (is_err(constructor_key)) return constructor_key;
jsval_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);
jsval_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, func, "prototype", 9, JS_DESC_W);
return js_mkundef();
}
static void infer_func_name(struct js *js, jsval_t func, const char *name, size_t len) {
jsval_t func_obj = mkval(T_OBJ, vdata(func));
if (vtype(get_slot(js, func_obj, SLOT_NAME)) != T_UNDEF) return;
jsval_t name_val = js_mkstr(js, name, len);
set_slot(js, func_obj, SLOT_NAME, name_val);
js_setprop(js, func_obj, js_mkstr(js, "name", 4), name_val);
}
static jsval_t validate_array_length(struct js *js, jsval_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 jsval_t check_object_extensibility(struct js *js, jsval_t obj) {
jsoff_t obj_off_fp = (jsoff_t)vdata(obj);
jsoff_t next = loadoff(js, obj_off_fp) & ~(3U | FLAGMASK);
while (next != 0 && next < js->brk) {
jsoff_t hdr = loadoff(js, next);
if ((hdr & SLOTMASK) == 0) break;
jsoff_t sk = loadoff(js, next + sizeof(jsoff_t));
jsval_t sv = loadval(js, next + sizeof(jsoff_t) * 2);
if (sk == (jsoff_t)SLOT_FROZEN && js_truthy(js, sv)) {
return (js->flags & F_STRICT)
? js_mkerr(js, "cannot add property to frozen object")
: js_mkundef();
}
if (sk == (jsoff_t)SLOT_SEALED && js_truthy(js, sv)) {
return (js->flags & F_STRICT)
? js_mkerr(js, "cannot add property to sealed object")
: js_mkundef();
}
if (sk == (jsoff_t)SLOT_EXTENSIBLE && !js_truthy(js, sv)) {
return (js->flags & F_STRICT)
? js_mkerr(js, "cannot add property to non-extensible object")
: js_mkundef();
}
next = hdr & ~(3U | FLAGMASK);
}
return js_mkundef();
}
static inline void update_array_length(struct js *js, jsval_t obj, jsoff_t new_len) {
jsoff_t len_off = lkp_interned(js, obj, INTERN_LENGTH, 6);
jsval_t new_len_val = tov((double)new_len);
if (len_off != 0) saveval(js, len_off + sizeof(jsoff_t) * 2, new_len_val); else {
js_mkprop_fast(js, obj, "length", 6, new_len_val);
}
}
static jsval_t js_setprop_array_fast(struct js *js, jsval_t obj, jsval_t k, jsval_t v, jsoff_t klen, const char *key) {
unsigned long idx;
if (!parse_array_index(key, klen, (jsoff_t)-1, &idx)) return js_mkundef();
jsoff_t doff = get_dense_buf(js, obj);
if (doff) {
jsoff_t cur_len = dense_length(js, doff);
if (idx < cur_len) { dense_set(js, doff, (jsoff_t)idx, v); return v; }
jsoff_t density_limit = cur_len > 0 ? cur_len * 4 : 64;
if (idx >= density_limit) goto sparse;
jsval_t extensibility_error = check_object_extensibility(js, obj);
if (!is_undefined(extensibility_error)) return extensibility_error;
arr_set(js, obj, (jsoff_t)idx, v);
return v;
}
sparse:;
jsoff_t cur_len = get_array_length(js, obj);
if (idx < cur_len) return js_mkundef();
jsval_t extensibility_error = check_object_extensibility(js, obj);
if (!is_undefined(extensibility_error)) return extensibility_error;
jsval_t result = mkprop_fast(js, obj, k, v, 0);
update_array_length(js, obj, idx + 1);
return result;
}
jsval_t js_setprop(struct js *js, jsval_t obj, jsval_t k, jsval_t v) {
jsoff_t klen; jsoff_t koff = vstr(js, k, &klen);
const char *key = (char *) &js->mem[koff];
if (vtype(obj) == T_ARR && !is_proxy(js, obj) && klen > 0 && key[0] >= '0' && key[0] <= '9') {
jsval_t result = js_setprop_array_fast(js, obj, k, v, klen, key);
if (vtype(result) != T_UNDEF) return result;
}
if (vtype(obj) == T_ARR && streq(key, klen, "length", 6)) {
jsval_t err = validate_array_length(js, v);
if (is_err(err)) return err;
jsoff_t doff = get_dense_buf(js, obj);
if (doff) {
jsoff_t new_len_val = (jsoff_t) tod(v);
jsoff_t cur_len = dense_length(js, doff);
if (new_len_val < cur_len) {
for (jsoff_t i = new_len_val; i < cur_len; i++)
dense_set(js, doff, i, T_EMPTY);
dense_set_length(js, doff, new_len_val);
return v;
} else if (new_len_val <= dense_capacity(js, doff)) {
dense_set_length(js, doff, new_len_val);
return v;
}
}
}
if (is_proxy(js, obj)) {
jsval_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;
jsoff_t existing = lkp(js, obj, key, klen);
{
jsoff_t obj_off = (jsoff_t)vdata(obj);
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, key, klen);
if (!desc) goto no_descriptor;
if (desc->has_setter) {
jsval_t setter = desc->setter;
uint8_t setter_type = vtype(setter);
if (setter_type == T_FUNC || setter_type == T_CFUNC) {
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
uint8_t saved_flags = js->flags;
jsoff_t saved_toff = js->toff;
jsoff_t saved_tlen = js->tlen;
jsval_t saved_this = js->this_val;
js->this_val = obj;
push_this(obj);
jsval_t result = call_js_with_args(js, setter, &v, 1);
pop_this();
js->this_val = saved_this;
JS_RESTORE_STATE(js, saved);
js->flags = saved_flags;
js->toff = saved_toff;
js->tlen = saved_tlen;
if (is_err(result)) return result;
return v;
}
}
if (desc->has_getter && !desc->has_setter) {
if (js->flags & F_STRICT) return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot set property which has only a getter");
return v;
}
if (existing <= 0) goto no_descriptor;
if (!desc->writable) {
if (js->flags & F_STRICT) return js_mkerr(js, "assignment to read-only property");
return mkval(T_PROP, existing);
}
}
no_descriptor:
if (existing <= 0) goto create_new;
if (is_const_prop(js, existing)) return js_mkerr(js, "assignment to constant");
saveval(js, existing + sizeof(jsoff_t) * 2, v);
if (vtype(obj) != T_ARR || klen == 0 || key[0] < '0' || key[0] > '9') goto done_update;
{ jsoff_t doff = get_dense_buf(js, obj); if (doff) goto done_update; }
char *endptr;
unsigned long update_idx = strtoul(key, &endptr, 10);
if (endptr != key + klen) goto done_update;
jsoff_t len_off = lkp_interned(js, obj, INTERN_LENGTH, 6);
jsoff_t cur_len = 0;
if (len_off != 0) {
jsval_t len_val = resolveprop(js, mkval(T_PROP, len_off));
if (vtype(len_val) == T_NUM) cur_len = (jsoff_t) tod(len_val);
}
if (update_idx < cur_len) goto done_update;
jsval_t new_len = tov((double)(update_idx + 1));
if (len_off != 0) saveval(js, len_off + sizeof(jsoff_t) * 2, new_len); else {
mkprop(js, obj, js->length_str, new_len, 0);
}
done_update:
return mkval(T_PROP, existing);
create_new:
if (js_truthy(js, get_slot(js, obj, SLOT_FROZEN))) {
if (js->flags & F_STRICT) return js_mkerr(js, "cannot add property to frozen object");
return js_mkundef();
}
if (js_truthy(js, get_slot(js, obj, SLOT_SEALED))) {
if (js->flags & F_STRICT) return js_mkerr(js, "cannot add property to sealed object");
return js_mkundef();
}
jsval_t ext_slot = get_slot(js, obj, SLOT_EXTENSIBLE);
if (vtype(ext_slot) != T_UNDEF && !js_truthy(js, ext_slot)) {
if (js->flags & F_STRICT) return js_mkerr(js, "cannot add property to non-extensible object");
return js_mkundef();
}
int need_length_update = 0;
unsigned long idx = 0;
if (vtype(obj) == T_ARR && klen > 0 && key[0] >= '0' && key[0] <= '9') {
char *inner_endptr;
idx = strtoul(key, &inner_endptr, 10);
if (inner_endptr == key + klen) {
jsoff_t inner_len_off = lkp_interned(js, obj, INTERN_LENGTH, 6);
jsoff_t inner_cur_len = 0;
if (inner_len_off != 0) {
jsval_t len_val = resolveprop(js, mkval(T_PROP, inner_len_off));
if (vtype(len_val) == T_NUM) inner_cur_len = (jsoff_t) tod(len_val);
}
if (idx >= inner_cur_len) need_length_update = 1;
}
}
jsval_t result = mkprop(js, obj, k, v, 0);
if (need_length_update) {
jsoff_t inner_len_off = lkp_interned(js, obj, INTERN_LENGTH, 6);
jsval_t inner_new_len = tov((double)(idx + 1));
if (inner_len_off != 0) saveval(js, inner_len_off + sizeof(jsoff_t) * 2, inner_new_len); else {
mkprop(js, obj, js->length_str, inner_new_len, 0);
}
}
return result;
}
static inline void esm_export_binding(struct js *js, const char *exported, size_t exported_len, jsval_t value) {
jsval_t export_key = js_mkstr(js, exported, exported_len);
js_setprop(js, js->module_ns, export_key, value);
if (exported_len == 7 && strncmp(exported, "default", 7) == 0) js_set_slot(js, js->module_ns, SLOT_DEFAULT, value);
}
jsval_t setprop_cstr(struct js *js, jsval_t obj, const char *key, size_t len, jsval_t v) {
jsval_t k = js_mkstr(js, key, len);
if (is_err(k)) return k;
return mkprop(js, obj, k, v, 0);
}
jsval_t setprop_interned(struct js *js, jsval_t obj, const char *key, size_t len, jsval_t v) {
jsval_t k = js_mkstr(js, key, len);
if (is_err(k)) return k;
return js_setprop(js, obj, k, v);
}
jsval_t js_setprop_nonconfigurable(struct js *js, jsval_t obj, const char *key, size_t keylen, jsval_t v) {
jsval_t k = js_mkstr(js, key, keylen);
if (is_err(k)) return k;
jsval_t result = js_setprop(js, obj, k, v);
if (is_err(result)) return result;
js_set_descriptor(js, obj, key, keylen, JS_DESC_W);
return result;
}
jsval_t js_mksym(struct js *js, const char *desc) {
uint64_t id = ++js->sym_counter;
jsoff_t desc_off = 0;
if (desc && *desc) {
jsval_t desc_str = js_mkstr(js, desc, strlen(desc));
desc_off = (jsoff_t)vdata(desc_str);
}
uint64_t payload = ((id & PROPREF_PAYLOAD) << PROPREF_KEY_SHIFT) | (desc_off & PROPREF_PAYLOAD);
return mkval(T_SYMBOL, payload);
}
static inline uint64_t sym_get_id(jsval_t v) {
return (vdata(v) >> PROPREF_KEY_SHIFT) & PROPREF_PAYLOAD;
}
static inline jsoff_t sym_get_desc_off(jsval_t v) {
return vdata(v) & PROPREF_PAYLOAD;
}
static const char *sym_get_desc(struct js *js, jsval_t v) {
jsoff_t off = sym_get_desc_off(v);
if (off == 0) return NULL;
return (const char *)&js->mem[off + sizeof(jsoff_t)];
}
uint64_t inline js_sym_id(jsval_t sym) {
return sym_get_id(sym);
}
jsval_t js_mksym_for(struct js *js, const char *key) {
(void)js;
const char *interned = intern_string(key, strlen(key));
uint64_t id = (uint64_t)(uintptr_t)interned;
return mkval(T_SYMBOL, id | (1ULL << 47));
}
const char *js_sym_key(jsval_t sym) {
if (vtype(sym) != T_SYMBOL) return NULL;
uint64_t data = vdata(sym);
if (!(data & (1ULL << 47))) return NULL;
return (const char *)(uintptr_t)(data & ~(1ULL << 47));
}
const inline char *js_sym_desc(struct js *js, jsval_t sym) {
return sym_get_desc(js, sym);
}
jsoff_t esize(jsoff_t w) {
jsoff_t cleaned = w & ~FLAGMASK;
switch (cleaned & 3U) {
case T_OBJ: return (jsoff_t) (sizeof(jsoff_t) + sizeof(jsoff_t) + sizeof(jsoff_t));
case T_PROP: return (jsoff_t) (sizeof(jsoff_t) + sizeof(jsoff_t) + sizeof(jsval_t));
case T_STR: return (jsoff_t) (sizeof(jsoff_t) + align64(cleaned >> 3U));
default: return (jsoff_t) ~0U;
}
}
static int is_unicode_space(const unsigned char *p, jsoff_t remaining, bool *is_line_term) {
if (is_line_term) *is_line_term = false;
if (p[0] < 0x80) return 0;
if (remaining >= 2 && p[0] == 0xC2 && p[1] == 0xA0) return 2;
if (remaining >= 3 && p[0] == 0xE2 && p[1] == 0x80) {
if (p[2] >= 0x80 && p[2] <= 0x8A) return 3;
if (p[2] == 0xAF) return 3;
if (p[2] == 0xA8) { if (is_line_term) *is_line_term = true; return 3; }
if (p[2] == 0xA9) { if (is_line_term) *is_line_term = true; return 3; }
}
if (remaining >= 3 && p[0] == 0xE1 && p[1] == 0x9A && p[2] == 0x80) return 3;
if (remaining >= 3 && p[0] == 0xE2 && p[1] == 0x81 && p[2] == 0x9F) return 3;
if (remaining >= 3 && p[0] == 0xE3 && p[1] == 0x80 && p[2] == 0x80) return 3;
if (remaining >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF) return 3;
return 0;
}
enum { C_0 = 0, C_SPC, C_NL, C_SL, C_HI };
static const uint8_t cc[128] = {
0,0,0,0,0,0,0,0,0,C_SPC,C_NL,C_SPC,C_SPC,C_SPC,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
C_SPC,0,0,0,0,0,0,0,0,0,0,0,0,0,0,C_SL,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};
static jsoff_t skiptonext(const char *code, jsoff_t len, jsoff_t n, bool *nl) {
static const void *D[] = { &&L0, &&LS, &&LN, &&LSL, &&LH };
bool saw_nl = false;
unsigned char c;
const char *p = code + n;
const char *end = code + len;
if (__builtin_expect(p == code && end - p >= 2 && p[0] == '#' && p[1] == '!', 0)) {
for (p += 2; p < end && *p != '\n'; p++);
if (p < end) { saw_nl = true; p++; }
}
if (__builtin_expect(p >= end, 0)) goto L0;
c = (unsigned char)*p;
goto *D[c & 0x80 ? C_HI : cc[c]];
LS:
p++;
if (__builtin_expect(p >= end, 0)) goto L0;
c = (unsigned char)*p;
goto *D[c & 0x80 ? C_HI : cc[c]];
LN:
saw_nl = true;
p++;
if (__builtin_expect(p >= end, 0)) goto L0;
c = (unsigned char)*p;
goto *D[c & 0x80 ? C_HI : cc[c]];
LSL:
if (p + 1 >= end) goto L0;
if (p[1] == '/') {
for (p += 2; p < end && *p != '\n'; p++);
if (p < end) { saw_nl = true; p++; }
if (__builtin_expect(p >= end, 0)) goto L0;
c = (unsigned char)*p;
goto *D[c & 0x80 ? C_HI : cc[c]];
}
if (p[1] == '*') {
for (p += 2; p + 1 < end; p++) {
if (*p == '*' && p[1] == '/') { p += 2; break; }
if (*p == '\n') saw_nl = true;
}
if (__builtin_expect(p >= end, 0)) goto L0;
c = (unsigned char)*p;
goto *D[c & 0x80 ? C_HI : cc[c]];
}
goto L0;
LH: {
bool lt;
int u = is_unicode_space((const unsigned char *)p, (jsoff_t)(end - p), &lt);
if (u > 0) {
if (lt) saw_nl = true;
p += u;
if (__builtin_expect(p >= end, 0)) goto L0;
c = (unsigned char)*p;
goto *D[c & 0x80 ? C_HI : cc[c]];
}
}
L0:
if (nl) *nl = saw_nl;
return (jsoff_t)(p - code);
}
#define K(s, t) if (len == sizeof(s)-1 && !memcmp(buf, s, sizeof(s)-1)) return t
#define M(s) (len == sizeof(s)-1 && !memcmp(buf, s, sizeof(s)-1))
static uint8_t parsekeyword(const char *buf, size_t len) {
switch (buf[0]) {
case 'a':
K("as", TOK_AS);
K("async", TOK_ASYNC);
K("await", TOK_AWAIT);
break;
case 'b':
K("break", TOK_BREAK);
break;
case 'c':
K("case", TOK_CASE);
K("catch", TOK_CATCH);
K("class", TOK_CLASS);
K("const", TOK_CONST);
K("continue", TOK_CONTINUE);
break;
case 'd':
K("do", TOK_DO);
K("default", TOK_DEFAULT);
K("delete", TOK_DELETE);
K("debugger", TOK_DEBUGGER);
break;
case 'e':
K("else", TOK_ELSE);
K("export", TOK_EXPORT);
break;
case 'f':
K("for", TOK_FOR);
K("from", TOK_FROM);
K("false", TOK_FALSE);
K("finally", TOK_FINALLY);
K("function", TOK_FUNC);
break;
case 'g':
K("globalThis", TOK_GLOBAL_THIS);
break;
case 'i':
K("if", TOK_IF);
K("in", TOK_IN);
K("import", TOK_IMPORT);
K("instanceof", TOK_INSTANCEOF);
break;
case 'l':
K("let", TOK_LET);
break;
case 'n':
K("new", TOK_NEW);
K("null", TOK_NULL);
break;
case 'o':
K("of", TOK_OF);
break;
case 'r':
K("return", TOK_RETURN);
break;
case 's':
K("super", TOK_SUPER);
K("static", TOK_STATIC);
K("switch", TOK_SWITCH);
break;
case 't':
K("try", TOK_TRY);
K("this", TOK_THIS);
K("true", TOK_TRUE);
K("throw", TOK_THROW);
K("typeof", TOK_TYPEOF);
break;
case 'u':
K("undefined", TOK_UNDEF);
break;
case 'v':
K("var", TOK_VAR);
K("void", TOK_VOID);
break;
case 'w':
K("while", TOK_WHILE);
K("with", TOK_WITH);
K("window", TOK_WINDOW);
break;
case 'y':
K("yield", TOK_YIELD);
break;
}
return TOK_IDENTIFIER;
}
static bool is_strict_reserved(const char *buf, size_t len) {
switch (buf[0]) {
case 'i':
if M("interface") return true;
if M("implements") return true;
break;
case 'l':
if M("let") return true;
break;
case 'p':
if M("private") return true;
if M("package") return true;
if M("public") return true;
if M("protected") return true;
break;
case 's':
if M("static") return true;
break;
case 'y':
if M("yield") return true;
break;
}
return false;
}
#undef K
#undef M
static inline bool streq(const char *buf, size_t len, const char *s, size_t n) {
return len == n && !memcmp(buf, s, n);
}
static inline bool is_strict_restricted(const char *buf, size_t len) {
if (len == 4) return streq(buf, len, "eval", 4);
if (len == 9) return streq(buf, len, "arguments", 9);
return false;
}
#define CHAR_DIGIT 0x01
#define CHAR_XDIGIT 0x02
#define CHAR_ALPHA 0x04
#define CHAR_IDENT 0x08
#define CHAR_IDENT1 0x10
#define CHAR_WS 0x20
#define CHAR_OCTAL 0x40
static const uint8_t char_type[256] = {
['\t'] = CHAR_WS, ['\n'] = CHAR_WS, ['\r'] = CHAR_WS, [' '] = CHAR_WS,
['0'] = CHAR_DIGIT | CHAR_XDIGIT | CHAR_IDENT | CHAR_OCTAL,
['1'] = CHAR_DIGIT | CHAR_XDIGIT | CHAR_IDENT | CHAR_OCTAL,
['2'] = CHAR_DIGIT | CHAR_XDIGIT | CHAR_IDENT | CHAR_OCTAL,
['3'] = CHAR_DIGIT | CHAR_XDIGIT | CHAR_IDENT | CHAR_OCTAL,
['4'] = CHAR_DIGIT | CHAR_XDIGIT | CHAR_IDENT | CHAR_OCTAL,
['5'] = CHAR_DIGIT | CHAR_XDIGIT | CHAR_IDENT | CHAR_OCTAL,
['6'] = CHAR_DIGIT | CHAR_XDIGIT | CHAR_IDENT | CHAR_OCTAL,
['7'] = CHAR_DIGIT | CHAR_XDIGIT | CHAR_IDENT | CHAR_OCTAL,
['8'] = CHAR_DIGIT | CHAR_XDIGIT | CHAR_IDENT,
['9'] = CHAR_DIGIT | CHAR_XDIGIT | CHAR_IDENT,
['A'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['B'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['C'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['D'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['E'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['F'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['a'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['b'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['c'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['d'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['e'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['f'] = CHAR_XDIGIT | CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['G'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['H'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['I'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['J'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['K'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['L'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['M'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['N'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['O'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['P'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['Q'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['R'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['S'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['T'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['U'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['V'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['W'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['X'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['Y'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['Z'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['g'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['h'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['i'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['j'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['k'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['l'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['m'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['n'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['o'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['p'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['q'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['r'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['s'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['t'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['u'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['v'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['w'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['x'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['y'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1, ['z'] = CHAR_ALPHA | CHAR_IDENT | CHAR_IDENT1,
['_'] = CHAR_IDENT | CHAR_IDENT1,
['$'] = CHAR_IDENT | CHAR_IDENT1,
};
#define IS_DIGIT(c) (char_type[(uint8_t)(c)] & CHAR_DIGIT)
#define IS_XDIGIT(c) (char_type[(uint8_t)(c)] & CHAR_XDIGIT)
#define IS_IDENT(c) (char_type[(uint8_t)(c)] & CHAR_IDENT)
#define IS_IDENT1(c) (char_type[(uint8_t)(c)] & CHAR_IDENT1)
#define IS_OCTAL(c) (char_type[(uint8_t)(c)] & CHAR_OCTAL)
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
static inline bool is_function_keyword(const char *code, jsoff_t pos, jsoff_t end) {
if (pos + 8 > end) return false;
uint64_t word;
memcpy(&word, code + pos, 8);
if (word != 0x6e6f6974636e7566ULL) return false;
return (pos + 8 >= end) || !IS_IDENT(code[pos + 8]);
}
static inline bool is_async_function(const char *code, jsoff_t pos, jsoff_t end) {
if (pos + 5 > end) return false;
uint32_t word4;
memcpy(&word4, code + pos, 4);
if (word4 != 0x6e797361U || code[pos + 4] != 'c') return false;
if (pos + 5 < end && IS_IDENT(code[pos + 5])) return false;
jsoff_t scan = pos + 5;
while (scan < end && (
code[scan] == ' ' ||
code[scan] == '\t' ||
code[scan] == '\n' ||
code[scan] == '\r'
)) scan++;
return is_function_keyword(code, scan, end);
}
static bool code_has_function_decl(const char *code, size_t len) {
if (!memmem(code, len, "function", 8)) return false;
size_t pos = 0;
int target_depth = 0;
if (len > 0 && code[0] == '(') target_depth = 1;
int depth = 0;
while (pos < len) {
uint8_t c = (uint8_t)code[pos];
if (c == '"' || c == '\'' || c == '`') {
uint8_t quote = c;
pos++;
while (pos < len && (uint8_t)code[pos] != quote) {
if (code[pos] == '\\' && pos + 1 < len) pos++;
pos++;
}
if (pos < len) pos++;
continue;
}
if (c == '/' && pos + 1 < len) {
if (code[pos + 1] == '/') {
pos += 2;
while (pos < len && code[pos] != '\n') pos++;
continue;
}
if (code[pos + 1] == '*') {
pos += 2;
while (pos + 1 < len && !(code[pos] == '*' && code[pos + 1] == '/')) pos++;
if (pos + 1 < len) pos += 2;
continue;
}
}
if (c == '{') { depth++; pos++; continue; }
if (c == '}') {
if (depth <= target_depth) break;
depth--; pos++; continue;
}
if (depth == target_depth) {
if (c == 'f' && is_function_keyword(code, (jsoff_t)pos, (jsoff_t)len)) return true;
if (c == 'a' && is_async_function(code, (jsoff_t)pos, (jsoff_t)len)) return true;
}
pos++;
}
return false;
}
static const uint8_t single_char_tok[128] = {
['('] = TOK_LPAREN,
[')'] = TOK_RPAREN,
['{'] = TOK_LBRACE,
['}'] = TOK_RBRACE,
['['] = TOK_LBRACKET,
[']'] = TOK_RBRACKET,
[';'] = TOK_SEMICOLON,
[','] = TOK_COMMA,
[':'] = TOK_COLON,
['~'] = TOK_TILDA,
['#'] = TOK_HASH,
};
static bool is_space(int c) {
if (c < 0 || c >= 256) return false;
return (char_type[(uint8_t)c] & CHAR_WS) != 0;
}
static bool is_digit(int c) {
if (c < 0 || c >= 256) return false;
return (char_type[(uint8_t)c] & CHAR_DIGIT) != 0;
}
static bool is_alpha(int c) {
if (c < 0 || c >= 256) return false;
return (char_type[(uint8_t)c] & CHAR_ALPHA) != 0;
}
static bool is_ident_begin(int c) {
if (c < 0) return false;
if (c < 128) return (char_type[(uint8_t)c] & CHAR_IDENT1) != 0;
return (c & 0x80) != 0;
}
static bool is_ident_continue(int c) {
if (c < 0) return false;
if (c < 128) return (char_type[(uint8_t)c] & (CHAR_IDENT | CHAR_IDENT1)) != 0;
return (c & 0x80) != 0;
}
static int parse_unicode_escape(const char *buf, jsoff_t len, jsoff_t pos, uint32_t *codepoint) {
if (pos + 5 >= len) return 0;
if (buf[pos] != '\\' || buf[pos + 1] != 'u') return 0;
uint32_t cp = 0;
for (int i = 0; i < 4; i++) {
int c = (unsigned char)buf[pos + 2 + i];
if (!is_xdigit(c)) return 0;
cp <<= 4;
cp |= (c <= '9') ? (c - '0') : ((c | 0x20) - 'a' + 10);
}
*codepoint = cp;
return 6;
}
static bool is_unicode_ident_begin(uint32_t cp) {
if (cp < 128) return (char_type[(uint8_t)cp] & CHAR_IDENT1) != 0;
return true;
}
static bool is_unicode_ident_continue(uint32_t cp) {
if (cp < 128) return (char_type[(uint8_t)cp] & (CHAR_IDENT | CHAR_IDENT1)) != 0;
return true;
}
static size_t decode_ident_escapes(const char *src, size_t srclen, char *dst, size_t dstlen) {
size_t si = 0, di = 0;
while (si < srclen && di + 4 < dstlen) {
uint32_t cp;
int el = parse_unicode_escape(src, (jsoff_t)srclen, (jsoff_t)si, &cp);
if (el > 0) {
di += utf8_encode(cp, dst + di);
si += el;
} else dst[di++] = src[si++];
}
dst[di] = '\0';
return di;
}
static bool has_unicode_escape(const char *src, size_t len) {
if (len < 6) return false;
const char *end = src + len - 5;
const char *p = src;
while ((p = memchr(p, '\\', end - p)) != NULL) {
if (p[1] == 'u') return true;
p++;
}
return false;
}
static jsval_t js_mkstr_ident(struct js *js, const char *src, size_t srclen) {
if (!has_unicode_escape(src, srclen)) {
return js_mkstr(js, src, srclen);
}
char decoded[256];
size_t decoded_len = decode_ident_escapes(src, srclen, decoded, sizeof(decoded));
return js_mkstr(js, decoded, decoded_len);
}
static uint8_t parseident(const char *buf, jsoff_t len, jsoff_t *tlen) {
if (len == 0) return TOK_ERR;
unsigned char c = (unsigned char)buf[0];
jsoff_t i = 0;
if (c < 128 && c != '\\' && is_ident_begin(c)) {
i = 1;
while (i < len) {
c = (unsigned char)buf[i];
if (c >= 128 || c == '\\') goto slow_path_continue;
if (!is_ident_continue(c)) break;
i++;
}
*tlen = i;
return parsekeyword(buf, i);
}
if (c == '\\') {
uint32_t first_cp;
int esc_len = parse_unicode_escape(buf, len, 0, &first_cp);
if (esc_len <= 0 || !is_unicode_ident_begin(first_cp)) return TOK_ERR;
*tlen = esc_len;
goto slow_path_loop;
}
if (c >= 128) {
if ((c & 0xC0) == 0x80) return TOK_ERR;
int ws_len = is_unicode_space((const unsigned char *)buf, len, NULL);
if (ws_len > 0) return TOK_ERR;
i = 1;
while (i < len && ((unsigned char)buf[i] & 0xC0) == 0x80) i++;
*tlen = i;
goto slow_path_loop;
}
return TOK_ERR;
slow_path_continue:
*tlen = i;
slow_path_loop:;
int has_escapes = (buf[0] == '\\');
while (*tlen < len) {
c = (unsigned char)buf[*tlen];
if (c == '\\') {
uint32_t cp;
int el = parse_unicode_escape(buf, len, *tlen, &cp);
if (el <= 0 || !is_unicode_ident_continue(cp)) break;
*tlen += el;
has_escapes = 1;
} else if (c < 128) {
if (!is_ident_continue(c)) break;
(*tlen)++;
} else {
if ((c & 0xC0) == 0x80) break;
int ws_len = is_unicode_space((const unsigned char *)&buf[*tlen], len - *tlen, NULL);
if (ws_len > 0) break;
(*tlen)++;
while (*tlen < len && ((unsigned char)buf[*tlen] & 0xC0) == 0x80) (*tlen)++;
}
}
if (has_escapes) {
char decoded[256];
size_t decoded_len = decode_ident_escapes(buf, *tlen, decoded, sizeof(decoded));
return parsekeyword(decoded, decoded_len);
}
return parsekeyword(buf, *tlen);
}
static inline jsoff_t parse_decimal(const char *buf, jsoff_t maxlen, double *out) {
uint64_t int_part = 0, frac_part = 0;
int frac_digits = 0;
jsoff_t i = 0;
while (i < maxlen && (IS_DIGIT(buf[i]) || buf[i] == '_')) {
if (buf[i] != '_') int_part = int_part * 10 + (buf[i] - '0');
i++;
}
if (i < maxlen && buf[i] == '.') {
i++;
while (i < maxlen && (IS_DIGIT(buf[i]) || buf[i] == '_')) {
if (buf[i] != '_') { frac_part = frac_part * 10 + (buf[i] - '0'); frac_digits++; }
i++;
}
}
static const double neg_pow10[] = {
1e0,1e-1,1e-2,1e-3,1e-4,1e-5,1e-6,1e-7,1e-8,1e-9,1e-10,
1e-11,1e-12,1e-13,1e-14,1e-15,1e-16,1e-17,1e-18,1e-19,1e-20
};
static const double pos_pow10[] = {
1e0,1e1,1e2,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,
1e11,1e12,1e13,1e14,1e15,1e16,1e17,1e18,1e19,1e20
};
double val = (double)int_part;
if (frac_digits > 0) {
val += (frac_digits <= 20)
? (double)frac_part * neg_pow10[frac_digits]
: (double)frac_part * pow(10.0, -frac_digits);
}
if (i < maxlen && (buf[i] == 'e' || buf[i] == 'E')) {
i++;
int exp_sign = 1, exp_val = 0;
if (i < maxlen && (buf[i] == '+' || buf[i] == '-')) {
exp_sign = (buf[i] == '-') ? -1 : 1;
i++;
}
while (i < maxlen && (IS_DIGIT(buf[i]) || buf[i] == '_')) {
if (buf[i] != '_') exp_val = exp_val * 10 + (buf[i] - '0');
i++;
}
if (exp_val <= 20) {
val = (exp_sign > 0) ? val * pos_pow10[exp_val] : val * neg_pow10[exp_val];
} else val *= pow(10.0, exp_sign * exp_val);
}
*out = val;
return i;
}
static inline jsoff_t parse_binary(const char *buf, jsoff_t maxlen, double *out) {
double val = 0;
jsoff_t i = 2;
while (i < maxlen && (buf[i] == '0' || buf[i] == '1' || buf[i] == '_')) {
if (buf[i] != '_') val = val * 2 + (buf[i] - '0');
i++;
}
*out = val;
return i;
}
static inline jsoff_t parse_octal(const char *buf, jsoff_t maxlen, double *out) {
double val = 0;
jsoff_t i = 2;
while (i < maxlen && (IS_OCTAL(buf[i]) || buf[i] == '_')) {
if (buf[i] != '_') val = val * 8 + (buf[i] - '0');
i++;
}
*out = val;
return i;
}
static inline jsoff_t parse_legacy_octal(const char *buf, jsoff_t maxlen, double *out) {
double val = 0;
jsoff_t i = 1;
while (i < maxlen && IS_OCTAL(buf[i])) {
val = val * 8 + (buf[i] - '0');
i++;
}
*out = val;
return i;
}
static inline jsoff_t parse_hex(const char *buf, jsoff_t maxlen, double *out) {
double val = 0;
jsoff_t i = 2;
while (i < maxlen && (IS_XDIGIT(buf[i]) || buf[i] == '_')) {
if (buf[i] != '_') {
int d =
(buf[i] >= 'a') ? (buf[i] - 'a' + 10) :
(buf[i] >= 'A') ? (buf[i] - 'A' + 10) : (buf[i] - '0');
val = val * 16 + d;
} i++;
}
*out = val;
return i;
}
static inline uint8_t parse_number(struct js *js, const char *buf, jsoff_t remaining) {
double value = 0;
jsoff_t numlen = 0;
if (buf[0] == '0' && remaining > 1) {
char c1 = buf[1] | 0x20;
if (c1 == 'b') {
numlen = parse_binary(buf, remaining, &value);
} else if (c1 == 'o') {
numlen = parse_octal(buf, remaining, &value);
} else if (c1 == 'x') {
numlen = parse_hex(buf, remaining, &value);
} else if (IS_OCTAL(buf[1])) {
if (js->flags & F_STRICT) {
js->tok = TOK_ERR;
js->tlen = 1;
return TOK_ERR;
}
numlen = parse_legacy_octal(buf, remaining, &value);
} else if (is_digit(buf[1]) && (js->flags & F_STRICT)) {
js->tok = TOK_ERR;
js->tlen = 1;
return TOK_ERR;
} else numlen = parse_decimal(buf, remaining, &value);
} else numlen = parse_decimal(buf, remaining, &value);
js->tval = tov(value);
if (numlen < remaining && buf[numlen] == 'n') {
js->tok = TOK_BIGINT;
js->tlen = numlen + 1;
} else {
js->tok = TOK_NUMBER;
js->tlen = numlen;
}
return js->tok;
}
static inline uint8_t scan_string(struct js *js, const char *buf, jsoff_t rem, char quote) {
jsoff_t i = 1;
while (i < rem) {
const char *p = buf + i;
jsoff_t search_len = rem - i;
const char *q = memchr(p, quote, search_len);
const char *b = memchr(p, '\\', search_len);
if (q == NULL) {
js->tok = TOK_ERR;
js->tlen = rem;
return TOK_ERR;
}
if (b == NULL || q < b) {
i = (jsoff_t)((q - buf) + 1);
js->tok = TOK_STRING;
js->tlen = i;
return TOK_STRING;
}
jsoff_t esc_pos = (jsoff_t)(b - buf);
if (esc_pos + 1 >= rem) {
js->tok = TOK_ERR;
js->tlen = rem;
return TOK_ERR;
}
char esc_char = buf[esc_pos + 1];
jsoff_t skip = 2;
if (esc_char == 'x') { skip = 4; } else if (esc_char == 'u') {
skip = (esc_pos + 2 < rem && buf[esc_pos + 2] == '{') ? 0 : 6;
if (skip == 0) {
jsoff_t j = esc_pos + 3;
while (j < rem && buf[j] != '}') j++;
skip = (j < rem) ? (j - esc_pos + 1) : (rem - esc_pos);
}
}
if (esc_pos + skip > rem) {
js->tok = TOK_ERR;
js->tlen = rem;
return TOK_ERR;
}
i = esc_pos + skip;
}
js->tok = TOK_ERR;
js->tlen = rem;
return TOK_ERR;
}
static inline jsoff_t skip_string_literal(const char *buf, jsoff_t rem, jsoff_t start, char quote) {
jsoff_t i = start + 1;
while (i < rem) {
if (buf[i] == '\\') { i += 2; continue; }
if (buf[i] == quote) { return i + 1; } i++;
}
return rem;
}
static inline jsoff_t skip_line_comment(const char *buf, jsoff_t rem, jsoff_t start) {
jsoff_t i = start + 2;
while (i < rem && buf[i] != '\n') i++;
return i;
}
static inline jsoff_t skip_block_comment(const char *buf, jsoff_t rem, jsoff_t start) {
jsoff_t i = start + 2;
while (i + 1 < rem && !(buf[i] == '*' && buf[i + 1] == '/')) i++;
return (i + 1 < rem) ? (i + 2) : rem;
}
static jsoff_t skip_template_literal(const char *buf, jsoff_t rem, jsoff_t start) {
jsoff_t i = start + 1;
int expr_depth = 0;
while (i < rem) {
char c = buf[i];
if (c == '\\') {
i += 2;
continue;
}
if (expr_depth == 0) {
if (c == '`') return i + 1;
if (c == '$' && i + 1 < rem && buf[i + 1] == '{') {
expr_depth = 1;
i += 2;
continue;
} i++; continue;
}
if (c == '\'' || c == '"') {
i = skip_string_literal(buf, rem, i, c);
continue;
}
if (c == '`') {
jsoff_t next = skip_template_literal(buf, rem, i);
if (next <= i) return rem;
i = next; continue;
}
if (c == '/' && i + 1 < rem) {
if (buf[i + 1] == '/') { i = skip_line_comment(buf, rem, i); continue; }
if (buf[i + 1] == '*') { i = skip_block_comment(buf, rem, i); continue; }
}
if (c == '{') { expr_depth++; i++; continue; }
if (c == '}') { expr_depth--; i++; continue; }
i++;
}
return rem;
}
static inline uint8_t scan_template(struct js *js, const char *buf, jsoff_t rem) {
jsoff_t end = skip_template_literal(buf, rem, 0);
if (end <= 1 || end > rem) {
js->tok = TOK_ERR;
js->tlen = rem;
return TOK_ERR;
}
js->tok = TOK_TEMPLATE;
js->tlen = end;
return TOK_TEMPLATE;
}
static inline uint8_t parse_operator(struct js *js, const char *buf, jsoff_t rem) {
#define MATCH2(c1,c2) (rem >= 2 && buf[1] == (c2))
#define MATCH3(c1,c2,c3) (rem >= 3 && buf[1] == (c2) && buf[2] == (c3))
#define MATCH4(c1,c2,c3,c4) (rem >= 4 && buf[1]==(c2) && buf[2]==(c3) && buf[3]==(c4))
switch (buf[0]) {
case '?':
if (MATCH3('?','?','=')) { js->tok = TOK_NULLISH_ASSIGN; js->tlen = 3; }
else if (MATCH2('?','?')) { js->tok = TOK_NULLISH; js->tlen = 2; }
else if (MATCH2('?','.')) { js->tok = TOK_OPTIONAL_CHAIN; js->tlen = 2; }
else { js->tok = TOK_Q; js->tlen = 1; }
break;
case '!':
if (MATCH3('!','=','=')) { js->tok = TOK_SNE; js->tlen = 3; }
else if (MATCH2('!','=')) { js->tok = TOK_NE; js->tlen = 2; }
else { js->tok = TOK_NOT; js->tlen = 1; }
break;
case '=':
if (MATCH3('=','=','=')) { js->tok = TOK_SEQ; js->tlen = 3; }
else if (MATCH2('=','=')) { js->tok = TOK_EQ; js->tlen = 2; }
else if (MATCH2('=','>')) { js->tok = TOK_ARROW; js->tlen = 2; }
else { js->tok = TOK_ASSIGN; js->tlen = 1; }
break;
case '<':
if (MATCH3('<','<','=')) { js->tok = TOK_SHL_ASSIGN; js->tlen = 3; }
else if (MATCH2('<','<')) { js->tok = TOK_SHL; js->tlen = 2; }
else if (MATCH2('<','=')) { js->tok = TOK_LE; js->tlen = 2; }
else { js->tok = TOK_LT; js->tlen = 1; }
break;
case '>':
if (MATCH4('>','>','>','=')) { js->tok = TOK_ZSHR_ASSIGN; js->tlen = 4; }
else if (MATCH3('>','>','>')) { js->tok = TOK_ZSHR; js->tlen = 3; }
else if (MATCH3('>','>','=')) { js->tok = TOK_SHR_ASSIGN; js->tlen = 3; }
else if (MATCH2('>','>')) { js->tok = TOK_SHR; js->tlen = 2; }
else if (MATCH2('>','=')) { js->tok = TOK_GE; js->tlen = 2; }
else { js->tok = TOK_GT; js->tlen = 1; }
break;
case '&':
if (MATCH3('&','&','=')) { js->tok = TOK_LAND_ASSIGN; js->tlen = 3; }
else if (MATCH2('&','&')) { js->tok = TOK_LAND; js->tlen = 2; }
else if (MATCH2('&','=')) { js->tok = TOK_AND_ASSIGN; js->tlen = 2; }
else { js->tok = TOK_AND; js->tlen = 1; }
break;
case '|':
if (MATCH3('|','|','=')) { js->tok = TOK_LOR_ASSIGN; js->tlen = 3; }
else if (MATCH2('|','|')) { js->tok = TOK_LOR; js->tlen = 2; }
else if (MATCH2('|','=')) { js->tok = TOK_OR_ASSIGN; js->tlen = 2; }
else { js->tok = TOK_OR; js->tlen = 1; }
break;
case '+':
if (MATCH2('+','+')) { js->tok = TOK_POSTINC; js->tlen = 2; }
else if (MATCH2('+','=')) { js->tok = TOK_PLUS_ASSIGN; js->tlen = 2; }
else { js->tok = TOK_PLUS; js->tlen = 1; }
break;
case '-':
if (MATCH2('-','-')) { js->tok = TOK_POSTDEC; js->tlen = 2; }
else if (MATCH2('-','=')) { js->tok = TOK_MINUS_ASSIGN; js->tlen = 2; }
else { js->tok = TOK_MINUS; js->tlen = 1; }
break;
case '*':
if (MATCH2('*','*')) { js->tok = TOK_EXP; js->tlen = 2; }
else if (MATCH2('*','=')) { js->tok = TOK_MUL_ASSIGN; js->tlen = 2; }
else { js->tok = TOK_MUL; js->tlen = 1; }
break;
case '/':
if (MATCH2('/','=')) { js->tok = TOK_DIV_ASSIGN; js->tlen = 2; }
else { js->tok = TOK_DIV; js->tlen = 1; }
break;
case '%':
if (MATCH2('%','=')) { js->tok = TOK_REM_ASSIGN; js->tlen = 2; }
else { js->tok = TOK_REM; js->tlen = 1; }
break;
case '^':
if (MATCH2('^','=')) { js->tok = TOK_XOR_ASSIGN; js->tlen = 2; }
else { js->tok = TOK_XOR; js->tlen = 1; }
break;
case '.':
if (MATCH3('.','.', '.')) { js->tok = TOK_REST; js->tlen = 3; }
else if (rem > 1 && IS_DIGIT(buf[1])) {
double val;
js->tlen = parse_decimal(buf, rem, &val);
js->tval = tov(val);
js->tok = TOK_NUMBER;
}
else { js->tok = TOK_DOT; js->tlen = 1; }
break;
default:
return 0;
}
#undef MATCH2
#undef MATCH3
#undef MATCH4
return js->tok;
}
static token_stream_t *token_stream_create(void) {
token_stream_t *ts = (token_stream_t *)malloc(sizeof(token_stream_t));
if (!ts) return NULL;
ts->capacity = 64;
ts->tokens = (cached_token_t *)malloc(ts->capacity * sizeof(cached_token_t));
if (!ts->tokens) { free(ts); return NULL; }
ts->count = 0;
return ts;
}
static bool token_stream_push(token_stream_t *ts, struct js *js) {
if (ts->count >= ts->capacity) {
int new_cap = ts->capacity * 2;
cached_token_t *new_tokens = (cached_token_t *)realloc(ts->tokens, new_cap * sizeof(cached_token_t));
if (!new_tokens) return false;
ts->tokens = new_tokens;
ts->capacity = new_cap;
}
cached_token_t *ct = &ts->tokens[ts->count++];
ct->tok = js->tok;
ct->had_newline = js->had_newline ? 1 : 0;
ct->toff = js->toff;
ct->tlen = js->tlen;
ct->pos = js->pos;
ct->tval = js->tval;
return true;
}
static inline bool token_stream_next(struct js *js) {
token_stream_t *ts = (token_stream_t *)js->token_stream;
if (js->token_stream_pos >= ts->count) {
js->tok = TOK_EOF;
return false;
}
cached_token_t *ct = &ts->tokens[js->token_stream_pos++];
js->tok = ct->tok;
js->had_newline = ct->had_newline;
js->toff = ct->toff;
js->tlen = ct->tlen;
js->pos = ct->pos;
js->tval = ct->tval;
return true;
}
static inline void token_stream_sync_pos(struct js *js) {
if (!js->token_stream || js->code != js->token_stream_code) return;
token_stream_t *ts = (token_stream_t *)js->token_stream;
jsoff_t target = js->pos;
int lo = 0, hi = ts->count;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (ts->tokens[mid].toff < target) lo = mid + 1;
else hi = mid;
}
js->token_stream_pos = lo;
}
static token_stream_t *tokenize_body(struct js *js, const char *code, jsoff_t len) {
token_stream_t *ts = token_stream_create();
if (!ts) return NULL;
const char *saved_code = js->code;
jsoff_t saved_clen = js->clen;
jsoff_t saved_pos = js->pos;
uint8_t saved_tok = js->tok;
uint8_t saved_consumed = js->consumed;
js->code = code;
js->clen = len;
js->pos = 0;
js->consumed = 1;
while (next_raw(js) != TOK_EOF) {
if (!token_stream_push(ts, js)) {
free(ts->tokens);
free(ts);
ts = NULL;
break;
}
js->consumed = 1;
}
if (ts) {
cached_token_t eof_tok = {
.tok = TOK_EOF,
.had_newline = 0,
.toff = len,
.tlen = 0,
.pos = len,
.tval = 0
};
if (ts->count < ts->capacity ||
(ts->count >= ts->capacity &&
(ts->tokens = realloc(ts->tokens, (ts->capacity + 1) * sizeof(cached_token_t))) != NULL)) {
ts->tokens[ts->count++] = eof_tok;
}
}
js->code = saved_code;
js->clen = saved_clen;
js->pos = saved_pos;
js->tok = saved_tok;
js->consumed = saved_consumed;
return ts;
}
static uint8_t next_raw(struct js *js) {
if (likely(js->consumed == 0)) return js->tok;
js->consumed = 0;
js->tok = TOK_ERR;
js->toff = js->pos = skiptonext(js->code, js->clen, js->pos, &js->had_newline);
js->tlen = 0;
if (unlikely(js->toff >= js->clen)) {
js->tok = TOK_EOF;
return TOK_EOF;
}
const char *buf = js->code + js->toff;
jsoff_t rem = js->clen - js->toff;
uint8_t c = (uint8_t)buf[0];
if (likely(c < 128)) {
uint8_t simple_tok = single_char_tok[c];
if (simple_tok != 0) {
js->tok = simple_tok;
js->tlen = 1;
js->pos = js->toff + 1;
return simple_tok;
}
}
if (likely(IS_IDENT1(c))) {
js->tok = parseident(buf, rem, &js->tlen);
js->pos = js->toff + js->tlen;
return js->tok;
}
if (IS_DIGIT(c)) {
parse_number(js, buf, rem);
if (js->tlen == 0) js->tlen = 1;
js->pos = js->toff + js->tlen;
return js->tok;
}
if (c == '"' || c == '\'') {
scan_string(js, buf, rem, c);
if (js->tlen == 0) js->tlen = 1;
js->pos = js->toff + js->tlen;
return js->tok;
}
if (c == '`') {
scan_template(js, buf, rem);
if (js->tlen == 0) js->tlen = 1;
js->pos = js->toff + js->tlen;
return js->tok;
}
if (parse_operator(js, buf, rem)) {
if (js->tlen == 0) js->tlen = 1;
js->pos = js->toff + js->tlen;
return js->tok;
}
js->tok = parseident(buf, rem, &js->tlen);
if (js->tlen == 0) js->tlen = 1;
js->pos = js->toff + js->tlen;
return js->tok;
}
static uint8_t next(struct js *js) {
if (likely(js->consumed == 0)) return js->tok;
if (js->token_stream && js->code == js->token_stream_code) {
token_stream_t *ts = (token_stream_t *)js->token_stream;
int idx = js->token_stream_pos;
if (unlikely(idx > 0 && idx <= ts->count && js->pos != ts->tokens[idx - 1].pos)) {
token_stream_sync_pos(js);
}
js->consumed = 0;
token_stream_next(js);
return js->tok;
}
return next_raw(js);
}
static inline uint8_t lookahead(struct js *js) {
uint8_t old = js->tok, tok = 0;
uint8_t old_consumed = js->consumed;
jsoff_t pos = js->pos;
int stream_pos = js->token_stream_pos;
js->consumed = 1;
tok = next(js);
js->pos = pos;
js->tok = old;
js->consumed = old_consumed;
js->token_stream_pos = stream_pos;
return tok;
}
static bool is_typeof_bare_ident(struct js *js) {
jsoff_t pos = js->pos, toff = js->toff, tlen = js->tlen;
uint8_t tok = js->tok, consumed = js->consumed;
bool had_newline = js->had_newline;
int stream_pos = js->token_stream_pos;
int depth = 0;
uint8_t t = next(js);
while (t == TOK_LPAREN) { js->consumed = 1; t = next(js); depth++; }
bool bare = (t == TOK_IDENTIFIER);
if (bare) {
js->consumed = 1;
t = next(js);
while (depth > 0 && t == TOK_RPAREN) { js->consumed = 1; t = next(js); depth--; }
if (depth != 0 || t == TOK_DOT || t == TOK_LBRACKET || t == TOK_LPAREN || t == TOK_OPTIONAL_CHAIN) bare = false;
}
js->pos = pos; js->toff = toff; js->tlen = tlen;
js->tok = tok; js->consumed = consumed; js->had_newline = had_newline;
js->token_stream_pos = stream_pos;
return bare;
}
jsval_t js_mkscope(struct js *js) {
assert((js->flags & F_NOEXEC) == 0);
if (global_scope_stack == NULL) utarray_new(global_scope_stack, &jsoff_icd);
jsoff_t prev = (jsoff_t) vdata(js->scope);
utarray_push_back(global_scope_stack, &prev);
js->scope = mkobj(js, prev);
return js->scope;
}
void js_delscope(struct js *js) {
if (global_scope_stack && utarray_len(global_scope_stack) > 0) {
jsoff_t *prev = (jsoff_t *)utarray_back(global_scope_stack);
js->scope = mkval(T_OBJ, *prev);
utarray_pop_back(global_scope_stack);
} else js->scope = upper(js, js->scope);
}
static void mkscope(struct js *js) { (void)js_mkscope(js); }
static void delscope(struct js *js) { (void)js_delscope(js); }
static void for_let_push(struct js *js, const char *var_name, jsoff_t var_len, jsoff_t prop_off, jsval_t body_scope) {
if (js->for_let_stack_len >= js->for_let_stack_cap) {
int new_cap = js->for_let_stack_cap ? js->for_let_stack_cap * 2 : 4;
js->for_let_stack = realloc(js->for_let_stack, new_cap * sizeof(struct for_let_ctx));
js->for_let_stack_cap = new_cap;
}
js->for_let_stack[js->for_let_stack_len++] = (struct for_let_ctx){var_name, var_len, prop_off, body_scope};
}
static inline void for_let_set_body_scope(struct js *js, jsval_t body_scope) {
if (js->for_let_stack_len > 0) js->for_let_stack[js->for_let_stack_len - 1].body_scope = body_scope;
}
static inline void for_let_pop(struct js *js) {
if (js->for_let_stack_len > 0) js->for_let_stack_len--;
}
static inline struct for_let_ctx *for_let_current(struct js *js) {
return js->for_let_stack_len > 0 ? &js->for_let_stack[js->for_let_stack_len - 1] : NULL;
}
static void copy_body_scope_props(struct js *js, jsval_t body_scope, jsval_t closure_scope, const char *skip_var, jsoff_t skip_len) {
if (vtype(body_scope) != T_OBJ) return;
jsoff_t prop_off = loadoff(js, (jsoff_t)vdata(body_scope)) & ~(3U | FLAGMASK);
while (prop_off < js->brk && prop_off != 0) {
jsoff_t header = loadoff(js, prop_off);
if (is_slot_prop(header)) { prop_off = next_prop(header); continue; }
jsoff_t koff = loadoff(js, prop_off + (jsoff_t)sizeof(prop_off));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *)&js->mem[koff + sizeof(koff)];
jsval_t val = loadval(js, prop_off + (jsoff_t)(sizeof(prop_off) + sizeof(koff)));
prop_off = next_prop(header);
if (is_internal_prop(key, klen)) continue;
if (skip_var && klen == skip_len && memcmp(key, skip_var, klen) == 0) continue;
jsval_t key_str = js_mkstr(js, key, klen);
mkprop(js, closure_scope, key_str, val, 0);
}
}
static jsval_t for_let_capture_scope(struct js *js) {
struct for_let_ctx *flc = for_let_current(js);
if (!flc || flc->prop_off == 0) return js->scope;
jsval_t loop_var_val = resolveprop(js, mkval(T_PROP, flc->prop_off));
jsval_t closure_scope = js_mkscope(js);
if (is_err(closure_scope)) return closure_scope;
jsval_t var_key = js_mkstr(js, flc->var_name, flc->var_len);
mkprop(js, closure_scope, var_key, loop_var_val, 0);
if (vtype(flc->body_scope) == T_OBJ) {
copy_body_scope_props(js, flc->body_scope, closure_scope, flc->var_name, flc->var_len);
}
delscope(js);
return closure_scope;
}
static void scope_clear_props(struct js *js, jsval_t scope) {
jsoff_t off = (jsoff_t)vdata(scope);
jsoff_t header = loadoff(js, off);
jsoff_t parent = loadoff(js, off + sizeof(jsoff_t));
saveoff(js, off, (header & FLAGMASK) | T_OBJ);
saveoff(js, off + sizeof(jsoff_t), parent);
saveoff(js, off + sizeof(jsoff_t) + sizeof(jsoff_t), 0);
}
static bool block_needs_scope(struct js *js) {
jsoff_t pos = js->pos, toff = js->toff, tlen = js->tlen;
uint8_t tok = js->tok, consumed = js->consumed;
bool had_newline = js->had_newline;
int stream_pos = js->token_stream_pos;
bool needs = false;
int depth = 1;
js->consumed = 1;
while (depth > 0) {
uint8_t t = next(js);
if (t == TOK_EOF) break;
if (t == TOK_LBRACE) { depth++; js->consumed = 1; continue; }
if (t == TOK_RBRACE) { depth--; js->consumed = 1; continue; }
if (depth == 1 && (
t == TOK_LET ||
t == TOK_CONST ||
t == TOK_CLASS ||
t == TOK_FUNC
)) { needs = true; break; }
js->consumed = 1;
}
js->pos = pos; js->toff = toff; js->tlen = tlen;
js->tok = tok; js->consumed = consumed; js->had_newline = had_newline;
js->token_stream_pos = stream_pos;
return needs;
}
static inline bool push_this(jsval_t this_value) {
if (global_this_stack.depth >= global_this_stack.capacity) {
int new_capacity = global_this_stack.capacity == 0 ? 16 : global_this_stack.capacity * 2;
jsval_t *new_stack = (jsval_t *) realloc(global_this_stack.stack, new_capacity * sizeof(jsval_t));
if (!new_stack) return false;
global_this_stack.stack = new_stack;
global_this_stack.capacity = new_capacity;
}
global_this_stack.stack[global_this_stack.depth++] = this_value;
return true;
}
static inline jsval_t pop_this() {
if (global_this_stack.depth > 0) {
return global_this_stack.stack[--global_this_stack.depth];
}
return js_mkundef();
}
static inline jsval_t peek_this() {
if (global_this_stack.depth > 0) {
return global_this_stack.stack[global_this_stack.depth - 1];
}
return js_mkundef();
}
static jsval_t js_func_decl(struct js *js);
static jsval_t js_func_decl_async(struct js *js);
static void hoist_function_declarations(struct js *js) {
if (js->flags & F_NOEXEC) return;
if (js->is_hoisting) return;
if (js->skip_func_hoist) return;
if (!code_has_function_decl(js->code + js->pos, js->clen - js->pos)) return;
js->is_hoisting = true;
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
jsval_t saved_scope = js->scope;
int depth = 0;
uint8_t tok, prev_tok = TOK_EOF;
while ((tok = next(js)) != TOK_EOF && !(tok == TOK_RBRACE && depth == 0)) {
if (tok == TOK_LBRACE) { depth++; prev_tok = tok; js->consumed = 1; continue; }
if (tok == TOK_RBRACE) { depth--; prev_tok = tok; js->consumed = 1; continue; }
if (depth > 0) { prev_tok = tok; js->consumed = 1; continue; }
if (tok == TOK_EXPORT) {
js->consumed = 1;
uint8_t next_tok = next(js);
if (next_tok != TOK_FUNC && next_tok != TOK_ASYNC && next_tok != TOK_DEFAULT)
goto skip_export;
int brace_depth = 0;
while (next(js) != TOK_EOF) {
if (js->tok == TOK_LBRACE) brace_depth++;
else if (js->tok == TOK_RBRACE && --brace_depth <= 0) break;
js->consumed = 1;
}
skip_export: {
prev_tok = tok;
continue;
}
}
if (depth == 0 && tok == TOK_FUNC) {
if (expr_context_tok[prev_tok]) {
prev_tok = tok;
js->consumed = 1;
continue;
}
jsoff_t after_func = js->pos;
js->consumed = 1;
if (next(js) == TOK_IDENTIFIER) {
js->pos = after_func;
js->tok = TOK_FUNC;
js->consumed = 1;
js_func_decl(js);
}
prev_tok = tok;
continue;
}
if (depth == 0 && tok == TOK_ASYNC) {
if (expr_context_tok[prev_tok]) {
prev_tok = tok;
js->consumed = 1;
continue;
}
js->consumed = 1;
if (next(js) != TOK_FUNC) goto skip_async;
jsoff_t func_pos = js->pos;
js->consumed = 1;
if (next(js) != TOK_IDENTIFIER) goto skip_async;
js->pos = func_pos;
js->tok = TOK_FUNC;
js->consumed = 0;
js_func_decl_async(js);
skip_async: {
prev_tok = tok;
continue;
}
}
prev_tok = tok;
js->consumed = 1;
}
js->is_hoisting = false;
JS_RESTORE_STATE(js, saved);
js->scope = saved_scope;
}
static void declare_hoisted_vars(struct js *js, jsval_t var_scope, const char *var_names) {
const char *ptr = var_names;
while (*ptr) {
size_t len = strlen(ptr);
jsoff_t existing = lkp(js, var_scope, ptr, len);
if (existing == 0) mkprop(js, var_scope, js_mkstr(js, ptr, len), js_mkundef(), 0);
ptr += len + 1;
}
}
static void hoist_var_declarations_from_slot(struct js *js, jsval_t var_scope, jsval_t func_obj) {
jsval_t vars_val = get_slot(js, func_obj, SLOT_HOISTED_VARS);
if (vtype(vars_val) != T_CFUNC) return;
const char *var_names = (const char *)vdata(vars_val);
if (!var_names) return;
declare_hoisted_vars(js, var_scope, var_names);
}
static void hoist_var_declarations(struct js *js, jsval_t var_scope) {
if (js->flags & F_NOEXEC) return;
if (js->clen == 0) return;
if (!memmem(js->code, js->clen, "var", 3)) return;
size_t buf_len;
char *var_names = OXC_get_hoisted_vars(js->code, (size_t)js->clen, &buf_len);
if (!var_names) return;
declare_hoisted_vars(js, var_scope, var_names);
OXC_free_hoisted_vars(var_names, buf_len);
}
static jsval_t js_block(struct js *js, bool create_scope) {
jsval_t res = js_mkundef();
bool scope_created = false;
if (create_scope && lookahead(js) != TOK_RBRACE && block_needs_scope(js)) {
mkscope(js);
scope_created = true;
}
js->consumed = 1;
hoist_function_declarations(js);
uint8_t peek;
while ((peek = next(js)) != TOK_EOF && peek != TOK_RBRACE && !is_err(res)) {
uint8_t t = js->tok;
res = js_stmt(js);
if (!is_err(res) && !is_block_tok(t) && !(js->had_newline || is_asi_ok_tok(js->tok))) {
res = js_mkerr_typed(js, JS_ERR_SYNTAX, "; expected"); break;
}
if (js->flags & (F_RETURN | F_THROW)) break;
}
if (js->tok == TOK_RBRACE) js->consumed = 1;
if (scope_created) delscope(js);
return res;
}
static inline jsoff_t lkp_interned(struct js *js, jsval_t obj, const char *search_intern, size_t len) {
jsoff_t obj_off = (jsoff_t)vdata(obj);
jsoff_t tail = loadoff(js, obj_off + sizeof(jsoff_t) * 2);
uint32_t slot = (((uintptr_t)search_intern >> 3) ^ obj_off) & (ANT_LIMIT_SIZE_CACHE - 1);
intern_prop_cache_entry_t *ce = &intern_prop_cache[slot];
if (ce->generation == intern_prop_cache_gen
&& ce->obj_off == obj_off
&& ce->intern_ptr == search_intern
&& ce->tail == tail
) return ce->prop_off;
jsoff_t first_prop = loadoff(js, obj_off) & ~(3U | FLAGMASK);
jsoff_t off = first_prop; jsoff_t result = 0;
while (off < js->brk && off != 0) {
jsoff_t header = loadoff(js, off);
if (is_slot_prop(header)) { off = next_prop(header); continue; }
jsoff_t koff = loadoff(js, off + sizeof(jsoff_t));
jsoff_t klen = (loadoff(js, koff) >> 3) - 1;
if (klen == len) {
const char *p = (char *)&js->mem[koff + sizeof(jsoff_t)];
if (intern_string(p, klen) == search_intern) { result = off; break; }
}
off = next_prop(header);
}
ce->generation = intern_prop_cache_gen;
ce->obj_off = obj_off;
ce->intern_ptr = search_intern;
ce->prop_off = result;
ce->tail = tail;
return result;
}
inline jsoff_t lkp(struct js *js, jsval_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);
}
static jsval_t *resolve_bound_args(struct js *js, jsval_t func_obj, jsval_t *args, int nargs, int *out_nargs) {
*out_nargs = nargs;
jsval_t bound_arr = get_slot(js, func_obj, SLOT_BOUND_ARGS);
int bound_argc = 0;
if (vtype(bound_arr) == T_ARR) {
bound_argc = (int)get_array_length(js, bound_arr);
} if (bound_argc <= 0) return NULL;
*out_nargs = bound_argc + nargs;
jsval_t *combined = (jsval_t *)ant_calloc(sizeof(jsval_t) * (*out_nargs));
if (!combined) return NULL;
for (int i = 0; i < bound_argc; i++) combined[i] = arr_get(js, bound_arr, (jsoff_t)i);
for (int i = 0; i < nargs; i++) combined[bound_argc + i] = args[i];
return combined;
}
static jsoff_t lkp_scope(struct js *js, jsval_t scope, const char *buf, size_t len) {
const char *search_intern = intern_string(buf, len);
if (!search_intern) return 0;
jsoff_t scope_off = (jsoff_t)vdata(scope);
jsoff_t off = loadoff(js, scope_off) & ~(3U | FLAGMASK);
while (off < js->brk && off != 0) {
jsoff_t header = loadoff(js, off);
if (is_slot_prop(header)) { off = next_prop(header); continue; }
jsoff_t koff = loadoff(js, (jsoff_t)(off + sizeof(off)));
jsoff_t klen = (loadoff(js, koff) >> 3) - 1;
if (klen == len) {
const char *p = (char *)&js->mem[koff + sizeof(koff)];
if (intern_string(p, klen) == search_intern) return off;
}
off = next_prop(header);
}
return 0;
}
static jsoff_t lkp_with_getter(struct js *js, jsval_t obj, const char *buf, size_t len, jsval_t *getter_out, bool *has_getter_out) {
*has_getter_out = false;
*getter_out = js_mkundef();
for (jsval_t current = obj; is_object_type(current); ) {
jsoff_t current_off = (jsoff_t)vdata(current);
descriptor_entry_t *desc = lookup_descriptor(js, current_off, buf, len);
if (desc && desc->has_getter) {
*getter_out = desc->getter;
*has_getter_out = true;
return current_off;
}
jsoff_t prop_off = lkp_interned(js, current, intern_string(buf, len), len);
if (prop_off != 0) return prop_off;
jsval_t proto = get_proto(js, current);
if (!is_object_type(proto)) break;
current = proto;
}
return 0;
}
static jsoff_t lkp_with_setter(struct js *js, jsval_t obj, const char *buf, size_t len, jsval_t *setter_out, bool *has_setter_out) {
*has_setter_out = false;
*setter_out = js_mkundef();
jsval_t current = obj;
while (vtype(current) == T_OBJ || vtype(current) == T_FUNC) {
jsoff_t current_off = (jsoff_t)vdata(current);
descriptor_entry_t *desc = lookup_descriptor(js, current_off, buf, len);
if (desc && desc->has_setter) {
*setter_out = desc->setter;
*has_setter_out = true;
return current_off;
}
jsoff_t prop_off = lkp_interned(js, current, intern_string(buf, len), len);
if (prop_off != 0) return prop_off;
jsval_t proto = get_proto(js, current);
if (vtype(proto) != T_OBJ && vtype(proto) != T_FUNC) break;
current = proto;
}
return 0;
}
static jsval_t call_proto_accessor(struct js *js, jsval_t prim, jsval_t accessor, bool has_accessor, jsval_t *arg, int arg_count, bool is_setter) {
if (!has_accessor || (vtype(accessor) != T_FUNC && vtype(accessor) != T_CFUNC)) return js_mkundef();
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
uint8_t saved_flags = js->flags;
jsoff_t saved_toff = js->toff;
jsoff_t saved_tlen = js->tlen;
jsval_t saved_this = js->this_val;
js->this_val = prim;
push_this(prim);
jsval_t result = call_js_with_args(js, accessor, arg, arg_count);
pop_this();
js->this_val = saved_this;
JS_RESTORE_STATE(js, saved);
js->flags = saved_flags;
js->toff = saved_toff;
js->tlen = saved_tlen;
if (is_setter) return is_err(result) ? result : (arg ? *arg : js_mkundef());
return result;
}
jsval_t js_get_proto(struct js *js, jsval_t obj) {
uint8_t t = vtype(obj);
if (!is_object_type(obj)) return js_mknull();
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
jsval_t proto = get_slot(js, as_obj, SLOT_PROTO);
if (is_object_type(proto)) return proto;
if (t != T_OBJ) return get_prototype_for_type(js, t);
return js_mknull();
}
static jsval_t get_proto(struct js *js, jsval_t obj) {
return js_get_proto(js, obj);
}
void js_set_proto(struct js *js, jsval_t obj, jsval_t proto) {
uint8_t t = vtype(obj);
if (!is_object_type(obj)) return;
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
set_slot(js, as_obj, SLOT_PROTO, proto);
}
static void set_proto(struct js *js, jsval_t obj, jsval_t proto) {
js_set_proto(js, obj, proto);
}
jsval_t js_get_ctor_proto(struct js *js, const char *name, size_t len) {
jsoff_t ctor_off = lkp_scope(js, js->scope, name, len);
if (ctor_off == 0 && global_scope_stack) {
unsigned int stack_len = utarray_len(global_scope_stack);
for (int i = (int)stack_len - 1; i >= 0 && ctor_off == 0; i--) {
jsoff_t *scope_off = (jsoff_t *)utarray_eltptr(global_scope_stack, (unsigned int)i);
jsval_t scope = mkval(T_OBJ, *scope_off);
ctor_off = lkp_scope(js, scope, name, len);
}
} else if (ctor_off == 0) {
for (jsval_t scope = upper(js, js->scope); vdata(scope) != 0 && ctor_off == 0; scope = upper(js, scope)) {
ctor_off = lkp_scope(js, scope, name, len);
}
}
if (ctor_off == 0) return js_mknull();
jsval_t ctor = resolveprop(js, mkval(T_PROP, ctor_off));
if (vtype(ctor) != T_FUNC) return js_mknull();
jsval_t ctor_obj = mkval(T_OBJ, vdata(ctor));
jsoff_t proto_off = lkp_interned(js, ctor_obj, INTERN_PROTOTYPE, 9);
if (proto_off == 0) return js_mknull();
return resolveprop(js, mkval(T_PROP, proto_off));
}
static inline jsval_t get_ctor_proto(struct js *js, const char *name, size_t len) {
return js_get_ctor_proto(js, name, len);
}
static jsval_t get_prototype_for_type(struct js *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);
default: return js_mknull();
}
}
jsoff_t lkp_proto(struct js *js, jsval_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;
if (len == STR_PROTO_LEN && memcmp(key, STR_PROTO, STR_PROTO_LEN) == 0) return 0;
jsval_t cur = obj;
int depth = 0;
while (depth < 32) {
if (t == T_OBJ || t == T_ARR || t == T_FUNC || t == T_PROMISE) {
jsval_t as_obj = mkval(T_OBJ, vdata(cur));
jsoff_t off = lkp_interned(js, as_obj, key_intern, len);
if (off != 0) return off;
jsval_t proto = get_slot(js, as_obj, SLOT_PROTO);
uint8_t pt = vtype(proto);
if (pt == T_NULL) break;
if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC) {
if (TYPE_FLAG(t) & T_NEEDS_PROTO_FALLBACK) {
cur = get_prototype_for_type(js, t);
t = vtype(cur);
if (t == T_NULL || t == T_UNDEF) break;
depth++; continue;
}
break;
}
cur = proto;
t = vtype(cur);
if (t == T_NULL || t == T_UNDEF) break;
depth++;
} else if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) {
cur = get_prototype_for_type(js, t);
t = vtype(cur);
if (t == T_NULL || t == T_UNDEF) break;
depth++;
} else if (t == T_CFUNC) {
jsval_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) {
jsoff_t off = lkp(js, mkval(T_OBJ, vdata(func_proto)), key, len);
if (off != 0) return off;
}
break;
} else {
break;
}
}
return 0;
}
static jsval_t getprop_any(struct js *js, jsval_t obj, const char *key, size_t key_len) {
uint8_t t = vtype(obj);
if (t == T_STR && key_len == 6 && memcmp(key, "length", 6) == 0) {
jsoff_t byte_len;
jsoff_t str_off = vstr(js, obj, &byte_len);
return tov(D(utf16_strlen((const char *)&js->mem[str_off], byte_len)));
}
if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) {
jsoff_t off = lkp_proto(js, obj, key, key_len);
if (off != 0) return resolveprop(js, mkval(T_PROP, off));
return js_mkundef();
}
if (t == T_OBJ || t == T_ARR || t == T_FUNC) {
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
jsoff_t off = lkp(js, as_obj, key, key_len);
if (off != 0) return resolveprop(js, mkval(T_PROP, off));
off = lkp_proto(js, obj, key, key_len);
if (off != 0) return resolveprop(js, mkval(T_PROP, off));
}
return js_mkundef();
}
static jsval_t try_dynamic_getter(struct js *js, jsval_t obj, const char *key, size_t key_len) {
jsoff_t obj_off = (jsoff_t)vdata(obj);
dynamic_accessors_t *entry = NULL;
HASH_FIND(hh, accessor_registry, &obj_off, sizeof(jsoff_t), entry);
if (!entry || !entry->getter) return js_mkundef();
return entry->getter(js, obj, key, key_len);
}
static bool try_dynamic_setter(struct js *js, jsval_t obj, const char *key, size_t key_len, jsval_t value) {
jsoff_t obj_off = (jsoff_t)vdata(obj);
dynamic_accessors_t *entry = NULL;
HASH_FIND(hh, accessor_registry, &obj_off, sizeof(jsoff_t), entry);
if (!entry || !entry->setter) return false;
return entry->setter(js, obj, key, key_len, value);
}
static bool try_dynamic_deleter(struct js *js, jsval_t obj, const char *key, size_t key_len) {
jsoff_t obj_off = (jsoff_t)vdata(obj);
dynamic_accessors_t *entry = NULL;
HASH_FIND(hh, accessor_registry, &obj_off, sizeof(jsoff_t), entry);
if (!entry || !entry->deleter) return false;
return entry->deleter(js, obj, key, key_len);
}
static jsval_t lookup(struct js *js, const char *buf, size_t len) {
if (js->flags & F_NOEXEC) return 0;
char decoded[256];
const char *key_str = buf;
size_t key_len = len;
if (has_unicode_escape(buf, len)) {
key_len = decode_ident_escapes(buf, len, decoded, sizeof(decoded));
key_str = decoded;
}
if (key_len == STR_PROTO_LEN && memcmp(key_str, STR_PROTO, STR_PROTO_LEN) == 0) {
jsval_t proto = get_slot(js, js->scope, SLOT_PROTO);
if (vtype(proto) != T_UNDEF) return proto;
return get_prototype_for_type(js, vtype(js->scope));
}
const char *key_intern = intern_string(key_str, key_len);
jsval_t parent_scope = upper(js, js->scope);
jsoff_t off = lkp_interned(js, js->scope, key_intern, key_len);
if (off != 0) {
return mkval(T_PROP, off);
}
jsval_t with_slot = get_slot(js, js->scope, SLOT_WITH);
if (vtype(with_slot) != T_UNDEF) {
jsval_t with_obj = (
vtype(with_slot) == T_OBJ ||
vtype(with_slot) == T_ARR ||
vtype(with_slot) == T_FUNC) ?
with_slot : mkval(T_OBJ, vdata(with_slot)
);
jsoff_t prop_off = lkp_interned(js, with_obj, key_intern, key_len);
if (prop_off != 0) {
jsval_t key = js_mkstr(js, key_str, key_len);
if (is_err(key)) return key;
return mkpropref((jsoff_t)vdata(with_obj), (jsoff_t)vdata(key));
}
}
uint8_t depth = 1;
for (jsval_t scope = parent_scope; depth < 255; depth++) {
off = lkp_interned(js, scope, key_intern, key_len);
if (off != 0) {
return mkval(T_PROP, off);
}
jsval_t scope_with_slot = get_slot(js, scope, SLOT_WITH);
if (vtype(scope_with_slot) != T_UNDEF) {
jsval_t with_obj = (
vtype(scope_with_slot) == T_OBJ ||
vtype(scope_with_slot) == T_ARR ||
vtype(scope_with_slot) == T_FUNC) ?
scope_with_slot : mkval(T_OBJ, vdata(scope_with_slot)
);
jsoff_t prop_off = lkp_interned(js, with_obj, key_intern, key_len);
if (prop_off != 0) {
jsval_t key = js_mkstr(js, key_str, key_len);
if (is_err(key)) return key;
return mkpropref((jsoff_t)vdata(with_obj), (jsoff_t)vdata(key));
}
}
if (vdata(scope) == 0) break;
scope = upper(js, scope);
}
if (global_scope_stack && utarray_len(global_scope_stack) > 0) {
jsoff_t *root_off = (jsoff_t *)utarray_eltptr(global_scope_stack, 0);
if (root_off && *root_off != 0) {
jsval_t root_scope = mkval(T_OBJ, *root_off);
jsoff_t root_lkp_off = lkp(js, root_scope, key_str, key_len);
if (root_lkp_off != 0) return mkval(T_PROP, root_lkp_off);
}
}
return js_mkerr_typed(js, JS_ERR_REFERENCE, "'%.*s' is not defined", (int) key_len, key_str);
}
static bool try_accessor_getter(struct js *js, jsval_t obj, const char *key, size_t key_len, jsval_t *out) {
jsval_t getter = js_mkundef();
bool has_getter = false;
lkp_with_getter(js, obj, key, key_len, &getter, &has_getter);
jsval_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 inline jsval_t resolve_array_length(struct js *js, jsoff_t obj_off) {
return tov(D(get_array_length(js, mkval(T_ARR, obj_off))));
}
static inline jsval_t resolve_array_index_prop(struct js *js, jsoff_t obj_off, const char *key_str, jsoff_t len) {
jsval_t arr = mkval(T_ARR, obj_off);
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
unsigned long idx = 0;
for (jsoff_t i = 0; i < len; i++) idx = idx * 10 + (key_str[i] - '0');
jsoff_t dlen = dense_length(js, doff);
if (idx < dlen) {
jsval_t v = dense_get(js, doff, (jsoff_t)idx);
if (!is_empty_slot(v)) return v;
}
}
jsoff_t prop_off = lkp(js, arr, key_str, len);
if (prop_off != 0) return resolveprop(js, mkval(T_PROP, prop_off));
return js_mkundef();
}
static inline jsval_t resolve_prim_propref(struct js *js, jsval_t v) {
prim_propref_data_t *prim_data = prim_propref_get(v);
if (!prim_data) return js_mkundef();
jsval_t prim = prim_data->prim_val;
jsval_t key = mkval(T_STR, prim_data->key_off);
jsoff_t key_len; const char *key_str = (const char *)&js->mem[vstr(js, key, &key_len)];
jsval_t proto = get_prototype_for_type(js, vtype(prim));
if (vtype(proto) == T_OBJ) {
jsval_t getter = js_mkundef();
bool has_getter = false;
lkp_with_getter(js, proto, key_str, key_len, &getter, &has_getter);
jsval_t result = call_proto_accessor(js, prim, getter, has_getter, NULL, 0, false);
if (vtype(result) != T_UNDEF) return result;
jsoff_t off = lkp_proto(js, prim, key_str, key_len);
if (off != 0) return resolveprop(js, mkval(T_PROP, off));
}
return js_mkundef();
}
static inline jsval_t resolve_array_named_prop(struct js *js, jsoff_t obj_off, const char *key_str, jsoff_t len) {
jsval_t obj = mkval(T_ARR, obj_off);
if (len == STR_PROTO_LEN && memcmp(key_str, STR_PROTO, STR_PROTO_LEN) == 0) {
jsval_t proto = get_slot(js, obj, SLOT_PROTO);
if (vtype(proto) != T_UNDEF) return proto;
return get_prototype_for_type(js, T_ARR);
}
jsval_t proto = get_slot(js, obj, SLOT_PROTO);
if (vtype(proto) == T_UNDEF || vtype(proto) == T_NULL) {
proto = get_prototype_for_type(js, T_ARR);
}
if (is_object_type(proto)) {
const char *key_intern = intern_string(key_str, len);
if (key_intern) {
jsoff_t off = lkp_interned(js, proto, key_intern, len);
if (off != 0) return resolveprop(js, mkval(T_PROP, off));
}
jsoff_t proto_off = lkp_proto(js, proto, key_str, len);
if (proto_off != 0) return resolveprop(js, mkval(T_PROP, proto_off));
}
jsoff_t prop_off = lkp(js, obj, key_str, len);
if (prop_off != 0) return resolveprop(js, mkval(T_PROP, prop_off));
jsval_t dyn_result = try_dynamic_getter(js, obj, key_str, len);
if (vtype(dyn_result) != T_UNDEF) return dyn_result;
return js_mkundef();
}
static inline jsval_t resolve_object_prop(struct js *js, jsval_t obj, const char *key_str, jsoff_t len) {
if (is_proxy(js, obj)) return proxy_get(js, obj, key_str, len);
if (len == STR_PROTO_LEN && memcmp(key_str, STR_PROTO, STR_PROTO_LEN) == 0) {
jsval_t proto = get_slot(js, obj, SLOT_PROTO);
if (vtype(proto) != T_UNDEF) return proto;
return get_prototype_for_type(js, vtype(obj));
}
jsoff_t prop_off = lkp(js, obj, key_str, len);
if (prop_off != 0) return resolveprop(js, mkval(T_PROP, prop_off));
jsoff_t proto_off = lkp_proto(js, obj, key_str, len);
if (proto_off != 0) return resolveprop(js, mkval(T_PROP, proto_off));
jsval_t accessor_result;
if (try_accessor_getter(js, obj, key_str, len, &accessor_result)) {
return accessor_result;
}
jsval_t dyn_result = try_dynamic_getter(js, obj, key_str, len);
if (vtype(dyn_result) != T_UNDEF) return dyn_result;
return js_mkundef();
}
jsval_t resolveprop(struct js *js, jsval_t v) {
if (vtype(v) == T_PROPREF) {
if (is_prim_propref(v)) return resolve_prim_propref(js, v);
jsoff_t obj_off = propref_obj(v);
jsoff_t key_off = propref_key(v);
jsval_t key = mkval(T_STR, key_off);
jsoff_t len; const char *key_str = (const char *)&js->mem[vstr(js, key, &len)];
if (is_arr_off(js, obj_off) && streq(key_str, len, "length", 6)) return resolve_array_length(js, obj_off);
if (is_arr_off(js, obj_off) && len > 0 && key_str[0] >= '0' && key_str[0] <= '9') {
if (is_array_index(key_str, len)) return resolve_array_index_prop(js, obj_off, key_str, len);
}
if (is_arr_off(js, obj_off) && len > 0 && (key_str[0] < '0' || key_str[0] > '9')) {
return resolve_array_named_prop(js, obj_off, key_str, len);
}
uint8_t obj_type = is_arr_off(js, obj_off)
? T_ARR : (is_func_off(js, obj_off) ? T_FUNC : T_OBJ);
jsval_t obj = mkval(obj_type, obj_off);
return resolve_object_prop(js, obj, key_str, len);
}
if (vtype(v) != T_PROP) return v;
return resolveprop(js, loadval(js, (jsoff_t) (vdata(v) + sizeof(jsoff_t) * 2)));
}
static bool try_accessor_setter(struct js *js, jsval_t obj, const char *key, size_t key_len, jsval_t val, jsval_t *out) {
jsval_t setter = js_mkundef();
bool has_setter = false;
lkp_with_setter(js, obj, key, key_len, &setter, &has_setter);
if (!has_setter) return false;
jsval_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;
}
static jsval_t assign_array_index_fast(
struct js *js, jsval_t obj,
jsoff_t obj_off, jsval_t key,
const char *key_str,
jsoff_t key_len, jsval_t val
) {
int is_numeric = 1;
unsigned long idx = 0;
for (jsoff_t ci = 0; ci < key_len; ci++) {
if (key_str[ci] < '0' || key_str[ci] > '9') { is_numeric = 0; break; }
idx = idx * 10 + (key_str[ci] - '0');
}
if (!is_numeric || (key_len > 1 && key_str[0] == '0')) return js_mkundef();
jsoff_t doff = get_dense_buf(js, obj);
if (doff) {
jsoff_t cur_len = dense_length(js, doff);
jsoff_t density_limit = cur_len > 0 ? cur_len * 4 : 64;
if (idx < density_limit) { arr_set(js, obj, (jsoff_t)idx, val); return val; }
}
int known_new = 0; jsoff_t tail = loadoff(js, obj_off + sizeof(jsoff_t) * 2);
if (tail != 0 && tail < js->brk) {
jsoff_t tail_koff = loadoff(js, tail + sizeof(jsoff_t));
jsoff_t tail_klen = offtolen(loadoff(js, tail_koff));
const char *tail_key = (char *)&js->mem[tail_koff + sizeof(jsoff_t)];
if (tail_klen > 0 && tail_key[0] >= '0' && tail_key[0] <= '9') {
unsigned long tail_idx = 0;
int tail_valid = 1;
for (jsoff_t ci = 0; ci < tail_klen; ci++) {
if (tail_key[ci] < '0' || tail_key[ci] > '9') { tail_valid = 0; break; }
tail_idx = tail_idx * 10 + (tail_key[ci] - '0');
}
if (tail_valid && idx > tail_idx) known_new = 1;
}
}
jsoff_t existing = 0;
if (!known_new) {
const char *interned = intern_string(key_str, key_len);
existing = interned ? lkp_interned(js, obj, interned, key_len) : 0;
}
if (existing != 0) {
saveval(js, existing + sizeof(jsoff_t) * 2, val);
} else {
mkprop_fast(js, obj, key, val, 0);
jsoff_t len_off = lkp_interned(js, obj, INTERN_LENGTH, 6);
jsoff_t cur_len = 0;
if (len_off != 0) {
jsval_t lv = resolveprop(js, mkval(T_PROP, len_off));
if (vtype(lv) == T_NUM) cur_len = (jsoff_t) tod(lv);
}
if (idx >= cur_len) {
jsval_t new_len = tov((double)(idx + 1));
if (len_off != 0) saveval(js, len_off + sizeof(jsoff_t) * 2, new_len);
else js_mkprop_fast(js, obj, "length", 6, new_len);
}
}
return val;
}
static jsval_t assign(struct js *js, jsval_t lhs, jsval_t val) {
if (js->flags & F_NOEXEC) return val;
if (vtype(lhs) == T_PROPREF) {
if (is_prim_propref(lhs)) {
prim_propref_data_t *prim_data = prim_propref_get(lhs);
if (!prim_data) {
if (js->flags & F_STRICT) return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot create property on primitive value");
return val;
}
jsval_t prim = prim_data->prim_val;
jsval_t key = mkval(T_STR, prim_data->key_off);
jsoff_t key_len;
const char *key_str = (const char *)&js->mem[vstr(js, key, &key_len)];
jsval_t proto = get_prototype_for_type(js, vtype(prim));
if (vtype(proto) == T_OBJ) {
jsval_t setter = js_mkundef();
bool has_setter = false;
lkp_with_setter(js, proto, key_str, key_len, &setter, &has_setter);
jsval_t result = call_proto_accessor(js, prim, setter, has_setter, &val, 1, true);
if (vtype(result) != T_UNDEF) return result;
}
if (js->flags & F_STRICT) {
return js_mkerr_typed(
js, JS_ERR_TYPE, "Cannot create property '%.*s' on %s",
(int)key_len, key_str, typestr(vtype(prim))
);
}
return val;
}
jsoff_t obj_off = propref_obj(lhs);
jsoff_t key_off = propref_key(lhs);
jsval_t obj = mkval(is_arr_off(js, obj_off) ? T_ARR : T_OBJ, obj_off);
jsval_t key = mkval(T_STR, key_off);
jsoff_t key_len;
const char *key_str = (const char *)&js->mem[vstr(js, key, &key_len)];
if (vtype(obj) == T_ARR && !is_proxy(js, obj) && key_len > 0 && key_str[0] >= '0' && key_str[0] <= '9') {
jsval_t result = assign_array_index_fast(js, obj, obj_off, key, key_str, key_len, val);
if (vtype(result) != T_UNDEF) return result;
}
jsval_t setter_result;
if (try_accessor_setter(js, obj, key_str, key_len, val, &setter_result)) {
return setter_result;
} return js_setprop(js, obj, key, val);
}
if (vtype(lhs) != T_PROP) {
if (js->flags & F_STRICT) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "Invalid left-hand side in assignment");
} return val;
}
jsoff_t propoff = (jsoff_t) vdata(lhs);
jsoff_t koff = loadoff(js, propoff + sizeof(jsoff_t));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *)&js->mem[koff + sizeof(jsoff_t)];
if (is_const_prop(js, propoff)) return js_mkerr(js, "assignment to constant");
if ((klen == 9 && memcmp(key, "undefined", 9) == 0) ||
(klen == 3 && memcmp(key, "NaN", 3) == 0) ||
(klen == 8 && memcmp(key, "Infinity", 8) == 0)) {
if (js->flags & F_STRICT) return js_mkerr(js, "Cannot assign to read only property");
return lhs;
}
saveval(js, (jsoff_t) ((vdata(lhs) & ~3ULL) + sizeof(jsoff_t) * 2), val);
return lhs;
}
static jsval_t do_assign_op(struct js *js, uint8_t op, jsval_t l, jsval_t r) {
uint8_t m[] = {
TOK_PLUS, TOK_MINUS, TOK_MUL, TOK_DIV, TOK_REM, TOK_SHL,
TOK_SHR, TOK_ZSHR, TOK_AND, TOK_XOR, TOK_OR
};
jsval_t res = do_op(js, m[op - TOK_PLUS_ASSIGN], resolveprop(js, l), r);
return assign(js, l, res);
}
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 jsval_t string_builder_finalize(struct js *js, string_builder_t *sb) {
jsval_t result = js_mkstr(js, sb->buffer, sb->size);
if (sb->is_dynamic && sb->buffer) free(sb->buffer);
return result;
}
static inline jsoff_t str_len_fast(struct js *js, jsval_t str) {
if (vtype(str) != T_STR) return 0;
jsoff_t off = (jsoff_t) vdata(str);
jsoff_t header = loadoff(js, off);
if (header & ROPE_FLAG) {
return offtolen(header & ~(ROPE_FLAG | (ROPE_DEPTH_MASK << ROPE_DEPTH_SHIFT)));
}
return offtolen(header);
}
static jsval_t do_string_op(struct js *js, uint8_t op, jsval_t l, jsval_t r) {
if (op == TOK_PLUS) {
jsoff_t n1 = str_len_fast(js, l);
jsoff_t n2 = str_len_fast(js, r);
jsoff_t total_len = n1 + n2;
if (n2 == 0) return l;
if (n1 == 0) return r;
uint8_t left_depth = (vtype(l) == T_STR && is_rope(js, l)) ? rope_depth(js, l) : 0;
uint8_t right_depth = (vtype(r) == T_STR && is_rope(js, r)) ? rope_depth(js, 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) {
jsval_t flat_l = l, flat_r = r;
if (is_rope(js, l)) flat_l = rope_flatten(js, l);
if (is_err(flat_l)) return flat_l;
if (is_rope(js, r)) flat_r = rope_flatten(js, r);
if (is_err(flat_r)) return flat_r;
jsoff_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 *)&js->mem[off1], len1) ||
!string_builder_append(&sb, (char *)&js->mem[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);
}
jsoff_t n1, off1 = vstr(js, l, &n1);
jsoff_t n2, off2 = vstr(js, r, &n2);
if (op == TOK_EQ) {
bool eq = n1 == n2 && memcmp(&js->mem[off1], &js->mem[off2], n1) == 0;
return mkval(T_BOOL, eq ? 1 : 0);
} else if (op == TOK_NE) {
bool eq = n1 == n2 && memcmp(&js->mem[off1], &js->mem[off2], n1) == 0;
return mkval(T_BOOL, eq ? 0 : 1);
} else if (op == TOK_LT || op == TOK_LE || op == TOK_GT || op == TOK_GE) {
jsoff_t min_len = n1 < n2 ? n1 : n2;
int cmp = memcmp(&js->mem[off1], &js->mem[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");
}
static jsval_t do_bracket_op(struct js *js, jsval_t l, jsval_t r) {
jsval_t obj = resolveprop(js, l);
jsval_t key_val = resolveprop(js, r);
char keybuf[64];
const char *keystr;
size_t keylen;
if (vtype(key_val) == T_NUM) {
double dv = tod(key_val);
if (dv >= 0 && dv <= 0xFFFFFFFF && dv == (double)(uint32_t)dv) {
keylen = uint_to_str(keybuf, sizeof(keybuf), (uint32_t)dv);
} else {
keylen = strnum(key_val, keybuf, sizeof(keybuf));
}
keystr = keybuf;
} else if (vtype(key_val) == T_STR) {
jsoff_t slen;
jsoff_t off = vstr(js, key_val, &slen);
keystr = (char *) &js->mem[off];
keylen = slen;
} else if (vtype(key_val) == T_SYMBOL) {
sym_to_prop_key(key_val, keybuf, sizeof(keybuf));
keystr = keybuf;
keylen = strlen(keybuf);
} else {
jsval_t str_val = js_tostring_val(js, key_val);
if (is_err(str_val)) return str_val;
jsoff_t slen;
jsoff_t off = vstr(js, str_val, &slen);
keystr = (char *) &js->mem[off];
keylen = slen;
}
if (streq(keystr, keylen, "length", 6)) {
if (vtype(obj) == T_STR) {
jsoff_t byte_len;
jsoff_t str_off = vstr(js, obj, &byte_len);
const char *str_data = (const char *)&js->mem[str_off];
return tov(D(utf16_strlen(str_data, byte_len)));
}
if (vtype(obj) == T_ARR) return mkpropref((jsoff_t)vdata(obj), (jsoff_t)vdata(js->length_str));
}
if (vtype(obj) == T_STR) {
double idx_d = JS_NAN;
if (vtype(key_val) == T_NUM) {
idx_d = tod(key_val);
} else {
char *endptr;
char temp[64];
size_t copy_len = keylen < sizeof(temp) - 1 ? keylen : sizeof(temp) - 1;
memcpy(temp, keystr, copy_len);
temp[copy_len] = '\0';
idx_d = strtod(temp, &endptr);
if (endptr == temp || *endptr != '\0') idx_d = JS_NAN;
}
if (!isnan(idx_d) && idx_d >= 0 && idx_d == (double)(long)idx_d) {
jsoff_t idx = (jsoff_t) idx_d; jsoff_t byte_len;
jsoff_t str_off = vstr(js, obj, &byte_len);
const char *str_data = (const char *)&js->mem[str_off];
size_t char_bytes;
int byte_offset = utf16_index_to_byte_offset(str_data, byte_len, idx, &char_bytes);
if (byte_offset >= 0) {
return js_mkstr(js, str_data + byte_offset, char_bytes);
}
}
jsoff_t off = lkp_proto(js, obj, keystr, keylen);
if (off != 0) return resolveprop(js, mkval(T_PROP, off));
return js_mkundef();
}
if (vtype(obj) == T_FUNC) {
if ((js->flags & F_STRICT) && (streq(keystr, keylen, "caller", 6) || streq(keystr, keylen, "arguments", 9))) {
return js_mkerr_typed(js, JS_ERR_TYPE, "'%.*s' not allowed on functions in strict mode", (int)keylen, keystr);
}
jsoff_t obj_off = (jsoff_t)vdata(obj);
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, keystr, keylen);
if (desc && (desc->has_getter || desc->has_setter)) {
jsval_t key = js_mkstr(js, keystr, keylen);
return mkpropref(obj_off, (jsoff_t)vdata(key));
}
jsval_t func_obj = mkval(T_OBJ, vdata(obj));
jsoff_t off = lkp_proto(js, obj, keystr, keylen);
if (off != 0) {
jsval_t key = js_mkstr(js, keystr, keylen);
return mkpropref(obj_off, (jsoff_t)vdata(key));
}
if (streq(keystr, keylen, "name", 4)) return js_mkstr(js, "", 0);
jsval_t key = js_mkstr(js, keystr, keylen);
jsval_t prop = js_setprop(js, func_obj, key, js_mkundef());
return prop;
}
if (vtype(obj) == T_CFUNC) {
if ((js->flags & F_STRICT) && (streq(keystr, keylen, "caller", 6) || streq(keystr, keylen, "arguments", 9))) {
return js_mkerr_typed(js, JS_ERR_TYPE, "'%.*s' not allowed on functions in strict mode", (int)keylen, keystr);
}
jsoff_t off = lkp_proto(js, obj, keystr, keylen);
if (off != 0) return resolveprop(js, mkval(T_PROP, off));
if (streq(keystr, keylen, "name", 4)) return js_mkstr(js, "", 0);
return js_mkundef();
}
if (vtype(obj) == T_NUM || vtype(obj) == T_BOOL || vtype(obj) == T_BIGINT) {
jsval_t key = js_mkstr(js, keystr, keylen);
return mkprim_propref(obj, (jsoff_t)vdata(key));
}
if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR) {
return js_mkundef();
}
if (vtype(obj) == T_ARR && !is_proxy(js, obj) && keylen > 0 && keystr[0] >= '0' && keystr[0] <= '9') {
int valid = 1;
for (size_t ci = 0; ci < keylen; ci++) {
if (keystr[ci] < '0' || keystr[ci] > '9') { valid = 0; break; }
}
if (valid && (keylen == 1 || keystr[0] != '0')) {
jsval_t key = js_mkstr(js, keystr, keylen);
return mkpropref((jsoff_t)vdata(obj), (jsoff_t)vdata(key));
}
}
if ((streq(keystr, keylen, "callee", 6) || streq(keystr, keylen, "caller", 6)) &&
vtype(get_slot(js, obj, SLOT_STRICT_ARGS)) != T_UNDEF) {
return js_mkerr_typed(js, JS_ERR_TYPE, "'%.*s' not allowed on strict arguments", (int)keylen, keystr);
}
jsval_t getter = js_mkundef();
bool has_getter = false;
jsoff_t prop_off = lkp_with_getter(js, obj, keystr, keylen, &getter, &has_getter);
jsval_t setter = js_mkundef();
bool has_setter = false;
if (!has_getter) {
lkp_with_setter(js, obj, keystr, keylen, &setter, &has_setter);
}
if (has_getter || has_setter) {
jsval_t key = js_mkstr(js, keystr, keylen);
return mkpropref((jsoff_t)vdata(obj), (jsoff_t)vdata(key));
}
if (prop_off != 0) {
jsval_t key = js_mkstr(js, keystr, keylen);
return mkpropref((jsoff_t)vdata(obj), (jsoff_t)vdata(key));
}
jsval_t dyn_result = try_dynamic_getter(js, obj, keystr, keylen);
if (vtype(dyn_result) != T_UNDEF) {
jsval_t key = js_mkstr(js, keystr, keylen);
return mkpropref((jsoff_t)vdata(obj), (jsoff_t)vdata(key));
}
jsval_t key = js_mkstr(js, keystr, keylen);
return mkpropref((jsoff_t)vdata(obj), (jsoff_t)vdata(key));
}
static jsval_t do_dot_op(struct js *js, jsval_t l, jsval_t r) {
if (vtype(r) == T_CODEREF && is_special_object(l) && !is_proxy(js, l)) {
dot_ic_entry_t *ic = &dot_ic_table[dot_ic_slot(js->code, coderefoff(r))];
if (
ic->generation == intern_prop_cache_gen
&& ic->code_ptr == js->code
&& ic->code_off == coderefoff(r)
) return mkpropref((jsoff_t)vdata(l), ic->cached_key_off);
}
const char *raw_ptr = (char *) &js->code[coderefoff(r)];
size_t raw_len = codereflen(r);
char decoded_buf[256];
size_t plen = decode_ident_escapes(raw_ptr, raw_len, decoded_buf, sizeof(decoded_buf));
const char *ptr = decoded_buf;
if (vtype(r) != T_CODEREF) return js_mkerr_typed(js, JS_ERR_SYNTAX, "ident expected");
uint8_t t = vtype(l);
if (t == T_STR && streq(ptr, plen, "length", 6)) {
jsoff_t byte_len;
jsoff_t str_off = vstr(js, l, &byte_len);
const char *str_data = (const char *)&js->mem[str_off];
return tov(D(utf16_strlen(str_data, byte_len)));
}
if (t == T_ARR && streq(ptr, plen, "length", 6)) {
return mkpropref((jsoff_t)vdata(l), (jsoff_t)vdata(js->length_str));
}
if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) {
jsval_t key = js_mkstr(js, ptr, plen);
return mkprim_propref(l, (jsoff_t)vdata(key));
}
if (t == T_PROMISE) {
jsoff_t off = lkp_proto(js, mkval(T_OBJ, vdata(l)), ptr, plen);
if (off != 0) {
return resolveprop(js, mkval(T_PROP, off));
}
jsval_t promise_proto = get_ctor_proto(js, "Promise", 7);
if (vtype(promise_proto) != T_UNDEF && vtype(promise_proto) != T_NULL) {
off = lkp_proto(js, promise_proto, ptr, plen);
if (off != 0) {
return resolveprop(js, mkval(T_PROP, off));
}
}
return js_mkundef();
}
if (t == T_FUNC) {
if ((js->flags & F_STRICT) && (streq(ptr, plen, "caller", 6) || streq(ptr, plen, "arguments", 9))) {
return js_mkerr_typed(js, JS_ERR_TYPE, "'%.*s' not allowed on functions in strict mode", (int)plen, ptr);
}
if (plen == STR_PROTO_LEN && memcmp(ptr, STR_PROTO, STR_PROTO_LEN) == 0) {
jsval_t proto = get_slot(js, mkval(T_OBJ, vdata(l)), SLOT_PROTO);
if (vtype(proto) != T_UNDEF) return proto;
return get_prototype_for_type(js, T_FUNC);
}
jsoff_t obj_off = (jsoff_t)vdata(l);
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, ptr, plen);
if (desc && (desc->has_getter || desc->has_setter)) {
jsval_t key = js_mkstr(js, ptr, plen);
return mkpropref(obj_off, (jsoff_t)vdata(key));
}
jsval_t func_obj = mkval(T_OBJ, vdata(l));
jsoff_t off = lkp_proto(js, l, ptr, plen);
if (off != 0) {
jsval_t key = js_mkstr(js, ptr, plen);
return mkpropref(obj_off, (jsoff_t)vdata(key));
}
if (streq(ptr, plen, "name", 4)) return js_mkstr(js, "", 0);
jsval_t key = js_mkstr(js, ptr, plen);
jsval_t prop = js_setprop(js, func_obj, key, js_mkundef());
return prop;
}
if (t == T_CFUNC) {
if ((js->flags & F_STRICT) && (streq(ptr, plen, "caller", 6) || streq(ptr, plen, "arguments", 9))) {
return js_mkerr_typed(js, JS_ERR_TYPE, "'%.*s' not allowed on functions in strict mode", (int)plen, ptr);
}
jsoff_t off = lkp_proto(js, l, ptr, plen);
if (off != 0) return resolveprop(js, mkval(T_PROP, off));
if (streq(ptr, plen, "name", 4)) return js_mkstr(js, "", 0);
return js_mkundef();
}
if (t == T_SYMBOL) {
if (streq(ptr, plen, "description", 11)) {
const char *desc = sym_get_desc(js, l);
if (desc) return js_mkstr(js, desc, strlen(desc));
return js_mkundef();
}
jsval_t sym_proto = get_ctor_proto(js, "Symbol", 6);
if (vtype(sym_proto) == T_OBJ) {
jsoff_t off = lkp(js, sym_proto, ptr, plen);
if (off != 0) return resolveprop(js, mkval(T_PROP, off));
}
return js_mkundef();
}
if (t != T_OBJ && t != T_ARR) {
jsoff_t saved_toff = js->toff;
jsoff_t saved_tlen = js->tlen;
js->toff = coderefoff(r);
js->tlen = codereflen(r);
jsval_t err = js_mkerr(
js, "Cannot read properties of %s (reading '%.*s')",
t == T_UNDEF ? "undefined" : t == T_NULL ? "null" : typestr(t),
(int)plen, ptr
);
js->toff = saved_toff;
js->tlen = saved_tlen;
return err;
}
bool ic_eligible = !is_proxy(js, l);
jsoff_t ic_obj_off = (jsoff_t)vdata(l);
jsoff_t ic_key_off = 0;
if ((streq(ptr, plen, "callee", 6) || streq(ptr, plen, "caller", 6)) &&
vtype(get_slot(js, l, SLOT_STRICT_ARGS)) != T_UNDEF) {
return js_mkerr_typed(js, JS_ERR_TYPE, "'%.*s' not allowed on strict arguments", (int)plen, ptr);
}
if (plen == STR_PROTO_LEN && memcmp(ptr, STR_PROTO, STR_PROTO_LEN) == 0) {
jsval_t key = js_mkstr(js, ptr, plen);
return mkpropref((jsoff_t)vdata(l), (jsoff_t)vdata(key));
}
if (t == T_ARR && !is_proxy(js, l) && plen > 0 && (ptr[0] < '0' || ptr[0] > '9')) {
jsoff_t obj_off = (jsoff_t)vdata(l);
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, ptr, plen);
if (desc) {
if (desc->has_getter || desc->has_setter) ic_eligible = false;
jsval_t key = js_mkstr(js, ptr, plen);
ic_key_off = (jsoff_t)vdata(key);
if (ic_eligible) goto ic_populate;
return mkpropref(obj_off, ic_key_off);
}
jsval_t result = try_dynamic_getter(js, l, ptr, plen);
if (vtype(result) != T_UNDEF) {
jsoff_t own_off = lkp(js, l, ptr, plen);
if (own_off != 0) return mkval(T_PROP, own_off);
}
jsval_t key = js_mkstr(js, ptr, plen);
ic_key_off = (jsoff_t)vdata(key);
if (ic_eligible) goto ic_populate;
return mkpropref((jsoff_t)vdata(l), ic_key_off);
}
{
jsoff_t own_off = lkp(js, l, ptr, plen);
if (own_off != 0) {
if (ic_eligible) {
descriptor_entry_t *desc = lookup_descriptor(js, ic_obj_off, ptr, plen);
if (desc && (desc->has_getter || desc->has_setter)) ic_eligible = false;
}
jsval_t key = js_mkstr(js, ptr, plen);
ic_key_off = (jsoff_t)vdata(key);
if (ic_eligible) goto ic_populate;
return mkpropref((jsoff_t)vdata(l), ic_key_off);
}
jsval_t result = try_dynamic_getter(js, l, ptr, plen);
if (vtype(result) != T_UNDEF) {
own_off = lkp(js, l, ptr, plen);
if (own_off != 0) {
jsval_t key = js_mkstr(js, ptr, plen);
ic_key_off = (jsoff_t)vdata(key);
if (ic_eligible) goto ic_populate;
return mkpropref((jsoff_t)vdata(l), ic_key_off);
}
}
jsval_t key = js_mkstr(js, ptr, plen);
ic_key_off = (jsoff_t)vdata(key);
if (ic_eligible) goto ic_populate;
return mkpropref((jsoff_t)vdata(l), ic_key_off);
}
ic_populate: {
dot_ic_entry_t *ic = &dot_ic_table[dot_ic_slot(js->code, coderefoff(r))];
ic->code_ptr = js->code;
ic->code_off = coderefoff(r);
ic->generation = intern_prop_cache_gen;
ic->cached_key_off = ic_key_off;
return mkpropref(ic_obj_off, ic_key_off);
}
}
static jsval_t do_optional_chain_op(struct js *js, jsval_t l, jsval_t r) {
if (vtype(l) == T_NULL || vtype(l) == T_UNDEF) return js_mkundef();
return do_dot_op(js, l, r);
}
static jsval_t js_call_params(struct js *js) {
jsoff_t pos = js->pos;
uint8_t flags = js->flags;
js->flags |= F_NOEXEC;
js->consumed = 1;
while (next(js) != TOK_EOF) {
if (next(js) == TOK_RPAREN) break;
if (next(js) == TOK_REST) js->consumed = 1;
js_expr(js);
if (next(js) == TOK_RPAREN) break;
EXPECT(TOK_COMMA, js->flags = flags);
}
EXPECT(TOK_RPAREN, js->flags = flags);
js->flags = flags;
return mkcoderef(pos, js->pos - pos - js->tlen);
}
static void reverse(jsval_t *args, int nargs) {
for (int i = 0; i < nargs / 2; i++) {
jsval_t tmp = args[i];
args[i] = args[nargs - i - 1], args[nargs - i - 1] = tmp;
}
}
static int parse_call_args(struct js *js, UT_array *args, jsval_t *err_out) {
while (js->pos < js->clen) {
if (next(js) == TOK_RPAREN) break;
bool is_spread = (next(js) == TOK_REST);
if (is_spread) js->consumed = 1;
jsval_t arg = resolveprop(js, js_expr(js));
if (is_err(arg)) { *err_out = arg; return -1; }
if (is_spread && vtype(arg) == T_ARR) {
jsoff_t len = js_arr_len(js, arg);
for (jsoff_t i = 0; i < len; i++) {
jsval_t elem = js_arr_get(js, arg, i);
utarray_push_back(args, &elem);
}
} else utarray_push_back(args, &arg);
if (next(js) == TOK_COMMA) js->consumed = 1;
}
return (int)utarray_len(args);
}
static jsval_t call_c(struct js *js, jsval_t (*fn)(struct js *, jsval_t *, int)) {
UT_array *args;
utarray_new(args, &jsval_icd);
jsval_t err, res;
int argc = parse_call_args(js, args, &err);
if (argc < 0) { utarray_free(args); return err; }
jsval_t *argv = (jsval_t *)utarray_front(args);
jsval_t saved_this = js->this_val;
js->this_val = peek_this();
res = fn(js, argv, argc);
js->this_val = saved_this;
utarray_free(args);
return res;
}
static jsoff_t extract_default_param_value(const char *fn, jsoff_t fnlen, jsoff_t start_pos, jsoff_t *out_start, jsoff_t *out_len) {
jsoff_t after_ident = skiptonext(fn, fnlen, start_pos, NULL);
if (after_ident >= fnlen || fn[after_ident] != '=') {
*out_start = 0;
*out_len = 0;
return after_ident;
}
jsoff_t default_start = skiptonext(fn, fnlen, after_ident + 1, NULL);
jsoff_t default_len = 0;
jsoff_t depth = 0;
bool in_string = false;
char string_char = 0;
for (jsoff_t i = default_start; i < fnlen; i++) {
if (in_string) {
if (fn[i] == '\\' && i + 1 < fnlen) {
default_len += 2;
i++;
continue;
}
if (fn[i] == string_char) {
in_string = false;
}
default_len++;
} else {
if (fn[i] == '"' || fn[i] == '\'' || fn[i] == '`') {
in_string = true;
string_char = fn[i];
default_len++;
} else if (fn[i] == '(' || fn[i] == '[' || fn[i] == '{') {
depth++;
default_len++;
} else if (fn[i] == ')' || fn[i] == ']' || fn[i] == '}') {
if (depth == 0 && fn[i] == ')') break;
depth--;
default_len++;
} else if (depth == 0 && fn[i] == ',') {
break;
} else {
default_len++;
}
}
}
*out_start = default_start;
*out_len = default_len;
return skiptonext(fn, fnlen, default_start + default_len, NULL);
}
static jsoff_t skip_default_expr(const char *p, jsoff_t len, jsoff_t pos) {
int depth = 0;
while (pos < len) {
char c = p[pos];
if (c == '(' || c == '[' || c == '{') depth++;
else if (c == ')' || c == ']' || c == '}') { if (depth == 0) break; depth--; }
else if (c == ',' && depth == 0) break;
pos++;
}
return pos;
}
static jsval_t bind_destruct_pattern(struct js *js, const char *p, jsoff_t len, jsval_t val, jsval_t scope) {
jsoff_t pos = skiptonext(p, len, 0, NULL);
if (pos >= len) return js_mkundef();
bool is_arr = (p[pos] == '[');
if (!is_arr && p[pos] != '{') return js_mkerr(js, "invalid destructuring pattern");
pos++;
int idx = 0;
while (pos < len) {
pos = skiptonext(p, len, pos, NULL);
if (pos >= len) break;
if ((is_arr && p[pos] == ']') || (!is_arr && p[pos] == '}')) break;
if (p[pos] == ',') { pos++; idx++; continue; }
bool is_rest = (pos + 2 < len && p[pos] == '.' && p[pos+1] == '.' && p[pos+2] == '.');
if (is_rest) { pos += 3; pos = skiptonext(p, len, pos, NULL); }
jsoff_t name_len = 0;
if (parseident(&p[pos], len - pos, &name_len) != TOK_IDENTIFIER) break;
jsoff_t var_pos = pos, var_len = name_len;
jsoff_t src_pos = pos, src_len = name_len;
pos += name_len;
pos = skiptonext(p, len, pos, NULL);
bool is_nested = false;
jsoff_t nested_start = 0, nested_len = 0;
if (!is_arr && !is_rest && pos < len && p[pos] == ':') {
pos = skiptonext(p, len, pos + 1, NULL);
if (pos < len && (p[pos] == '{' || p[pos] == '[')) {
is_nested = true;
nested_start = pos;
char open = p[pos], close = (open == '{') ? '}' : ']';
int depth = 1;
pos++;
while (pos < len && depth > 0) {
if (p[pos] == open) depth++;
else if (p[pos] == close) depth--;
pos++;
}
nested_len = pos - nested_start;
pos = skiptonext(p, len, pos, NULL);
} else {
jsoff_t rlen = 0;
if (parseident(&p[pos], len - pos, &rlen) == TOK_IDENTIFIER) {
var_pos = pos; var_len = rlen;
pos += rlen;
pos = skiptonext(p, len, pos, NULL);
}
}
}
jsval_t prop_val;
if (is_rest && is_arr) {
jsval_t rest = js_mkarr(js);
if (is_err(rest)) return rest;
jsoff_t alen = js_arr_len(js, val);
for (jsoff_t i = idx; i < alen; i++) js_arr_push(js, rest, js_arr_get(js, val, i));
prop_val = rest;
} else if (is_rest) prop_val = mkobj(js, 0);
else if (is_arr) prop_val = js_arr_get(js, val, idx);
else prop_val = getprop_any(js, val, &p[src_pos], src_len);
if (is_nested) {
jsval_t r = bind_destruct_pattern(js, &p[nested_start], nested_len, prop_val, scope);
if (is_err(r)) return r;
idx++; pos = skiptonext(p, len, pos, NULL);
if (pos < len && p[pos] == ',') pos++;
continue;
}
if (is_rest) goto bind;
if (pos >= len || p[pos] != '=') goto bind;
pos++;
jsoff_t def_start = pos;
pos = skip_default_expr(p, len, pos);
if (vtype(prop_val) != T_UNDEF) goto bind;
prop_val = js_eval_str(js, &p[def_start], pos - def_start);
if (is_err(prop_val)) return prop_val;
prop_val = resolveprop(js, prop_val);
bind:;
jsval_t vname = js_mkstr(js, &p[var_pos], var_len);
if (is_err(vname)) return vname;
jsval_t r = js_setprop(js, scope, vname, prop_val);
if (is_err(r)) return r;
idx++;
pos = skiptonext(p, len, pos, NULL);
if (pos < len && p[pos] == ',') pos++;
}
return js_mkundef();
}
static bool is_strict_function_body(const char *body, size_t len) {
size_t i = 0;
while (i < len && (body[i] == ' ' || body[i] == '\t' || body[i] == '\n' || body[i] == '\r')) i++;
if (i + 12 <= len && (body[i] == '\'' || body[i] == '"')) {
char q = body[i];
if (memcmp(&body[i+1], "use strict", 10) == 0 && body[i+11] == q) return true;
}
return false;
}
static bool code_uses_arguments(const char *code, jsoff_t len) {
if (len < 9) return false;
for (jsoff_t i = 0; i + 8 < len; i++) {
if (code[i] == 'a' && memcmp(&code[i], INTERN_ARGUMENTS, 9) == 0) {
if (i > 0 && (is_alpha(code[i-1]) || code[i-1] == '_' || (code[i-1] >= '0' && code[i-1] <= '9'))) continue;
if (i + 9 < len && (is_alpha(code[i+9]) || code[i+9] == '_' || (code[i+9] >= '0' && code[i+9] <= '9'))) continue;
return true;
}
} return false;
}
static inline int find_statement_end(const token_stream_t *restrict ts, int start) {
const cached_token_t *restrict toks = ts->tokens;
const int count = ts->count;
if (start >= count) return count;
static const void *dispatch[] = {
[TOK_LPAREN] = &&open,
[TOK_LBRACKET] = &&open,
[TOK_LBRACE] = &&open,
[TOK_RPAREN] = &&close,
[TOK_RBRACKET] = &&close,
[TOK_RBRACE] = &&close,
[TOK_SEMICOLON] = &&semi,
[TOK_EOF] = &&semi,
};
int depth = 0;
int j = start;
for (;;) {
uint8_t t = toks[j].tok;
if (t < sizeof(dispatch) / sizeof(*dispatch) && dispatch[t]) goto *dispatch[t];
next:
if (++j >= count) return count;
continue;
open:
depth++;
goto next;
close:
if (depth == 0) return j;
depth--;
goto next;
semi:
if (depth == 0) return j;
goto next;
}
}
static inline void find_top_level_tokens(
const token_stream_t *restrict ts, int start, int end,
uint8_t tok1, int *restrict first_tok1,
uint8_t tok2, int *restrict first_tok2_after_tok1
) {
const cached_token_t *restrict toks = ts->tokens;
int depth = 0;
int j = start;
for (; j < end; j++) {
uint8_t t = toks[j].tok;
if (t == TOK_LPAREN || t == TOK_LBRACKET) depth++;
else if (t == TOK_RPAREN || t == TOK_RBRACKET) depth--;
else if (depth == 0 && t == tok1) {
*first_tok1 = j;
if (!tok2 || !first_tok2_after_tok1) return;
j++; goto phase2;
}
}
*first_tok1 = -1;
if (first_tok2_after_tok1) *first_tok2_after_tok1 = -1;
return;
phase2:
for (; j < end; j++) {
uint8_t t = toks[j].tok;
if (t == TOK_LPAREN || t == TOK_LBRACKET) depth++;
else if (t == TOK_RPAREN || t == TOK_RBRACKET) depth--;
else if (depth == 0 && t == tok2) { *first_tok2_after_tok1 = j; return; }
}
*first_tok2_after_tok1 = -1;
}
static inline int find_last_top_level_token(const token_stream_t *restrict ts, int start, int end, uint8_t tok) {
const cached_token_t *restrict toks = ts->tokens;
int depth = 0;
for (int j = end - 1; j >= start; j--) {
uint8_t t = toks[j].tok;
if (t == TOK_RPAREN || t == TOK_RBRACKET) depth++;
else if (t == TOK_LPAREN || t == TOK_LBRACKET) depth--;
else if (depth == 0 && t == tok) return j;
}
return -1;
}
static inline int find_matching_open(const token_stream_t *restrict ts, int rparen_idx) {
const cached_token_t *restrict toks = ts->tokens;
int depth = 0;
for (int j = rparen_idx; j >= 0; j--) {
uint8_t t = toks[j].tok;
if (t == TOK_RPAREN) depth++;
else if (t == TOK_LPAREN) if (--depth == 0) return j;
}
return -1;
}
static inline bool expr_ends_with_bare_call(const token_stream_t *restrict ts, int start, int end) {
if (start >= end) return false;
const cached_token_t *restrict toks = ts->tokens;
if (toks[end - 1].tok != TOK_RPAREN) return false;
int depth = 0;
for (int j = start; j < end - 1; j++) {
uint8_t t = toks[j].tok;
if (t == TOK_LPAREN || t == TOK_LBRACKET) depth++;
else if (t == TOK_RPAREN || t == TOK_RBRACKET) depth--;
}
if (depth != 1) return false;
int match = find_matching_open(ts, end - 1);
if (match < 0 || match <= start) return false;
uint8_t before = toks[match - 1].tok;
return before == TOK_IDENTIFIER || before == TOK_RPAREN || before == TOK_RBRACKET;
}
static inline bool return_expr_is_tail_call(
const token_stream_t *restrict ts, int start,
const char *body, jsoff_t body_len
) {
int end = find_statement_end(ts, start);
int last = end - 1;
if (last < start) return false;
const int count = ts->count;
int ternary, colon;
find_top_level_tokens(ts, start, end, TOK_Q, &ternary, TOK_COLON, &colon);
if (ternary >= 0) {
if (colon < 0) return false;
jsoff_t then_start = ts->tokens[ternary + 1].toff;
jsoff_t then_end = ts->tokens[colon].toff;
jsoff_t else_start = (colon + 1 < count) ? ts->tokens[colon + 1].toff : body_len;
jsoff_t else_end = (end < count) ? ts->tokens[end].toff : body_len;
int then_r = scan_tail_span(body, body_len, then_start, then_end);
if (then_r == TAIL_UNSAFE) return false;
int else_r = scan_tail_span(body, body_len, else_start, else_end);
return else_r != TAIL_UNSAFE && (then_r == TAIL_OK || else_r == TAIL_OK);
}
int last_comma = find_last_top_level_token(ts, start, end, TOK_COMMA);
if (last_comma >= 0) return return_expr_is_tail_call(ts, last_comma + 1, body, body_len);
return expr_ends_with_bare_call(ts, start, end);
}
static inline bool tokens_have_tail_calls(
const token_stream_t *restrict ts,
const char *body, jsoff_t body_len, bool is_expr
) {
if (!ts || ts->count == 0) return false;
if (is_expr) return scan_tail_span(body, body_len, 0, body_len) == TAIL_OK;
const cached_token_t *restrict toks = ts->tokens;
const int count = ts->count;
for (int i = 0; i < count; i++) {
if (toks[i].tok != TOK_RETURN) continue;
int j = i + 1;
if (j >= count) continue;
uint8_t first = toks[j].tok;
if (first == TOK_SEMICOLON || first == TOK_RBRACE || first == TOK_EOF) continue;
if (return_expr_is_tail_call(ts, j, body, body_len)) return true;
}
return false;
}
static parsed_func_t *get_or_parse_func(struct js *js, const char *fn, jsoff_t fnlen) {
uint64_t h = hash_key(fn, fnlen);
parsed_func_t *cached = NULL;
HASH_FIND(hh, func_parse_cache, &h, sizeof(h), cached);
if (cached) return cached;
parsed_func_t *pf = (parsed_func_t *)malloc(sizeof(parsed_func_t));
if (!pf) return NULL;
memset(pf, 0, sizeof(*pf));
pf->code_hash = h;
utarray_new(pf->params, &parsed_param_icd);
jsoff_t fnpos = 1;
while (fnpos < fnlen) {
fnpos = skiptonext(fn, fnlen, fnpos, NULL);
if (fnpos < fnlen && fn[fnpos] == ')') break;
bool is_rest = false;
if (fnpos + 3 < fnlen && fn[fnpos] == '.' && fn[fnpos + 1] == '.' && fn[fnpos + 2] == '.') {
is_rest = true;
pf->has_rest = true;
fnpos += 3;
fnpos = skiptonext(fn, fnlen, fnpos, NULL);
}
jsoff_t identlen = 0;
uint8_t tok = parseident(&fn[fnpos], fnlen - fnpos, &identlen);
bool is_valid_ident = (tok == TOK_IDENTIFIER || is_contextual_keyword(tok));
if (!is_valid_ident && (fn[fnpos] == '{' || fn[fnpos] == '[')) {
char bracket_open = fn[fnpos];
char bracket_close = (bracket_open == '{') ? '}' : ']';
jsoff_t pattern_start = fnpos;
int depth = 1; fnpos++;
while (fnpos < fnlen && depth > 0) {
if (fn[fnpos] == bracket_open) depth++;
else if (fn[fnpos] == bracket_close) depth--;
fnpos++;
}
jsoff_t pattern_len = fnpos - pattern_start;
{
parsed_param_t pp = {0};
pp.is_destruct = true;
pp.pattern_off = pattern_start;
pp.pattern_len = pattern_len;
fnpos = skiptonext(fn, fnlen, fnpos, NULL);
if (fnpos < fnlen && fn[fnpos] == '=') {
fnpos = extract_default_param_value(fn, fnlen, fnpos, &pp.default_start, &pp.default_len);
}
utarray_push_back(pf->params, &pp);
pf->param_count++;
}
if (fnpos < fnlen && fn[fnpos] == ',') fnpos++;
continue;
}
if (!is_valid_ident) break;
if (is_rest) {
pf->rest_param_start = fnpos;
pf->rest_param_len = identlen;
fnpos = skiptonext(fn, fnlen, fnpos + identlen, NULL);
break;
}
{
parsed_param_t pp = {0};
pp.name_off = fnpos;
pp.name_len = identlen;
pp.is_destruct = false;
fnpos = extract_default_param_value(fn, fnlen, fnpos + identlen, &pp.default_start, &pp.default_len);
utarray_push_back(pf->params, &pp);
pf->param_count++;
}
if (fnpos < fnlen && fn[fnpos] == ',') fnpos++;
}
if (fnpos < fnlen && fn[fnpos] == ')') fnpos++;
fnpos = skiptonext(fn, fnlen, fnpos, NULL);
bool is_block = (fnpos < fnlen && fn[fnpos] == '{');
if (is_block) fnpos++;
pf->body_start = fnpos;
pf->body_len = (fnlen > fnpos) ? (fnlen - fnpos - (is_block ? 1 : 0)) : 0;
pf->is_expr = !is_block;
pf->is_strict = is_strict_function_body(&fn[fnpos], pf->body_len);
pf->uses_arguments = code_uses_arguments(&fn[pf->body_start], pf->body_len);
pf->tokens = (pf->body_len > 0) ? tokenize_body(js, &fn[pf->body_start], pf->body_len) : NULL;
pf->has_tail_calls = tokens_have_tail_calls(pf->tokens, &fn[pf->body_start], pf->body_len, pf->is_expr);
HASH_ADD(hh, func_parse_cache, code_hash, sizeof(pf->code_hash), pf);
return pf;
}
static bool is_eval_or_arguments(struct js *js, jsoff_t toff, jsoff_t tlen) {
if (tlen == 4 && memcmp(&js->code[toff], "eval", 4) == 0) return true;
if (tlen == 9 && memcmp(&js->code[toff], "arguments", 9) == 0) return true;
return false;
}
static void setup_arguments(struct js *js, jsval_t scope, jsval_t *args, int nargs, bool strict) {
if (vtype(js->current_func) == T_FUNC) {
jsval_t func_obj = mkval(T_OBJ, vdata(js->current_func));
if (vtype(get_slot(js, func_obj, SLOT_THIS)) != T_UNDEF) return;
}
jsval_t arguments_obj = mkobj(js, 0);
for (int i = 0; i < nargs; i++) {
if (i < 10) js_setprop(js, arguments_obj, js_mkstr(js, INTERN_IDX[i], 1), args[i]); else {
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)i);
js_setprop(js, arguments_obj, js_mkstr(js, idxstr, idxlen), args[i]);
}
}
setprop_interned(js, arguments_obj, INTERN_LENGTH, 6, tov((double) nargs));
if (strict) {
set_slot(js, arguments_obj, SLOT_STRICT_ARGS, tov(1));
} else if (vtype(js->current_func) == T_FUNC) {
setprop_interned(js, arguments_obj, INTERN_CALLEE, 6, js->current_func);
}
const char *toStringTag_key = get_toStringTag_sym_key();
if (toStringTag_key && toStringTag_key[0] != '\0') {
js_setprop(js, arguments_obj, js_mkstr(js, toStringTag_key, strlen(toStringTag_key)), js_mkstr(js, "Arguments", 9));
}
arguments_obj = mkval(T_ARR, vdata(arguments_obj));
setprop_interned(js, scope, INTERN_ARGUMENTS, 9, arguments_obj);
if (!strict && vtype(js->current_func) == T_FUNC) {
jsval_t func_obj = mkval(T_OBJ, vdata(js->current_func));
setprop_interned(js, func_obj, INTERN_ARGUMENTS, 9, arguments_obj);
}
}
static inline void restore_saved_scope(struct js *js) {
if (saved_scope_stack && utarray_len(saved_scope_stack) >= 2) {
jsval_t *saved_this_ptr = (jsval_t *)utarray_back(saved_scope_stack);
js->this_val = *saved_this_ptr;
utarray_pop_back(saved_scope_stack);
jsval_t *saved_scope_ptr = (jsval_t *)utarray_back(saved_scope_stack);
js->scope = *saved_scope_ptr;
utarray_pop_back(saved_scope_stack);
}
}
jsval_t call_js_internal(
struct js *js, const char *fn, jsoff_t fnlen,
jsval_t closure_scope, jsval_t *bound_args, int bound_argc, jsval_t func_val
) {
if (saved_scope_stack == NULL) utarray_new(saved_scope_stack, &jsval_icd);
utarray_push_back(saved_scope_stack, &js->scope);
utarray_push_back(saved_scope_stack, &js->this_val);
jsval_t res; jsval_t *tc_args = NULL;
int tc_argc = 0; bool tc_iter = false;
for (;;) {
jsval_t target_this = peek_this();
jsoff_t parent_scope_offset;
if (vtype(closure_scope) == T_OBJ) parent_scope_offset = (jsoff_t) vdata(closure_scope);
else parent_scope_offset = (jsoff_t) vdata(js->scope);
if (global_scope_stack == NULL) utarray_new(global_scope_stack, &jsoff_icd);
jsval_t function_scope = mkobj(js, parent_scope_offset);
jsoff_t function_scope_offset = (jsoff_t)vdata(function_scope);
utarray_push_back(global_scope_stack, &function_scope_offset);
jsval_t args_buf[8];
jsval_t *args;
int argc;
bool args_on_heap;
if (tc_iter) {
args = tc_args ? tc_args : args_buf;
argc = tc_argc;
args_on_heap = (tc_args != NULL && tc_argc > 0);
tc_args = NULL; tc_argc = 0; tc_iter = false;
} else {
const char *caller_code = js->code;
jsoff_t caller_clen = js->clen;
jsoff_t caller_pos = js->pos;
args = args_buf;
argc = 0;
args_on_heap = false;
int args_cap = 8;
#define ARGS_PUSH(val) do { \
if (argc >= args_cap) { \
int _new_cap = args_cap * 2; \
jsval_t *_new = try_oom(_new_cap * sizeof(jsval_t)); \
memcpy(_new, args, argc * sizeof(jsval_t)); \
if (args_on_heap) free(args); \
args = _new; \
args_cap = _new_cap; \
args_on_heap = true; \
} \
args[argc++] = (val); \
} while (0)
for (int i = 0; i < bound_argc; i++) ARGS_PUSH(bound_args[i]);
caller_pos = skiptonext(caller_code, caller_clen, caller_pos, NULL);
while (caller_pos < caller_clen && caller_code[caller_pos] != ')') {
bool is_spread = (
caller_code[caller_pos] == '.' && caller_pos + 2 < caller_clen &&
caller_code[caller_pos + 1] == '.' && caller_code[caller_pos + 2] == '.'
);
if (is_spread) caller_pos += 3;
js->pos = caller_pos;
js->consumed = 1;
jsval_t arg = resolveprop(js, js_expr(js));
caller_pos = js->pos;
if (is_spread && vtype(arg) == T_ARR) {
jsoff_t len = js_arr_len(js, arg);
for (jsoff_t i = 0; i < len; i++) ARGS_PUSH(js_arr_get(js, arg, i));
} else ARGS_PUSH(arg);
caller_pos = skiptonext(caller_code, caller_clen, caller_pos, NULL);
if (caller_pos < caller_clen && caller_code[caller_pos] == ',') caller_pos++;
caller_pos = skiptonext(caller_code, caller_clen, caller_pos, NULL);
}
#undef ARGS_PUSH
js->pos = caller_pos;
}
js->scope = function_scope;
parsed_func_t *pf = get_or_parse_func(js, fn, fnlen);
if (!pf) {
if (args_on_heap) free(args);
restore_saved_scope(js);
if (global_scope_stack && utarray_len(global_scope_stack) > 0) utarray_pop_back(global_scope_stack);
return js_mkerr(js, "failed to parse function");
}
bool func_strict = pf->is_strict;
if (!func_strict && vtype(func_val) == T_FUNC) {
jsval_t strict_slot = get_slot(js, mkval(T_OBJ, vdata(func_val)), SLOT_STRICT);
func_strict = (vtype(strict_slot) == T_BOOL && vdata(strict_slot) == 1);
}
if (func_strict && (vtype(target_this) == T_UNDEF || vtype(target_this) == T_NULL ||
(vtype(target_this) == T_OBJ && vdata(target_this) == 0))) {
js->this_val = js_mkundef();
} else js->this_val = target_this;
int argi = 0;
for (int i = 0; i < pf->param_count; i++) {
parsed_param_t *pp = (parsed_param_t *)utarray_eltptr(pf->params, (unsigned int)i);
if (pp->is_destruct) {
jsval_t arg_val = (argi < argc) ? args[argi++] : js_mkundef();
if (vtype(arg_val) == T_UNDEF && pp->default_len > 0) {
arg_val = js_eval_str(js, &fn[pp->default_start], pp->default_len);
}
jsval_t r = bind_destruct_pattern(js, &fn[pp->pattern_off], pp->pattern_len, arg_val, function_scope);
if (is_err(r)) {
if (args_on_heap) free(args);
restore_saved_scope(js);
if (global_scope_stack && utarray_len(global_scope_stack) > 0) utarray_pop_back(global_scope_stack);
return r;
}
} else {
jsval_t v;
if (argi < argc) v = args[argi++];
else if (pp->default_len > 0) v = js_eval_str(js, &fn[pp->default_start], pp->default_len);
else v = js_mkundef();
jsval_t k = js_mkstr(js, &fn[pp->name_off], pp->name_len);
if (!is_err(k)) mkprop_fast(js, function_scope, k, v, 0);
}
}
if (pf->has_rest && pf->rest_param_len > 0) {
jsval_t rest_array = mkarr(js);
if (!is_err(rest_array)) {
jsoff_t idx = 0;
while (argi < argc) { arr_set(js, rest_array, idx, args[argi++]); idx++; }
rest_array = mkval(T_ARR, vdata(rest_array));
js_setprop(js, function_scope, js_mkstr(js, &fn[pf->rest_param_start], pf->rest_param_len), rest_array);
}
}
bool needs_arguments = pf->uses_arguments;
if (needs_arguments) setup_arguments(js, function_scope, args, argc, func_strict);
jsval_t slot_name = get_slot(js, func_val, SLOT_NAME);
if (vtype(slot_name) == T_STR && vtype(func_val) == T_FUNC) {
jsoff_t len;
(void)vstr(js, slot_name, &len);
if (len > 0) mkprop_fast(js, function_scope, slot_name, func_val, CONSTMASK);
}
if (vtype(func_val) == T_FUNC) {
jsval_t func_obj = mkval(T_OBJ, vdata(func_val));
hoist_var_declarations_from_slot(js, function_scope, func_obj);
jsval_t no_func_decls = get_slot(js, func_obj, SLOT_NO_FUNC_DECLS);
js->skip_func_hoist = (vtype(no_func_decls) == T_BOOL && vdata(no_func_decls) == 1);
} else js->skip_func_hoist = false;
js->flags = F_CALL | (func_strict ? F_STRICT : 0);
void *saved_token_stream = js->token_stream;
int saved_token_stream_pos = js->token_stream_pos;
const char *saved_token_stream_code = js->token_stream_code;
if (pf->tokens) {
js->token_stream = pf->tokens;
js->token_stream_pos = 0;
js->token_stream_code = &fn[pf->body_start];
} else {
js->token_stream = NULL;
js->token_stream_code = NULL;
}
bool saved_has_tail = js->has_tail_calls;
js->has_tail_calls = pf->has_tail_calls;
if (pf->is_expr) {
js->tail_ctx = pf->has_tail_calls;
res = js_eval_str(js, &fn[pf->body_start], pf->body_len);
js->tail_ctx = false;
if (res != (jsval_t)T_TAILCALL) res = resolveprop(js, res);
} else {
res = js_eval(js, &fn[pf->body_start], pf->body_len);
if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef();
}
js->has_tail_calls = saved_has_tail;
js->token_stream = saved_token_stream;
js->token_stream_pos = saved_token_stream_pos;
js->token_stream_code = saved_token_stream_code;
js->skip_func_hoist = false;
if (global_scope_stack && utarray_len(global_scope_stack) > 0) utarray_pop_back(global_scope_stack);
if (args_on_heap) free(args);
if (res == (jsval_t)T_TAILCALL && js->tc.pending) {
js->tc.pending = false;
fn = js->tc.code_str;
fnlen = js->tc.fnlen;
closure_scope = js->tc.closure_scope;
func_val = js->tc.func;
bound_args = NULL;
bound_argc = 0;
tc_args = js->tc.args;
js->tc.args = NULL;
tc_argc = js->tc.argc;
tc_iter = true;
continue;
}
break;
} // end trampoline
free(tc_args);
tc_args = NULL;
free(js->tc.args);
js->tc.args = NULL;
restore_saved_scope(js);
return res;
}
jsval_t call_js(struct js *js, const char *fn, jsoff_t fnlen, jsval_t closure_scope) {
return call_js_internal(js, fn, fnlen, closure_scope, NULL, 0, js_mkundef());
}
jsval_t call_js_with_args(struct js *js, jsval_t func, jsval_t *args, int nargs) {
if (vtype(func) == T_CFUNC) {
jsval_t (*fn)(struct js *, jsval_t *, int) = (jsval_t(*)(struct js *, jsval_t *, int)) vdata(func);
return fn(js, args, nargs);
}
if (vtype(func) != T_FUNC) return js_mkerr(js, "not a function");
jsval_t func_obj = mkval(T_OBJ, vdata(func));
jsval_t *combined_args = NULL;
int combined_nargs = nargs;
int bound_argc = 0;
jsval_t bound_arr = get_slot(js, func_obj, SLOT_BOUND_ARGS);
if (vtype(bound_arr) == T_ARR) {
bound_argc = (int) get_array_length(js, bound_arr);
if (bound_argc > 0) {
combined_nargs = bound_argc + nargs;
combined_args = (jsval_t *)ant_calloc(sizeof(jsval_t) * combined_nargs);
if (combined_args) {
for (int i = 0; i < bound_argc; i++) combined_args[i] = arr_get(js, bound_arr, (jsoff_t)i);
for (int i = 0; i < nargs; i++) combined_args[bound_argc + i] = args[i];
args = combined_args; nargs = combined_nargs;
}
}
}
jsval_t cfunc_slot = get_slot(js, func_obj, SLOT_CFUNC);
if (vtype(cfunc_slot) == T_CFUNC) {
jsval_t bound_this = get_slot(js, func_obj, SLOT_BOUND_THIS);
jsval_t saved_this = js->this_val;
if (vtype(bound_this) != T_UNDEF) {
push_this(bound_this);
js->this_val = bound_this;
}
jsval_t saved_func = js->current_func;
js->current_func = func;
jsval_t (*fn)(struct js *, jsval_t *, int) = (jsval_t(*)(struct js *, jsval_t *, int)) vdata(cfunc_slot);
jsval_t result = fn(js, args, nargs);
js->current_func = saved_func;
if (vtype(bound_this) != T_UNDEF) {
pop_this();
js->this_val = saved_this;
}
if (combined_args) free(combined_args);
return result;
}
jsoff_t fnlen;
const char *fn = get_func_code(js, func_obj, &fnlen);
if (!fn) {
if (combined_args) free(combined_args);
return js_mkerr(js, "function has no code");
}
jsval_t closure_scope = get_slot(js, func_obj, SLOT_SCOPE);
jsval_t saved_super = js->super_val;
jsval_t func_super = get_slot(js, func_obj, SLOT_SUPER);
if (vtype(func_super) != T_UNDEF) js->super_val = func_super;
jsval_t captured_this = get_slot(js, func_obj, SLOT_THIS);
if (vtype(captured_this) != T_UNDEF) {
pop_this();
push_this(captured_this);
}
jsval_t bound_this = get_slot(js, func_obj, SLOT_BOUND_THIS);
if (vtype(bound_this) != T_UNDEF) {
pop_this();
push_this(bound_this);
}
jsval_t result = call_js_code_with_args(js, fn, fnlen, closure_scope, args, nargs, func);
js->super_val = saved_super;
if (combined_args) free(combined_args);
return result;
}
jsval_t call_js_code_with_args(struct js *js, const char *fn, jsoff_t fnlen, jsval_t closure_scope, jsval_t *args, int nargs, jsval_t func_val) {
jsoff_t parent_scope_offset;
if (vtype(closure_scope) == T_OBJ) {
parent_scope_offset = (jsoff_t) vdata(closure_scope);
} else parent_scope_offset = (jsoff_t) vdata(js->scope);
jsval_t saved_scope = js->scope;
if (global_scope_stack == NULL) utarray_new(global_scope_stack, &jsoff_icd);
jsval_t function_scope = mkobj(js, parent_scope_offset);
jsoff_t function_scope_offset = (jsoff_t) vdata(function_scope);
utarray_push_back(global_scope_stack, &function_scope_offset);
js->scope = function_scope;
jsval_t slot_name = get_slot(js, func_val, SLOT_NAME);
if (vtype(slot_name) == T_STR && vtype(func_val) == T_FUNC) {
jsoff_t len; vstr(js, slot_name, &len);
if (len > 0) mkprop(js, function_scope, slot_name, func_val, CONSTMASK);
}
jsval_t func_obj = mkval(T_OBJ, vdata(func_val));
hoist_var_declarations_from_slot(js, function_scope, func_obj);
jsoff_t fnpos = 1;
int arg_idx = 0;
bool has_rest = false;
jsoff_t rest_param_start = 0, rest_param_len = 0;
while (fnpos < fnlen) {
fnpos = skiptonext(fn, fnlen, fnpos, NULL);
if (fnpos < fnlen && fn[fnpos] == ')') break;
bool is_rest = false;
if (fnpos + 3 < fnlen && fn[fnpos] == '.' && fn[fnpos + 1] == '.' && fn[fnpos + 2] == '.') {
is_rest = true;
has_rest = true;
fnpos += 3;
fnpos = skiptonext(fn, fnlen, fnpos, NULL);
}
jsoff_t identlen = 0;
uint8_t tok = parseident(&fn[fnpos], fnlen - fnpos, &identlen);
fnpos = skiptonext(fn, fnlen, fnpos, NULL);
if (tok != TOK_IDENTIFIER && fnpos < fnlen && (fn[fnpos] == '{' || fn[fnpos] == '[')) {
char bracket_open = fn[fnpos];
char bracket_close = (bracket_open == '{') ? '}' : ']';
jsoff_t pattern_start = fnpos;
int depth = 1;
fnpos++;
while (fnpos < fnlen && depth > 0) {
if (fn[fnpos] == bracket_open) depth++;
else if (fn[fnpos] == bracket_close) depth--;
fnpos++;
}
jsoff_t pattern_len = fnpos - pattern_start;
jsval_t arg_val = (arg_idx < nargs) ? args[arg_idx] : js_mkundef();
jsval_t r = bind_destruct_pattern(js, &fn[pattern_start], pattern_len, arg_val, function_scope);
if (is_err(r)) return r;
arg_idx++;
fnpos = skiptonext(fn, fnlen, fnpos, NULL);
if (fnpos < fnlen && fn[fnpos] == ',') fnpos++;
continue;
}
if (tok != TOK_IDENTIFIER) break;
if (is_rest) {
rest_param_start = fnpos;
rest_param_len = identlen;
fnpos = skiptonext(fn, fnlen, fnpos + identlen, NULL);
break;
}
jsoff_t param_name_pos = fnpos;
jsoff_t default_start = 0, default_len = 0;
fnpos = extract_default_param_value(fn, fnlen, fnpos + identlen, &default_start, &default_len);
jsval_t v;
if (arg_idx < nargs) {
v = args[arg_idx];
} else if (default_len > 0) {
v = js_eval_str(js, &fn[default_start], default_len);
} else {
v = js_mkundef();
}
js_setprop(js, function_scope, js_mkstr(js, &fn[param_name_pos], identlen), v);
arg_idx++;
if (fnpos < fnlen && fn[fnpos] == ',') fnpos++;
}
if (has_rest && rest_param_len > 0) {
jsval_t rest_array = mkarr(js);
if (!is_err(rest_array)) {
jsoff_t idx = 0;
while (arg_idx < nargs) {
arr_set(js, rest_array, idx, args[arg_idx]);
idx++; arg_idx++;
}
rest_array = mkval(T_ARR, vdata(rest_array));
js_setprop(js, function_scope, js_mkstr(js, &fn[rest_param_start], rest_param_len), rest_array);
}
}
if (fnpos < fnlen && fn[fnpos] == ')') fnpos++;
fnpos = skiptonext(fn, fnlen, fnpos, NULL);
if (fnpos >= fnlen) return js_mkerr(js, "unexpected end of function");
bool is_block = (fn[fnpos] == '{');
if (is_block) fnpos++;
jsoff_t body_len = fnlen - fnpos - (is_block ? 1 : 0);
bool func_strict = is_strict_function_body(&fn[fnpos], body_len);
if (code_uses_arguments(&fn[fnpos], body_len)) {
setup_arguments(js, function_scope, args, nargs, func_strict);
}
jsval_t saved_this = js->this_val;
jsval_t target_this = peek_this();
if (func_strict && (vtype(target_this) == T_UNDEF || vtype(target_this) == T_NULL ||
(vtype(target_this) == T_OBJ && vdata(target_this) == 0))) {
js->this_val = js_mkundef();
} else {
js->this_val = target_this;
}
js->flags = F_CALL | (func_strict ? F_STRICT : 0);
jsval_t res;
if (!is_block) {
res = js_eval_str(js, &fn[fnpos], body_len);
res = resolveprop(js, res);
} else {
res = js_eval(js, &fn[fnpos], body_len);
if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef();
}
js->this_val = saved_this;
if (global_scope_stack && utarray_len(global_scope_stack) > 0) utarray_pop_back(global_scope_stack);
js->scope = saved_scope;
return res;
}
static jsval_t call_ffi(struct js *js, unsigned int func_index) {
UT_array *args;
utarray_new(args, &jsval_icd);
jsval_t err, res;
int argc = parse_call_args(js, args, &err);
if (argc < 0) { utarray_free(args); return err; }
jsval_t *argv = (jsval_t *)utarray_front(args);
res = ffi_call_by_index(js, func_index, argv, argc);
utarray_free(args);
return res;
}
static jsval_t do_call_op(struct js *js, jsval_t func, jsval_t args) {
if (js_stack_overflow(js)) {
return js_mkerr_typed(js, JS_ERR_RANGE | JS_ERR_NO_STACK, "Maximum call stack size exceeded");
}
if (vtype(args) != T_CODEREF) return js_mkerr(js, "bad call");
if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC && vtype(func) != T_FFI) return js_mkerr(js, "calling non-function");
bool is_tail = js->tail_ctx;
js->tail_ctx = false;
if (vtype(func) == T_FFI) {
const char *code = js->code;
jsoff_t clen = js->clen, pos = js->pos;
uint8_t tok = js->tok, flags = js->flags;
js->code = &js->code[coderefoff(args)];
js->clen = codereflen(args);
js->pos = skiptonext(js->code, js->clen, 0, NULL);
jsval_t res = call_ffi(js, (unsigned int)vdata(func));
js->code = code; js->clen = clen; js->pos = pos;
js->flags = (flags & ~F_THROW) | (js->flags & F_THROW);
js->tok = tok;
js->consumed = 1;
return res;
}
jsval_t target_this = peek_this();
jsval_t target_proto = (vtype(target_this) == T_OBJ) ? get_slot(js, target_this, SLOT_PROTO) : js_mkundef();
if (vtype(func) == T_FUNC && vtype(target_this) == T_OBJ) {
if (vtype(target_proto) == T_UNDEF) {
jsval_t func_obj = mkval(T_OBJ, vdata(func));
jsval_t target_func = get_slot(js, func_obj, SLOT_TARGET_FUNC);
jsval_t proto_source = func_obj;
if (vtype(target_func) == T_FUNC) {
proto_source = mkval(T_OBJ, vdata(target_func));
}
jsoff_t proto_off = lkp_interned(js, proto_source, INTERN_PROTOTYPE, 9);
if (proto_off != 0) {
jsval_t proto = resolveprop(js, mkval(T_PROP, proto_off));
if (vtype(proto) == T_OBJ) set_proto(js, target_this, proto);
}
}
}
const char *code = js->code;
jsoff_t clen = js->clen, pos = js->pos;
js->code = &js->code[coderefoff(args)];
js->clen = codereflen(args);
js->pos = skiptonext(js->code, js->clen, 0, NULL);
uint8_t tok = js->tok, flags = js->flags;
jsval_t res = js_mkundef();
if (vtype(func) == T_FUNC) {
jsval_t func_obj = mkval(T_OBJ, vdata(func));
jsval_t cfunc_slot = get_slot(js, func_obj, SLOT_CFUNC);
if (vtype(cfunc_slot) == T_CFUNC) {
jsval_t bound_this_slot = get_slot(js, func_obj, SLOT_BOUND_THIS);
bool has_bound_this = vtype(bound_this_slot) != T_UNDEF;
if (has_bound_this) {
pop_this();
push_this(bound_this_slot);
}
jsval_t saved_func = js->current_func;
js->current_func = func;
int bound_argc;
jsval_t *bound_args = resolve_bound_args(js, func_obj, NULL, 0, &bound_argc);
if (!bound_args) {
res = call_c(js, (jsval_t(*)(struct js *, jsval_t *, int)) vdata(cfunc_slot));
} else {
UT_array *args_arr;
utarray_new(args_arr, &jsval_icd);
for (int i = 0; i < bound_argc; i++) utarray_push_back(args_arr, &bound_args[i]);
free(bound_args);
jsval_t err;
int call_argc = parse_call_args(js, args_arr, &err);
if (call_argc < 0) {
utarray_free(args_arr);
js->current_func = saved_func;
if (has_bound_this) pop_this();
return err;
}
jsval_t *argv = (jsval_t *)utarray_front(args_arr);
int total_argc = (int)utarray_len(args_arr);
jsval_t saved_this = js->this_val;
js->this_val = peek_this();
res = ((jsval_t(*)(struct js *, jsval_t *, int)) vdata(cfunc_slot))(js, argv, total_argc);
js->this_val = saved_this;
utarray_free(args_arr);
}
js->current_func = saved_func;
if (has_bound_this) {
pop_this();
push_this(target_this);
}
} else {
jsval_t builtin_slot = get_slot(js, func_obj, SLOT_BUILTIN);
if (vtype(builtin_slot) == T_NUM && (int)tod(builtin_slot) == BUILTIN_OBJECT) res = call_c(js, builtin_Object); else {
jsoff_t fnlen;
const char *code_str = get_func_code(js, func_obj, &fnlen);
if (!code_str) return js_mkerr(js, "function has no code");
jsval_t closure_scope = get_slot(js, func_obj, SLOT_SCOPE);
jsval_t async_slot = get_slot(js, func_obj, SLOT_ASYNC);
bool is_async = (async_slot == js_true);
jsval_t captured_this = js_mkundef();
bool is_arrow = false;
bool is_bound = false;
jsval_t this_slot = get_slot(js, func_obj, SLOT_THIS);
if (vtype(this_slot) != T_UNDEF) {
captured_this = this_slot; is_arrow = true;
}
jsval_t bound_this_slot = get_slot(js, func_obj, SLOT_BOUND_THIS);
if (vtype(bound_this_slot) != T_UNDEF && vtype(js->new_target) == T_UNDEF) {
captured_this = bound_this_slot; is_bound = true;
}
int bound_argc;
jsval_t *bound_args = resolve_bound_args(js, func_obj, NULL, 0, &bound_argc);
jsval_t nfe_name_val = js_mkundef();
jsval_t slot_name = get_slot(js, func_obj, SLOT_NAME);
if (vtype(slot_name) == T_STR) nfe_name_val = slot_name; else {
jsoff_t nfe_name_off = lkp(js, func_obj, "name", 4);
if (nfe_name_off != 0) nfe_name_val = resolveprop(js, mkval(T_PROP, nfe_name_off));
}
const char *func_name = NULL;
if (vtype(nfe_name_val) == T_STR) {
jsoff_t name_len, name_offset = vstr(js, nfe_name_val, &name_len);
func_name = (const char *)&js->mem[name_offset];
}
const char *final_name = func_name ? func_name : "<anonymous>";
push_call_frame(js->filename, final_name, code, (uint32_t) pos);
jsval_t saved_func = js->current_func;
js->current_func = func;
jsval_t saved_super = js->super_val;
jsval_t func_super = get_slot(js, func_obj, SLOT_SUPER);
if (vtype(func_super) != T_UNDEF) js->super_val = func_super;
if (is_arrow || is_bound) {
pop_this();
push_this(captured_this);
}
if (get_slot(js, func_obj, SLOT_DEFAULT_CTOR) == js_true) {
jsval_t super_ctor = js->super_val;
uint8_t st = vtype(super_ctor);
if (st == T_FUNC || st == T_CFUNC) {
js->code = code; js->clen = clen; js->pos = pos;
res = do_call_op(js, super_ctor, args);
js->super_val = saved_super;
js->current_func = saved_func;
pop_call_frame(); goto restore_state;
}
}
jsval_t count_val = get_slot(js, func_obj, SLOT_FIELD_COUNT);
if (vtype(count_val) != T_NUM || vtype(target_this) != T_OBJ) goto skip_fields;
int field_count = (int)tod(count_val);
jsval_t src_val = get_slot(js, func_obj, SLOT_SOURCE);
jsval_t fields_meta = get_slot(js, func_obj, SLOT_FIELDS);
if (vtype(src_val) == T_UNDEF || vtype(fields_meta) == T_UNDEF) goto skip_fields;
if (vtype(src_val) != T_CFUNC) goto skip_fields;
const char *source = (const char *)vdata(src_val);
jsoff_t meta_len, meta_ptr_off = vstr(js, fields_meta, &meta_len);
const jsoff_t *metadata = (const jsoff_t *)(&js->mem[meta_ptr_off]);
for (int i = 0; i < field_count; i++) {
jsoff_t name_off = metadata[i * 5 + 0];
jsoff_t name_len = metadata[i * 5 + 1];
jsoff_t init_start = metadata[i * 5 + 2];
jsoff_t init_end = metadata[i * 5 + 3];
bool computed = metadata[i * 5 + 4] != 0;
jsval_t fname;
if (computed) fname = (jsval_t) name_off;
else fname = js_mkstr(js, &source[name_off], name_len);
if (is_err(fname)) {
js->current_func = saved_func;
pop_call_frame();
return fname;
}
jsval_t field_val = js_mkundef();
if (init_start > 0 && init_end > init_start) {
field_val = js_eval_str(js, &source[init_start], init_end - init_start);
field_val = resolveprop(js, field_val);
}
jsval_t set_res = js_setprop(js, target_this, fname, field_val);
if (is_err(set_res)) {
js->current_func = saved_func;
pop_call_frame();
return set_res;
}
}
skip_fields:
if (is_async) {
UT_array *call_args;
utarray_new(call_args, &jsval_icd);
for (int i = 0; i < bound_argc; i++) utarray_push_back(call_args, &bound_args[i]);
jsval_t err;
int call_argc = parse_call_args(js, call_args, &err);
if (call_argc < 0) {
utarray_free(call_args);
pop_call_frame();
if (bound_args) free(bound_args);
js->super_val = saved_super;
js->current_func = saved_func;
return err;
}
jsval_t *argv = (jsval_t *)utarray_front(call_args);
int argc = (int)utarray_len(call_args);
res = start_async_in_coroutine(js, code_str, fnlen, closure_scope, argv, argc);
utarray_free(call_args);
} else if (is_tail && !is_arrow && !is_bound && vtype(js->new_target) == T_UNDEF) {
UT_array *tc_args_arr;
utarray_new(tc_args_arr, &jsval_icd);
if (bound_args) {
for (int i = 0; i < bound_argc; i++) utarray_push_back(tc_args_arr, &bound_args[i]);
free(bound_args); bound_args = NULL;
}
jsval_t tc_err;
int tc_argc = parse_call_args(js, tc_args_arr, &tc_err);
if (tc_argc < 0) {
utarray_free(tc_args_arr);
pop_call_frame();
js->super_val = saved_super;
js->current_func = saved_func;
js->code = code; js->clen = clen; js->pos = pos;
js->flags = (flags & ~F_THROW) | (js->flags & F_THROW);
js->tok = tok; js->consumed = 1;
return tc_err;
}
jsval_t *tc_argv = NULL;
if (tc_argc > 0) {
tc_argv = malloc(tc_argc * sizeof(jsval_t));
memcpy(tc_argv, utarray_front(tc_args_arr), tc_argc * sizeof(jsval_t));
}
utarray_free(tc_args_arr);
js->tc.pending = true;
js->tc.func = func;
js->tc.closure_scope = closure_scope;
js->tc.code_str = code_str;
js->tc.fnlen = fnlen;
js->tc.args = tc_argv;
js->tc.argc = tc_argc;
pop_call_frame();
js->super_val = saved_super;
js->current_func = saved_func;
js->code = code; js->clen = clen; js->pos = pos;
js->flags = (flags & ~F_THROW) | (js->flags & F_THROW);
js->tok = tok;
js->consumed = 1;
return (jsval_t)T_TAILCALL;
} else res = call_js_internal(js, code_str, fnlen, closure_scope, bound_args, bound_argc, func);
pop_call_frame();
if (bound_args) free(bound_args);
js->super_val = saved_super;
js->current_func = saved_func;
}
}
} else {
res = call_c(js, (jsval_t(*)(struct js *, jsval_t *, int)) vdata(func));
}
restore_state:
js->code = code, js->clen = clen, js->pos = pos;
js->flags = (flags & ~F_THROW) | (js->flags & F_THROW);
js->tok = tok;
js->consumed = 1;
return res;
}
static bool js_try_call_method(struct js *js, jsval_t obj, const char *method, size_t method_len, jsval_t *args, int nargs, jsval_t *out_result) {
jsval_t getter = js_mkundef(); bool has_getter = false;
jsoff_t off = lkp_with_getter(js, obj, method, method_len, &getter, &has_getter);
jsval_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 = resolveprop(js, mkval(T_PROP, off));
} else return false;
uint8_t ft = vtype(fn);
if (ft != T_FUNC && ft != T_CFUNC) return false;
js_parse_state_t saved_state;
JS_SAVE_STATE(js, saved_state);
uint8_t saved_flags = js->flags;
jsval_t saved_this = js->this_val;
js->this_val = obj;
push_this(obj);
jsval_t result;
if (ft == T_CFUNC) result = ((jsval_t (*)(struct js *, jsval_t *, int))vdata(fn))(js, args, nargs);
else result = call_js_with_args(js, fn, args, nargs);
bool had_throw = (js->flags & F_THROW) != 0;
jsval_t thrown = js->thrown_value;
pop_this();
js->this_val = saved_this;
JS_RESTORE_STATE(js, saved_state);
js->flags = saved_flags;
if (had_throw) {
js->flags |= F_THROW;
js->thrown_value = thrown;
}
*out_result = result;
return true;
}
static jsval_t js_call_method(struct js *js, jsval_t obj, const char *method, size_t method_len, jsval_t *args, int nargs) {
jsval_t result;
if (!js_try_call_method(js, obj, method, method_len, args, nargs, &result)) return js_mkundef();
return result;
}
static jsval_t js_call_toString(struct js *js, jsval_t value) {
jsval_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 jsval_t js_call_valueOf(struct js *js, jsval_t value) {
jsval_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(jsval_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 jsval_t try_exotic_to_primitive(struct js *js, jsval_t value, int hint) {
const char *tp_key = get_toPrimitive_sym_key();
if (!tp_key || !tp_key[0]) return mkval(T_UNDEF, 0);
size_t tp_key_len = strlen(tp_key);
jsoff_t tp_off = lkp(js, value, tp_key, tp_key_len);
if (tp_off == 0) tp_off = lkp_proto(js, value, tp_key, tp_key_len);
if (tp_off == 0) return mkval(T_UNDEF, 0);
jsval_t tp_fn = resolveprop(js, mkval(T_PROP, 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");
jsval_t hint_arg = js_mkstr(js, hint_str, strlen(hint_str));
jsval_t result = js_call_method(js, value, tp_key, tp_key_len, &hint_arg, 1);
if (is_err(result) || is_primitive(result)) return result;
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert object to primitive value");
}
static jsval_t try_ordinary_to_primitive(struct js *js, jsval_t value, int hint) {
static const char *names[] = {"valueOf", "toString"};
static const size_t lens[] = {7, 8};
int first = (hint == 1);
jsval_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");
}
static jsval_t js_to_primitive(struct js *js, jsval_t value, int hint) {
if (is_primitive(value)) return value;
if (!is_object_type(value)) return value;
jsval_t result = try_exotic_to_primitive(js, value, hint);
if (vtype(result) != T_UNDEF) return result;
return try_ordinary_to_primitive(js, value, hint);
}
static inline bool strict_eq_values(struct js *js, jsval_t l, jsval_t r) {
uint8_t t = vtype(l);
if (t != vtype(r)) return false;
if (t == T_STR) {
jsoff_t n1, n2, off1 = vstr(js, l, &n1), off2 = vstr(js, r, &n2);
return n1 == n2 && memcmp(&js->mem[off1], &js->mem[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);
}
jsval_t coerce_to_str(struct js *js, jsval_t v) {
if (vtype(v) == T_STR) return v;
if (is_object_type(v)) {
jsval_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);
}
jsval_t coerce_to_str_concat(struct js *js, jsval_t v) {
if (vtype(v) == T_STR) return v;
if (is_object_type(v)) {
jsval_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 jsval_t do_op(struct js *js, uint8_t op, jsval_t lhs, jsval_t rhs) {
if (js->flags & F_NOEXEC) return 0;
jsval_t l = is_assign(op) ? lhs : resolveprop(js, lhs);
jsval_t r = resolveprop(js, rhs);
if (is_err(l)) return l;
if (is_err(r)) return r;
if (is_assign(op) && vtype(lhs) != T_PROP && vtype(lhs) != T_PROPREF) {
if (!(js->flags & F_STRICT) && vtype(lhs) == T_UNDEF) return r;
if (!(js->flags & F_STRICT) && vtype(lhs) == T_CODEREF && op == TOK_ASSIGN) {
jsoff_t id_off = coderefoff(lhs), id_len = codereflen(lhs);
jsval_t global_scope = js->scope;
while (vdata(upper(js, global_scope)) != 0) global_scope = upper(js, global_scope);
jsval_t key = js_mkstr(js, &js->code[id_off], id_len);
if (is_err(key)) return key;
jsval_t prop = js_setprop(js, global_scope, key, r);
return is_err(prop) ? prop : r;
}
return js_mkerr_typed(js, (js->flags & F_STRICT) && vtype(lhs) == T_UNDEF ? JS_ERR_TYPE : JS_ERR_SYNTAX,
(js->flags & F_STRICT) && vtype(lhs) == T_UNDEF ? "Cannot create property on primitive value" : "Invalid left-hand side in assignment");
}
#define L(tok) [tok] = &&L_##tok
static const void *dispatch[TOK_MAX] = {
L(TOK_TYPEOF), L(TOK_VOID), L(TOK_INSTANCEOF), L(TOK_IN),
L(TOK_CALL), L(TOK_BRACKET), L(TOK_ASSIGN), L(TOK_DOT),
L(TOK_OPTIONAL_CHAIN), L(TOK_POSTINC), L(TOK_POSTDEC),
L(TOK_NOT), L(TOK_UMINUS), L(TOK_UPLUS), L(TOK_SEQ),
L(TOK_SNE), L(TOK_EQ), L(TOK_NE), L(TOK_PLUS),
L(TOK_MINUS), L(TOK_MUL), L(TOK_DIV), L(TOK_REM),
L(TOK_EXP), L(TOK_LT), L(TOK_LE), L(TOK_GT), L(TOK_GE),
L(TOK_XOR), L(TOK_AND), L(TOK_OR), L(TOK_TILDA),
L(TOK_SHL), L(TOK_SHR), L(TOK_ZSHR),
};
#undef L
if (op < TOK_MAX && dispatch[op]) goto *dispatch[op];
goto L_default;
L_TOK_TYPEOF: {
const char *ts = typestr(vtype(r));
return js_mkstr(js, ts, strlen(ts));
}
L_TOK_VOID: return js_mkundef();
L_TOK_INSTANCEOF: return do_instanceof(js, l, r);
L_TOK_IN: return do_in(js, l, r);
L_TOK_CALL: return do_call_op(js, l, r);
L_TOK_BRACKET: return do_bracket_op(js, l, rhs);
L_TOK_ASSIGN: return assign(js, lhs, r);
L_TOK_DOT: return do_dot_op(js, l, r);
L_TOK_OPTIONAL_CHAIN: return do_optional_chain_op(js, l, r);
L_TOK_POSTINC:
L_TOK_POSTDEC: {
uint8_t lhs_type = vtype(lhs);
if (lhs_type != T_PROP && lhs_type != T_PROPREF)
return js_mkerr_typed(js, JS_ERR_SYNTAX, "Invalid left-hand side expression in postfix operation");
jsval_t assign_res = do_assign_op(js, op == TOK_POSTINC ? TOK_PLUS_ASSIGN : TOK_MINUS_ASSIGN, lhs, tov(1));
if (is_err(assign_res)) return assign_res;
return l;
}
L_TOK_NOT: return mkval(T_BOOL, !js_truthy(js, r));
L_TOK_UMINUS:
if (vtype(r) == T_BIGINT) return bigint_neg(js, r);
if (is_object_type(r)) {
jsval_t prim = js_to_primitive(js, r, 2);
if (is_err(prim)) return prim;
return tov(-js_to_number(js, prim));
}
return tov(-js_to_number(js, r));
L_TOK_UPLUS:
if (vtype(r) == T_BIGINT) return js_mkerr(js, "Cannot convert a BigInt value to a number");
if (is_object_type(r)) {
jsval_t prim = js_to_primitive(js, r, 2);
if (is_err(prim)) return prim;
return tov(js_to_number(js, prim));
}
return tov(js_to_number(js, r));
L_TOK_TILDA: return tov((double)(~js_to_int32(js_to_number(js, r))));
L_TOK_SEQ:
L_TOK_SNE: {
bool eq = strict_eq_values(js, l, r);
return mkval(T_BOOL, op == TOK_SEQ ? eq : !eq);
}
L_TOK_EQ:
L_TOK_NE: {
bool eq = false;
uint8_t lt = vtype(l), rtype = vtype(r);
if ((lt == T_NULL && rtype == T_NULL) || (lt == T_UNDEF && rtype == T_UNDEF) ||
(lt == T_UNDEF && rtype == T_NULL) || (lt == T_NULL && rtype == T_UNDEF)) {
eq = true;
} else if (lt == T_NULL || rtype == T_NULL || lt == T_UNDEF || rtype == T_UNDEF) {
eq = false;
} else if (lt == rtype) {
eq = strict_eq_values(js, l, r);
} else if ((lt == T_BIGINT && rtype == T_NUM) || (lt == T_NUM && rtype == T_BIGINT)) {
double num_val = lt == T_NUM ? tod(l) : tod(r);
jsval_t bigint_val = lt == T_BIGINT ? l : r;
if (isfinite(num_val) && num_val == trunc(num_val)) {
bool neg = num_val < 0;
if (neg) num_val = -num_val;
char buf[64];
snprintf(buf, sizeof(buf), "%.0f", num_val);
eq = bigint_compare(js, bigint_val, js_mkbigint(js, buf, strlen(buf), neg)) == 0;
}
} else if (lt == T_BOOL) {
return do_op(js, op, tov(vdata(l) ? 1.0 : 0.0), r);
} else if (rtype == T_BOOL) {
return do_op(js, op, l, tov(vdata(r) ? 1.0 : 0.0));
} else if ((lt == T_NUM && rtype == T_STR) || (lt == T_STR && rtype == T_NUM)) {
eq = js_to_number(js, l) == js_to_number(js, r);
} else if (lt == T_ARR || lt == T_OBJ) {
jsval_t l_prim = js_tostring_val(js, l);
if (!is_err(l_prim)) return do_op(js, op, l_prim, r);
} else if (rtype == T_ARR || rtype == T_OBJ) {
jsval_t r_prim = js_tostring_val(js, r);
if (!is_err(r_prim)) return do_op(js, op, l, r_prim);
}
return mkval(T_BOOL, op == TOK_EQ ? eq : !eq);
}
L_TOK_PLUS: {
if (vtype(l) == T_NUM && vtype(r) == T_NUM) return tov(tod(l) + tod(r));
jsval_t lu = unwrap_primitive(js, l);
jsval_t ru = unwrap_primitive(js, r);
if (vtype(lu) == T_BIGINT && vtype(ru) == T_BIGINT) return bigint_add(js, lu, ru);
if (vtype(lu) == T_BIGINT || vtype(ru) == T_BIGINT) return js_mkerr(js, "Cannot mix BigInt value and other types");
if (is_non_numeric(lu) || is_non_numeric(ru) || (vtype(lu) == T_STR && vtype(ru) == T_STR)) {
jsval_t l_str = coerce_to_str_concat(js, l);
if (is_err(l_str)) return l_str;
jshdl_t lh = js_root(js, l_str);
jsval_t r_str = coerce_to_str_concat(js, r);
l_str = js_deref(js, lh); js_unroot(js, lh);
if (is_err(r_str)) return r_str;
return do_string_op(js, op, l_str, r_str);
}
return tov(js_to_number(js, l) + js_to_number(js, r));
}
L_TOK_MINUS:
L_TOK_MUL:
L_TOK_DIV:
L_TOK_REM:
L_TOK_EXP: {
uint8_t lt = vtype(l), rtype = vtype(r);
if (lt == T_NUM && rtype == T_NUM) {
double a = tod(l), b = tod(r);
switch (op) {
case TOK_MINUS: return tov(a - b);
case TOK_MUL: return tov(a * b);
case TOK_DIV: return tov(a / b);
case TOK_REM: return tov(a - b * ((double)(long)(a / b)));
case TOK_EXP: return tov(pow(a, b));
}
}
if (lt == T_BIGINT && rtype == T_BIGINT) {
switch (op) {
case TOK_MINUS: return bigint_sub(js, l, r);
case TOK_MUL: return bigint_mul(js, l, r);
case TOK_DIV: return bigint_div(js, l, r);
case TOK_REM: return bigint_mod(js, l, r);
case TOK_EXP: return bigint_exp(js, l, r);
}
}
if (lt == T_BIGINT || rtype == T_BIGINT)
return js_mkerr(js, "Cannot mix BigInt value and other types");
double a = js_to_number(js, l), b = js_to_number(js, r);
switch (op) {
case TOK_MINUS: return tov(a - b);
case TOK_MUL: return tov(a * b);
case TOK_DIV: return tov(a / b);
case TOK_REM: return tov(a - b * ((double)(long)(a / b)));
case TOK_EXP: return tov(pow(a, b));
}
}
L_TOK_LT:
L_TOK_LE:
L_TOK_GT:
L_TOK_GE: {
uint8_t lt = vtype(l), rtype = vtype(r);
if (lt == T_NUM && rtype == T_NUM) {
double a = tod(l), b = tod(r);
switch (op) {
case TOK_LT: return mkval(T_BOOL, a < b);
case TOK_LE: return mkval(T_BOOL, a <= b);
case TOK_GT: return mkval(T_BOOL, a > b);
case TOK_GE: return mkval(T_BOOL, a >= b);
}
}
if (lt == T_BIGINT && rtype == T_BIGINT) {
int cmp = bigint_compare(js, l, r);
switch (op) {
case TOK_LT: return mkval(T_BOOL, cmp < 0);
case TOK_LE: return mkval(T_BOOL, cmp <= 0);
case TOK_GT: return mkval(T_BOOL, cmp > 0);
case TOK_GE: return mkval(T_BOOL, cmp >= 0);
}
}
if (lt == T_BIGINT || rtype == T_BIGINT)
return js_mkerr(js, "Cannot mix BigInt value and other types");
if (lt == T_STR && rtype == T_STR)
return do_string_op(js, op, l, r);
double a = js_to_number(js, l), b = js_to_number(js, r);
switch (op) {
case TOK_LT: return mkval(T_BOOL, a < b);
case TOK_LE: return mkval(T_BOOL, a <= b);
case TOK_GT: return mkval(T_BOOL, a > b);
case TOK_GE: return mkval(T_BOOL, a >= b);
}
}
L_TOK_XOR:
L_TOK_AND:
L_TOK_OR:
L_TOK_SHL:
L_TOK_SHR:
L_TOK_ZSHR: {
uint8_t lt = vtype(l), rtype = vtype(r);
if (lt == T_BIGINT || rtype == T_BIGINT) {
if (lt != T_BIGINT || rtype != T_BIGINT)
return js_mkerr(js, "Cannot mix BigInt value and other types");
if (op == TOK_AND || op == TOK_OR || op == TOK_XOR)
return js_mkerr_typed(js, JS_ERR_TYPE, "BigInt does not support bitwise ops");
uint64_t shift = 0;
jsval_t shift_err = bigint_asint_bits(js, r, &shift);
if (is_err(shift_err)) return shift_err;
switch (op) {
case TOK_SHL: return bigint_shift_left(js, l, shift);
case TOK_SHR: return bigint_shift_right(js, l, shift);
case TOK_ZSHR: return bigint_shift_right_logical(js, l, shift);
}
}
int32_t ai = (lt == T_NUM) ? js_to_int32(tod(l)) : js_to_int32(js_to_number(js, l));
uint32_t bi = (rtype == T_NUM) ? js_to_uint32(tod(r)) : js_to_uint32(js_to_number(js, r));
switch (op) {
case TOK_XOR: return tov((double)(ai ^ (int32_t)bi));
case TOK_AND: return tov((double)(ai & (int32_t)bi));
case TOK_OR: return tov((double)(ai | (int32_t)bi));
case TOK_SHL: return tov((double)(ai << (bi & 0x1f)));
case TOK_SHR: return tov((double)(ai >> (bi & 0x1f)));
case TOK_ZSHR: return tov((double)((uint32_t)ai >> (bi & 0x1f)));
}
}
L_default:
if (is_assign(op)) return do_assign_op(js, op, lhs, r);
return js_mkerr(js, "unknown op %d", (int)op);
}
static jsval_t js_template_literal(struct js *js) {
uint8_t *in = (uint8_t *) &js->code[js->toff];
size_t template_len = js->tlen;
size_t n = 1;
jsval_t parts[64];
int part_count = 0;
while (n < template_len - 1 && part_count < 64) {
size_t part_start = n;
while (n < template_len - 1) {
if (in[n] == '\\' && n + 1 < template_len - 1) {
n += 2;
continue;
}
if (in[n] == '$' && n + 1 < template_len - 1 && in[n + 1] == '{') {
break;
}
n++;
}
if (n > part_start || (n == part_start && (n >= template_len - 1 || in[n] != '$'))) {
size_t part_len = n - part_start;
size_t needed = sizeof(jsoff_t) + part_len;
if (js->brk + needed > js->size) {
if (!js_try_grow_memory(js, needed)) return js_mkerr(js, "oom");
}
uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)];
size_t out_len = 0;
for (size_t i = part_start; i < n; i++) {
if (in[i] == '\\' && i + 1 < n) {
if (is_octal_escape(in, i)) return js_mkerr_typed(
js, JS_ERR_SYNTAX, "Octal escape sequences are not allowed in template strings."
);
i += 1 + decode_escape(in, i, n, out, &out_len, '`');
} else out[out_len++] = in[i];
}
parts[part_count++] = js_mkstr(js, NULL, out_len);
}
if (n < template_len - 1 && in[n] == '$' && in[n + 1] == '{') {
n += 2;
size_t expr_start = n;
int brace_count = 1;
while (n < template_len - 1 && brace_count > 0) {
if (in[n] == '{') brace_count++;
else if (in[n] == '}') brace_count--;
if (brace_count > 0) n++;
}
if (brace_count != 0) return js_mkerr_typed(js, JS_ERR_SYNTAX, "unclosed ${");
jsval_t expr_result = js_eval_str(js, (const char *)&in[expr_start], (jsoff_t)(n - expr_start));
if (is_err(expr_result)) return expr_result;
expr_result = resolveprop(js, expr_result);
if (vtype(expr_result) == T_SYMBOL) {
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert a Symbol value to a string");
}
if (vtype(expr_result) != T_STR) {
expr_result = coerce_to_str(js, expr_result);
if (is_err(expr_result)) return expr_result;
}
parts[part_count++] = expr_result;
n++;
}
}
if (part_count == 0) return js_mkstr(js, "", 0);
if (part_count == 1) return parts[0];
size_t total_len = 0;
for (int i = 0; i < part_count; i++) {
if (vtype(parts[i]) == T_STR) {
total_len += vstrlen(js, parts[i]);
}
}
jsval_t result = js_mkstr(js, NULL, total_len);
if (is_err(result)) return result;
jsoff_t result_len, result_off = vstr(js, result, &result_len);
size_t pos = 0;
for (int i = 0; i < part_count; i++) {
if (vtype(parts[i]) == T_STR) {
jsoff_t part_len, part_off = vstr(js, parts[i], &part_len);
memmove(&js->mem[result_off + pos], &js->mem[part_off], part_len);
pos += part_len;
}
}
return result;
}
static jsval_t js_tagged_template(struct js *js, jsval_t tag_func) {
if (js->flags & F_NOEXEC) return js_mkundef();
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
uint8_t *in = (uint8_t *) &js->code[js->toff];
size_t template_len = js->tlen;
jsval_t strings[64], values[64];
int string_count = 0, value_count = 0;
size_t n = 1;
while (n < template_len - 1) {
size_t part_start = n;
while (n < template_len - 1 && !(in[n] == '$' && n + 1 < template_len - 1 && in[n + 1] == '{')) {
if (in[n] == '\\' && n + 1 < template_len - 1) n += 2;
else n++;
}
size_t out_len = 0;
size_t needed = sizeof(jsoff_t) + (n - part_start);
if (js->brk + needed > js->size) {
if (!js_try_grow_memory(js, needed)) return js_mkerr(js, "oom");
} uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)];
for (size_t i = part_start; i < n; i++) {
if (in[i] == '\\' && i + 1 < n) {
if (is_octal_escape(in, i)) return js_mkerr_typed(
js, JS_ERR_SYNTAX, "Octal escape sequences are not allowed in template strings."
);
i += 1 + decode_escape(in, i, n, out, &out_len, '`');
} else out[out_len++] = in[i];
}
strings[string_count++] = js_mkstr(js, NULL, out_len);
if (n >= template_len - 1 || in[n] != '$') break;
n += 2;
int brace_count = 1;
size_t expr_start = n;
while (n < template_len - 1 && brace_count > 0) {
if (in[n] == '{') brace_count++;
else if (in[n] == '}') brace_count--;
if (brace_count > 0) n++;
}
if (brace_count != 0) return js_mkerr_typed(js, JS_ERR_SYNTAX, "unclosed ${");
jsval_t expr_result = js_eval_str(js, (const char *)&in[expr_start], (jsoff_t)(n - expr_start));
if (is_err(expr_result)) return expr_result;
expr_result = resolveprop(js, expr_result);
values[value_count++] = expr_result;
n++;
}
jsval_t strings_arr = mkarr(js);
for (int i = 0; i < string_count; i++) {
arr_set(js, strings_arr, (jsoff_t)i, strings[i]);
}
jsval_t args[65];
args[0] = strings_arr;
for (int i = 0; i < value_count; i++) {
args[i + 1] = values[i];
}
uint8_t saved_flags = js->flags;
jsval_t result = call_js_with_args(js, tag_func, args, 1 + value_count);
JS_RESTORE_STATE(js, saved);
js->flags = saved_flags;
js->consumed = 1;
return result;
}
static jsval_t js_str_literal(struct js *js) {
uint8_t *in = (uint8_t *) &js->code[js->toff];
size_t n1 = 0, n2 = 0;
size_t needed = sizeof(jsoff_t) + js->tlen;
if (js->brk + needed > js->size) {
if (!js_try_grow_memory(js, needed)) return js_mkerr(js, "oom");
}
uint8_t *out = &js->mem[js->brk + sizeof(jsoff_t)];
while (n2++ + 2 < js->tlen) {
if (in[n2] == '\\') {
if ((js->flags & F_STRICT) && is_octal_escape(in, n2)) return js_mkerr_typed(
js, JS_ERR_SYNTAX, "Octal escape sequences are not allowed in strict mode."
);
size_t extra = decode_escape(in, n2, js->tlen, out, &n1, in[0]);
n2 += extra + 1;
} else out[n1++] = ((uint8_t *) js->code)[js->toff + n2];
}
return js_mkstr(js, NULL, n1);
}
static jsval_t js_bigint_literal(struct js *js) {
const char *start = &js->code[js->toff];
size_t len = js->tlen - 1;
while (len > 1 && start[0] == '0') { start++; len--; }
bool neg = false;
if (len > 0 && start[0] == '-') { neg = true; start++; len--; }
return js_mkbigint(js, start, len, neg);
}
static jsval_t js_arr_destruct_assign(struct js *js) {
uint8_t exe = !(js->flags & F_NOEXEC);
js->consumed = 1;
js_parse_state_t pattern_state;
JS_SAVE_STATE(js, pattern_state);
int depth = 1;
while (depth > 0 && next(js) != TOK_EOF) {
if (js->tok == TOK_LBRACKET) depth++;
else if (js->tok == TOK_RBRACKET) depth--;
if (depth > 0) js->consumed = 1;
}
js->consumed = 1;
if (next(js) != TOK_ASSIGN) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "array destructuring requires assignment");
}
js->consumed = 1;
jsval_t v = js_expr(js);
if (is_err(v)) return v;
jsval_t arr = js_mkundef();
if (exe) {
arr = resolveprop(js, v);
if (vtype(arr) != T_ARR && vtype(arr) != T_STR) {
return js_mkerr(js, "cannot array destructure non-iterable");
}
}
js_parse_state_t end_state;
JS_SAVE_STATE(js, end_state);
JS_RESTORE_STATE(js, pattern_state);
int index = 0;
while (next(js) != TOK_RBRACKET && next(js) != TOK_EOF) {
if (next(js) == TOK_COMMA) {
js->consumed = 1;
index++;
continue;
}
bool is_rest = false;
if (next(js) == TOK_REST) {
is_rest = true;
js->consumed = 1;
}
if (next(js) != TOK_IDENTIFIER) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "identifier expected in array destructuring");
}
jsoff_t var_off = js->toff, var_len = js->tlen;
js->consumed = 1;
jsoff_t default_off = 0, default_len = 0;
if (!is_rest && next(js) == TOK_ASSIGN) {
js->consumed = 1;
default_off = js->pos;
uint8_t sf = js->flags;
js->flags |= F_NOEXEC;
jsval_t r = js_expr(js);
js->flags = sf;
if (is_err(r)) return r;
default_len = js->pos - default_off;
}
if (exe) {
const char *var_name = &js->code[var_off];
jsval_t prop_val;
if (is_rest) {
jsval_t rest_arr = js_mkarr(js);
if (is_err(rest_arr)) return rest_arr;
jsoff_t total_len = vtype(arr) == T_STR ? 0 : js_arr_len(js, arr);
if (vtype(arr) == T_STR) {
jsoff_t slen;
vstr(js, arr, &slen);
total_len = slen;
}
for (jsoff_t i = index; i < total_len; i++) {
jsval_t elem;
if (vtype(arr) == T_STR) {
jsoff_t slen, soff = vstr(js, arr, &slen);
elem = js_mkstr(js, (char *)&js->mem[soff + i], 1);
} else {
elem = js_arr_get(js, arr, i);
}
js_arr_push(js, rest_arr, elem);
}
prop_val = rest_arr;
} else {
if (vtype(arr) == T_STR) {
jsoff_t slen, soff = vstr(js, arr, &slen);
if ((jsoff_t)index < slen) {
prop_val = js_mkstr(js, (char *)&js->mem[soff + index], 1);
} else {
prop_val = js_mkundef();
}
} else {
prop_val = js_arr_get(js, arr, index);
}
if (vtype(prop_val) == T_UNDEF && default_len > 0) {
prop_val = js_eval_slice(js, default_off, default_len);
if (is_err(prop_val)) return prop_val;
prop_val = resolveprop(js, prop_val);
}
}
jsoff_t existing = lkp_scope(js, js->scope, var_name, var_len);
if (existing != 0) {
jsval_t res = js_setprop(js, js->scope, js_mkstr(js, var_name, var_len), prop_val);
if (is_err(res)) return res;
} else {
jsval_t global_scope = js->scope;
while (vdata(upper(js, global_scope)) != 0) {
global_scope = upper(js, global_scope);
}
jsval_t res = js_setprop(js, global_scope, js_mkstr(js, var_name, var_len), prop_val);
if (is_err(res)) return res;
}
}
index++;
if (next(js) == TOK_RBRACKET) break;
EXPECT(TOK_COMMA);
}
JS_RESTORE_STATE(js, end_state);
return v;
}
static jsval_t js_arr_literal(struct js *js);
static jsval_t js_arr_or_destruct(struct js *js) {
jsoff_t saved_pos = js->pos;
uint8_t saved_tok = js->tok;
uint8_t saved_consumed = js->consumed;
uint8_t saved_flags = js->flags;
int saved_stream_pos = js->token_stream_pos;
js->flags |= F_NOEXEC;
js->consumed = 1;
int depth = 1;
while (depth > 0 && next(js) != TOK_EOF) {
if (js->tok == TOK_LBRACKET) depth++;
else if (js->tok == TOK_RBRACKET) depth--;
if (depth > 0) js->consumed = 1;
}
js->consumed = 1;
bool is_destruct = (next(js) == TOK_ASSIGN);
js->pos = saved_pos;
js->tok = saved_tok;
js->consumed = saved_consumed;
js->flags = saved_flags;
js->token_stream_pos = saved_stream_pos;
if (is_destruct) {
return js_arr_destruct_assign(js);
}
return js_arr_literal(js);
}
static jsval_t js_arr_literal(struct js *js) {
bool saved_tail = js->tail_ctx;
js->tail_ctx = false;
uint8_t exe = !(js->flags & F_NOEXEC);
jsval_t arr = exe ? mkarr(js) : js_mkundef();
if (is_err(arr)) return arr;
js->consumed = 1;
jsoff_t idx = 0;
while (next(js) != TOK_RBRACKET) {
if (next(js) == TOK_COMMA) {
if (exe) {
jsoff_t doff = get_dense_buf(js, arr);
if (doff && idx >= dense_capacity(js, doff)) doff = dense_grow(js, arr, idx + 1);
if (doff) {
dense_set(js, doff, idx, T_EMPTY);
if (idx >= dense_length(js, doff)) dense_set_length(js, doff, idx + 1);
}
}
idx++; js->consumed = 1;
continue;
}
bool is_spread = (next(js) == TOK_REST);
if (is_spread) js->consumed = 1;
jsval_t val = js_expr(js);
if (!exe) goto next_elem;
if (is_err(val)) return val;
jsval_t resolved = resolveprop(js, val);
if (!is_spread) {
arr_set(js, arr, idx, resolved);
idx++; goto next_elem;
}
uint8_t t = vtype(resolved);
if (t != T_ARR && t != T_STR) goto next_elem;
if (t == T_STR) {
jsoff_t slen, soff = vstr(js, resolved, &slen);
for (jsoff_t i = 0; i < slen; i++) {
jsval_t ch = js_mkstr(js, (char *)&js->mem[soff + i], 1);
arr_set(js, arr, idx, ch); idx++;
}
goto next_elem;
}
jsoff_t len = js_arr_len(js, resolved);
for (jsoff_t i = 0; i < len; i++) {
jsval_t elem = arr_get(js, resolved, i);
arr_set(js, arr, idx, elem); idx++;
}
next_elem:
if (next(js) == TOK_RBRACKET) break;
EXPECT(TOK_COMMA);
}
EXPECT(TOK_RBRACKET);
if (exe) {
jsoff_t doff = get_dense_buf(js, arr);
if (doff) dense_set_length(js, doff, idx); else {
jsval_t res = js_mkprop_fast(js, arr, "length", 6, tov((double)idx));
if (is_err(res)) return res;
}
arr = mkval(T_ARR, vdata(arr));
}
js->tail_ctx = saved_tail;
return arr;
}
static jsval_t js_regex_literal(struct js *js) {
jsoff_t start = js->pos;
jsoff_t pattern_start = start;
bool in_class = false;
while (js->pos < js->clen) {
char c = js->code[js->pos];
if (c == '\\' && js->pos + 1 < js->clen) {
js->pos += 2;
continue;
}
if (c == '[') in_class = true;
else if (c == ']') in_class = false;
else if (c == '/' && !in_class) break;
js->pos++;
}
if (js->pos >= js->clen || js->code[js->pos] != '/') {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "unterminated regex");
}
jsoff_t pattern_end = js->pos;
js->pos++;
jsoff_t flags_start = js->pos;
while (js->pos < js->clen) {
char c = js->code[js->pos];
if (c == 'g' || c == 'i' || c == 'm' || c == 's' || c == 'u' || c == 'y') {
js->pos++;
} else {
break;
}
}
jsoff_t flags_end = js->pos;
if (js->flags & F_NOEXEC) return js_mkundef();
jsval_t pattern = js_mkstr(js, &js->code[pattern_start], pattern_end - pattern_start);
jsval_t flags = js_mkstr(js, &js->code[flags_start], flags_end - flags_start);
jsval_t regexp_obj = mkobj(js, 0);
jsval_t regexp_proto = get_ctor_proto(js, "RegExp", 6);
if (vtype(regexp_proto) == T_OBJ) set_proto(js, regexp_obj, regexp_proto);
js_setprop(js, regexp_obj, js_mkstr(js, "source", 6), pattern);
js_setprop(js, regexp_obj, js_mkstr(js, "flags", 5), flags);
jsoff_t flen = flags_end - flags_start;
const char *fstr = &js->code[flags_start];
bool global = false, ignoreCase = false, multiline = false, dotAll = false, sticky = false;
for (jsoff_t i = 0; i < flen; i++) {
if (fstr[i] == 'g') global = true;
if (fstr[i] == 'i') ignoreCase = true;
if (fstr[i] == 'm') multiline = true;
if (fstr[i] == 's') dotAll = true;
if (fstr[i] == 'y') sticky = true;
}
js_setprop(js, regexp_obj, js_mkstr(js, "global", 6), mkval(T_BOOL, global ? 1 : 0));
js_setprop(js, regexp_obj, js_mkstr(js, "ignoreCase", 10), mkval(T_BOOL, ignoreCase ? 1 : 0));
js_setprop(js, regexp_obj, js_mkstr(js, "multiline", 9), mkval(T_BOOL, multiline ? 1 : 0));
js_setprop(js, regexp_obj, js_mkstr(js, "dotAll", 6), mkval(T_BOOL, dotAll ? 1 : 0));
js_setprop(js, regexp_obj, js_mkstr(js, "sticky", 6), mkval(T_BOOL, sticky ? 1 : 0));
js_setprop(js, regexp_obj, js_mkstr(js, "lastIndex", 9), tov(0));
return regexp_obj;
}
static jsval_t set_obj_property(struct js *js, jsval_t obj, jsval_t key, jsval_t val, bool is_computed, bool *proto_set) {
bool is_proto = false;
if (!is_computed && vtype(key) == T_STR) {
jsoff_t klen;
const char *kstr = (char *)&js->mem[vstr(js, key, &klen)];
is_proto = (klen == STR_PROTO_LEN && memcmp(kstr, STR_PROTO, STR_PROTO_LEN) == 0);
}
if (is_proto) {
if (*proto_set) return js_mkerr_typed(js, JS_ERR_SYNTAX, "Duplicate __proto__ fields are not allowed in object literals");
*proto_set = true;
uint8_t pt = vtype(val);
if (pt == T_OBJ || pt == T_ARR || pt == T_FUNC || pt == T_NULL) {
set_proto(js, obj, pt == T_NULL ? js_mknull() : val);
}
return js_mkundef();
}
if (vtype(val) == T_FUNC) {
jsval_t func_obj = mkval(T_OBJ, vdata(val));
if (lkp(js, func_obj, "name", 4) == 0) {
jsval_t name_key = js_mkstr(js, "name", 4);
if (!is_err(name_key)) js_setprop(js, func_obj, name_key, key);
}
}
return js_setprop(js, obj, key, val);
}
static jsval_t js_obj_literal(struct js *js) {
bool saved_tail = js->tail_ctx;
js->tail_ctx = false;
uint8_t exe = !(js->flags & F_NOEXEC);
jsval_t obj = exe ? mkobj(js, 0) : js_mkundef();
if (is_err(obj)) return obj;
if (exe) {
jsval_t object_proto = get_ctor_proto(js, "Object", 6);
if (vtype(object_proto) == T_OBJ) set_proto(js, obj, object_proto);
}
js->consumed = 1;
bool proto_set_in_literal = false;
while (next(js) != TOK_RBRACE) {
jsval_t key = 0;
jsoff_t id_off = 0, id_len = 0;
bool is_computed = false;
if (js->tok == TOK_REST) {
js->consumed = 1;
jsval_t spread_expr = js_expr(js);
if (is_err(spread_expr)) return spread_expr;
if (!exe) goto spread_next;
jsval_t spread_obj = resolveprop(js, spread_expr);
uint8_t st = vtype(spread_obj);
if (st != T_OBJ && st != T_ARR && st != T_FUNC) goto spread_next;
jsval_t src_obj = (st == T_OBJ) ? spread_obj : mkval(T_OBJ, vdata(spread_obj));
jsoff_t next_prop_off = loadoff(js, (jsoff_t) vdata(src_obj)) & ~(3U | FLAGMASK);
while (next_prop_off < js->brk && next_prop_off != 0) {
jsoff_t header = loadoff(js, next_prop_off);
if (is_slot_prop(header)) { next_prop_off = next_prop(header); continue; }
jsoff_t koff = loadoff(js, next_prop_off + (jsoff_t) sizeof(next_prop_off));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *prop_key = (char *) &js->mem[koff + sizeof(koff)];
jsval_t prop_val = loadval(js, next_prop_off + (jsoff_t) (sizeof(next_prop_off) + sizeof(koff)));
next_prop_off = next_prop(header);
if (is_internal_prop(prop_key, klen)) continue;
jsval_t key_str = js_mkstr(js, prop_key, klen);
js_setprop(js, obj, key_str, prop_val);
}
spread_next:
if (next(js) == TOK_RBRACE) break;
EXPECT(TOK_COMMA);
continue;
}
bool is_getter = false, is_setter = false;
if (js->tok == TOK_IDENTIFIER) {
bool is_get = (js->tlen == 3 && memcmp(js->code + js->toff, "get", 3) == 0);
bool is_set = (js->tlen == 3 && memcmp(js->code + js->toff, "set", 3) == 0);
if (is_get || is_set) {
jsoff_t saved_pos = js->pos;
uint8_t saved_tok = js->tok;
uint8_t saved_consumed = js->consumed;
jsoff_t saved_toff = js->toff;
jsoff_t saved_tlen = js->tlen;
int saved_stream_pos = js->token_stream_pos;
js->consumed = 1;
uint8_t peek = next(js);
if (peek == TOK_IDENTIFIER || peek == TOK_STRING || peek == TOK_NUMBER || peek == TOK_LBRACKET) {
is_getter = is_get;
is_setter = is_set;
if (peek == TOK_IDENTIFIER) {
id_off = js->toff;
id_len = js->tlen;
if (exe) key = js_mkstr_ident(js, js->code + js->toff, js->tlen);
} else if (peek == TOK_STRING) {
id_off = js->toff;
id_len = js->tlen;
if (exe) key = js_str_literal(js);
} else if (peek == TOK_NUMBER) {
id_off = js->toff;
id_len = js->tlen;
if (exe) {
double num = strtod(js->code + js->toff, NULL);
char buf[64];
size_t n = strnum(tov(num), buf, sizeof(buf));
key = js_mkstr(js, buf, n);
}
} else if (peek == TOK_LBRACKET) {
is_computed = true;
js->consumed = 1;
jsval_t key_expr = js_expr(js);
if (is_err(key_expr)) return key_expr;
if (exe) {
jsval_t resolved_key = resolveprop(js, key_expr);
if (vtype(resolved_key) == T_STR) {
key = resolved_key;
} else {
char buf[64];
size_t n = tostr(js, resolved_key, buf, sizeof(buf));
key = js_mkstr(js, buf, n);
}
if (is_err(key)) return key;
}
if (next(js) != TOK_RBRACKET) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "] expected after computed property name");
}
}
} else {
js->pos = saved_pos;
js->tok = saved_tok;
js->consumed = saved_consumed;
js->toff = saved_toff;
js->tlen = saved_tlen;
js->token_stream_pos = saved_stream_pos;
id_off = saved_toff;
id_len = saved_tlen;
if (exe) key = js_mkstr_ident(js, js->code + saved_toff, saved_tlen);
}
} else {
id_off = js->toff;
id_len = js->tlen;
if (exe) key = js_mkstr_ident(js, js->code + js->toff, js->tlen);
}
} else if (js->tok == TOK_STRING) {
if (exe) key = js_str_literal(js);
} else if (js->tok == TOK_NUMBER) {
if (exe) {
double num = strtod(js->code + js->toff, NULL);
char buf[64];
size_t n = strnum(tov(num), buf, sizeof(buf));
key = js_mkstr(js, buf, n);
}
} else if (js->tok == TOK_LBRACKET) {
is_computed = true;
js->consumed = 1;
jsval_t key_expr = js_expr(js);
if (is_err(key_expr)) return key_expr;
if (exe) {
jsval_t resolved_key = resolveprop(js, key_expr);
if (vtype(resolved_key) == T_STR) {
key = resolved_key;
} else if (vtype(resolved_key) == T_SYMBOL) {
char buf[64];
sym_to_prop_key(resolved_key, buf, sizeof(buf));
key = js_mkstr(js, buf, strlen(buf));
} else {
char buf[64];
size_t n = tostr(js, resolved_key, buf, sizeof(buf));
key = js_mkstr(js, buf, n);
}
if (is_err(key)) return key;
}
if (next(js) != TOK_RBRACKET) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "] expected after computed property name");
}
} else if (is_keyword_propname(js->tok)) {
id_off = js->toff;
id_len = js->tlen;
if (exe) key = js_mkstr(js, js->code + js->toff, js->tlen);
} else {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "parse error");
}
js->consumed = 1;
if (!is_computed && id_len > 0 && (next(js) == TOK_COMMA || next(js) == TOK_RBRACE)) {
jsval_t val = lookup(js, js->code + id_off, id_len);
if (exe) {
if (is_err(val)) return val;
if (is_err(key)) return key;
jsval_t res = js_setprop(js, obj, key, resolveprop(js, val));
if (is_err(res)) return res;
}
} else if (
(is_getter || is_setter) ||
(!is_computed && id_len > 0 && next(js) == TOK_LPAREN) ||
(is_computed && next(js) == TOK_LPAREN)
) {
uint8_t flags = js->flags;
jsoff_t pos = js->pos - 1;
js->consumed = 1;
if (!parse_func_params(js, &flags, NULL)) {
js->flags = flags;
return js_mkerr_typed(js, JS_ERR_SYNTAX, "invalid parameters");
}
EXPECT(TOK_RPAREN, js->flags = flags);
EXPECT(TOK_LBRACE, js->flags = flags);
js->consumed = 0;
js->flags |= F_NOEXEC;
jsval_t block_res = js_block(js, false);
if (is_err(block_res)) {
js->flags = flags;
return block_res;
}
js->flags = flags;
js->consumed = 1;
if (exe) {
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) return func_obj;
set_func_code(js, func_obj, &js->code[pos], js->pos - pos);
jsval_t name_key = js_mkstr(js, "name", 4);
js_setprop(js, func_obj, name_key, key);
jsval_t closure_scope = for_let_capture_scope(js);
if (is_err(closure_scope)) return closure_scope;
set_slot(js, func_obj, SLOT_SCOPE, closure_scope);
jsval_t func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) set_proto(js, func_obj, func_proto);
jsval_t val = mkval(T_FUNC, (unsigned long) vdata(func_obj));
if (is_getter || is_setter) {
jsoff_t key_len;
const char *key_str = NULL;
if (vtype(key) == T_STR) {
jsoff_t key_off = vstr(js, key, &key_len);
key_str = (char *)&js->mem[key_off];
}
if (key_str) {
if (is_getter) {
js_set_getter_desc(js, obj, key_str, key_len, val, JS_DESC_E | JS_DESC_C);
} else js_set_setter_desc(js, obj, key_str, key_len, val, JS_DESC_E | JS_DESC_C);
}
} else {
jsval_t res = js_setprop(js, obj, key, val);
if (is_err(res)) return res;
}
}
} else {
EXPECT(TOK_COLON);
jsval_t val = js_expr(js);
if (exe) {
if (is_err(val)) return val;
if (is_err(key)) return key;
jsval_t res = set_obj_property(
js, obj, key, resolveprop(js, val),
is_computed, &proto_set_in_literal
);
if (is_err(res)) return res;
}
}
if (next(js) == TOK_RBRACE) break;
EXPECT(TOK_COMMA);
}
EXPECT(TOK_RBRACE);
js->tail_ctx = saved_tail;
return obj;
}
static void skip_default_value(struct js *js) {
int depth = 0;
while (next(js) != TOK_EOF) {
uint8_t tok = next(js);
if (depth == 0 && (tok == TOK_RPAREN || tok == TOK_COMMA)) break;
js->consumed = 1;
if (tok == TOK_LPAREN || tok == TOK_LBRACKET || tok == TOK_LBRACE) depth++;
else if (tok == TOK_RPAREN || tok == TOK_RBRACKET || tok == TOK_RBRACE) depth--;
}
}
static void skip_destructuring_pattern(struct js *js) {
uint8_t open_tok = js->tok;
uint8_t close_tok = (open_tok == TOK_LBRACE) ? TOK_RBRACE : TOK_RBRACKET;
int depth = 1;
js->consumed = 1;
while (depth > 0 && next(js) != TOK_EOF) {
if (js->tok == open_tok) depth++;
else if (js->tok == close_tok) depth--;
if (depth > 0) js->consumed = 1;
}
js->consumed = 1;
}
typedef struct { const char *name; size_t len; } param_entry_t;
static const UT_icd param_entry_icd = { sizeof(param_entry_t), NULL, NULL, NULL };
static bool parse_func_params(struct js *js, uint8_t *flags, int *out_count) {
UT_array *params;
utarray_new(params, &param_entry_icd);
#define FAIL(msg, ...) do { \
if (flags) js->flags = *flags; \
js_mkerr_typed(js, JS_ERR_SYNTAX, msg, ##__VA_ARGS__); \
utarray_free(params); \
return false; \
} while(0)
while (next(js) != TOK_EOF && next(js) != TOK_RPAREN) {
bool is_rest = (next(js) == TOK_REST);
if (is_rest) { js->consumed = 1; next(js); }
if (next(js) == TOK_LBRACE || next(js) == TOK_LBRACKET) {
skip_destructuring_pattern(js);
param_entry_t entry = {NULL, 0};
utarray_push_back(params, &entry);
if (next(js) == TOK_ASSIGN) { js->consumed = 1; skip_default_value(js); }
} else if (is_valid_param_name(next(js))) {
const char *name = &js->code[js->toff];
size_t len = js->tlen;
if ((js->flags & F_STRICT) && is_strict_restricted(name, len))
FAIL("cannot use '%.*s' as parameter name in strict mode", (int)len, name);
if (js->flags & F_STRICT) {
param_entry_t *p = NULL;
while ((p = (param_entry_t *)utarray_next(params, p))) {
if (p->len == len && p->name && memcmp(p->name, name, len) == 0)
FAIL("duplicate parameter name '%.*s' in strict mode", (int)len, name);
}
}
param_entry_t entry = {name, len};
utarray_push_back(params, &entry);
js->consumed = 1;
if (next(js) == TOK_ASSIGN) { js->consumed = 1; skip_default_value(js); }
} else {
FAIL("identifier expected");
}
if (is_rest && next(js) != TOK_RPAREN) FAIL("rest parameter must be last");
if (next(js) == TOK_RPAREN) break;
if (next(js) != TOK_COMMA) FAIL("parse error");
js->consumed = 1;
}
#undef FAIL
if (out_count) *out_count = (int)utarray_len(params);
utarray_free(params);
return true;
}
static jsval_t js_func_literal(struct js *js, bool is_async) {
uint8_t flags = js->flags;
js->consumed = 1;
jsoff_t name_off = 0, name_len = 0;
if (next(js) == TOK_IDENTIFIER) {
name_off = js->toff;
name_len = js->tlen;
js->consumed = 1;
}
EXPECT(TOK_LPAREN, js->flags = flags);
jsoff_t pos = js->pos - 1;
int param_count = 0;
if (!parse_func_params(js, &flags, &param_count)) {
js->flags = flags;
return js_mkerr_typed(js, JS_ERR_SYNTAX, "invalid parameters");
}
EXPECT(TOK_RPAREN, js->flags = flags);
EXPECT(TOK_LBRACE, js->flags = flags);
js->consumed = 0;
js->flags |= F_NOEXEC;
jsval_t res = js_block(js, false);
if (is_err(res)) {
js->flags = flags;
return res;
}
js->flags = flags;
js->consumed = 1;
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) return func_obj;
set_func_code(js, func_obj, &js->code[pos], js->pos - pos);
jsval_t res_len = js_setprop(js, func_obj, js->length_str, tov(param_count));
if (is_err(res_len)) return res_len;
js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C);
if (is_async) {
set_slot(js, func_obj, SLOT_ASYNC, js_true);
jsval_t async_proto = get_slot(js, js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) set_proto(js, func_obj, async_proto);
} else {
jsval_t func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) set_proto(js, func_obj, func_proto);
}
if (name_len > 0) {
jsval_t name_val = js_mkstr(js, &js->code[name_off], name_len);
if (is_err(name_val)) return name_val;
set_slot(js, func_obj, SLOT_NAME, name_val);
jsval_t name_key = js_mkstr(js, "name", 4);
if (is_err(name_key)) return name_key;
jsval_t res3 = js_setprop(js, func_obj, name_key, name_val);
if (is_err(res3)) return res3;
}
if (!(flags & F_NOEXEC)) {
jsval_t closure_scope = for_let_capture_scope(js);
if (is_err(closure_scope)) return closure_scope;
set_slot(js, func_obj, SLOT_SCOPE, closure_scope);
if (flags & F_STRICT) set_slot(js, func_obj, SLOT_STRICT, js_true);
}
jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj));
jsval_t proto_setup = setup_func_prototype(js, func);
if (is_err(proto_setup)) return proto_setup;
return func;
}
#define RTL_BINOP(_f1, _f2, _cond) \
jsval_t res = _f1(js); \
while (!is_err(res) && (_cond)) { \
uint8_t op = js->tok; \
js->consumed = 1; \
jsval_t rhs = _f2(js); \
if (is_err(rhs)) return rhs; \
res = do_op(js, op, res, rhs); \
} \
return res;
#define LTR_BINOP(_f, _cond) \
jsval_t res = _f(js); \
while (!is_err(res) && (_cond)) { \
uint8_t op = js->tok; \
js->consumed = 1; \
jsval_t rhs = _f(js); \
if (is_err(rhs)) return rhs; \
res = do_op(js, op, res, rhs); \
} \
return res;
static jsval_t js_class_expr(struct js *js, bool is_expression);
static jsval_t js_literal(struct js *js) {
next(js);
js->consumed = 1;
switch (js->tok) {
case TOK_ERR:
if ((js->flags & F_STRICT) && js->toff < js->clen && js->code[js->toff] == '0' &&
js->toff + 1 < js->clen && is_digit(js->code[js->toff + 1])) {
uint8_t d = js->code[js->toff + 1];
return js_mkerr_typed(js, JS_ERR_SYNTAX, d >= '0' && d <= '7'
? "Octal literals are not allowed in strict mode."
: "Decimals with leading zeros are not allowed in strict mode.");
}
return js_mkerr_typed(js, JS_ERR_SYNTAX, "parse error");
case TOK_NUMBER: return js->tval;
case TOK_BIGINT: return js_bigint_literal(js);
case TOK_STRING: return js_str_literal(js);
case TOK_TEMPLATE: return js_template_literal(js);
case TOK_LBRACE: return js_obj_literal(js);
case TOK_LBRACKET: return js_arr_or_destruct(js);
case TOK_DIV: return js_regex_literal(js);
case TOK_CLASS: return js_class_expr(js, true);
case TOK_FUNC: {
uint8_t la = lookahead(js);
if (la != TOK_LPAREN && la != TOK_IDENTIFIER) {
return mkcoderef((jsoff_t) js->toff, (jsoff_t) js->tlen);
}
return js_func_literal(js, false);
}
case TOK_ASYNC: {
jsoff_t async_off = js->toff, async_len = js->tlen;
js->consumed = 1;
uint8_t next_tok = next(js);
if (next_tok == TOK_FUNC) {
return js_func_literal(js, true);
} else if (next_tok == TOK_LPAREN) {
return js_async_arrow_paren(js);
} else if (next_tok == TOK_IDENTIFIER) {
jsoff_t id_start = js->toff;
jsoff_t id_len = js->tlen;
js->consumed = 1;
if (next(js) == TOK_ARROW) {
js->consumed = 1;
char param_buf[256];
if (id_len + 3 > sizeof(param_buf)) return js_mkerr(js, "param too long");
param_buf[0] = '(';
memcpy(param_buf + 1, &js->code[id_start], id_len);
param_buf[id_len + 1] = ')';
param_buf[id_len + 2] = '\0';
uint8_t flags = js->flags;
bool is_expr = next(js) != TOK_LBRACE;
jsoff_t body_start = is_expr ? js->toff : js->pos;
jsoff_t body_end = 0;
jsval_t body_result;
if (is_expr) {
js->flags |= F_NOEXEC;
body_result = js_assignment(js);
if (is_err(body_result)) { js->flags = flags; return body_result; }
uint8_t tok = next(js);
body_end = is_body_end_tok(tok) ? js->toff : js->pos;
} else {
body_start = js->toff;
js->flags |= F_NOEXEC;
js->consumed = 1;
body_result = js_block(js, false);
if (is_err(body_result)) { js->flags = flags; return body_result; }
if (js->tok == TOK_RBRACE && js->consumed) {
body_end = js->pos;
} else if (next(js) == TOK_RBRACE) {
body_end = js->pos;
js->consumed = 1;
} else body_end = js->pos;
}
js->flags = flags;
size_t body_len = body_end - body_start;
size_t param_len = id_len + 2;
size_t fn_len = param_len + body_len;
char *fn_str = (char *) malloc(fn_len + 1);
if (!fn_str) return js_mkerr(js, "oom");
memcpy(fn_str, param_buf, param_len);
memcpy(fn_str + param_len, &js->code[body_start], body_len);
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) { free(fn_str); return func_obj; }
set_func_code(js, func_obj, fn_str, fn_len);
free(fn_str);
set_slot(js, func_obj, SLOT_ASYNC, js_true);
set_slot(js, func_obj, SLOT_ARROW, js_true);
jsval_t async_proto = get_slot(js, js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) set_proto(js, func_obj, async_proto);
if (!(flags & F_NOEXEC)) {
jsval_t closure_scope = for_let_capture_scope(js);
if (is_err(closure_scope)) return closure_scope;
set_slot(js, func_obj, SLOT_SCOPE, closure_scope);
set_slot(js, func_obj, SLOT_THIS, js->this_val);
}
return mkval(T_FUNC, (unsigned long) vdata(func_obj));
}
return mkcoderef((jsoff_t) id_start, (jsoff_t) id_len);
}
return mkcoderef(async_off, async_len);
}
case TOK_SUPER: {
jsval_t super_ctor = js->super_val;
uint8_t la = lookahead(js);
if ((la == TOK_DOT || la == TOK_LBRACKET) && vtype(super_ctor) == T_FUNC) {
jsval_t ctor_obj = mkval(T_OBJ, vdata(super_ctor));
jsoff_t proto_off = lkp_interned(js, ctor_obj, INTERN_PROTOTYPE, 9);
if (proto_off == 0) return js_mkundef();
jsval_t proto = resolveprop(js, mkval(T_PROP, proto_off));
next(js); js->consumed = 1;
const char *prop; jsoff_t prop_len;
if (la == TOK_DOT) {
if (next(js) != TOK_IDENTIFIER && !is_keyword_propname(js->tok))
return js_mkerr_typed(js, JS_ERR_SYNTAX, "identifier expected");
prop = &js->code[js->toff]; prop_len = js->tlen;
js->consumed = 1;
} else {
jsval_t idx = js_expr(js);
if (is_err(idx)) return idx;
if (next(js) != TOK_RBRACKET) return js_mkerr_typed(js, JS_ERR_SYNTAX, "] expected");
js->consumed = 1;
idx = resolveprop(js, idx);
if (vtype(idx) == T_STR) {
prop_len = 0; prop = (const char *)&js->mem[vstr(js, idx, &prop_len)];
} else {
char buf[32]; prop_len = (jsoff_t)tostr(js, idx, buf, sizeof(buf));
prop = buf;
}
}
jsoff_t off = lkp(js, proto, prop, prop_len);
if (off == 0) off = lkp_proto(js, proto, prop, prop_len);
if (off == 0) return js_mkundef();
jsval_t method = resolveprop(js, mkval(T_PROP, off));
if (vtype(method) != T_FUNC) return method;
jsval_t bound = mkobj(js, 0);
jsval_t method_obj = mkval(T_OBJ, vdata(method));
set_slot(js, bound, SLOT_CODE, get_slot(js, method_obj, SLOT_CODE));
set_slot(js, bound, SLOT_CODE_LEN, get_slot(js, method_obj, SLOT_CODE_LEN));
set_slot(js, bound, SLOT_SCOPE, get_slot(js, method_obj, SLOT_SCOPE));
set_slot(js, bound, SLOT_BOUND_THIS, js->this_val);
jsval_t func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) set_proto(js, bound, func_proto);
return mkval(T_FUNC, vdata(bound));
}
return super_ctor;
}
case TOK_TRUE: return js_true;
case TOK_FALSE: return js_false;
case TOK_NULL: return js_mknull();
case TOK_UNDEF: return js_mkundef();
case TOK_THIS: return js->this_val;
default:
if (is_identifier_like(js->tok)) return mkcoderef((jsoff_t) js->toff, (jsoff_t) js->tlen);
size_t tok_len = js->tlen > 20 ? 20 : js->tlen;
if (tok_len == 0) return js_mkerr_typed(js, JS_ERR_SYNTAX, "Unexpected token 'EOF'");
return js_mkerr_typed(js, JS_ERR_SYNTAX, "Unexpected token '%.*s'", (int)tok_len, &js->code[js->toff]);
}
}
static jsval_t js_arrow_func(struct js *js, jsoff_t params_start, jsoff_t params_end, bool is_async) {
uint8_t flags = js->flags;
bool is_expr = next(js) != TOK_LBRACE;
jsoff_t body_start, body_end_actual;
jsval_t body_result;
if (is_expr) {
body_start = js->toff;
js->flags |= F_NOEXEC;
body_result = js_assignment(js);
if (is_err(body_result)) {
js->flags = flags;
return body_result;
}
uint8_t tok = next(js);
body_end_actual = is_body_end_tok(tok) ? js->toff : js->pos;
} else {
body_start = js->toff;
js->flags |= F_NOEXEC;
js->consumed = 1;
body_result = js_block(js, false);
if (is_err(body_result)) {
js->flags = flags;
return body_result;
}
if (js->tok == TOK_RBRACE && js->consumed) {
body_end_actual = js->pos;
} else if (next(js) == TOK_RBRACE) {
body_end_actual = js->pos;
js->consumed = 1;
} else {
body_end_actual = js->pos;
}
}
js->flags = flags;
size_t param_len = params_end - params_start;
size_t body_len = body_end_actual - body_start;
size_t fn_len = param_len + body_len;
char *fn_str = (char *) malloc(fn_len + 1);
if (!fn_str) return js_mkerr(js, "oom");
memcpy(fn_str, &js->code[params_start], param_len);
memcpy(fn_str + param_len, &js->code[body_start], body_len);
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) { free(fn_str); return func_obj; }
set_func_code(js, func_obj, fn_str, fn_len);
free(fn_str);
if (is_async) {
set_slot(js, func_obj, SLOT_ASYNC, js_true);
jsval_t async_proto = get_slot(js, js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) set_proto(js, func_obj, async_proto);
} else {
jsval_t func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) set_proto(js, func_obj, func_proto);
}
if (!(flags & F_NOEXEC)) {
jsval_t closure_scope = for_let_capture_scope(js);
if (is_err(closure_scope)) return closure_scope;
set_slot(js, func_obj, SLOT_SCOPE, closure_scope);
set_slot(js, func_obj, SLOT_THIS, js->this_val);
}
set_slot(js, func_obj, SLOT_ARROW, js_true);
return mkval(T_FUNC, (unsigned long) vdata(func_obj));
}
static jsval_t js_async_arrow_paren(struct js *js) {
jsoff_t paren_start = js->pos - 1;
js->consumed = 1;
jsoff_t saved_pos = js->pos;
uint8_t saved_tok = js->tok;
uint8_t saved_consumed = js->consumed;
uint8_t saved_flags = js->flags;
int saved_stream_pos = js->token_stream_pos;
int paren_depth = 1;
js->flags |= F_NOEXEC;
while (paren_depth > 0 && next(js) != TOK_EOF) {
if (js->tok == TOK_LPAREN) paren_depth++;
else if (js->tok == TOK_RPAREN) paren_depth--;
js->consumed = 1;
}
jsoff_t paren_end = js->pos;
bool is_arrow = lookahead(js) == TOK_ARROW;
js->pos = saved_pos;
js->tok = saved_tok;
js->consumed = saved_consumed;
js->flags = saved_flags;
js->token_stream_pos = saved_stream_pos;
if (is_arrow) {
js->flags |= F_NOEXEC;
while (next(js) != TOK_RPAREN && next(js) != TOK_EOF) {
js->consumed = 1;
}
if (next(js) != TOK_RPAREN) return js_mkerr_typed(js, JS_ERR_SYNTAX, ") expected");
js->consumed = 1;
js->flags = saved_flags;
if (next(js) != TOK_ARROW) return js_mkerr_typed(js, JS_ERR_SYNTAX, "=> expected");
js->consumed = 1;
return js_arrow_func(js, paren_start, paren_end, true);
}
return js_mkerr_typed(js, JS_ERR_SYNTAX, "async ( must be arrow function");
}
static jsval_t js_group(struct js *js) {
if (next(js) == TOK_LPAREN) {
if (++js->parse_depth > JS_MAX_PARSE_DEPTH) {
js->parse_depth--;
return js_mkerr_typed(js, JS_ERR_RANGE | JS_ERR_NO_STACK, "Maximum call stack size exceeded");
}
jsoff_t paren_start = js->pos - 1;
js->consumed = 1;
jsoff_t saved_pos = js->pos;
uint8_t saved_tok = js->tok;
uint8_t saved_consumed = js->consumed;
uint8_t saved_flags = js->flags;
int saved_stream_pos = js->token_stream_pos;
int paren_depth = 1;
bool could_be_arrow = true;
js->flags |= F_NOEXEC;
while (paren_depth > 0 && next(js) != TOK_EOF) {
if (js->tok == TOK_LPAREN) paren_depth++;
else if (js->tok == TOK_RPAREN) paren_depth--;
if (paren_depth > 0 && !is_valid_arrow_param_tok(js->tok)) could_be_arrow = false;
js->consumed = 1;
}
jsoff_t paren_end = js->pos;
bool is_arrow = could_be_arrow && lookahead(js) == TOK_ARROW;
js->pos = saved_pos;
js->tok = saved_tok;
js->consumed = saved_consumed;
js->flags = saved_flags;
js->token_stream_pos = saved_stream_pos;
if (is_arrow) {
js->flags |= F_NOEXEC;
int skip_paren_depth = 1;
while (skip_paren_depth > 0 && next(js) != TOK_EOF) {
if (js->tok == TOK_LPAREN) skip_paren_depth++;
else if (js->tok == TOK_RPAREN) skip_paren_depth--;
if (skip_paren_depth > 0) js->consumed = 1;
}
if (next(js) != TOK_RPAREN) {
js->parse_depth--;
return js_mkerr_typed(js, JS_ERR_SYNTAX, ") expected");
}
js->consumed = 1;
js->flags = saved_flags;
if (next(js) != TOK_ARROW) {
js->parse_depth--;
return js_mkerr_typed(js, JS_ERR_SYNTAX, "=> expected");
}
js->consumed = 1;
js->parse_depth--;
return js_arrow_func(js, paren_start, paren_end, false);
} else {
if (next(js) == TOK_RPAREN) {
js->parse_depth--;
return js_mkerr_typed(js, JS_ERR_SYNTAX, "Parenthesized expression cannot be empty");
}
jsval_t v = js_expr(js);
if (is_err(v)) { js->parse_depth--; return v; }
while (next(js) == TOK_COMMA) {
js->consumed = 1;
v = js_expr(js);
if (is_err(v)) { js->parse_depth--; return v; }
}
if (next(js) != TOK_RPAREN) {
js->parse_depth--;
return js_mkerr_typed(js, JS_ERR_SYNTAX, ") expected");
}
js->consumed = 1;
js->parse_depth--;
return v;
}
} else return js_literal(js);
}
static jsval_t js_call_dot(struct js *js) {
jsval_t res = js_group(js);
jsval_t obj = js_mkundef();
if (is_err(res)) return res;
if (vtype(res) == T_CODEREF) {
if (lookahead(js) == TOK_ARROW) return res;
if (lookahead(js) == TOK_TEMPLATE) {
jsval_t tag_func = lookup(js, &js->code[coderefoff(res)], codereflen(res));
if (is_err(tag_func)) return tag_func;
if (!(js->flags & F_NOEXEC) && !is_err(tag_func)) tag_func = resolveprop(js, tag_func);
js->consumed = 1;
next(js);
js->consumed = 1;
res = js_tagged_template(js, tag_func);
if (is_err(res)) return res;
goto js_call_dot_loop;
}
if ((js->flags & F_STRICT) && is_eval_or_arguments(js, coderefoff(res), codereflen(res))) {
uint8_t la = lookahead(js);
if (la == TOK_ASSIGN || la == TOK_PLUS_ASSIGN || la == TOK_MINUS_ASSIGN ||
la == TOK_MUL_ASSIGN || la == TOK_DIV_ASSIGN || la == TOK_REM_ASSIGN ||
la == TOK_SHL_ASSIGN || la == TOK_SHR_ASSIGN || la == TOK_ZSHR_ASSIGN ||
la == TOK_AND_ASSIGN || la == TOK_XOR_ASSIGN || la == TOK_OR_ASSIGN ||
la == TOK_POSTINC || la == TOK_POSTDEC) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "cannot modify eval or arguments in strict mode");
}
}
jsoff_t id_off = coderefoff(res);
jsoff_t id_len = codereflen(res);
jsoff_t saved_pos = js->pos;
uint8_t saved_tok = js->tok;
uint8_t saved_consumed = js->consumed;
int saved_stream_pos = js->token_stream_pos;
res = lookup(js, &js->code[id_off], id_len);
if (is_err(res)) {
if (!(js->flags & F_STRICT) && !(js->flags & F_NOEXEC)) {
js->pos = saved_pos;
js->tok = saved_tok;
js->consumed = saved_consumed;
js->token_stream_pos = saved_stream_pos;
uint8_t next_tok = next(js);
if (next_tok == TOK_ASSIGN) {
js->flags &= (uint8_t)~F_THROW;
js->thrown_value = js_mkundef();
if (js->errmsg) js->errmsg[0] = '\0';
return mkcoderef(id_off, id_len);
}
}
if (is_err(res)) return res;
}
}
js_call_dot_loop:
bool opt_chain_skip = false;
uint8_t cd_tok;
while (
cd_tok = next(js),
cd_tok == TOK_LPAREN
|| cd_tok == TOK_DOT
|| cd_tok == TOK_OPTIONAL_CHAIN
|| cd_tok == TOK_LBRACKET
|| cd_tok == TOK_TEMPLATE
) {
if (opt_chain_skip) {
if (js->tok == TOK_OPTIONAL_CHAIN || js->tok == TOK_DOT) {
js->consumed = 1;
uint8_t nxt = next(js);
if (nxt == TOK_HASH) { js->consumed = 1; next(js); }
if (next(js) == TOK_IDENTIFIER || is_keyword_propname(next(js))) js->consumed = 1;
else if (next(js) == TOK_LBRACKET) {
js->consumed = 1; js_expr(js);
if (next(js) == TOK_RBRACKET) js->consumed = 1;
}
if (js->tok == TOK_OPTIONAL_CHAIN) opt_chain_skip = false;
continue;
} else if (js->tok == TOK_LBRACKET) {
js->consumed = 1; js_expr(js);
if (next(js) == TOK_RBRACKET) js->consumed = 1;
continue;
} else if (js->tok == TOK_LPAREN) {
js->consumed = 1; js_call_params(js);
continue;
} else opt_chain_skip = false;
}
if (js->tok == TOK_TEMPLATE) {
if (vtype(res) == T_PROP) res = resolveprop(js, res);
if (is_err(res)) return res;
js->consumed = 1;
res = js_tagged_template(js, res);
if (is_err(res)) return res;
} else if (js->tok == TOK_DOT || js->tok == TOK_OPTIONAL_CHAIN) {
uint8_t op = js->tok;
js->consumed = 1;
if (vtype(res) != T_PROP && vtype(res) != T_PROPREF) {
obj = res;
} else obj = resolveprop(js, res);
jsval_t prop_name;
uint8_t nxt = next(js);
if (nxt == TOK_HASH) {
js->consumed = 1;
if (next(js) != TOK_IDENTIFIER) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "private field name expected");
}
js->consumed = 1;
prop_name = mkcoderef((jsoff_t) (js->toff - 1), (jsoff_t) (js->tlen + 1));
} else if (nxt == TOK_IDENTIFIER || is_keyword_propname(nxt)) {
js->consumed = 1;
prop_name = mkcoderef((jsoff_t) js->toff, (jsoff_t) js->tlen);
} else if (nxt == TOK_LBRACKET) {
js->consumed = 1;
jsval_t idx = js_expr(js);
if (is_err(idx)) return idx;
if (next(js) != TOK_RBRACKET) return js_mkerr_typed(js, JS_ERR_SYNTAX, "] expected");
js->consumed = 1;
if (op == TOK_OPTIONAL_CHAIN && (vtype(obj) == T_NULL || vtype(obj) == T_UNDEF)) {
res = js_mkundef();
opt_chain_skip = true;
} else res = do_op(js, TOK_BRACKET, res, idx);
continue;
} else prop_name = js_group(js);
if (op == TOK_OPTIONAL_CHAIN && (vtype(obj) == T_NULL || vtype(obj) == T_UNDEF)) {
res = js_mkundef();
opt_chain_skip = true;
} else res = do_op(js, op, res, prop_name);
} else if (js->tok == TOK_LBRACKET) {
js->consumed = 1;
if (vtype(res) != T_PROP && vtype(res) != T_PROPREF) {
obj = res;
} else obj = resolveprop(js, res);
jsval_t idx = js_expr(js);
if (is_err(idx)) return idx;
if (next(js) != TOK_RBRACKET) return js_mkerr_typed(js, JS_ERR_SYNTAX, "] expected");
js->consumed = 1;
res = do_op(js, TOK_BRACKET, res, idx);
} else {
jsval_t func_this = obj;
bool is_super_call = (vtype(js->super_val) != T_UNDEF && res == js->super_val);
if (vtype(obj) == T_UNDEF) {
if (vtype(res) == T_PROPREF) {
jsoff_t obj_off = propref_obj(res);
func_this = mkval(T_OBJ, obj_off);
} else func_this = js->this_val;
}
push_this(func_this);
jsval_t params = js_call_params(js);
if (is_err(params)) {
pop_this();
return params;
}
res = do_op(js, TOK_CALL, res, params);
pop_this();
if (is_super_call && !is_err(res) && is_object_type(res)) {
jsoff_t proto_off = lkp_interned(js, mkval(T_OBJ, vdata(js->current_func)), INTERN_PROTOTYPE, 9);
if (proto_off) set_proto(js, res, resolveprop(js, mkval(T_PROP, proto_off)));
js->this_val = res;
if (global_this_stack.depth > 0) global_this_stack.stack[global_this_stack.depth - 1] = res;
}
obj = js_mkundef();
}
}
return res;
}
static jsval_t js_postfix(struct js *js) {
jsval_t res = js_call_dot(js);
if (is_err(res)) return res;
next(js);
if ((js->tok == TOK_POSTINC || js->tok == TOK_POSTDEC) && !js->had_newline) {
js->consumed = 1;
res = do_op(js, js->tok, res, 0);
}
return res;
}
static inline jsval_t resolve_coderef(struct js *js, jsval_t v) {
if (vtype(v) == T_CODEREF) return lookup(js, &js->code[coderefoff(v)], codereflen(v));
return v;
}
static void unlink_prop(struct js *js, jsoff_t obj_off, jsoff_t prop_off, jsoff_t prev_off) {
jsoff_t deleted_next = loadoff(js, prop_off) & ~FLAGMASK;
jsoff_t target = prev_off ? prev_off : obj_off;
jsoff_t current = loadoff(js, target);
saveoff(js, target, (deleted_next & ~3ULL) | (current & (FLAGMASK | 3ULL)));
jsoff_t tail = loadoff(js, obj_off + sizeof(jsoff_t) + sizeof(jsoff_t));
if (tail == prop_off) {
saveoff(js, obj_off + sizeof(jsoff_t) + sizeof(jsoff_t), prev_off);
}
invalidate_prop_cache(js, obj_off, prop_off);
js->needs_gc = true;
}
static jsval_t check_frozen_sealed(struct js *js, jsval_t obj, const char *action) {
if (js_truthy(js, get_slot(js, obj, SLOT_FROZEN))) {
if (js->flags & F_STRICT) return js_mkerr(js, "cannot %s property of frozen object", action);
return js_false;
}
if (js_truthy(js, get_slot(js, obj, SLOT_SEALED))) {
if (js->flags & F_STRICT) return js_mkerr(js, "cannot %s property of sealed object", action);
return js_false;
}
return js_mkundef();
}
static jsval_t js_unary(struct js *js) {
uint8_t tok = next(js);
static const void *dispatch[] = {
[TOK_NEW] = &&do_new,
[TOK_DELETE] = &&do_delete,
[TOK_AWAIT] = &&do_await,
[TOK_POSTINC] = &&do_prefix_inc,
[TOK_POSTDEC] = &&do_prefix_inc,
[TOK_NOT] = &&do_unary_op,
[TOK_TILDA] = &&do_unary_op,
[TOK_TYPEOF] = &&do_typeof,
[TOK_VOID] = &&do_unary_op,
[TOK_MINUS] = &&do_unary_op,
[TOK_PLUS] = &&do_unary_op,
};
if (tok < sizeof(dispatch)/sizeof(dispatch[0]) && dispatch[tok]) {
goto *dispatch[tok];
}
return js_postfix(js);
do_new: {
js->consumed = 1;
jsval_t obj = mkobj(js, 0);
jsval_t saved_this = js->this_val;
jsval_t saved_new_target = js->new_target;
jsval_t ctor = js_group(js);
if (is_err(ctor)) { return ctor; }
while (next(js) == TOK_DOT || next(js) == TOK_LBRACKET) {
ctor = resolve_coderef(js, ctor);
if (is_err(ctor)) { return ctor; }
if (js->tok == TOK_DOT) {
js->consumed = 1;
if (next(js) != TOK_IDENTIFIER && !is_keyword_propname(js->tok)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "identifier expected");
}
js->consumed = 1;
ctor = do_op(js, TOK_DOT, ctor, mkcoderef((jsoff_t)js->toff, (jsoff_t)js->tlen));
} else {
js->consumed = 1;
jsval_t idx = js_expr(js);
if (is_err(idx)) { return idx; }
if (next(js) != TOK_RBRACKET) { return js_mkerr_typed(js, JS_ERR_SYNTAX, "] expected"); }
js->consumed = 1;
ctor = do_op(js, TOK_BRACKET, ctor, idx);
}
}
ctor = resolve_coderef(js, ctor);
if (is_err(ctor)) { return ctor; }
if (vtype(ctor) == T_PROP || vtype(ctor) == T_PROPREF) ctor = resolveprop(js, ctor);
js->new_target = ctor;
jsval_t result;
push_this(obj);
if (next(js) == TOK_LPAREN) {
jsval_t params = js_call_params(js);
if (is_err(params)) {
pop_this();
js->new_target = saved_new_target; return params;
}
result = do_op(js, TOK_CALL, ctor, params);
} else {
result = do_op(js, TOK_CALL, ctor, mkcoderef(0, 0));
js->consumed = 0;
}
jsval_t constructed_obj = peek_this();
pop_this();
js->this_val = saved_this;
js->new_target = saved_new_target;
if (is_err(result)) return result;
uint8_t rtype = vtype(result);
jsval_t new_result = (
rtype == T_OBJ || rtype == T_ARR ||
rtype == T_PROMISE || rtype == T_FUNC
) ? result : constructed_obj;
if (vtype(new_result) == T_OBJ && (vtype(ctor) == T_FUNC || vtype(ctor) == T_CFUNC)) {
set_slot(js, new_result, SLOT_CTOR, ctor);
}
jsval_t call_obj = js_mkundef();
while (next(js) == TOK_DOT || next(js) == TOK_LBRACKET || next(js) == TOK_OPTIONAL_CHAIN || next(js) == TOK_LPAREN) {
uint8_t op = js->tok;
if (op == TOK_DOT || op == TOK_OPTIONAL_CHAIN) {
js->consumed = 1;
call_obj = new_result;
if (op == TOK_OPTIONAL_CHAIN && (vtype(call_obj) == T_NULL || vtype(call_obj) == T_UNDEF)) {
new_result = call_obj = js_mkundef();
} else {
if (next(js) != TOK_IDENTIFIER && !is_keyword_propname(js->tok)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "identifier expected");
}
js->consumed = 1;
new_result = do_op(js, op, new_result, mkcoderef((jsoff_t)js->toff, (jsoff_t)js->tlen));
}
} else if (op == TOK_LBRACKET) {
js->consumed = 1;
call_obj = new_result;
jsval_t idx = js_expr(js);
if (is_err(idx)) return idx;
if (next(js) != TOK_RBRACKET) return js_mkerr_typed(js, JS_ERR_SYNTAX, "] expected");
js->consumed = 1;
new_result = do_op(js, TOK_BRACKET, new_result, idx);
} else {
jsval_t func_this = vtype(call_obj) == T_UNDEF ? js->this_val : call_obj;
push_this(func_this);
jsval_t params = js_call_params(js);
if (is_err(params)) { pop_this(); return params; }
new_result = do_op(js, TOK_CALL, new_result, params);
pop_this();
call_obj = js_mkundef();
}
}
return new_result;
}
do_delete: {
js->consumed = 1;
if ((js->flags & F_STRICT) && next(js) == TOK_IDENTIFIER) {
jsoff_t id_pos = js->pos;
uint8_t id_tok = js->tok;
jsoff_t id_toff = js->toff, id_tlen = js->tlen;
int id_stream_pos = js->token_stream_pos;
js->consumed = 1;
uint8_t after = next(js);
if (after != TOK_DOT && after != TOK_LBRACKET && after != TOK_OPTIONAL_CHAIN) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "cannot delete unqualified identifier in strict mode");
}
js->pos = id_pos; js->tok = id_tok; js->toff = id_toff; js->tlen = id_tlen; js->consumed = 0;
js->token_stream_pos = id_stream_pos;
}
js_parse_state_t saved_state;
JS_SAVE_STATE(js, saved_state);
uint8_t saved_flags = js->flags;
jsval_t operand = js_postfix(js);
if (is_err(operand)) {
JS_RESTORE_STATE(js, saved_state);
js->flags = (saved_flags & ~F_THROW) | F_NOEXEC;
js_postfix(js);
js->flags = saved_flags;
return js_true;
}
if (js->flags & F_NOEXEC) return js_true;
if (vtype(operand) == T_PROPREF) {
jsoff_t obj_off = propref_obj(operand);
jsoff_t key_off = propref_key(operand);
jsval_t obj = mkval(T_OBJ, obj_off);
jsval_t key = mkval(T_STR, key_off);
jsoff_t len;
const char *key_str = (const char *)&js->mem[vstr(js, key, &len)];
if (is_proxy(js, obj)) {
jsval_t result = proxy_delete(js, obj, key_str, len);
return is_err(result) ? result : js_bool(js_truthy(js, result));
}
jsval_t err = check_frozen_sealed(js, obj, "delete");
if (vtype(err) != T_UNDEF) return err;
if (is_arr_off(js, obj_off)) {
jsoff_t doff = get_dense_buf_off(js, obj_off);
unsigned long del_idx;
if (doff && parse_array_index(key_str, len, dense_length(js, doff), &del_idx)) {
arr_del(js, mkval(T_ARR, (uint64_t)obj_off), (jsoff_t)del_idx);
return js_true;
}
}
jsoff_t prop_off = lkp(js, obj, key_str, len);
if (prop_off == 0) {
try_dynamic_deleter(js, obj, key_str, len);
return js_true;
}
if (is_nonconfig_prop(js, prop_off)) {
if (js->flags & F_STRICT) return js_mkerr_typed(js, JS_ERR_TYPE, "cannot delete non-configurable property");
return js_false;
}
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, key_str, len);
if (desc && !desc->configurable) {
if (js->flags & F_STRICT) return js_mkerr_typed(js, JS_ERR_TYPE, "cannot delete non-configurable property");
return js_false;
}
jsoff_t first_prop = loadoff(js, obj_off) & ~(3U | FLAGMASK);
if (first_prop == prop_off) {
unlink_prop(js, obj_off, prop_off, 0);
return js_true;
}
for (jsoff_t prev = first_prop; prev != 0; ) {
jsoff_t next_prop = loadoff(js, prev) & ~(3U | FLAGMASK);
if (next_prop == prop_off) {
unlink_prop(js, obj_off, prop_off, prev);
return js_true;
}
prev = next_prop;
}
return js_true;
}
if (vtype(operand) == T_PROP) {
jsoff_t prop_off = (jsoff_t)vdata(operand);
if (is_nonconfig_prop(js, prop_off)) {
if (js->flags & F_STRICT) return js_mkerr(js, "cannot delete non-configurable property");
return js_false;
}
for (jsval_t scope = js->scope; ; scope = upper(js, scope)) {
jsoff_t scope_off = (jsoff_t)vdata(scope);
jsoff_t first = loadoff(js, scope_off) & ~(3U | FLAGMASK);
if (first == prop_off) {
unlink_prop(js, scope_off, prop_off, 0);
return js_true;
}
for (jsoff_t cur = first; cur != 0 && cur < js->brk; ) {
jsoff_t nx = loadoff(js, cur) & ~(3U | FLAGMASK);
if (nx == prop_off) {
unlink_prop(js, scope_off, prop_off, cur);
return js_true;
} cur = nx;
} if (vdata(scope) == 0) break;
} return js_true;
} return js_true;
}
do_await: {
js->consumed = 1;
jsval_t expr = js_unary(js);
if (is_err(expr)) return expr;
if (js->flags & F_NOEXEC) return expr;
jsval_t resolved = resolveprop(js, expr);
if (vtype(resolved) != T_PROMISE) return resolved;
uint32_t pid = get_promise_id(js, resolved);
promise_data_entry_t *pd = get_promise_data(pid, false);
if (!pd) return js_mkerr(js, "invalid promise state");
if (pd->state == 1) return pd->value;
if (pd->state == 2) return js_throw(js, pd->value);
mco_coro *current_mco = mco_running();
if (!current_mco) return js_mkerr(js, "await can only be used inside async functions");
async_exec_context_t *ctx = (async_exec_context_t *)mco_get_user_data(current_mco);
if (!ctx || !ctx->coro) return js_mkerr(js, "invalid async context");
coroutine_t *coro = ctx->coro;
coro->awaited_promise = resolved;
coro->is_settled = coro->is_ready = false;
jsval_t resume_obj = mkobj(js, 0);
set_slot(js, resume_obj, SLOT_CFUNC, js_mkfun(resume_coroutine_wrapper));
set_slot(js, resume_obj, SLOT_CORO, tov((double)(uintptr_t)coro));
jsval_t reject_obj = mkobj(js, 0);
set_slot(js, reject_obj, SLOT_CFUNC, js_mkfun(reject_coroutine_wrapper));
set_slot(js, reject_obj, SLOT_CORO, tov((double)(uintptr_t)coro));
jsval_t then_args[] = { mkval(T_FUNC, vdata(resume_obj)), mkval(T_FUNC, vdata(reject_obj)) };
jsval_t saved_this = js->this_val;
js->this_val = resolved;
builtin_promise_then(js, then_args, 2);
js->this_val = saved_this;
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
uint8_t saved_flags = js->flags;
jsval_t saved_current_func = js->current_func;
mco_result mco_res = mco_yield(current_mco);
JS_RESTORE_STATE(js, saved);
js->flags = saved_flags;
js->current_func = saved_current_func;
if (mco_res != MCO_SUCCESS) return js_mkerr(js, "failed to yield coroutine");
jsval_t result = coro->result;
bool is_error = coro->is_error;
coro->is_settled = false;
coro->awaited_promise = js_mkundef();
return is_error ? js_throw(js, result) : result;
}
do_prefix_inc: {
uint8_t op = js->tok;
js->consumed = 1;
if ((js->flags & F_STRICT) && next(js) == TOK_IDENTIFIER && is_eval_or_arguments(js, js->toff, js->tlen)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "cannot modify eval or arguments in strict mode");
}
jsval_t operand = js_unary(js);
if (is_err(operand)) return operand;
if (js->flags & F_NOEXEC) return operand;
jsval_t resolved = resolveprop(js, operand);
if (vtype(operand) == T_PROP || vtype(operand) == T_PROPREF) {
jsval_t assign_res = do_assign_op(js, op == TOK_POSTINC ? TOK_PLUS_ASSIGN : TOK_MINUS_ASSIGN, operand, tov(1));
if (is_err(assign_res)) return assign_res;
} else return js_mkerr_typed(js, JS_ERR_SYNTAX, "Invalid left-hand side in assignment");
return do_op(js, op == TOK_POSTINC ? TOK_PLUS : TOK_MINUS, resolved, tov(1));
}
do_typeof: {
js->consumed = 1;
jsoff_t saved_pos = js->pos;
uint8_t saved_tok = js->tok, saved_consumed = js->consumed, saved_flags = js->flags;
int saved_stream_pos = js->token_stream_pos;
bool bare = is_typeof_bare_ident(js);
jsval_t operand = js_unary(js);
if (is_err(operand)) {
if (!bare || get_error_type(js) != JS_ERR_REFERENCE) return operand;
js->pos = saved_pos; js->tok = saved_tok; js->consumed = saved_consumed;
js->token_stream_pos = saved_stream_pos;
js->flags = (saved_flags & ~F_THROW) | F_NOEXEC;
js_unary(js);
js->flags = saved_flags & ~F_THROW;
operand = js_mkundef();
}
return do_op(js, TOK_TYPEOF, js_mkundef(), operand);
}
do_unary_op: {
uint8_t t = js->tok;
if (t == TOK_MINUS) t = TOK_UMINUS;
if (t == TOK_PLUS) t = TOK_UPLUS;
js->consumed = 1;
jsval_t operand = js_unary(js);
if (is_err(operand)) return operand;
if (next(js) == TOK_EXP) {
return js_mkerr_typed(
js, JS_ERR_SYNTAX,
"Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence"
);
}
return do_op(js, t, js_mkundef(), operand);
}
}
static jsval_t js_expr_bp(struct js *js, uint8_t min_bp) {
jsval_t lhs = js_unary(js);
if (is_err(lhs)) return lhs;
loop: {
uint8_t tok = next(js);
if (tok >= TOK_MAX) return lhs;
uint8_t bp = prec_table[tok];
if (bp == 0 || bp < min_bp) return lhs;
js->consumed = 1;
static const void *dispatch[] = {
[TOK_LOR] = &&do_lor,
[TOK_LAND] = &&do_land,
[TOK_NULLISH] = &&do_nullish,
[TOK_EXP] = &&do_exp,
[TOK_OR] = &&do_binop,
[TOK_XOR] = &&do_binop,
[TOK_AND] = &&do_binop,
[TOK_EQ] = &&do_binop,
[TOK_NE] = &&do_binop,
[TOK_SEQ] = &&do_binop,
[TOK_SNE] = &&do_binop,
[TOK_LT] = &&do_binop,
[TOK_LE] = &&do_binop,
[TOK_GT] = &&do_binop,
[TOK_GE] = &&do_binop,
[TOK_INSTANCEOF] = &&do_binop,
[TOK_IN] = &&do_binop,
[TOK_SHL] = &&do_binop,
[TOK_SHR] = &&do_binop,
[TOK_ZSHR] = &&do_binop,
[TOK_PLUS] = &&do_binop,
[TOK_MINUS] = &&do_binop,
[TOK_MUL] = &&do_binop,
[TOK_DIV] = &&do_binop,
[TOK_REM] = &&do_binop,
};
goto *dispatch[tok];
do_binop: {
jshdl_t lh = js_root(js, lhs);
jsval_t rhs = js_expr_bp(js, bp + 1);
lhs = js_deref(js, lh); js_unroot(js, lh);
if (is_err(rhs)) return rhs;
lhs = do_op(js, tok, lhs, rhs);
goto loop;
}
do_exp: {
jshdl_t lh = js_root(js, lhs);
jsval_t rhs = js_expr_bp(js, bp);
lhs = js_deref(js, lh); js_unroot(js, lh);
if (is_err(rhs)) return rhs;
lhs = do_op(js, TOK_EXP, lhs, rhs);
goto loop;
}
do_lor: {
uint8_t flags = js->flags;
lhs = resolveprop(js, lhs);
if (js_truthy(js, lhs)) js->flags |= F_NOEXEC;
jshdl_t lh = js_root(js, lhs);
jsval_t rhs = js_expr_bp(js, bp);
lhs = js_deref(js, lh); js_unroot(js, lh);
if (!(flags & F_NOEXEC) && !js_truthy(js, lhs)) lhs = rhs;
js->flags = flags;
if (is_err(rhs)) return rhs;
goto loop;
}
do_land: {
uint8_t flags = js->flags;
lhs = resolveprop(js, lhs);
if (!js_truthy(js, lhs)) js->flags |= F_NOEXEC;
jshdl_t lh = js_root(js, lhs);
jsval_t rhs = js_expr_bp(js, bp);
lhs = js_deref(js, lh); js_unroot(js, lh);
if (!(flags & F_NOEXEC) && js_truthy(js, lhs)) lhs = rhs;
js->flags = flags;
if (is_err(rhs)) return rhs;
goto loop;
}
do_nullish: {
uint8_t flags = js->flags;
lhs = resolveprop(js, lhs);
uint8_t lhs_type = vtype(lhs);
if (lhs_type != T_NULL && lhs_type != T_UNDEF) js->flags |= F_NOEXEC;
jshdl_t lh = js_root(js, lhs);
jsval_t rhs = js_expr_bp(js, bp);
lhs = js_deref(js, lh); js_unroot(js, lh);
if (!(flags & F_NOEXEC) && (lhs_type == T_NULL || lhs_type == T_UNDEF)) lhs = rhs;
js->flags = flags;
if (is_err(rhs)) return rhs;
goto loop;
}
}
}
static jsval_t js_ternary(struct js *js) {
jsval_t res = js_expr_bp(js, 1);
if (next(js) == TOK_Q) {
uint8_t flags = js->flags;
js->consumed = 1;
if (js_truthy(js, resolveprop(js, res))) {
res = js_ternary(js);
js->flags |= F_NOEXEC;
EXPECT(TOK_COLON, js->flags = flags);
js_ternary(js);
js->flags = flags;
} else {
js->flags |= F_NOEXEC;
js_ternary(js);
EXPECT(TOK_COLON, js->flags = flags);
js->flags = flags;
res = js_ternary(js);
}
}
return res;
}
static jsval_t js_assignment(struct js *js) {
jsval_t res = js_ternary(js);
if (!is_err(res) && vtype(res) == T_CODEREF && next(js) == TOK_ARROW) {
jsoff_t param_start = coderefoff(res);
jsoff_t param_len = codereflen(res);
js->consumed = 1;
char param_buf[256];
if (param_len + 3 > sizeof(param_buf)) return js_mkerr(js, "param too long");
param_buf[0] = '(';
memcpy(param_buf + 1, &js->code[param_start], param_len);
param_buf[param_len + 1] = ')';
param_buf[param_len + 2] = '\0';
uint8_t flags = js->flags;
bool is_expr = next(js) != TOK_LBRACE;
jsoff_t body_start = js->pos;
if (is_expr && js->tok != TOK_EOF) {
body_start = js->toff;
}
jsval_t body_result;
if (is_expr) {
js->flags |= F_NOEXEC;
body_result = js_assignment(js);
if (is_err(body_result)) {
js->flags = flags;
return body_result;
}
} else {
body_start = js->toff;
js->flags |= F_NOEXEC;
js->consumed = 1;
body_result = js_block(js, false);
if (is_err(body_result)) {
js->flags = flags;
return body_result;
}
if (js->tok == TOK_RBRACE && js->consumed) {
} else if (next(js) == TOK_RBRACE) {
js->consumed = 1;
}
}
js->flags = flags;
jsoff_t body_end;
if (is_expr) {
uint8_t tok = next(js);
body_end = is_body_end_tok(tok) ? js->toff : js->pos;
} else body_end = js->pos;
size_t params_len = param_len + 2;
size_t body_len = body_end - body_start;
size_t fn_len = params_len + body_len;
char *fn_str = (char *) malloc(fn_len + 1);
if (!fn_str) return js_mkerr(js, "oom");
memcpy(fn_str, param_buf, params_len);
memcpy(fn_str + params_len, &js->code[body_start], body_len);
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) { free(fn_str); return func_obj; }
set_func_code(js, func_obj, fn_str, fn_len);
free(fn_str);
jsval_t func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) set_proto(js, func_obj, func_proto);
if (!(flags & F_NOEXEC)) {
jsval_t closure_scope = for_let_capture_scope(js);
if (is_err(closure_scope)) return closure_scope;
set_slot(js, func_obj, SLOT_SCOPE, closure_scope);
set_slot(js, func_obj, SLOT_THIS, js->this_val);
}
set_slot(js, func_obj, SLOT_ARROW, js_true);
return mkval(T_FUNC, (unsigned long) vdata(func_obj));
}
while (
!is_err(res) && (next(js) == TOK_ASSIGN || js->tok == TOK_PLUS_ASSIGN ||
js->tok == TOK_MINUS_ASSIGN || js->tok == TOK_MUL_ASSIGN ||
js->tok == TOK_DIV_ASSIGN || js->tok == TOK_REM_ASSIGN ||
js->tok == TOK_SHL_ASSIGN || js->tok == TOK_SHR_ASSIGN ||
js->tok == TOK_ZSHR_ASSIGN || js->tok == TOK_AND_ASSIGN ||
js->tok == TOK_XOR_ASSIGN || js->tok == TOK_OR_ASSIGN ||
js->tok == TOK_LOR_ASSIGN || js->tok == TOK_LAND_ASSIGN ||
js->tok == TOK_NULLISH_ASSIGN)
) {
uint8_t op = js->tok;
js->consumed = 1;
if (op == TOK_LOR_ASSIGN || op == TOK_LAND_ASSIGN || op == TOK_NULLISH_ASSIGN) {
jsval_t lhs_val = js_mkundef();
bool should_assign = true;
if (!(js->flags & F_NOEXEC)) {
lhs_val = resolveprop(js, res);
if (is_err(lhs_val)) return lhs_val;
if (op == TOK_LOR_ASSIGN) {
should_assign = !js_truthy(js, lhs_val);
} else if (op == TOK_LAND_ASSIGN) {
should_assign = js_truthy(js, lhs_val);
} else should_assign = is_null(lhs_val) || is_undefined(lhs_val);
}
if (should_assign || (js->flags & F_NOEXEC)) {
jsval_t rhs = js_assignment(js);
if (is_err(rhs)) return rhs;
if (!(js->flags & F_NOEXEC) && should_assign) {
jsval_t rhs_resolved = resolveprop(js, rhs);
if (is_err(rhs_resolved)) return rhs_resolved;
res = assign(js, res, rhs_resolved);
}
} else {
uint8_t saved_flags = js->flags;
js->flags |= F_NOEXEC;
jsval_t rhs = js_assignment(js);
js->flags = saved_flags;
if (is_err(rhs)) return rhs;
res = lhs_val;
}
continue;
}
jsval_t lhs_val = js_mkundef();
if (op != TOK_ASSIGN && !(js->flags & F_NOEXEC)) {
lhs_val = resolveprop(js, res);
if (is_err(lhs_val)) return lhs_val;
}
jsval_t rhs = js_assignment(js);
if (is_err(rhs)) return rhs;
if (op == TOK_ASSIGN) {
res = do_op(js, op, res, rhs);
} else {
jsval_t rhs_resolved = resolveprop(js, rhs);
if (is_err(rhs_resolved)) return rhs_resolved;
uint8_t m[] = {
TOK_PLUS, TOK_MINUS, TOK_MUL, TOK_DIV, TOK_REM, TOK_SHL,
TOK_SHR, TOK_ZSHR, TOK_AND, TOK_XOR, TOK_OR
};
uint8_t binary_op = m[op - TOK_PLUS_ASSIGN];
jsval_t op_result = do_op(js, binary_op, lhs_val, rhs_resolved);
if (is_err(op_result)) return op_result;
res = assign(js, res, op_result);
}
}
return res;
}
static jsval_t js_decl(struct js *js, bool is_const) {
uint8_t exe = !(js->flags & F_NOEXEC);
js->consumed = 1;
for (;;) {
if (next(js) == TOK_LBRACE) {
js->consumed = 1;
js_parse_state_t pattern_state;
JS_SAVE_STATE(js, pattern_state);
int depth = 1;
while (depth > 0 && next(js) != TOK_EOF) {
if (js->tok == TOK_LBRACE) depth++;
else if (js->tok == TOK_RBRACE) depth--;
if (depth > 0) js->consumed = 1;
}
js->consumed = 1;
if (next(js) != TOK_ASSIGN) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "destructuring requires assignment");
}
js->consumed = 1;
jsval_t v = js_expr(js);
if (is_err(v)) return v;
jsval_t obj = js_mkundef();
if (exe) {
obj = resolveprop(js, v);
uint8_t ot = vtype(obj);
if (ot == T_NULL || ot == T_UNDEF) return js_mkerr(js, "cannot destructure null or undefined");
}
js_parse_state_t end_state;
JS_SAVE_STATE(js, end_state);
JS_RESTORE_STATE(js, pattern_state);
jsval_t picked_keys = exe ? js_mkarr(js) : js_mkundef();
if (exe && is_err(picked_keys)) return picked_keys;
while (next(js) != TOK_RBRACE && next(js) != TOK_EOF) {
bool is_rest = false;
if (next(js) == TOK_REST) {
is_rest = true;
js->consumed = 1;
}
if (next(js) != TOK_IDENTIFIER) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "identifier expected in object destructuring");
}
jsoff_t src_off = js->toff, src_len = js->tlen;
jsoff_t var_off = src_off, var_len = src_len;
js->consumed = 1;
bool is_nested_obj = false;
bool is_nested_arr = false;
jsoff_t nested_pattern_start = 0;
if (!is_rest && next(js) == TOK_COLON) {
js->consumed = 1;
if (next(js) == TOK_LBRACE) {
is_nested_obj = true;
nested_pattern_start = js->toff;
int inner_depth = 1;
js->consumed = 1;
while (inner_depth > 0 && next(js) != TOK_EOF) {
if (js->tok == TOK_LBRACE) inner_depth++;
else if (js->tok == TOK_RBRACE) inner_depth--;
if (inner_depth > 0) js->consumed = 1;
}
js->consumed = 1;
} else if (next(js) == TOK_LBRACKET) {
is_nested_arr = true;
nested_pattern_start = js->toff;
int inner_depth = 1;
js->consumed = 1;
while (inner_depth > 0 && next(js) != TOK_EOF) {
if (js->tok == TOK_LBRACKET) inner_depth++;
else if (js->tok == TOK_RBRACKET) inner_depth--;
if (inner_depth > 0) js->consumed = 1;
}
js->consumed = 1;
} else {
EXPECT_IDENT();
var_off = js->toff;
var_len = js->tlen;
js->consumed = 1;
}
}
jsoff_t default_off = 0, default_len = 0;
if (!is_rest && !is_nested_obj && !is_nested_arr && next(js) == TOK_ASSIGN) {
js->consumed = 1;
default_off = js->pos;
uint8_t sf = js->flags;
js->flags |= F_NOEXEC;
jsval_t r = js_expr(js);
js->flags = sf;
if (is_err(r)) return r;
default_len = js->pos - default_off;
}
if (!exe) goto obj_destruct_next;
if (is_rest) goto obj_destruct_rest;
if (is_nested_obj || is_nested_arr) goto obj_destruct_nested;
goto obj_destruct_simple;
obj_destruct_rest:;
jsval_t rest_obj = mkobj(js, 0);
if (is_err(rest_obj)) return rest_obj;
uint8_t obj_type = vtype(obj);
if (obj_type == T_OBJ || obj_type == T_ARR) {
jsoff_t scan = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | FLAGMASK);
while (scan < js->brk && scan != 0) {
jsoff_t header = loadoff(js, scan);
if (is_slot_prop(header)) { scan = next_prop(header); continue; }
const char *key; jsoff_t klen;
get_prop_key(js, scan, &key, &klen);
bool is_picked = false;
jsoff_t picked_len = js_arr_len(js, picked_keys);
for (jsoff_t i = 0; i < picked_len; i++) {
jsval_t pk = js_arr_get(js, picked_keys, i);
if (vtype(pk) != T_STR) continue;
jsoff_t pklen, pkoff = vstr(js, pk, &pklen);
if (klen == pklen && memcmp(key, &js->mem[pkoff], klen) == 0) { is_picked = true; break; }
}
if (!is_picked && !(klen == STR_PROTO_LEN && memcmp(key, STR_PROTO, STR_PROTO_LEN) == 0)) {
jsval_t val = get_prop_val(js, scan);
jsval_t key_str = js_mkstr(js, key, klen);
if (is_err(key_str)) return key_str;
jsval_t res = js_setprop(js, rest_obj, key_str, val);
if (is_err(res)) return res;
}
scan = next_prop(header);
}
}
{
const char *vn = &js->code[var_off];
if (lkp_scope(js, js->scope, vn, var_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int)var_len, vn);
jsval_t x = mkprop(js, js->scope, js_mkstr(js, vn, var_len), rest_obj, is_const ? CONSTMASK : 0);
if (is_err(x)) return x;
}
goto obj_destruct_next;
obj_destruct_nested:;
{
jsval_t sk = js_mkstr(js, &js->code[src_off], src_len);
if (is_err(sk)) return sk;
js_arr_push(js, picked_keys, sk);
jsval_t nobj = getprop_any(js, obj, &js->code[src_off], src_len);
jsoff_t pattern_end = js->pos;
js->pos = nested_pattern_start;
js->consumed = 1;
jsval_t saved_obj = obj;
obj = nobj;
if (!is_nested_obj) goto nested_done;
if (next(js) != TOK_LBRACE) return js_mkerr_typed(js, JS_ERR_SYNTAX, "expected '{' in nested destructuring");
js->consumed = 1;
while (next(js) != TOK_RBRACE && next(js) != TOK_EOF) {
if (next(js) != TOK_IDENTIFIER) return js_mkerr_typed(js, JS_ERR_SYNTAX, "identifier expected in nested destructuring");
jsoff_t isoff = js->toff, islen = js->tlen;
jsoff_t ivoff = isoff, ivlen = islen;
js->consumed = 1;
if (next(js) == TOK_COLON) {
js->consumed = 1;
EXPECT_IDENT();
ivoff = js->toff; ivlen = js->tlen;
js->consumed = 1;
}
const char *ivn = &js->code[ivoff];
if (lkp_scope(js, js->scope, ivn, ivlen) > 0) return js_mkerr(js, "'%.*s' already declared", (int)ivlen, ivn);
jsval_t ival = getprop_any(js, nobj, &js->code[isoff], islen);
jsval_t ix = mkprop(js, js->scope, js_mkstr(js, ivn, ivlen), ival, is_const ? CONSTMASK : 0);
if (is_err(ix)) return ix;
if (next(js) == TOK_RBRACE) break;
EXPECT(TOK_COMMA);
}
js->consumed = 1;
nested_done:
obj = saved_obj;
js->pos = pattern_end;
js->consumed = 1;
}
goto obj_destruct_next;
obj_destruct_simple:;
{
const char *vn = &js->code[var_off];
if (lkp_scope(js, js->scope, vn, var_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int)var_len, vn);
jsval_t sk = js_mkstr(js, &js->code[src_off], src_len);
if (is_err(sk)) return sk;
js_arr_push(js, picked_keys, sk);
jsval_t pval = getprop_any(js, obj, &js->code[src_off], src_len);
if (vtype(pval) == T_UNDEF && default_len > 0) {
pval = js_eval_slice(js, default_off, default_len);
if (is_err(pval)) return pval;
pval = resolveprop(js, pval);
}
jsval_t x = mkprop(js, js->scope, js_mkstr(js, vn, var_len), pval, is_const ? CONSTMASK : 0);
if (is_err(x)) return x;
}
obj_destruct_next:
if (next(js) == TOK_RBRACE) break;
EXPECT(TOK_COMMA);
}
JS_RESTORE_STATE(js, end_state);
} else if (next(js) == TOK_LBRACKET) {
js->consumed = 1;
js_parse_state_t pattern_state;
JS_SAVE_STATE(js, pattern_state);
int depth = 1;
while (depth > 0 && next(js) != TOK_EOF) {
if (js->tok == TOK_LBRACKET) depth++;
else if (js->tok == TOK_RBRACKET) depth--;
if (depth > 0) js->consumed = 1;
}
js->consumed = 1;
if (next(js) != TOK_ASSIGN) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "array destructuring requires assignment");
}
js->consumed = 1;
jsval_t v = js_expr(js);
if (is_err(v)) return v;
jsval_t arr = js_mkundef();
if (exe) {
arr = resolveprop(js, v);
if (vtype(arr) != T_ARR && vtype(arr) != T_STR) {
return js_mkerr(js, "cannot array destructure non-iterable");
}
}
js_parse_state_t end_state;
JS_SAVE_STATE(js, end_state);
JS_RESTORE_STATE(js, pattern_state);
int index = 0;
while (next(js) != TOK_RBRACKET && next(js) != TOK_EOF) {
if (next(js) == TOK_COMMA) {
js->consumed = 1;
index++;
continue;
}
bool is_rest = false;
if (next(js) == TOK_REST) {
is_rest = true;
js->consumed = 1;
}
if (next(js) != TOK_IDENTIFIER) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "identifier expected in array destructuring");
}
jsoff_t var_off = js->toff, var_len = js->tlen;
js->consumed = 1;
jsoff_t default_off = 0, default_len = 0;
if (!is_rest && next(js) == TOK_ASSIGN) {
js->consumed = 1;
default_off = js->pos;
uint8_t sf = js->flags;
js->flags |= F_NOEXEC;
jsval_t r = js_expr(js);
js->flags = sf;
if (is_err(r)) return r;
default_len = js->pos - default_off;
}
if (exe) {
const char *var_name = &js->code[var_off];
if (lkp_scope(js, js->scope, var_name, var_len) > 0) {
return js_mkerr(js, "'%.*s' already declared", (int)var_len, var_name);
}
jsval_t prop_val;
if (is_rest) {
jsval_t rest_arr = js_mkarr(js);
if (is_err(rest_arr)) return rest_arr;
jsoff_t total_len = vtype(arr) == T_STR ? 0 : js_arr_len(js, arr);
if (vtype(arr) == T_STR) {
jsoff_t slen;
vstr(js, arr, &slen);
total_len = slen;
}
for (jsoff_t i = index; i < total_len; i++) {
jsval_t elem;
if (vtype(arr) == T_STR) {
jsoff_t slen, soff = vstr(js, arr, &slen);
elem = js_mkstr(js, (char *)&js->mem[soff + i], 1);
} else {
elem = js_arr_get(js, arr, i);
}
js_arr_push(js, rest_arr, elem);
}
prop_val = rest_arr;
} else {
if (vtype(arr) == T_STR) {
jsoff_t slen, soff = vstr(js, arr, &slen);
if ((jsoff_t)index < slen) {
prop_val = js_mkstr(js, (char *)&js->mem[soff + index], 1);
} else {
prop_val = js_mkundef();
}
} else {
prop_val = js_arr_get(js, arr, index);
}
if (vtype(prop_val) == T_UNDEF && default_len > 0) {
prop_val = js_eval_slice(js, default_off, default_len);
if (is_err(prop_val)) return prop_val;
prop_val = resolveprop(js, prop_val);
}
}
jsval_t x = mkprop(js, js->scope, js_mkstr(js, var_name, var_len), prop_val, is_const ? CONSTMASK : 0);
if (is_err(x)) return x;
}
index++;
if (next(js) == TOK_RBRACKET) break;
EXPECT(TOK_COMMA);
}
JS_RESTORE_STATE(js, end_state);
} else {
EXPECT_IDENT();
js->consumed = 0;
jsoff_t noff = js->toff, nlen = js->tlen;
char *name = (char *) &js->code[noff];
if ((js->flags & F_STRICT) && is_strict_restricted(name, nlen)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "cannot use '%.*s' as variable name in strict mode", (int) nlen, name);
}
if ((js->flags & F_STRICT) && is_strict_reserved(name, nlen)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "'%.*s' is reserved in strict mode", (int) nlen, name);
}
jsval_t v = js_mkundef();
js->consumed = 1;
if (next(js) == TOK_ASSIGN) {
js->consumed = 1;
v = js_expr(js);
if (is_err(v)) return v;
} else if (is_const) return js_mkerr_typed(js, JS_ERR_SYNTAX, "Missing initializer in const declaration");
if (exe) {
char decoded_name[256];
size_t decoded_len = decode_ident_escapes(name, nlen, decoded_name, sizeof(decoded_name));
jsval_t resolved = resolveprop(js, v);
if (vtype(resolved) == T_FUNC) infer_func_name(js, resolved, decoded_name, decoded_len);
if (lkp_scope(js, js->scope, decoded_name, decoded_len) > 0) return js_mkerr(js, "'%.*s' already declared", (int) decoded_len, decoded_name);
jsval_t x = mkprop(js, js->scope, js_mkstr(js, decoded_name, decoded_len), resolved, is_const ? CONSTMASK : 0);
if (is_err(x)) return x;
}
}
uint8_t decl_next = next(js);
bool asi = js->had_newline || decl_next == TOK_EOF || decl_next == TOK_RBRACE;
if (decl_next == TOK_SEMICOLON || asi) break;
EXPECT(TOK_COMMA);
}
return js_mkundef();
}
static jsval_t js_expr(struct js *js) {
return js_assignment(js);
}
static jsval_t js_expr_comma(struct js *js) {
jsval_t res = js_assignment(js);
if (is_err(res)) return res;
while (next(js) == TOK_COMMA) {
js->consumed = 1;
res = js_assignment(js);
if (is_err(res)) return res;
}
return res;
}
static jsval_t js_eval_slice(struct js *js, jsoff_t off, jsoff_t len) {
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
js->code = saved.code + off;
js->clen = len;
js->pos = 0;
js->consumed = 1;
jsval_t result = js_expr(js);
JS_RESTORE_STATE(js, saved);
return result;
}
static jsval_t js_eval_str(struct js *js, const char *code, jsoff_t len) {
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
js->code = code;
js->clen = len;
js->pos = 0;
js->consumed = 1;
jsval_t result = js_expr(js);
JS_RESTORE_STATE(js, saved);
return result;
}
static jsval_t js_let(struct js *js) {
return js_decl(js, false);
}
static jsval_t js_const(struct js *js) {
return js_decl(js, true);
}
static jsval_t js_func_decl(struct js *js) {
uint8_t exe = !(js->flags & F_NOEXEC);
uint8_t saved_flags = js->flags;
js->consumed = 1;
EXPECT_IDENT();
js->consumed = 0;
jsoff_t noff = js->toff, nlen = js->tlen;
char *name = (char *) &js->code[noff];
js->consumed = 1;
EXPECT(TOK_LPAREN);
jsoff_t pos = js->pos - 1;
int param_count = 0;
if (!parse_func_params(js, &saved_flags, &param_count)) {
js->flags = saved_flags;
return js_mkerr_typed(js, JS_ERR_SYNTAX, "invalid parameters");
}
EXPECT(TOK_RPAREN);
EXPECT(TOK_LBRACE);
js->consumed = 0;
uint8_t flags = js->flags;
js->flags |= F_NOEXEC;
jsval_t res = js_block(js, false);
if (is_err(res)) {
js->flags = flags;
return res;
}
js->flags = flags;
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) return func_obj;
set_func_code(js, func_obj, &js->code[pos], js->pos - pos);
jsval_t func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) set_proto(js, func_obj, func_proto);
jsval_t res_len = js_setprop(js, func_obj, js->length_str, tov(param_count));
if (is_err(res_len)) return res_len;
js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C);
jsval_t name_key = js_mkstr(js, "name", 4);
if (is_err(name_key)) return name_key;
jsval_t name_val = js_mkstr(js, name, nlen);
if (is_err(name_val)) return name_val;
set_slot(js, func_obj, SLOT_NAME, name_val);
jsval_t res3 = js_setprop(js, func_obj, name_key, name_val);
if (is_err(res3)) return res3;
if (exe) {
jsval_t closure_scope = for_let_capture_scope(js);
if (is_err(closure_scope)) return closure_scope;
set_slot(js, func_obj, SLOT_SCOPE, closure_scope);
if (flags & F_STRICT) {
set_slot(js, func_obj, SLOT_STRICT, js_true);
}
}
jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj));
jsval_t proto_setup = setup_func_prototype(js, func);
if (is_err(proto_setup)) return proto_setup;
if (exe) {
jsoff_t existing = lkp_scope(js, js->scope, name, nlen);
if (existing > 0) {
saveval(js, existing + sizeof(jsoff_t) * 2, func);
} else {
jsval_t x = mkprop(js, js->scope, js_mkstr(js, name, nlen), func, 0);
if (is_err(x)) return x;
}
}
return js_mkundef();
}
static jsval_t js_func_decl_async(struct js *js) {
uint8_t exe = !(js->flags & F_NOEXEC);
js->consumed = 1;
EXPECT_IDENT();
js->consumed = 0;
jsoff_t noff = js->toff, nlen = js->tlen;
char *name = (char *) &js->code[noff];
js->consumed = 1;
EXPECT(TOK_LPAREN);
jsoff_t pos = js->pos - 1;
if (!parse_func_params(js, NULL, NULL)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "invalid parameters");
}
EXPECT(TOK_RPAREN);
EXPECT(TOK_LBRACE);
js->consumed = 0;
uint8_t flags = js->flags;
js->flags |= F_NOEXEC;
jsval_t res = js_block(js, false);
if (is_err(res)) {
js->flags = flags;
return res;
}
js->flags = flags;
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) return func_obj;
set_func_code(js, func_obj, &js->code[pos], js->pos - pos);
set_slot(js, func_obj, SLOT_ASYNC, js_true);
jsval_t async_proto = get_slot(js, js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) set_proto(js, func_obj, async_proto);
jsval_t res_len = js_setprop(js, func_obj, js->length_str, tov(0));
if (is_err(res_len)) return res_len;
js_set_descriptor(js, func_obj, "length", 6, JS_DESC_C);
jsval_t name_key = js_mkstr(js, "name", 4);
if (is_err(name_key)) return name_key;
jsval_t name_val = js_mkstr(js, name, nlen);
if (is_err(name_val)) return name_val;
set_slot(js, func_obj, SLOT_NAME, name_val);
jsval_t res3 = js_setprop(js, func_obj, name_key, name_val);
if (is_err(res3)) return res3;
if (exe) {
jsval_t closure_scope = for_let_capture_scope(js);
if (is_err(closure_scope)) return closure_scope;
set_slot(js, func_obj, SLOT_SCOPE, closure_scope);
if (flags & F_STRICT) set_slot(js, func_obj, SLOT_STRICT, js_true);
}
jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj));
jsval_t proto_setup = setup_func_prototype(js, func);
if (is_err(proto_setup)) return proto_setup;
if (exe) {
jsoff_t existing = lkp_scope(js, js->scope, name, nlen);
if (existing > 0) {
saveval(js, existing + sizeof(jsoff_t) * 2, func);
} else {
jsval_t x = mkprop(js, js->scope, js_mkstr(js, name, nlen), func, 0);
if (is_err(x)) return x;
}
}
return js_mkundef();
}
static jsval_t js_block_or_stmt(struct js *js) {
if (next(js) == TOK_LBRACE) return js_block(js, !(js->flags & F_NOEXEC));
uint8_t stmt_tok = js->tok;
jsval_t res = resolveprop(js, js_stmt(js));
bool is_block_stmt = (
stmt_tok == TOK_FUNC || stmt_tok == TOK_CLASS ||
stmt_tok == TOK_IF || stmt_tok == TOK_WHILE ||
stmt_tok == TOK_DO || stmt_tok == TOK_FOR ||
stmt_tok == TOK_SWITCH || stmt_tok == TOK_TRY ||
stmt_tok == TOK_LBRACE || stmt_tok == TOK_ASYNC
);
if (!is_block_stmt) js->consumed = 0;
return res;
}
typedef struct {
bool is_block;
bool needs_scope;
bool has_func_decl;
bool has_func_decl_checked;
jshdl_t loop_scope_handle;
} loop_block_ctx_t;
static void loop_block_init(struct js *js, loop_block_ctx_t *ctx) {
ctx->is_block = (lookahead(js) == TOK_LBRACE);
ctx->needs_scope = false;
ctx->has_func_decl = false;
ctx->has_func_decl_checked = false;
ctx->loop_scope_handle = -1;
if (ctx->is_block && !(js->flags & F_NOEXEC)) {
jsoff_t saved_pos = js->pos;
uint8_t saved_tok = js->tok;
uint8_t saved_consumed = js->consumed;
int saved_stream_pos = js->token_stream_pos;
js->consumed = 1;
next(js);
ctx->needs_scope = block_needs_scope(js);
js->pos = saved_pos;
js->tok = saved_tok;
js->consumed = saved_consumed;
js->token_stream_pos = saved_stream_pos;
if (ctx->needs_scope) ctx->loop_scope_handle = js_root(js, js_mkscope(js));
}
}
static inline jsval_t loop_block_exec(struct js *js, loop_block_ctx_t *ctx) {
if (ctx->is_block) {
next(js);
if (!ctx->has_func_decl_checked) {
ctx->has_func_decl = code_has_function_decl(
js->code + js->pos,
js->clen - js->pos
);
ctx->has_func_decl_checked = true;
}
bool saved_skip = js->skip_func_hoist;
if (!ctx->has_func_decl) js->skip_func_hoist = true;
jsval_t result = js_block(js, false);
js->skip_func_hoist = saved_skip;
return result;
}
return js_block_or_stmt(js);
}
static inline void loop_block_sync_scope(struct js *js, loop_block_ctx_t *ctx) {
struct for_let_ctx *flc = for_let_current(js);
if (flc && vtype(flc->body_scope) == T_OBJ) js_root_update(js, ctx->loop_scope_handle, flc->body_scope);
}
#define loop_block_clear(js, ctx) \
if ((ctx)->needs_scope) scope_clear_props(js, js_deref(js, (ctx)->loop_scope_handle))
#define loop_block_cleanup(js, ctx) \
do { if ((ctx)->needs_scope) { js_unroot(js, (ctx)->loop_scope_handle); delscope(js); } } while(0)
static jsval_t js_if(struct js *js) {
js->consumed = 1;
EXPECT(TOK_LPAREN);
jsval_t res = js_mkundef(), cond_expr = js_expr(js);
if (is_err(cond_expr)) return cond_expr;
jsval_t cond = resolveprop(js, cond_expr);
if (is_err(cond)) return cond;
EXPECT(TOK_RPAREN);
bool cond_true = js_truthy(js, cond), exe = !(js->flags & F_NOEXEC);
if (!cond_true) js->flags |= F_NOEXEC;
jsval_t blk = js_block_or_stmt(js);
if (cond_true) res = blk;
if (exe && !cond_true) js->flags &= (uint8_t) ~F_NOEXEC;
if (lookahead(js) == TOK_ELSE) {
js->consumed = 1;
next(js);
js->consumed = 1;
if (cond_true) js->flags |= F_NOEXEC;
blk = js_block_or_stmt(js);
if (!cond_true) res = blk;
if (cond_true && exe) js->flags &= (uint8_t) ~F_NOEXEC;
}
return res;
}
static inline bool expect(struct js *js, uint8_t tok, jsval_t *res) {
if (next(js) != tok) {
*res = js_mkerr_typed(js, JS_ERR_SYNTAX, "parse error");
return false;
} else { js->consumed = 1; return true; }
}
static inline bool is_err2(jsval_t *v, jsval_t *res) {
bool r = is_err(*v);
if (r) { *res = *v; } return r;
}
typedef struct {
jsoff_t body_start;
jsoff_t body_end;
jsoff_t var_name_off;
jsoff_t var_name_len;
bool is_const_var;
uint8_t flags;
bool has_destructure;
jsoff_t destructure_off;
jsoff_t destructure_len;
int marker_index;
loop_block_ctx_t loop_ctx;
} for_iter_ctx_t;
static jsval_t for_iter_bind_var(struct js *js, for_iter_ctx_t *ctx, jsval_t value) {
loop_block_clear(js, &ctx->loop_ctx);
if (ctx->has_destructure) {
return bind_destruct_pattern(js, &js->code[ctx->destructure_off], ctx->destructure_len, value, js->scope);
}
const char *var_name = &js->code[ctx->var_name_off];
jsoff_t existing = lkp_scope(js, js->scope, var_name, ctx->var_name_len);
if (existing > 0) {
saveval(js, existing + sizeof(jsoff_t) * 2, value);
return js_mkundef();
}
return mkprop(js, js->scope, js_mkstr(js, var_name, ctx->var_name_len), value, ctx->is_const_var ? CONSTMASK : 0);
}
static jsval_t for_iter_exec_body(struct js *js, for_iter_ctx_t *ctx) {
js->pos = ctx->body_start;
js->consumed = 1;
js->flags = (ctx->flags & ~F_NOEXEC) | F_LOOP;
return loop_block_exec(js, &ctx->loop_ctx);
}
static inline bool for_iter_handle_continue(struct js *js, for_iter_ctx_t *ctx) {
if (!(label_flags & F_CONTINUE_LABEL)) return false;
if (is_this_loop_continue_target(ctx->marker_index)) {
clear_continue_label();
js->flags &= ~(F_BREAK | F_NOEXEC);
return false;
}
js->flags |= F_BREAK;
return true;
}
static int for_iter_step(struct js *js, for_iter_ctx_t *ctx, jsval_t key_str, jsval_t *out) {
jsval_t err = for_iter_bind_var(js, ctx, key_str);
if (is_err(err)) { *out = err; return 2; }
jsval_t v = for_iter_exec_body(js, ctx);
if (is_err(v)) { *out = v; return 2; }
if (for_iter_handle_continue(js, ctx)) return 1;
if (js->flags & F_BREAK) return 1;
if (js->flags & F_RETURN) { *out = v; return 2; }
return 0;
}
static jsval_t for_iter_string_indices(struct js *js, for_iter_ctx_t *ctx, jsval_t str) {
jsoff_t slen = vstrlen(js, str);
for (jsoff_t i = 0; i < slen; i++) {
char idx[16];
snprintf(idx, sizeof(idx), "%u", (unsigned)i);
jsval_t key_str = js_mkstr(js, idx, strlen(idx));
jsval_t out;
int rc = for_iter_step(js, ctx, key_str, &out);
if (rc) return (rc == 2) ? out : js_mkundef();
}
return js_mkundef();
}
static jsval_t for_in_iter_object(struct js *js, for_iter_ctx_t *ctx, jsval_t obj) {
uint8_t obj_type = vtype(obj);
if (obj_type == T_NULL || obj_type == T_UNDEF) return js_mkundef();
if (obj_type == T_STR) return for_iter_string_indices(js, ctx, obj);
if (obj_type != T_OBJ && obj_type != T_ARR && obj_type != T_FUNC)
return js_mkerr(js, "for-in requires object");
jsval_t iter_obj = (obj_type == T_FUNC) ? mkval(T_OBJ, vdata(obj)) : obj;
jsval_t prim = get_slot(js, iter_obj, SLOT_PRIMITIVE);
if (vtype(prim) == T_STR) return for_iter_string_indices(js, ctx, prim);
jshdl_t h_obj = js_root(js, iter_obj);
const char *tag_sym_key = get_toStringTag_sym_key();
size_t tag_sym_len = tag_sym_key ? strlen(tag_sym_key) : 0;
char key_buf[256];
if (obj_type == T_ARR) {
jsval_t cur_obj = js_deref(js, h_obj);
jsoff_t doff = get_dense_buf(js, cur_obj);
if (doff) {
jsoff_t dense_len = dense_length(js, doff);
for (jsoff_t i = 0; i < dense_len; i++) {
jsval_t v = dense_get(js, doff, i);
if (is_empty_slot(v)) continue;
size_t klen = uint_to_str(key_buf, sizeof(key_buf), (unsigned)i);
jsval_t out;
int rc = for_iter_step(js, ctx, js_mkstr(js, key_buf, (jsoff_t)klen), &out);
if (rc) { js_unroot(js, h_obj); return (rc == 2) ? out : js_mkundef(); }
}
}
}
jsoff_t prop_idx = 0;
for (;;) {
jsval_t cur_obj = js_deref(js, h_obj);
jsoff_t cur_obj_off = (jsoff_t)vdata(cur_obj);
jsoff_t prop_off = loadoff(js, cur_obj_off) & ~(3U | FLAGMASK);
jsoff_t cur_idx = 0;
while (prop_off < js->brk && prop_off != 0 && cur_idx < prop_idx) {
jsoff_t header = loadoff(js, prop_off);
prop_off = next_prop(header); cur_idx++;
}
if (prop_off >= js->brk || prop_off == 0) break;
jsoff_t header = loadoff(js, prop_off);
if (is_slot_prop(header)) { prop_idx++; continue; }
jsoff_t koff = loadoff(js, prop_off + (jsoff_t)sizeof(prop_off));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *)&js->mem[koff + sizeof(koff)];
bool skip = streq(key, klen, STR_PROTO, STR_PROTO_LEN);
if (!skip && tag_sym_key) skip = streq(key, klen, tag_sym_key, tag_sym_len);
if (!skip && obj_type == T_ARR && is_array_index(key, klen)) {
jsval_t cur = js_deref(js, h_obj);
jsoff_t d = get_dense_buf(js, cur);
if (d) {
unsigned long pidx = 0;
for (jsoff_t ci = 0; ci < klen; ci++) pidx = pidx * 10 + (key[ci] - '0');
if (pidx < dense_length(js, d)) skip = true;
}
}
if (!skip) {
descriptor_entry_t *desc = lookup_descriptor(js, cur_obj_off, key, klen);
if (desc && !desc->enumerable) skip = true;
}
if (!skip) {
size_t copy_len = klen < sizeof(key_buf) - 1 ? klen : sizeof(key_buf) - 1;
memcpy(key_buf, key, copy_len);
key_buf[copy_len] = '\0';
jsval_t out;
int rc = for_iter_step(js, ctx, js_mkstr(js, key_buf, (jsoff_t)copy_len), &out);
if (rc) { js_unroot(js, h_obj); return (rc == 2) ? out : js_mkundef(); }
}
prop_idx++;
}
js_unroot(js, h_obj);
return js_mkundef();
}
static jsval_t for_of_iter_array(struct js *js, for_iter_ctx_t *ctx, jsval_t iterable) {
jshdl_t h_iterable = js_root(js, iterable);
jsoff_t length = 0; {
jsval_t arr = js_deref(js, h_iterable);
length = get_array_length(js, arr);
}
for (jsoff_t i = 0; i < length; i++) {
jsval_t arr = js_deref(js, h_iterable);
jsval_t val = arr_get(js, arr, i);
jsval_t err = for_iter_bind_var(js, ctx, val);
if (is_err(err)) { js_unroot(js, h_iterable); return err; }
jsval_t v = for_iter_exec_body(js, ctx);
if (is_err(v)) { js_unroot(js, h_iterable); return v; }
if (for_iter_handle_continue(js, ctx)) break;
if (js->flags & F_BREAK) break;
if (js->flags & F_RETURN) { js_unroot(js, h_iterable); return v; }
}
js_unroot(js, h_iterable);
return js_mkundef();
}
static void init_ascii_cache(struct js *js) {
if (js->ascii_cache_init) return;
for (int i = 0; i < 128; i++) {
char c = (char)i;
js->ascii_char_cache[i] = js_mkstr(js, &c, 1);
}
js->ascii_cache_init = true;
}
static jsval_t for_of_iter_string(struct js *js, for_iter_ctx_t *ctx, jsval_t iterable) {
jshdl_t h_iterable = js_root(js, iterable);
size_t byte_pos = 0;
if (!js->ascii_cache_init) init_ascii_cache(js);
for (;;) {
jsval_t cur = js_deref(js, h_iterable);
jsoff_t cur_byte_len;
jsoff_t cur_soff = vstr(js, cur, &cur_byte_len);
if (byte_pos >= cur_byte_len) break;
const char *cur_str = (char *) &js->mem[cur_soff];
unsigned char c = (unsigned char)cur_str[byte_pos];
size_t char_bytes;
jsval_t char_str;
if (c < 0x80) { char_bytes = 1; char_str = js->ascii_char_cache[c]; } else {
if ((c & 0xE0) == 0xC0) char_bytes = 2;
else if ((c & 0xF0) == 0xE0) char_bytes = 3;
else if ((c & 0xF8) == 0xF0) char_bytes = 4;
else char_bytes = 1;
if (byte_pos + char_bytes > cur_byte_len) char_bytes = cur_byte_len - byte_pos;
char_str = js_mkstr(js, cur_str + byte_pos, char_bytes);
} byte_pos += char_bytes;
jsval_t err = for_iter_bind_var(js, ctx, char_str);
if (is_err(err)) { js_unroot(js, h_iterable); return err; }
jsval_t v = for_iter_exec_body(js, ctx);
if (is_err(v)) { js_unroot(js, h_iterable); return v; }
if (for_iter_handle_continue(js, ctx)) break;
if (js->flags & F_BREAK) break;
if (js->flags & F_RETURN) { js_unroot(js, h_iterable); return v; }
}
js_unroot(js, h_iterable);
return js_mkundef();
}
typedef enum { ITER_CONTINUE, ITER_BREAK, ITER_ERROR } iter_action_t;
typedef iter_action_t (*iter_callback_t)(struct js *js, jsval_t value, void *ctx, jsval_t *out);
static jsval_t iter_foreach(struct js *js, jsval_t iterable, iter_callback_t cb, void *ctx) {
const char *iter_key = get_iterator_sym_key();
jsoff_t iter_prop = iter_key ? lkp_proto(js, iterable, iter_key, strlen(iter_key)) : 0;
if (iter_prop == 0) return js_mkerr(js, "not iterable");
js_parse_state_t saved_state;
JS_SAVE_STATE(js, saved_state);
uint8_t saved_flags = js->flags;
jsval_t iter_method = loadval(js, iter_prop + sizeof(jsoff_t) * 2);
jsval_t outer_saved_this = js->this_val;
push_this(iterable);
js->this_val = iterable;
jsval_t iterator = call_js_with_args(js, iter_method, NULL, 0);
pop_this();
js->this_val = outer_saved_this;
JS_RESTORE_STATE(js, saved_state);
js->flags = saved_flags;
if (is_err(iterator)) return iterator;
jshdl_t h_iterator = js_root(js, iterator);
jsval_t out = js_mkundef();
while (true) {
jsval_t cur_iter = js_deref(js, h_iterator);
jsoff_t next_off = lkp_proto(js, cur_iter, "next", 4);
if (next_off == 0) { js_unroot(js, h_iterator); return js_mkerr(js, "iterator.next is not a function"); }
jsval_t next_method = loadval(js, next_off + sizeof(jsoff_t) * 2);
if (vtype(next_method) != T_FUNC && vtype(next_method) != T_CFUNC) {
js_unroot(js, h_iterator);
return js_mkerr(js, "iterator.next is not a function");
}
cur_iter = js_deref(js, h_iterator);
jsval_t saved_this = js->this_val;
push_this(cur_iter);
js->this_val = cur_iter;
jsval_t result = call_js_with_args(js, next_method, NULL, 0);
pop_this();
js->this_val = saved_this;
JS_RESTORE_STATE(js, saved_state);
js->flags = saved_flags;
if (is_err(result)) { js_unroot(js, h_iterator); return result; }
jsoff_t done_off = lkp(js, result, "done", 4);
jsval_t done_val = done_off ? loadval(js, done_off + sizeof(jsoff_t) * 2) : js_mkundef();
if (js_truthy(js, done_val)) break;
jsoff_t value_off = lkp(js, result, "value", 5);
jsval_t value = value_off ? loadval(js, value_off + sizeof(jsoff_t) * 2) : js_mkundef();
iter_action_t action = cb(js, value, ctx, &out);
if (action == ITER_BREAK) break;
if (action == ITER_ERROR) { js_unroot(js, h_iterator); return out; }
}
js_unroot(js, h_iterator);
return out;
}
static iter_action_t for_of_iter_cb(struct js *js, jsval_t value, void *ctx, jsval_t *out) {
for_iter_ctx_t *fctx = (for_iter_ctx_t *)ctx;
jsval_t err = for_iter_bind_var(js, fctx, value);
if (is_err(err)) { *out = err; return ITER_ERROR; }
jsval_t v = for_iter_exec_body(js, fctx);
if (is_err(v)) { *out = v; return ITER_ERROR; }
if (for_iter_handle_continue(js, fctx)) return ITER_BREAK;
if (js->flags & F_BREAK) return ITER_BREAK;
if (js->flags & F_RETURN) { *out = v; return ITER_BREAK; }
return ITER_CONTINUE;
}
static jsval_t for_of_iter_object(struct js *js, for_iter_ctx_t *ctx, jsval_t iterable) {
jsval_t result = iter_foreach(js, iterable, for_of_iter_cb, ctx);
if (is_err(result) && strcmp(js->errmsg, "not iterable") == 0) {
return js_mkerr(js, "for-of requires iterable");
}
return result;
}
static jsval_t js_for(struct js *js) {
uint8_t flags = js->flags, exe = !(flags & F_NOEXEC);
jsval_t v, res = js_mkundef();
jsoff_t pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
bool use_label_stack = label_stack && utarray_len(label_stack) > 0;
int marker_index = 0;
if (use_label_stack) {
label_entry_t marker = { .name = NULL, .name_len = 0, .is_loop = true, .is_block = false };
utarray_push_back(label_stack, &marker);
marker_index = utarray_len(label_stack) - 1;
}
if (exe) mkscope(js);
if (!expect(js, TOK_FOR, &res)) goto done;
if (!expect(js, TOK_LPAREN, &res)) goto done;
bool is_for_in = false;
bool is_for_of = false;
bool is_var_decl = false;
bool is_const_var = false;
bool is_let_loop = false;
jsoff_t var_name_off = 0;
jsoff_t var_name_len = 0;
jsoff_t let_var_off = 0;
jsoff_t let_var_len = 0;
bool has_destructure = false;
jsoff_t destructure_off = 0;
jsoff_t destructure_len = 0;
if (next(js) == TOK_LET || next(js) == TOK_CONST || next(js) == TOK_VAR) {
if (js->tok == TOK_VAR) {
is_var_decl = true;
if ((js->flags & F_STRICT) && !js->var_warning_shown) {
fprintf(stderr, "Warning: 'var' is deprecated, use 'let' or 'const' instead\n");
js->var_warning_shown = true;
}
} else if (js->tok == TOK_LET) {
is_let_loop = true;
}
is_const_var = (js->tok == TOK_CONST);
js->consumed = 1;
if (next(js) == TOK_LBRACKET || next(js) == TOK_LBRACE) {
has_destructure = true;
destructure_off = js->toff;
uint8_t open_tok = js->tok;
uint8_t close_tok = (open_tok == TOK_LBRACKET) ? TOK_RBRACKET : TOK_RBRACE;
int depth = 1;
js->consumed = 1;
while (depth > 0 && next(js) != TOK_EOF) {
if (js->tok == open_tok) depth++;
else if (js->tok == close_tok) depth--;
js->consumed = 1;
}
destructure_len = js->pos - destructure_off;
if (next(js) == TOK_IN) {
is_for_in = true;
js->consumed = 1;
} else if (next(js) == TOK_OF) {
is_for_of = true;
js->consumed = 1;
} else if (next(js) == TOK_ASSIGN) {
js->pos = destructure_off;
js->consumed = 1;
if (is_const_var) v = js_const(js);
else if (is_var_decl) v = js_var_decl(js);
else v = js_let(js);
if (is_err2(&v, &res)) goto done;
has_destructure = false;
} else {
res = js_mkerr_typed(js, JS_ERR_SYNTAX, "expected 'in', 'of', or '=' after destructuring pattern");
goto done;
}
} else if (next(js) == TOK_IDENTIFIER) {
var_name_off = js->toff;
var_name_len = js->tlen;
if (is_let_loop) {
let_var_off = var_name_off;
let_var_len = var_name_len;
}
js->consumed = 1;
if (next(js) == TOK_IN) {
is_for_in = true;
js->consumed = 1;
} else if (next(js) == TOK_OF) {
is_for_of = true;
js->consumed = 1;
} else {
js->pos = var_name_off;
js->consumed = 1;
if (is_const_var) v = js_const(js);
else if (is_var_decl) v = js_var_decl(js);
else v = js_let(js);
if (is_err2(&v, &res)) goto done;
}
}
} else if (next(js) == TOK_IDENTIFIER) {
var_name_off = js->toff;
var_name_len = js->tlen;
js->consumed = 1;
if (next(js) == TOK_IN) {
is_for_in = true;
js->consumed = 1;
} else if (next(js) == TOK_OF) {
is_for_of = true;
js->consumed = 1;
} else {
js->pos = var_name_off;
js->consumed = 1;
v = js_expr_comma(js);
if (is_err2(&v, &res)) goto done;
}
} else if (next(js) == TOK_SEMICOLON) {
} else {
v = js_expr_comma(js);
if (is_err2(&v, &res)) goto done;
}
if (is_for_in) {
jsval_t obj_expr = js_expr(js);
if (is_err2(&obj_expr, &res)) goto done;
if (!expect(js, TOK_RPAREN, &res)) goto done;
jsoff_t body_start = js->pos;
loop_block_ctx_t forin_loop_ctx = {0};
if (exe) loop_block_init(js, &forin_loop_ctx);
js->flags |= F_NOEXEC;
v = js_block_or_stmt(js);
if (is_err2(&v, &res)) goto done;
jsoff_t body_end = js->pos;
if (exe) {
jsval_t obj = resolveprop(js, obj_expr);
for_iter_ctx_t ctx = {
body_start, body_end,
var_name_off, var_name_len,
is_const_var, flags,
has_destructure, destructure_off,
destructure_len, marker_index,
forin_loop_ctx
};
res = for_in_iter_object(js, &ctx, obj);
loop_block_cleanup(js, &forin_loop_ctx);
if (is_err(res)) goto done;
if (js->flags & F_RETURN) goto done;
}
js->pos = body_end;
js->tok = TOK_SEMICOLON;
js->consumed = 0;
goto done;
}
if (is_for_of) {
jsval_t iter_expr = js_expr(js);
if (is_err2(&iter_expr, &res)) goto done;
if (!expect(js, TOK_RPAREN, &res)) goto done;
jsoff_t body_start = js->pos;
loop_block_ctx_t forof_loop_ctx = {0};
if (exe) loop_block_init(js, &forof_loop_ctx);
js->flags |= F_NOEXEC;
v = js_block_or_stmt(js);
if (is_err2(&v, &res)) goto done;
jsoff_t body_end = js->pos;
if (exe) {
jsval_t iterable = resolveprop(js, iter_expr);
uint8_t itype = vtype(iterable);
for_iter_ctx_t ctx = {
body_start, body_end,
var_name_off, var_name_len,
is_const_var, flags,
has_destructure, destructure_off,
destructure_len, marker_index,
forof_loop_ctx
};
if (itype == T_ARR) res = for_of_iter_array(js, &ctx, iterable);
else if (itype == T_STR) res = for_of_iter_string(js, &ctx, iterable);
else if (itype == T_OBJ) res = for_of_iter_object(js, &ctx, iterable);
else res = js_mkerr(js, "for-of requires iterable");
loop_block_cleanup(js, &forof_loop_ctx);
if (is_err(res)) goto done;
if (js->flags & F_RETURN) goto done;
}
js->pos = body_end;
js->tok = TOK_SEMICOLON;
js->consumed = 0;
goto done;
}
if (!expect(js, TOK_SEMICOLON, &res)) goto done;
js->flags |= F_NOEXEC;
pos1 = js->pos;
if (next(js) != TOK_SEMICOLON) {
v = js_expr(js);
if (is_err2(&v, &res)) goto done;
}
if (!expect(js, TOK_SEMICOLON, &res)) goto done;
pos2 = js->pos;
if (next(js) != TOK_RPAREN) {
v = js_expr_comma(js);
if (is_err2(&v, &res)) goto done;
}
if (!expect(js, TOK_RPAREN, &res)) goto done;
pos3 = js->pos;
jsoff_t iter_var_prop_off = 0;
if (is_let_loop && let_var_len > 0 && exe) {
js->flags = flags;
mkscope(js);
jsval_t let_var_key = js_mkstr(js, &js->code[let_var_off], let_var_len);
jsoff_t outer_off = lkp_scope(js, upper(js, js->scope), &js->code[let_var_off], let_var_len);
jsval_t init_val = outer_off ? resolveprop(js, mkval(T_PROP, outer_off)) : js_mkundef();
mkprop(js, js->scope, let_var_key, init_val, 0);
iter_var_prop_off = lkp(js, js->scope, &js->code[let_var_off], let_var_len);
const char *var_interned = intern_string(&js->code[let_var_off], let_var_len);
for_let_push(js, var_interned, let_var_len, iter_var_prop_off, js_mkundef());
}
loop_block_ctx_t loop_ctx = {0};
if (exe) {
loop_block_init(js, &loop_ctx);
if (is_let_loop && let_var_len > 0 && loop_ctx.needs_scope) for_let_set_body_scope(js, js_deref(js, loop_ctx.loop_scope_handle));
}
js->flags |= F_NOEXEC;
v = js_block_or_stmt(js);
if (exe) js->flags = flags;
if (is_err2(&v, &res)) goto done;
pos4 = js->pos;
while (!(flags & F_NOEXEC)) {
js_gc_safepoint(js);
js->flags = flags, js->pos = pos1, js->consumed = 1;
if (next(js) != TOK_SEMICOLON) {
v = resolveprop(js, js_expr(js));
if (is_err2(&v, &res)) goto done;
if (!js_truthy(js, v)) break;
}
js->flags |= F_LOOP;
js->pos = pos3;
js->consumed = 1;
if (is_let_loop && let_var_len > 0 && loop_ctx.needs_scope) {
loop_block_sync_scope(js, &loop_ctx);
}
loop_block_clear(js, &loop_ctx);
v = loop_block_exec(js, &loop_ctx);
if (is_err2(&v, &res)) {
loop_block_cleanup(js, &loop_ctx);
if (is_let_loop && let_var_len > 0) {
for_let_pop(js); delscope(js);
}
goto done;
}
if (label_flags & F_CONTINUE_LABEL) {
if (is_this_loop_continue_target(marker_index)) {
clear_continue_label();
js->flags &= ~(F_BREAK | F_NOEXEC);
js->flags = flags;
js->pos = pos2, js->consumed = 1;
if (next(js) != TOK_RPAREN) {
v = js_expr_comma(js);
if (is_err2(&v, &res)) goto done;
} continue;
}
}
if (js->flags & F_BREAK) break;
if (js->flags & F_RETURN) { res = v; break; }
js->flags = flags, js->pos = pos2, js->consumed = 1;
if (next(js) != TOK_RPAREN) {
v = js_expr_comma(js);
if (is_err2(&v, &res)) goto done;
}
}
if (exe) loop_block_cleanup(js, &loop_ctx);
if (is_let_loop && let_var_len > 0 && exe) {
for_let_pop(js); delscope(js);
}
js->pos = pos4, js->tok = TOK_SEMICOLON, js->consumed = 0;
done:
if (use_label_stack && label_stack && utarray_len(label_stack) > 0) {
utarray_pop_back(label_stack);
}
if (exe) delscope(js);
uint8_t preserve = 0;
if (js->flags & F_RETURN) {
preserve = js->flags & (F_RETURN | F_NOEXEC);
}
if ((js->flags & F_BREAK) && (label_flags & F_BREAK_LABEL)) {
preserve |= (js->flags & (F_BREAK | F_NOEXEC));
}
if (label_flags & F_CONTINUE_LABEL) {
preserve |= F_BREAK | F_NOEXEC;
}
js->flags = flags | preserve;
return res;
}
static jsval_t js_while(struct js *js) {
uint8_t flags = js->flags, exe = !(flags & F_NOEXEC);
jsval_t res = js_mkundef(), v;
loop_block_ctx_t loop_ctx = {0};
bool use_label_stack = label_stack && utarray_len(label_stack) > 0;
int marker_index = 0;
if (use_label_stack) {
label_entry_t marker = { .name = NULL, .name_len = 0, .is_loop = true, .is_block = false };
utarray_push_back(label_stack, &marker);
marker_index = utarray_len(label_stack) - 1;
}
js->consumed = 1;
if (!expect(js, TOK_LPAREN, &res)) goto done;
jsoff_t cond_start = js->pos;
js->flags |= F_NOEXEC;
v = js_expr(js);
if (is_err(v)) { res = v; goto done; }
if (!expect(js, TOK_RPAREN, &res)) goto done;
jsoff_t body_start = js->pos;
if (exe) {
js->flags = flags;
loop_block_init(js, &loop_ctx);
js->flags |= F_NOEXEC;
}
v = js_block_or_stmt(js);
if (is_err(v)) { res = v; goto done; }
jsoff_t body_end = js->pos;
if (exe) {
while (true) {
js_gc_safepoint(js);
js->flags = flags;
js->pos = cond_start;
js->consumed = 1;
v = resolveprop(js, js_expr(js));
if (is_err(v)) { res = v; break; }
if (!js_truthy(js, v)) break;
js->pos = body_start;
js->consumed = 1;
js->flags = (flags & ~F_NOEXEC) | F_LOOP;
loop_block_clear(js, &loop_ctx);
v = loop_block_exec(js, &loop_ctx);
if (is_err(v)) { res = v; break; }
if (label_flags & F_CONTINUE_LABEL) {
if (is_this_loop_continue_target(marker_index)) {
clear_continue_label();
js->flags &= ~(F_BREAK | F_NOEXEC);
continue;
}
}
if (js->flags & F_BREAK) break;
if (js->flags & F_RETURN) { res = v; break; }
}
loop_block_cleanup(js, &loop_ctx);
}
js->pos = body_end;
js->tok = TOK_SEMICOLON;
js->consumed = 0;
done:
if (use_label_stack && label_stack && utarray_len(label_stack) > 0) {
utarray_pop_back(label_stack);
}
uint8_t preserve = 0;
if (js->flags & F_RETURN) {
preserve = js->flags & (F_RETURN | F_NOEXEC);
}
if ((js->flags & F_BREAK) && (label_flags & F_BREAK_LABEL)) {
preserve |= (js->flags & (F_BREAK | F_NOEXEC));
}
if (label_flags & F_CONTINUE_LABEL) {
preserve |= F_BREAK | F_NOEXEC;
}
js->flags = flags | preserve;
return res;
}
static jsval_t js_do_while(struct js *js) {
uint8_t flags = js->flags, exe = !(flags & F_NOEXEC);
jsval_t res = js_mkundef(), v;
loop_block_ctx_t loop_ctx = {0};
bool use_label_stack = label_stack && utarray_len(label_stack) > 0;
int marker_index = 0;
if (use_label_stack) {
label_entry_t marker = { .name = NULL, .name_len = 0, .is_loop = true, .is_block = false };
utarray_push_back(label_stack, &marker);
marker_index = utarray_len(label_stack) - 1;
}
js->consumed = 1;
jsoff_t body_start = js->pos;
bool is_block = (next(js) == TOK_LBRACE);
if (exe) loop_block_init(js, &loop_ctx);
js->flags |= F_NOEXEC;
v = js_block_or_stmt(js);
if (is_err(v)) { res = v; goto done; }
if (is_block && next(js) == TOK_RBRACE) {
js->consumed = 1;
}
(void) js->pos;
if (!expect(js, TOK_WHILE, &res)) goto done;
if (!expect(js, TOK_LPAREN, &res)) goto done;
jsoff_t cond_start = js->pos;
v = js_expr(js);
if (is_err(v)) { res = v; goto done; }
if (!expect(js, TOK_RPAREN, &res)) goto done;
jsoff_t cond_end = js->pos;
if (exe) {
do {
js_gc_safepoint(js);
js->pos = body_start;
js->consumed = 1;
js->flags = (flags & ~F_NOEXEC) | F_LOOP;
loop_block_clear(js, &loop_ctx);
v = loop_block_exec(js, &loop_ctx);
if (is_err(v)) {
res = v; break;
}
if (label_flags & F_CONTINUE_LABEL) {
if (is_this_loop_continue_target(marker_index)) {
clear_continue_label();
js->flags &= ~(F_BREAK | F_NOEXEC);
} else { break; }
}
if (js->flags & F_BREAK) {
break;
}
if (js->flags & F_RETURN) {
res = v;
break;
}
js->flags = flags;
js->pos = cond_start;
js->consumed = 1;
v = resolveprop(js, js_expr(js));
if (is_err(v)) {
res = v;
break;
}
} while (js_truthy(js, v));
loop_block_cleanup(js, &loop_ctx);
}
js->pos = cond_end;
js->consumed = 1;
done:
if (use_label_stack && label_stack && utarray_len(label_stack) > 0) {
utarray_pop_back(label_stack);
}
uint8_t preserve = 0;
if (js->flags & F_RETURN) {
preserve = js->flags & (F_RETURN | F_NOEXEC);
}
if ((js->flags & F_BREAK) && (label_flags & F_BREAK_LABEL)) {
preserve |= (js->flags & (F_BREAK | F_NOEXEC));
}
if (label_flags & F_CONTINUE_LABEL) {
preserve |= F_BREAK | F_NOEXEC;
}
js->flags = flags | preserve;
return res;
}
static jsval_t js_try(struct js *js) {
uint8_t flags = js->flags, exe = !(flags & F_NOEXEC);
jsval_t res = js_mkundef();
jsval_t try_result = js_mkundef();
jsval_t catch_result = js_mkundef();
jsval_t finally_result = js_mkundef();
bool had_exception = false;
char saved_errmsg[256] = {0};
jsval_t exception_value = js_mkundef();
js->consumed = 1;
if (next(js) != TOK_LBRACE) {
return js_mkerr(js, "{ expected after try");
}
jsoff_t try_start = js->pos;
js->flags |= F_NOEXEC;
js->consumed = 1;
while (next(js) != TOK_EOF && next(js) != TOK_RBRACE) {
jsval_t v = js_stmt(js);
if (is_err(v)) break;
}
if (next(js) == TOK_RBRACE) js->consumed = 1;
jsoff_t try_end = js->pos;
bool has_catch = false;
bool has_finally = false;
jsoff_t catch_start = 0, catch_end = 0;
jsoff_t finally_start = 0, finally_end = 0;
jsoff_t catch_param_off = 0, catch_param_len = 0;
if (lookahead(js) == TOK_CATCH) {
has_catch = true;
js->consumed = 1;
next(js);
js->consumed = 1;
if (next(js) == TOK_LPAREN) {
js->consumed = 1;
if (next(js) == TOK_IDENTIFIER) {
catch_param_off = js->toff;
catch_param_len = js->tlen;
if ((js->flags & F_STRICT) && is_strict_restricted(&js->code[catch_param_off], catch_param_len)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "cannot use '%.*s' as catch parameter in strict mode", (int) catch_param_len, &js->code[catch_param_off]);
}
js->consumed = 1;
}
if (next(js) != TOK_RPAREN) return js_mkerr(js, ") expected in catch");
js->consumed = 1;
}
if (next(js) != TOK_LBRACE) {
return js_mkerr(js, "{ expected after catch");
}
catch_start = js->pos;
js->consumed = 1;
while (next(js) != TOK_EOF && next(js) != TOK_RBRACE) {
jsval_t v = js_stmt(js);
if (is_err(v)) break;
}
if (next(js) == TOK_RBRACE) js->consumed = 1;
catch_end = js->pos;
}
if (lookahead(js) == TOK_FINALLY) {
has_finally = true;
js->consumed = 1;
next(js);
js->consumed = 1;
if (next(js) != TOK_LBRACE) {
return js_mkerr(js, "{ expected after finally");
}
finally_start = js->pos;
js->consumed = 1;
while (next(js) != TOK_EOF && next(js) != TOK_RBRACE) {
jsval_t v = js_stmt(js);
if (is_err(v)) break;
}
if (next(js) == TOK_RBRACE) js->consumed = 1;
finally_end = js->pos;
}
if (!has_catch && !has_finally) {
return js_mkerr(js, "try requires catch or finally");
}
jsoff_t end_pos = has_finally ? finally_end : (has_catch ? catch_end : try_end);
if (exe) {
bool try_returned = false;
jsval_t try_return_value = js_mkundef();
js->flags = flags & (uint8_t)~F_NOEXEC;
js->pos = try_start;
js->consumed = 1;
mkscope(js);
while (next(js) != TOK_EOF && next(js) != TOK_RBRACE && !(js->flags & (F_RETURN | F_THROW | F_BREAK))) {
try_result = js_stmt(js);
if (is_err(try_result)) { had_exception = true; break; }
} delscope(js);
if (js->flags & F_RETURN) {
try_returned = true;
try_return_value = try_result;
js->flags &= (uint8_t)~(F_RETURN | F_NOEXEC);
}
if (js->flags & F_THROW) {
had_exception = true;
js->flags &= (uint8_t)~F_THROW;
strncpy(saved_errmsg, js->errmsg, sizeof(saved_errmsg) - 1);
saved_errmsg[sizeof(saved_errmsg) - 1] = '\0';
exception_value = js->thrown_value;
js->thrown_value = js_mkundef();
js->errmsg[0] = '\0';
}
if (next(js) == TOK_RBRACE) js->consumed = 1;
bool exception_handled = false;
bool catch_returned = false;
jsval_t catch_return_value = js_mkundef();
if (had_exception && has_catch) {
exception_handled = true;
mkscope(js);
if (catch_param_len > 0) {
jsval_t key = js_mkstr(js, &js->code[catch_param_off], catch_param_len);
mkprop(js, js->scope, key, exception_value, 0);
}
js->flags = flags & (uint8_t)~F_NOEXEC;
js->pos = catch_start;
js->consumed = 1;
while (next(js) != TOK_EOF && next(js) != TOK_RBRACE && !(js->flags & (F_RETURN | F_THROW | F_BREAK))) {
catch_result = js_stmt(js);
if (is_err(catch_result)) break;
}
if (js->flags & F_RETURN) {
catch_returned = true;
catch_return_value = catch_result;
js->flags &= (uint8_t)~(F_RETURN | F_NOEXEC);
}
if (next(js) == TOK_RBRACE) js->consumed = 1;
delscope(js);
if (js->flags & F_THROW) {
exception_handled = false;
strncpy(saved_errmsg, js->errmsg, sizeof(saved_errmsg) - 1);
saved_errmsg[sizeof(saved_errmsg) - 1] = '\0';
exception_value = js->thrown_value;
js->thrown_value = js_mkundef();
js->flags &= (uint8_t)~F_THROW;
js->errmsg[0] = '\0';
} else {
res = catch_result;
}
}
if (has_finally) {
uint8_t pre_finally_flags = js->flags;
bool had_pre_finally_exception = (js->flags & F_THROW) != 0;
char pre_finally_errmsg[256] = {0};
if (had_pre_finally_exception) {
strncpy(pre_finally_errmsg, js->errmsg, sizeof(pre_finally_errmsg) - 1);
js->flags &= (uint8_t)~F_THROW;
js->errmsg[0] = '\0';
}
js->flags = flags & (uint8_t)~F_NOEXEC;
js->pos = finally_start;
js->consumed = 1;
while (next(js) != TOK_EOF && next(js) != TOK_RBRACE && !(js->flags & (F_RETURN | F_THROW | F_BREAK))) {
finally_result = js_stmt(js);
if (is_err(finally_result)) break;
}
if (next(js) == TOK_RBRACE) js->consumed = 1;
if (!(js->flags & (F_RETURN | F_THROW))) {
if (had_pre_finally_exception) {
js->flags = pre_finally_flags;
strncpy(js->errmsg, pre_finally_errmsg, sizeof(js->errmsg) - 1);
} else if (had_exception && !exception_handled) {
js->flags |= F_THROW;
strncpy(js->errmsg, saved_errmsg, sizeof(js->errmsg) - 1);
js->thrown_value = exception_value;
} else if (catch_returned) {
js->flags |= F_RETURN;
res = catch_return_value;
} else if (try_returned) {
js->flags |= F_RETURN;
res = try_return_value;
}
}
} else if (had_exception && !exception_handled) {
js->flags |= F_THROW;
strncpy(js->errmsg, saved_errmsg, sizeof(js->errmsg) - 1);
js->thrown_value = exception_value;
res = mkval(T_ERR, 0);
} else if (catch_returned) {
js->flags |= F_RETURN;
res = catch_return_value;
} else if (try_returned) {
js->flags |= F_RETURN;
res = try_return_value;
}
if (!had_exception && !try_returned && !(js->flags & (F_RETURN | F_THROW))) {
res = try_result;
}
}
js->pos = end_pos;
js->tok = TOK_SEMICOLON;
js->consumed = 0;
return res;
}
static bool label_exists(const char *name, jsoff_t len, bool check_loop) {
if (!label_stack) return false;
unsigned int depth = utarray_len(label_stack);
for (int i = (int)depth - 1; i >= 0; i--) {
label_entry_t *entry = (label_entry_t *)utarray_eltptr(label_stack, (unsigned int)i);
if (entry && entry->name_len == len &&
memcmp(entry->name, name, len) == 0) {
if (check_loop && !entry->is_loop) {
return false;
}
return true;
}
}
return false;
}
static bool is_this_loop_continue_target(int marker_index) {
if (!(label_flags & F_CONTINUE_LABEL)) return false;
if (!label_stack || !continue_target_label) return false;
if (marker_index <= 0) return false;
label_entry_t *entry = (label_entry_t *)utarray_eltptr(label_stack, (unsigned int)(marker_index - 1));
if (!entry) return false;
if (entry->name == NULL) return false;
if (!entry->is_loop) return false;
if (entry->name_len == continue_target_label_len &&
memcmp(entry->name, continue_target_label, continue_target_label_len) == 0) {
return true;
}
return false;
}
static jsval_t js_break(struct js *js) {
js->consumed = 1;
uint8_t nxt = next(js);
if (nxt == TOK_IDENTIFIER && !js->had_newline) {
const char *label = &js->code[js->toff];
jsoff_t label_len = js->tlen;
js->consumed = 1;
if (js->flags & F_NOEXEC) {
return js_mkundef();
}
if (!label_exists(label, label_len, false)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "undefined label '%.*s'", (int)label_len, label);
}
break_target_label = label;
break_target_label_len = label_len;
label_flags |= F_BREAK_LABEL;
js->flags |= F_BREAK | F_NOEXEC;
return js_mkundef();
}
if (js->flags & F_NOEXEC) {
return js_mkundef();
}
if (!(js->flags & (F_LOOP | F_SWITCH))) {
bool in_labeled_block = false;
if (label_stack) {
unsigned int depth = utarray_len(label_stack);
for (int i = (int)depth - 1; i >= 0; i--) {
label_entry_t *entry = (label_entry_t *)utarray_eltptr(label_stack, (unsigned int)i);
if (entry && entry->is_block) {
in_labeled_block = true;
break;
}
}
}
if (!in_labeled_block) {
return js_mkerr(js, "not in loop or switch");
}
}
js->flags |= F_BREAK | F_NOEXEC;
return js_mkundef();
}
static jsval_t js_continue(struct js *js) {
js->consumed = 1;
uint8_t nxt = next(js);
if (nxt == TOK_IDENTIFIER && !js->had_newline) {
const char *label = &js->code[js->toff];
jsoff_t label_len = js->tlen;
js->consumed = 1;
if (js->flags & F_NOEXEC) {
return js_mkundef();
}
if (!label_exists(label, label_len, true)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "undefined label '%.*s' or not a loop", (int)label_len, label);
}
continue_target_label = label;
continue_target_label_len = label_len;
label_flags |= F_CONTINUE_LABEL;
js->flags |= F_BREAK | F_NOEXEC;
return js_mkundef();
}
if (js->flags & F_NOEXEC) {
return js_mkundef();
}
if (!(js->flags & F_LOOP)) {
return js_mkerr(js, "not in loop");
}
js->flags |= F_NOEXEC;
return js_mkundef();
}
static inline jsoff_t skip_comment_or_string(const char *code, jsoff_t clen, jsoff_t p) {
char c = code[p];
if (c == '/' && p + 1 < clen) {
if (code[p + 1] == '/') return skip_line_comment(code, clen, p);
if (code[p + 1] == '*') return skip_block_comment(code, clen, p);
}
if (c == '\'' || c == '"') return skip_string_literal(code, clen, p, c);
if (c == '`') return skip_template_literal(code, clen, p);
return 0;
}
static inline bool is_stmt_end(char c) {
return c == ';' || c == '}' || c == '\n';
}
static const uint8_t binop_table[128] = {
['+']=1, ['*']=1, ['%']=1, ['^']=1, [',']=1, ['=']=1,
['/']=2, ['-']=2, ['&']=2, ['|']=2, ['<']=2, ['>']=2, ['!']=2,
};
static bool is_binop_char(const char *code, jsoff_t clen, jsoff_t p) {
unsigned char c = (unsigned char)code[p];
if (c > 127) return false;
uint8_t kind = binop_table[c];
if (kind == 1) return true;
if (kind == 0) return false;
if (p + 1 >= clen) return c == '/';
char n = code[p + 1];
switch (c) {
case '/': return n != '/' && n != '*';
case '-': return n != '-';
case '&': return n != '&';
case '|': return n != '|';
case '<': return true;
case '>': return true;
case '!': return n == '=';
}
return false;
}
static jsoff_t find_ternary_colon(const char *code, jsoff_t clen, jsoff_t p) {
int depth = 0, nesting = 0;
for (; p < clen; p++) {
jsoff_t skip = skip_comment_or_string(code, clen, p);
if (skip) { p = skip - 1; continue; }
switch (code[p]) {
case '(': case '[': case '{': depth++; break;
case ')': case ']': case '}': depth--; break;
case '?': if (!depth) nesting++; break;
case ':': if (!depth && nesting-- == 0) return p; break;
}
}
return clen;
}
static enum tail_scan scan_tail_span(const char *code, jsoff_t clen, jsoff_t start, jsoff_t end) {
jsoff_t p = skiptonext(code, end, start, NULL);
int depth = 0, bdepth = 0, cdepth = 0;
jsoff_t last_paren_close = 0;
bool seen_binop = false;
while (p < end) {
jsoff_t skip = skip_comment_or_string(code, end, p);
if (skip) { p = skip; continue; }
char c = code[p];
if (c == '{') { cdepth++; p++; continue; }
if (c == '}') {
cdepth--; p++;
if (cdepth < 0) return TAIL_UNSAFE;
continue;
}
if (c == '[') { bdepth++; p++; continue; }
if (c == ']') {
bdepth--; p++;
if (bdepth < 0) return TAIL_UNSAFE;
continue;
}
if (c == '(') { depth++; p++; continue; }
if (c == ')') {
depth--; p++;
if (depth < 0) return TAIL_UNSAFE;
if (depth == 0 && bdepth == 0 && cdepth == 0) last_paren_close = p;
continue;
}
if (depth == 0 && bdepth == 0 && cdepth == 0) {
if (c == '?') {
p++;
jsoff_t colon = find_ternary_colon(code, end, p);
if (colon >= end) return TAIL_UNSAFE;
enum tail_scan t = scan_tail_span(code, colon, p, colon);
enum tail_scan f = scan_tail_span(code, end, colon + 1, end);
if (t == TAIL_UNSAFE || f == TAIL_UNSAFE) return TAIL_UNSAFE;
if (t == TAIL_OK || f == TAIL_OK) return TAIL_OK;
return TAIL_NONE;
}
if (is_binop_char(code, end, p)) seen_binop = true;
} p++;
}
if (last_paren_close == 0) return TAIL_NONE;
if (seen_binop) return TAIL_UNSAFE;
jsoff_t after = skiptonext(code, end, last_paren_close, NULL);
if ((after >= end) || is_stmt_end(code[after])) return TAIL_OK;
return TAIL_UNSAFE;
}
static bool is_tail_call_expr(struct js *js) {
const char *code = js->code;
jsoff_t clen = js->clen;
jsoff_t start = skiptonext(code, clen, js->pos, NULL);
return scan_tail_span(code, clen, start, clen) == TAIL_OK;
}
static jsval_t js_return(struct js *js) {
uint8_t exe = !(js->flags & F_NOEXEC);
uint8_t in_func = js->flags & F_CALL;
js->consumed = 1;
jsval_t res = js_mkundef();
uint8_t nxt = next(js);
if (nxt != TOK_SEMICOLON && nxt != TOK_RBRACE && nxt != TOK_EOF && !js->had_newline) {
bool prev_tail = js->tail_ctx;
if (exe && in_func && js->has_tail_calls && is_tail_call_expr(js)) js->tail_ctx = true;
res = resolveprop(js, js_expr_comma(js));
js->tail_ctx = prev_tail;
}
if (exe && !in_func) return js_mkundef();
if (exe) {
js->pos = js->clen;
js->flags |= F_RETURN | F_NOEXEC;
}
return res;
}
static jsval_t js_switch(struct js *js) {
uint8_t flags = js->flags, exe = !(flags & F_NOEXEC);
jsval_t res = js_mkundef();
js->consumed = 1;
if (!expect(js, TOK_LPAREN, &res)) return res;
jsoff_t switch_expr_start = js->pos;
uint8_t saved_flags = js->flags;
js->flags |= F_NOEXEC;
jsval_t switch_expr = js_expr(js);
js->flags = saved_flags;
if (is_err(switch_expr)) return switch_expr;
if (!expect(js, TOK_RPAREN, &res)) return res;
if (!expect(js, TOK_LBRACE, &res)) return res;
typedef struct {
jsoff_t case_expr_start;
jsoff_t case_expr_end;
jsoff_t body_start;
bool is_default;
} CaseInfo;
CaseInfo cases[64];
int case_count = 0;
js->flags |= F_NOEXEC;
while (next(js) != TOK_RBRACE && next(js) != TOK_EOF && case_count < 64) {
if (next(js) == TOK_CASE) {
js->consumed = 1;
cases[case_count].is_default = false;
cases[case_count].case_expr_start = js->pos;
jsval_t case_val = js_expr(js);
if (is_err(case_val)) {
js->flags = flags;
return case_val;
}
cases[case_count].case_expr_end = js->pos;
if (!expect(js, TOK_COLON, &res)) {
js->flags = flags;
return res;
}
cases[case_count].body_start = js->pos;
case_count++;
while (next(js) != TOK_EOF && next(js) != TOK_CASE && next(js) != TOK_DEFAULT && next(js) != TOK_RBRACE) {
jsval_t stmt = js_stmt(js);
if (is_err(stmt)) {
js->flags = flags;
return stmt;
}
}
} else if (next(js) == TOK_DEFAULT) {
js->consumed = 1;
cases[case_count].is_default = true;
cases[case_count].case_expr_start = 0;
cases[case_count].case_expr_end = 0;
if (!expect(js, TOK_COLON, &res)) {
js->flags = flags;
return res;
}
cases[case_count].body_start = js->pos;
case_count++;
while (next(js) != TOK_EOF && next(js) != TOK_CASE && next(js) != TOK_DEFAULT && next(js) != TOK_RBRACE) {
jsval_t stmt = js_stmt(js);
if (is_err(stmt)) {
js->flags = flags;
return stmt;
}
}
} else break;
}
if (!expect(js, TOK_RBRACE, &res)) {
js->flags = flags;
return res;
}
jsoff_t end_pos = js->pos;
if (exe) {
js->pos = switch_expr_start;
js->consumed = 1;
js->flags = flags;
jsval_t switch_val = resolveprop(js, js_expr(js));
if (is_err(switch_val)) {
js->pos = end_pos;
js->flags = flags;
return switch_val;
}
int matching_case = -1;
int default_case = -1;
for (int i = 0; i < case_count; i++) {
if (cases[i].is_default) {
default_case = i;
continue;
}
js->pos = cases[i].case_expr_start;
js->consumed = 1;
js->flags = flags;
jsval_t case_val = resolveprop(js, js_expr(js));
if (is_err(case_val)) {
js->pos = end_pos;
js->flags = flags;
return case_val;
}
bool matches = false;
if (vtype(switch_val) == vtype(case_val)) {
if (vtype(switch_val) == T_NUM) {
matches = tod(switch_val) == tod(case_val);
} else if (vtype(switch_val) == T_STR) {
jsoff_t n1, off1 = vstr(js, switch_val, &n1);
jsoff_t n2, off2 = vstr(js, case_val, &n2);
matches = n1 == n2 && memcmp(&js->mem[off1], &js->mem[off2], n1) == 0;
} else if (vtype(switch_val) == T_BOOL) {
matches = vdata(switch_val) == vdata(case_val);
} else {
matches = vdata(switch_val) == vdata(case_val);
}
}
if (matches) {
matching_case = i;
break;
}
}
if (matching_case < 0 && default_case >= 0) matching_case = default_case;
if (matching_case >= 0) {
js->flags = (flags & ~F_NOEXEC) | F_SWITCH;
for (int i = matching_case; i < case_count; i++) {
js->pos = cases[i].body_start;
js->consumed = 1;
while (next(js) != TOK_EOF && next(js) != TOK_CASE &&
next(js) != TOK_DEFAULT && next(js) != TOK_RBRACE &&
!(js->flags & (F_BREAK | F_RETURN | F_THROW))) {
res = js_stmt(js);
if (is_err(res)) {
js->pos = end_pos;
uint8_t preserve = 0;
if (js->flags & F_RETURN) {
preserve = js->flags & (F_RETURN | F_NOEXEC);
}
if (js->flags & F_THROW) {
preserve = js->flags & (F_THROW | F_NOEXEC);
}
js->flags = flags | preserve;
return res;
}
}
if (js->flags & F_BREAK) { js->flags &= ~F_BREAK; break; }
if (js->flags & (F_RETURN | F_THROW)) break;
}
}
}
js->pos = end_pos;
js->tok = TOK_SEMICOLON;
js->consumed = 0;
uint8_t preserve = 0;
if (js->flags & F_RETURN) preserve = js->flags & (F_RETURN | F_NOEXEC);
if (js->flags & F_THROW) preserve = js->flags & (F_THROW | F_NOEXEC);
js->flags = (flags & ~F_SWITCH) | preserve;
return res;
}
static jsval_t js_with(struct js *js) {
uint8_t flags = js->flags, exe = !(flags & F_NOEXEC);
jsval_t res = js_mkundef();
if (flags & F_STRICT) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "with statement not allowed in strict mode");
}
js->consumed = 1;
if (!expect(js, TOK_LPAREN, &res)) return res;
jsval_t obj_expr = js_expr(js);
if (is_err(obj_expr)) return obj_expr;
if (!expect(js, TOK_RPAREN, &res)) return res;
if (exe) {
jsval_t obj = resolveprop(js, obj_expr);
if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) {
return js_mkerr(js, "with requires object");
}
jsval_t with_obj = obj;
if (vtype(obj) == T_FUNC) {
with_obj = mkval(T_OBJ, vdata(obj));
}
jsoff_t parent_scope_offset = (jsoff_t) vdata(js->scope);
if (global_scope_stack == NULL) utarray_new(global_scope_stack, &jsoff_icd);
utarray_push_back(global_scope_stack, &parent_scope_offset);
jsval_t with_scope = mkobj(js, parent_scope_offset);
set_slot(js, with_scope, SLOT_WITH, with_obj);
jsval_t saved_scope = js->scope;
js->scope = with_scope;
res = js_block_or_stmt(js);
if (global_scope_stack && utarray_len(global_scope_stack) > 0) utarray_pop_back(global_scope_stack);
js->scope = saved_scope;
} else res = js_block_or_stmt(js);
js->flags = flags;
return res;
}
static jsval_t js_class_expr(struct js *js, bool is_expression);
static jsval_t js_class_decl(struct js *js) { return js_class_expr(js, false); }
static jsval_t js_class_expr(struct js *js, bool is_expression) {
uint8_t exe = !(js->flags & F_NOEXEC);
js->consumed = 1;
jsoff_t class_name_off = 0, class_name_len = 0;
char *class_name = NULL;
if (next(js) == TOK_IDENTIFIER) {
if (!(js->tlen == 7 && streq(&js->code[js->toff], js->tlen, "extends", 7))) {
class_name_off = js->toff;
class_name_len = js->tlen;
class_name = (char *) &js->code[class_name_off];
js->consumed = 1;
}
} else if (!is_expression) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "class name expected");
}
jsoff_t super_off = 0, super_len = 0;
if (next(js) == TOK_IDENTIFIER && js->tlen == 7 &&
streq(&js->code[js->toff], js->tlen, "extends", 7)) {
js->consumed = 1;
EXPECT_IDENT();
super_off = js->toff;
super_len = js->tlen;
js->consumed = 1;
}
EXPECT(TOK_LBRACE);
jsoff_t constructor_params_start = 0;
jsoff_t constructor_body_start = 0, constructor_body_end = 0;
uint8_t save_flags = js->flags;
js->flags |= F_NOEXEC;
typedef struct {
jsoff_t name_off, name_len, fn_start, fn_end;
bool is_async;
bool is_static;
bool is_field;
bool is_private;
bool is_getter;
bool is_setter;
bool is_static_block;
bool is_computed;
jsval_t computed_key;
jsoff_t field_start, field_end;
jsoff_t param_start;
} MethodInfo;
static const UT_icd method_info_icd = {
.sz = sizeof(MethodInfo),
.init = NULL,
.copy = NULL,
.dtor = NULL,
};
UT_array *methods = NULL;
utarray_new(methods, &method_info_icd);
uint8_t class_tok;
while ((class_tok = next(js)) != TOK_RBRACE && class_tok != TOK_EOF) {
bool is_async_method = false;
bool is_static_member = false;
bool is_getter_method = false;
bool is_setter_method = false;
if (next(js) == TOK_STATIC) {
is_static_member = true;
js->consumed = 1;
}
if (next(js) == TOK_ASYNC) {
is_async_method = true;
js->consumed = 1;
}
bool is_private_member = false;
if (next(js) == TOK_HASH) {
js->consumed = 1;
if (next(js) == TOK_IDENTIFIER) { is_private_member = true; } else {
js->flags = save_flags;
return js_mkerr_typed(js, JS_ERR_SYNTAX, "private field name expected");
}
}
if (next(js) == TOK_IDENTIFIER) {
bool is_get = (js->tlen == 3 && memcmp(&js->code[js->toff], "get", 3) == 0);
bool is_set = (js->tlen == 3 && memcmp(&js->code[js->toff], "set", 3) == 0);
if (is_get || is_set) {
jsoff_t saved_pos = js->pos;
jsoff_t saved_toff = js->toff;
jsoff_t saved_tlen = js->tlen;
uint8_t saved_tok = js->tok;
int saved_stream_pos = js->token_stream_pos;
js->consumed = 1;
uint8_t peek = next(js);
bool next_is_name = (peek == TOK_IDENTIFIER);
if (!next_is_name && peek == TOK_HASH) {
js->consumed = 1;
if (next(js) == TOK_IDENTIFIER) {
next_is_name = true;
is_private_member = true;
}
}
if (next_is_name) {
is_getter_method = is_get;
is_setter_method = is_set;
} else {
js->pos = saved_pos;
js->toff = saved_toff;
js->tlen = saved_tlen;
js->tok = saved_tok;
js->token_stream_pos = saved_stream_pos;
js->consumed = 0;
}
}
}
if (is_static_member && !is_getter_method && !is_setter_method && next(js) == TOK_LBRACE) {
jsoff_t block_start = js->pos - 1; js->consumed = 0;
jsval_t blk = js_block(js, false);
if (is_err(blk)) { js->flags = save_flags; utarray_free(methods); return blk; }
jsoff_t block_end = js->pos;
MethodInfo static_block = {0};
static_block.is_static = true;
static_block.is_static_block = true;
static_block.field_start = block_start;
static_block.field_end = block_end;
utarray_push_back(methods, &static_block);
js->consumed = 1;
continue;
}
if (next(js) == TOK_LBRACKET) {
jsoff_t key_start = js->pos;
js->consumed = 1;
jsval_t computed_key = js_expr(js);
if (is_err(computed_key)) { js->flags = save_flags; utarray_free(methods); return computed_key; }
if (next(js) != TOK_RBRACKET) { js->flags = save_flags; return js_mkerr_typed(js, JS_ERR_SYNTAX, "] expected"); }
jsoff_t key_end = js->pos - 1;
js->consumed = 1;
MethodInfo computed = {0};
computed.is_static = is_static_member;
computed.is_computed = true;
computed.name_off = key_start;
computed.name_len = key_end - key_start;
if (next(js) == TOK_LPAREN) {
computed.is_field = false;
computed.is_async = is_async_method;
computed.is_getter = is_getter_method;
computed.is_setter = is_setter_method;
EXPECT(TOK_LPAREN, js->flags = save_flags);
jsoff_t method_params_start = js->pos - 1;
if (!parse_func_params(js, &save_flags, NULL)) {
js->flags = save_flags;
return js_mkerr_typed(js, JS_ERR_SYNTAX, "invalid method parameters");
}
EXPECT(TOK_RPAREN, js->flags = save_flags);
EXPECT(TOK_LBRACE, js->flags = save_flags);
js->consumed = 0;
jsval_t blk = js_block(js, false);
if (is_err(blk)) { js->flags = save_flags; return blk; }
jsoff_t method_body_end = js->pos;
computed.fn_start = method_params_start;
computed.fn_end = method_body_end;
computed.param_start = method_params_start;
js->consumed = 1;
} else if (next(js) == TOK_ASSIGN) {
computed.is_field = true;
js->consumed = 1;
jsoff_t field_start = js->pos;
jsval_t field_expr = js_expr(js);
if (is_err(field_expr)) { js->flags = save_flags; utarray_free(methods); return field_expr; }
jsoff_t field_end = js->pos;
computed.field_start = field_start;
computed.field_end = field_end;
if (next(js) == TOK_SEMICOLON) js->consumed = 1;
} else {
computed.is_field = true;
if (next(js) == TOK_SEMICOLON) js->consumed = 1;
}
utarray_push_back(methods, &computed);
continue;
}
if (next(js) != TOK_IDENTIFIER && (next(js) < TOK_ASYNC || next(js) > TOK_STATIC)) {
js->flags = save_flags;
return js_mkerr_typed(js, JS_ERR_SYNTAX, "method name expected");
}
jsoff_t method_name_off = is_private_member ? js->toff - 1 : js->toff;
jsoff_t method_name_len = is_private_member ? js->tlen + 1 : js->tlen;
js->consumed = 1;
if (next(js) == TOK_ASSIGN) {
js->consumed = 1;
jsoff_t field_start = js->pos;
jsval_t field_expr = js_expr(js);
if (is_err(field_expr)) {
js->flags = save_flags;
utarray_free(methods);
return field_expr;
}
jsoff_t field_end = js->pos;
if (next(js) == TOK_SEMICOLON) js->consumed = 1;
MethodInfo field_method = {
.name_off = method_name_off,
.name_len = method_name_len,
.is_static = is_static_member,
.is_async = false,
.is_field = true,
.is_private = is_private_member,
.field_start = field_start,
.field_end = field_end,
.fn_start = 0,
.fn_end = 0,
};
utarray_push_back(methods, &field_method);
continue;
}
if (next(js) == TOK_SEMICOLON || (next(js) != TOK_LPAREN && next(js) == TOK_IDENTIFIER)) {
if (next(js) == TOK_SEMICOLON) js->consumed = 1;
MethodInfo bare_method = {
.name_off = method_name_off,
.name_len = method_name_len,
.is_static = is_static_member,
.is_async = false,
.is_field = true,
.is_private = is_private_member,
.field_start = 0,
.field_end = 0,
.fn_start = 0,
.fn_end = 0,
};
utarray_push_back(methods, &bare_method);
continue;
}
EXPECT(TOK_LPAREN, js->flags = save_flags);
jsoff_t method_params_start = js->pos - 1;
if (!parse_func_params(js, &save_flags, NULL)) {
js->flags = save_flags;
return js_mkerr_typed(js, JS_ERR_SYNTAX, "invalid method parameters");
}
EXPECT(TOK_RPAREN, js->flags = save_flags);
EXPECT(TOK_LBRACE, js->flags = save_flags);
jsoff_t method_body_start = js->pos - 1;
js->consumed = 0;
jsval_t blk = js_block(js, false);
if (is_err(blk)) {
js->flags = save_flags;
return blk;
}
jsoff_t method_body_end = js->pos;
if (streq(&js->code[method_name_off], method_name_len, "constructor", 11)) {
constructor_params_start = method_params_start;
constructor_body_start = method_body_start + 1;
constructor_body_end = method_body_end;
} else {
MethodInfo func_method = {
.name_off = method_name_off,
.name_len = method_name_len,
.fn_start = method_params_start,
.fn_end = method_body_end,
.param_start = method_params_start,
.is_async = is_async_method,
.is_static = is_static_member,
.is_field = false,
.is_private = is_private_member,
.is_getter = is_getter_method,
.is_setter = is_setter_method,
.field_start = 0,
.field_end = 0,
};
utarray_push_back(methods, &func_method);
}
js->consumed = 1;
}
EXPECT(TOK_RBRACE, js->flags = save_flags);
js->flags = save_flags;
if (exe) {
jsval_t super_constructor = js_mkundef();
jsval_t super_proto = js_mknull();
if (super_len > 0) {
jsval_t super_val = lookup(js, &js->code[super_off], super_len);
if (is_err(super_val)) return super_val;
super_constructor = resolveprop(js, super_val);
if (vtype(super_constructor) != T_FUNC && vtype(super_constructor) != T_CFUNC) {
return js_mkerr(js, "super class must be a constructor");
}
jsval_t super_obj = mkval(T_OBJ, vdata(super_constructor));
jsoff_t super_proto_off = lkp_interned(js, super_obj, INTERN_PROTOTYPE, 9);
if (super_proto_off != 0) {
super_proto = resolveprop(js, mkval(T_PROP, super_proto_off));
}
}
jsval_t proto = js_mkobj(js);
if (is_err(proto)) return proto;
if (vtype(super_proto) == T_OBJ) {
set_proto(js, proto, super_proto);
} else {
jsval_t object_proto = get_ctor_proto(js, "Object", 6);
if (vtype(object_proto) == T_OBJ) set_proto(js, proto, object_proto);
}
jsval_t func_scope = mkobj(js, (jsoff_t) vdata(js->scope));
for (unsigned int i = 0; i < utarray_len(methods); i++) {
MethodInfo *m = (MethodInfo *)utarray_eltptr(methods, i);
if (!m->is_computed) continue;
jsval_t key_val = js_eval_slice(js, m->name_off, m->name_len);
key_val = resolveprop(js, key_val);
if (vtype(key_val) == T_SYMBOL) {
char sym_buf[48];
sym_to_prop_key(key_val, sym_buf, sizeof(sym_buf));
m->computed_key = js_mkstr(js, sym_buf, strlen(sym_buf));
} else m->computed_key = coerce_to_str(js, key_val);
if (is_err(m->computed_key)) { utarray_free(methods); return m->computed_key; }
}
for (unsigned int i = 0; i < utarray_len(methods); i++) {
MethodInfo *m = (MethodInfo *)utarray_eltptr(methods, i);
if (m->is_static) continue;
if (m->is_field) continue;
jsval_t method_name;
if (m->is_computed) method_name = m->computed_key;
else method_name = js_mkstr(js, &js->code[m->name_off], m->name_len);
if (is_err(method_name)) return method_name;
jsoff_t mlen = m->fn_end - m->fn_start;
jsval_t method_obj = mkobj(js, 0);
if (is_err(method_obj)) return method_obj;
set_func_code(js, method_obj, &js->code[m->fn_start], mlen);
set_slot(js, method_obj, SLOT_SCOPE, func_scope);
if (m->is_async) {
set_slot(js, method_obj, SLOT_ASYNC, js_true);
jsval_t async_proto = get_slot(js, js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) set_proto(js, method_obj, async_proto);
} else {
jsval_t func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) set_proto(js, method_obj, func_proto);
}
if (super_len > 0) set_slot(js, method_obj, SLOT_SUPER, super_constructor);
jsval_t method_func = mkval(T_FUNC, (unsigned long) vdata(method_obj));
if (m->is_getter || m->is_setter) {
jsoff_t name_len;
const char *name_str = (const char *)&js->mem[vstr(js, method_name, &name_len)];
if (m->is_getter) {
js_set_getter_desc(js, proto, name_str, name_len, method_func, JS_DESC_C);
} else {
js_set_setter_desc(js, proto, name_str, name_len, method_func, JS_DESC_C);
}
} else {
jsval_t set_res = js_setprop(js, proto, method_name, method_func);
if (is_err(set_res)) return set_res;
}
}
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) return func_obj;
if (constructor_params_start > 0 && constructor_body_start > 0) {
jsoff_t code_len = constructor_body_end - constructor_params_start;
set_func_code(js, func_obj, &js->code[constructor_params_start], code_len);
} else {
set_func_code_ptr(js, func_obj, "(){}", 4);
if (super_len > 0) set_slot(js, func_obj, SLOT_DEFAULT_CTOR, js_true);
}
int instance_field_count = 0;
for (unsigned int i = 0; i < utarray_len(methods); i++) {
MethodInfo *m = (MethodInfo *)utarray_eltptr(methods, i);
if (m->is_static) continue;
if (!m->is_field) continue;
instance_field_count++;
}
if (instance_field_count > 0) {
size_t metadata_size = instance_field_count * sizeof(jsoff_t) * 5;
jsoff_t meta_len = (jsoff_t) (metadata_size + 1);
jsoff_t meta_header = (jsoff_t) ((meta_len << 3) | T_STR);
jsoff_t meta_off = js_alloc(js, meta_len + sizeof(meta_header));
if (meta_off == (jsoff_t) ~0) return js_mkerr(js, "oom");
memcpy(&js->mem[meta_off], &meta_header, sizeof(meta_header));
jsoff_t *metadata = (jsoff_t *)(&js->mem[meta_off + sizeof(meta_header)]);
int meta_idx = 0;
for (unsigned int i = 0; i < utarray_len(methods); i++) {
MethodInfo *m = (MethodInfo *)utarray_eltptr(methods, i);
if (m->is_static) continue;
if (!m->is_field) continue;
if (m->is_computed) {
metadata[meta_idx * 5 + 0] = (jsoff_t) m->computed_key;
metadata[meta_idx * 5 + 1] = 0;
} else {
metadata[meta_idx * 5 + 0] = m->name_off;
metadata[meta_idx * 5 + 1] = m->name_len;
}
metadata[meta_idx * 5 + 2] = m->field_start;
metadata[meta_idx * 5 + 3] = m->field_end;
metadata[meta_idx * 5 + 4] = m->is_computed ? 1 : 0;
meta_idx++;
}
js->mem[meta_off + sizeof(meta_header) + metadata_size] = 0;
jsval_t fields_meta = mkval(T_STR, meta_off);
set_slot(js, func_obj, SLOT_FIELD_COUNT, tov((double)instance_field_count));
set_slot(js, func_obj, SLOT_FIELDS, fields_meta);
const char *arena_src = code_arena_alloc(js->code, js->clen);
if (arena_src) set_slot(js, func_obj, SLOT_SOURCE, mkval(T_CFUNC, (size_t)arena_src));
}
set_slot(js, func_obj, SLOT_SCOPE, func_scope);
if (super_len > 0) set_slot(js, func_obj, SLOT_SUPER, super_constructor);
jsval_t func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) set_proto(js, func_obj, func_proto);
jsval_t name_key = js_mkstr(js, "name", 4);
if (is_err(name_key)) return name_key;
jsval_t name_val = class_name_len > 0 ? js_mkstr(js, class_name, class_name_len) : js_mkstr(js, "", 0);
if (is_err(name_val)) return name_val;
jsval_t res_name = js_setprop(js, func_obj, name_key, name_val);
if (is_err(res_name)) return res_name;
jsval_t proto_key = js_mkstr(js, "prototype", 9);
if (is_err(proto_key)) return proto_key;
jsval_t proto_res = js_setprop(js, func_obj, proto_key, proto);
if (is_err(proto_res)) return proto_res;
jsval_t constructor = mkval(T_FUNC, (unsigned long) vdata(func_obj));
jsval_t ctor_key = js_mkstr(js, "constructor", 11);
if (is_err(ctor_key)) return ctor_key;
jsval_t ctor_res = js_setprop(js, proto, ctor_key, constructor);
if (is_err(ctor_res)) return ctor_res;
js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
if (class_name_len > 0) {
if (lkp_scope(js, js->scope, class_name, class_name_len) > 0) {
return js_mkerr(js, "'%.*s' already declared", (int) class_name_len, class_name);
}
jsval_t x = mkprop(js, js->scope, js_mkstr(js, class_name, class_name_len), constructor, 0);
if (is_err(x)) return x;
}
for (unsigned int i = 0; i < utarray_len(methods); i++) {
MethodInfo *m = (MethodInfo *)utarray_eltptr(methods, i);
if (!m->is_static) continue;
if (m->is_static_block) {
if (m->field_start > 0 && m->field_end > m->field_start) {
const char *saved_code = js->code;
jsoff_t saved_clen = js->clen, saved_pos = js->pos;
uint8_t saved_tok = js->tok, saved_consumed = js->consumed;
jsval_t blk_res = js_eval(js, &saved_code[m->field_start], m->field_end - m->field_start);
js->code = saved_code; js->clen = saved_clen; js->pos = saved_pos;
js->tok = saved_tok; js->consumed = saved_consumed;
if (is_err(blk_res)) { utarray_free(methods); return blk_res; }
}
continue;
}
jsval_t member_name;
if (m->is_computed) member_name = m->computed_key;
else member_name = js_mkstr(js, &js->code[m->name_off], m->name_len);
if (is_err(member_name)) return member_name;
if (m->is_field) {
jsval_t field_val = js_mkundef();
if (m->field_start > 0 && m->field_end > m->field_start) {
field_val = js_eval_slice(js, m->field_start, m->field_end - m->field_start);
field_val = resolveprop(js, field_val);
}
jsval_t set_res = js_setprop(js, func_obj, member_name, field_val);
if (is_err(set_res)) return set_res;
} else {
jsoff_t mlen = m->fn_end - m->fn_start;
jsval_t method_obj = mkobj(js, 0);
if (is_err(method_obj)) return method_obj;
set_func_code(js, method_obj, &js->code[m->fn_start], mlen);
set_slot(js, method_obj, SLOT_SCOPE, func_scope);
if (super_len > 0) set_slot(js, method_obj, SLOT_SUPER, super_constructor);
jsval_t method_func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(method_func_proto) == T_FUNC) set_proto(js, method_obj, method_func_proto);
jsval_t method_func = mkval(T_FUNC, (unsigned long) vdata(method_obj));
if (m->is_getter || m->is_setter) {
jsoff_t nm_len;
const char *nm_str = (const char *)&js->mem[vstr(js, member_name, &nm_len)];
if (m->is_getter) {
js_set_getter_desc(js, func_obj, nm_str, nm_len, method_func, JS_DESC_C);
} else {
js_set_setter_desc(js, func_obj, nm_str, nm_len, method_func, JS_DESC_C);
}
} else {
jsval_t set_res = js_setprop(js, func_obj, member_name, method_func);
if (is_err(set_res)) return set_res;
}
}
}
utarray_free(methods);
return constructor;
}
utarray_free(methods);
return js_mkundef();
}
static void js_throw_handle(struct js *js, jsval_t *res) {
js->consumed = 1;
jsval_t throw_val = js_expr(js);
if (js->flags & F_NOEXEC) *res = js_mkundef(); else {
throw_val = resolveprop(js, throw_val);
if (is_err(throw_val)) *res = throw_val;
else *res = js_throw(js, throw_val);
}
}
static jsval_t find_var_scope(struct js *js) {
jsval_t scope = js->scope;
jsval_t eval_marker = get_slot(js, scope, SLOT_STRICT_EVAL_SCOPE);
if (vtype(eval_marker) != T_UNDEF) return scope;
jsval_t module_marker = get_slot(js, scope, SLOT_MODULE_SCOPE);
if (vtype(module_marker) != T_UNDEF) return scope;
if ((js->flags & F_CALL) && global_scope_stack && utarray_len(global_scope_stack) > 0) {
jsoff_t *scope_off = (jsoff_t *)utarray_back(global_scope_stack);
if (scope_off && *scope_off != 0) return mkval(T_OBJ, *scope_off);
}
while (vdata(upper(js, scope)) != 0) {
jsval_t parent = upper(js, scope);
jsval_t parent_eval_marker = get_slot(js, parent, SLOT_STRICT_EVAL_SCOPE);
if (vtype(parent_eval_marker) != T_UNDEF) return scope;
jsval_t parent_module_marker = get_slot(js, parent, SLOT_MODULE_SCOPE);
if (vtype(parent_module_marker) != T_UNDEF) return scope;
scope = parent;
}
return scope;
}
static jsval_t js_var_decl(struct js *js) {
uint8_t exe = !(js->flags & F_NOEXEC);
jsval_t var_scope = find_var_scope(js);
js->consumed = 1;
for (;;) {
uint8_t tok = next(js);
if (tok == TOK_LBRACKET || tok == TOK_LBRACE) {
jsoff_t pattern_start = js->toff;
uint8_t close_tok = (tok == TOK_LBRACKET) ? TOK_RBRACKET : TOK_RBRACE;
js->consumed = 1;
int depth = 1;
while (depth > 0 && next(js) != TOK_EOF) {
if (js->tok == tok) depth++;
else if (js->tok == close_tok) depth--;
if (depth > 0) js->consumed = 1;
}
js->consumed = 1;
jsoff_t pattern_end = js->pos;
jsoff_t pattern_len = pattern_end - pattern_start;
if (next(js) != TOK_ASSIGN) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "destructuring requires assignment");
} js->consumed = 1;
jsval_t v = js_expr(js);
if (is_err(v)) return v;
if (exe) {
jsval_t val = resolveprop(js, v);
jsval_t r = bind_destruct_pattern(js, &js->code[pattern_start], pattern_len, val, var_scope);
if (is_err(r)) return r;
}
} else {
EXPECT_IDENT();
js->consumed = 0;
jsoff_t noff = js->toff, nlen = js->tlen;
char *name = (char *) &js->code[noff];
if (exe && (js->flags & F_STRICT) && is_strict_restricted(name, nlen)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "cannot use '%.*s' as variable name in strict mode", (int) nlen, name);
}
if (exe && (js->flags & F_STRICT) && is_strict_reserved(name, nlen)) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "'%.*s' is reserved in strict mode", (int) nlen, name);
}
jsval_t v = js_mkundef();
bool has_initializer = false;
js->consumed = 1;
if (next(js) == TOK_ASSIGN) {
js->consumed = 1;
v = js_expr(js);
if (is_err(v)) return v;
has_initializer = true;
}
if (exe) {
char decoded_name[256];
size_t decoded_len = decode_ident_escapes(name, nlen, decoded_name, sizeof(decoded_name));
jsoff_t existing_off = lkp(js, var_scope, decoded_name, decoded_len);
if (existing_off > 0) {
if (has_initializer && !is_err(v)) {
jsval_t key_val = js_mkstr(js, decoded_name, decoded_len);
js_setprop(js, var_scope, key_val, resolveprop(js, v));
}
} else {
jsval_t x = mkprop(js, var_scope, js_mkstr(js, decoded_name, decoded_len), resolveprop(js, v), 0);
if (is_err(x)) return x;
}
}
}
uint8_t var_next = next(js);
if (var_next == TOK_SEMICOLON || var_next == TOK_EOF || var_next == TOK_RBRACE || js->had_newline) break;
EXPECT(TOK_COMMA);
}
return js_mkundef();
}
static void js_var(struct js *js, jsval_t *res) {
if ((js->flags & F_STRICT) && !js->var_warning_shown) {
fprintf(stderr, "Warning: 'var' is deprecated, use 'let' or 'const' instead\n");
js->var_warning_shown = true;
}
*res = js_var_decl(js);
}
static void js_async(struct js *js, jsval_t *res) {
js->consumed = 1;
uint8_t next_tok = next(js);
if (next_tok == TOK_FUNC) {
*res = js_func_decl_async(js);
return;
}
if (next_tok == TOK_LPAREN) {
*res = js_async_arrow_paren(js);
return;
}
*res = js_mkerr_typed(js, JS_ERR_SYNTAX, "async must be followed by function");
}
static jsval_t js_stmt(struct js *js);
static bool is_break_target(const char *name, jsoff_t len) {
if (!(label_flags & F_BREAK_LABEL)) return false;
if (break_target_label_len != len) return false;
return memcmp(break_target_label, name, len) == 0;
}
static bool is_continue_target(const char *name, jsoff_t len) {
if (!(label_flags & F_CONTINUE_LABEL)) return false;
if (continue_target_label_len != len) return false;
return memcmp(continue_target_label, name, len) == 0;
}
static jsval_t js_labeled_stmt(struct js *js, const char *label, jsoff_t label_len) {
uint8_t flags = js->flags;
jsval_t res = js_mkundef();
if (!label_stack) {
utarray_new(label_stack, &label_entry_icd);
}
uint8_t next_tok = next(js);
bool is_loop = (next_tok == TOK_WHILE || next_tok == TOK_DO || next_tok == TOK_FOR);
bool is_block = (next_tok == TOK_LBRACE);
label_entry_t entry = {
.name = label,
.name_len = label_len,
.is_loop = is_loop,
.is_block = is_block || !is_loop
};
utarray_push_back(label_stack, &entry);
if (is_loop && !(flags & F_NOEXEC)) {
res = js_stmt_impl(js);
if ((js->flags & F_BREAK) && is_break_target(label, label_len)) {
js->flags &= ~(F_BREAK | F_NOEXEC);
clear_break_label();
res = js_mkundef();
}
if ((label_flags & F_CONTINUE_LABEL) && is_continue_target(label, label_len)) {
clear_continue_label();
}
} else if (is_loop && (flags & F_NOEXEC)) {
res = js_stmt_impl(js);
} else {
res = js_stmt_impl(js);
if ((js->flags & F_BREAK) && is_break_target(label, label_len)) {
js->flags &= ~(F_BREAK | F_NOEXEC);
js->flags |= (flags & F_NOEXEC);
clear_break_label();
res = js_mkundef();
}
}
if (label_stack && utarray_len(label_stack) > 0) {
utarray_pop_back(label_stack);
}
return res;
}
static jsval_t js_stmt_impl(struct js *js) {
jsval_t res;
uint8_t stmt_tok = next(js);
switch (stmt_tok) {
case TOK_SEMICOLON:
res = js_mkundef();
break;
case TOK_CASE: case TOK_CATCH:
case TOK_DEFAULT: case TOK_FINALLY:
res = js_mkerr(js, "SyntaxError '%.*s'", (int) js->tlen, js->code + js->toff);
break;
case TOK_YIELD:
res = js_mkerr(js, " '%.*s' not implemented", (int) js->tlen, js->code + js->toff);
break;
case TOK_IMPORT: res = js_import_stmt(js); break;
case TOK_EXPORT: res = js_export_stmt(js); break;
case TOK_THROW: js_throw_handle(js, &res); break;
case TOK_VAR: js_var(js, &res); break;
case TOK_ASYNC: js_async(js, &res); break;
case TOK_WITH: res = js_with(js); break;
case TOK_SWITCH: res = js_switch(js); break;
case TOK_WHILE: res = js_while(js); break;
case TOK_DO: res = js_do_while(js); break;
case TOK_DEBUGGER: js->consumed = 1; res = js_mkundef(); break;
case TOK_CONTINUE: res = js_continue(js); break;
case TOK_BREAK: res = js_break(js); break;
case TOK_LET: res = js_let(js); break;
case TOK_CONST: res = js_const(js); break;
case TOK_FUNC: res = js_func_decl(js); break;
case TOK_CLASS: res = js_class_decl(js); break;
case TOK_IF: res = js_if(js); break;
case TOK_LBRACE: res = js_block(js, !(js->flags & F_NOEXEC)); break;
case TOK_FOR: res = js_for(js); break;
case TOK_RETURN: res = js_return(js); break;
case TOK_TRY: res = js_try(js); break;
default:
res = resolveprop(js, js_expr(js));
while (next(js) == TOK_COMMA) {
js->consumed = 1;
res = resolveprop(js, js_expr(js));
}
break;
}
bool is_block_statement = (
stmt_tok == TOK_FUNC || stmt_tok == TOK_CLASS ||
stmt_tok == TOK_EXPORT || stmt_tok == TOK_IMPORT ||
stmt_tok == TOK_IF || stmt_tok == TOK_WHILE ||
stmt_tok == TOK_DO || stmt_tok == TOK_FOR ||
stmt_tok == TOK_SWITCH || stmt_tok == TOK_TRY ||
stmt_tok == TOK_LBRACE || stmt_tok == TOK_ASYNC
);
if (is_err(res)) return res;
if (!is_block_statement) {
int next_tok = next(js);
bool asi_applies = js->had_newline || next_tok == TOK_EOF || next_tok == TOK_RBRACE;
bool missing_semicolon = next_tok != TOK_SEMICOLON && !asi_applies;
if (missing_semicolon) return js_mkerr_typed(js, JS_ERR_SYNTAX, "; expected");
if (next_tok == TOK_SEMICOLON) js->consumed = 1;
}
return res;
}
static jsval_t js_stmt(struct js *js) {
uint8_t tok = next(js);
if (tok == TOK_IDENTIFIER) {
js_parse_state_t saved_state;
JS_SAVE_STATE(js, saved_state);
jsoff_t saved_toff = js->toff;
jsoff_t saved_tlen = js->tlen;
const char *potential_label = &js->code[js->toff];
jsoff_t potential_label_len = js->tlen;
js->consumed = 1;
if (next(js) == TOK_COLON) {
js->consumed = 1;
return js_labeled_stmt(js, potential_label, potential_label_len);
}
JS_RESTORE_STATE(js, saved_state);
js->toff = saved_toff;
js->tlen = saved_tlen;
}
return js_stmt_impl(js);
}
jsval_t js_symbol_to_string(struct js *js, jsval_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';
jsval_t result = js_mkstr(js, buf, total);
if (buf != stack_buf) free(buf);
return result;
}
static jsval_t builtin_String(struct js *js, jsval_t *args, int nargs) {
jsval_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;
}
jsval_t string_proto = js_get_ctor_proto(js, "String", 6);
if (is_unboxed_obj(js, js->this_val, string_proto)) {
set_slot(js, js->this_val, SLOT_PRIMITIVE, sval);
jsoff_t slen;
vstr(js, sval, &slen);
js_setprop(js, js->this_val, js->length_str, tov((double)slen));
js_set_descriptor(js, js->this_val, "length", 6, 0);
}
return sval;
}
static jsval_t builtin_Number_isNaN(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return mkval(T_BOOL, 0);
jsval_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 jsval_t builtin_Number_isFinite(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return mkval(T_BOOL, 0);
jsval_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 jsval_t builtin_global_isNaN(struct js *js, jsval_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 jsval_t builtin_global_isFinite(struct js *js, jsval_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 jsval_t builtin_Number_isInteger(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return mkval(T_BOOL, 0);
jsval_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 jsval_t builtin_Number_isSafeInteger(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return mkval(T_BOOL, 0);
jsval_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 jsval_t builtin_Number(struct js *js, jsval_t *args, int nargs) {
jsval_t nval = tov(nargs > 0 ? js_to_number(js, args[0]) : 0.0);
jsval_t number_proto = js_get_ctor_proto(js, "Number", 6);
if (is_unboxed_obj(js, js->this_val, number_proto)) {
set_slot(js, js->this_val, SLOT_PRIMITIVE, nval);
}
return nval;
}
static jsval_t builtin_Boolean(struct js *js, jsval_t *args, int nargs) {
jsval_t bval = mkval(T_BOOL, nargs > 0 && js_truthy(js, args[0]) ? 1 : 0);
jsval_t boolean_proto = js_get_ctor_proto(js, "Boolean", 7);
if (is_unboxed_obj(js, js->this_val, boolean_proto)) {
set_slot(js, js->this_val, SLOT_PRIMITIVE, bval);
}
return bval;
}
static jsval_t builtin_Object(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0 || vtype(args[0]) == T_NULL || vtype(args[0]) == T_UNDEF) {
jsval_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);
}
jsval_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) {
jsval_t wrapper = js_mkobj(js);
if (is_err(wrapper)) return wrapper;
set_slot(js, wrapper, SLOT_PRIMITIVE, arg);
jsval_t proto = get_prototype_for_type(js, t);
if (vtype(proto) == T_OBJ) set_proto(js, wrapper, proto);
return wrapper;
}
return arg;
}
static jsval_t js_eval_inherit_strict(struct js *js, const char *buf, size_t len, bool inherit_strict);
static jsval_t builtin_eval(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_mkundef();
jsval_t code_arg = args[0];
if (vtype(code_arg) != T_STR) return code_arg;
jsoff_t code_len, code_off = vstr(js, code_arg, &code_len);
const char *code_str = (const char *)&js->mem[code_off];
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
uint8_t saved_flags = js->flags;
bool caller_strict = (js->flags & F_STRICT) != 0;
jsval_t result = js_eval_inherit_strict(js, code_str, code_len, caller_strict);
bool had_throw = (js->flags & F_THROW) != 0;
JS_RESTORE_STATE(js, saved);
js->flags = saved_flags;
if (is_err(result) || had_throw) {
js->flags |= F_THROW;
}
return result;
}
static jsval_t builtin_Function(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) {
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) return func_obj;
set_func_code_ptr(js, func_obj, "(){}", 4);
set_slot(js, func_obj, SLOT_SCOPE, js_glob(js));
jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj));
jsval_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++) {
if (vtype(args[i]) != T_STR) {
const char *str = js_str(js, args[i]);
args[i] = js_mkstr(js, str, strlen(str));
if (is_err(args[i])) return args[i];
}
total_len += vstrlen(js, args[i]);
if (i < nargs - 2) total_len += 1;
}
total_len += 2;
jsval_t body = args[nargs - 1];
if (vtype(body) != T_STR) {
const char *str = js_str(js, body);
body = js_mkstr(js, str, strlen(str));
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++) {
jsoff_t param_len, param_off = vstr(js, args[i], &param_len);
memcpy(code_buf + pos, &js->mem[param_off], param_len);
pos += param_len;
if (i < nargs - 2) {
code_buf[pos++] = ',';
}
}
code_buf[pos++] = ')';
code_buf[pos++] = '{';
jsoff_t body_len, body_off = vstr(js, body, &body_len);
memcpy(code_buf + pos, &js->mem[body_off], body_len);
pos += body_len;
code_buf[pos++] = '}';
code_buf[pos] = '\0';
bool is_strict_body = is_strict_function_body((const char *)&js->mem[body_off], body_len);
if (is_strict_body && nargs > 1) {
int i = 0, j;
jsoff_t param_len_i, param_off_i;
const char *param_i;
check_param:
if (i >= nargs - 1) goto params_done;
param_off_i = vstr(js, args[i], &param_len_i);
param_i = (const char *)&js->mem[param_off_i];
if (is_strict_restricted(param_i, param_len_i)) {
free(code_buf);
return js_mkerr_typed(js, JS_ERR_SYNTAX, "cannot use '%.*s' as parameter name in strict mode", (int)param_len_i, param_i);
}
j = i + 1;
check_dup:
if (j >= nargs - 1) { i++; goto check_param; }
jsoff_t param_len_j, param_off_j = vstr(js, args[j], &param_len_j);
const char *param_j = (const char *)&js->mem[param_off_j];
if (param_len_i == param_len_j && memcmp(param_i, param_j, param_len_i) == 0) {
free(code_buf);
return js_mkerr_typed(js, JS_ERR_SYNTAX, "duplicate parameter name '%.*s' in strict mode", (int)param_len_i, param_i);
}
j++;
goto check_dup;
params_done:;
}
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) { free(code_buf); return func_obj; }
set_func_code(js, func_obj, code_buf, pos);
free(code_buf);
set_slot(js, func_obj, SLOT_SCOPE, js_glob(js));
jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj));
jsval_t proto_setup = setup_func_prototype(js, func);
if (is_err(proto_setup)) return proto_setup;
return func;
}
static jsval_t builtin_AsyncFunction(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) {
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) return func_obj;
set_func_code_ptr(js, func_obj, "(){}", 4);
set_slot(js, func_obj, SLOT_SCOPE, js_glob(js));
set_slot(js, func_obj, SLOT_ASYNC, js_true);
jsval_t async_proto = get_slot(js, js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) set_proto(js, func_obj, async_proto);
jsval_t func = mkval(T_FUNC, (unsigned long)vdata(func_obj));
jsval_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++) {
if (vtype(args[i]) != T_STR) {
const char *str = js_str(js, args[i]);
args[i] = js_mkstr(js, str, strlen(str));
if (is_err(args[i])) return args[i];
}
total_len += vstrlen(js, args[i]);
if (i < nargs - 2) total_len += 1;
}
total_len += 2;
jsval_t body = args[nargs - 1];
if (vtype(body) != T_STR) {
const char *str = js_str(js, body);
body = js_mkstr(js, str, strlen(str));
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++) {
jsoff_t param_len, param_off = vstr(js, args[i], &param_len);
memcpy(code_buf + pos, &js->mem[param_off], param_len);
pos += param_len;
if (i < nargs - 2) code_buf[pos++] = ',';
}
code_buf[pos++] = ')';
code_buf[pos++] = '{';
jsoff_t body_len, body_off = vstr(js, body, &body_len);
memcpy(code_buf + pos, &js->mem[body_off], body_len);
pos += body_len;
code_buf[pos++] = '}';
code_buf[pos] = '\0';
jsval_t func_obj = mkobj(js, 0);
if (is_err(func_obj)) { free(code_buf); return func_obj; }
set_func_code(js, func_obj, code_buf, pos);
free(code_buf);
set_slot(js, func_obj, SLOT_SCOPE, js_glob(js));
set_slot(js, func_obj, SLOT_ASYNC, js_true);
jsval_t async_proto = get_slot(js, js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) set_proto(js, func_obj, async_proto);
jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj));
jsval_t proto_setup = setup_func_prototype(js, func);
if (is_err(proto_setup)) return proto_setup;
return func;
}
static jsval_t builtin_function_empty(struct js *js, jsval_t *args, int nargs) {
(void)js; (void)args; (void)nargs;
return js_mkundef();
}
static jsval_t builtin_function_call(struct js *js, jsval_t *args, int nargs) {
jsval_t func = js->this_val;
if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC) {
return js_mkerr(js, "call requires a function");
}
jsval_t this_arg = (nargs > 0) ? args[0] : js_mkundef();
jsval_t *call_args = NULL;
int call_nargs = (nargs > 1) ? nargs - 1 : 0;
if (call_nargs > 0) {
call_args = &args[1];
}
jsval_t saved_this = js->this_val;
push_this(this_arg);
js->this_val = this_arg;
jsval_t result = call_js_with_args(js, func, call_args, call_nargs);
pop_this();
js->this_val = saved_this;
return result;
}
static int extract_array_args(struct js *js, jsval_t arr, jsval_t **out_args) {
int len = (int) get_array_length(js, arr);
if (len <= 0) return 0;
jsval_t *args_out = (jsval_t *)ant_calloc(sizeof(jsval_t) * len);
if (!args_out) return 0;
for (int i = 0; i < len; i++) {
args_out[i] = arr_get(js, arr, (jsoff_t)i);
}
*out_args = args_out;
return len;
}
static jsval_t builtin_function_toString(struct js *js, jsval_t *args, int nargs) {
jsval_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] }");
jsval_t func_obj = mkval(T_OBJ, vdata(func));
jsval_t code_val = get_slot(js, func_obj, SLOT_CODE);
jsval_t len_val = get_slot(js, 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) {
jsval_t async_slot = get_slot(js, func_obj, SLOT_ASYNC);
jsval_t arrow_slot = get_slot(js, func_obj, SLOT_ARROW);
bool is_async = (async_slot == js_true);
bool is_arrow = (arrow_slot == js_true);
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);
jsval_t result = js_mkstr(js, buf, n);
free(buf);
return result;
fallback_arrow:;
}
jsoff_t name_len = 0;
const char *name = get_func_name(js, func, &name_len);
size_t total = (is_async ? 6 : 0) + 9 + name_len + code_len + 1;
char *buf = ant_calloc(total + 1);
size_t n = 0;
if (is_async) n += cpy(buf + n, total - n, "async ", 6);
n += cpy(buf + n, total - n, "function ", 9);
if (name && name_len > 0) n += cpy(buf + n, total - n, name, name_len);
n += cpy(buf + n, total - n, code, code_len);
n += cpy(buf + n, total - n, "}", 1);
jsval_t result = js_mkstr(js, buf, n);
free(buf);
return result;
}
}
char buf[256];
size_t len = strfunc(js, func, buf, sizeof(buf));
return js_mkstr(js, buf, len);
}
static jsval_t builtin_function_apply(struct js *js, jsval_t *args, int nargs) {
jsval_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");
}
jsval_t this_arg = (nargs > 0) ? args[0] : js_mkundef();
jsval_t *call_args = NULL;
int call_nargs = 0;
if (nargs > 1) {
jsval_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) {}
}
jsval_t saved_this = js->this_val;
push_this(this_arg);
js->this_val = this_arg;
jsval_t result = call_js_with_args(js, func, call_args, call_nargs);
pop_this();
js->this_val = saved_this;
if (call_args) free(call_args);
return result;
}
static jsval_t builtin_function_bind(struct js *js, jsval_t *args, int nargs) {
jsval_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");
}
jsval_t this_arg = (nargs > 0) ? args[0] : js_mkundef();
int bound_argc = (nargs > 1) ? nargs - 1 : 0;
jsval_t *bound_args = (bound_argc > 0) ? &args[1] : NULL;
int orig_length = 0;
jsval_t target_func_obj;
if (vtype(func) == T_CFUNC) {
orig_length = 0;
} else {
target_func_obj = mkval(T_OBJ, vdata(func));
jsoff_t len_off = lkp_interned(js, target_func_obj, INTERN_LENGTH, 6);
if (len_off != 0) {
jsval_t len_val = resolveprop(js, mkval(T_PROP, len_off));
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) {
jsval_t bound_func = mkobj(js, 0);
if (is_err(bound_func)) return bound_func;
set_slot(js, bound_func, SLOT_CFUNC, func);
set_slot(js, bound_func, SLOT_BOUND_THIS, this_arg);
if (bound_argc > 0) {
jsval_t bound_arr = mkarr(js);
for (int i = 0; i < bound_argc; i++) arr_set(js, bound_arr, (jsoff_t)i, bound_args[i]);
set_slot(js, bound_func, SLOT_BOUND_ARGS, bound_arr);
}
jsval_t func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) set_proto(js, bound_func, func_proto);
jsval_t bound = mkval(T_FUNC, (unsigned long) vdata(bound_func));
js_setprop(js, bound_func, js->length_str, tov((double) bound_length));
jsval_t proto_setup = setup_func_prototype(js, bound);
if (is_err(proto_setup)) return proto_setup;
return bound;
}
jsval_t func_obj = mkval(T_OBJ, vdata(func));
jsval_t bound_func = mkobj(js, 0);
if (is_err(bound_func)) return bound_func;
jsval_t code_val = get_slot(js, func_obj, SLOT_CODE);
if (vtype(code_val) == T_STR || vtype(code_val) == T_CFUNC) {
set_slot(js, bound_func, SLOT_CODE, code_val);
set_slot(js, bound_func, SLOT_CODE_LEN, get_slot(js, func_obj, SLOT_CODE_LEN));
}
jsval_t cfunc_slot = get_slot(js, func_obj, SLOT_CFUNC);
if (vtype(cfunc_slot) == T_CFUNC) {
set_slot(js, bound_func, SLOT_CFUNC, cfunc_slot);
}
jsval_t scope_slot = get_slot(js, func_obj, SLOT_SCOPE);
if (vtype(scope_slot) != T_UNDEF) {
set_slot(js, bound_func, SLOT_SCOPE, scope_slot);
}
jsval_t async_slot = get_slot(js, func_obj, SLOT_ASYNC);
if (vtype(async_slot) == T_BOOL && vdata(async_slot) == 1) {
set_slot(js, bound_func, SLOT_ASYNC, js_true);
jsval_t async_proto = get_slot(js, js_glob(js), SLOT_ASYNC_PROTO);
if (vtype(async_proto) == T_FUNC) set_proto(js, bound_func, async_proto);
} else {
jsval_t func_proto = get_slot(js, js_glob(js), SLOT_FUNC_PROTO);
if (vtype(func_proto) == T_FUNC) set_proto(js, bound_func, func_proto);
}
jsval_t data_slot = get_slot(js, func_obj, SLOT_DATA);
if (vtype(data_slot) != T_UNDEF) {
set_slot(js, bound_func, SLOT_DATA, data_slot);
}
set_slot(js, bound_func, SLOT_TARGET_FUNC, func);
set_slot(js, bound_func, SLOT_BOUND_THIS, this_arg);
if (bound_argc > 0) {
jsval_t bound_arr = mkarr(js);
for (int i = 0; i < bound_argc; i++) arr_set(js, bound_arr, (jsoff_t)i, bound_args[i]);
set_slot(js, bound_func, SLOT_BOUND_ARGS, bound_arr);
}
js_setprop(js, bound_func, js->length_str, tov((double) bound_length));
jsval_t bound = mkval(T_FUNC, (unsigned long) vdata(bound_func));
jsval_t proto_setup = setup_func_prototype(js, bound);
if (is_err(proto_setup)) return proto_setup;
return bound;
}
static jsval_t builtin_Array(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = mkarr(js);
if (nargs == 1 && vtype(args[0]) == T_NUM) {
jsval_t err = validate_array_length(js, args[0]);
if (is_err(err)) return err;
jsoff_t new_len = (jsoff_t)tod(args[0]);
jsoff_t doff = get_dense_buf(js, arr);
if (doff && new_len <= 1024) {
if (new_len > dense_capacity(js, doff)) doff = dense_grow(js, arr, new_len);
if (doff) dense_set_length(js, doff, new_len);
}
update_array_length(js, arr, new_len);
} else if (nargs > 0) {
for (int i = 0; i < nargs; i++) arr_set(js, arr, (jsoff_t)i, args[i]);
}
return arr;
}
static jsval_t builtin_Error(struct js *js, jsval_t *args, int nargs) {
bool is_new = (vtype(js->new_target) != T_UNDEF);
jsval_t this_val = js->this_val;
jsval_t target = is_new ? js->new_target : js->current_func;
jsval_t name = ANT_STRING("Error");
if (vtype(target) == T_FUNC) {
jsoff_t off = lkp(js, mkval(T_OBJ, vdata(target)), "name", 4);
if (off) name = resolveprop(js, mkval(T_PROP, off));
}
if (!is_new) {
this_val = js_mkobj(js);
jsoff_t proto_off = lkp_interned(js, mkval(T_OBJ, vdata(js->current_func)), INTERN_PROTOTYPE, 9);
if (proto_off) set_proto(js, this_val, resolveprop(js, mkval(T_PROP, proto_off)));
else set_proto(js, this_val, get_ctor_proto(js, "Error", 5));
}
if (nargs > 0) {
jsval_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);
}
js_mkprop_fast(js, this_val, "name", 4, name);
return this_val;
}
static jsval_t builtin_Error_toString(struct js *js, jsval_t *args, int nargs) {
jsval_t this_val = js_getthis(js);
jsval_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));
}
jsval_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));
}
jsoff_t name_len, msg_len;
jsoff_t name_off = vstr(js, name, &name_len);
jsoff_t msg_off = vstr(js, msg, &msg_len);
const char *name_str = (const char *)&js->mem[name_off];
const char *msg_str = (const char *)&js->mem[msg_off];
if (name_len == 0) return msg;
if (msg_len == 0) return name;
size_t total = name_len + 2 + msg_len;
char *buf = malloc(total + 1);
if (!buf) return js_mkerr(js, "out of memory");
memcpy(buf, name_str, name_len);
buf[name_len] = ':'; buf[name_len + 1] = ' ';
memcpy(buf + name_len + 2, msg_str, msg_len);
buf[total] = '\0';
jsval_t result = js_mkstr(js, buf, total);
free(buf); return result;
}
static jsval_t builtin_AggregateError(struct js *js, jsval_t *args, int nargs) {
bool is_new = (vtype(js->new_target) != T_UNDEF);
jsval_t this_val = js->this_val;
if (!is_new) {
this_val = js_mkobj(js);
jsoff_t proto_off = lkp_interned(js, mkval(T_OBJ, vdata(js->current_func)), INTERN_PROTOTYPE, 9);
if (proto_off) set_proto(js, this_val, resolveprop(js, mkval(T_PROP, proto_off)));
else set_proto(js, this_val, get_ctor_proto(js, "AggregateError", 14));
}
jsval_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) {
jsval_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);
}
js_mkprop_fast(js, this_val, "name", 4, ANT_STRING("AggregateError"));
return this_val;
}
static jsval_t builtin_RegExp(struct js *js, jsval_t *args, int nargs) {
jsval_t regexp_obj = js->this_val;
bool use_this = (vtype(regexp_obj) == T_OBJ);
if (!use_this) {
regexp_obj = mkobj(js, 0);
}
jsval_t regexp_proto = get_ctor_proto(js, "RegExp", 6);
if (vtype(regexp_proto) == T_OBJ) set_proto(js, regexp_obj, regexp_proto);
jsval_t pattern = js_mkstr(js, "", 0);
if (nargs > 0) {
if (vtype(args[0]) == T_STR) {
pattern = args[0];
} else {
const char *str = js_str(js, args[0]);
pattern = js_mkstr(js, str, strlen(str));
}
}
jsval_t flags = js_mkstr(js, "", 0);
if (nargs > 1 && vtype(args[1]) == T_STR) {
flags = args[1];
}
jsval_t source_key = js_mkstr(js, "source", 6);
js_setprop(js, regexp_obj, source_key, pattern);
jsval_t flags_key = js_mkstr(js, "flags", 5);
js_setprop(js, regexp_obj, flags_key, flags);
jsoff_t flags_len, flags_off = vstr(js, flags, &flags_len);
const char *flags_str = (char *) &js->mem[flags_off];
bool global = false, ignoreCase = false, multiline = false, dotAll = false, sticky = false;
for (jsoff_t i = 0; i < flags_len; i++) {
if (flags_str[i] == 'g') global = true;
if (flags_str[i] == 'i') ignoreCase = true;
if (flags_str[i] == 'm') multiline = true;
if (flags_str[i] == 's') dotAll = true;
if (flags_str[i] == 'y') sticky = true;
}
js_setprop(js, regexp_obj, js_mkstr(js, "global", 6), mkval(T_BOOL, global ? 1 : 0));
js_setprop(js, regexp_obj, js_mkstr(js, "ignoreCase", 10), mkval(T_BOOL, ignoreCase ? 1 : 0));
js_setprop(js, regexp_obj, js_mkstr(js, "multiline", 9), mkval(T_BOOL, multiline ? 1 : 0));
js_setprop(js, regexp_obj, js_mkstr(js, "dotAll", 6), mkval(T_BOOL, dotAll ? 1 : 0));
js_setprop(js, regexp_obj, js_mkstr(js, "sticky", 6), mkval(T_BOOL, sticky ? 1 : 0));
js_setprop(js, regexp_obj, js_mkstr(js, "lastIndex", 9), tov(0));
return regexp_obj;
}
static jsval_t builtin_regexp_test(struct js *js, jsval_t *args, int nargs) {
jsval_t regexp = js->this_val;
if (vtype(regexp) != T_OBJ) return js_mkerr(js, "test called on non-regexp");
if (nargs < 1) return mkval(T_BOOL, 0);
jsval_t str_arg = args[0];
if (vtype(str_arg) != T_STR) return mkval(T_BOOL, 0);
jsoff_t source_off = lkp(js, regexp, "source", 6);
if (source_off == 0) return mkval(T_BOOL, 0);
jsval_t source_val = resolveprop(js, mkval(T_PROP, source_off));
if (vtype(source_val) != T_STR) return mkval(T_BOOL, 0);
jsoff_t plen, poff = vstr(js, source_val, &plen);
const char *pattern_ptr = (char *) &js->mem[poff];
bool ignore_case = false, multiline = false;
jsoff_t flags_off = lkp(js, regexp, "flags", 5);
if (flags_off != 0) {
jsval_t flags_val = resolveprop(js, mkval(T_PROP, flags_off));
if (vtype(flags_val) == T_STR) {
jsoff_t flen, foff = vstr(js, flags_val, &flen);
const char *flags_str = (char *) &js->mem[foff];
for (jsoff_t i = 0; i < flen; i++) {
if (flags_str[i] == 'i') ignore_case = true;
if (flags_str[i] == 'm') multiline = true;
}
}
}
jsoff_t str_len, str_off = vstr(js, str_arg, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
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;
if (ignore_case) options |= PCRE2_CASELESS;
if (multiline) options |= PCRE2_MULTILINE;
int errcode;
PCRE2_SIZE erroffset;
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pcre2_pattern, pcre2_len, options, &errcode, &erroffset, NULL);
if (re == NULL) return mkval(T_BOOL, 0);
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);
int rc = pcre2_match(re, (PCRE2_SPTR)str_ptr, str_len, 0, 0, match_data, NULL);
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return mkval(T_BOOL, rc >= 0 ? 1 : 0);
}
static jsval_t builtin_regexp_exec(struct js *js, jsval_t *args, int nargs) {
jsval_t regexp = js->this_val;
if (vtype(regexp) != T_OBJ) return js_mkerr(js, "exec called on non-regexp");
if (nargs < 1) return js_mknull();
jsval_t str_arg = args[0];
if (vtype(str_arg) != T_STR) return js_mknull();
jsoff_t source_off = lkp(js, regexp, "source", 6);
if (source_off == 0) return js_mknull();
jsval_t source_val = resolveprop(js, mkval(T_PROP, source_off));
if (vtype(source_val) != T_STR) return js_mknull();
jsoff_t plen, poff = vstr(js, source_val, &plen);
const char *pattern_ptr = (char *) &js->mem[poff];
bool ignore_case = false, multiline = false, global_flag = false;
jsoff_t flags_off = lkp(js, regexp, "flags", 5);
if (flags_off != 0) {
jsval_t flags_val = resolveprop(js, mkval(T_PROP, flags_off));
if (vtype(flags_val) == T_STR) {
jsoff_t flen, foff = vstr(js, flags_val, &flen);
const char *flags_str = (char *) &js->mem[foff];
for (jsoff_t i = 0; i < flen; i++) {
if (flags_str[i] == 'i') ignore_case = true;
if (flags_str[i] == 'm') multiline = true;
if (flags_str[i] == 'g') global_flag = true;
}
}
}
jsoff_t str_len, str_off = vstr(js, str_arg, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
PCRE2_SIZE start_offset = 0;
if (global_flag) {
jsoff_t lastindex_off = lkp(js, regexp, "lastIndex", 9);
if (lastindex_off != 0) {
jsval_t li_val = resolveprop(js, mkval(T_PROP, lastindex_off));
if (vtype(li_val) == T_NUM) {
double li = tod(li_val);
if (li >= 0 && li <= D(str_len)) start_offset = (PCRE2_SIZE)li;
}
}
}
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;
if (ignore_case) options |= PCRE2_CASELESS;
if (multiline) options |= PCRE2_MULTILINE;
int errcode;
PCRE2_SIZE erroffset;
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pcre2_pattern, pcre2_len, options, &errcode, &erroffset, NULL);
if (re == NULL) return js_mknull();
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);
int rc = pcre2_match(re, (PCRE2_SPTR)str_ptr, str_len, start_offset, 0, match_data, NULL);
if (rc < 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
if (global_flag) {
js_setprop(js, regexp, js_mkstr(js, "lastIndex", 9), tov(0));
}
return js_mknull();
}
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
uint32_t ovcount = pcre2_get_ovector_count(match_data);
jsval_t result_arr = js_mkarr(js);
for (uint32_t i = 0; i < ovcount && i < 32; i++) {
PCRE2_SIZE start = ovector[2*i];
PCRE2_SIZE end = ovector[2*i+1];
if (start == PCRE2_UNSET) {
js_arr_push(js, result_arr, js_mkundef());
} else {
jsval_t match_str = js_mkstr(js, str_ptr + start, end - start);
js_arr_push(js, result_arr, match_str);
}
}
js_setprop(js, result_arr, js_mkstr(js, "index", 5), tov((double)ovector[0]));
js_setprop(js, result_arr, js_mkstr(js, "input", 5), str_arg);
if (global_flag) {
PCRE2_SIZE new_lastindex = ovector[1];
if (ovector[0] == ovector[1]) new_lastindex++;
js_setprop(js, regexp, js_mkstr(js, "lastIndex", 9), tov((double)new_lastindex));
}
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return result_arr;
}
static jsval_t builtin_regexp_toString(struct js *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
jsval_t regexp = js->this_val;
if (vtype(regexp) != T_OBJ) return js_mkerr(js, "toString called on non-regexp");
jsoff_t source_off = lkp(js, regexp, "source", 6);
if (source_off == 0) return js_mkstr(js, "/undefined/", 11);
jsval_t source_val = resolveprop(js, mkval(T_PROP, source_off));
if (vtype(source_val) != T_STR) return js_mkstr(js, "/undefined/", 11);
jsoff_t src_len;
jsoff_t src_off = vstr(js, source_val, &src_len);
const char *src_ptr = (const char *)(js->mem + src_off);
char flags[8] = {0};
int fi = 0;
jsoff_t prop_off;
prop_off = lkp(js, regexp, "global", 6);
if (prop_off && vdata(resolveprop(js, mkval(T_PROP, prop_off)))) flags[fi++] = 'g';
prop_off = lkp(js, regexp, "ignoreCase", 10);
if (prop_off && vdata(resolveprop(js, mkval(T_PROP, prop_off)))) flags[fi++] = 'i';
prop_off = lkp(js, regexp, "multiline", 9);
if (prop_off && vdata(resolveprop(js, mkval(T_PROP, prop_off)))) flags[fi++] = 'm';
prop_off = lkp(js, regexp, "dotAll", 6);
if (prop_off && vdata(resolveprop(js, mkval(T_PROP, prop_off)))) flags[fi++] = 's';
prop_off = lkp(js, regexp, "sticky", 6);
if (prop_off && vdata(resolveprop(js, mkval(T_PROP, prop_off)))) flags[fi++] = 'y';
size_t result_len = 1 + src_len + 1 + fi;
char *result = (char *)malloc(result_len + 1);
if (!result) return js_mkerr(js, "out of memory");
result[0] = '/';
memcpy(result + 1, src_ptr, src_len);
result[1 + src_len] = '/';
memcpy(result + 2 + src_len, flags, fi);
result[result_len] = '\0';
jsval_t ret = js_mkstr(js, result, result_len);
free(result);
return ret;
}
static jsval_t builtin_string_search(struct js *js, jsval_t *args, int nargs) {
jsval_t this_unwrapped = unwrap_primitive(js, js->this_val);
jsval_t str = js_tostring_val(js, this_unwrapped);
if (is_err(str)) return str;
if (nargs < 1) return tov(-1);
jsval_t pattern = args[0];
const char *pattern_ptr = NULL;
jsoff_t pattern_len = 0;
bool ignore_case = false, multiline = false;
if (vtype(pattern) == T_OBJ) {
jsoff_t source_off = lkp(js, pattern, "source", 6);
if (source_off == 0) return tov(-1);
jsval_t source_val = resolveprop(js, mkval(T_PROP, source_off));
if (vtype(source_val) != T_STR) return tov(-1);
jsoff_t poff;
poff = vstr(js, source_val, &pattern_len);
pattern_ptr = (char *) &js->mem[poff];
jsoff_t flags_off = lkp(js, pattern, "flags", 5);
if (flags_off != 0) {
jsval_t flags_val = resolveprop(js, mkval(T_PROP, flags_off));
if (vtype(flags_val) == T_STR) {
jsoff_t flen, foff = vstr(js, flags_val, &flen);
const char *flags_str = (char *) &js->mem[foff];
for (jsoff_t i = 0; i < flen; i++) {
if (flags_str[i] == 'i') ignore_case = true;
if (flags_str[i] == 'm') multiline = true;
}
}
}
} else if (vtype(pattern) == T_STR) {
jsoff_t poff;
poff = vstr(js, pattern, &pattern_len);
pattern_ptr = (char *) &js->mem[poff];
} else {
return tov(-1);
}
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
char pcre2_pattern[512];
size_t pcre2_len = js_to_pcre2_pattern(pattern_ptr, pattern_len, pcre2_pattern, sizeof(pcre2_pattern));
uint32_t options = PCRE2_UTF | PCRE2_UCP | PCRE2_MATCH_UNSET_BACKREF;
if (ignore_case) options |= PCRE2_CASELESS;
if (multiline) options |= PCRE2_MULTILINE;
int errcode;
PCRE2_SIZE erroffset;
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pcre2_pattern, pcre2_len, options, &errcode, &erroffset, NULL);
if (re == NULL) return tov(-1);
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);
int rc = pcre2_match(re, (PCRE2_SPTR)str_ptr, str_len, 0, 0, match_data, NULL);
if (rc < 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return tov(-1);
}
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
double result = (double)ovector[0];
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return tov(result);
}
static jsval_t builtin_Date(struct js *js, jsval_t *args, int nargs) {
jsval_t date_obj = js->this_val;
if (vtype(js->new_target) == T_UNDEF) {
struct timeval tv;
gettimeofday(&tv, NULL);
time_t t = tv.tv_sec;
struct tm *tm = localtime(&t);
static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
char buf[64];
snprintf(
buf, sizeof(buf), "%s %s %02d %04d %02d:%02d:%02d GMT%+03ld%02ld",
days[tm->tm_wday], months[tm->tm_mon], tm->tm_mday, tm->tm_year + 1900,
tm->tm_hour, tm->tm_min, tm->tm_sec, -timezone/3600, (labs(timezone)/60)%60
);
return js_mkstr(js, buf, strlen(buf));
}
double timestamp_ms;
if (nargs == 0) {
struct timeval tv;
gettimeofday(&tv, NULL);
// NOLINTNEXTLINE(bugprone-integer-division)
timestamp_ms = (double)tv.tv_sec * 1000.0 + (double)(tv.tv_usec / 1000);
} else if (nargs == 1) {
if (vtype(args[0]) == T_NUM) {
timestamp_ms = tod(args[0]);
} else if (vtype(args[0]) == T_STR) {
timestamp_ms = 0;
} else timestamp_ms = 0;
} else {
int year = (int)tod(args[0]);
int month = nargs >= 2 ? (int)tod(args[1]) : 0;
int day = nargs >= 3 ? (int)tod(args[2]) : 1;
int hour = nargs >= 4 ? (int)tod(args[3]) : 0;
int minute = nargs >= 5 ? (int)tod(args[4]) : 0;
int sec = nargs >= 6 ? (int)tod(args[5]) : 0;
int ms = nargs >= 7 ? (int)tod(args[6]) : 0;
if (year >= 0 && year <= 99) year += 1900;
struct tm tm_val = {0};
tm_val.tm_year = year - 1900;
tm_val.tm_mon = month;
tm_val.tm_mday = day;
tm_val.tm_hour = hour;
tm_val.tm_min = minute;
tm_val.tm_sec = sec;
tm_val.tm_isdst = -1;
time_t t = mktime(&tm_val);
timestamp_ms = (double)t * 1000.0 + ms;
}
js_set_slot(js, date_obj, SLOT_DATA, tov(timestamp_ms));
return date_obj;
}
static jsval_t builtin_Date_now(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
struct timeval tv;
gettimeofday(&tv, NULL);
// NOLINTNEXTLINE(bugprone-integer-division)
double timestamp_ms = (double)tv.tv_sec * 1000.0 + (double)(tv.tv_usec / 1000);
return tov(timestamp_ms);
}
static jsval_t builtin_Date_UTC(struct js *js, jsval_t *args, int nargs) {
(void) js;
if (nargs < 1) return tov(JS_NAN);
int year = (int)tod(args[0]);
int month = nargs >= 2 ? (int)tod(args[1]) : 0;
int day = nargs >= 3 ? (int)tod(args[2]) : 1;
int hour = nargs >= 4 ? (int)tod(args[3]) : 0;
int min = nargs >= 5 ? (int)tod(args[4]) : 0;
int sec = nargs >= 6 ? (int)tod(args[5]) : 0;
int ms = nargs >= 7 ? (int)tod(args[6]) : 0;
if (year >= 0 && year <= 99) year += 1900;
struct tm tm = {0};
tm.tm_year = year - 1900;
tm.tm_mon = month;
tm.tm_mday = day;
tm.tm_hour = hour;
tm.tm_min = min;
tm.tm_sec = sec;
time_t t = timegm(&tm);
return tov((double)t * 1000.0 + ms);
}
static double date_get_time(struct js *js, jsval_t this_val) {
jsval_t time_val = js_get_slot(js, this_val, SLOT_DATA);
if (vtype(time_val) != T_NUM) return JS_NAN;
return tod(time_val);
}
static jsval_t builtin_Date_getTime(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
return tov(date_get_time(js, js->this_val));
}
static jsval_t builtin_Date_getFullYear(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
return tov((double)(tm->tm_year + 1900));
}
static jsval_t builtin_Date_getMonth(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
return tov((double)tm->tm_mon);
}
static jsval_t builtin_Date_getDate(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
return tov((double)tm->tm_mday);
}
static jsval_t builtin_Date_getHours(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
return tov((double)tm->tm_hour);
}
static jsval_t builtin_Date_getMinutes(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
return tov((double)tm->tm_min);
}
static jsval_t builtin_Date_getSeconds(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
return tov((double)tm->tm_sec);
}
static jsval_t builtin_Date_getMilliseconds(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
return tov(fmod(ms, 1000.0));
}
static jsval_t builtin_Date_getDay(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
return tov((double)tm->tm_wday);
}
static jsval_t builtin_Date_toISOString(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid time value");
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
int millis = (int)fmod(ms, 1000.0);
if (millis < 0) millis += 1000;
char buf[32];
snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec, millis);
return js_mkstr(js, buf, strlen(buf));
}
static jsval_t builtin_Date_toString(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return js_mkstr(js, "Invalid Date", 12);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm_local = localtime(&t);
if (!tm_local) return js_mkstr(js, "Invalid Date", 12);
struct tm local_copy = *tm_local;
struct tm *gm = gmtime(&t);
long offset_sec = (long)difftime(mktime(&local_copy), mktime(gm));
int offset_hours = (int)(offset_sec / 3600);
int offset_mins = (int)(labs(offset_sec) % 3600) / 60;
char tz[32], buf[80];
strftime(tz, sizeof(tz), "%Z", &local_copy);
strftime(buf, sizeof(buf), "%a %b %d %Y %H:%M:%S", &local_copy);
size_t n = strlen(buf);
n += snprintf(buf + n, sizeof(buf) - n, " GMT%+03d%02d (%s)", offset_hours, offset_mins, tz);
return js_mkstr(js, buf, n);
}
static jsval_t builtin_Date_valueOf(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
return tov(date_get_time(js, js->this_val));
}
static jsval_t builtin_Date_getTimezoneOffset(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *local = localtime(&t);
struct tm *utc = gmtime(&t);
int diff = (local->tm_hour - utc->tm_hour) * 60 + (local->tm_min - utc->tm_min);
if (local->tm_mday != utc->tm_mday) {
diff += (local->tm_mday > utc->tm_mday || (local->tm_mday == 1 && utc->tm_mday > 1)) ? 1440 : -1440;
}
return tov((double)(-diff));
}
static jsval_t builtin_Date_getUTCFullYear(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
return tov((double)(tm->tm_year + 1900));
}
static jsval_t builtin_Date_getUTCMonth(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
return tov((double)tm->tm_mon);
}
static jsval_t builtin_Date_getUTCDate(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
return tov((double)tm->tm_mday);
}
static jsval_t builtin_Date_getUTCHours(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
return tov((double)tm->tm_hour);
}
static jsval_t builtin_Date_getUTCMinutes(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
return tov((double)tm->tm_min);
}
static jsval_t builtin_Date_getUTCSeconds(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
return tov((double)tm->tm_sec);
}
static jsval_t builtin_Date_getUTCMilliseconds(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
return tov(fmod(ms, 1000.0));
}
static jsval_t builtin_Date_getUTCDay(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
return tov((double)tm->tm_wday);
}
static void date_set_time(struct js *js, jsval_t date, double ms) {
if (vtype(date) != T_OBJ) return;
js_set_slot(js, date, SLOT_DATA, tov(ms));
}
static jsval_t builtin_Date_setTime(struct js *js, jsval_t *args, int nargs) {
double ms = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setMilliseconds(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double newMs = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(newMs)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
ms = floor(ms / 1000.0) * 1000.0 + newMs;
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setSeconds(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double sec = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(sec)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
tm->tm_sec = (int)sec;
if (nargs >= 2) ms = floor(ms / 1000.0) * 1000.0 + tod(args[1]);
else ms = floor(ms / 1000.0) * 1000.0 + fmod(ms, 1000.0);
time_t newt = mktime(tm);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setMinutes(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double min = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(min)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
tm->tm_min = (int)min;
if (nargs >= 2) tm->tm_sec = (int)tod(args[1]);
time_t newt = mktime(tm);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setHours(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double hour = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(hour)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
tm->tm_hour = (int)hour;
if (nargs >= 2) tm->tm_min = (int)tod(args[1]);
if (nargs >= 3) tm->tm_sec = (int)tod(args[2]);
time_t newt = mktime(tm);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setDate(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double day = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(day)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
tm->tm_mday = (int)day;
time_t newt = mktime(tm);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setMonth(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double mon = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(mon)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
tm->tm_mon = (int)mon;
if (nargs >= 2) tm->tm_mday = (int)tod(args[1]);
time_t newt = mktime(tm);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setFullYear(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double year = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(year)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
if (isnan(ms)) ms = 0;
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
tm->tm_year = (int)year - 1900;
if (nargs >= 2) tm->tm_mon = (int)tod(args[1]);
if (nargs >= 3) tm->tm_mday = (int)tod(args[2]);
time_t newt = mktime(tm);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setUTCMilliseconds(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double newMs = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(newMs)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
ms = floor(ms / 1000.0) * 1000.0 + newMs;
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setUTCSeconds(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double sec = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(sec)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
struct tm copy = *tm;
copy.tm_sec = (int)sec;
time_t newt = timegm(&copy);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setUTCMinutes(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double min = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(min)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
struct tm copy = *tm;
copy.tm_min = (int)min;
if (nargs >= 2) copy.tm_sec = (int)tod(args[1]);
time_t newt = timegm(&copy);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setUTCHours(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double hour = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(hour)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
struct tm copy = *tm;
copy.tm_hour = (int)hour;
if (nargs >= 2) copy.tm_min = (int)tod(args[1]);
if (nargs >= 3) copy.tm_sec = (int)tod(args[2]);
time_t newt = timegm(&copy);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setUTCDate(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double day = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(day)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
struct tm copy = *tm;
copy.tm_mday = (int)day;
time_t newt = timegm(&copy);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setUTCMonth(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double mon = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(ms) || isnan(mon)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
struct tm copy = *tm;
copy.tm_mon = (int)mon;
if (nargs >= 2) copy.tm_mday = (int)tod(args[1]);
time_t newt = timegm(&copy);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_setUTCFullYear(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
double year = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(year)) { date_set_time(js, js->this_val, JS_NAN); return tov(JS_NAN); }
if (isnan(ms)) ms = 0;
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
struct tm copy = *tm;
copy.tm_year = (int)year - 1900;
if (nargs >= 2) copy.tm_mon = (int)tod(args[1]);
if (nargs >= 3) copy.tm_mday = (int)tod(args[2]);
time_t newt = timegm(&copy);
ms = (double)newt * 1000.0 + fmod(ms, 1000.0);
date_set_time(js, js->this_val, ms);
return tov(ms);
}
static jsval_t builtin_Date_toUTCString(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return js_mkstr(js, "Invalid Date", 12);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = gmtime(&t);
static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
char buf[64];
snprintf(buf, sizeof(buf), "%s, %02d %s %04d %02d:%02d:%02d GMT",
days[tm->tm_wday], tm->tm_mday, months[tm->tm_mon],
tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec);
return js_mkstr(js, buf, strlen(buf));
}
static jsval_t builtin_Date_toDateString(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return js_mkstr(js, "Invalid Date", 12);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
char buf[32];
snprintf(buf, sizeof(buf), "%s %s %02d %04d",
days[tm->tm_wday], months[tm->tm_mon], tm->tm_mday, tm->tm_year + 1900);
return js_mkstr(js, buf, strlen(buf));
}
static jsval_t builtin_Date_toTimeString(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return js_mkstr(js, "Invalid Date", 12);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
char buf[32];
int offset = (int)(-timezone / 60);
int offset_hours = offset / 60;
int offset_mins = abs(offset % 60);
snprintf(buf, sizeof(buf), "%02d:%02d:%02d GMT%+03d%02d",
tm->tm_hour, tm->tm_min, tm->tm_sec, offset_hours, offset_mins);
return js_mkstr(js, buf, strlen(buf));
}
static jsval_t builtin_Date_toLocaleDateString(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return js_mkstr(js, "Invalid Date", 12);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
char buf[32];
snprintf(buf, sizeof(buf), "%d/%d/%04d", tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900);
return js_mkstr(js, buf, strlen(buf));
}
static jsval_t builtin_Date_toLocaleTimeString(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return js_mkstr(js, "Invalid Date", 12);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
char buf[16];
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
return js_mkstr(js, buf, strlen(buf));
}
static jsval_t builtin_Date_getYear(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return tov(JS_NAN);
time_t t = (time_t)(ms / 1000.0);
struct tm *tm = localtime(&t);
return tov((double)(tm->tm_year));
}
static jsval_t builtin_Date_setYear(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) {
date_set_time(js, js->this_val, JS_NAN);
return tov(JS_NAN);
}
double year_arg = tod(args[0]);
if (isnan(year_arg)) {
date_set_time(js, js->this_val, JS_NAN);
return tov(JS_NAN);
}
int year = (int)year_arg;
if (year >= 0 && year <= 99) year += 1900;
double ms = date_get_time(js, js->this_val);
time_t t;
struct tm tm_val;
if (isnan(ms)) {
t = 0;
tm_val = *localtime(&t);
tm_val.tm_mday = 1;
tm_val.tm_mon = 0;
tm_val.tm_hour = 0;
tm_val.tm_min = 0;
tm_val.tm_sec = 0;
} else {
t = (time_t)(ms / 1000.0);
tm_val = *localtime(&t);
}
tm_val.tm_year = year - 1900;
time_t new_t = mktime(&tm_val);
double new_ms = (double)new_t * 1000.0;
date_set_time(js, js->this_val, new_ms);
return tov(new_ms);
}
static jsval_t builtin_Date_toJSON(struct js *js, jsval_t *args, int nargs) {
double ms = date_get_time(js, js->this_val);
if (isnan(ms)) return js_mknull();
return builtin_Date_toISOString(js, args, nargs);
}
static jsval_t builtin_Math_abs(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(fabs(x));
}
static jsval_t builtin_Math_acos(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(acos(x));
}
static jsval_t builtin_Math_acosh(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(acosh(x));
}
static jsval_t builtin_Math_asin(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(asin(x));
}
static jsval_t builtin_Math_asinh(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(asinh(x));
}
static jsval_t builtin_Math_atan(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(atan(x));
}
static jsval_t builtin_Math_atanh(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(atanh(x));
}
static jsval_t builtin_Math_atan2(struct js *js, jsval_t *args, int nargs) {
double y = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
double x = (nargs < 2) ? JS_NAN : js_to_number(js, args[1]);
if (isnan(y) || isnan(x)) return tov(JS_NAN);
return tov(atan2(y, x));
}
static jsval_t builtin_Math_cbrt(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(cbrt(x));
}
static jsval_t builtin_Math_ceil(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(ceil(x));
}
static jsval_t builtin_Math_clz32(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return tov(32);
double x = js_to_number(js, args[0]);
if (isnan(x) || isinf(x)) return tov(32);
uint32_t n = (uint32_t) x;
if (n == 0) return tov(32);
int count = 0;
while ((n & 0x80000000U) == 0) { count++; n <<= 1; }
return tov((double) count);
}
static jsval_t builtin_Math_cos(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(cos(x));
}
static jsval_t builtin_Math_cosh(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(cosh(x));
}
static jsval_t builtin_Math_exp(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(exp(x));
}
static jsval_t builtin_Math_expm1(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(expm1(x));
}
static jsval_t builtin_Math_floor(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(floor(x));
}
static jsval_t builtin_Math_fround(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov((double)(float)x);
}
static jsval_t builtin_Math_hypot(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return tov(0.0);
double sum = 0.0;
for (int i = 0; i < nargs; i++) {
double v = js_to_number(js, args[i]);
if (isnan(v)) return tov(JS_NAN);
sum += v * v;
}
return tov(sqrt(sum));
}
static int32_t toInt32(double d) {
if (isnan(d) || isinf(d) || d == 0) return 0;
double int_val = trunc(d);
double two32 = (double)(1ULL << 32);
double two31 = (double)(1ULL << 31);
double mod_val = fmod(int_val, two32);
if (mod_val < 0) mod_val += two32;
if (mod_val >= two31) mod_val -= two32;
return (int32_t)mod_val;
}
static jsval_t builtin_Math_imul(struct js *js, jsval_t *args, int nargs) {
if (nargs < 2) return tov(0);
int32_t a = toInt32(js_to_number(js, args[0]));
int32_t b = toInt32(js_to_number(js, args[1]));
return tov((double)((int32_t)((uint32_t)a * (uint32_t)b)));
}
static jsval_t builtin_Math_log(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(log(x));
}
static jsval_t builtin_Math_log1p(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(log1p(x));
}
static jsval_t builtin_Math_log10(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(log10(x));
}
static jsval_t builtin_Math_log2(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(log2(x));
}
static jsval_t builtin_Math_max(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return tov(JS_NEG_INF);
double max_val = JS_NEG_INF;
for (int i = 0; i < nargs; i++) {
double v = js_to_number(js, args[i]);
if (isnan(v)) return tov(JS_NAN);
if (v > max_val) max_val = v;
}
return tov(max_val);
}
static jsval_t builtin_Math_min(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return tov(JS_INF);
double min_val = JS_INF;
for (int i = 0; i < nargs; i++) {
double v = js_to_number(js, args[i]);
if (isnan(v)) return tov(JS_NAN);
if (v < min_val) min_val = v;
}
return tov(min_val);
}
static jsval_t builtin_Math_pow(struct js *js, jsval_t *args, int nargs) {
double base = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
double exp = (nargs < 2) ? JS_NAN : js_to_number(js, args[1]);
if (isnan(base) || isnan(exp)) return tov(JS_NAN);
return tov(pow(base, exp));
}
static bool random_seeded = false;
static jsval_t builtin_Math_random(struct js *js, jsval_t *args, int nargs) {
if (!random_seeded) {
srand((unsigned int) time(NULL));
random_seeded = true;
}
return tov((double) rand() / ((double) RAND_MAX + 1.0));
}
static jsval_t builtin_Math_round(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x) || isinf(x)) return tov(x);
return tov(floor(x + 0.5));
}
static jsval_t builtin_Math_sign(struct js *js, jsval_t *args, int nargs) {
double v = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(v)) return tov(JS_NAN);
if (v > 0) return tov(1.0);
if (v < 0) return tov(-1.0);
return tov(v);
}
static jsval_t builtin_Math_sin(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(sin(x));
}
static jsval_t builtin_Math_sinh(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(sinh(x));
}
static jsval_t builtin_Math_sqrt(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(sqrt(x));
}
static jsval_t builtin_Math_tan(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(tan(x));
}
static jsval_t builtin_Math_tanh(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(tanh(x));
}
static jsval_t builtin_Math_trunc(struct js *js, jsval_t *args, int nargs) {
double x = (nargs < 1) ? JS_NAN : js_to_number(js, args[0]);
if (isnan(x)) return tov(JS_NAN);
return tov(trunc(x));
}
typedef jsval_t (*dynamic_kv_mapper_fn)(
struct js *js,
jsval_t key,
jsval_t val
);
static jsval_t iterate_dynamic_keys(struct js *js, jsval_t obj, dynamic_accessors_t *acc, dynamic_kv_mapper_fn mapper) {
jsval_t keys_arr = acc->keys(js, obj);
jsval_t arr = mkarr(js);
jsoff_t len = get_array_length(js, keys_arr);
for (jsoff_t i = 0; i < len; i++) {
jsval_t key_val = arr_get(js, keys_arr, i);
if (vtype(key_val) != T_STR) continue;
jsoff_t klen; jsoff_t str_off = vstr(js, key_val, &klen);
const char *key = (const char *)&js->mem[str_off];
jsval_t val = acc->getter(js, obj, key, klen);
js_arr_push(js, arr, mapper ? mapper(js, key_val, val) : val);
}
return mkval(T_ARR, vdata(arr));
}
static jsval_t builtin_object_is(struct js *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_false;
jsval_t x = args[0];
jsval_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 x == y ? js_true : js_false;
}
enum obj_enum_mode {
OBJ_ENUM_KEYS,
OBJ_ENUM_VALUES,
OBJ_ENUM_ENTRIES
};
static jsval_t map_to_entry(struct js *js, jsval_t key, jsval_t val) {
jsval_t pair = mkarr(js);
arr_set(js, pair, 0, key);
arr_set(js, pair, 1, val);
return mkval(T_ARR, vdata(pair));
}
static jsval_t object_enum(struct js *js, jsval_t obj, enum obj_enum_mode mode) {
bool is_arr = (vtype(obj) == T_ARR);
if (vtype(obj) == T_FUNC) obj = mkval(T_OBJ, vdata(obj));
jsoff_t obj_off = (jsoff_t)vdata(obj);
dynamic_accessors_t *acc = NULL;
HASH_FIND(hh, accessor_registry, &obj_off, sizeof(jsoff_t), acc);
if (acc && acc->keys) {
if (mode == OBJ_ENUM_KEYS && !acc->getter) return acc->keys(js, obj);
if (acc->getter) {
dynamic_kv_mapper_fn mapper = (mode == OBJ_ENUM_ENTRIES) ? map_to_entry : NULL;
return iterate_dynamic_keys(js, obj, acc, mapper);
}
}
jsval_t arr = mkarr(js);
jsoff_t idx = 0;
if (is_arr) {
jsoff_t doff = get_dense_buf_off(js, obj_off);
if (doff) {
jsoff_t dense_len = dense_length(js, doff);
for (jsoff_t i = 0; i < dense_len; i++) {
jsval_t v = dense_get(js, doff, i);
if (is_empty_slot(v)) continue;
char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)i);
jsval_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++;
}
}
}
jsoff_t next = loadoff(js, obj_off) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (is_slot_prop(header)) { next = next_prop(header); continue; }
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *) &js->mem[koff + sizeof(koff)];
jsval_t val = loadval(js, next + (jsoff_t) (sizeof(next) + sizeof(koff)));
next = next_prop(header);
if (is_internal_prop(key, klen)) continue;
if (is_arr && is_array_index(key, klen)) {
jsoff_t doff = get_dense_buf_off(js, obj_off);
if (doff) {
unsigned long pidx = 0;
for (jsoff_t ci = 0; ci < klen; ci++) pidx = pidx * 10 + (key[ci] - '0');
if (pidx < dense_length(js, doff)) continue;
}
}
bool should_include = true;
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, key, klen);
if (desc) should_include = desc->enumerable;
if (should_include) {
jsval_t key_val = js_mkstr(js, key, 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++;
}
}
if (mode == OBJ_ENUM_KEYS) {
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;
jsval_t key_val = js_mkstr(js, desc->prop_name, desc->prop_len);
arr_set(js, arr, idx, key_val);
idx++;
}
}
return mkval(T_ARR, vdata(arr));
}
static jsval_t builtin_object_keys(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
jsval_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_KEYS);
}
static jsval_t builtin_object_values(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
jsval_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 jsval_t builtin_object_entries(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
jsval_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 jsval_t builtin_object_getPrototypeOf(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_mkerr(js, "Object.getPrototypeOf requires an argument");
jsval_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 jsval_t builtin_object_setPrototypeOf(struct js *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "Object.setPrototypeOf requires 2 arguments");
jsval_t obj = args[0];
jsval_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");
}
for (jsval_t cur = proto; pt != T_NULL && vtype(cur) != T_NULL; cur = get_proto(js, cur)) {
if (vdata(cur) == vdata(obj)) return js_mkerr(js, "Cyclic __proto__ value");
}
set_proto(js, obj, proto);
return obj;
}
static jsval_t builtin_proto_getter(struct js *js, jsval_t *args, int nargs) {
jsval_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 jsval_t builtin_proto_setter(struct js *js, jsval_t *args, int nargs) {
jsval_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();
jsval_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();
}
for (jsval_t cur = proto; pt != T_NULL && vtype(cur) == T_OBJ; cur = get_proto(js, cur)) {
if (vdata(cur) == vdata(this_val)) return js_mkundef();
}
set_proto(js, this_val, proto);
return js_mkundef();
}
static jsval_t builtin_object_create(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_mkerr(js, "Object.create requires a prototype argument");
jsval_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");
}
jsval_t obj = js_mkobj(js);
if (pt == T_NULL) {
set_proto(js, obj, js_mknull());
} else set_proto(js, obj, proto);
if (nargs >= 2 && vtype(args[1]) == T_OBJ) {
jsval_t props = args[1];
jsoff_t next = loadoff(js, (jsoff_t) vdata(props)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (is_slot_prop(header)) { next = next_prop(header); continue; }
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *) &js->mem[koff + sizeof(koff)];
jsval_t descriptor = resolveprop(js, mkval(T_PROP, next));
if (vtype(descriptor) == T_OBJ) {
jsoff_t val_off = lkp(js, descriptor, "value", 5);
if (val_off != 0) {
jsval_t val = resolveprop(js, mkval(T_PROP, val_off));
jsval_t key_str = js_mkstr(js, key, klen);
js_setprop(js, obj, key_str, val);
}
}
next = next_prop(header);
}
}
return obj;
}
static jsval_t builtin_object_hasOwn(struct js *js, jsval_t *args, int nargs) {
if (nargs < 2) return mkval(T_BOOL, 0);
jsval_t obj = args[0];
jsval_t key = args[1];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return mkval(T_BOOL, 0);
if (vtype(key) != T_STR) return mkval(T_BOOL, 0);
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
jsoff_t key_len, key_off = vstr(js, key, &key_len);
const char *key_str = (char *) &js->mem[key_off];
jsoff_t off = lkp(js, as_obj, key_str, key_len);
return mkval(T_BOOL, off != 0 ? 1 : 0);
}
static jsval_t builtin_object_defineProperty(struct js *js, jsval_t *args, int nargs) {
if (nargs < 3) return js_mkerr(js, "Object.defineProperty requires 3 arguments");
jsval_t obj = args[0];
jsval_t prop = args[1];
jsval_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");
}
if (vtype(prop) == T_SYMBOL) {
char keybuf[64];
sym_to_prop_key(prop, keybuf, sizeof(keybuf));
prop = js_mkstr(js, keybuf, strlen(keybuf));
} else if (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");
}
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
jsoff_t prop_len, prop_off = vstr(js, prop, &prop_len);
const char *prop_str = (char *) &js->mem[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, has_writable = false;
jsval_t value = js_mkundef();
bool writable = true, enumerable = false, configurable = false;
jsoff_t value_off = lkp(js, descriptor, "value", 5);
if (value_off != 0) {
has_value = true;
value = resolveprop(js, mkval(T_PROP, value_off));
}
jsoff_t get_off = lkp_interned(js, descriptor, INTERN_GET, 3);
if (get_off != 0) {
has_get = true;
jsval_t getter = resolveprop(js, mkval(T_PROP, get_off));
if (vtype(getter) != T_FUNC && vtype(getter) != T_UNDEF) {
return js_mkerr(js, "Getter must be a function");
}
}
jsoff_t set_off = lkp_interned(js, descriptor, INTERN_SET, 3);
if (set_off != 0) {
has_set = true;
jsval_t setter = resolveprop(js, mkval(T_PROP, set_off));
if (vtype(setter) != T_FUNC && vtype(setter) != T_UNDEF) {
return js_mkerr(js, "Setter must be a function");
}
}
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");
}
jsoff_t writable_off = lkp(js, descriptor, "writable", 8);
if (writable_off != 0) {
has_writable = true;
jsval_t w_val = resolveprop(js, mkval(T_PROP, writable_off));
writable = js_truthy(js, w_val);
}
jsoff_t enumerable_off = lkp(js, descriptor, "enumerable", 10);
if (enumerable_off != 0) {
jsval_t e_val = resolveprop(js, mkval(T_PROP, enumerable_off));
enumerable = js_truthy(js, e_val);
}
jsoff_t configurable_off = lkp(js, descriptor, "configurable", 12);
if (configurable_off != 0) {
jsval_t c_val = resolveprop(js, mkval(T_PROP, configurable_off));
configurable = js_truthy(js, c_val);
}
jsoff_t existing_off = lkp(js, as_obj, prop_str, prop_len);
if (existing_off == 0) {
if (js_truthy(js, get_slot(js, as_obj, SLOT_FROZEN)))
return js_mkerr(js, "Cannot define property %.*s, object is not extensible", (int)prop_len, prop_str);
if (js_truthy(js, get_slot(js, as_obj, SLOT_SEALED)))
return js_mkerr(js, "Cannot define property %.*s, object is not extensible", (int)prop_len, prop_str);
if (get_slot(js, as_obj, SLOT_EXTENSIBLE) == js_false)
return js_mkerr(js, "Cannot define property %.*s, object is not extensible", (int)prop_len, prop_str);
}
if (has_get || has_set) {
int desc_flags =
(enumerable ? JS_DESC_E : 0) |
(configurable ? JS_DESC_C : 0);
if (has_get && has_set) {
jsval_t getter = resolveprop(js, mkval(T_PROP, get_off));
jsval_t setter = resolveprop(js, mkval(T_PROP, set_off));
js_set_accessor_desc(js, as_obj, prop_str, prop_len, getter, setter, desc_flags);
} else if (has_get) {
jsval_t getter = resolveprop(js, mkval(T_PROP, get_off));
js_set_getter_desc(js, as_obj, prop_str, prop_len, getter, desc_flags);
} else {
jsval_t setter = resolveprop(js, mkval(T_PROP, set_off));
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);
js_set_descriptor(js, as_obj, prop_str, prop_len, desc_flags);
if (existing_off > 0) {
bool is_frozen = js_truthy(js, get_slot(js, as_obj, SLOT_FROZEN));
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_readonly && has_value) return js_mkerr(js, "Cannot assign to read-only property '%.*s'", (int)prop_len, prop_str);
if (has_value) saveval(js, existing_off + sizeof(jsoff_t) * 2, value);
if (!writable || !configurable) {
jsoff_t head = (jsoff_t) vdata(as_obj);
jsoff_t firstprop = loadoff(js, head);
if ((firstprop & ~(3U | FLAGMASK)) == existing_off) {
jsoff_t flags = 0;
if (!writable) flags |= CONSTMASK;
if (!configurable) flags |= NONCONFIGMASK;
saveoff(js, head, firstprop | flags);
} else {
jsoff_t prop_header = loadoff(js, existing_off);
jsoff_t flags = 0;
if (!writable) flags |= CONSTMASK;
if (!configurable) flags |= NONCONFIGMASK;
saveoff(js, existing_off, prop_header | flags);
}
}
} else {
if (!has_value) value = js_mkundef();
jsval_t prop_key = js_mkstr(js, prop_str, prop_len);
jsoff_t flags = (writable ? 0 : CONSTMASK) | (configurable ? 0 : NONCONFIGMASK);
mkprop(js, as_obj, prop_key, value, flags);
}
}
return obj;
}
static jsval_t builtin_object_defineProperties(struct js *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "Object.defineProperties requires 2 arguments");
jsval_t obj = args[0];
jsval_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");
}
jsval_t props_obj = props;
jsoff_t next = loadoff(js, (jsoff_t) vdata(props_obj)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (is_slot_prop(header)) { next = next_prop(header); continue; }
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *) &js->mem[koff + sizeof(koff)];
jsval_t descriptor = loadval(js, next + (jsoff_t) (sizeof(next) + sizeof(koff)));
next = next_prop(header);
if (is_internal_prop(key, klen)) continue;
jsval_t prop_key = js_mkstr(js, key, klen);
jsval_t define_args[3] = { obj, prop_key, descriptor };
jsval_t result = builtin_object_defineProperty(js, define_args, 3);
if (is_err(result)) return result;
}
return obj;
}
static jsval_t builtin_object_assign(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_mkerr(js, "Object.assign requires at least 1 argument");
jsval_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);
}
jsval_t as_obj = (vtype(target) == T_OBJ) ? target : mkval(T_OBJ, vdata(target));
for (int i = 1; i < nargs; i++) {
jsval_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;
jsval_t src_obj = (st == T_OBJ) ? source : mkval(T_OBJ, vdata(source));
jsoff_t next = loadoff(js, (jsoff_t) vdata(src_obj)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (is_slot_prop(header)) { next = next_prop(header); continue; }
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *) &js->mem[koff + sizeof(koff)];
jsval_t val = loadval(js, next + (jsoff_t) (sizeof(next) + sizeof(koff)));
next = next_prop(header);
if (is_internal_prop(key, klen)) continue;
bool should_copy = true;
jsoff_t src_obj_off = (jsoff_t)vdata(src_obj);
descriptor_entry_t *desc = lookup_descriptor(js, src_obj_off, key, klen);
if (desc) should_copy = desc->enumerable;
if (should_copy) {
jsval_t key_str = js_mkstr(js, key, klen);
js_setprop(js, as_obj, key_str, val);
}
}
}
return target;
}
static jsval_t builtin_object_freeze(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_mkundef();
jsval_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return obj;
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
jsoff_t next = loadoff(js, (jsoff_t) vdata(as_obj)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (is_slot_prop(header)) { next = next_prop(header); continue; }
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *) &js->mem[koff + sizeof(koff)];
jsoff_t cur_prop = next;
next = next_prop(header);
if (is_internal_prop(key, klen)) continue;
jsoff_t freeze_flags = CONSTMASK | NONCONFIGMASK;
jsoff_t head = (jsoff_t) vdata(as_obj);
jsoff_t firstprop = loadoff(js, head);
if ((firstprop & ~(3U | FLAGMASK)) == cur_prop) {
saveoff(js, head, firstprop | freeze_flags);
} else {
jsoff_t prop_header = loadoff(js, cur_prop);
saveoff(js, cur_prop, prop_header | freeze_flags);
}
js_set_descriptor(js, as_obj, key, klen, JS_DESC_E);
}
set_slot(js, as_obj, SLOT_FROZEN, js_true);
return obj;
}
static jsval_t builtin_object_isFrozen(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_true;
jsval_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_true;
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
return js_bool(js_truthy(js, get_slot(js, as_obj, SLOT_FROZEN)));
}
static jsval_t builtin_object_seal(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_mkundef();
jsval_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return obj;
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
set_slot(js, as_obj, SLOT_SEALED, js_true);
jsoff_t next = loadoff(js, (jsoff_t) vdata(as_obj)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (is_slot_prop(header)) { next = next_prop(header); continue; }
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *) &js->mem[koff + sizeof(koff)];
jsoff_t cur_prop = next;
next = next_prop(header);
if (is_internal_prop(key, klen)) continue;
jsoff_t head = (jsoff_t) vdata(as_obj);
jsoff_t firstprop = loadoff(js, head);
if ((firstprop & ~(3U | FLAGMASK)) == cur_prop) {
saveoff(js, head, firstprop | NONCONFIGMASK);
} else {
jsoff_t prop_header = loadoff(js, cur_prop);
saveoff(js, cur_prop, prop_header | NONCONFIGMASK);
}
js_set_descriptor(js, as_obj, key, klen, JS_DESC_W | JS_DESC_E);
}
return obj;
}
static jsval_t builtin_object_isSealed(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_true;
jsval_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_true;
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
if (js_truthy(js, get_slot(js, as_obj, SLOT_SEALED))) return js_true;
if (js_truthy(js, get_slot(js, as_obj, SLOT_FROZEN))) return js_true;
return js_false;
}
static jsval_t builtin_object_fromEntries(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_mkerr(js, "Object.fromEntries requires an iterable argument");
jsval_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");
}
jsval_t result = js_mkobj(js);
jsoff_t len = get_array_length(js, iterable);
if (len == 0) return result;
for (jsoff_t i = 0; i < len; i++) {
jsval_t entry = arr_get(js, iterable, i);
if (vtype(entry) != T_ARR && vtype(entry) != T_OBJ) continue;
jsval_t key = arr_get(js, entry, 0);
if (is_undefined(key)) continue;
jsval_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 jsval_t builtin_object_getOwnPropertyDescriptor(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
jsval_t obj = args[0];
jsval_t key = args[1];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_mkundef();
char sym_buf[32];
const char *key_str;
jsoff_t key_len;
if (vtype(key) == T_SYMBOL) {
sym_to_prop_key(key, sym_buf, sizeof(sym_buf));
key_str = sym_buf;
key_len = (jsoff_t)strlen(sym_buf);
} else if (vtype(key) == T_STR) {
jsoff_t key_off = vstr(js, key, &key_len);
key_str = (char *) &js->mem[key_off];
} else {
char buf[64];
size_t n = tostr(js, key, buf, sizeof(buf));
key = js_mkstr(js, buf, n);
jsoff_t key_off = vstr(js, key, &key_len);
key_str = (char *) &js->mem[key_off];
}
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
jsoff_t obj_off = (jsoff_t)vdata(as_obj);
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, key_str, key_len);
jsoff_t prop_off = lkp(js, as_obj, key_str, key_len);
if (prop_off == 0 && !desc) {
return js_mkundef();
}
jsval_t result = js_mkobj(js);
if (desc && (desc->has_getter || desc->has_setter)) {
if (desc->has_getter) {
js_setprop(js, result, js_mkstr(js, "get", 3), desc->getter);
}
if (desc->has_setter) {
js_setprop(js, result, js_mkstr(js, "set", 3), desc->setter);
}
js_setprop(js, result, js_mkstr(js, "enumerable", 10), js_bool(desc->enumerable));
js_setprop(js, result, js_mkstr(js, "configurable", 12), js_bool(desc->configurable));
} else {
if (prop_off != 0) {
jsval_t prop_val = resolveprop(js, mkval(T_PROP, prop_off));
js_setprop(js, result, js_mkstr(js, "value", 5), prop_val);
}
js_setprop(js, result, js_mkstr(js, "writable", 8), desc ? (js_bool(desc->writable)) : js_true);
js_setprop(js, result, js_mkstr(js, "enumerable", 10), desc ? (js_bool(desc->enumerable)) : js_true);
js_setprop(js, result, js_mkstr(js, "configurable", 12), desc ? (js_bool(desc->configurable)) : js_true);
}
return result;
return js_mkundef();
}
static jsval_t builtin_object_getOwnPropertyNames(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
jsval_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 = mkval(T_OBJ, vdata(obj));
jsoff_t count = 0;
jsoff_t next = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (!is_slot_prop(header)) {
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *) &js->mem[koff + sizeof(koff)];
if (!is_internal_prop(key, klen)) count++;
}
next = next_prop(header);
}
if (count == 0) return mkarr(js);
bool is_arr_obj = (vtype(obj) == T_ARR);
jsval_t arr = mkarr(js);
jsoff_t idx = 0;
if (is_arr_obj) {
jsoff_t doff = get_dense_buf_off(js, (jsoff_t)vdata(obj));
if (doff) {
jsoff_t dense_len = dense_length(js, doff);
for (jsoff_t i = 0; i < dense_len; i++) {
jsval_t v = dense_get(js, 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));
}
}
}
next = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (!is_slot_prop(header)) {
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *) &js->mem[koff + sizeof(koff)];
if (!is_internal_prop(key, klen)) {
if (is_arr_obj && is_array_index(key, klen)) {
jsoff_t doff = get_dense_buf_off(js, (jsoff_t)vdata(obj));
if (doff) {
unsigned long pidx = 0;
for (jsoff_t ci = 0; ci < klen; ci++) pidx = pidx * 10 + (key[ci] - '0');
if (pidx < dense_length(js, doff)) continue;
}
} arr_set(js, arr, idx++, js_mkstr(js, key, klen));
}
} next = next_prop(header);
}
if (is_arr_obj) arr_set(js, arr, idx++, js->length_str);
return mkval(T_ARR, vdata(arr));
}
// refactor with desc when SLOT_SYMBOL
static jsval_t builtin_object_getOwnPropertySymbols(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
jsval_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 = mkval(T_OBJ, vdata(obj));
jsval_t arr = mkarr(js); jsoff_t idx = 0;
jsoff_t next = loadoff(js, (jsoff_t)vdata(obj)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (!is_slot_prop(header)) {
jsoff_t koff = loadoff(js, next + (jsoff_t)sizeof(next));
jsoff_t klen = offtolen(loadoff(js, koff));
const char *key = (char *)&js->mem[koff + sizeof(koff)];
if (is_symbol_key(key, klen)) {
uint64_t sym_id = 0;
for (const char *p = key + 6; *p >= '0' && *p <= '9' && sym_id <= PROPREF_PAYLOAD / 10; p++)
sym_id = sym_id * 10 + (uint64_t)(*p - '0');
arr_set(js, arr, idx++, mkval(T_SYMBOL, (sym_id & PROPREF_PAYLOAD) << PROPREF_KEY_SHIFT));
}
}
next = next_prop(header);
}
return mkval(T_ARR, vdata(arr));
}
static jsval_t builtin_object_isExtensible(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_true;
jsval_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return js_true;
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
if (js_truthy(js, get_slot(js, as_obj, SLOT_FROZEN))) return js_false;
if (js_truthy(js, get_slot(js, as_obj, SLOT_SEALED))) return js_false;
jsval_t ext_slot = get_slot(js, as_obj, SLOT_EXTENSIBLE);
if (vtype(ext_slot) != T_UNDEF) return js_bool(js_truthy(js, ext_slot));
return js_true;
}
static jsval_t builtin_object_preventExtensions(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return js_mkundef();
jsval_t obj = args[0];
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return obj;
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
set_slot(js, as_obj, SLOT_EXTENSIBLE, js_false);
return obj;
}
static jsval_t builtin_object_hasOwnProperty(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return mkval(T_BOOL, 0);
jsval_t obj = js->this_val;
jsval_t key = args[0];
obj = resolveprop(js, obj);
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return mkval(T_BOOL, 0);
if (vtype(key) != T_STR) {
char buf[64];
size_t n = tostr(js, key, buf, sizeof(buf));
key = js_mkstr(js, buf, n);
}
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
jsoff_t key_len, key_off = vstr(js, key, &key_len);
const char *key_str = (char *) &js->mem[key_off];
jsoff_t off = lkp(js, as_obj, key_str, key_len);
return mkval(T_BOOL, off != 0 ? 1 : 0);
}
static jsval_t builtin_object_isPrototypeOf(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return mkval(T_BOOL, 0);
jsval_t proto_obj = resolveprop(js, js->this_val);
jsval_t obj = args[0];
uint8_t obj_type = vtype(obj);
if (obj_type != T_OBJ && obj_type != T_ARR && obj_type != T_FUNC) return mkval(T_BOOL, 0);
uint8_t proto_type = vtype(proto_obj);
if (proto_type != T_OBJ && proto_type != T_ARR && proto_type != T_FUNC) return mkval(T_BOOL, 0);
jsoff_t proto_data = (jsoff_t)vdata(proto_obj);
jsval_t current = get_proto(js, obj);
while (!is_undefined(current) && !is_null(current)) {
uint8_t cur_type = vtype(current);
if (cur_type != T_OBJ && cur_type != T_ARR && cur_type != T_FUNC) break;
if (vdata(current) == proto_data) return mkval(T_BOOL, 1);
current = get_proto(js, current);
}
return mkval(T_BOOL, 0);
}
static jsval_t builtin_object_propertyIsEnumerable(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return mkval(T_BOOL, 0);
jsval_t obj = js->this_val;
jsval_t key = args[0];
obj = resolveprop(js, obj);
uint8_t t = vtype(obj);
if (t != T_OBJ && t != T_ARR && t != T_FUNC) return mkval(T_BOOL, 0);
if (vtype(key) != T_STR) {
char buf[64];
size_t n = tostr(js, key, buf, sizeof(buf));
key = js_mkstr(js, buf, n);
}
jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj));
jsoff_t key_len, key_off = vstr(js, key, &key_len);
const char *key_str = (char *) &js->mem[key_off];
if (t == T_ARR && streq(key_str, key_len, "length", 6)) {
return mkval(T_BOOL, 0);
}
if (t == T_ARR) {
jsoff_t doff = get_dense_buf(js, obj);
if (doff) {
unsigned long idx;
if (parse_array_index(key_str, key_len, dense_length(js, doff), &idx)) {
return mkval(T_BOOL, !is_empty_slot(dense_get(js, doff, (jsoff_t)idx)) ? 1 : 0);
}
}
}
jsoff_t off = lkp(js, as_obj, key_str, key_len);
if (off == 0) return mkval(T_BOOL, 0);
jsoff_t obj_off = (jsoff_t)vdata(as_obj);
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, key_str, key_len);
if (desc) {
return mkval(T_BOOL, desc->enumerable ? 1 : 0);
}
return mkval(T_BOOL, 1);
}
static jsval_t builtin_object_toString(struct js *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
jsval_t obj = js->this_val;
obj = resolveprop(js, obj);
uint8_t t = vtype(obj);
if (is_object_type(obj)) {
jsval_t check_obj = (t != T_OBJ) ? mkval(T_OBJ, vdata(obj)) : obj;
const char *tostr_tag_key = get_toStringTag_sym_key();
jsoff_t tag_off = lkp(js, check_obj, tostr_tag_key, strlen(tostr_tag_key));
if (tag_off == 0) tag_off = lkp_proto(js, check_obj, tostr_tag_key, strlen(tostr_tag_key));
if (tag_off != 0) {
jsval_t tag_val = resolveprop(js, mkval(T_PROP, tag_off));
if (vtype(tag_val) == T_STR) {
jsoff_t tag_len, tag_str_off = vstr(js, tag_val, &tag_len);
const char *tag_str = (const char *)&js->mem[tag_str_off];
char buf[256];
int n = snprintf(buf, sizeof(buf), "[object %.*s]", (int)tag_len, tag_str);
return js_mkstr(js, buf, n);
}
}
}
const char *type_name = NULL;
switch (t) {
case T_UNDEF: type_name = "Undefined"; break;
case T_NULL: type_name = "Null"; break;
case T_BOOL: type_name = "Boolean"; break;
case T_NUM: type_name = "Number"; break;
case T_STR: type_name = "String"; break;
case T_ARR: type_name = "Array"; break;
case T_FUNC: type_name = "Function"; break;
case T_ERR: type_name = "Error"; break;
case T_BIGINT: type_name = "BigInt"; break;
case T_PROMISE: type_name = "Promise"; break;
case T_OBJ: type_name = "Object"; break;
default: type_name = "Unknown"; break;
}
char buf[256];
int n = snprintf(buf, sizeof(buf), "[object %s]", type_name);
return js_mkstr(js, buf, n);
}
static jsval_t builtin_object_valueOf(struct js *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
return js->this_val;
}
static jsval_t builtin_object_toLocaleString(struct js *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
return js_call_toString(js, js->this_val);
}
static inline bool is_callable(jsval_t v) {
uint8_t t = vtype(v);
return t == T_FUNC || t == T_CFUNC;
}
static inline jsval_t require_callback(struct js *js, jsval_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 jsval_t array_shallow_copy(struct js *js, jsval_t arr, jsoff_t len) {
jsval_t result = mkarr(js);
if (is_err(result)) return result;
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
for (jsoff_t i = 0; i < len; i++) {
jsval_t v = dense_get(js, 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;
jsval_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);
js_mkprop_fast(js, result, "length", 6, tov((double)len));
return result;
}
static jsval_t builtin_array_push(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
arr = resolveprop(js, arr);
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "push called on non-array");
}
if (is_proxy(js, arr)) {
jsoff_t off = lkp_interned(js, arr, INTERN_LENGTH, 6);
jsoff_t len = 0;
if (off != 0) {
jsval_t len_val = resolveprop(js, mkval(T_PROP, off));
if (vtype(len_val) == T_NUM) len = (jsoff_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);
jsval_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, arr, key, args[i]);
len++;
}
jsval_t len_val = tov((double) len);
js_setprop(js, arr, js->length_str, len_val);
return len_val;
}
jsoff_t len = get_array_length(js, arr);
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
for (int i = 0; i < nargs; i++) {
jsoff_t cap = dense_capacity(js, 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++;
dense_set_length(js, doff, len);
}
return tov((double) len);
}
jsoff_t off = lkp_interned(js, arr, INTERN_LENGTH, 6);
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++;
}
jsval_t new_len = tov((double) len);
if (off != 0) saveval(js, off + sizeof(jsoff_t) * 2, new_len);
else js_mkprop_fast(js, arr, "length", 6, new_len);
return new_len;
}
void js_arr_push(struct js *js, jsval_t arr, jsval_t val) {
arr = resolveprop(js, arr);
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) return;
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
jsoff_t len = dense_length(js, doff);
jsoff_t cap = dense_capacity(js, doff);
if (len >= cap) {
doff = dense_grow(js, arr, len + 1);
if (doff == 0) return;
}
dense_set(js, doff, len, val);
dense_set_length(js, doff, len + 1);
return;
}
jsoff_t len_off = lkp_interned(js, arr, INTERN_LENGTH, 6);
jsoff_t len = 0;
if (len_off != 0) {
jsval_t len_val = resolveprop(js, mkval(T_PROP, len_off));
if (vtype(len_val) == T_NUM) len = (jsoff_t) tod(len_val);
}
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len);
js_mkprop_fast(js, arr, idxstr, idxlen, val);
if (len_off != 0) saveval(js, len_off + sizeof(jsoff_t) * 2, tov((double)(len + 1)));
else js_mkprop_fast(js, arr, "length", 6, tov((double)(len + 1)));
}
static jsval_t builtin_array_pop(struct js *js, jsval_t *args, int nargs) {
jsval_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(js, arr)) {
jsoff_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);
jsval_t result = proxy_aware_get_elem(js, arr, idxstr, idxlen);
js_setprop(js, arr, js->length_str, tov((double) len));
js->needs_gc = true;
return result;
}
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
jsoff_t len = dense_length(js, doff);
if (len == 0) return js_mkundef();
len--;
jsval_t result = dense_get(js, doff, len);
if (is_empty_slot(result)) result = js_mkundef();
dense_set(js, doff, len, T_EMPTY);
dense_set_length(js, doff, len);
return result;
}
jsoff_t off = lkp_interned(js, arr, INTERN_LENGTH, 6);
jsoff_t len = 0;
if (off != 0) {
jsval_t len_val = resolveprop(js, mkval(T_PROP, off));
if (vtype(len_val) == T_NUM) len = (jsoff_t) tod(len_val);
}
if (len == 0) return js_mkundef();
len--; char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)len);
jsoff_t arr_off = (jsoff_t)vdata(arr);
jsoff_t tail = loadoff(js, arr_off + sizeof(jsoff_t) * 2);
jsoff_t elem_off = 0;
if (tail != 0 && tail < js->brk) {
jsoff_t tail_hdr = loadoff(js, tail);
if ((tail_hdr & SLOTMASK) == 0) {
jsoff_t tail_koff = loadoff(js, tail + sizeof(jsoff_t));
jsoff_t tail_klen = offtolen(loadoff(js, tail_koff));
const char *tail_key = (char *)&js->mem[tail_koff + sizeof(jsoff_t)];
if (tail_klen == idxlen && memcmp(tail_key, idxstr, idxlen) == 0) elem_off = tail;
}
}
if (elem_off == 0) elem_off = lkp(js, arr, idxstr, idxlen);
jsval_t result = js_mkundef();
if (elem_off != 0) result = resolveprop(js, mkval(T_PROP, elem_off));
if (off != 0) {
saveval(js, off + sizeof(jsoff_t) * 2, tov((double) len));
} else js_setprop(js, arr, js->length_str, tov((double) len));
js->needs_gc = true;
return result;
}
static jsval_t builtin_array_slice(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "slice called on non-array");
}
jsoff_t len = get_array_length(js, arr);
jsoff_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 = (jsoff_t) (d + dlen < 0 ? 0 : d + dlen);
} else start = (jsoff_t) (d > dlen ? dlen : d);
}
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double d = tod(args[1]);
if (d < 0) {
end = (jsoff_t) (d + dlen < 0 ? 0 : d + dlen);
} else {
end = (jsoff_t) (d > dlen ? dlen : d);
}
}
if (start > end) start = end;
jsval_t result = mkarr(js);
if (is_err(result)) return result;
jsoff_t result_idx = 0;
for (jsoff_t i = start; i < end; i++) {
jsval_t elem = arr_get(js, arr, i);
arr_set(js, result, result_idx, elem);
result_idx++;
}
return mkval(T_ARR, vdata(result));
}
static jsval_t builtin_array_join(struct js *js, jsval_t *args, int nargs) {
jsval_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 = ",";
jsoff_t sep_len = 1;
if (nargs >= 1) {
if (vtype(args[0]) == T_STR) {
sep_len = 0;
jsoff_t sep_off = vstr(js, args[0], &sep_len);
sep = (const char *) &js->mem[sep_off];
} else if (vtype(args[0]) != T_UNDEF) {
const char *sep_str = js_str(js, args[0]);
sep = sep_str;
sep_len = (jsoff_t) strlen(sep_str);
}
}
jsoff_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 (jsoff_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;
}
{
jsval_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];
jsval_t str_val = js_mkundef();
if (et == T_STR) {
jsoff_t soff, slen;
soff = vstr(js, elem, &slen);
elem_str = (const char *)&js->mem[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) {
jsoff_t soff, slen;
soff = vstr(js, str_val, &slen);
elem_str = (const char *)&js->mem[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;
}
}
}
jsval_t ret = js_mkstr(js, result, result_len);
free(result); return ret;
}
static jsval_t builtin_array_includes(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsval_t search = args[0];
jsoff_t len = get_array_length(js, arr);
if (len == 0) return mkval(T_BOOL, 0);
jsoff_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 = (jsoff_t) s;
}
for (jsoff_t i = start; i < len; i++) {
jsval_t val = arr_get(js, arr, i);
if (vtype(val) == vtype(search)) {
bool match = false;
if (vtype(val) == T_NUM) {
if (isnan(tod(val)) && isnan(tod(search))) match = true;
else if (tod(val) == tod(search)) match = true;
}
else if (vtype(val) == T_BOOL && vdata(val) == vdata(search)) match = true;
else if (vtype(val) == T_STR) {
jsoff_t vl, vo = vstr(js, val, &vl);
jsoff_t sl, so = vstr(js, search, &sl);
if (vl == sl && memcmp(&js->mem[vo], &js->mem[so], vl) == 0) match = true;
}
else if ((vtype(val) == T_OBJ || vtype(val) == T_ARR || vtype(val) == T_FUNC) && vdata(val) == vdata(search)) match = true;
if (match) return mkval(T_BOOL, 1);
}
else if (vtype(search) == T_UNDEF && vtype(val) == T_UNDEF) return mkval(T_BOOL, 1);
else if (vtype(search) == T_NULL && vtype(val) == T_NULL) return mkval(T_BOOL, 1);
}
return mkval(T_BOOL, 0);
}
static jsval_t builtin_array_every(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "every called on non-array");
jsval_t callback = require_callback(js, args, nargs, "every");
if (is_err(callback)) return callback;
jsoff_t len = get_array_length(js, arr);
for (jsoff_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
jsval_t val = arr_get(js, arr, i);
jsval_t call_args[3] = { val, tov((double)i), arr };
jsval_t result = call_js_with_args(js, callback, call_args, 3);
if (is_err(result)) return result;
if (!js_truthy(js, result)) return mkval(T_BOOL, 0);
}
return mkval(T_BOOL, 1);
}
static jsval_t builtin_array_forEach(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "forEach called on non-array");
jsval_t callback = require_callback(js, args, nargs, "forEach");
if (is_err(callback)) return callback;
jsoff_t len = get_array_length(js, arr);
for (jsoff_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
jsval_t val = arr_get(js, arr, i);
jsval_t call_args[3] = { val, tov((double)i), arr };
jsval_t result = call_js_with_args(js, callback, call_args, 3);
if (is_err(result)) return result;
}
return js_mkundef();
}
static jsval_t builtin_array_reverse(struct js *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
jsval_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(js, arr)) {
jsoff_t len = proxy_aware_length(js, arr);
if (len <= 1) return arr;
jsval_t read_from = proxy_read_target(js, arr);
jsoff_t lower = 0;
while (lower < len / 2) {
jsoff_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);
jsval_t lower_val = lower_exists ? arr_get(js, read_from, lower) : js_mkundef();
jsval_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;
}
jsoff_t len = get_array_length(js, arr);
if (len <= 1) return arr;
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
for (jsoff_t i = 0; i < len / 2; i++) {
jsval_t a = dense_get(js, doff, i);
jsval_t b = dense_get(js, doff, len - 1 - i);
dense_set(js, doff, i, b);
dense_set(js, doff, len - 1 - i, a);
}
return arr;
}
jsval_t *vals = malloc(len * sizeof(jsval_t));
jsoff_t *offs = malloc(len * sizeof(jsoff_t));
if (!vals || !offs) { free(vals); free(offs); return js_mkerr(js, "out of memory"); }
jsoff_t count = 0;
ant_iter_t iter = js_prop_iter_begin(js, arr);
const char *key;
size_t key_len;
jsval_t val;
while (js_prop_iter_next(&iter, &key, &key_len, &val)) {
unsigned long parsed_idx;
if (!parse_array_index(key, key_len, len, &parsed_idx) || count >= len) continue;
vals[count] = val;
offs[count] = iter.off;
count++;
}
js_prop_iter_end(&iter);
for (jsoff_t i = 0; i < count / 2; i++) {
jsval_t tmp = vals[i];
vals[i] = vals[count - 1 - i];
vals[count - 1 - i] = tmp;
}
for (jsoff_t i = 0; i < count; i++) {
saveval(js, offs[i] + sizeof(jsoff_t) * 2, vals[i]);
}
free(vals);
free(offs);
return arr;
}
static jsval_t builtin_array_map(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "map called on non-array");
jsval_t callback = require_callback(js, args, nargs, "map");
if (is_err(callback)) return callback;
jsoff_t len = get_array_length(js, arr);
jsval_t result = mkarr(js);
if (is_err(result)) return result;
for (jsoff_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
jsval_t val = arr_get(js, arr, i);
jsval_t call_args[3] = { val, tov((double)i), arr };
jsval_t mapped = call_js_with_args(js, callback, call_args, 3);
if (is_err(mapped)) return mapped;
arr_set(js, result, i, mapped);
}
return mkval(T_ARR, vdata(result));
}
static jsval_t builtin_array_filter(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "filter called on non-array");
jsval_t callback = require_callback(js, args, nargs, "filter");
if (is_err(callback)) return callback;
jsoff_t len = get_array_length(js, arr);
jsval_t result = mkarr(js);
if (is_err(result)) return result;
jsoff_t result_idx = 0;
for (jsoff_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
jsval_t val = arr_get(js, arr, i);
jsval_t call_args[3] = { val, tov((double)i), arr };
jsval_t test = call_js_with_args(js, callback, call_args, 3);
if (is_err(test)) return test;
if (js_truthy(js, test)) {
arr_set(js, result, result_idx, val);
result_idx++;
}
}
return mkval(T_ARR, vdata(result));
}
static jsval_t builtin_array_reduce(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "reduce called on non-array");
jsval_t callback = require_callback(js, args, nargs, "reduce");
if (is_err(callback)) return callback;
bool has_initial = (nargs >= 2);
jsoff_t len = get_array_length(js, arr);
jsval_t accumulator = has_initial ? args[1] : js_mkundef();
bool first = !has_initial;
for (jsoff_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
jsval_t val = arr_get(js, arr, i);
if (first) { accumulator = val; first = false; continue; }
jsval_t call_args[4] = { accumulator, val, tov((double)i), arr };
accumulator = call_js_with_args(js, callback, call_args, 4);
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(struct js *js, jsval_t arr, jsval_t result, jsoff_t *result_idx, int depth) {
jsoff_t len = get_array_length(js, arr);
if (len == 0) return;
for (jsoff_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
jsval_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 jsval_t builtin_array_flat(struct js *js, jsval_t *args, int nargs) {
jsval_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;
}
jsval_t result = mkarr(js);
if (is_err(result)) return result;
jsoff_t result_idx = 0;
flat_helper(js, arr, result, &result_idx, depth);
return mkval(T_ARR, vdata(result));
}
static jsval_t builtin_array_concat(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "concat called on non-array");
}
jsval_t result = mkarr(js);
if (is_err(result)) return result;
jsoff_t result_idx = 0;
jsoff_t len = get_array_length(js, arr);
for (jsoff_t i = 0; i < len; i++) {
jsval_t elem = arr_get(js, arr, i);
arr_set(js, result, result_idx, elem);
result_idx++;
}
for (int a = 0; a < nargs; a++) {
jsval_t arg = args[a];
if (vtype(arg) == T_ARR || vtype(arg) == T_OBJ) {
jsoff_t arg_len = get_array_length(js, arg);
for (jsoff_t i = 0; i < arg_len; i++) {
jsval_t elem = arr_get(js, arg, i);
arr_set(js, result, result_idx, elem);
result_idx++;
}
} else {
arr_set(js, result, result_idx, arg);
result_idx++;
}
}
return mkval(T_ARR, vdata(result));
}
static jsval_t builtin_array_at(struct js *js, jsval_t *args, int nargs) {
jsval_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();
jsoff_t len = get_array_length(js, arr);
int idx = (int) tod(args[0]);
if (idx < 0) idx = (int)len + idx;
if (idx < 0 || (jsoff_t)idx >= len) return js_mkundef();
return arr_get(js, arr, (jsoff_t)idx);
}
static jsval_t builtin_array_fill(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "fill called on non-array");
}
jsval_t value = nargs >= 1 ? args[0] : js_mkundef();
jsoff_t len = proxy_aware_length(js, arr);
jsoff_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 = (jsoff_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 = (jsoff_t) e;
}
if (start > len) start = len;
if (end > len) end = len;
for (jsoff_t i = start; i < end; i++) {
arr_set(js, arr, i, value);
}
return arr;
}
static jsval_t array_find_impl(struct js *js, jsval_t *args, int nargs, bool return_index, const char *name) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "%s called on non-array", name);
jsval_t callback = require_callback(js, args, nargs, name);
if (is_err(callback)) return callback;
jsoff_t len = get_array_length(js, arr);
if (len == 0) return return_index ? tov(-1) : js_mkundef();
for (jsoff_t i = 0; i < len; i++) {
jsval_t val = arr_get(js, arr, i);
jsval_t call_args[3] = { val, tov((double)i), arr };
jsval_t result = call_js_with_args(js, callback, call_args, 3);
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 jsval_t builtin_array_find(struct js *js, jsval_t *args, int nargs) {
return array_find_impl(js, args, nargs, false, "find");
}
static jsval_t builtin_array_findIndex(struct js *js, jsval_t *args, int nargs) {
return array_find_impl(js, args, nargs, true, "findIndex");
}
static jsval_t array_find_last_impl(struct js *js, jsval_t *args, int nargs, bool return_index, const char *name) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "%s called on non-array", name);
jsval_t callback = require_callback(js, args, nargs, name);
if (is_err(callback)) return callback;
jsoff_t len = get_array_length(js, arr);
if (len == 0) return return_index ? tov(-1) : js_mkundef();
for (jsoff_t i = len; i > 0; i--) {
jsval_t val = arr_get(js, arr, i - 1);
jsval_t call_args[3] = { val, tov((double)(i - 1)), arr };
jsval_t result = call_js_with_args(js, callback, call_args, 3);
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 jsval_t builtin_array_findLast(struct js *js, jsval_t *args, int nargs) {
return array_find_last_impl(js, args, nargs, false, "findLast");
}
static jsval_t builtin_array_findLastIndex(struct js *js, jsval_t *args, int nargs) {
return array_find_last_impl(js, args, nargs, true, "findLastIndex");
}
static jsval_t builtin_array_flatMap(struct js *js, jsval_t *args, int nargs) {
jsval_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");
}
jsval_t callback = args[0];
jsoff_t len = get_array_length(js, arr);
jsval_t result = mkarr(js);
if (is_err(result)) return result;
jsoff_t result_idx = 0;
for (jsoff_t i = 0; i < len; i++) {
jsval_t elem = arr_get(js, arr, i);
jsval_t call_args[3] = { elem, tov((double)i), arr };
jsval_t mapped = call_js_with_args(js, callback, call_args, 3);
if (is_err(mapped)) return mapped;
if (vtype(mapped) == T_ARR || vtype(mapped) == T_OBJ) {
jsoff_t m_len = get_array_length(js, mapped);
for (jsoff_t j = 0; j < m_len; j++) {
jsval_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(struct js *js, jsval_t v) {
if (vtype(v) == T_STR) {
jsoff_t slen, off = vstr(js, v, &slen);
return (const char *)&js->mem[off];
}
return js_str(js, v);
}
static int js_compare_values(struct js *js, jsval_t a, jsval_t b, jsval_t compareFn) {
uint8_t t = vtype(compareFn);
if (t == T_FUNC || t == T_CFUNC) {
jsval_t call_args[2] = { a, b };
jsval_t result = call_js_with_args(js, compareFn, call_args, 2);
if (vtype(result) == T_NUM) return (int)tod(result);
return 0;
}
if (vtype(a) == T_STR && vtype(b) == T_STR) {
jsoff_t len_a, len_b;
const char *sa = (const char *)&js->mem[vstr(js, a, &len_a)];
const char *sb = (const char *)&js->mem[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 jsval_t builtin_array_indexOf(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsval_t search = args[0];
jsoff_t len = get_array_length(js, arr);
jsoff_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 = (jsoff_t) s;
}
for (jsoff_t i = start; i < len; i++) {
jsval_t elem = arr_get(js, arr, i);
if (vtype(elem) == T_UNDEF && !arr_has(js, arr, i)) continue;
if (vtype(elem) == vtype(search)) {
if (vtype(elem) == T_NUM && tod(elem) == tod(search)) return tov((double)i);
if (vtype(elem) == T_BOOL && vdata(elem) == vdata(search)) return tov((double)i);
if (vtype(elem) == T_STR) {
jsoff_t el, eo = vstr(js, elem, &el);
jsoff_t sl, so = vstr(js, search, &sl);
if (el == sl && memcmp(&js->mem[eo], &js->mem[so], el) == 0) return tov((double)i);
}
if ((vtype(elem) == T_OBJ || vtype(elem) == T_ARR || vtype(elem) == T_FUNC) && vdata(elem) == vdata(search)) return tov((double)i);
}
}
return tov(-1);
}
static jsval_t builtin_array_lastIndexOf(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsval_t search = args[0];
jsoff_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--) {
jsval_t elem = arr_get(js, arr, (jsoff_t)i);
if (vtype(elem) == T_UNDEF && !arr_has(js, arr, (jsoff_t)i)) continue;
if (vtype(elem) == vtype(search)) {
if (vtype(elem) == T_NUM && tod(elem) == tod(search)) return tov((double)i);
if (vtype(elem) == T_BOOL && vdata(elem) == vdata(search)) return tov((double)i);
if (vtype(elem) == T_STR) {
jsoff_t el, eo = vstr(js, elem, &el);
jsoff_t sl, so = vstr(js, search, &sl);
if (el == sl && memcmp(&js->mem[eo], &js->mem[so], el) == 0) return tov((double)i);
}
if ((vtype(elem) == T_OBJ || vtype(elem) == T_ARR || vtype(elem) == T_FUNC) && vdata(elem) == vdata(search)) return tov((double)i);
}
}
return tov(-1);
}
static jsval_t builtin_array_reduceRight(struct js *js, jsval_t *args, int nargs) {
jsval_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");
}
jsval_t callback = args[0];
jsoff_t len = get_array_length(js, arr);
int start_idx = (int)len - 1;
jsval_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--) {
jsval_t elem = arr_get(js, arr, (jsoff_t)i);
jsval_t call_args[4] = { accumulator, elem, tov((double)i), arr };
accumulator = call_js_with_args(js, callback, call_args, 4);
if (is_err(accumulator)) return accumulator;
}
return accumulator;
}
static jsval_t builtin_array_shift(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "shift called on non-array");
}
jsoff_t len = proxy_aware_length(js, arr);
if (len == 0) return js_mkundef();
jsoff_t doff = get_dense_buf(js, arr);
if (doff && !is_proxy(js, arr)) {
jsoff_t d_len = dense_length(js, doff);
if (d_len == 0) return js_mkundef();
jsval_t first = dense_get(js, doff, 0);
if (is_empty_slot(first)) first = js_mkundef();
memmove(&js->mem[doff + sizeof(jsoff_t) * 2],
&js->mem[doff + sizeof(jsoff_t) * 2 + sizeof(jsval_t)],
sizeof(jsval_t) * (d_len - 1));
dense_set(js, doff, d_len - 1, T_EMPTY);
dense_set_length(js, doff, d_len - 1);
return first;
}
jsval_t read_from = is_proxy(js, arr) ? proxy_read_target(js, arr) : arr;
jsval_t first = arr_get(js, read_from, 0);
for (jsoff_t i = 1; i < len; i++) {
if (arr_has(js, read_from, i)) {
jsval_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);
}
}
js_setprop(js, arr, js->length_str, tov((double)(len - 1)));
js->needs_gc = true;
return first;
}
static jsval_t builtin_array_unshift(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "unshift called on non-array");
}
jsoff_t len = proxy_aware_length(js, arr);
jsoff_t doff = get_dense_buf(js, arr);
if (doff && !is_proxy(js, arr)) {
jsoff_t d_len = dense_length(js, doff);
jsoff_t new_len = d_len + nargs;
jsoff_t cap = dense_capacity(js, doff);
if (new_len > cap) {
doff = dense_grow(js, arr, new_len);
if (doff == 0) return js_mkerr(js, "oom");
}
memmove(&js->mem[doff + sizeof(jsoff_t) * 2 + sizeof(jsval_t) * nargs],
&js->mem[doff + sizeof(jsoff_t) * 2],
sizeof(jsval_t) * d_len);
for (int i = 0; i < nargs; i++)
dense_set(js, doff, (jsoff_t)i, args[i]);
dense_set_length(js, doff, new_len);
return tov((double) new_len);
}
jsval_t read_from = is_proxy(js, arr) ? proxy_read_target(js, arr) : arr;
for (int i = (int)len - 1; i >= 0; i--) {
if (arr_has(js, read_from, (jsoff_t)i)) {
jsval_t elem = arr_get(js, read_from, (jsoff_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);
jsval_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, arr, key, args[i]);
}
jsoff_t new_len = len + nargs;
js_setprop(js, arr, js->length_str, tov((double) new_len));
return tov((double) new_len);
}
static jsval_t builtin_array_some(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "some called on non-array");
jsval_t callback = require_callback(js, args, nargs, "some");
if (is_err(callback)) return callback;
jsoff_t len = get_array_length(js, arr);
if (len == 0) return mkval(T_BOOL, 0);
for (jsoff_t i = 0; i < len; i++) {
if (!arr_has(js, arr, i)) continue;
jsval_t val = arr_get(js, arr, i);
jsval_t call_args[3] = { val, tov((double)i), arr };
jsval_t result = call_js_with_args(js, callback, call_args, 3);
if (is_err(result)) return result;
if (js_truthy(js, result)) return mkval(T_BOOL, 1);
}
return mkval(T_BOOL, 0);
}
static jsval_t builtin_array_sort(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
jsval_t compareFn = js_mkundef();
jsval_t *vals = NULL, *keys = NULL, *temp_vals = NULL, *temp_keys = NULL;
jsoff_t *offs = NULL;
jsoff_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;
jsoff_t doff = get_dense_buf(js, arr);
if (doff) {
vals = malloc(len * sizeof(jsval_t));
if (!vals) goto oom;
for (jsoff_t i = 0; i < len; i++) {
jsval_t v = dense_get(js, doff, i);
if (is_empty_slot(v) || vtype(v) == T_UNDEF) undef_count++;
else vals[count++] = v;
}
} else {
vals = malloc(len * sizeof(jsval_t));
offs = malloc(len * sizeof(jsoff_t));
if (!vals || !offs) goto oom;
ant_iter_t iter = js_prop_iter_begin(js, arr);
const char *key;
size_t key_len;
jsval_t val;
while (js_prop_iter_next(&iter, &key, &key_len, &val)) {
if (key_len == 0 || key[0] > '9' || key[0] < '0') continue;
unsigned idx = 0;
bool valid = true;
for (size_t i = 0; i < key_len && valid; i++) {
if (key[i] < '0' || key[i] > '9') valid = false;
else idx = idx * 10 + (key[i] - '0');
}
if (!valid || idx >= len || (count + undef_count) >= len) continue;
offs[count + undef_count] = iter.off;
if (vtype(val) == T_UNDEF) undef_count++;
else vals[count++] = val;
}
js_prop_iter_end(&iter);
}
if (count <= 1) goto writeback;
bool use_keys = (vtype(compareFn) == T_UNDEF);
if (use_keys) {
keys = malloc(count * sizeof(jsval_t));
if (!keys) goto oom;
for (jsoff_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(jsval_t));
if (use_keys) temp_keys = malloc(count * sizeof(jsval_t));
if (!temp_vals || (use_keys && !temp_keys)) goto oom;
for (jsoff_t width = 1; width < count; width *= 2) {
for (jsoff_t left = 0; left < count; left += width * 2) {
jsoff_t mid = left + width;
jsoff_t right = (mid + width < count) ? mid + width : count;
if (mid >= count) break;
jsoff_t i = left, j = mid, k = 0;
while (i < mid && j < right) {
int cmp;
if (use_keys) {
jsoff_t len_a, len_b;
const char *sa = (const char *)&js->mem[vstr(js, keys[i], &len_a)];
const char *sb = (const char *)&js->mem[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(jsval_t));
if (use_keys) memcpy(&keys[left], temp_keys, k * sizeof(jsval_t));
}
}
writeback:
if (doff) {
for (jsoff_t i = 0; i < count; i++) dense_set(js, doff, i, vals[i]);
for (jsoff_t i = count; i < count + undef_count; i++) dense_set(js, doff, i, js_mkundef());
for (jsoff_t i = count + undef_count; i < len; i++) dense_set(js, doff, i, T_EMPTY);
} else {
for (jsoff_t i = 0; i < count; i++)
saveval(js, offs[i] + sizeof(jsoff_t) * 2, vals[i]);
for (jsoff_t i = 0; i < undef_count; i++)
saveval(js, offs[count + i] + sizeof(jsoff_t) * 2, js_mkundef());
}
free(temp_keys);
free(temp_vals);
free(keys);
free(vals);
free(offs);
return arr;
oom:
free(temp_keys);
free(temp_vals);
free(keys);
free(vals);
free(offs);
return js_mkerr(js, "out of memory");
}
static jsval_t builtin_array_splice(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "splice called on non-array");
}
jsoff_t len = proxy_aware_length(js, arr);
jsval_t read_from = is_proxy(js, 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;
jsval_t removed = mkarr(js);
if (is_err(removed)) return removed;
jsoff_t doff = get_dense_buf(js, arr);
if (doff && !is_proxy(js, arr)) {
for (int i = 0; i < deleteCount; i++) {
jsval_t elem = arr_get(js, arr, (jsoff_t)(start + i));
arr_set(js, removed, (jsoff_t)i, elem);
}
jsoff_t d_len = dense_length(js, doff);
int shift = insertCount - deleteCount;
jsoff_t new_len = (jsoff_t)((int)d_len + shift);
if (shift != 0) {
if (new_len > dense_capacity(js, doff)) {
doff = dense_grow(js, arr, new_len);
if (doff == 0) return js_mkerr(js, "oom");
}
jsoff_t move_start = (jsoff_t)(start + deleteCount);
jsoff_t move_dest = (jsoff_t)(start + insertCount);
jsoff_t move_count = d_len - move_start;
if (move_count > 0) memmove(
&js->mem[doff + sizeof(jsoff_t) * 2 + sizeof(jsval_t) * move_dest],
&js->mem[doff + sizeof(jsoff_t) * 2 + sizeof(jsval_t) * move_start],
sizeof(jsval_t) * move_count
);
}
for (int i = 0; i < insertCount; i++)
dense_set(js, doff, (jsoff_t)(start + i), args[2 + i]);
if (shift < 0) {
for (jsoff_t i = new_len; i < d_len; i++)
dense_set(js, doff, i, T_EMPTY);
}
dense_set_length(js, doff, new_len);
if (deleteCount > 0) js->needs_gc = true;
return mkval(T_ARR, vdata(removed));
}
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);
jsoff_t elem_off = lkp(js, read_from, src, strlen(src));
if (elem_off != 0) {
jsval_t elem = resolveprop(js, mkval(T_PROP, elem_off));
jsval_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));
jsoff_t elem_off = lkp(js, read_from, src, strlen(src));
jsval_t elem = elem_off ? resolveprop(js, mkval(T_PROP, elem_off)) : js_mkundef();
jsval_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));
jsoff_t elem_off = lkp(js, read_from, src, strlen(src));
jsval_t elem = elem_off ? resolveprop(js, mkval(T_PROP, elem_off)) : js_mkundef();
jsval_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));
jsval_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, arr, key, args[2 + i]);
}
js_setprop(js, arr, js->length_str, tov((double)((int)len + shift)));
if (deleteCount > 0) js->needs_gc = true;
return mkval(T_ARR, vdata(removed));
}
static jsval_t builtin_array_copyWithin(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "copyWithin called on non-array");
}
jsoff_t len = proxy_aware_length(js, arr);
jsval_t read_from = is_proxy(js, 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;
jsoff_t doff = get_dense_buf(js, arr);
if (doff && !is_proxy(js, arr)) {
if (start < target) {
for (int i = count - 1; i >= 0; i--) {
jsval_t v = dense_get(js, doff, (jsoff_t)(start + i));
dense_set(js, doff, (jsoff_t)(target + i), is_empty_slot(v) ? js_mkundef() : v);
}
} else {
for (int i = 0; i < count; i++) {
jsval_t v = dense_get(js, doff, (jsoff_t)(start + i));
dense_set(js, doff, (jsoff_t)(target + i), is_empty_slot(v) ? js_mkundef() : v);
}
}
return arr;
}
jsval_t *temp = (jsval_t *)malloc(count * sizeof(jsval_t));
for (int i = 0; i < count; i++) {
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(start + i));
jsoff_t elem_off = lkp(js, read_from, idxstr, idxlen);
temp[i] = elem_off ? resolveprop(js, mkval(T_PROP, 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));
jsval_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, arr, key, temp[i]);
}
free(temp);
return arr;
}
static jsval_t builtin_array_toSorted(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "toSorted called on non-array");
jsval_t result = array_shallow_copy(js, arr, get_array_length(js, arr));
if (is_err(result)) return result;
jsval_t saved_this = js->this_val;
js->this_val = result;
jsval_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 jsval_t builtin_array_toReversed(struct js *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "toReversed called on non-array");
jsval_t result = array_shallow_copy(js, arr, get_array_length(js, arr));
if (is_err(result)) return result;
jsval_t saved_this = js->this_val;
js->this_val = result;
jsval_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 jsval_t builtin_array_toSpliced(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ)
return js_mkerr(js, "toSpliced called on non-array");
jsval_t result = array_shallow_copy(js, arr, get_array_length(js, arr));
if (is_err(result)) return result;
jsval_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 jsval_t builtin_array_with(struct js *js, jsval_t *args, int nargs) {
jsval_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");
jsoff_t len = get_array_length(js, arr);
int idx = (int) tod(args[0]);
if (idx < 0) idx = (int)len + idx;
if (idx < 0 || (jsoff_t)idx >= len) return js_mkerr(js, "Invalid index");
jsval_t result = mkarr(js);
if (is_err(result)) return result;
for (jsoff_t i = 0; i < len; i++) {
jsval_t elem = ((jsoff_t)idx == i) ? args[1] : arr_get(js, arr, i);
arr_set(js, result, i, elem);
}
return mkval(T_ARR, vdata(result));
}
static jsval_t builtin_array_keys(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "keys called on non-array");
}
jsoff_t len = get_array_length(js, arr);
jsval_t result = mkarr(js);
if (is_err(result)) return result;
for (jsoff_t i = 0; i < len; i++) {
arr_set(js, result, i, tov((double) i));
}
return mkval(T_ARR, vdata(result));
}
static jsval_t builtin_array_values(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "values called on non-array");
}
jsoff_t len = get_array_length(js, arr);
jsval_t result = mkarr(js);
if (is_err(result)) return result;
for (jsoff_t i = 0; i < len; i++) {
arr_set(js, result, i, arr_get(js, arr, i));
}
return mkval(T_ARR, vdata(result));
}
static jsval_t builtin_array_entries(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) {
return js_mkerr(js, "entries called on non-array");
}
jsoff_t len = get_array_length(js, arr);
jsval_t result = mkarr(js);
if (is_err(result)) return result;
for (jsoff_t i = 0; i < len; i++) {
jsval_t entry = mkarr(js);
if (is_err(entry)) return entry;
jsval_t elem = arr_get(js, arr, i);
arr_set(js, entry, 0, tov((double) i));
arr_set(js, entry, 1, elem);
arr_set(js, result, i, mkval(T_ARR, vdata(entry)));
}
return mkval(T_ARR, vdata(result));
}
static jsval_t builtin_array_toString(struct js *js, jsval_t *args, int nargs) {
jsval_t arr = js->this_val;
jsval_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 jsval_t builtin_array_toLocaleString(struct js *js, jsval_t *args, int nargs) {
(void) args;
(void) nargs;
jsval_t arr = js->this_val;
if (vtype(arr) != T_ARR) return js_mkerr(js, "toLocaleString called on non-array");
jsoff_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 (jsoff_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;
jsval_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;
}
jsval_t ret = js_mkstr(js, result, result_len);
free(result);
return ret;
}
static jsval_t builtin_Array_isArray(struct js *js, jsval_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 {
jsval_t write_target;
jsval_t result;
jsval_t mapFn;
jsoff_t index;
} array_from_iter_ctx_t;
static iter_action_t array_from_iter_cb(struct js *js, jsval_t value, void *ctx, jsval_t *out) {
array_from_iter_ctx_t *fctx = (array_from_iter_ctx_t *)ctx;
jsval_t elem = value;
if (is_callable(fctx->mapFn)) {
jsval_t call_args[2] = { elem, tov((double)fctx->index) };
elem = call_js_with_args(js, fctx->mapFn, call_args, 2);
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 jsval_t builtin_Array_from(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return mkarr(js);
jsval_t src = args[0];
jsval_t mapFn = (nargs >= 2 && is_callable(args[1])) ? args[1] : js_mkundef();
jsval_t ctor = js->this_val;
bool use_ctor = (vtype(ctor) == T_FUNC || vtype(ctor) == T_CFUNC);
jsval_t result;
if (use_ctor) {
jsval_t ctor_args[1] = { tov(0.0) };
result = js_call(js, ctor, ctor_args, 0);
if (is_err(result)) return result;
} else {
result = mkarr(js);
if (is_err(result)) return result;
}
bool result_is_proxy = is_proxy(js, result);
jsval_t write_target = result_is_proxy ? proxy_read_target(js, result) : result;
if (vtype(src) == T_STR) {
array_from_iter_ctx_t ctx = { write_target, result, mapFn, 0 };
jsoff_t str_len, str_off = vstr(js, src, &str_len);
const char *str_ptr = (const char *)&js->mem[str_off];
for (jsoff_t i = 0; i < str_len; i++) {
jsval_t unused;
iter_action_t act = array_from_iter_cb(js, js_mkstr(js, str_ptr + i, 1), &ctx, &unused);
if (act == ITER_ERROR) return unused;
}
if (vtype(result) != T_ARR) js_setprop(js, result, js->length_str, tov((double) str_len));
} else {
const char *iter_key = get_iterator_sym_key();
jsoff_t iter_prop = iter_key ? lkp_proto(js, src, iter_key, strlen(iter_key)) : 0;
if (iter_prop != 0) {
array_from_iter_ctx_t ctx = { write_target, result, mapFn, 0 };
jsval_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_ARR || vtype(src) == T_OBJ) {
array_from_iter_ctx_t ctx = { write_target, result, mapFn, 0 };
jsoff_t len = get_array_length(js, src);
for (jsoff_t i = 0; i < len; i++) {
jsval_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 jsval_t builtin_Array_of(struct js *js, jsval_t *args, int nargs) {
jsval_t ctor = js->this_val;
bool use_ctor = (vtype(ctor) == T_FUNC || vtype(ctor) == T_CFUNC);
jsval_t arr;
if (use_ctor) {
jsval_t ctor_args[1] = { tov((double)nargs) };
arr = js_call(js, ctor, ctor_args, 1);
if (is_err(arr)) return arr;
} else {
arr = mkarr(js);
if (is_err(arr)) return arr;
}
bool arr_is_proxy = is_proxy(js, arr);
jsval_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, (jsoff_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 jsval_t builtin_string_indexOf(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsval_t search = args[0];
if (vtype(search) != T_STR) return tov(-1);
jsoff_t str_len, str_off = vstr(js, str, &str_len);
jsoff_t search_len, search_off = vstr(js, search, &search_len);
jsoff_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 = (jsoff_t) pos;
}
if (search_len == 0) return tov(D(start));
if (start + search_len > str_len) return tov(-1);
const char *str_ptr = (char *) &js->mem[str_off];
const char *search_ptr = (char *) &js->mem[search_off];
for (jsoff_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 jsval_t builtin_string_substring(struct js *js, jsval_t *args, int nargs) {
jsval_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "substring called on non-string");
jsoff_t byte_len, str_off = vstr(js, str, &byte_len);
const char *str_ptr = (char *) &js->mem[str_off];
size_t utf16_len = utf16_strlen(str_ptr, byte_len);
jsoff_t start = 0, end = (jsoff_t)utf16_len;
double dstr_len2 = D(utf16_len);
if (nargs >= 1 && vtype(args[0]) == T_NUM) {
double d = tod(args[0]);
start = (jsoff_t) (d < 0 ? 0 : (d > dstr_len2 ? dstr_len2 : d));
}
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double d = tod(args[1]);
end = (jsoff_t) (d < 0 ? 0 : (d > dstr_len2 ? dstr_len2 : d));
}
if (start > end) {
jsoff_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 jsval_t builtin_string_substr(struct js *js, jsval_t *args, int nargs) {
jsval_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "substr called on non-string");
jsoff_t byte_len, str_off = vstr(js, str, &byte_len);
const char *str_ptr = (char *) &js->mem[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]);
jsoff_t start;
if (d_start < 0) {
start = (jsoff_t)((double)utf16_len + d_start);
if ((int)start < 0) start = 0;
} else {
start = (jsoff_t)d_start;
}
if (start > (jsoff_t)utf16_len) start = (jsoff_t)utf16_len;
jsoff_t len = (jsoff_t)utf16_len - start;
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double d = tod(args[1]);
if (d < 0) d = 0;
len = (jsoff_t)d;
}
if (start + len > (jsoff_t)utf16_len) len = (jsoff_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 jsval_t builtin_string_split(struct js *js, jsval_t *args, int nargs) {
jsval_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "split called on non-string");
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
jsval_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;
jsval_t sep_arg = args[0];
if (vtype(sep_arg) == T_OBJ) {
jsoff_t source_off = lkp(js, sep_arg, "source", 6);
if (source_off == 0) goto return_whole;
jsval_t source_val = resolveprop(js, mkval(T_PROP, source_off));
if (vtype(source_val) != T_STR) goto return_whole;
jsoff_t plen, poff = vstr(js, source_val, &plen);
const char *pattern_ptr = (char *) &js->mem[poff];
if (plen == 0 || (plen == 4 && memcmp(pattern_ptr, "(?:)", 4) == 0)) {
jsoff_t idx = 0;
for (jsoff_t i = 0; i < str_len && idx < limit; i++) {
jsval_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));
}
}
jsoff_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;
jsval_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) {
jsval_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;
jsoff_t sep_len, sep_off = vstr(js, sep_arg, &sep_len);
const char *sep_ptr = (char *) &js->mem[sep_off];
jsoff_t idx = 0, start = 0;
if (sep_len == 0) {
for (jsoff_t i = 0; i < str_len && idx < limit; i++) {
jsval_t part = js_mkstr(js, str_ptr + i, 1);
arr_set(js, arr, idx, part);
idx++;
}
return mkval(T_ARR, vdata(arr));
}
for (jsoff_t i = 0; i + sep_len <= str_len && idx < limit; i++) {
if (memcmp(str_ptr + i, sep_ptr, sep_len) != 0) continue;
jsval_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) {
jsval_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 jsval_t builtin_string_slice(struct js *js, jsval_t *args, int nargs) {
jsval_t this_unwrapped = unwrap_primitive(js, js->this_val);
jsval_t str = js_tostring_val(js, this_unwrapped);
if (is_err(str)) return str;
jsoff_t byte_len, str_off = vstr(js, str, &byte_len);
const char *str_ptr = (char *) &js->mem[str_off];
size_t utf16_len = utf16_strlen(str_ptr, byte_len);
jsoff_t start = 0, end = (jsoff_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 = (jsoff_t) (d + dstr_len < 0 ? 0 : d + dstr_len);
} else {
start = (jsoff_t) (d > dstr_len ? dstr_len : d);
}
}
if (nargs >= 2 && vtype(args[1]) == T_NUM) {
double d = tod(args[1]);
if (d < 0) {
end = (jsoff_t) (d + dstr_len < 0 ? 0 : d + dstr_len);
} else {
end = (jsoff_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 jsval_t builtin_string_includes(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsval_t search = args[0];
if (vtype(search) != T_STR) return mkval(T_BOOL, 0);
jsoff_t str_len, str_off = vstr(js, str, &str_len);
jsoff_t search_len, search_off = vstr(js, search, &search_len);
const char *str_ptr = (char *) &js->mem[str_off];
const char *search_ptr = (char *) &js->mem[search_off];
jsoff_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 = (jsoff_t) pos;
}
if (search_len == 0) return mkval(T_BOOL, 1);
if (start + search_len > str_len) return mkval(T_BOOL, 0);
for (jsoff_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 jsval_t builtin_string_startsWith(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsval_t search = args[0];
if (vtype(search) != T_STR) return mkval(T_BOOL, 0);
jsoff_t str_len, str_off = vstr(js, str, &str_len);
jsoff_t search_len, search_off = vstr(js, search, &search_len);
const char *str_ptr = (char *) &js->mem[str_off];
const char *search_ptr = (char *) &js->mem[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 jsval_t builtin_string_endsWith(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsval_t search = args[0];
if (vtype(search) != T_STR) return mkval(T_BOOL, 0);
jsoff_t str_len, str_off = vstr(js, str, &str_len);
jsoff_t search_len, search_off = vstr(js, search, &search_len);
const char *str_ptr = (char *) &js->mem[str_off];
const char *search_ptr = (char *) &js->mem[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 jsval_t builtin_string_replace(struct js *js, jsval_t *args, int nargs) {
jsval_t this_unwrapped = unwrap_primitive(js, js->this_val);
jsval_t str = js_tostring_val(js, this_unwrapped);
if (is_err(str)) return str;
if (nargs < 2) return str;
jsval_t search = args[0];
jsval_t replacement = args[1];
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
bool is_regex = false;
bool global_flag = false;
bool is_func_replacement = (vtype(replacement) == T_FUNC);
char *pattern_buf = NULL;
jsoff_t pattern_len = 0;
if (vtype(search) == T_OBJ) {
jsoff_t pattern_off = lkp(js, search, "source", 6);
if (pattern_off == 0) goto not_regex;
jsval_t pattern_val = resolveprop(js, mkval(T_PROP, pattern_off));
if (vtype(pattern_val) != T_STR) goto not_regex;
is_regex = true;
jsoff_t plen, poff = vstr(js, pattern_val, &plen);
pattern_len = plen;
pattern_buf = (char *)ant_calloc(plen + 1);
if (!pattern_buf) return js_mkerr(js, "oom");
memcpy(pattern_buf, &js->mem[poff], plen);
pattern_buf[plen] = '\0';
jsoff_t flags_off = lkp(js, search, "flags", 5);
if (flags_off == 0) { free(pattern_buf); pattern_buf = NULL; goto not_regex; }
jsval_t flags_val = resolveprop(js, mkval(T_PROP, flags_off));
if (vtype(flags_val) != T_STR) { free(pattern_buf); pattern_buf = NULL; goto not_regex; }
jsoff_t flen, foff = vstr(js, flags_val, &flen);
const char *flags_str = (char *) &js->mem[foff];
for (jsoff_t i = 0; i < flen; i++) {
if (flags_str[i] == 'g') global_flag = true;
}
}
not_regex:
jsoff_t repl_len = 0;
const char *repl_ptr = NULL;
if (!is_func_replacement) {
if (vtype(replacement) != T_STR) { if (pattern_buf) free(pattern_buf); return str; }
jsoff_t repl_off;
repl_off = vstr(js, replacement, &repl_len);
repl_ptr = (char *) &js->mem[repl_off];
}
size_t result_cap = str_len + repl_len + 256;
size_t result_len = 0;
char *result = (char *)ant_calloc(result_cap);
if (!result) return js_mkerr(js, "oom");
#define ENSURE_RESULT_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)
if (is_regex) {
size_t pcre2_cap = pattern_len * 2 + 16;
char *pcre2_pattern = (char *)ant_calloc(pcre2_cap);
if (!pcre2_pattern) return js_mkerr(js, "oom");
size_t pcre2_len = js_to_pcre2_pattern(pattern_buf, pattern_len, pcre2_pattern, pcre2_cap);
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);
free(pcre2_pattern);
if (re == NULL) return js_mkerr(js, "invalid regex pattern");
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);
PCRE2_SIZE pos = 0;
bool replaced = false;
while (pos <= str_len) {
int rc = pcre2_match(re, (PCRE2_SPTR)str_ptr, str_len, 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];
PCRE2_SIZE before_len = match_start - pos;
ENSURE_RESULT_CAP(before_len);
memcpy(result + result_len, str_ptr + pos, before_len);
result_len += before_len;
if (is_func_replacement) {
int nargs_cb = 1 + capture_count + 2;
jsval_t *cb_args = (jsval_t *)ant_calloc(nargs_cb * sizeof(jsval_t));
if (!cb_args) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return js_mkerr(js, "oom");
}
cb_args[0] = js_mkstr(js, str_ptr + match_start, match_end - match_start);
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) {
cb_args[i] = js_mkundef();
} else {
cb_args[i] = js_mkstr(js, str_ptr + cap_start, cap_end - cap_start);
}
}
cb_args[1 + capture_count] = tov((double)match_start);
cb_args[2 + capture_count] = str;
jsval_t cb_result = js_call(js, replacement, cb_args, nargs_cb);
free(cb_args);
if (vtype(cb_result) == T_ERR) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return cb_result;
}
if (vtype(cb_result) == T_STR) {
jsoff_t cb_len, cb_off = vstr(js, cb_result, &cb_len);
ENSURE_RESULT_CAP(cb_len);
memcpy(result + result_len, &js->mem[cb_off], cb_len);
result_len += cb_len;
} else {
char numbuf[32];
size_t n = tostr(js, cb_result, numbuf, sizeof(numbuf));
ENSURE_RESULT_CAP(n);
memcpy(result + result_len, numbuf, n);
result_len += n;
}
} else {
for (jsoff_t ri = 0; ri < repl_len; ) {
if (repl_ptr[ri] == '$' && ri + 1 < repl_len) {
char next = repl_ptr[ri + 1];
if (next == '$') {
ENSURE_RESULT_CAP(1);
result[result_len++] = '$';
ri += 2;
} else if (next == '&') {
PCRE2_SIZE mlen = match_end - match_start;
ENSURE_RESULT_CAP(mlen);
memcpy(result + result_len, str_ptr + match_start, mlen);
result_len += mlen;
ri += 2;
} else if (next == '`') {
ENSURE_RESULT_CAP(match_start);
memcpy(result + result_len, str_ptr, match_start);
result_len += match_start;
ri += 2;
} else if (next == '\'') {
PCRE2_SIZE after_len = str_len - match_end;
ENSURE_RESULT_CAP(after_len);
memcpy(result + result_len, str_ptr + match_end, after_len);
result_len += after_len;
ri += 2;
} else if (next >= '0' && next <= '9') {
int group_num = next - '0';
ri += 2;
if (ri < repl_len && repl_ptr[ri] >= '0' && repl_ptr[ri] <= '9') {
int second_digit = repl_ptr[ri] - '0';
int two_digit = group_num * 10 + second_digit;
if (two_digit <= (int)capture_count) {
group_num = two_digit;
ri++;
}
}
if (group_num > 0 && group_num <= (int)capture_count) {
PCRE2_SIZE cap_start = ovector[2*group_num];
PCRE2_SIZE cap_end = ovector[2*group_num+1];
if (cap_start != PCRE2_UNSET) {
PCRE2_SIZE cap_len = cap_end - cap_start;
ENSURE_RESULT_CAP(cap_len);
memcpy(result + result_len, str_ptr + cap_start, cap_len);
result_len += cap_len;
}
} else {
ENSURE_RESULT_CAP(2);
result[result_len++] = '$';
result[result_len++] = next;
}
} else {
ENSURE_RESULT_CAP(1);
result[result_len++] = repl_ptr[ri++];
}
} else {
ENSURE_RESULT_CAP(1);
result[result_len++] = repl_ptr[ri++];
}
}
}
if (match_start == match_end) {
if (pos < str_len) {
ENSURE_RESULT_CAP(1);
result[result_len++] = str_ptr[pos];
}
pos = match_end + 1;
} else pos = match_end;
replaced = true;
if (!global_flag) break;
}
if (pos < str_len) {
size_t remaining = str_len - pos;
ENSURE_RESULT_CAP(remaining);
memcpy(result + result_len, str_ptr + pos, remaining);
result_len += remaining;
}
pcre2_match_data_free(match_data); pcre2_code_free(re);
if (pattern_buf) free(pattern_buf);
jsval_t ret = replaced ? js_mkstr(js, result, result_len) : str;
free(result); return ret;
} else {
if (vtype(search) != T_STR) { free(result); return str; }
jsoff_t search_len, search_off = vstr(js, search, &search_len);
const char *search_ptr = (char *) &js->mem[search_off];
if (search_len > str_len) { free(result); return str; }
for (jsoff_t i = 0; i <= str_len - search_len; i++) {
if (memcmp(str_ptr + i, search_ptr, search_len) == 0) {
ENSURE_RESULT_CAP(i);
memcpy(result + result_len, str_ptr, i);
result_len += i;
if (is_func_replacement) {
jsval_t match_str = js_mkstr(js, search_ptr, search_len);
jsval_t cb_args[1] = { match_str };
jsval_t cb_result = js_call(js, replacement, cb_args, 1);
if (vtype(cb_result) == T_ERR) { free(result); return cb_result; }
if (vtype(cb_result) == T_STR) {
jsoff_t cb_len, cb_off = vstr(js, cb_result, &cb_len);
ENSURE_RESULT_CAP(cb_len);
memcpy(result + result_len, &js->mem[cb_off], cb_len);
result_len += cb_len;
} else {
char numbuf[32];
size_t n = tostr(js, cb_result, numbuf, sizeof(numbuf));
ENSURE_RESULT_CAP(n);
memcpy(result + result_len, numbuf, n);
result_len += n;
}
} else {
ENSURE_RESULT_CAP(repl_len);
memcpy(result + result_len, repl_ptr, repl_len);
result_len += repl_len;
}
jsoff_t after_start = i + search_len;
jsoff_t after_len = str_len - after_start;
if (after_len > 0) {
ENSURE_RESULT_CAP(after_len);
memcpy(result + result_len, str_ptr + after_start, after_len);
result_len += after_len;
}
jsval_t ret = js_mkstr(js, result, result_len);
free(result);
return ret;
}
}
free(result);
return str;
}
#undef ENSURE_RESULT_CAP
}
static jsval_t builtin_string_replaceAll(struct js *js, jsval_t *args, int nargs) {
jsval_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "replaceAll called on non-string");
if (nargs < 2) return str;
jsval_t search = args[0];
jsval_t replacement = args[1];
if (vtype(search) != T_STR) return js_mkerr(js, "replaceAll requires string search pattern");
if (vtype(replacement) != T_STR) return js_mkerr(js, "replaceAll requires string replacement");
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
jsoff_t search_len, search_off = vstr(js, search, &search_len);
const char *search_ptr = (char *) &js->mem[search_off];
jsoff_t repl_len, repl_off = vstr(js, replacement, &repl_len);
const char *repl_ptr = (char *) &js->mem[repl_off];
if (search_len == 0) {
size_t total_len = str_len + (str_len + 1) * repl_len;
char *result = (char *)ant_calloc(total_len + 1);
if (!result) return js_mkerr(js, "oom");
size_t pos = 0;
memcpy(result + pos, repl_ptr, repl_len);
pos += repl_len;
for (jsoff_t i = 0; i < str_len; i++) {
result[pos++] = str_ptr[i];
memcpy(result + pos, repl_ptr, repl_len);
pos += repl_len;
}
jsval_t ret = js_mkstr(js, result, pos);
free(result);
return ret;
}
jsoff_t count = 0;
for (jsoff_t i = 0; i <= str_len - search_len; i++) {
if (memcmp(str_ptr + i, search_ptr, search_len) == 0) {
count++;
i += search_len - 1;
}
}
if (count == 0) return str;
size_t result_total = str_len - (count * search_len) + (count * repl_len);
char *result = (char *)ant_calloc(result_total + 1);
if (!result) return js_mkerr(js, "oom");
size_t result_pos = 0;
jsoff_t str_pos = 0;
while (str_pos <= str_len - search_len) {
if (memcmp(str_ptr + str_pos, search_ptr, search_len) == 0) {
memcpy(result + result_pos, repl_ptr, repl_len);
result_pos += repl_len;
str_pos += search_len;
} else {
result[result_pos++] = str_ptr[str_pos++];
}
}
while (str_pos < str_len) {
result[result_pos++] = str_ptr[str_pos++];
}
jsval_t ret = js_mkstr(js, result, result_pos);
free(result);
return ret;
}
static size_t js_to_pcre2_pattern(const char *src, size_t src_len, char *dst, size_t dst_size) {
size_t di = 0;
for (size_t si = 0; si < src_len && di < dst_size - 1; si++) {
if (src[si] == '\\' && si + 1 < src_len) {
char next = src[si + 1];
if (next == 'u' && si + 5 < src_len) {
bool valid = true;
for (int i = 0; i < 4; i++) {
char c = src[si + 2 + i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
valid = false;
break;
}
}
if (valid && di + 8 < dst_size) {
dst[di++] = '\\';
dst[di++] = 'x';
dst[di++] = '{';
dst[di++] = src[si + 2];
dst[di++] = src[si + 3];
dst[di++] = src[si + 4];
dst[di++] = src[si + 5];
dst[di++] = '}';
si += 5;
continue;
}
}
if (next == '0' && (si + 2 >= src_len || src[si + 2] < '0' || src[si + 2] > '9')) {
if (di + 5 < dst_size) {
dst[di++] = '\\';
dst[di++] = 'x';
dst[di++] = '{';
dst[di++] = '0';
dst[di++] = '}';
si += 1;
continue;
}
}
}
dst[di++] = src[si];
}
dst[di] = '\0';
return di;
}
static jsval_t do_regex_match_pcre2(
struct js *js, const char *pattern_ptr, jsoff_t pattern_len,
const char *str_ptr, jsoff_t str_len,
bool global_flag, bool ignore_case, bool multiline
) {
char pcre2_pattern[512];
size_t pcre2_len = js_to_pcre2_pattern(pattern_ptr, pattern_len, pcre2_pattern, sizeof(pcre2_pattern));
uint32_t options = PCRE2_UTF | PCRE2_UCP | PCRE2_MATCH_UNSET_BACKREF;
if (ignore_case) options |= PCRE2_CASELESS;
if (multiline) options |= PCRE2_MULTILINE;
int errcode;
PCRE2_SIZE erroffset;
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pcre2_pattern, pcre2_len, options, &errcode, &erroffset, NULL);
if (re == NULL) return js_mknull();
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);
jsval_t result_arr = js_mkarr(js);
if (is_err(result_arr)) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return result_arr;
}
PCRE2_SIZE pos = 0;
int match_count = 0;
while (pos <= str_len) {
int rc = pcre2_match(re, (PCRE2_SPTR)str_ptr, str_len, 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 (global_flag) {
jsval_t match_str = js_mkstr(js, str_ptr + match_start, match_end - match_start);
if (is_err(match_str)) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return match_str;
}
js_arr_push(js, result_arr, match_str);
} else {
for (uint32_t i = 0; i <= capture_count; i++) {
PCRE2_SIZE start = ovector[2*i];
PCRE2_SIZE end = ovector[2*i+1];
if (start == PCRE2_UNSET) {
js_arr_push(js, result_arr, js_mkundef());
} else {
jsval_t match_str = js_mkstr(js, str_ptr + start, end - start);
if (is_err(match_str)) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return match_str;
}
js_arr_push(js, result_arr, match_str);
}
}
js_setprop(js, result_arr, js_mkstr(js, "index", 5), tov((double)match_start));
}
match_count++;
if (!global_flag) break;
if (match_start == match_end) {
pos = match_end + 1;
} else { pos = match_end; }
}
pcre2_match_data_free(match_data);
pcre2_code_free(re);
if (match_count == 0) return js_mknull();
return result_arr;
}
static jsval_t builtin_string_match(struct js *js, jsval_t *args, int nargs) {
jsval_t this_unwrapped = unwrap_primitive(js, js->this_val);
jsval_t str = js_tostring_val(js, this_unwrapped);
if (is_err(str)) return str;
if (nargs < 1) return js_mknull();
jsval_t pattern = args[0];
const char *pattern_ptr = NULL;
jsoff_t pattern_len = 0;
bool global_flag = false;
bool ignore_case = false;
bool multiline = false;
if (vtype(pattern) == T_OBJ) {
jsoff_t source_off = lkp(js, pattern, "source", 6);
if (source_off == 0) return js_mknull();
jsval_t source_val = resolveprop(js, mkval(T_PROP, source_off));
if (vtype(source_val) != T_STR) return js_mknull();
jsoff_t poff;
poff = vstr(js, source_val, &pattern_len);
pattern_ptr = (char *) &js->mem[poff];
jsoff_t flags_off = lkp(js, pattern, "flags", 5);
if (flags_off != 0) {
jsval_t flags_val = resolveprop(js, mkval(T_PROP, flags_off));
if (vtype(flags_val) == T_STR) {
jsoff_t flen, foff = vstr(js, flags_val, &flen);
const char *flags_str = (char *) &js->mem[foff];
for (jsoff_t i = 0; i < flen; i++) {
if (flags_str[i] == 'g') global_flag = true;
if (flags_str[i] == 'i') ignore_case = true;
if (flags_str[i] == 'm') multiline = true;
}
}
}
} else if (vtype(pattern) == T_STR) {
jsoff_t poff;
poff = vstr(js, pattern, &pattern_len);
pattern_ptr = (char *) &js->mem[poff];
} else {
return js_mknull();
}
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
jsval_t result = do_regex_match_pcre2(js, pattern_ptr, pattern_len, str_ptr, str_len, global_flag, ignore_case, multiline);
if (!global_flag && vtype(result) == T_ARR) {
js_setprop(js, result, js_mkstr(js, "input", 5), str);
}
return result;
}
static jsval_t builtin_string_template(struct js *js, jsval_t *args, int nargs) {
jsval_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;
jsval_t data = args[0];
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[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");
jsoff_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] == '{') {
jsoff_t start = i + 2;
jsoff_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] == '}') {
jsoff_t key_len = end - start;
jsoff_t prop_off = lkp(js, data, str_ptr + start, key_len);
if (prop_off != 0) {
jsval_t value = resolveprop(js, mkval(T_PROP, prop_off));
if (vtype(value) == T_STR) {
jsoff_t val_len, val_off = vstr(js, value, &val_len);
ENSURE_CAP(val_len);
memcpy(result + result_len, &js->mem[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++];
}
jsval_t ret = js_mkstr(js, result, result_len);
free(result);
return ret;
#undef ENSURE_CAP
}
static jsval_t builtin_string_charCodeAt(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsoff_t byte_len; jsoff_t str_off = vstr(js, str, &byte_len);
const char *str_data = (const char *)&js->mem[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 jsval_t builtin_string_codePointAt(struct js *js, jsval_t *args, int nargs) {
jsval_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();
jsoff_t byte_len;
jsoff_t str_off = vstr(js, str, &byte_len);
const char *str_data = (const char *)&js->mem[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 jsval_t builtin_string_toLowerCase(struct js *js, jsval_t *args, int nargs) {
(void) args; (void) nargs;
jsval_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "toLowerCase called on non-string");
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
jsval_t result = js_mkstr(js, NULL, str_len);
if (is_err(result)) return result;
jsoff_t result_len, result_off = vstr(js, result, &result_len);
char *result_ptr = (char *) &js->mem[result_off];
for (jsoff_t i = 0; i < str_len; i++) {
char ch = str_ptr[i];
result_ptr[i] = (ch >= 'A' && ch <= 'Z') ? ch + 32 : ch;
}
return result;
}
static jsval_t builtin_string_toUpperCase(struct js *js, jsval_t *args, int nargs) {
(void) args; (void) nargs;
jsval_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "toUpperCase called on non-string");
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
jsval_t result = js_mkstr(js, NULL, str_len);
if (is_err(result)) return result;
jsoff_t result_len, result_off = vstr(js, result, &result_len);
char *result_ptr = (char *) &js->mem[result_off];
for (jsoff_t i = 0; i < str_len; i++) {
char ch = str_ptr[i];
result_ptr[i] = (ch >= 'a' && ch <= 'z') ? ch - 32 : ch;
}
return result;
}
static jsval_t builtin_string_trim(struct js *js, jsval_t *args, int nargs) {
(void) args; (void) nargs;
jsval_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "trim called on non-string");
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
jsoff_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 jsval_t builtin_string_trimStart(struct js *js, jsval_t *args, int nargs) {
(void) args; (void) nargs;
jsval_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "trimStart called on non-string");
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
jsoff_t start = 0;
while (start < str_len && is_space(str_ptr[start])) start++;
return js_mkstr(js, str_ptr + start, str_len - start);
}
static jsval_t builtin_string_trimEnd(struct js *js, jsval_t *args, int nargs) {
(void) args; (void) nargs;
jsval_t str = to_string_val(js, js->this_val);
if (vtype(str) != T_STR) return js_mkerr(js, "trimEnd called on non-string");
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
jsoff_t end = str_len;
while (end > 0 && is_space(str_ptr[end - 1])) end--;
return js_mkstr(js, str_ptr, end);
}
static jsval_t builtin_string_repeat(struct js *js, jsval_t *args, int nargs) {
jsval_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");
jsoff_t count = (jsoff_t) count_d;
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
if (count == 0 || str_len == 0) return js_mkstr(js, "", 0);
jsval_t result = js_mkstr(js, NULL, str_len * count);
if (is_err(result)) return result;
jsoff_t result_len, result_off = vstr(js, result, &result_len);
char *result_ptr = (char *) &js->mem[result_off];
for (jsoff_t i = 0; i < count; i++) {
memcpy(result_ptr + i * str_len, str_ptr, str_len);
}
return result;
}
static jsval_t builtin_string_padStart(struct js *js, jsval_t *args, int nargs) {
jsval_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;
jsoff_t target_len = (jsoff_t) tod(args[0]);
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
if (target_len <= str_len) return str;
const char *pad_str = " ";
jsoff_t pad_len = 1;
if (nargs >= 2 && vtype(args[1]) == T_STR) {
jsoff_t pad_off = vstr(js, args[1], &pad_len);
pad_str = (char *) &js->mem[pad_off];
}
if (pad_len == 0) return str;
jsoff_t fill_len = target_len - str_len;
jsval_t result = js_mkstr(js, NULL, target_len);
if (is_err(result)) return result;
jsoff_t result_len, result_off = vstr(js, result, &result_len);
char *result_ptr = (char *) &js->mem[result_off];
jsoff_t pos = 0;
while (pos < fill_len) {
jsoff_t copy_len = (fill_len - pos < pad_len) ? fill_len - pos : pad_len;
memcpy(result_ptr + pos, pad_str, copy_len);
pos += copy_len;
}
memcpy(result_ptr + fill_len, str_ptr, str_len);
return result;
}
static jsval_t builtin_string_padEnd(struct js *js, jsval_t *args, int nargs) {
jsval_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;
jsoff_t target_len = (jsoff_t) tod(args[0]);
jsoff_t str_len, str_off = vstr(js, str, &str_len);
const char *str_ptr = (char *) &js->mem[str_off];
if (target_len <= str_len) return str;
const char *pad_str = " ";
jsoff_t pad_len = 1;
if (nargs >= 2 && vtype(args[1]) == T_STR) {
jsoff_t pad_off = vstr(js, args[1], &pad_len);
pad_str = (char *) &js->mem[pad_off];
}
if (pad_len == 0) return str;
jsval_t result = js_mkstr(js, NULL, target_len);
if (is_err(result)) return result;
jsoff_t result_len, result_off = vstr(js, result, &result_len);
char *result_ptr = (char *) &js->mem[result_off];
memcpy(result_ptr, str_ptr, str_len);
jsoff_t pos = str_len;
while (pos < target_len) {
jsoff_t copy_len = (target_len - pos < pad_len) ? target_len - pos : pad_len;
memcpy(result_ptr + pos, pad_str, copy_len);
pos += copy_len;
}
return result;
}
static jsval_t builtin_string_charAt(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsoff_t idx = (jsoff_t) idx_d;
jsoff_t byte_len;
jsoff_t str_off = vstr(js, str, &byte_len);
const char *str_data = (const char *)&js->mem[str_off];
size_t char_bytes;
int byte_offset = utf16_index_to_byte_offset(str_data, byte_len, idx, &char_bytes);
if (byte_offset < 0) return js_mkstr(js, "", 0);
return js_mkstr(js, str_data + byte_offset, char_bytes);
}
static jsval_t builtin_string_at(struct js *js, jsval_t *args, int nargs) {
jsval_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();
jsoff_t byte_len; jsoff_t str_off = vstr(js, str, &byte_len);
const char *str_data = (const char *)&js->mem[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();
size_t char_bytes;
int byte_offset = utf16_index_to_byte_offset(str_data, byte_len, idx, &char_bytes);
if (byte_offset < 0) return js_mkundef();
return js_mkstr(js, str_data + byte_offset, char_bytes);
}
static jsval_t builtin_string_localeCompare(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsval_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);
}
jsoff_t str_len, str_off = vstr(js, str, &str_len);
jsoff_t that_len, that_off = vstr(js, that, &that_len);
const char *str_ptr = (char *)&js->mem[str_off];
const char *that_ptr = (char *)&js->mem[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 jsval_t builtin_string_lastIndexOf(struct js *js, jsval_t *args, int nargs) {
jsval_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);
jsval_t search = args[0];
if (vtype(search) != T_STR) return tov(-1);
jsoff_t str_len, str_off = vstr(js, str, &str_len);
jsoff_t search_len, search_off = vstr(js, search, &search_len);
jsoff_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 = (jsoff_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 *) &js->mem[str_off];
const char *search_ptr = (char *) &js->mem[search_off];
jsoff_t start = (max_start + search_len > str_len) ? str_len - search_len : max_start;
for (jsoff_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 jsval_t builtin_string_concat(struct js *js, jsval_t *args, int nargs) {
jsval_t this_unwrapped = unwrap_primitive(js, js->this_val);
jsval_t str = js_tostring_val(js, this_unwrapped);
if (is_err(str)) return str;
jsoff_t total_len;
jsoff_t base_off = vstr(js, str, &total_len);
jsval_t *str_args = NULL;
if (nargs > 0) {
str_args = (jsval_t *)ant_calloc(nargs * sizeof(jsval_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];
}
jsoff_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");
}
jsoff_t base_len;
base_off = vstr(js, str, &base_len);
memcpy(result, &js->mem[base_off], base_len);
jsoff_t pos = base_len;
for (int i = 0; i < nargs; i++) {
jsoff_t arg_len, arg_off = vstr(js, str_args[i], &arg_len);
memcpy(result + pos, &js->mem[arg_off], arg_len);
pos += arg_len;
}
result[pos] = '\0';
jsval_t ret = js_mkstr(js, result, pos);
free(result); if (str_args) free(str_args);
return ret;
}
static jsval_t builtin_string_fromCharCode(struct js *js, jsval_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';
jsval_t ret = js_mkstr(js, buf, nargs);
free(buf);
return ret;
}
static jsval_t builtin_string_fromCodePoint(struct js *js, jsval_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';
jsval_t ret = js_mkstr(js, buf, len);
free(buf);
return ret;
}
static jsval_t builtin_number_toString(struct js *js, jsval_t *args, int nargs) {
jsval_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 jsval_t builtin_number_toFixed(struct js *js, jsval_t *args, int nargs) {
jsval_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 jsval_t builtin_number_toPrecision(struct js *js, jsval_t *args, int nargs) {
jsval_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 jsval_t builtin_number_toExponential(struct js *js, jsval_t *args, int nargs) {
jsval_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 jsval_t builtin_number_valueOf(struct js *js, jsval_t *args, int nargs) {
(void) args; (void) nargs;
jsval_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 jsval_t builtin_number_toLocaleString(struct js *js, jsval_t *args, int nargs) {
(void) args; (void) nargs;
jsval_t num = unwrap_primitive(js, js->this_val);
if (vtype(num) != T_NUM) return js_mkerr(js, "toLocaleString called on non-number");
char buf[64];
strnum(num, buf, sizeof(buf));
return js_mkstr(js, buf, strlen(buf));
}
static jsval_t builtin_string_valueOf(struct js *js, jsval_t *args, int nargs) {
(void) args; (void) nargs;
jsval_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 jsval_t builtin_string_toString(struct js *js, jsval_t *args, int nargs) {
return builtin_string_valueOf(js, args, nargs);
}
static jsval_t builtin_boolean_valueOf(struct js *js, jsval_t *args, int nargs) {
(void) args; (void) nargs;
jsval_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 jsval_t builtin_boolean_toString(struct js *js, jsval_t *args, int nargs) {
(void) args; (void) nargs;
jsval_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 jsval_t builtin_parseInt(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return tov(JS_NAN);
jsval_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));
}
jsoff_t str_len, str_off = vstr(js, str_val, &str_len);
const char *str = (char *) &js->mem[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);
}
jsoff_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 jsval_t builtin_parseFloat(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return tov(JS_NAN);
jsval_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));
}
jsoff_t str_len, str_off = vstr(js, str_val, &str_len);
const char *str = (char *) &js->mem[str_off];
jsoff_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 const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static jsval_t builtin_btoa(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "btoa requires 1 argument");
jsval_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));
}
jsoff_t str_len, str_off = vstr(js, str_val, &str_len);
const char *str = (char *) &js->mem[str_off];
for (jsoff_t i = 0; i < str_len; i++) {
if ((unsigned char)str[i] > 255) {
return js_mkerr(js, "btoa: character out of range");
}
}
size_t out_len = ((str_len + 2) / 3) * 4;
char *out = (char *)ant_calloc(out_len + 1);
if (!out) return js_mkerr(js, "out of memory");
size_t i = 0, j = 0;
while (i < str_len) {
size_t remaining = str_len - i;
uint32_t a = (unsigned char)str[i++];
uint32_t b = (remaining > 1) ? (unsigned char)str[i++] : 0;
uint32_t c = (remaining > 2) ? (unsigned char)str[i++] : 0;
uint32_t triple = (a << 16) | (b << 8) | c;
out[j++] = base64_chars[(triple >> 18) & 0x3F];
out[j++] = base64_chars[(triple >> 12) & 0x3F];
out[j++] = (remaining > 1) ? base64_chars[(triple >> 6) & 0x3F] : '=';
out[j++] = (remaining > 2) ? base64_chars[triple & 0x3F] : '=';
}
out[j] = '\0';
jsval_t result = js_mkstr(js, out, j);
free(out);
return result;
}
static const int8_t decode_table[256] = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
};
static jsval_t builtin_atob(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "atob requires 1 argument");
jsval_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));
}
jsoff_t str_len, str_off = vstr(js, str_val, &str_len);
const char *str = (char *) &js->mem[str_off];
if (str_len == 0) return js_mkstr(js, "", 0);
if (str_len % 4 != 0) return js_mkerr(js, "atob: invalid base64 string");
size_t out_len = (str_len / 4) * 3;
if (str_len > 0 && str[str_len - 1] == '=') out_len--;
if (str_len > 1 && str[str_len - 2] == '=') out_len--;
char *out = (char *)ant_calloc(out_len + 1);
if (!out) return js_mkerr(js, "out of memory");
size_t i = 0, j = 0;
while (i < str_len) {
int8_t a = decode_table[(unsigned char)str[i++]];
int8_t b = decode_table[(unsigned char)str[i++]];
int8_t c = (str[i] == '=') ? 0 : decode_table[(unsigned char)str[i]]; i++;
int8_t d = (str[i] == '=') ? 0 : decode_table[(unsigned char)str[i]]; i++;
if (a < 0 || b < 0 || (str[i-2] != '=' && c < 0) || (str[i-1] != '=' && d < 0)) {
free(out);
return js_mkerr(js, "atob: invalid character in base64 string");
}
uint32_t triple = ((uint32_t)a << 18) | ((uint32_t)b << 12) | ((uint32_t)c << 6) | (uint32_t)d;
if (j < out_len) out[j++] = (triple >> 16) & 0xFF;
if (j < out_len) out[j++] = (triple >> 8) & 0xFF;
if (j < out_len) out[j++] = triple & 0xFF;
}
jsval_t result = js_mkstr(js, out, out_len);
free(out);
return result;
}
static jsval_t builtin_resolve_internal(struct js *js, jsval_t *args, int nargs);
static jsval_t builtin_reject_internal(struct js *js, jsval_t *args, int nargs);
static void resolve_promise(struct js *js, jsval_t p, jsval_t val);
static void reject_promise(struct js *js, jsval_t p, jsval_t val);
static size_t strpromise(struct js *js, jsval_t value, char *buf, size_t len) {
uint32_t pid = get_promise_id(js, value);
promise_data_entry_t *pd = get_promise_data(pid, 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); }
size_t result = (pd && pd->trigger_pid)
? (size_t)snprintf(buf, len, "Promise {\n %s,\n Symbol(async_id): %u,\n Symbol(trigger_async_id): %u\n}", content, pid, pd->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 promise_data_entry_t *get_promise_data(uint32_t promise_id, bool create) {
promise_data_entry_t *entry = NULL;
HASH_FIND(hh, promise_registry, &promise_id, sizeof(uint32_t), entry);
if (entry) return entry;
if (!create) return NULL;
entry = (promise_data_entry_t *)malloc(sizeof(promise_data_entry_t));
entry->promise_id = promise_id;
entry->trigger_pid = 0;
entry->obj_offset = 0;
entry->state = 0;
entry->value = js_mkundef();
entry->has_rejection_handler = false;
utarray_new(entry->handlers, &promise_handler_icd);
HASH_ADD(hh, promise_registry, promise_id, sizeof(uint32_t), entry);
return entry;
}
static uint32_t get_promise_id(struct js *js, jsval_t p) {
jsval_t p_obj = mkval(T_OBJ, vdata(p));
jsval_t pid_val = get_slot(js, p_obj, SLOT_PID);
if (vtype(pid_val) == T_UNDEF) return 0;
return (uint32_t)tod(pid_val);
}
static jsval_t mkpromise(struct js *js) {
jsval_t obj = mkobj(js, 0);
if (is_err(obj)) return obj;
uint32_t pid = next_promise_id++;
set_slot(js, obj, SLOT_PID, tov((double)pid));
promise_data_entry_t *pd = get_promise_data(pid, true);
if (pd) pd->obj_offset = (jsoff_t)vdata(obj);
return mkval(T_PROMISE, vdata(obj));
}
static inline void trigger_handlers(struct js *js, jsval_t p) {
uint32_t pid = get_promise_id(js, p);
queue_promise_trigger(pid);
}
void js_process_promise_handlers(struct js *js, uint32_t pid) {
promise_data_entry_t *pd = get_promise_data(pid, false);
if (!pd) return;
int state = pd->state;
jsval_t val = pd->value;
unsigned int len = utarray_len(pd->handlers);
if (len == 0) { return; } pd->processing = true;
for (unsigned int i = 0; i < len; i++) {
promise_handler_t *h = (promise_handler_t *)utarray_eltptr(pd->handlers, i);
jsval_t handler = (state == 1) ? h->onFulfilled : h->onRejected;
if (vtype(handler) == T_FUNC || vtype(handler) == T_CFUNC) {
jsval_t res;
if (vtype(handler) == T_CFUNC) {
jsval_t (*fn)(struct js *, jsval_t *, int) = (jsval_t(*)(struct js *, jsval_t *, int)) vdata(handler);
res = fn(js, &val, 1);
} else {
jsval_t call_args[] = { val };
res = js_call(js, handler, call_args, 1);
}
if (is_err(res)) {
jsval_t reject_val = js->thrown_value;
if (vtype(reject_val) == T_UNDEF) {
reject_val = js_mkstr(js, js->errmsg, strlen(js->errmsg));
}
js->thrown_value = js_mkundef();
reject_promise(js, h->nextPromise, reject_val);
} else resolve_promise(js, h->nextPromise, res);
} else {
if (state == 1) resolve_promise(js, h->nextPromise, val);
else reject_promise(js, h->nextPromise, val);
}
}
pd->processing = false;
utarray_clear(pd->handlers);
}
static void resolve_promise(struct js *js, jsval_t p, jsval_t val) {
uint32_t pid = get_promise_id(js, p);
promise_data_entry_t *pd = get_promise_data(pid, false);
if (!pd || pd->state != 0) return;
if (vtype(val) == T_PROMISE) {
uint32_t val_pid = get_promise_id(js, val);
if (val_pid == pid) {
jsval_t err = js_mkerr(js, "TypeError: Chaining cycle");
return reject_promise(js, p, err);
}
jsval_t res_obj = mkobj(js, 0);
set_slot(js, res_obj, SLOT_CFUNC, js_mkfun(builtin_resolve_internal));
set_slot(js, res_obj, SLOT_DATA, p);
jsval_t res_fn = mkval(T_FUNC, vdata(res_obj));
jsval_t rej_obj = mkobj(js, 0);
set_slot(js, rej_obj, SLOT_CFUNC, js_mkfun(builtin_reject_internal));
set_slot(js, rej_obj, SLOT_DATA, p);
jsval_t rej_fn = mkval(T_FUNC, vdata(rej_obj));
jsval_t call_args[] = { res_fn, rej_fn };
jsval_t then_prop = js_get(js, val, "then");
if (vtype(then_prop) == T_FUNC || vtype(then_prop) == T_CFUNC) {
(void)js_call_with_this(js, then_prop, val, call_args, 2); return;
}
}
pd->state = 1;
pd->value = val;
trigger_handlers(js, p);
}
static void reject_promise(struct js *js, jsval_t p, jsval_t val) {
uint32_t pid = get_promise_id(js, p);
promise_data_entry_t *pd = get_promise_data(pid, false);
if (!pd || pd->state != 0) return;
pd->state = 2;
pd->value = val;
if (!pd->has_rejection_handler) {
promise_data_entry_t *existing = NULL;
HASH_FIND(hh_unhandled, unhandled_rejections, &pd->promise_id, sizeof(uint32_t), existing);
if (!existing) {
HASH_ADD(hh_unhandled, unhandled_rejections, promise_id, sizeof(uint32_t), pd);
}
}
trigger_handlers(js, p);
}
static jsval_t builtin_resolve_internal(struct js *js, jsval_t *args, int nargs) {
jsval_t me = js->current_func;
jsval_t p = get_slot(js, me, SLOT_DATA);
if (vtype(p) != T_PROMISE) return js_mkundef();
resolve_promise(js, p, nargs > 0 ? args[0] : js_mkundef());
return js_mkundef();
}
static jsval_t builtin_reject_internal(struct js *js, jsval_t *args, int nargs) {
jsval_t me = js->current_func;
jsval_t p = get_slot(js, me, SLOT_DATA);
if (vtype(p) != T_PROMISE) return js_mkundef();
reject_promise(js, p, nargs > 0 ? args[0] : js_mkundef());
return js_mkundef();
}
static jsval_t builtin_Promise(struct js *js, jsval_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);
}
jsval_t p = mkpromise(js);
jsval_t new_target = js->new_target;
if (vtype(new_target) == T_FUNC) {
jsoff_t proto_off = lkp_interned(js, mkval(T_OBJ, vdata(new_target)), INTERN_PROTOTYPE, 9);
jsval_t subclass_proto = proto_off ? resolveprop(js, mkval(T_PROP, proto_off)) : js_mkundef();
if (vtype(subclass_proto) == T_OBJ) {
jsval_t p_obj = mkval(T_OBJ, vdata(p));
set_slot(js, p_obj, SLOT_PROTO, subclass_proto);
set_slot(js, p_obj, SLOT_CTOR, new_target);
}
}
jsval_t res_obj = mkobj(js, 0);
set_slot(js, res_obj, SLOT_CFUNC, js_mkfun(builtin_resolve_internal));
set_slot(js, res_obj, SLOT_DATA, p);
jsval_t res_fn = mkval(T_FUNC, vdata(res_obj));
jsval_t rej_obj = mkobj(js, 0);
set_slot(js, rej_obj, SLOT_CFUNC, js_mkfun(builtin_reject_internal));
set_slot(js, rej_obj, SLOT_DATA, p);
jsval_t rej_fn = mkval(T_FUNC, vdata(rej_obj));
jsval_t exec_args[] = { res_fn, rej_fn };
js_call(js, args[0], exec_args, 2);
return p;
}
static jsval_t builtin_Promise_resolve(struct js *js, jsval_t *args, int nargs) {
jsval_t val = nargs > 0 ? args[0] : js_mkundef();
if (vtype(val) == T_PROMISE) return val;
jsval_t p = mkpromise(js);
resolve_promise(js, p, val);
return p;
}
static jsval_t builtin_Promise_reject(struct js *js, jsval_t *args, int nargs) {
jsval_t val = nargs > 0 ? args[0] : js_mkundef();
jsval_t p = mkpromise(js);
reject_promise(js, p, val);
return p;
}
static jsval_t builtin_promise_then(struct js *js, jsval_t *args, int nargs) {
jsval_t p = js->this_val;
if (vtype(p) != T_PROMISE) return js_mkerr(js, "not a promise");
jsval_t nextP = mkpromise(js);
jsval_t p_proto = get_slot(js, mkval(T_OBJ, vdata(p)), SLOT_PROTO);
if (vtype(p_proto) == T_OBJ) {
set_slot(js, mkval(T_OBJ, vdata(nextP)), SLOT_PROTO, p_proto);
jsval_t p_ctor = get_slot(js, mkval(T_OBJ, vdata(p)), SLOT_CTOR);
if (vtype(p_ctor) == T_FUNC) set_slot(js, mkval(T_OBJ, vdata(nextP)), SLOT_CTOR, p_ctor);
}
jsval_t onFulfilled = nargs > 0 ? args[0] : js_mkundef();
jsval_t onRejected = nargs > 1 ? args[1] : js_mkundef();
uint32_t pid = get_promise_id(js, p);
uint32_t next_pid = get_promise_id(js, nextP);
promise_data_entry_t *next_pd = get_promise_data(next_pid, false);
if (next_pd) next_pd->trigger_pid = pid;
promise_data_entry_t *pd = get_promise_data(pid, false);
if (pd) {
promise_handler_t h = { onFulfilled, onRejected, nextP };
utarray_push_back(pd->handlers, &h);
if (vtype(onRejected) == T_FUNC || vtype(onRejected) == T_CFUNC) {
pd->has_rejection_handler = true;
promise_data_entry_t *in_unhandled = NULL;
HASH_FIND(hh_unhandled, unhandled_rejections, &pd->promise_id, sizeof(uint32_t), in_unhandled);
if (in_unhandled) HASH_DELETE(hh_unhandled, unhandled_rejections, pd);
}
}
if (pd && pd->state != 0) trigger_handlers(js, p);
return nextP;
}
static jsval_t builtin_promise_catch(struct js *js, jsval_t *args, int nargs) {
jsval_t args_then[] = { js_mkundef(), nargs > 0 ? args[0] : js_mkundef() };
return builtin_promise_then(js, args_then, 2);
}
static jsval_t finally_value_thunk(struct js *js, jsval_t *args, int nargs) {
jsval_t me = js->current_func;
return get_slot(js, me, SLOT_DATA);
}
static jsval_t finally_thrower(struct js *js, jsval_t *args, int nargs) {
jsval_t me = js->current_func;
jsval_t reason = get_slot(js, me, SLOT_DATA);
jsval_t rejected = js_mkpromise(js);
js_reject_promise(js, rejected, reason);
return rejected;
}
static jsval_t finally_identity_reject(struct js *js, jsval_t *args, int nargs) {
jsval_t reason = nargs > 0 ? args[0] : js_mkundef();
jsval_t rejected = js_mkpromise(js);
js_reject_promise(js, rejected, reason);
return rejected;
}
static jsval_t finally_fulfilled_wrapper(struct js *js, jsval_t *args, int nargs) {
jsval_t me = js->current_func;
jsval_t callback = get_slot(js, me, SLOT_DATA);
jsval_t value = nargs > 0 ? args[0] : js_mkundef();
jsval_t result = js_mkundef();
if (vtype(callback) == T_FUNC || vtype(callback) == T_CFUNC) {
result = js_call(js, callback, NULL, 0);
if (is_err(result)) return result;
}
if (vtype(result) == T_PROMISE || (vtype(result) == T_OBJ && vtype(js_get(js, result, "then")) == T_FUNC)) {
jsval_t thunk_obj = mkobj(js, 0);
set_slot(js, thunk_obj, SLOT_CFUNC, js_mkfun(finally_value_thunk));
set_slot(js, thunk_obj, SLOT_DATA, value);
jsval_t thunk_fn = mkval(T_FUNC, vdata(thunk_obj));
jsval_t identity_rej_fn = js_mkfun(finally_identity_reject);
jsval_t then_fn = js_get(js, result, "then");
jsval_t call_args[] = { thunk_fn, identity_rej_fn };
return js_call_with_this(js, then_fn, result, call_args, 2);
}
return value;
}
static jsval_t finally_rejected_wrapper(struct js *js, jsval_t *args, int nargs) {
jsval_t me = js->current_func;
jsval_t callback = get_slot(js, me, SLOT_DATA);
jsval_t reason = nargs > 0 ? args[0] : js_mkundef();
jsval_t result = js_mkundef();
if (vtype(callback) == T_FUNC || vtype(callback) == T_CFUNC) {
result = js_call(js, callback, NULL, 0);
if (is_err(result)) return result;
}
if (vtype(result) == T_PROMISE || (vtype(result) == T_OBJ && vtype(js_get(js, result, "then")) == T_FUNC)) {
jsval_t thrower_obj = mkobj(js, 0);
set_slot(js, thrower_obj, SLOT_CFUNC, js_mkfun(finally_thrower));
set_slot(js, thrower_obj, SLOT_DATA, reason);
jsval_t thrower_fn = mkval(T_FUNC, vdata(thrower_obj));
jsval_t identity_rej_fn = js_mkfun(finally_identity_reject);
jsval_t then_prop = js_get(js, result, "then");
jsval_t call_args[] = { thrower_fn, identity_rej_fn };
return js_call_with_this(js, then_prop, result, call_args, 2);
}
jsval_t rejected = js_mkpromise(js);
js_reject_promise(js, rejected, reason);
return rejected;
}
static jsval_t builtin_promise_finally(struct js *js, jsval_t *args, int nargs) {
jsval_t callback = nargs > 0 ? args[0] : js_mkundef();
jsval_t fulfilled_obj = mkobj(js, 0);
set_slot(js, fulfilled_obj, SLOT_CFUNC, js_mkfun(finally_fulfilled_wrapper));
set_slot(js, fulfilled_obj, SLOT_DATA, callback);
jsval_t fulfilled_fn = mkval(T_FUNC, vdata(fulfilled_obj));
jsval_t rejected_obj = mkobj(js, 0);
set_slot(js, rejected_obj, SLOT_CFUNC, js_mkfun(finally_rejected_wrapper));
set_slot(js, rejected_obj, SLOT_DATA, callback);
jsval_t rejected_fn = mkval(T_FUNC, vdata(rejected_obj));
jsval_t args_then[] = { fulfilled_fn, rejected_fn };
return builtin_promise_then(js, args_then, 2);
}
static jsval_t builtin_Promise_try(struct js *js, jsval_t *args, int nargs) {
if (nargs == 0) return builtin_Promise_resolve(js, args, 0);
jsval_t fn = args[0];
jsval_t *call_args = nargs > 1 ? &args[1] : NULL;
int call_nargs = nargs > 1 ? nargs - 1 : 0;
jsval_t res = js_call_with_this(js, fn, js_mkundef(), call_args, call_nargs);
if (is_err(res)) {
jsval_t reject_val = js->thrown_value;
if (vtype(reject_val) == T_UNDEF) {
reject_val = js_mkstr(js, js->errmsg, strlen(js->errmsg));
}
js->thrown_value = js_mkundef();
jsval_t rej_args[] = { reject_val };
return builtin_Promise_reject(js, rej_args, 1);
}
jsval_t res_args[] = { res };
return builtin_Promise_resolve(js, res_args, 1);
}
static jsval_t builtin_Promise_all_resolve_handler(struct js *js, jsval_t *args, int nargs) {
jsval_t me = js->current_func;
jsval_t tracker = js_get(js, me, "tracker");
jsval_t index_val = js_get(js, me, "index");
int index = (int)tod(index_val);
jsval_t value = nargs > 0 ? args[0] : js_mkundef();
jsval_t results = js_get(js, tracker, "results");
arr_set(js, results, (jsoff_t)index, value);
jsval_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) {
jsval_t result_promise = get_slot(js, tracker, SLOT_DATA);
resolve_promise(js, result_promise, mkval(T_ARR, vdata(results)));
}
return js_mkundef();
}
static jsval_t builtin_Promise_all_reject_handler(struct js *js, jsval_t *args, int nargs) {
jsval_t me = js->current_func;
jsval_t tracker = js_get(js, me, "tracker");
jsval_t result_promise = get_slot(js, tracker, SLOT_DATA);
jsval_t reason = nargs > 0 ? args[0] : js_mkundef();
reject_promise(js, result_promise, reason);
return js_mkundef();
}
typedef struct {
jsval_t tracker;
int index;
} promise_all_iter_ctx_t;
static iter_action_t promise_all_iter_cb(struct js *js, jsval_t value, void *ctx, jsval_t *out) {
promise_all_iter_ctx_t *pctx = (promise_all_iter_ctx_t *)ctx;
jsval_t item = value;
if (vtype(item) != T_PROMISE) {
jsval_t wrap_args[] = { item };
item = builtin_Promise_resolve(js, wrap_args, 1);
}
jsval_t resolve_obj = mkobj(js, 0);
set_slot(js, 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);
jsval_t resolve_fn = mkval(T_FUNC, vdata(resolve_obj));
jsval_t reject_obj = mkobj(js, 0);
set_slot(js, reject_obj, SLOT_CFUNC, js_mkfun(builtin_Promise_all_reject_handler));
js_setprop(js, reject_obj, js_mkstr(js, "tracker", 7), pctx->tracker);
jsval_t reject_fn = mkval(T_FUNC, vdata(reject_obj));
jsval_t then_args[] = { resolve_fn, reject_fn };
jsval_t saved_this = js->this_val;
js->this_val = item;
builtin_promise_then(js, then_args, 2);
js->this_val = saved_this;
pctx->index++;
return ITER_CONTINUE;
}
static jsval_t builtin_Promise_all(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "Promise.all requires an iterable");
jsval_t iterable = args[0];
uint8_t t = vtype(iterable);
if (t != T_ARR && t != T_OBJ) return js_mkerr(js, "Promise.all requires an iterable");
jsval_t result_promise = mkpromise(js);
jsval_t tracker = mkobj(js, 0);
jsval_t results = mkarr(js);
js_setprop(js, tracker, js_mkstr(js, "remaining", 9), tov(0.0));
js_setprop(js, tracker, js_mkstr(js, "results", 7), results);
set_slot(js, tracker, SLOT_DATA, result_promise);
promise_all_iter_ctx_t ctx = { .tracker = tracker, .index = 0 };
jsval_t iter_result = iter_foreach(js, iterable, promise_all_iter_cb, &ctx);
if (is_err(iter_result)) return iter_result;
int len = ctx.index;
{
jsoff_t doff = get_dense_buf(js, results);
if (doff) {
if ((jsoff_t)len > dense_capacity(js, doff)) doff = dense_grow(js, results, (jsoff_t)len);
if (doff) dense_set_length(js, doff, (jsoff_t)len);
}
}
if (len == 0) {
jsval_t resolve_args[] = { mkval(T_ARR, vdata(results)) };
return builtin_Promise_resolve(js, resolve_args, 1);
}
js_setprop(js, tracker, js_mkstr(js, "remaining", 9), tov((double)len));
return result_promise;
}
typedef struct {
jsval_t result_promise;
jsval_t resolve_fn;
jsval_t reject_fn;
bool settled;
} promise_race_iter_ctx_t;
static iter_action_t promise_race_iter_cb(struct js *js, jsval_t value, void *ctx, jsval_t *out) {
promise_race_iter_ctx_t *pctx = (promise_race_iter_ctx_t *)ctx;
jsval_t item = value;
if (vtype(item) != T_PROMISE) {
resolve_promise(js, pctx->result_promise, item);
pctx->settled = true;
return ITER_BREAK;
}
uint32_t item_pid = get_promise_id(js, item);
promise_data_entry_t *pd = get_promise_data(item_pid, false);
if (pd) {
if (pd->state == 1) {
resolve_promise(js, pctx->result_promise, pd->value);
pctx->settled = true;
return ITER_BREAK;
} else if (pd->state == 2) {
reject_promise(js, pctx->result_promise, pd->value);
pctx->settled = true;
return ITER_BREAK;
}
}
jsval_t then_args[] = { pctx->resolve_fn, pctx->reject_fn };
jsval_t saved_this = js->this_val;
js->this_val = item;
builtin_promise_then(js, then_args, 2);
js->this_val = saved_this;
return ITER_CONTINUE;
}
static jsval_t builtin_Promise_race(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "Promise.race requires an iterable");
jsval_t iterable = args[0];
uint8_t t = vtype(iterable);
if (t != T_ARR && t != T_OBJ) return js_mkerr(js, "Promise.race requires an iterable");
jsval_t result_promise = mkpromise(js);
jsval_t resolve_obj = mkobj(js, 0);
set_slot(js, resolve_obj, SLOT_CFUNC, js_mkfun(builtin_resolve_internal));
set_slot(js, resolve_obj, SLOT_DATA, result_promise);
jsval_t resolve_fn = mkval(T_FUNC, vdata(resolve_obj));
jsval_t reject_obj = mkobj(js, 0);
set_slot(js, reject_obj, SLOT_CFUNC, js_mkfun(builtin_reject_internal));
set_slot(js, reject_obj, SLOT_DATA, result_promise);
jsval_t reject_fn = mkval(T_FUNC, vdata(reject_obj));
promise_race_iter_ctx_t ctx = {
.result_promise = result_promise,
.resolve_fn = resolve_fn,
.reject_fn = reject_fn,
.settled = false
};
jsval_t iter_result = iter_foreach(js, iterable, promise_race_iter_cb, &ctx);
if (is_err(iter_result)) return iter_result;
return result_promise;
}
static jsval_t mk_aggregate_error(struct js *js, jsval_t errors) {
jsval_t args[] = { errors, js_mkstr(js, "All promises were rejected", 26) };
jsoff_t off = lkp(js, js_glob(js), "AggregateError", 14);
jsval_t ctor = off ? resolveprop(js, mkval(T_PROP, off)) : js_mkundef();
return js_call(js, ctor, args, 2);
}
static bool promise_any_try_resolve(struct js *js, jsval_t tracker, jsval_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(js, tracker, SLOT_DATA), value);
return true;
}
static void promise_any_record_rejection(struct js *js, jsval_t tracker, int index, jsval_t reason) {
jsval_t errors = resolveprop(js, js_get(js, tracker, "errors"));
arr_set(js, errors, (jsoff_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(js, tracker, SLOT_DATA), mk_aggregate_error(js, errors));
}
static jsval_t builtin_Promise_any_resolve_handler(struct js *js, jsval_t *args, int nargs) {
jsval_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 jsval_t builtin_Promise_any_reject_handler(struct js *js, jsval_t *args, int nargs) {
jsval_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 jsval_t builtin_Promise_any(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "Promise.any requires an array");
jsval_t arr = args[0];
if (vtype(arr) != T_ARR) return js_mkerr(js, "Promise.any requires an array");
int len = (int)get_array_length(js, arr);
if (len == 0) {
jsval_t reject_args[] = { mk_aggregate_error(js, mkarr(js)) };
return builtin_Promise_reject(js, reject_args, 1);
}
jsval_t result_promise = mkpromise(js);
jsval_t tracker = mkobj(js, 0);
jsval_t errors = mkarr(js);
set_slot(js, 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);
{
jsoff_t doff = get_dense_buf(js, errors);
if (doff) {
if ((jsoff_t)len > dense_capacity(js, doff)) doff = dense_grow(js, errors, (jsoff_t)len);
if (doff) dense_set_length(js, doff, (jsoff_t)len);
}
}
for (int i = 0; i < len; i++) {
jsval_t item = arr_get(js, arr, (jsoff_t)i);
item = resolveprop(js, item);
if (vtype(item) != T_PROMISE) {
promise_any_try_resolve(js, tracker, item);
return result_promise;
}
uint32_t item_pid = get_promise_id(js, item);
promise_data_entry_t *pd = get_promise_data(item_pid, false);
if (pd) {
pd->has_rejection_handler = true;
promise_data_entry_t *in_unhandled = NULL;
HASH_FIND(hh_unhandled, unhandled_rejections, &pd->promise_id, sizeof(uint32_t), in_unhandled);
if (in_unhandled) HASH_DELETE(hh_unhandled, unhandled_rejections, pd);
if (pd->state == 1) {
promise_any_try_resolve(js, tracker, pd->value);
return result_promise;
} else if (pd->state == 2) {
promise_any_record_rejection(js, tracker, i, pd->value);
continue;
}
}
jsval_t resolve_obj = mkobj(js, 0);
set_slot(js, resolve_obj, SLOT_CFUNC, js_mkfun(builtin_Promise_any_resolve_handler));
js_setprop(js, resolve_obj, js_mkstr(js, "tracker", 7), tracker);
jsval_t reject_obj = mkobj(js, 0);
set_slot(js, 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);
jsval_t then_args[] = { mkval(T_FUNC, vdata(resolve_obj)), mkval(T_FUNC, vdata(reject_obj)) };
jsval_t saved_this = js->this_val;
js->this_val = item;
builtin_promise_then(js, then_args, 2);
js->this_val = saved_this;
}
return result_promise;
}
static jsval_t handle_proxy_instanceof(struct js *js, jsval_t l, jsval_t r, uint8_t ltype) {
jsval_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");
}
const char *hi_key = get_hasInstance_sym_key();
if (hi_key && *hi_key) proxy_get(js, r, hi_key, strlen(hi_key));
jsval_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);
}
jsval_t current = get_proto(js, l);
for (int depth = 0; vtype(current) != T_NULL && depth < 32; depth++) {
if (vdata(current) == vdata(proto_val)) {
return mkval(T_BOOL, 1);
}
current = get_proto(js, current);
}
return mkval(T_BOOL, 0);
}
static jsval_t handle_cfunc_instanceof(jsval_t l, jsval_t r, uint8_t ltype) {
jsval_t (*fn)(struct js *, jsval_t *, int) = (jsval_t(*)(struct js *, jsval_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 jsval_t walk_prototype_chain(struct js *js, jsval_t l, jsval_t ctor_proto) {
jsval_t current = get_proto(js, l);
const int MAX_DEPTH = 32;
for (int depth = 0; vtype(current) != T_NULL && depth < MAX_DEPTH; depth++) {
if (vdata(current) == vdata(ctor_proto)) return mkval(T_BOOL, 1);
current = get_proto(js, current);
}
return mkval(T_BOOL, 0);
}
static jsval_t do_instanceof(struct js *js, jsval_t l, jsval_t r) {
uint8_t ltype = vtype(l);
uint8_t rtype = vtype(r);
if (rtype != T_FUNC && rtype != T_CFUNC) {
if (is_proxy(js, 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);
}
jsval_t func_obj = mkval(T_OBJ, vdata(r));
jsoff_t proto_off = lkp_interned(js, func_obj, INTERN_PROTOTYPE, 9);
if (proto_off == 0) return mkval(T_BOOL, 0);
jsval_t ctor_proto = resolveprop(js, mkval(T_PROP, 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) {
jsval_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);
}
static jsval_t do_in(struct js *js, jsval_t l, jsval_t r) {
jsoff_t prop_len;
const char *prop_name;
char num_buf[32];
if (vtype(l) == T_STR) {
jsoff_t prop_off = vstr(js, l, &prop_len);
prop_name = (char *) &js->mem[prop_off];
} else if (vtype(l) == T_NUM) {
prop_len = (jsoff_t) strnum(l, num_buf, sizeof(num_buf));
prop_name = num_buf;
} else {
return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot use 'in' operator to search for '%s' in non-object", js_str(js, l));
}
if (vtype(r) != T_OBJ && vtype(r) != T_ARR && vtype(r) != T_FUNC) {
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(js, r)) {
jsval_t result = proxy_has(js, r, prop_name, prop_len);
if (is_err(result)) return result;
return js_bool(js_truthy(js, result));
}
if (vtype(r) == T_ARR) {
unsigned long idx;
jsoff_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, (jsoff_t)idx) ? 1 : 0);
if (prop_len == 6 && memcmp(prop_name, "length", 6) == 0) return mkval(T_BOOL, 1);
}
jsoff_t found = lkp_proto(js, r, prop_name, prop_len);
return mkval(T_BOOL, found != 0 ? 1 : 0);
}
static char *esm_get_extension(const char *path) {
const char *dot = strrchr(path, '.');
const char *slash = strrchr(path, '/');
if (dot && (!slash || dot > slash)) {
return strdup(dot);
}
return strdup(".js");
}
static char *esm_try_resolve(const char *dir, const char *spec, const char *suffix) {
char path[PATH_MAX];
snprintf(path, PATH_MAX, "%s/%s%s", dir, spec, suffix);
char *resolved = realpath(path, NULL);
if (resolved) {
struct stat st;
if (stat(resolved, &st) == 0 && S_ISREG(st.st_mode)) return resolved;
free(resolved);
}
return NULL;
}
static bool esm_has_extension(const char *spec) {
const char *dot = strrchr(spec, '.');
const char *slash = strrchr(spec, '/');
return dot && (!slash || dot > slash);
}
static char *esm_resolve_absolute(const char *specifier) {
char *result;
if ((result = esm_try_resolve("", specifier, ""))) return result;
if (esm_has_extension(specifier)) return NULL;
if ((result = esm_try_resolve("", specifier, ".js"))) return result;
if ((result = esm_try_resolve("", specifier, ".ts"))) return result;
if ((result = esm_try_resolve("", specifier, ".mts"))) return result;
if ((result = esm_try_resolve("", specifier, ".cts"))) return result;
if ((result = esm_try_resolve("", specifier, ".json"))) return result;
return NULL;
}
static char *esm_resolve_path(const char *specifier, const char *base_path) {
if (specifier[0] == '/' && specifier[1] != '.') {
return esm_resolve_absolute(specifier);
}
if (!((specifier[0] == '.' && specifier[1] == '/') ||
(specifier[0] == '.' && specifier[1] == '.' && specifier[2] == '/'))) {
return strdup(specifier);
}
char *base_copy = strdup(base_path);
char *dir = dirname(base_copy);
char *result = NULL;
const char *spec = (specifier[0] == '.' && specifier[1] == '/') ? specifier + 2 : specifier;
bool has_ext = esm_has_extension(spec);
if ((result = esm_try_resolve(dir, spec, ""))) goto cleanup;
if (has_ext) goto cleanup;
char *base_ext = esm_get_extension(base_path);
if ((result = esm_try_resolve(dir, spec, base_ext))) goto cleanup_ext;
if (strcmp(base_ext, ".js") != 0 && (result = esm_try_resolve(dir, spec, ".js"))) goto cleanup_ext;
if ((result = esm_try_resolve(dir, spec, ".ts"))) goto cleanup_ext;
if ((result = esm_try_resolve(dir, spec, ".mts"))) goto cleanup_ext;
if ((result = esm_try_resolve(dir, spec, ".cts"))) goto cleanup_ext;
if ((result = esm_try_resolve(dir, spec, ".json"))) goto cleanup_ext;
char idx[PATH_MAX];
snprintf(idx, PATH_MAX, "%s/index%s", spec, base_ext);
if ((result = esm_try_resolve(dir, idx, ""))) goto cleanup_ext;
if (strcmp(base_ext, ".js") != 0) {
snprintf(idx, PATH_MAX, "%s/index.js", spec);
if ((result = esm_try_resolve(dir, idx, ""))) goto cleanup_ext;
}
snprintf(idx, PATH_MAX, "%s/index.ts", spec);
if ((result = esm_try_resolve(dir, idx, ""))) goto cleanup_ext;
cleanup_ext:
free(base_ext);
cleanup:
free(base_copy);
return result;
}
static bool esm_has_suffix(const char *path, const char *ext) {
size_t len = strlen(path);
size_t elen = strlen(ext);
return len > elen && strcmp(path + len - elen, ext) == 0;
}
static bool esm_is_json(const char *path) {
return esm_has_suffix(path, ".json");
}
static bool esm_is_text(const char *path) {
return esm_has_suffix(path, ".txt") ||
esm_has_suffix(path, ".md") ||
esm_has_suffix(path, ".html") ||
esm_has_suffix(path, ".css");
}
static bool esm_is_image(const char *path) {
return esm_has_suffix(path, ".png") ||
esm_has_suffix(path, ".jpg") ||
esm_has_suffix(path, ".jpeg") ||
esm_has_suffix(path, ".gif") ||
esm_has_suffix(path, ".svg") ||
esm_has_suffix(path, ".webp");
}
static char *esm_canonicalize_path(const char *path) {
if (!path) return NULL;
char *canonical = strdup(path);
if (!canonical) return NULL;
char *src = canonical, *dst = canonical;
while (*src) {
if (*src == '/') {
*dst++ = '/';
while (*src == '/') src++;
if (strncmp(src, "./", 2) == 0) {
src += 2;
} else if (strncmp(src, "../", 3) == 0) {
src += 3;
if (dst > canonical + 1) {
dst--;
while (dst > canonical && *(dst - 1) != '/') dst--;
}
}
} else {
*dst++ = *src++;
}
}
*dst = '\0';
if (strlen(canonical) > 1 && canonical[strlen(canonical) - 1] == '/') {
canonical[strlen(canonical) - 1] = '\0';
}
return canonical;
}
static esm_module_t *esm_find_module(const char *resolved_path) {
char *canonical_path = esm_canonicalize_path(resolved_path);
if (!canonical_path) return NULL;
esm_module_t *mod = NULL;
HASH_FIND_STR(global_module_cache.modules, canonical_path, mod);
free(canonical_path);
return mod;
}
static esm_module_t *esm_create_module(const char *path, const char *resolved_path) {
bool is_url = esm_is_url(resolved_path);
char *canonical_path = is_url ? strdup(resolved_path) : esm_canonicalize_path(resolved_path);
if (!canonical_path) return NULL;
esm_module_t *existing_mod = NULL;
HASH_FIND_STR(global_module_cache.modules, canonical_path, existing_mod);
if (existing_mod) {
free(canonical_path);
return existing_mod;
}
esm_module_t *mod = (esm_module_t *)malloc(sizeof(esm_module_t));
if (!mod) {
free(canonical_path);
return NULL;
}
mod->path = strdup(path);
mod->resolved_path = canonical_path;
mod->namespace_obj = js_mkundef();
mod->default_export = js_mkundef();
mod->is_loaded = false;
mod->is_loading = false;
mod->is_json = esm_is_json(resolved_path);
mod->is_text = esm_is_text(resolved_path);
mod->is_image = esm_is_image(resolved_path);
mod->is_url = is_url;
mod->url_content = NULL;
mod->url_content_len = 0;
mod->next = NULL;
HASH_ADD_STR(global_module_cache.modules, resolved_path, mod);
global_module_cache.count++;
return mod;
}
static void esm_cleanup_module_cache(void) {
esm_module_t *current, *tmp;
HASH_ITER(hh, global_module_cache.modules, current, tmp) {
HASH_DEL(global_module_cache.modules, current);
if (current->path) free(current->path);
if (current->resolved_path) free(current->resolved_path);
if (current->url_content) free(current->url_content);
free(current);
}
global_module_cache.count = 0;
}
typedef struct {
char *data;
size_t size;
} esm_file_data_t;
static jsval_t esm_read_file(struct js *js, const char *path, const char *kind, esm_file_data_t *out) {
FILE *fp = fopen(path, "rb");
if (!fp) return js_mkerr(js, "Cannot open %s: %s", kind, path);
fseek(fp, 0, SEEK_END);
long fsize = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buf = (char *)malloc((size_t)fsize + 1);
if (!buf) {
fclose(fp);
return js_mkerr(js, "OOM loading %s", kind);
}
fread(buf, 1, (size_t)fsize, fp);
fclose(fp);
buf[fsize] = '\0';
out->data = buf;
out->size = (size_t)fsize;
return js_mkundef();
}
static jsval_t esm_load_json(struct js *js, const char *path) {
esm_file_data_t file;
jsval_t err = esm_read_file(js, path, "JSON file", &file);
if (is_err(err)) return err;
jsval_t json_str = js_mkstr(js, file.data, file.size);
free(file.data);
return js_json_parse(js, &json_str, 1);
}
static jsval_t esm_load_text(struct js *js, const char *path) {
esm_file_data_t file;
jsval_t err = esm_read_file(js, path, "text file", &file);
if (is_err(err)) return err;
jsval_t result = js_mkstr(js, file.data, file.size);
free(file.data);
return result;
}
static jsval_t esm_load_image(struct js *js, const char *path) {
esm_file_data_t file;
jsval_t err = esm_read_file(js, path, "image file", &file);
if (is_err(err)) return err;
unsigned char *content = (unsigned char *)file.data;
size_t size = file.size;
jsval_t obj = mkobj(js, 0);
jsval_t data_arr = mkarr(js);
for (size_t i = 0; i < size; i++) {
arr_set(js, data_arr, (jsoff_t)i, tov((double)content[i]));
}
js_setprop(js, obj, js_mkstr(js, "data", 4), mkval(T_ARR, vdata(data_arr)));
js_setprop(js, obj, js_mkstr(js, "path", 4), js_mkstr(js, path, strlen(path)));
js_setprop(js, obj, js_mkstr(js, "size", 4), tov((double)size));
free(file.data);
return obj;
}
static jsval_t esm_load_module(struct js *js, esm_module_t *mod) {
if (mod->is_loaded) return mod->namespace_obj;
if (mod->is_loading) return js_mkerr(js, "Circular dependency detected: %s", mod->path);
mod->is_loading = true;
if (mod->is_json) {
jsval_t json_val = esm_load_json(js, mod->resolved_path);
if (is_err(json_val)) {
mod->is_loading = false;
return json_val;
}
mod->namespace_obj = json_val;
mod->default_export = json_val;
mod->is_loaded = true;
mod->is_loading = false;
return json_val;
}
if (mod->is_text) {
jsval_t text_val = esm_load_text(js, mod->resolved_path);
if (is_err(text_val)) {
mod->is_loading = false;
return text_val;
}
mod->namespace_obj = text_val;
mod->default_export = text_val;
mod->is_loaded = true;
mod->is_loading = false;
return text_val;
}
if (mod->is_image) {
jsval_t img_val = esm_load_image(js, mod->resolved_path);
if (is_err(img_val)) {
mod->is_loading = false;
return img_val;
}
mod->namespace_obj = img_val;
mod->default_export = img_val;
mod->is_loaded = true;
mod->is_loading = false;
return img_val;
}
char *content = NULL;
size_t size = 0;
if (mod->is_url) {
if (mod->url_content) {
content = strdup(mod->url_content);
size = mod->url_content_len;
} else {
char *error = NULL;
content = esm_fetch_url(mod->resolved_path, &size, &error);
if (!content) {
mod->is_loading = false;
jsval_t err = js_mkerr(js, "Cannot fetch module %s: %s", mod->resolved_path, error ? error : "unknown error");
if (error) free(error);
return err;
}
mod->url_content = strdup(content);
mod->url_content_len = size;
}
} else {
esm_file_data_t file;
jsval_t err = esm_read_file(js, mod->resolved_path, "module", &file);
if (is_err(err)) {
mod->is_loading = false;
return err;
}
content = file.data;
size = file.size;
}
content[size] = '\0';
char *js_code = content;
size_t js_len = size;
if (is_typescript_file(mod->resolved_path)) {
int result = OXC_strip_types(content, mod->resolved_path, content, size + 1);
if (result < 0) {
free(content);
mod->is_loading = false;
return js_mkerr(js, "TypeScript error: strip failed (%d)", result);
}
js_len = (size_t)result;
}
jsval_t ns = mkobj(js, 0);
mod->namespace_obj = ns;
jsval_t prev_module = js->module_ns;
js->module_ns = ns;
const char *prev_filename = js->filename;
jsval_t saved_scope = js->scope;
js_set_filename(js, mod->resolved_path);
mkscope(js); set_slot(js, js->scope, SLOT_MODULE_SCOPE, tov(1));
jsval_t result = js_eval_cached(js, js_code, js_len);
free(content);
js->scope = saved_scope;
js_set_filename(js, prev_filename);
js->module_ns = prev_module;
if (is_err(result)) {
mod->is_loading = false;
return result;
}
jsval_t default_val = js_get_slot(js, ns, SLOT_DEFAULT);
mod->default_export = vtype(default_val) != T_UNDEF ? default_val : ns;
mod->is_loaded = true;
mod->is_loading = false;
return ns;
}
static jsval_t esm_get_or_load(struct js *js, const char *specifier, const char *resolved_path) {
esm_module_t *mod = esm_find_module(resolved_path);
if (!mod) {
mod = esm_create_module(specifier, resolved_path);
if (!mod) return js_mkerr(js, "Cannot create module");
}
return esm_load_module(js, mod);
}
typedef struct {
const char *import_name;
size_t import_len;
const char *local_name;
size_t local_len;
} esm_import_binding_t;
static char *esm_jsval_to_cstr(struct js *js, jsval_t str, jsoff_t *out_len) {
jsoff_t len;
jsoff_t off = vstr(js, str, &len);
if (out_len) *out_len = len;
return strndup((char *)&js->mem[off], len);
}
static jsval_t esm_resolve_and_load(struct js *js, const char *spec_str, jsoff_t spec_len) {
ant_library_t *lib = find_library(spec_str, spec_len);
if (lib) return lib->init_fn(js);
const char *base_path = js->filename ? js->filename : ".";
char *resolved_path = esm_resolve(spec_str, base_path, esm_resolve_path);
if (!resolved_path) return js_mkerr(js, "Cannot resolve module: %s", spec_str);
jsval_t ns = esm_get_or_load(js, spec_str, resolved_path);
free(resolved_path);
return ns;
}
static jsval_t esm_make_file_url(struct js *js, const char *path) {
size_t url_len = strlen(path) + 8;
char *url = malloc(url_len);
if (!url) return js_mkerr(js, "oom");
snprintf(url, url_len, "file://%s", path);
jsval_t val = js_mkstr(js, url, strlen(url));
free(url);
return val;
}
static int esm_parse_named_imports(struct js *js, esm_import_binding_t *bindings, int max_bindings) {
int count = 0;
while (next(js) != TOK_RBRACE && count < max_bindings) {
if (next(js) != TOK_IDENTIFIER && next(js) != TOK_DEFAULT) {
return -1;
}
const char *import_name = &js->code[js->toff];
size_t import_len = js->tlen;
js->consumed = 1;
const char *local_name = import_name;
size_t local_len = import_len;
if (next(js) == TOK_AS) {
js->consumed = 1;
if (next(js) != TOK_IDENTIFIER && next(js) != TOK_DEFAULT) {
return -1;
}
local_name = &js->code[js->toff];
local_len = js->tlen;
js->consumed = 1;
}
bindings[count].import_name = import_name;
bindings[count].import_len = import_len;
bindings[count].local_name = local_name;
bindings[count].local_len = local_len;
count++;
if (next(js) == TOK_COMMA) js->consumed = 1;
}
if (next(js) != TOK_RBRACE) return -1;
js->consumed = 1;
return count;
}
static jsval_t builtin_import(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1 || vtype(args[0]) != T_STR) {
return js_mkerr(js, "import() requires a string specifier");
}
jsoff_t spec_len;
char *specifier = esm_jsval_to_cstr(js, args[0], &spec_len);
jsval_t ns = esm_resolve_and_load(js, specifier, spec_len);
free(specifier);
if (is_err(ns)) return builtin_Promise_reject(js, &ns, 1);
jsval_t promise_args[] = { ns };
return builtin_Promise_resolve(js, promise_args, 1);
}
static jsval_t builtin_import_meta_resolve(struct js *js, jsval_t *args, int nargs) {
if (nargs < 1 || vtype(args[0]) != T_STR) {
return js_mkerr(js, "import.meta.resolve() requires a string specifier");
}
char *specifier = esm_jsval_to_cstr(js, args[0], NULL);
const char *base_path = js->filename ? js->filename : ".";
char *resolved_path = esm_resolve(specifier, base_path, esm_resolve_path);
if (!resolved_path) {
jsval_t err = js_mkerr(js, "Cannot resolve module: %s", specifier);
free(specifier); return err;
}
free(specifier);
if (esm_is_url(resolved_path)) {
jsval_t result = js_mkstr(js, resolved_path, strlen(resolved_path));
free(resolved_path); return result;
}
jsval_t result = esm_make_file_url(js, resolved_path);
free(resolved_path);
return result;
}
void js_setup_import_meta(struct js *js, const char *filename) {
if (!filename) return;
jsval_t import_meta = mkobj(js, 0);
if (is_err(import_meta)) return;
bool is_url = esm_is_url(filename);
jsval_t url_val = is_url ? js_mkstr(js, filename, strlen(filename)) : esm_make_file_url(js, filename);
if (!is_err(url_val)) js_setprop(js, import_meta, js_mkstr(js, "url", 3), url_val);
jsval_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';
jsval_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) {
jsval_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), js_true);
jsval_t resolve_fn = js_mkfun(builtin_import_meta_resolve);
js_setprop(js, import_meta, js_mkstr(js, "resolve", 7), resolve_fn);
jsval_t glob = js_glob(js);
jsoff_t import_off = lkp(js, glob, "import", 6);
if (import_off != 0) {
jsval_t import_fn = resolveprop(js, mkval(T_PROP, import_off));
if (vtype(import_fn) == T_FUNC) {
jsval_t import_obj = mkval(T_OBJ, vdata(import_fn));
js_setprop(js, import_obj, js_mkstr(js, "meta", 4), import_meta);
}
}
}
static jsval_t js_import_stmt(struct js *js) {
js->consumed = 1;
if (next(js) == TOK_LPAREN) {
js->consumed = 1;
jsval_t spec = js_expr(js);
EXPECT(TOK_RPAREN);
if (vtype(spec) != T_STR) {
return js_mkerr(js, "import() requires string");
}
jsval_t args[] = { spec };
return builtin_import(js, args, 1);
}
if (next(js) == TOK_MUL) {
js->consumed = 1;
EXPECT(TOK_AS);
EXPECT(TOK_IDENTIFIER);
const char *namespace_name = &js->code[js->toff];
size_t namespace_len = js->tlen;
js->consumed = 1;
EXPECT(TOK_FROM);
EXPECT(TOK_STRING);
jsval_t spec = js_str_literal(js);
jsoff_t spec_len;
char *spec_str = esm_jsval_to_cstr(js, spec, &spec_len);
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
jsval_t ns = esm_resolve_and_load(js, spec_str, spec_len);
JS_RESTORE_STATE(js, saved);
free(spec_str);
js->consumed = 1; next(js); js->consumed = 0;
if (is_err(ns)) return ns;
if (vtype(ns) != T_OBJ) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "Cannot re-export from non-object module");
}
js_setprop(js, js->scope, js_mkstr(js, namespace_name, namespace_len), ns);
return js_mkundef();
}
if (next(js) == TOK_IDENTIFIER) {
const char *default_name = &js->code[js->toff];
size_t default_len = js->tlen;
js->consumed = 1;
esm_import_binding_t bindings[64];
int binding_count = 0;
if (next(js) == TOK_COMMA) {
js->consumed = 1;
if (next(js) == TOK_LBRACE) {
js->consumed = 1;
binding_count = esm_parse_named_imports(js, bindings, 64);
if (binding_count < 0) return js_mkerr(js, "Failed to parse named imports");
} else if (next(js) == TOK_MUL) {
js->consumed = 1;
EXPECT(TOK_AS);
EXPECT(TOK_IDENTIFIER);
bindings[binding_count].import_name = NULL;
bindings[binding_count].import_len = 0;
bindings[binding_count].local_name = &js->code[js->toff];
bindings[binding_count].local_len = js->tlen;
binding_count++;
js->consumed = 1;
} else {
return js_mkerr(js, "Expected '{' or '*' after ',' in import");
}
}
EXPECT(TOK_FROM);
EXPECT(TOK_STRING);
jsval_t spec = js_str_literal(js);
jsoff_t spec_len;
char *spec_str = esm_jsval_to_cstr(js, spec, &spec_len);
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
jsval_t ns = esm_resolve_and_load(js, spec_str, spec_len);
JS_RESTORE_STATE(js, saved);
free(spec_str);
js->consumed = 1; next(js); js->consumed = 0;
if (is_err(ns)) return ns;
jsval_t default_val;
if (vtype(ns) == T_OBJ) {
jsval_t slot_val = js_get_slot(js, ns, SLOT_DEFAULT);
default_val = vtype(slot_val) != T_UNDEF ? slot_val : ns;
} else default_val = ns;
js_setprop(js, js->scope, js_mkstr(js, default_name, default_len), default_val);
for (int i = 0; i < binding_count; i++) {
if (bindings[i].import_name == NULL) {
js_setprop(js, js->scope, js_mkstr(js, bindings[i].local_name, bindings[i].local_len), ns);
} else if (vtype(ns) == T_OBJ) {
jsoff_t prop_off = lkp(js, ns, bindings[i].import_name, bindings[i].import_len);
if (prop_off == 0) return js_mkerr_typed(
js, JS_ERR_SYNTAX, "The requested module does not provide an export named '%.*s'",
(int)bindings[i].import_len, bindings[i].import_name
);
jsval_t imported_val = resolveprop(js, mkval(T_PROP, prop_off));
js_setprop(js, js->scope, js_mkstr(js, bindings[i].local_name, bindings[i].local_len), imported_val);
} else return js_mkerr_typed(js, JS_ERR_SYNTAX, "Cannot use named imports from non-object module");
}
return js_mkundef();
}
if (next(js) == TOK_LBRACE) {
js->consumed = 1;
esm_import_binding_t bindings[64];
int binding_count = esm_parse_named_imports(js, bindings, 64);
if (binding_count < 0) return js_mkerr(js, "Failed to parse named imports");
EXPECT(TOK_FROM);
EXPECT(TOK_STRING);
jsval_t spec = js_str_literal(js);
jsoff_t spec_len;
char *spec_str = esm_jsval_to_cstr(js, spec, &spec_len);
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
jsoff_t saved_toff = js->toff, saved_tlen = js->tlen;
jsval_t saved_scope = js->scope;
jsval_t ns = esm_resolve_and_load(js, spec_str, spec_len);
JS_RESTORE_STATE(js, saved);
js->toff = saved_toff;
js->tlen = saved_tlen;
js->scope = saved_scope;
free(spec_str);
js->consumed = 1;
next(js);
js->consumed = 0;
if (is_err(ns)) return ns;
if (vtype(ns) != T_OBJ) return js_mkerr(js, "Cannot use named imports from non-object module");
for (int i = 0; i < binding_count; i++) {
jsoff_t prop_off = lkp(js, ns, bindings[i].import_name, bindings[i].import_len);
if (prop_off == 0) return js_mkerr_typed(
js, JS_ERR_SYNTAX, "The requested module does not provide an export named '%.*s'",
(int)bindings[i].import_len, bindings[i].import_name
);
jsval_t imported_val = resolveprop(js, mkval(T_PROP, prop_off));
js_setprop(js, js->scope, js_mkstr(js, bindings[i].local_name, bindings[i].local_len), imported_val);
}
return js_mkundef();
}
if (next(js) == TOK_STRING) {
jsval_t spec = js_str_literal(js);
jsoff_t spec_len;
char *spec_str = esm_jsval_to_cstr(js, spec, &spec_len);
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
jsval_t ns = esm_resolve_and_load(js, spec_str, spec_len);
JS_RESTORE_STATE(js, saved);
free(spec_str);
js->consumed = 1;
next(js);
js->consumed = 0;
if (is_err(ns)) return ns;
return js_mkundef();
}
return js_mkerr_typed(js, JS_ERR_SYNTAX, "Invalid import statement");
}
static jsval_t js_export_stmt(struct js *js) {
js->consumed = 1;
if (vtype(js->module_ns) != T_OBJ) {
js->module_ns = mkobj(js, 0);
}
if (next(js) == TOK_DEFAULT) {
js->consumed = 1;
jsval_t value = js_assignment(js);
if (is_err(value)) return value;
jsval_t resolved = resolveprop(js, value);
js_set_slot(js, js->module_ns, SLOT_DEFAULT, resolved);
js_mkprop_fast(js, js->module_ns, "default", 7, resolved);
return value;
}
if (next(js) == TOK_CONST || next(js) == TOK_LET || next(js) == TOK_VAR) {
bool is_const = (next(js) == TOK_CONST);
js->consumed = 1;
EXPECT(TOK_IDENTIFIER);
const char *name = &js->code[js->toff];
size_t name_len = js->tlen;
js->consumed = 1;
jsval_t value = js_mkundef();
if (next(js) == TOK_ASSIGN) {
js->consumed = 1;
value = js_assignment(js);
if (is_err(value)) return value;
}
jsval_t key = js_mkstr(js, name, name_len);
mkprop(js, js->scope, key, resolveprop(js, value), is_const ? CONSTMASK : 0);
js_setprop(js, js->module_ns, key, resolveprop(js, value));
return value;
}
if (next(js) == TOK_FUNC) {
jsval_t func = js_func_literal(js, false);
if (is_err(func)) return func;
jsval_t func_obj = mkval(T_OBJ, vdata(func));
jsoff_t name_off = lkp(js, func_obj, "name", 4);
if (name_off != 0) {
jsval_t name_val = resolveprop(js, mkval(T_PROP, name_off));
if (vtype(name_val) == T_STR) {
js_setprop(js, js->scope, name_val, func);
js_setprop(js, js->module_ns, name_val, func);
}
}
return func;
}
if (next(js) == TOK_CLASS) {
jsval_t cls = js_class_decl(js);
if (is_err(cls)) return cls;
jsval_t cls_obj = mkval(T_OBJ, vdata(cls));
jsoff_t name_off = lkp(js, cls_obj, "name", 4);
if (name_off != 0) {
jsval_t name_val = resolveprop(js, mkval(T_PROP, name_off));
if (vtype(name_val) == T_STR) {
js_setprop(js, js->scope, name_val, cls);
js_setprop(js, js->module_ns, name_val, cls);
}
}
return cls;
}
if (next(js) == TOK_MUL) {
js->consumed = 1;
const char *alias_name = NULL;
size_t alias_len = 0;
if (next(js) == TOK_AS) {
js->consumed = 1;
EXPECT(TOK_IDENTIFIER);
alias_name = &js->code[js->toff];
alias_len = js->tlen;
js->consumed = 1;
}
EXPECT(TOK_FROM);
EXPECT(TOK_STRING);
jsval_t spec = js_str_literal(js);
jsoff_t spec_len;
char *spec_str = esm_jsval_to_cstr(js, spec, &spec_len);
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
jsval_t ns = esm_resolve_and_load(js, spec_str, spec_len);
JS_RESTORE_STATE(js, saved);
free(spec_str);
js->consumed = 1; next(js); js->consumed = 0;
if (is_err(ns)) return ns;
if (alias_name) {
js_setprop(js, js->module_ns, js_mkstr(js, alias_name, alias_len), ns);
} else if (vtype(ns) == T_OBJ) {
ant_iter_t iter = js_prop_iter_begin(js, ns);
const char *key; size_t key_len; jsval_t value;
while (js_prop_iter_next(&iter, &key, &key_len, &value)) {
js_setprop(js, js->module_ns, js_mkstr(js, key, key_len), resolveprop(js, value));
}
js_prop_iter_end(&iter);
}
return js_mkundef();
}
if (next(js) == TOK_LBRACE) {
js->consumed = 1;
typedef struct { const char *local; size_t local_len; const char *exported; size_t export_len; } export_spec_t;
export_spec_t specs[64];
int spec_count = 0;
while (next(js) != TOK_RBRACE) {
if (spec_count >= 64) return js_mkerr(js, "too many export specifiers");
if (next(js) != TOK_IDENTIFIER && next(js) != TOK_DEFAULT) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "expected identifier or 'default' in export list");
}
specs[spec_count].local = &js->code[js->toff];
specs[spec_count].local_len = js->tlen;
specs[spec_count].exported = specs[spec_count].local;
specs[spec_count].export_len = specs[spec_count].local_len;
js->consumed = 1;
if (next(js) == TOK_AS) {
js->consumed = 1;
if (next(js) != TOK_IDENTIFIER && next(js) != TOK_DEFAULT) {
return js_mkerr_typed(js, JS_ERR_SYNTAX, "expected identifier or 'default' after 'as'");
}
specs[spec_count].exported = &js->code[js->toff];
specs[spec_count].export_len = js->tlen;
js->consumed = 1;
}
spec_count++;
if (next(js) == TOK_COMMA) js->consumed = 1;
}
EXPECT(TOK_RBRACE);
if (next(js) == TOK_FROM) {
js->consumed = 1;
EXPECT(TOK_STRING);
jsval_t spec = js_str_literal(js);
jsoff_t spec_len;
char *spec_str = esm_jsval_to_cstr(js, spec, &spec_len);
js_parse_state_t saved;
JS_SAVE_STATE(js, saved);
jsval_t ns = esm_resolve_and_load(js, spec_str, spec_len);
JS_RESTORE_STATE(js, saved);
free(spec_str);
js->consumed = 1; next(js); js->consumed = 0;
if (is_err(ns)) return ns;
for (int i = 0; i < spec_count; i++) {
jsoff_t prop_off = lkp(js, ns, specs[i].local, specs[i].local_len);
jsval_t import_val = prop_off != 0 ? resolveprop(js, mkval(T_PROP, prop_off)) : js_mkundef();
esm_export_binding(js, specs[i].exported, specs[i].export_len, import_val);
}
} else {
for (int i = 0; i < spec_count; i++) {
jsval_t local_val = lookup(js, specs[i].local, specs[i].local_len);
if (is_err(local_val)) return local_val;
jsval_t export_val = resolveprop(js, local_val);
esm_export_binding(js, specs[i].exported, specs[i].export_len, export_val);
}
}
if (next(js) == TOK_SEMICOLON) js->consumed = 1;
return js_mkundef();
}
return js_mkerr(js, "Invalid export statement");
}
static proxy_data_t *get_proxy_data(jsval_t obj) {
if (vtype(obj) != T_OBJ) return NULL;
jsoff_t off = (jsoff_t)vdata(obj);
proxy_data_t *data = NULL;
HASH_FIND(hh, proxy_registry, &off, sizeof(jsoff_t), data);
return data;
}
static bool is_proxy(struct js *js, jsval_t obj) {
(void)js;
return get_proxy_data(obj) != NULL;
}
static jsval_t proxy_read_target(struct js *js, jsval_t obj) {
proxy_data_t *data = get_proxy_data(obj);
return data ? data->target : obj;
}
static jsoff_t proxy_aware_length(struct js *js, jsval_t obj) {
jsval_t src = is_proxy(js, obj) ? proxy_read_target(js, obj) : obj;
if (vtype(src) == T_ARR) {
jsoff_t doff = get_dense_buf(js, src);
if (doff) return dense_length(js, doff);
}
jsoff_t off = lkp_interned(js, src, INTERN_LENGTH, 6);
if (off == 0) return 0;
jsval_t len_val = resolveprop(js, mkval(T_PROP, off));
return vtype(len_val) == T_NUM ? (jsoff_t)tod(len_val) : 0;
}
static jsval_t proxy_aware_get_elem(struct js *js, jsval_t obj, const char *key, size_t key_len) {
jsval_t src = is_proxy(js, obj) ? proxy_read_target(js, obj) : obj;
jsoff_t off = lkp(js, src, key, key_len);
return off ? resolveprop(js, mkval(T_PROP, off)) : js_mkundef();
}
static jsval_t throw_proxy_error(struct js *js, const char *message) {
jsval_t err_obj = mkobj(js, 0);
js_setprop(js, err_obj, js_mkstr(js, "message", 7), js_mkstr(js, message, strlen(message)));
js_setprop(js, err_obj, js_mkstr(js, "name", 4), js_mkstr(js, "TypeError", 9));
return js_throw(js, err_obj);
}
static jsval_t proxy_get(struct js *js, jsval_t proxy, const char *key, size_t key_len) {
proxy_data_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");
jsval_t target = data->target;
jsval_t handler = data->handler;
jsoff_t get_trap_off = vtype(handler) == T_OBJ
? lkp_interned(js, handler, INTERN_GET, 3)
: 0;
if (get_trap_off != 0) {
jsval_t get_trap = resolveprop(js, mkval(T_PROP, get_trap_off));
if (vtype(get_trap) == T_FUNC || vtype(get_trap) == T_CFUNC) {
jsval_t key_val;
if (is_symbol_key(key, key_len)) {
key_val = get_wellknown_sym_by_key(key, key_len);
if (key_val == 0) key_val = js_mkstr(js, key, key_len);
} else key_val = js_mkstr(js, key, key_len);
jsval_t args[3] = { target, key_val, proxy };
return js_call(js, get_trap, args, 3);
}
}
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';
jsoff_t off = lkp(js, target, key_buf, len);
if (off != 0) return resolveprop(js, mkval(T_PROP, off));
jsoff_t proto_off = lkp_proto(js, target, key_buf, len);
if (proto_off != 0) return resolveprop(js, mkval(T_PROP, proto_off));
return js_mkundef();
}
static jsval_t proxy_set(struct js *js, jsval_t proxy, const char *key, size_t key_len, jsval_t value) {
proxy_data_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");
jsval_t target = data->target;
jsval_t handler = data->handler;
jsoff_t set_trap_off = vtype(handler) == T_OBJ ? lkp_interned(js, handler, INTERN_SET, 3) : 0;
if (set_trap_off != 0) {
jsval_t set_trap = resolveprop(js, mkval(T_PROP, set_trap_off));
if (vtype(set_trap) == T_FUNC || vtype(set_trap) == T_CFUNC) {
jsval_t key_val = js_mkstr(js, key, key_len);
jsval_t args[4] = { target, key_val, value, proxy };
jsval_t result = js_call(js, set_trap, args, 4);
if (is_err(result)) return result;
return js_true;
}
}
jsval_t key_str = js_mkstr(js, key, key_len);
js_setprop(js, target, key_str, value);
return js_true;
}
static jsval_t proxy_has(struct js *js, jsval_t proxy, const char *key, size_t key_len) {
proxy_data_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");
jsval_t target = data->target;
jsval_t handler = data->handler;
jsoff_t has_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "has", 3) : 0;
if (has_trap_off != 0) {
jsval_t has_trap = resolveprop(js, mkval(T_PROP, has_trap_off));
if (vtype(has_trap) == T_FUNC || vtype(has_trap) == T_CFUNC) {
jsval_t key_val = js_mkstr(js, key, key_len);
jsval_t args[2] = { target, key_val };
return js_call(js, has_trap, args, 2);
}
}
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';
jsoff_t off = lkp_proto(js, target, key_buf, len);
return js_bool(off != 0);
}
static jsval_t proxy_delete(struct js *js, jsval_t proxy, const char *key, size_t key_len) {
proxy_data_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");
jsval_t target = data->target;
jsval_t handler = data->handler;
jsoff_t delete_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "deleteProperty", 14) : 0;
if (delete_trap_off != 0) {
jsval_t delete_trap = resolveprop(js, mkval(T_PROP, delete_trap_off));
if (vtype(delete_trap) == T_FUNC || vtype(delete_trap) == T_CFUNC) {
jsval_t key_val = js_mkstr(js, key, key_len);
jsval_t args[2] = { target, key_val };
return js_call(js, delete_trap, args, 2);
}
}
jsval_t key_str = js_mkstr(js, key, key_len);
js_setprop(js, target, key_str, js_mkundef());
return js_true;
}
static jsval_t mkproxy(struct js *js, jsval_t target, jsval_t handler) {
jsval_t proxy_obj = mkobj(js, 0);
jsoff_t off = (jsoff_t)vdata(proxy_obj);
proxy_data_t *data = (proxy_data_t *)ant_calloc(sizeof(proxy_data_t));
if (!data) return js_mkerr(js, "out of memory");
data->obj_offset = off;
data->target = target;
data->handler = handler;
data->revoked = false;
HASH_ADD(hh, proxy_registry, obj_offset, sizeof(jsoff_t), data);
return proxy_obj;
}
static jsval_t builtin_Proxy(struct js *js, jsval_t *args, int nargs) {
if (nargs < 2) return js_mkerr(js, "Proxy requires two arguments: target and handler");
jsval_t target = args[0];
jsval_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 jsval_t proxy_revoke_fn(struct js *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
jsval_t func = js->current_func;
jsval_t ref_slot = get_slot(js, func, SLOT_PROXY_REF);
if (vtype(ref_slot) != T_UNDEF && vdata(ref_slot) != 0) {
jsval_t proxy = resolveprop(js, mkval(T_PROP, (jsoff_t)vdata(ref_slot)));
proxy_data_t *data = get_proxy_data(proxy);
if (data) data->revoked = true;
}
return js_mkundef();
}
static jsval_t builtin_Proxy_revocable(struct js *js, jsval_t *args, int nargs) {
jsval_t proxy = builtin_Proxy(js, args, nargs);
if (is_err(proxy)) return proxy;
jsval_t revoke_obj = mkobj(js, 0);
set_slot(js, revoke_obj, SLOT_CFUNC, js_mkfun(proxy_revoke_fn));
set_slot(js, revoke_obj, SLOT_PROXY_REF, proxy);
jsval_t revoke_func = mkval(T_FUNC, vdata(revoke_obj));
jsval_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 NaN-boxing limit"
);
intern_init();
ant_t *js = NULL;
if (len < sizeof(*js) + esize(T_OBJ)) return js;
memset(buf, 0, len);
js = (struct js *) buf;
js->mem = (uint8_t *) (js + 1);
js->size = (jsoff_t) (len - sizeof(*js));
js->global = mkobj(js, 0);
js->scope = js->global;
js->size = js->size / 8U * 8U;
js->this_val = js->scope;
js->super_val = js_mkundef();
js->new_target = js_mkundef();
js->errmsg_size = 4096;
js->errmsg = (char *)malloc(js->errmsg_size);
if (js->errmsg) js->errmsg[0] = '\0';
js->length_str = ANT_STRING("length");
#ifdef _WIN32
js->stack_limit = 512 * 1024;
#else
struct rlimit rl;
if (getrlimit(RLIMIT_STACK, &rl) == 0 && rl.rlim_cur != RLIM_INFINITY) {
js->stack_limit = rl.rlim_cur / 2;
} else {
js->stack_limit = 512 * 1024;
}
#endif
jsval_t glob = js->scope;
jsval_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);
jsval_t proto_getter = js_mkfun(builtin_proto_getter);
jsval_t proto_setter = js_mkfun(builtin_proto_setter);
js_setprop(js, object_proto, js_mkstr(js, STR_PROTO, STR_PROTO_LEN), js_mkundef());
js_set_accessor_desc(js, object_proto, STR_PROTO, STR_PROTO_LEN, proto_getter, proto_setter, JS_DESC_C);
jsval_t function_proto_obj = js_mkobj(js);
set_proto(js, function_proto_obj, object_proto);
set_slot(js, 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));
jsval_t function_proto = mkval(T_FUNC, vdata(function_proto_obj));
set_slot(js, glob, SLOT_FUNC_PROTO, function_proto);
jsval_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));
jsval_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, "replace", 7), js_mkfun(builtin_string_replace));
js_setprop(js, string_proto, js_mkstr(js, "replaceAll", 10), js_mkfun(builtin_string_replaceAll));
js_setprop(js, string_proto, js_mkstr(js, "match", 5), js_mkfun(builtin_string_match));
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, "search", 6), js_mkfun(builtin_string_search));
js_setprop(js, string_proto, js_mkstr(js, "localeCompare", 13), js_mkfun(builtin_string_localeCompare));
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));
jsval_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));
jsval_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));
jsval_t bigint_proto = js_mkobj(js);
set_proto(js, bigint_proto, object_proto);
js_setprop(js, bigint_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_bigint_toString));
jsval_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));
jsval_t err_ctor_obj = mkobj(js, 0);
set_proto(js, err_ctor_obj, function_proto);
set_slot(js, 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"));
js_setprop(js, glob, ANT_STRING("Error"), mkval(T_FUNC, vdata(err_ctor_obj)));
js_setprop(js, error_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(err_ctor_obj)));
js_set_descriptor(js, error_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
#define REGISTER_ERROR_SUBTYPE(name_str) do { \
jsval_t proto = js_mkobj(js); \
set_proto(js, proto, error_proto); \
js_setprop(js, proto, ANT_STRING("name"), ANT_STRING(name_str)); \
jsval_t ctor = mkobj(js, 0); \
set_proto(js, ctor, function_proto); \
set_slot(js, 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)); \
js_setprop(js, proto, ANT_STRING("constructor"), mkval(T_FUNC, vdata(ctor))); \
js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C); \
js_setprop(js, glob, ANT_STRING(name_str), mkval(T_FUNC, vdata(ctor))); \
} 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
jsval_t proto = js_mkobj(js);
set_proto(js, proto, error_proto);
js_setprop(js, proto, ANT_STRING("name"), ANT_STRING("AggregateError"));
jsval_t ctor = mkobj(js, 0);
set_proto(js, ctor, function_proto);
set_slot(js, 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"), mkval(T_FUNC, vdata(ctor)));
js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, glob, ANT_STRING("AggregateError"), mkval(T_FUNC, vdata(ctor)));
jsval_t date_proto = js_mkobj(js);
set_proto(js, date_proto, object_proto);
js_setprop(js, date_proto, js_mkstr(js, "getTime", 7), js_mkfun(builtin_Date_getTime));
js_setprop(js, date_proto, js_mkstr(js, "getFullYear", 11), js_mkfun(builtin_Date_getFullYear));
js_setprop(js, date_proto, js_mkstr(js, "getMonth", 8), js_mkfun(builtin_Date_getMonth));
js_setprop(js, date_proto, js_mkstr(js, "getDate", 7), js_mkfun(builtin_Date_getDate));
js_setprop(js, date_proto, js_mkstr(js, "getHours", 8), js_mkfun(builtin_Date_getHours));
js_setprop(js, date_proto, js_mkstr(js, "getMinutes", 10), js_mkfun(builtin_Date_getMinutes));
js_setprop(js, date_proto, js_mkstr(js, "getSeconds", 10), js_mkfun(builtin_Date_getSeconds));
js_setprop(js, date_proto, js_mkstr(js, "getMilliseconds", 15), js_mkfun(builtin_Date_getMilliseconds));
js_setprop(js, date_proto, js_mkstr(js, "getDay", 6), js_mkfun(builtin_Date_getDay));
js_setprop(js, date_proto, js_mkstr(js, "getTimezoneOffset", 17), js_mkfun(builtin_Date_getTimezoneOffset));
js_setprop(js, date_proto, js_mkstr(js, "getUTCFullYear", 14), js_mkfun(builtin_Date_getUTCFullYear));
js_setprop(js, date_proto, js_mkstr(js, "getUTCMonth", 11), js_mkfun(builtin_Date_getUTCMonth));
js_setprop(js, date_proto, js_mkstr(js, "getUTCDate", 10), js_mkfun(builtin_Date_getUTCDate));
js_setprop(js, date_proto, js_mkstr(js, "getUTCHours", 11), js_mkfun(builtin_Date_getUTCHours));
js_setprop(js, date_proto, js_mkstr(js, "getUTCMinutes", 13), js_mkfun(builtin_Date_getUTCMinutes));
js_setprop(js, date_proto, js_mkstr(js, "getUTCSeconds", 13), js_mkfun(builtin_Date_getUTCSeconds));
js_setprop(js, date_proto, js_mkstr(js, "getUTCMilliseconds", 18), js_mkfun(builtin_Date_getUTCMilliseconds));
js_setprop(js, date_proto, js_mkstr(js, "getUTCDay", 9), js_mkfun(builtin_Date_getUTCDay));
js_setprop(js, date_proto, js_mkstr(js, "setTime", 7), js_mkfun(builtin_Date_setTime));
js_setprop(js, date_proto, js_mkstr(js, "setMilliseconds", 15), js_mkfun(builtin_Date_setMilliseconds));
js_setprop(js, date_proto, js_mkstr(js, "setSeconds", 10), js_mkfun(builtin_Date_setSeconds));
js_setprop(js, date_proto, js_mkstr(js, "setMinutes", 10), js_mkfun(builtin_Date_setMinutes));
js_setprop(js, date_proto, js_mkstr(js, "setHours", 8), js_mkfun(builtin_Date_setHours));
js_setprop(js, date_proto, js_mkstr(js, "setDate", 7), js_mkfun(builtin_Date_setDate));
js_setprop(js, date_proto, js_mkstr(js, "setMonth", 8), js_mkfun(builtin_Date_setMonth));
js_setprop(js, date_proto, js_mkstr(js, "setFullYear", 11), js_mkfun(builtin_Date_setFullYear));
js_setprop(js, date_proto, js_mkstr(js, "setUTCMilliseconds", 18), js_mkfun(builtin_Date_setUTCMilliseconds));
js_setprop(js, date_proto, js_mkstr(js, "setUTCSeconds", 13), js_mkfun(builtin_Date_setUTCSeconds));
js_setprop(js, date_proto, js_mkstr(js, "setUTCMinutes", 13), js_mkfun(builtin_Date_setUTCMinutes));
js_setprop(js, date_proto, js_mkstr(js, "setUTCHours", 11), js_mkfun(builtin_Date_setUTCHours));
js_setprop(js, date_proto, js_mkstr(js, "setUTCDate", 10), js_mkfun(builtin_Date_setUTCDate));
js_setprop(js, date_proto, js_mkstr(js, "setUTCMonth", 11), js_mkfun(builtin_Date_setUTCMonth));
js_setprop(js, date_proto, js_mkstr(js, "setUTCFullYear", 14), js_mkfun(builtin_Date_setUTCFullYear));
js_setprop(js, date_proto, js_mkstr(js, "valueOf", 7), js_mkfun(builtin_Date_valueOf));
js_setprop(js, date_proto, js_mkstr(js, "toISOString", 11), js_mkfun(builtin_Date_toISOString));
js_setprop(js, date_proto, js_mkstr(js, "toUTCString", 11), js_mkfun(builtin_Date_toUTCString));
js_setprop(js, date_proto, js_mkstr(js, "toGMTString", 11), js_mkfun(builtin_Date_toUTCString));
js_setprop(js, date_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_Date_toString));
js_setprop(js, date_proto, js_mkstr(js, "toDateString", 12), js_mkfun(builtin_Date_toDateString));
js_setprop(js, date_proto, js_mkstr(js, "toTimeString", 12), js_mkfun(builtin_Date_toTimeString));
js_setprop(js, date_proto, js_mkstr(js, "toLocaleDateString", 18), js_mkfun(builtin_Date_toLocaleDateString));
js_setprop(js, date_proto, js_mkstr(js, "toLocaleTimeString", 18), js_mkfun(builtin_Date_toLocaleTimeString));
js_setprop(js, date_proto, js_mkstr(js, "getYear", 7), js_mkfun(builtin_Date_getYear));
js_setprop(js, date_proto, js_mkstr(js, "setYear", 7), js_mkfun(builtin_Date_setYear));
js_setprop(js, date_proto, js_mkstr(js, "toJSON", 6), js_mkfun(builtin_Date_toJSON));
jsval_t regexp_proto = js_mkobj(js);
set_proto(js, regexp_proto, object_proto);
js_setprop(js, regexp_proto, js_mkstr(js, "test", 4), js_mkfun(builtin_regexp_test));
js_setprop(js, regexp_proto, js_mkstr(js, "exec", 4), js_mkfun(builtin_regexp_exec));
js_setprop(js, regexp_proto, js_mkstr(js, "toString", 8), js_mkfun(builtin_regexp_toString));
jsval_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));
// Symbol.toStringTag is set in init_symbol_module after symbols are initialized
jsval_t obj_func_obj = mkobj(js, 0);
set_proto(js, obj_func_obj, function_proto);
set_slot(js, obj_func_obj, SLOT_BUILTIN, tov(BUILTIN_OBJECT));
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, "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);
js_setprop(js, glob, js_mkstr(js, "Object", 6), mkval(T_FUNC, vdata(obj_func_obj)));
jsval_t func_ctor_obj = mkobj(js, 0);
set_proto(js, func_ctor_obj, function_proto);
set_slot(js, 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"));
js_setprop(js, glob, js_mkstr(js, "Function", 8), mkval(T_FUNC, vdata(func_ctor_obj)));
jsval_t async_func_proto_obj = js_mkobj(js);
set_proto(js, async_func_proto_obj, function_proto);
set_slot(js, async_func_proto_obj, SLOT_ASYNC, js_true);
jsval_t async_func_proto = mkval(T_FUNC, vdata(async_func_proto_obj));
set_slot(js, glob, SLOT_ASYNC_PROTO, async_func_proto);
jsval_t async_func_ctor_obj = mkobj(js, 0);
set_proto(js, async_func_ctor_obj, function_proto);
set_slot(js, 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"));
jsval_t async_func_ctor = mkval(T_FUNC, vdata(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);
jsval_t str_ctor_obj = mkobj(js, 0);
set_proto(js, str_ctor_obj, function_proto);
set_slot(js, 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, ANT_STRING("name"), ANT_STRING("String"));
js_setprop(js, glob, js_mkstr(js, "String", 6), mkval(T_FUNC, vdata(str_ctor_obj)));
jsval_t number_ctor_obj = mkobj(js, 0);
set_proto(js, number_ctor_obj, function_proto);
set_slot(js, 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"));
js_setprop(js, glob, js_mkstr(js, "Number", 6), mkval(T_FUNC, vdata(number_ctor_obj)));
jsval_t bool_ctor_obj = mkobj(js, 0);
set_proto(js, bool_ctor_obj, function_proto);
set_slot(js, 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"));
js_setprop(js, glob, js_mkstr(js, "Boolean", 7), mkval(T_FUNC, vdata(bool_ctor_obj)));
jsval_t arr_ctor_obj = mkobj(js, 0);
set_proto(js, arr_ctor_obj, function_proto);
set_slot(js, 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"));
js_setprop(js, glob, js_mkstr(js, "Array", 5), mkval(T_FUNC, vdata(arr_ctor_obj)));
jsval_t proxy_ctor_obj = mkobj(js, 0);
set_proto(js, proxy_ctor_obj, function_proto);
set_slot(js, proxy_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Proxy));
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), mkval(T_FUNC, vdata(proxy_ctor_obj)));
jsval_t regex_ctor_obj = mkobj(js, 0);
set_proto(js, regex_ctor_obj, function_proto);
set_slot(js, regex_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_RegExp));
js_setprop_nonconfigurable(js, regex_ctor_obj, "prototype", 9, regexp_proto);
js_setprop(js, regex_ctor_obj, ANT_STRING("name"), ANT_STRING("RegExp"));
js_setprop(js, glob, js_mkstr(js, "RegExp", 6), mkval(T_FUNC, vdata(regex_ctor_obj)));
jsval_t date_ctor_obj = mkobj(js, 0);
set_proto(js, date_ctor_obj, function_proto);
set_slot(js, date_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Date));
js_setprop(js, date_ctor_obj, js_mkstr(js, "now", 3), js_mkfun(builtin_Date_now));
js_setprop(js, date_ctor_obj, js_mkstr(js, "UTC", 3), js_mkfun(builtin_Date_UTC));
js_setprop_nonconfigurable(js, date_ctor_obj, "prototype", 9, date_proto);
js_setprop(js, date_ctor_obj, ANT_STRING("name"), ANT_STRING("Date"));
js_setprop(js, glob, js_mkstr(js, "Date", 4), mkval(T_FUNC, vdata(date_ctor_obj)));
jsval_t p_ctor_obj = mkobj(js, 0);
set_proto(js, p_ctor_obj, function_proto);
set_slot(js, 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, "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), mkval(T_FUNC, vdata(p_ctor_obj)));
jsval_t bigint_ctor_obj = mkobj(js, 0);
set_proto(js, bigint_ctor_obj, function_proto);
set_slot(js, bigint_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_BigInt));
js_setprop(js, bigint_ctor_obj, js_mkstr(js, "asIntN", 6), js_mkfun(builtin_BigInt_asIntN));
js_setprop(js, bigint_ctor_obj, js_mkstr(js, "asUintN", 7), js_mkfun(builtin_BigInt_asUintN));
js_setprop_nonconfigurable(js, bigint_ctor_obj, "prototype", 9, bigint_proto);
js_setprop(js, bigint_ctor_obj, ANT_STRING("name"), ANT_STRING("BigInt"));
js_setprop(js, glob, js_mkstr(js, "BigInt", 6), mkval(T_FUNC, vdata(bigint_ctor_obj)));
js_setprop(js, glob, js_mkstr(js, "eval", 4), js_mkfun(builtin_eval));
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, "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_setprop(js, glob, js_mkstr(js, "Infinity", 8), tov(JS_INF));
js_setprop(js, glob, js_mkstr(js, "undefined", 9), js_mkundef());
jsval_t math_obj = mkobj(js, 0);
set_proto(js, math_obj, object_proto);
js_setprop(js, math_obj, js_mkstr(js, "E", 1), tov(M_E));
js_setprop(js, math_obj, js_mkstr(js, "LN10", 4), tov(M_LN10));
js_setprop(js, math_obj, js_mkstr(js, "LN2", 3), tov(M_LN2));
js_setprop(js, math_obj, js_mkstr(js, "LOG10E", 6), tov(M_LOG10E));
js_setprop(js, math_obj, js_mkstr(js, "LOG2E", 5), tov(M_LOG2E));
js_setprop(js, math_obj, js_mkstr(js, "PI", 2), tov(M_PI));
js_setprop(js, math_obj, js_mkstr(js, "SQRT1_2", 7), tov(M_SQRT1_2));
js_setprop(js, math_obj, js_mkstr(js, "SQRT2", 5), tov(M_SQRT2));
js_setprop(js, math_obj, js_mkstr(js, "abs", 3), js_mkfun(builtin_Math_abs));
js_setprop(js, math_obj, js_mkstr(js, "acos", 4), js_mkfun(builtin_Math_acos));
js_setprop(js, math_obj, js_mkstr(js, "acosh", 5), js_mkfun(builtin_Math_acosh));
js_setprop(js, math_obj, js_mkstr(js, "asin", 4), js_mkfun(builtin_Math_asin));
js_setprop(js, math_obj, js_mkstr(js, "asinh", 5), js_mkfun(builtin_Math_asinh));
js_setprop(js, math_obj, js_mkstr(js, "atan", 4), js_mkfun(builtin_Math_atan));
js_setprop(js, math_obj, js_mkstr(js, "atanh", 5), js_mkfun(builtin_Math_atanh));
js_setprop(js, math_obj, js_mkstr(js, "atan2", 5), js_mkfun(builtin_Math_atan2));
js_setprop(js, math_obj, js_mkstr(js, "cbrt", 4), js_mkfun(builtin_Math_cbrt));
js_setprop(js, math_obj, js_mkstr(js, "ceil", 4), js_mkfun(builtin_Math_ceil));
js_setprop(js, math_obj, js_mkstr(js, "clz32", 5), js_mkfun(builtin_Math_clz32));
js_setprop(js, math_obj, js_mkstr(js, "cos", 3), js_mkfun(builtin_Math_cos));
js_setprop(js, math_obj, js_mkstr(js, "cosh", 4), js_mkfun(builtin_Math_cosh));
js_setprop(js, math_obj, js_mkstr(js, "exp", 3), js_mkfun(builtin_Math_exp));
js_setprop(js, math_obj, js_mkstr(js, "expm1", 5), js_mkfun(builtin_Math_expm1));
js_setprop(js, math_obj, js_mkstr(js, "floor", 5), js_mkfun(builtin_Math_floor));
js_setprop(js, math_obj, js_mkstr(js, "fround", 6), js_mkfun(builtin_Math_fround));
js_setprop(js, math_obj, js_mkstr(js, "hypot", 5), js_mkfun(builtin_Math_hypot));
js_setprop(js, math_obj, js_mkstr(js, "imul", 4), js_mkfun(builtin_Math_imul));
js_setprop(js, math_obj, js_mkstr(js, "log", 3), js_mkfun(builtin_Math_log));
js_setprop(js, math_obj, js_mkstr(js, "log1p", 5), js_mkfun(builtin_Math_log1p));
js_setprop(js, math_obj, js_mkstr(js, "log10", 5), js_mkfun(builtin_Math_log10));
js_setprop(js, math_obj, js_mkstr(js, "log2", 4), js_mkfun(builtin_Math_log2));
js_setprop(js, math_obj, js_mkstr(js, "max", 3), js_mkfun(builtin_Math_max));
js_setprop(js, math_obj, js_mkstr(js, "min", 3), js_mkfun(builtin_Math_min));
js_setprop(js, math_obj, js_mkstr(js, "pow", 3), js_mkfun(builtin_Math_pow));
js_setprop(js, math_obj, js_mkstr(js, "random", 6), js_mkfun(builtin_Math_random));
js_setprop(js, math_obj, js_mkstr(js, "round", 5), js_mkfun(builtin_Math_round));
js_setprop(js, math_obj, js_mkstr(js, "sign", 4), js_mkfun(builtin_Math_sign));
js_setprop(js, math_obj, js_mkstr(js, "sin", 3), js_mkfun(builtin_Math_sin));
js_setprop(js, math_obj, js_mkstr(js, "sinh", 4), js_mkfun(builtin_Math_sinh));
js_setprop(js, math_obj, js_mkstr(js, "sqrt", 4), js_mkfun(builtin_Math_sqrt));
js_setprop(js, math_obj, js_mkstr(js, "tan", 3), js_mkfun(builtin_Math_tan));
js_setprop(js, math_obj, js_mkstr(js, "tanh", 4), js_mkfun(builtin_Math_tanh));
js_setprop(js, math_obj, js_mkstr(js, "trunc", 5), js_mkfun(builtin_Math_trunc));
js_setprop(js, glob, js_mkstr(js, "Math", 4), math_obj);
jsval_t import_obj = mkobj(js, 0);
set_proto(js, import_obj, function_proto);
set_slot(js, import_obj, SLOT_CFUNC, js_mkfun(builtin_import));
js_setprop(js, glob, js_mkstr(js, "import", 6), mkval(T_FUNC, vdata(import_obj)));
js->module_ns = js_mkundef();
js_setprop(js, object_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(obj_func_obj)));
js_set_descriptor(js, object_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, function_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(func_ctor_obj)));
js_set_descriptor(js, function_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, array_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(arr_ctor_obj)));
js_set_descriptor(js, array_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, string_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(str_ctor_obj)));
js_set_descriptor(js, string_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, number_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(number_ctor_obj)));
js_set_descriptor(js, number_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, boolean_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(bool_ctor_obj)));
js_set_descriptor(js, boolean_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, date_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(date_ctor_obj)));
js_set_descriptor(js, date_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
js_setprop(js, regexp_proto, js_mkstr(js, "constructor", 11), mkval(T_FUNC, vdata(regex_ctor_obj)));
js_set_descriptor(js, regexp_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;
}
-struct js *js_create_dynamic(size_t initial_size, size_t max_size) {
- if (initial_size < sizeof(struct js) + esize(T_OBJ)) initial_size = ANT_ARENA_MIN;
- if (max_size == 0 || max_size < initial_size) max_size = ANT_ARENA_MAX;
-
- void *init_buf = ant_calloc(initial_size);
+struct js *js_create_dynamic() {
+ void *init_buf = ant_calloc(ANT_ARENA_MIN);
if (init_buf == NULL) return NULL;
- struct js *js = js_create(init_buf, initial_size);
+ struct js *js = js_create(init_buf, ANT_ARENA_MIN);
if (js == NULL) { free(init_buf); return NULL; }
- uint8_t *arena = (uint8_t *)ant_arena_reserve(max_size);
+ uint8_t *arena = (uint8_t *)ant_arena_reserve(ANT_ARENA_MAX);
if (arena == NULL) { free(init_buf); return NULL; }
if (ant_arena_commit(arena, 0, js->size) != 0) {
- ant_arena_free(arena, max_size);
+ ant_arena_free(arena, ANT_ARENA_MAX);
free(init_buf); return NULL;
}
memcpy(arena, js->mem, js->brk);
js->mem = arena;
js->owns_mem = true;
- js->max_size = (jsoff_t) max_size;
+ js->max_size = (jsoff_t) ANT_ARENA_MAX;
struct js *new_js = (struct js *)malloc(sizeof(struct js));
if (new_js == NULL) {
- ant_arena_free(arena, max_size);
+ ant_arena_free(arena, ANT_ARENA_MAX);
free(init_buf);
return NULL;
}
memcpy(new_js, js, sizeof(struct js));
free(init_buf);
return new_js;
}
void js_destroy(struct js *js) {
if (js == NULL) return;
esm_cleanup_module_cache();
code_arena_reset();
cleanup_buffer_module();
cleanup_collections_module();
if (js->errmsg) {
free(js->errmsg);
js->errmsg = NULL;
}
if (js->for_let_stack) {
free(js->for_let_stack);
js->for_let_stack = NULL;
}
if (js->owns_mem) {
ant_arena_free(js->mem, js->max_size);
free(js);
}
destroy_runtime(js);
}
inline double js_getnum(jsval_t value) { return tod(value); }
inline void js_setstacklimit(struct js *js, size_t max) { js->stack_limit = max; }
inline void js_setstackbase(struct js *js, void *base) { js->cstk = base; }
inline void js_set_filename(struct js *js, const char *filename) { js->filename = filename; }
inline jsval_t js_mkundef(void) { return mkval(T_UNDEF, 0); }
inline jsval_t js_mknull(void) { return mkval(T_NULL, 0); }
inline jsval_t js_mknum(double value) { return tov(value); }
inline jsval_t js_mkobj(struct js *js) { return mkobj(js, 0); }
inline jsval_t js_glob(struct js *js) { return js->global; }
inline jsval_t js_getscope(struct js *js) { return js->scope; }
inline jsval_t js_mkfun(jsval_t (*fn)(struct js *, jsval_t *, int)) { return mkval(T_CFUNC, (size_t) (void *) fn); }
inline jsval_t js_getthis(struct js *js) { return js->this_val; }
inline void js_setthis(struct js *js, jsval_t val) { js->this_val = val; }
inline jsval_t js_getcurrentfunc(struct js *js) { return js->current_func; }
jsval_t js_heavy_mkfun(struct js *js, jsval_t (*fn)(struct js *, jsval_t *, int), jsval_t data) {
jsval_t cfunc = js_mkfun(fn);
jsval_t fn_obj = mkobj(js, 0);
set_slot(js, fn_obj, SLOT_CFUNC, cfunc);
set_slot(js, fn_obj, SLOT_DATA, data);
return mkval(T_FUNC, (unsigned long)vdata(fn_obj));
}
void js_set(struct js *js, jsval_t obj, const char *key, jsval_t val) {
size_t key_len = strlen(key);
if (vtype(obj) == T_OBJ) {
jsoff_t existing = lkp(js, obj, key, key_len);
if (existing > 0) {
if (is_const_prop(js, existing)) {
js_mkerr(js, "assignment to constant");
return;
}
saveval(js, existing + sizeof(jsoff_t) * 2, val);
} else {
jsval_t key_str = js_mkstr(js, key, key_len);
mkprop(js, obj, key_str, val, 0);
}
} else if (vtype(obj) == T_FUNC) {
jsval_t func_obj = mkval(T_OBJ, vdata(obj));
jsoff_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;
}
saveval(js, existing + sizeof(jsoff_t) * 2, val);
} else {
jsval_t key_str = js_mkstr(js, key, key_len);
mkprop(js, func_obj, key_str, val, 0);
}
}
}
bool js_del(struct js *js, jsval_t obj, const char *key) {
size_t len = strlen(key);
jsoff_t obj_off;
if (vtype(obj) == T_OBJ) {
obj_off = (jsoff_t)vdata(obj);
} else if (vtype(obj) == T_ARR || vtype(obj) == T_FUNC) {
obj_off = (jsoff_t)vdata(obj);
obj = mkval(T_OBJ, obj_off);
} else {
return false;
}
jsoff_t prop_off = lkp(js, obj, key, len);
if (prop_off == 0) return true;
if (is_nonconfig_prop(js, prop_off)) return false;
descriptor_entry_t *desc = lookup_descriptor(js, obj_off, key, len);
if (desc && !desc->configurable) return false;
jsoff_t first_prop = loadoff(js, obj_off) & ~(3U | FLAGMASK);
jsoff_t tail = loadoff(js, obj_off + sizeof(jsoff_t) + sizeof(jsoff_t));
if (first_prop == prop_off) {
jsoff_t deleted_next = loadoff(js, prop_off) & ~FLAGMASK;
jsoff_t current = loadoff(js, obj_off);
saveoff(js, obj_off, (deleted_next & ~3ULL) | (current & (FLAGMASK | 3ULL)));
if (tail == prop_off) saveoff(js, obj_off + sizeof(jsoff_t) + sizeof(jsoff_t), 0);
invalidate_prop_cache(js, obj_off, prop_off);
js->needs_gc = true;
return true;
}
jsoff_t prev = first_prop;
while (prev != 0 && prev < js->brk) {
jsoff_t next = loadoff(js, prev) & ~(3U | FLAGMASK);
if (next == prop_off) {
jsoff_t deleted_next = loadoff(js, prop_off) & ~(3U | FLAGMASK);
jsoff_t prev_flags = loadoff(js, prev) & FLAGMASK;
saveoff(js, prev, deleted_next | prev_flags);
if (tail == prop_off) saveoff(js, obj_off + sizeof(jsoff_t) + sizeof(jsoff_t), prev);
invalidate_prop_cache(js, obj_off, prop_off);
js->needs_gc = true;
return true;
}
prev = next;
}
return false;
}
static bool js_try_get(struct js *js, jsval_t obj, const char *key, jsval_t *out) {
size_t key_len = strlen(key);
if (vtype(obj) == T_FUNC) {
jsval_t func_obj = mkval(T_OBJ, vdata(obj));
jsoff_t off = lkp(js, func_obj, key, key_len);
if (off == 0) return false;
*out = resolveprop(js, mkval(T_PROP, off));
return true;
}
if (vtype(obj) == T_ARR) {
if (key_len == 6 && memcmp(key, "length", 6) == 0) {
*out = tov((double)get_array_length(js, obj));
return true;
}
unsigned long idx;
jsoff_t arr_len = get_array_length(js, obj);
if (parse_array_index(key, key_len, arr_len, &idx)) {
if (arr_has(js, obj, (jsoff_t)idx)) {
*out = arr_get(js, obj, (jsoff_t)idx);
return true;
} return false;
}
jsval_t arr_obj = mkval(T_OBJ, vdata(obj));
jsoff_t off = lkp(js, arr_obj, key, key_len);
if (off == 0) return false;
*out = resolveprop(js, mkval(T_PROP, off));
return true;
}
uint8_t t = vtype(obj);
bool is_promise = (t == T_PROMISE);
if (is_promise) obj = mkval(T_OBJ, vdata(obj));
else if (t != T_OBJ) return false;
jsoff_t off = lkp(js, obj, key, key_len);
if (off == 0) {
jsval_t result = try_dynamic_getter(js, obj, key, key_len);
if (vtype(result) != T_UNDEF) { *out = result; return true; }
}
if (off == 0 && is_promise) {
jsval_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 = resolveprop(js, mkval(T_PROP, off)); return true; }
}
}
if (off == 0) return false;
*out = resolveprop(js, mkval(T_PROP, off));
return true;
}
jsval_t js_get(struct js *js, jsval_t obj, const char *key) {
jsval_t val;
if (js_try_get(js, obj, key, &val)) return val;
return js_mkundef();
}
jsval_t js_getprop_proto(struct js *js, jsval_t obj, const char *key) {
size_t key_len = strlen(key);
jsoff_t off = lkp_proto(js, obj, key, key_len);
return off == 0 ? js_mkundef() : resolveprop(js, mkval(T_PROP, off));
}
jsval_t js_getprop_fallback(struct js *js, jsval_t obj, const char *name) {
jsval_t val;
if (js_try_get(js, obj, name, &val)) return val;
return js_getprop_proto(js, obj, name);
}
typedef struct {
bool (*callback)(struct js *js, jsval_t value, void *udata);
void *udata;
} js_iter_ctx_t;
static iter_action_t js_iter_cb(struct js *js, jsval_t value, void *ctx, jsval_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(struct js *js, jsval_t iterable, bool (*callback)(struct js *js, jsval_t value, void *udata), void *udata) {
js_iter_ctx_t ctx = { .callback = callback, .udata = udata };
jsval_t result = iter_foreach(js, iterable, js_iter_cb, &ctx);
return !is_err(result);
}
char *js_getstr(struct js *js, jsval_t value, size_t *len) {
if (vtype(value) != T_STR) return NULL;
jsoff_t n, off = vstr(js, value, &n);
if (len != NULL) *len = n;
return (char *) &js->mem[off];
}
void js_merge_obj(struct js *js, jsval_t dst, jsval_t src) {
if (vtype(dst) != T_OBJ || vtype(src) != T_OBJ) return;
jsoff_t next = loadoff(js, (jsoff_t) vdata(src)) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (is_slot_prop(header)) { next = next_prop(header); continue; }
jsoff_t koff = loadoff(js, next + (jsoff_t) sizeof(next));
jsval_t val = loadval(js, next + (jsoff_t) (sizeof(next) + sizeof(koff)));
js_setprop(js, dst, mkval(T_STR, koff), val);
next = next_prop(header);
}
}
#define UTARRAY_EACH(arr, type, var) \
if (arr) for (type *var = (type *)utarray_front(arr), *_end = var + utarray_len(arr); var < _end; var++)
#define REHASH_REGISTRY(registry, entry, tmp, new_reg, key_field, key_size, body) \
for (typeof(registry) new_reg = NULL, *_once = NULL; !_once; _once = (void*)1, registry = new_reg) \
HASH_ITER(hh, registry, entry, tmp) { \
HASH_DEL(registry, entry); \
body; \
HASH_ADD(hh, new_reg, key_field, key_size, entry); \
}
typedef jsoff_t (*gc_fwd_off_fn)(void *ctx, jsoff_t old);
typedef jsval_t (*gc_fwd_val_fn)(void *ctx, jsval_t old);
typedef void (*gc_off_op_t)(void *cb_ctx, jsoff_t *off);
typedef void (*gc_val_op_t)(void *cb_ctx, jsval_t *val);
typedef struct {
gc_fwd_off_fn fwd_off;
gc_fwd_val_fn fwd_val;
gc_fwd_off_fn weak_off;
void *ctx;
ant_t *js;
} gc_cb_ctx_t;
static inline void gc_reserve_off_cb(void *cb_ctx, jsoff_t *off) {
gc_cb_ctx_t *c = cb_ctx;
if (*off) (void)c->fwd_off(c->ctx, *off);
}
static inline void gc_reserve_val_cb(void *cb_ctx, jsval_t *val) {
gc_cb_ctx_t *c = cb_ctx;
(void)c->fwd_val(c->ctx, *val);
}
static inline void gc_update_off_cb(void *cb_ctx, jsoff_t *off) {
gc_cb_ctx_t *c = cb_ctx;
if (*off) *off = c->fwd_off(c->ctx, *off);
}
static inline void gc_update_val_cb(void *cb_ctx, jsval_t *val) {
gc_cb_ctx_t *c = cb_ctx;
*val = c->fwd_val(c->ctx, *val);
}
static inline jsoff_t gc_weak_off_cb(void *cb_ctx, jsoff_t old) {
gc_cb_ctx_t *c = cb_ctx;
return c->weak_off(c->ctx, old);
}
static void gc_roots_common(gc_off_op_t op_off, gc_val_op_t op_val, gc_cb_ctx_t *c) {
UTARRAY_EACH(global_scope_stack, jsoff_t, off) op_off(c, off);
UTARRAY_EACH(saved_scope_stack, jsval_t, val) op_val(c, val);
for (int i = 0; i < global_this_stack.depth; i++)
op_val(c, &global_this_stack.stack[i]);
UTARRAY_EACH(propref_stack, propref_data_t, pref) {
op_off(c, &pref->obj_off);
op_off(c, &pref->key_off);
}
UTARRAY_EACH(prim_propref_stack, prim_propref_data_t, ppref) {
op_val(c, &ppref->prim_val);
op_off(c, &ppref->key_off);
}
if (rt && rt->js == c->js)
op_val(c, &rt->ant_obj);
for (coroutine_t *coro = pending_coroutines.head; coro; coro = coro->next) {
op_val(c, &coro->scope);
op_val(c, &coro->this_val);
op_val(c, &coro->super_val);
op_val(c, &coro->new_target);
op_val(c, &coro->awaited_promise);
op_val(c, &coro->result);
op_val(c, &coro->async_func);
op_val(c, &coro->yield_value);
for (int i = 0; i < coro->for_let_stack_len; i++) {
op_val(c, &coro->for_let_stack[i].body_scope);
op_off(c, &coro->for_let_stack[i].prop_off);
}
if (coro->scope_stack) {
UTARRAY_EACH(coro->scope_stack, jsoff_t, off) op_off(c, off);
}
async_exec_context_t *actx;
if (coro->mco && (actx = mco_get_user_data(coro->mco))) {
op_val(c, &actx->closure_scope);
op_val(c, &actx->result);
op_val(c, &actx->promise);
}
}
esm_module_t *mod, *mod_tmp;
HASH_ITER(hh, global_module_cache.modules, mod, mod_tmp) {
op_val(c, &mod->namespace_obj);
op_val(c, &mod->default_export);
}
timer_gc_update_roots(op_val, c);
ffi_gc_update_roots(op_val, c);
fetch_gc_update_roots(op_val, c);
fs_gc_update_roots(op_val, c);
child_process_gc_update_roots(op_val, c);
readline_gc_update_roots(op_val, c);
process_gc_update_roots(op_val, c);
navigator_gc_update_roots(op_val, c);
server_gc_update_roots(op_val, c);
events_gc_update_roots(op_val, c);
for (int i = 0; i < c->js->for_let_stack_len; i++) {
op_val(c, &c->js->for_let_stack[i].body_scope);
op_off(c, &c->js->for_let_stack[i].prop_off);
}
op_val(c, &c->js->object);
op_val(c, &c->js->tval);
op_val(c, &c->js->scope);
op_val(c, &c->js->this_val);
op_val(c, &c->js->super_val);
op_val(c, &c->js->new_target);
op_val(c, &c->js->module_ns);
op_val(c, &c->js->current_func);
op_val(c, &c->js->thrown_value);
op_val(c, &c->js->length_str);
for (jshdl_t i = 0; i < c->js->gc_roots_len; i++) op_val(c, &c->js->gc_roots[i]);
if (c->js->ascii_cache_init) for (int i = 0; i < 128; i++) op_val(c, &c->js->ascii_char_cache[i]);
if (c->js->tc.pending) {
op_val(c, &c->js->tc.func); op_val(c, &c->js->tc.closure_scope);
for (int i = 0; i < c->js->tc.argc; i++) op_val(c, &c->js->tc.args[i]);
}
}
void js_gc_reserve_roots(GC_RESERVE_ARGS) {
#define RSV_OFF(x) ((x) ? (void)fwd_off(ctx, x) : (void)0)
#define RSV_VAL(x) (void)fwd_val(ctx, x)
gc_cb_ctx_t cb_ctx = { fwd_off, fwd_val, NULL, ctx, js };
gc_roots_common(gc_reserve_off_cb, gc_reserve_val_cb, &cb_ctx);
collections_gc_reserve_roots(gc_reserve_val_cb, &cb_ctx);
promise_data_entry_t *pd, *pd_tmp;
HASH_ITER(hh, promise_registry, pd, pd_tmp) {
bool can_collect = (pd->state != 0)
&& (utarray_len(pd->handlers) == 0)
&& !pd->processing;
if (can_collect) continue;
RSV_OFF(pd->obj_offset);
RSV_VAL(pd->value);
UTARRAY_EACH(pd->handlers, promise_handler_t, h) {
RSV_VAL(h->onFulfilled);
RSV_VAL(h->onRejected);
RSV_VAL(h->nextPromise);
}
}
proxy_data_t *proxy, *proxy_tmp;
HASH_ITER(hh, proxy_registry, proxy, proxy_tmp) {
RSV_VAL(proxy->target);
RSV_VAL(proxy->handler);
}
descriptor_entry_t *desc, *desc_tmp;
HASH_ITER(hh, desc_registry, desc, desc_tmp) {
if (desc->has_getter) RSV_VAL(desc->getter);
if (desc->has_setter) RSV_VAL(desc->setter);
}
// accessor registry is a weak reference
// objects only survive if reachable from other roots
(void)accessor_registry;
#undef RSV_OFF
#undef RSV_VAL
}
void js_gc_update_roots(GC_UPDATE_ARGS) {
#define FWD_OFF(x) ((x) ? ((x) = fwd_off(ctx, x)) : 0)
#define FWD_VAL(x) ((x) = fwd_val(ctx, x))
#define IS_UNREACHABLE(old, new) ((new) == (old) && (old) != 0 && (old) < js->brk)
gc_cb_ctx_t cb_ctx = { fwd_off, fwd_val, weak_off, ctx, js };
gc_roots_common(gc_update_off_cb, gc_update_val_cb, &cb_ctx);
promise_data_entry_t *pd, *pd_tmp;
promise_data_entry_t *new_unhandled = NULL;
for (
promise_data_entry_t *new_promise_registry = NULL, *_once = NULL; !_once; _once = (void*)1,
promise_registry = new_promise_registry, unhandled_rejections = new_unhandled
)
HASH_ITER(hh, promise_registry, pd, pd_tmp) {
HASH_DEL(promise_registry, pd);
promise_data_entry_t *in_unhandled = NULL;
HASH_FIND(
hh_unhandled, unhandled_rejections,
&pd->promise_id, sizeof(uint32_t), in_unhandled
);
if (in_unhandled) HASH_DELETE(hh_unhandled, unhandled_rejections, pd);
jsoff_t new_off = weak_off(ctx, pd->obj_offset);
if (new_off == (jsoff_t)~0 && !pd->processing) {
utarray_free(pd->handlers);
free(pd); continue;
}
bool can_collect = (pd->state != 0)
&& (utarray_len(pd->handlers) == 0)
&& !pd->processing;
if (can_collect) {
utarray_free(pd->handlers);
free(pd); continue;
}
pd->obj_offset = new_off;
FWD_VAL(pd->value);
UTARRAY_EACH(pd->handlers, promise_handler_t, h) {
FWD_VAL(h->onFulfilled);
FWD_VAL(h->onRejected);
FWD_VAL(h->nextPromise);
}
HASH_ADD(hh, new_promise_registry, promise_id, sizeof(uint32_t), pd);
if (in_unhandled) HASH_ADD(hh_unhandled, new_unhandled, promise_id, sizeof(uint32_t), pd);
}
proxy_data_t *proxy, *proxy_tmp;
for (proxy_data_t *new_proxy_registry = NULL, *_once = NULL; !_once; _once = (void*)1, proxy_registry = new_proxy_registry)
HASH_ITER(hh, proxy_registry, proxy, proxy_tmp) {
HASH_DEL(proxy_registry, proxy);
jsoff_t new_off = weak_off(ctx, proxy->obj_offset);
if (new_off == (jsoff_t)~0) { free(proxy); continue; }
proxy->obj_offset = new_off;
FWD_VAL(proxy->target); FWD_VAL(proxy->handler);
HASH_ADD(hh, new_proxy_registry, obj_offset, sizeof(jsoff_t), proxy);
}
dynamic_accessors_t *acc, *acc_tmp;
for (dynamic_accessors_t *new_acc_registry = NULL, *_once = NULL; !_once; _once = (void*)1, accessor_registry = new_acc_registry)
HASH_ITER(hh, accessor_registry, acc, acc_tmp) {
HASH_DEL(accessor_registry, acc);
jsoff_t new_off = weak_off(ctx, acc->obj_offset);
if (new_off == (jsoff_t)~0) { free(acc); continue; }
acc->obj_offset = new_off;
HASH_ADD(hh, new_acc_registry, obj_offset, sizeof(jsoff_t), acc);
}
descriptor_entry_t *desc, *desc_tmp;
for (descriptor_entry_t *new_desc_registry = NULL, *_once = NULL; !_once; _once = (void*)1, desc_registry = new_desc_registry)
HASH_ITER(hh, desc_registry, desc, desc_tmp) {
HASH_DEL(desc_registry, desc);
jsoff_t new_off = weak_off(ctx, (jsoff_t)(desc->key >> 32));
if (new_off == (jsoff_t)~0) { free(desc); continue; }
if (desc->has_getter) FWD_VAL(desc->getter);
if (desc->has_setter) FWD_VAL(desc->setter);
desc->key = ((uint64_t)new_off << 32) | (uint32_t)(desc->key & 0xFFFFFFFF);
desc->obj_off = new_off;
HASH_ADD(hh, new_desc_registry, key, sizeof(uint64_t), desc);
}
intern_prop_cache_gen++;
collections_gc_update_roots(gc_weak_off_cb, gc_update_val_cb, &cb_ctx);
#undef FWD_OFF
#undef FWD_VAL
}
#undef UTARRAY_EACH
#undef REHASH_REGISTRY
bool js_chkargs(jsval_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 jsval_t js_eval_inherit_strict(struct js *js, const char *buf, size_t len, bool inherit_strict) {
jsval_t res = js_mkundef();
if (len == (size_t) ~0U) len = strlen(buf);
js->eval_depth++;
js->consumed = 1;
js->tok = TOK_ERR;
js->code = buf;
js->clen = (jsoff_t) len;
js->pos = 0;
if (js->token_stream && js->code == js->token_stream_code) {
js->token_stream_pos = 0;
}
uint8_t saved_tok = js->tok;
jsoff_t saved_pos = js->pos;
uint8_t saved_consumed = js->consumed;
int saved_stream_pos = js->token_stream_pos;
js->consumed = 1;
bool is_strict = inherit_strict;
if (inherit_strict) js->flags |= F_STRICT;
if (next(js) == TOK_STRING) {
const char *str = &js->code[js->toff + 1];
size_t str_len = js->tlen - 2;
if (str_len == 10 && memcmp(str, "use strict", 10) == 0) {
js->flags |= F_STRICT;
is_strict = true;
}
}
js->tok = saved_tok;
js->pos = saved_pos;
js->consumed = saved_consumed;
js->token_stream_pos = saved_stream_pos;
if (is_strict) {
mkscope(js);
set_slot(js, js->scope, SLOT_STRICT_EVAL_SCOPE, tov(1));
}
hoist_function_declarations(js);
if (!(js->flags & F_CALL)) hoist_var_declarations(js, js->scope);
while (next(js) != TOK_EOF && !is_err(res)) {
res = js_stmt(js);
if (js->needs_gc && js->eval_depth == 1) js_gc_safepoint(js);
if (js->flags & F_RETURN) break;
}
if (is_strict) delscope(js);
js->eval_depth--;
return res;
}
inline jsval_t js_eval(struct js *js, const char *buf, size_t len) {
return js_eval_inherit_strict(js, buf, len, false);
}
jsval_t js_eval_cached(struct js *js, const char *buf, size_t len) {
if (len == (size_t)~0U) len = strlen(buf);
void *saved_stream = js->token_stream;
int saved_pos = js->token_stream_pos;
const char *saved_code = js->token_stream_code;
token_stream_t *ts = tokenize_body(js, buf, (jsoff_t)len);
if (ts) {
js->token_stream = ts;
js->token_stream_pos = 0;
js->token_stream_code = buf;
}
jsval_t result = js_eval_inherit_strict(js, buf, len, false);
if (ts) { free(ts->tokens); free(ts); }
js->token_stream = saved_stream;
js->token_stream_pos = saved_pos;
js->token_stream_code = saved_code;
return result;
}
static jsval_t js_call_internal(struct js *js, jsval_t func, jsval_t bound_this, jsval_t *args, int nargs, bool use_bound_this) {
if (vtype(func) == T_FFI) {
return ffi_call_by_index(js, (unsigned int)vdata(func), args, nargs);
} else if (vtype(func) == T_CFUNC) {
jsval_t saved_this = js->this_val;
if (use_bound_this) js->this_val = bound_this;
jsval_t (*fn)(struct js *, jsval_t *, int) = (jsval_t(*)(struct js *, jsval_t *, int)) vdata(func);
jsval_t res = fn(js, args, nargs);
js->this_val = saved_this; return res;
} else if (vtype(func) == T_FUNC) {
jsval_t func_obj = mkval(T_OBJ, vdata(func));
jsval_t cfunc_slot = get_slot(js, func_obj, SLOT_CFUNC);
if (vtype(cfunc_slot) == T_CFUNC) {
jsval_t slot_bound_this = get_slot(js, func_obj, SLOT_BOUND_THIS);
bool has_slot_bound_this = vtype(slot_bound_this) != T_UNDEF;
int final_nargs;
jsval_t *combined_args = resolve_bound_args(js, func_obj, args, nargs, &final_nargs);
jsval_t *final_args = combined_args ? combined_args : args;
jsval_t saved_func = js->current_func;
jsval_t saved_this = js->this_val;
js->current_func = func;
if (has_slot_bound_this) {
js->this_val = slot_bound_this;
} else if (use_bound_this) js->this_val = bound_this;
jsval_t (*fn)(struct js *, jsval_t *, int) = (jsval_t(*)(struct js *, jsval_t *, int)) vdata(cfunc_slot);
jsval_t res = fn(js, final_args, final_nargs);
js->current_func = saved_func;
js->this_val = saved_this;
if (combined_args) free(combined_args);
return res;
}
jsoff_t fnlen;
const char *fn = get_func_code(js, func_obj, &fnlen);
if (!fn) return js_mkerr(js, "function has no code");
jsval_t slot_bound_this = get_slot(js, func_obj, SLOT_BOUND_THIS);
bool has_slot_bound_this = vtype(slot_bound_this) != T_UNDEF;
int final_nargs;
jsval_t *combined_args = resolve_bound_args(js, func_obj, args, nargs, &final_nargs);
jsval_t *final_args = combined_args ? combined_args : args;
jsval_t async_slot = get_slot(js, func_obj, SLOT_ASYNC);
bool is_async = vtype(async_slot) == T_BOOL && vdata(async_slot) == 1;
if (is_async) {
jsval_t closure_scope = get_slot(js, func_obj, SLOT_SCOPE);
jsval_t res = start_async_in_coroutine(js, fn, fnlen, closure_scope, final_args, final_nargs);
if (combined_args) free(combined_args);
return res;
}
jsval_t saved_scope = js->scope;
jsval_t closure_scope = get_slot(js, func_obj, SLOT_SCOPE);
if (vtype(closure_scope) == T_OBJ) js->scope = closure_scope;
uint8_t saved_flags = js->flags;
js->flags = 0;
mkscope(js);
js->flags = saved_flags;
if (global_scope_stack == NULL) utarray_new(global_scope_stack, &jsoff_icd);
jsoff_t function_scope_offset = (jsoff_t) vdata(js->scope);
utarray_push_back(global_scope_stack, &function_scope_offset);
hoist_var_declarations_from_slot(js, js->scope, func_obj);
parsed_func_t *pf = get_or_parse_func(js, fn, fnlen);
if (!pf) {
if (global_scope_stack && utarray_len(global_scope_stack) > 0) utarray_pop_back(global_scope_stack);
delscope(js); js->scope = saved_scope;
if (combined_args) free(combined_args);
return js_mkerr(js, "failed to parse function");
}
int arg_idx = 0;
for (unsigned int i = 0; i < (unsigned int)pf->param_count; i++) {
parsed_param_t *pp = (parsed_param_t *)utarray_eltptr(pf->params, i);
if (pp->is_destruct) {
jsval_t arg_val = (arg_idx < final_nargs) ? final_args[arg_idx++] : js_mkundef();
if (vtype(arg_val) == T_UNDEF && pp->default_len > 0) {
arg_val = js_eval_str(js, &fn[pp->default_start], pp->default_len);
}
bind_destruct_pattern(js, &fn[pp->pattern_off], pp->pattern_len, arg_val, js->scope);
} else {
jsval_t v = arg_idx < final_nargs ? final_args[arg_idx++] : js_mkundef();
if (vtype(v) == T_UNDEF && pp->default_len > 0) {
v = js_eval_str(js, &fn[pp->default_start], pp->default_len);
}
js_setprop(js, js->scope, js_mkstr(js, &fn[pp->name_off], pp->name_len), v);
}
}
if (pf->has_rest && pf->rest_param_len > 0) {
jsval_t rest_array = mkarr(js);
if (!is_err(rest_array)) {
jsoff_t idx = 0;
while (arg_idx < final_nargs) {
char idxstr[16];
size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)idx);
jsval_t key = js_mkstr(js, idxstr, idxlen);
js_setprop(js, rest_array, key, final_args[arg_idx]);
idx++;
arg_idx++;
}
js_setprop(js, rest_array, js->length_str, tov((double) idx));
rest_array = mkval(T_ARR, vdata(rest_array));
js_setprop(js, js->scope, js_mkstr(js, &fn[pf->rest_param_start], pf->rest_param_len), rest_array);
}
}
if (pf->uses_arguments) {
setup_arguments(js, js->scope, final_args, final_nargs, pf->is_strict);
}
jsval_t saved_this = js->this_val;
if (has_slot_bound_this) {
js->this_val = slot_bound_this;
} else if (use_bound_this) {
js->this_val = bound_this;
} else js->this_val = js_glob(js);
js_parse_state_t saved_state;
JS_SAVE_STATE(js, saved_state);
uint8_t caller_flags = js->flags;
js->flags = F_CALL | (pf->is_strict ? F_STRICT : 0);
jsval_t res;
void *saved_token_stream = js->token_stream;
int saved_token_stream_pos = js->token_stream_pos;
const char *saved_token_stream_code = js->token_stream_code;
if (pf->tokens) {
js->token_stream = pf->tokens;
js->token_stream_pos = 0;
js->token_stream_code = &fn[pf->body_start];
} else {
js->token_stream = NULL;
js->token_stream_code = NULL;
}
if (pf->is_expr) {
res = js_eval_str(js, &fn[pf->body_start], pf->body_len);
res = resolveprop(js, res);
} else {
res = js_eval(js, &fn[pf->body_start], pf->body_len);
if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef();
}
js->token_stream = saved_token_stream;
js->token_stream_pos = saved_token_stream_pos;
js->token_stream_code = saved_token_stream_code;
JS_RESTORE_STATE(js, saved_state);
js->flags = caller_flags;
js->this_val = saved_this;
if (global_scope_stack && utarray_len(global_scope_stack) > 0) utarray_pop_back(global_scope_stack);
delscope(js); js->scope = saved_scope;
if (combined_args) free(combined_args);
return res;
}
return js_mkerr(js, "not a function");
}
jsval_t js_call(struct js *js, jsval_t func, jsval_t *args, int nargs) {
return js_call_internal(js, func, js_mkundef(), args, nargs, false);
}
jsval_t js_call_with_this(struct js *js, jsval_t func, jsval_t bound_this, jsval_t *args, int nargs) {
return js_call_internal(js, func, bound_this, args, nargs, true);
}
ant_iter_t js_prop_iter_begin(struct js *js, jsval_t obj) {
ant_iter_t iter = {.ctx = js, .off = 0};
uint8_t t = vtype(obj);
if (t == T_OBJ || t == T_ARR || t == T_FUNC) {
iter.off = (jsoff_t)vdata(obj);
}
return iter;
}
bool js_prop_iter_next(ant_iter_t *iter, const char **key, size_t *key_len, jsval_t *value) {
if (!iter || !iter->ctx) return false;
ant_t *js = (ant_t *)iter->ctx;
jsoff_t next = loadoff(js, iter->off) & ~(3U | FLAGMASK);
while (next < js->brk && next != 0) {
jsoff_t header = loadoff(js, next);
if (!is_slot_prop(header)) break;
next = next_prop(header);
}
if (next >= js->brk || next == 0) return false;
iter->off = next;
jsoff_t koff = loadoff(js, next + (jsoff_t)sizeof(jsoff_t));
jsval_t val = loadval(js, next + (jsoff_t)(sizeof(jsoff_t) * 2));
if (key) {
jsoff_t klen = offtolen(loadoff(js, koff));
*key = (const char *)&js->mem[koff + sizeof(jsoff_t)];
if (key_len) *key_len = klen;
}
if (value) *value = val;
return true;
}
void js_prop_iter_end(ant_iter_t *iter) {
if (iter) { iter->off = 0; iter->ctx = NULL; }
}
jsval_t js_mkpromise(struct js *js) { return mkpromise(js); }
void js_resolve_promise(struct js *js, jsval_t promise, jsval_t value) { resolve_promise(js, promise, value); }
void js_reject_promise(struct js *js, jsval_t promise, jsval_t value) { reject_promise(js, promise, value); }
void js_check_unhandled_rejections(struct js *js) {
promise_data_entry_t *pd, *tmp;
HASH_ITER(hh_unhandled, unhandled_rejections, pd, tmp) {
if (pd->has_rejection_handler) {
HASH_DELETE(hh_unhandled, unhandled_rejections, pd); continue;
}
if (pd->trigger_pid != 0) {
promise_data_entry_t *parent;
HASH_FIND(hh, promise_registry, &pd->trigger_pid, sizeof(uint32_t), parent);
if (parent && parent->has_rejection_handler) {
HASH_DELETE(hh_unhandled, unhandled_rejections, pd); continue;
}
}
if (js->fatal_error) {
fprintf(stderr, "%s\n", js->errmsg ? js->errmsg : js_str(js, pd->value));
js_destroy(js); exit(1);
}
char buf[1024];
buf[tostr(js, pd->value, buf, sizeof(buf) - 1)] = '\0';
fprintf(stderr, "Uncaught (in promise) %s\n", buf);
pd->has_rejection_handler = true;
HASH_DELETE(hh_unhandled, unhandled_rejections, pd);
}
}
bool js_is_slot_prop(jsoff_t header) { return is_slot_prop(header); }
jsoff_t js_next_prop(jsoff_t header) { return next_prop(header); }
jsoff_t js_loadoff(struct js *js, jsoff_t off) { return loadoff(js, off); }
void js_set_getter(struct js *js, jsval_t obj, js_getter_fn getter) {
if (!is_object_type(obj)) return;
if (vtype(obj) != T_OBJ) obj = mkval(T_OBJ, vdata(obj));
jsoff_t obj_off = (jsoff_t)vdata(obj);
dynamic_accessors_t *entry = NULL;
HASH_FIND(hh, accessor_registry, &obj_off, sizeof(jsoff_t), entry);
if (!entry) {
entry = (dynamic_accessors_t *)malloc(sizeof(dynamic_accessors_t));
if (!entry) return;
entry->obj_offset = obj_off;
entry->getter = NULL; entry->setter = NULL;
entry->deleter = NULL; entry->keys = NULL;
HASH_ADD(hh, accessor_registry, obj_offset, sizeof(jsoff_t), entry);
}
entry->getter = getter;
}
void js_set_setter(struct js *js, jsval_t obj, js_setter_fn setter) {
if (!is_object_type(obj)) return;
if (vtype(obj) != T_OBJ) obj = mkval(T_OBJ, vdata(obj));
jsoff_t obj_off = (jsoff_t)vdata(obj);
dynamic_accessors_t *entry = NULL;
HASH_FIND(hh, accessor_registry, &obj_off, sizeof(jsoff_t), entry);
if (!entry) {
entry = (dynamic_accessors_t *)malloc(sizeof(dynamic_accessors_t));
if (!entry) return;
entry->obj_offset = obj_off;
entry->getter = NULL; entry->setter = NULL;
entry->deleter = NULL; entry->keys = NULL;
HASH_ADD(hh, accessor_registry, obj_offset, sizeof(jsoff_t), entry);
}
entry->setter = setter;
}
void js_set_deleter(struct js *js, jsval_t obj, js_deleter_fn deleter) {
if (!is_object_type(obj)) return;
if (vtype(obj) != T_OBJ) obj = mkval(T_OBJ, vdata(obj));
jsoff_t obj_off = (jsoff_t)vdata(obj);
dynamic_accessors_t *entry = NULL;
HASH_FIND(hh, accessor_registry, &obj_off, sizeof(jsoff_t), entry);
if (!entry) {
entry = (dynamic_accessors_t *)malloc(sizeof(dynamic_accessors_t));
if (!entry) return;
entry->obj_offset = obj_off;
entry->getter = NULL; entry->setter = NULL;
entry->deleter = NULL; entry->keys = NULL;
HASH_ADD(hh, accessor_registry, obj_offset, sizeof(jsoff_t), entry);
}
entry->deleter = deleter;
}
void js_set_keys(struct js *js, jsval_t obj, js_keys_fn keys) {
if (!is_object_type(obj)) return;
if (vtype(obj) != T_OBJ) obj = mkval(T_OBJ, vdata(obj));
jsoff_t obj_off = (jsoff_t)vdata(obj);
dynamic_accessors_t *entry = NULL;
HASH_FIND(hh, accessor_registry, &obj_off, sizeof(jsoff_t), entry);
if (!entry) {
entry = (dynamic_accessors_t *)malloc(sizeof(dynamic_accessors_t));
if (!entry) return;
entry->obj_offset = obj_off;
entry->getter = NULL; entry->setter = NULL;
entry->deleter = NULL; entry->keys = NULL;
HASH_ADD(hh, accessor_registry, obj_offset, sizeof(jsoff_t), entry);
}
entry->keys = keys;
}
static inline uint64_t make_desc_key(jsoff_t obj_off, const char *key, size_t klen) {
uint32_t key_hash = (uint32_t)hash_key(key, klen);
return ((uint64_t)obj_off << 32) | key_hash;
}
static descriptor_entry_t arr_length_desc = {
.key = 0, .obj_off = 0,
.prop_name = "length", .prop_len = 6,
.writable = true, .enumerable = false, .configurable = false,
.has_getter = false, .has_setter = false,
.getter = 0, .setter = 0,
};
static descriptor_entry_t *lookup_descriptor(struct js *js, jsoff_t obj_off, const char *key, size_t klen) {
if (klen == 6
&& memcmp(key, "length", 6) == 0
&& is_arr_off(js, obj_off)
) return &arr_length_desc;
descriptor_entry_t *entry = NULL;
uint64_t desc_key = make_desc_key(obj_off, key, klen);
HASH_FIND(hh, desc_registry, &desc_key, sizeof(uint64_t), entry);
return entry;
}
static descriptor_entry_t *get_or_create_desc(struct js *js, jsval_t obj, const char *key, size_t klen) {
if (vtype(obj) != T_OBJ && vtype(obj) != T_FUNC && vtype(obj) != T_ARR) return NULL;
jsoff_t obj_off = (jsoff_t)vdata(obj);
uint64_t desc_key = make_desc_key(obj_off, key, klen);
descriptor_entry_t *entry = NULL;
HASH_FIND(hh, desc_registry, &desc_key, sizeof(uint64_t), entry);
if (!entry) {
entry = (descriptor_entry_t *)ant_calloc(sizeof(descriptor_entry_t) + klen + 1);
if (!entry) return NULL;
entry->key = desc_key;
entry->obj_off = obj_off;
entry->prop_name = (char *)(entry + 1);
memcpy(entry->prop_name, key, klen);
entry->prop_name[klen] = '\0';
entry->prop_len = klen;
entry->writable = true;
entry->enumerable = true;
entry->configurable = true;
entry->has_getter = false;
entry->has_setter = false;
entry->getter = js_mkundef();
entry->setter = js_mkundef();
HASH_ADD(hh, desc_registry, key, sizeof(uint64_t), entry);
}
return entry;
}
void js_set_descriptor(struct js *js, jsval_t obj, const char *key, size_t klen, int flags) {
descriptor_entry_t *entry = get_or_create_desc(js, obj, key, klen);
if (!entry) return;
entry->writable = (flags & JS_DESC_W) != 0;
entry->enumerable = (flags & JS_DESC_E) != 0;
entry->configurable = (flags & JS_DESC_C) != 0;
}
void js_set_getter_desc(struct js *js, jsval_t obj, const char *key, size_t klen, jsval_t getter, int flags) {
descriptor_entry_t *entry = get_or_create_desc(js, obj, key, klen);
if (!entry) return;
entry->enumerable = (flags & JS_DESC_E) != 0;
entry->configurable = (flags & JS_DESC_C) != 0;
entry->has_getter = true;
entry->getter = getter;
}
void js_set_setter_desc(struct js *js, jsval_t obj, const char *key, size_t klen, jsval_t setter, int flags) {
descriptor_entry_t *entry = get_or_create_desc(js, obj, key, klen);
if (!entry) return;
entry->enumerable = (flags & JS_DESC_E) != 0;
entry->configurable = (flags & JS_DESC_C) != 0;
entry->has_setter = true;
entry->setter = setter;
}
void js_set_accessor_desc(struct js *js, jsval_t obj, const char *key, size_t klen, jsval_t getter, jsval_t setter, int flags) {
descriptor_entry_t *entry = get_or_create_desc(js, obj, key, klen);
if (!entry) return;
entry->enumerable = (flags & JS_DESC_E) != 0;
entry->configurable = (flags & JS_DESC_C) != 0;
entry->has_getter = true;
entry->has_setter = true;
entry->getter = getter;
entry->setter = setter;
}
diff --git a/src/cli/cprintf.c b/src/cli/cprintf.c
index d16e4a4..16af2ca 100644
--- a/src/cli/cprintf.c
+++ b/src/cli/cprintf.c
@@ -1,775 +1,790 @@
/*
* cprintf - printf with inline color tags, powered by a register-based VM
*
* usage:
* cprintf("<red>error:</red> something went wrong\n");
* cprintf("<bold><cyan>info:</cyan></bold> hello %s\n", name);
* cprintf("<#ff8800>orange text</#ff8800>\n");
* cprintf(" <pad=18><green>%s</green></pad> %s\n", cmd->name, cmd->desc);
*
* supported tags:
* <red> <green> <yellow> <blue> <magenta> <cyan> <white> <black>
* <gray/grey> <bright_red> <bright_green> ... etc
* <bg_red> <bg_green> ... <bg_#RGB> <bg_#RRGGBB>
* <bold> <dim> <ul> (underline)
* <#RRGGBB> or <#RGB> for arbitrary 24-bit foreground colors
* <pad=N> ... </pad> - right-pad contents to N visible columns
* <rpad=N> ... </rpad> - left-pad (right-align) contents to N visible columns
* <let name=style1+style2+...> - define a named style variable
* <$name> to apply a previously defined style variable
* </tagname> or </> to reset
*
*/
#include "utils.h"
#include "cli/cprintf.h"
#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
typedef enum {
OP_NOP = 0,
OP_EMIT_LIT,
OP_EMIT_FMT,
OP_SET_FG,
OP_SET_BG,
OP_SET_FG_RGB,
OP_SET_BG_RGB,
OP_SET_BOLD,
OP_SET_DIM,
OP_SET_UL,
OP_STYLE_PUSH,
OP_STYLE_FLUSH,
OP_STYLE_RESET,
OP_PAD_BEGIN,
OP_RPAD_BEGIN,
OP_PAD_END,
+ OP_EMIT_SPACES,
OP_HALT,
OP_MAX
} opcode_t;
typedef struct {
uint32_t op;
uint32_t operand;
} instruction_t;
typedef enum {
COL_NONE = 0,
COL_BLACK = 30, COL_RED, COL_GREEN, COL_YELLOW,
COL_BLUE, COL_MAGENTA, COL_CYAN, COL_WHITE,
COL_GRAY = 90, COL_BRIGHT_RED, COL_BRIGHT_GREEN, COL_BRIGHT_YELLOW,
COL_BRIGHT_BLUE, COL_BRIGHT_MAGENTA, COL_BRIGHT_CYAN, COL_BRIGHT_WHITE,
COL_RGB = 0xFF
} color_t;
#define UNPACK_R(c) (((c)>>16)&0xFF)
#define UNPACK_G(c) (((c)>>8)&0xFF)
#define UNPACK_B(c) ((c)&0xFF)
#define PACK_RGB(r,g,b) \
(((uint32_t)(r)<<16)|((uint32_t)(g)<<8)|(uint32_t)(b))
typedef struct {
size_t mark;
int width;
int right_align;
} pad_entry_t;
typedef struct {
uint32_t fg;
uint32_t bg;
uint32_t fg_rgb;
uint32_t bg_rgb;
int bold;
int dim;
int ul;
} style_entry_t;
typedef struct {
uint32_t fg;
uint32_t bg;
uint32_t fg_rgb;
uint32_t bg_rgb;
int bold;
int dim;
int ul;
style_entry_t style_stack[8];
int style_depth;
pad_entry_t pad_stack[8];
int pad_depth;
} vm_regs_t;
#define MAX_VARS 16
#define MAX_VAR_NAME 32
#define MAX_VAR_VALUE 128
typedef struct {
char name[MAX_VAR_NAME];
char value[MAX_VAR_VALUE];
int nlen; int vlen;
} cprintf_var_t;
typedef struct {
cprintf_var_t vars[MAX_VARS];
int count;
} var_table_t;
struct program_t {
instruction_t *code;
size_t code_len;
size_t code_cap;
char *literals;
size_t lit_len;
size_t lit_cap;
};
static program_t *program_new(void) {
program_t *p = calloc(1, sizeof(*p));
*p = (program_t){
.code_cap = 32,
.code = malloc(32 * sizeof(instruction_t)),
.lit_cap = 256,
.literals = malloc(256),
};
return p;
}
static void emit_op(program_t *p, uint32_t op, uint32_t operand) {
if (p->code_len >= p->code_cap) {
p->code_cap *= 2;
p->code = realloc(p->code, p->code_cap * sizeof(instruction_t));
}
p->code[p->code_len++] = (instruction_t){ op, operand };
}
static uint32_t add_literal(program_t *p, const char *s, size_t len) {
while (p->lit_len + len + 1 > p->lit_cap) {
p->lit_cap *= 2;
p->literals = realloc(p->literals, p->lit_cap);
}
uint32_t off = (uint32_t)p->lit_len;
memcpy(p->literals + p->lit_len, s, len);
p->literals[p->lit_len + len] = '\0';
p->lit_len += len + 1;
return off;
}
typedef struct {
const char *name;
int nlen;
color_t col;
} color_entry_t;
static const color_entry_t fg_colors[] = {
{"black",5,COL_BLACK}, {"red",3,COL_RED}, {"green",5,COL_GREEN},
{"yellow",6,COL_YELLOW}, {"blue",4,COL_BLUE}, {"magenta",7,COL_MAGENTA},
{"cyan",4,COL_CYAN}, {"white",5,COL_WHITE},
{"gray",4,COL_GRAY}, {"grey",4,COL_GRAY},
{"bright_red",10,COL_BRIGHT_RED}, {"bright_green",12,COL_BRIGHT_GREEN},
{"bright_yellow",13,COL_BRIGHT_YELLOW}, {"bright_blue",11,COL_BRIGHT_BLUE},
{"bright_magenta",14,COL_BRIGHT_MAGENTA}, {"bright_cyan",11,COL_BRIGHT_CYAN},
{"bright_white",12,COL_BRIGHT_WHITE},
};
static const color_entry_t bg_colors[] = {
{"bg_black",8,COL_BLACK}, {"bg_red",6,COL_RED}, {"bg_green",8,COL_GREEN},
{"bg_yellow",9,COL_YELLOW}, {"bg_blue",7,COL_BLUE}, {"bg_magenta",10,COL_MAGENTA},
{"bg_cyan",7,COL_CYAN}, {"bg_white",8,COL_WHITE},
};
static const color_entry_t seg_bg_colors[] = {
{"black",5,COL_BLACK}, {"red",3,COL_RED}, {"green",5,COL_GREEN},
{"yellow",6,COL_YELLOW}, {"blue",4,COL_BLUE}, {"magenta",7,COL_MAGENTA},
{"cyan",4,COL_CYAN}, {"white",5,COL_WHITE},
};
#define FG_COUNT (sizeof(fg_colors)/sizeof(fg_colors[0]))
#define BG_COUNT (sizeof(bg_colors)/sizeof(bg_colors[0]))
#define SEG_BG_COUNT (sizeof(seg_bg_colors)/sizeof(seg_bg_colors[0]))
static int parse_hex_rgb(const char *hex, int len, uint32_t *rgb) {
int r, g, b;
if (len == 4) {
int r1=hex_digit(hex[1]), g1=hex_digit(hex[2]), b1=hex_digit(hex[3]);
if (r1<0||g1<0||b1<0) return 0;
r=r1*17; g=g1*17; b=b1*17;
} else if (len == 7) {
int r1=hex_digit(hex[1]),r2=hex_digit(hex[2]);
int g1=hex_digit(hex[3]),g2=hex_digit(hex[4]);
int b1=hex_digit(hex[5]),b2=hex_digit(hex[6]);
if (r1<0||r2<0||g1<0||g2<0||b1<0||b2<0) return 0;
r=r1*16+r2; g=g1*16+g2; b=b1*16+b2;
} else return 0;
*rgb = PACK_RGB(r, g, b);
return 1;
}
static int compile_hex_fg(program_t *p, const char *tag, int len) {
uint32_t rgb;
if (!parse_hex_rgb(tag, len, &rgb)) return 0;
emit_op(p, OP_SET_FG_RGB, rgb);
return 1;
}
static int compile_hex_bg(program_t *p, const char *hex, int hlen) {
uint32_t rgb;
if (!parse_hex_rgb(hex, hlen, &rgb)) return 0;
emit_op(p, OP_SET_BG_RGB, rgb);
return 1;
}
static int tag_eq(const char *tag, int len, const char *lit) {
int ll = (int)strlen(lit);
return len == ll && memcmp(tag, lit, ll) == 0;
}
static int tag_prefix(const char *tag, int len, const char *pfx) {
int pl = (int)strlen(pfx);
return len > pl && memcmp(tag, pfx, pl) == 0;
}
static int match_fg(program_t *p, const char *s, int len) {
for (int i = 0; i < (int)FG_COUNT; i++) if (
len == fg_colors[i].nlen && memcmp(s, fg_colors[i].name, len) == 0) {
emit_op(p, OP_SET_FG, fg_colors[i].col); return 1;
}
return 0;
}
static int match_bg(program_t *p, const char *s, int len) {
for (int i = 0; i < (int)BG_COUNT; i++) if (
len == bg_colors[i].nlen && memcmp(s, bg_colors[i].name, len) == 0) {
emit_op(p, OP_SET_BG, bg_colors[i].col); return 1;
}
return 0;
}
static int match_seg_bg(program_t *p, const char *s, int len) {
for (int i = 0; i < (int)SEG_BG_COUNT; i++) if (
len == seg_bg_colors[i].nlen && memcmp(s, seg_bg_colors[i].name, len) == 0) {
emit_op(p, OP_SET_BG, seg_bg_colors[i].col); return 1;
}
return 0;
}
typedef struct {
const char *name;
int nlen;
int op;
} attr_entry_t;
static const attr_entry_t attrs[] = {
{ "bold", 4, OP_SET_BOLD },
{ "dim", 3, OP_SET_DIM },
{ "ul", 2, OP_SET_UL },
};
#define ATTR_COUNT (sizeof(attrs) / sizeof(attrs[0]))
static int match_attr(program_t *p, const char *s, int len) {
for (int i = 0; i < (int)ATTR_COUNT; i++) if (
len == attrs[i].nlen && memcmp(s, attrs[i].name, len) == 0) {
emit_op(p, attrs[i].op, 1); return 1;
}
return 0;
}
static const char *next_seg(const char *cur, const char *end, int *out_len) {
const char *sep = cur;
while (sep < end && *sep != '_') sep++;
*out_len = (int)(sep - cur);
return sep;
}
static int match_plus_seg(program_t *p, const char *seg, int slen) {
if (match_attr(p, seg, slen)) return 1;
if (match_fg(p, seg, slen)) return 1;
if (match_bg(p, seg, slen)) return 1;
if (slen > 0 && seg[0] == '#') return compile_hex_fg(p, seg, slen);
if (tag_prefix(seg, slen, "bg_#")) return compile_hex_bg(p, seg + 3, slen - 3);
if (tag_prefix(seg, slen, "bg_")) return match_seg_bg(p, seg + 3, slen - 3);
return 0;
}
static int compile_plus_segs(program_t *p, const char *s, int len) {
const char *seg = s;
const char *end = s + len;
int emitted = 0;
while (seg < end) {
const char *next = seg;
while (next < end && *next != '+') next++;
int slen = (int)(next - seg);
if (!match_plus_seg(p, seg, slen)) return 0;
emitted++; seg = (next < end) ? next + 1 : end;
}
return emitted;
}
static int compile_let(var_table_t *vars, const char *tag, int len) {
const char *eq = memchr(tag + 4, '=', len - 4);
if (!eq) return 0;
int nlen = (int)(eq - (tag + 4));
int vlen = (int)((tag + len) - (eq + 1));
if (nlen <= 0 || nlen >= MAX_VAR_NAME || vlen <= 0 || vlen >= MAX_VAR_VALUE) return 0;
if (vars->count >= MAX_VARS) return 0;
cprintf_var_t *v = &vars->vars[vars->count++];
memcpy(v->name, tag + 4, nlen);
v->name[nlen] = '\0'; v->nlen = nlen;
memcpy(v->value, eq + 1, vlen);
v->value[vlen] = '\0'; v->vlen = vlen;
return 1;
}
static int compile_var_ref(program_t *p, var_table_t *vars, const char *tag, int len) {
int nlen = len - 1;
for (int i = 0; i < vars->count; i++) {
if (nlen == vars->vars[i].nlen && memcmp(tag + 1, vars->vars[i].name, nlen) == 0) {
emit_op(p, OP_STYLE_PUSH, 0);
if (!compile_plus_segs(p, vars->vars[i].value, vars->vars[i].vlen)) return 0;
emit_op(p, OP_STYLE_FLUSH, 0);
return 1;
}
}
return 0;
}
static int compile_tag(program_t *p, const char *tag, int len, int closing, var_table_t *vars) {
if (closing) {
if (tag_eq(tag, len, "pad") || tag_eq(tag, len, "rpad")) emit_op(p, OP_PAD_END, 0);
else emit_op(p, OP_STYLE_RESET, 0);
return 1;
}
if (tag_prefix(tag, len, "let ")) return compile_let(vars, tag, len);
if (tag[0] == '$' && len > 1) return compile_var_ref(p, vars, tag, len);
if (tag_prefix(tag, len, "pad=")) { emit_op(p, OP_PAD_BEGIN, (uint32_t)atoi(tag + 4)); return 1; }
if (tag_prefix(tag, len, "rpad=")) { emit_op(p, OP_RPAD_BEGIN, (uint32_t)atoi(tag + 5)); return 1; }
+
+ if (tag_prefix(tag, len, "space=") && tag[len-1] == '/') {
+ emit_op(p, OP_EMIT_SPACES, (uint32_t)atoi(tag + 6));
+ return 1;
+ }
emit_op(p, OP_STYLE_PUSH, 0);
if (match_attr(p, tag, len)) { emit_op(p, OP_STYLE_FLUSH, 0); return 1; }
if (match_fg(p, tag, len)) { emit_op(p, OP_STYLE_FLUSH, 0); return 1; }
if (match_bg(p, tag, len)) { emit_op(p, OP_STYLE_FLUSH, 0); return 1; }
if (tag[0] == '#') {
if (!compile_hex_fg(p, tag, len)) return 0;
emit_op(p, OP_STYLE_FLUSH, 0); return 1;
}
if (tag_prefix(tag, len, "bg_#")) {
if (!compile_hex_bg(p, tag + 3, len - 3)) return 0;
emit_op(p, OP_STYLE_FLUSH, 0); return 1;
}
const char *seg = tag;
const char *end = tag + len;
int emitted = 0;
while (seg < end) {
int slen;
const char *sep = next_seg(seg, end, &slen);
if (match_attr(p, seg, slen)) {
} else if (tag_eq(seg, slen, "bg") && sep < end) {
seg = sep + 1;
sep = next_seg(seg, end, &slen);
if (!match_seg_bg(p, seg, slen)) return 0;
} else if (!match_fg(p, seg, slen)) return 0;
emitted++;
seg = (sep < end) ? sep + 1 : end;
}
if (emitted) { emit_op(p, OP_STYLE_FLUSH, 0); return 1; }
return 0;
}
static void flush_lit(program_t *p, const char *lit, const char *end) {
if (end <= lit) return;
uint32_t off = add_literal(p, lit, end - lit);
emit_op(p, OP_EMIT_LIT, off);
}
static const char *scan_tag(program_t *p, const char *ptr, const char **lit, var_table_t *vars) {
flush_lit(p, *lit, ptr);
const char *start = ptr + 1;
int closing = 0;
if (*start == '/') { closing = 1; start++; }
if (closing && *start == '>') {
emit_op(p, OP_STYLE_RESET, 0);
*lit = start + 1;
return *lit;
}
const char *end = start;
while (*end && *end != '>') end++;
if (*end == '>' && compile_tag(p, start, (int)(end - start), closing, vars)) {
*lit = end + 1;
return *lit;
}
uint32_t off = add_literal(p, "<", 1);
emit_op(p, OP_EMIT_LIT, off);
*lit = ptr + 1;
return *lit;
}
typedef enum {
ARG_NONE = 0,
ARG_INT,
ARG_LONG,
ARG_LLONG,
ARG_SIZE,
ARG_DOUBLE,
ARG_CSTR,
ARG_PTR,
ARG_WINT,
ARG_WSTR,
} arg_class_t;
static arg_class_t classify_arg(const char *spec, int len) {
char conv = spec[len - 1];
if (conv == '%' || conv == 'n') return ARG_NONE;
if (conv == 's') return ARG_CSTR;
if (conv == 'p') return ARG_PTR;
if (conv == 'f' || conv == 'F' || conv == 'e' || conv == 'E' ||
conv == 'g' || conv == 'G' || conv == 'a' || conv == 'A')
return ARG_DOUBLE;
const char *p = spec + 1;
while (*p == '-' || *p == '+' || *p == ' ' || *p == '#' || *p == '0') p++;
if (*p == '*') p++; else while (*p >= '0' && *p <= '9') p++;
if (*p == '.') { p++; if (*p == '*') p++; else while (*p >= '0' && *p <= '9') p++; }
if (p[0] == 'z') return ARG_SIZE;
if (p[0] == 'l' && p[1] == 'l') return ARG_LLONG;
if (p[0] == 'l' && conv == 'c') return ARG_WINT;
if (p[0] == 'l' && conv == 's') return ARG_WSTR;
if (p[0] == 'l') return ARG_LONG;
if (p[0] == 'j') return ARG_LLONG;
return ARG_INT;
}
static const char *scan_fmt(program_t *p, const char *ptr, const char **lit) {
flush_lit(p, *lit, ptr);
const char *fs = ptr + 1;
while (*fs=='-'||*fs=='+'||*fs==' '||*fs=='#'||*fs=='0') fs++;
if (*fs == '*') fs++; else while (*fs >= '0' && *fs <= '9') fs++;
if (*fs == '.') { fs++; if (*fs == '*') fs++; else while (*fs >= '0' && *fs <= '9') fs++; }
while (*fs=='h'||*fs=='l'||*fs=='L'||*fs=='z'||*fs=='j'||*fs=='t') fs++;
if (*fs) fs++;
uint32_t off = add_literal(p, ptr, fs - ptr);
arg_class_t cls = classify_arg(ptr, (int)(fs - ptr));
emit_op(p, OP_EMIT_FMT, off | ((uint32_t)cls << 28));
*lit = fs;
return fs;
}
static const char *scan_percent_escape(program_t *p, const char *ptr, const char **lit) {
flush_lit(p, *lit, ptr);
uint32_t off = add_literal(p, "%%", 2);
emit_op(p, OP_EMIT_LIT, off);
*lit = ptr + 2;
return *lit;
}
program_t *cprintf_compile(const char *fmt) {
program_t *p = program_new();
var_table_t vars = {0};
const char *ptr = fmt;
const char *lit = ptr;
while (*ptr) {
if (*ptr == '<')
ptr = scan_tag(p, ptr, &lit, &vars);
else if (*ptr == '%' && ptr[1] && ptr[1] != '%')
ptr = scan_fmt(p, ptr, &lit);
else if (*ptr == '%' && ptr[1] == '%')
ptr = scan_percent_escape(p, ptr, &lit);
else ptr++;
}
flush_lit(p, lit, ptr);
emit_op(p, OP_HALT, 0);
return p;
}
static size_t visible_len(const char *s, size_t n) {
size_t vis = 0;
for (size_t i = 0; i < n; i++) {
if (s[i] == '\x1b') while (++i < n && !isalpha(s[i]));
else vis++;
}
return vis;
}
int cprintf_vm_exec(program_t *prog, FILE *stream, va_list ap) {
vm_regs_t regs = {0};
size_t cap = 512, pos = 0;
char *out = malloc(cap);
if (!out) return -1;
#define ENSURE(n) ({ \
while (pos + (n) + 1 > cap) { \
cap *= 2; out = realloc(out, cap); \
if (!out) return -1; \
}})
#define OUT_STR(s, l) ({ ENSURE(l); memcpy(out+pos, s, l); pos += (l); })
#define OUT_CSTR(s) ({ size_t _l = strlen(s); OUT_STR(s, _l); })
const instruction_t *ip = prog->code;
static const void *dispatch[OP_MAX] = {
[OP_NOP] = &&op_nop,
[OP_EMIT_LIT] = &&op_emit_lit,
[OP_EMIT_FMT] = &&op_emit_fmt,
[OP_SET_FG] = &&op_set_fg,
[OP_SET_BG] = &&op_set_bg,
[OP_SET_FG_RGB] = &&op_set_fg_rgb,
[OP_SET_BG_RGB] = &&op_set_bg_rgb,
[OP_SET_BOLD] = &&op_set_bold,
[OP_SET_DIM] = &&op_set_dim,
[OP_SET_UL] = &&op_set_ul,
[OP_STYLE_PUSH] = &&op_style_push,
[OP_STYLE_FLUSH] = &&op_style_flush,
[OP_STYLE_RESET] = &&op_style_reset,
[OP_PAD_BEGIN] = &&op_pad_begin,
[OP_RPAD_BEGIN] = &&op_rpad_begin,
[OP_PAD_END] = &&op_pad_end,
+ [OP_EMIT_SPACES] = &&op_emit_spaces,
[OP_HALT] = &&op_halt,
};
#define DISPATCH() goto *dispatch[ip->op]
#define NEXT() do { ip++; DISPATCH(); } while(0)
DISPATCH();
op_nop: NEXT();
op_emit_lit: {
OUT_CSTR(prog->literals + ip->operand);
NEXT();
}
op_emit_fmt: {
uint32_t lit_off = ip->operand & 0x0FFFFFFF;
arg_class_t cls = (arg_class_t)(ip->operand >> 28);
const char *spec = prog->literals + lit_off;
char tmp[256];
va_list ap_copy;
va_copy(ap_copy, ap);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
int n = vsnprintf(tmp, sizeof(tmp), spec, ap_copy);
va_end(ap_copy);
if (n > 0 && (size_t)n < sizeof(tmp)) {
OUT_STR(tmp, (size_t)n);
} else if (n > 0) {
ENSURE((size_t)n);
va_copy(ap_copy, ap);
vsnprintf(out + pos, (size_t)n + 1, spec, ap_copy);
va_end(ap_copy);
pos += (size_t)n;
}
#pragma GCC diagnostic pop
switch (cls) {
case ARG_INT: (void)va_arg(ap, int); break;
case ARG_LONG: (void)va_arg(ap, long); break;
case ARG_LLONG: (void)va_arg(ap, long long); break;
case ARG_SIZE: (void)va_arg(ap, size_t); break;
case ARG_DOUBLE: (void)va_arg(ap, double); break;
case ARG_CSTR: (void)va_arg(ap, const char *); break;
case ARG_PTR: (void)va_arg(ap, void *); break;
case ARG_WINT: (void)va_arg(ap, wint_t); break;
case ARG_WSTR: (void)va_arg(ap, wchar_t *); break;
case ARG_NONE: break;
}
NEXT();
}
op_set_fg: {
regs.fg = ip->operand;
NEXT();
}
op_set_bg: {
regs.bg = ip->operand;
NEXT();
}
op_set_bold: {
regs.bold = ip->operand;
NEXT();
}
op_set_dim: {
regs.dim = ip->operand;
NEXT();
}
op_set_ul: {
regs.ul = ip->operand;
NEXT();
}
op_set_fg_rgb: {
regs.fg = COL_RGB;
regs.fg_rgb = ip->operand;
NEXT();
}
op_set_bg_rgb: {
regs.bg = COL_RGB;
regs.bg_rgb = ip->operand;
NEXT();
}
op_style_push: {
if (regs.style_depth < 8) regs.style_stack[regs.style_depth++] = (style_entry_t){
regs.fg, regs.bg, regs.fg_rgb, regs.bg_rgb, regs.bold, regs.dim, regs.ul,
};
NEXT();
}
op_pad_begin: {
if (regs.pad_depth < 8) regs.pad_stack[regs.pad_depth++]
= (pad_entry_t){ pos, (int)ip->operand, 0 };
NEXT();
}
op_rpad_begin: {
if (regs.pad_depth < 8) regs.pad_stack[regs.pad_depth++]
= (pad_entry_t){ pos, (int)ip->operand, 1 };
NEXT();
}
op_pad_end: {
if (regs.pad_depth <= 0) NEXT();
regs.pad_depth--;
pad_entry_t pe = regs.pad_stack[regs.pad_depth];
size_t vis = visible_len(out + pe.mark, pos - pe.mark);
if ((size_t)pe.width <= vis) NEXT();
size_t pad_n = pe.width - vis;
ENSURE(pad_n);
if (pe.right_align) {
memmove(out + pe.mark + pad_n, out + pe.mark, pos - pe.mark);
memset(out + pe.mark, ' ', pad_n);
} else memset(out + pos, ' ', pad_n);
pos += pad_n;
NEXT();
}
+
+ op_emit_spaces: {
+ size_t n = (size_t)ip->operand;
+ ENSURE(n);
+ memset(out + pos, ' ', n);
+ pos += n;
+ NEXT();
+ }
op_style_reset: {
if (regs.style_depth > 0) {
style_entry_t se = regs.style_stack[--regs.style_depth];
regs.fg = se.fg; regs.bg = se.bg;
regs.fg_rgb = se.fg_rgb; regs.bg_rgb = se.bg_rgb;
regs.bold = se.bold; regs.dim = se.dim; regs.ul = se.ul;
} else {
regs.fg = COL_NONE; regs.bg = COL_NONE;
regs.fg_rgb = 0; regs.bg_rgb = 0;
regs.bold = 0; regs.dim = 0; regs.ul = 0;
}
if (!io_no_color) {
char esc[128];
int n = snprintf(esc, sizeof(esc), "\x1b[0m");
if (regs.bold) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[1m");
if (regs.dim) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[2m");
if (regs.ul) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[4m");
if (regs.fg == COL_RGB)
n += snprintf(esc+n, sizeof(esc)-n, "\x1b[38;2;%d;%d;%dm",
UNPACK_R(regs.fg_rgb), UNPACK_G(regs.fg_rgb), UNPACK_B(regs.fg_rgb));
else if (regs.fg)
n += snprintf(esc+n, sizeof(esc)-n, "\x1b[%dm", regs.fg);
if (regs.bg == COL_RGB)
n += snprintf(esc+n, sizeof(esc)-n, "\x1b[48;2;%d;%d;%dm",
UNPACK_R(regs.bg_rgb), UNPACK_G(regs.bg_rgb), UNPACK_B(regs.bg_rgb));
else if (regs.bg)
n += snprintf(esc+n, sizeof(esc)-n, "\x1b[%dm", regs.bg + 10);
OUT_STR(esc, (size_t)n);
}
NEXT();
}
op_style_flush: {
if (!io_no_color) {
char esc[128];
int n = snprintf(esc, sizeof(esc), "\x1b[0m");
if (regs.bold) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[1m");
if (regs.dim) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[2m");
if (regs.ul) n += snprintf(esc+n, sizeof(esc)-n, "\x1b[4m");
if (regs.fg == COL_RGB)
n += snprintf(esc+n, sizeof(esc)-n, "\x1b[38;2;%d;%d;%dm",
UNPACK_R(regs.fg_rgb), UNPACK_G(regs.fg_rgb), UNPACK_B(regs.fg_rgb));
else if (regs.fg)
n += snprintf(esc+n, sizeof(esc)-n, "\x1b[%dm", regs.fg);
if (regs.bg == COL_RGB)
n += snprintf(esc+n, sizeof(esc)-n, "\x1b[48;2;%d;%d;%dm",
UNPACK_R(regs.bg_rgb), UNPACK_G(regs.bg_rgb), UNPACK_B(regs.bg_rgb));
else if (regs.bg)
n += snprintf(esc+n, sizeof(esc)-n, "\x1b[%dm", regs.bg + 10);
OUT_STR(esc, (size_t)n);
}
NEXT();
}
op_halt: {
out[pos] = '\0';
int ret = (int)fwrite(out, 1, pos, stream);
free(out); return ret;
}
#undef ENSURE
#undef OUT_STR
#undef OUT_CSTR
}
int cprintf_exec(program_t *prog, FILE *stream, ...) {
va_list ap; va_start(ap, stream);
int ret = cprintf_vm_exec(prog, stream, ap);
va_end(ap);
return ret;
}
int csprintf_inner(program_t *prog, char *buf, size_t size, ...) {
FILE *f = fmemopen(buf, size, "w");
if (!f) return -1;
va_list ap; va_start(ap, size);
int ret = cprintf_vm_exec(prog, f, ap);
va_end(ap); fclose(f);
return ret;
}
\ No newline at end of file
diff --git a/src/cli/misc.c b/src/cli/misc.c
new file mode 100644
index 0000000..6cc6e03
--- /dev/null
+++ b/src/cli/misc.c
@@ -0,0 +1,37 @@
+#include <stdio.h>
+#include <argtable3.h>
+
+#include "cli/misc.h"
+#include "cli/cprintf.h"
+
+void print_flag(FILE *fp, flag_help_t f) {
+ const char *s = f.s, *l = f.l, *d = f.d, *g = f.g;
+ int opt = f.opt;
+
+ char syn[200];
+ csprintf(syn, sizeof(syn),
+ "<cyan>%s</cyan>%s<cyan>%s%s%s%s</cyan>",
+ s ? (char[]){'-', s[0], '\0'} : (l ? " " : ""),
+ s && l ? ", " : "",
+ l ? "--" : "",
+ l ? l : "",
+ d && l && opt ? "[=" :
+ d && l ? "=" :
+ d && s ? " " : "",
+ d ? d : "");
+
+ cfprintf(fp, "<space=2/><pad=32>%s</pad> %s</>\n", syn, g);
+}
+
+void print_flags_help(FILE *fp, void **argtable) {
+ struct arg_hdr **table = (struct arg_hdr **)argtable;
+ for (int i = 0; !(table[i]->flag & ARG_TERMINATOR); i++) {
+ struct arg_hdr *hdr = table[i];
+ if (!hdr->glossary) continue;
+ print_flag(fp, (flag_help_t){
+ .s = hdr->shortopts, .l = hdr->longopts,
+ .d = hdr->datatype, .g = hdr->glossary,
+ .opt = hdr->flag & ARG_HASOPTVALUE,
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/cli/pkg.c b/src/cli/pkg.c
index 55a9052..c962536 100644
--- a/src/cli/pkg.c
+++ b/src/cli/pkg.c
@@ -1,1503 +1,1505 @@
#include <compat.h> // IWYU pragma: keep
#include <pkg.h>
-#include <cli/pkg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <time.h>
#include <argtable3.h>
#include <yyjson.h>
+#include "cli/pkg.h"
+#include "cli/version.h"
+
#include "utils.h"
#include "config.h"
#include "progress.h"
#include "modules/io.h"
bool pkg_verbose = false;
static void progress_callback(void *user_data, pkg_phase_t phase, uint32_t current, uint32_t total, const char *message) {
progress_t *progress = (progress_t *)user_data;
if (!progress || !message || !message[0]) return;
const char *icon;
switch (phase) {
case PKG_PHASE_RESOLVING: icon = "🔍"; break;
case PKG_PHASE_FETCHING: icon = "🚚"; break;
case PKG_PHASE_EXTRACTING: icon = "📦"; break;
case PKG_PHASE_LINKING: icon = "🔗"; break;
case PKG_PHASE_CACHING: icon = "💾"; break;
case PKG_PHASE_POSTINSTALL: icon = "⚙️ "; break;
default: icon = "📦"; break;
}
char msg[PROGRESS_MSG_SIZE];
if (total > 0) snprintf(msg, sizeof(msg), "%s %s [%u/%u]", icon, message, current, total);
else if (current > 0) snprintf(msg, sizeof(msg), "%s %s [%u]", icon, message, current);
else snprintf(msg, sizeof(msg), "%s %s", icon, message);
progress_update(progress, msg);
}
static void print_added_packages(pkg_context_t *ctx) {
uint32_t count = pkg_get_added_count(ctx);
uint32_t printed = 0;
if (count > 0) printf("\n");
for (uint32_t i = 0; i < count; i++) {
pkg_added_package_t pkg;
if (pkg_get_added_package(ctx, i, &pkg) == PKG_OK && pkg.direct) {
printf("%s+%s %s%s%s@%s%s%s\n",
C_GREEN, C_RESET,
C_BOLD, pkg.name, C_RESET,
C_DIM, pkg.version, C_RESET
); printed++;
}
}
if (printed > 0) printf("\n");
}
static uint64_t timespec_diff_ms(struct timespec *start, struct timespec *end) {
int64_t sec = end->tv_sec - start->tv_sec;
int64_t nsec = end->tv_nsec - start->tv_nsec;
if (nsec < 0) { sec--; nsec += 1000000000; }
return (uint64_t)sec * 1000 + (uint64_t)nsec / 1000000;
}
static void print_elapsed(uint64_t elapsed_ms) {
fputs(C_BOLD, stdout);
if (elapsed_ms < 1000) {
printf("%llums", (unsigned long long)elapsed_ms);
} else printf("%.2fs", (double)elapsed_ms / 1000.0);
fputs(C_RESET, stdout);
}
static void print_install_header(const char *cmd) {
const char *version = ant_semver();
printf("%sant %s%s v%s %s(%s)%s\n",
C_BOLD, cmd, C_RESET, version,
C_DIM, ANT_GIT_HASH, C_RESET
);
}
static void print_bin_callback(const char *name, void *user_data) {
(void)user_data;
printf(" %s-%s %s\n", C_DIM, C_RESET, name);
}
static void prompt_with_default(const char *prompt, const char *def, char *buf, size_t buf_size) {
if (def && def[0]) {
printf("%s%s%s %s(%s)%s: ", C_CYAN, prompt, C_RESET, C_DIM, def, C_RESET);
} else printf("%s%s%s: ", C_CYAN, prompt, C_RESET);
fflush(stdout);
if (fgets(buf, (int)buf_size, stdin)) {
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') buf[len - 1] = '\0';
}
if (buf[0] == '\0' && def) {
strncpy(buf, def, buf_size - 1);
buf[buf_size - 1] = '\0';
}
}
typedef struct {
const char *target;
int count;
} why_ctx_t;
static void print_why_callback(const char *name, const char *version, const char *constraint, pkg_dep_type_t dep_type, void *user_data) {
why_ctx_t *ctx = (why_ctx_t *)user_data;
if (strcmp(name, "package.json") == 0) {
const char *type_str = dep_type.dev ? "devDependencies" : "dependencies";
printf(" %s└%s %s%s%s %s(%s)%s\n",
C_DIM, C_RESET,
C_GREEN, name, C_RESET,
C_DIM, type_str, C_RESET);
} else {
const char *type_str = dep_type.peer ? "peer" : (dep_type.dev ? "dev" : (dep_type.optional ? "optional" : ""));
if (type_str[0]) {
printf(" %s└%s %s %s%s%s@%s%s%s %s\"%s\"%s\n",
C_DIM, C_RESET,
type_str,
C_BOLD, name, C_RESET,
C_DIM, version, C_RESET,
C_CYAN, constraint, C_RESET);
} else {
printf(" %s└%s %s%s%s@%s%s%s %s\"%s\"%s\n",
C_DIM, C_RESET,
C_BOLD, name, C_RESET,
C_DIM, version, C_RESET,
C_CYAN, constraint, C_RESET);
}
}
ctx->count++;
}
static void print_script(const char *name, const char *command, void *ud) {
(void)ud;
if (strlen(command) > 50) {
printf(" %-15s %.47s...\n", name, command);
} else {
printf(" %-15s %s\n", name, command);
}
}
static void print_bin_name(const char *name, void *ud) {
(void)ud;
printf(" %s\n", name);
}
bool pkg_script_exists(const char *package_json_path, const char *script_name) {
char script_cmd[4096];
return pkg_get_script(package_json_path, script_name, script_cmd, sizeof(script_cmd)) >= 0;
}
static const char *get_global_dir(void) {
static char global_dir[4096] = {0};
if (global_dir[0] == '\0') {
const char *home = getenv("HOME");
if (home) snprintf(global_dir, sizeof(global_dir), "%s/.ant/pkg/global", home);
}
return global_dir;
}
static int cmd_add_global(const char *const *package_specs, int count) {
print_install_header("add -g");
char resolve_msg[64];
snprintf(resolve_msg, sizeof(resolve_msg), "🔍 Resolving [%d/%d]", count, count);
progress_t progress;
if (!pkg_verbose) progress_start(&progress, resolve_msg);
pkg_options_t opts = {
.progress_callback = pkg_verbose ? NULL : progress_callback,
.user_data = pkg_verbose ? NULL : &progress,
.verbose = pkg_verbose
};
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
if (!pkg_verbose) progress_stop(&progress);
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
pkg_error_t err = pkg_add_global_many(ctx, package_specs, (uint32_t)count);
if (!pkg_verbose) progress_stop(&progress);
if (err != PKG_OK) {
fprintf(stderr, "Error: %s\n", pkg_error_string(ctx));
pkg_free(ctx);
return EXIT_FAILURE;
}
pkg_install_result_t result;
if (pkg_get_install_result(ctx, &result) == PKG_OK) {
for (int i = 0; i < count; i++) {
printf("\n%sinstalled globally%s %s%s%s\n",
C_GREEN, C_RESET, C_BOLD, package_specs[i], C_RESET);
}
printf(" %s(binaries linked to ~/.ant/bin)%s\n", C_DIM, C_RESET);
printf("\n%s[%s", C_DIM, C_RESET);
print_elapsed(result.elapsed_ms);
printf("%s]%s done\n", C_DIM, C_RESET);
}
pkg_free(ctx);
return EXIT_SUCCESS;
}
static int cmd_remove_global(const char *package_name) {
print_install_header("remove -g");
progress_t progress;
if (!pkg_verbose) progress_start(&progress, "🔍 Resolving");
pkg_options_t opts = {
.progress_callback = pkg_verbose ? NULL : progress_callback,
.user_data = pkg_verbose ? NULL : &progress,
.verbose = pkg_verbose
};
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
if (!pkg_verbose) progress_stop(&progress);
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
pkg_error_t err = pkg_remove_global(ctx, package_name);
if (!pkg_verbose) progress_stop(&progress);
if (err == PKG_NOT_FOUND) {
printf("\nPackage '%s' not found in global dependencies\n", package_name);
pkg_free(ctx);
return EXIT_SUCCESS;
}
if (err != PKG_OK) {
fprintf(stderr, "Error: %s\n", pkg_error_string(ctx));
pkg_free(ctx);
return EXIT_FAILURE;
}
printf("\n%s-%s Removed globally: %s%s%s\n", C_RED, C_RESET, C_BOLD, package_name, C_RESET);
pkg_free(ctx);
return EXIT_SUCCESS;
}
static int cmd_install(void) {
print_install_header("install");
progress_t progress;
if (!pkg_verbose) {
progress_start(&progress, "🔍 Resolving [1/1]");
}
pkg_options_t opts = {
.progress_callback = pkg_verbose ? NULL : progress_callback,
.user_data = pkg_verbose ? NULL : &progress,
.verbose = pkg_verbose
};
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
struct stat st;
bool needs_resolve = (stat("ant.lockb", &st) != 0);
if (needs_resolve) {
if (stat("package.json", &st) != 0) {
if (!pkg_verbose) { progress_stop(&progress); }
fprintf(stderr, "Error: No package.json found\n");
pkg_free(ctx);
return EXIT_FAILURE;
}
pkg_error_t err = pkg_resolve_and_install(ctx, "package.json", "ant.lockb", "node_modules");
if (err != PKG_OK) {
if (!pkg_verbose) { progress_stop(&progress); }
fprintf(stderr, "Error: %s\n", pkg_error_string(ctx));
pkg_free(ctx);
return EXIT_FAILURE;
}
} else {
pkg_error_t err = pkg_install(ctx, "package.json", "ant.lockb", "node_modules");
if (err != PKG_OK) {
if (!pkg_verbose) { progress_stop(&progress); }
fprintf(stderr, "Error: %s\n", pkg_error_string(ctx));
pkg_free(ctx);
return EXIT_FAILURE;
}
}
if (!pkg_verbose) {
progress_stop(&progress);
}
pkg_install_result_t result;
if (pkg_get_install_result(ctx, &result) == PKG_OK) {
if (result.packages_installed > 0) {
print_added_packages(ctx);
printf("%s%u%s package%s installed",
C_GREEN, result.packages_installed, C_RESET,
result.packages_installed == 1 ? "" : "s");
if (result.cache_hits > 0) {
printf(" %s(%u cached)%s", C_DIM, result.cache_hits, C_RESET);
}
printf(" %s[%s", C_DIM, C_RESET);
print_elapsed(result.elapsed_ms);
printf("%s]%s\n", C_DIM, C_RESET);
} else {
printf("\n%sChecked%s %s%u%s installs across %s%u%s packages %s(no changes)%s %s[%s",
C_DIM, C_RESET,
C_GREEN, result.packages_installed + result.packages_skipped, C_RESET,
C_GREEN, result.package_count, C_RESET,
C_DIM, C_RESET,
C_DIM, C_RESET);
print_elapsed(result.elapsed_ms);
printf("%s]%s\n", C_DIM, C_RESET);
}
}
if (pkg_discover_lifecycle_scripts(ctx, "node_modules") == PKG_OK) {
uint32_t script_count = pkg_get_lifecycle_script_count(ctx);
if (script_count > 0) {
printf("\n%s%u%s package%s need%s to run lifecycle scripts:\n",
C_YELLOW, script_count, C_RESET,
script_count == 1 ? "" : "s",
script_count == 1 ? "s" : "");
for (uint32_t i = 0; i < script_count; i++) {
pkg_lifecycle_script_t script;
if (pkg_get_lifecycle_script(ctx, i, &script) == PKG_OK) {
printf(" %s•%s %s%s%s %s(%s)%s\n",
C_DIM, C_RESET,
C_CYAN, script.name, C_RESET,
C_DIM, script.script, C_RESET);
}
}
printf("\nRun: %sant trust <pkg>%s or %sant trust --all%s\n", C_DIM, C_RESET, C_DIM, C_RESET);
}
}
pkg_free(ctx);
return EXIT_SUCCESS;
}
static int cmd_add(const char *const *package_specs, int count, bool dev) {
print_install_header(dev ? "add -D" : "add");
char resolve_msg[64];
snprintf(resolve_msg, sizeof(resolve_msg), "🔍 Resolving [%d/%d]", count, count);
progress_t progress;
if (!pkg_verbose) {
progress_start(&progress, resolve_msg);
}
pkg_options_t opts = {
.progress_callback = pkg_verbose ? NULL : progress_callback,
.user_data = pkg_verbose ? NULL : &progress,
.verbose = pkg_verbose
};
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
pkg_error_t err = pkg_add_many(ctx, "package.json", package_specs, (uint32_t)count, dev);
if (err != PKG_OK) {
if (!pkg_verbose) { progress_stop(&progress); }
fprintf(stderr, "Error: %s\n", pkg_error_string(ctx));
pkg_free(ctx);
return EXIT_FAILURE;
}
err = pkg_resolve_and_install(ctx, "package.json", "ant.lockb", "node_modules");
if (err != PKG_OK) {
if (!pkg_verbose) { progress_stop(&progress); }
fprintf(stderr, "Error: %s\n", pkg_error_string(ctx));
pkg_free(ctx);
return EXIT_FAILURE;
}
if (!pkg_verbose) progress_stop(&progress);
pkg_install_result_t result;
if (pkg_get_install_result(ctx, &result) == PKG_OK) {
printf("\n");
uint32_t added_count = pkg_get_added_count(ctx);
for (uint32_t i = 0; i < added_count; i++) {
pkg_added_package_t pkg;
if (pkg_get_added_package(ctx, i, &pkg) == PKG_OK && pkg.direct) {
int bin_count = pkg_list_package_bins("node_modules", pkg.name, NULL, NULL);
if (bin_count > 0) {
printf("%sinstalled%s %s%s@%s%s with binaries:\n",
C_GREEN, C_RESET,
C_BOLD, pkg.name, pkg.version, C_RESET);
pkg_list_package_bins("node_modules", pkg.name, print_bin_callback, NULL);
} else {
printf("%sinstalled%s %s%s@%s%s\n",
C_GREEN, C_RESET,
C_BOLD, pkg.name, pkg.version, C_RESET);
}
}
}
printf("\n%s%u%s package%s installed %s[%s",
C_GREEN, result.packages_installed, C_RESET,
result.packages_installed == 1 ? "" : "s",
C_DIM, C_RESET);
print_elapsed(result.elapsed_ms);
printf("%s]%s\n", C_DIM, C_RESET);
}
pkg_free(ctx);
return EXIT_SUCCESS;
}
static int cmd_remove(const char *package_name) {
print_install_header("remove");
progress_t progress;
if (!pkg_verbose) {
progress_start(&progress, "🔍 Resolving");
}
pkg_options_t opts = {
.progress_callback = pkg_verbose ? NULL : progress_callback,
.user_data = pkg_verbose ? NULL : &progress,
.verbose = pkg_verbose
};
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
pkg_error_t err = pkg_remove(ctx, "package.json", package_name);
if (err != PKG_OK && err != PKG_NOT_FOUND) {
if (!pkg_verbose) { progress_stop(&progress); }
fprintf(stderr, "Error: %s\n", pkg_error_string(ctx));
pkg_free(ctx);
return EXIT_FAILURE;
}
if (err == PKG_NOT_FOUND) {
if (!pkg_verbose) { progress_stop(&progress); }
printf("\n%s[%s", C_DIM, C_RESET);
printf("%s0ms%s", C_BOLD, C_RESET);
printf("%s]%s done\n", C_DIM, C_RESET);
pkg_free(ctx);
return EXIT_SUCCESS;
}
err = pkg_resolve_and_install(ctx, "package.json", "ant.lockb", "node_modules");
if (err != PKG_OK) {
if (!pkg_verbose) { progress_stop(&progress); }
fprintf(stderr, "Error: %s\n", pkg_error_string(ctx));
pkg_free(ctx);
return EXIT_FAILURE;
}
if (!pkg_verbose) {
progress_stop(&progress);
}
pkg_install_result_t result;
if (pkg_get_install_result(ctx, &result) == PKG_OK) {
printf("\n%s%u%s package%s installed %s[%s",
C_GREEN, result.packages_installed, C_RESET,
result.packages_installed == 1 ? "" : "s",
C_DIM, C_RESET);
print_elapsed(result.elapsed_ms);
printf("%s]%s\n", C_DIM, C_RESET);
}
printf("%s-%s Removed: %s%s%s\n", C_RED, C_RESET, C_BOLD, package_name, C_RESET);
pkg_free(ctx);
return EXIT_SUCCESS;
}
static int cmd_trust(const char **pkgs, int count, bool all) {
print_install_header("trust");
struct timespec start_time;
clock_gettime(CLOCK_MONOTONIC, &start_time);
pkg_options_t opts = { .verbose = pkg_verbose };
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
if (pkg_discover_lifecycle_scripts(ctx, "node_modules") != PKG_OK) {
fprintf(stderr, "Error: Failed to scan node_modules\n");
pkg_free(ctx);
return EXIT_FAILURE;
}
uint32_t script_count = pkg_get_lifecycle_script_count(ctx);
if (script_count == 0) {
printf("No packages need lifecycle scripts to run.\n");
pkg_free(ctx);
return EXIT_SUCCESS;
}
const char **to_run = NULL;
uint32_t to_run_count = 0;
if (all) {
to_run = try_oom(script_count * sizeof(char *));
if (to_run) {
for (uint32_t i = 0; i < script_count; i++) {
pkg_lifecycle_script_t script;
if (pkg_get_lifecycle_script(ctx, i, &script) == PKG_OK) {
to_run[to_run_count++] = script.name;
}
}
}
} else if (count > 0) {
to_run = try_oom(count * sizeof(char *));
if (to_run) {
for (int i = 0; i < count; i++) {
bool found = false;
for (uint32_t j = 0; j < script_count; j++) {
pkg_lifecycle_script_t script;
if (pkg_get_lifecycle_script(ctx, j, &script) == PKG_OK) {
if (strcmp(pkgs[i], script.name) == 0) {
to_run[to_run_count++] = script.name;
found = true; break;
}
}
}
if (!found) fprintf(stderr, "Warning: %s has no pending lifecycle script\n", pkgs[i]);
}
}
} else {
printf("%s%u%s package%s with lifecycle scripts:\n",
C_YELLOW, script_count, C_RESET,
script_count == 1 ? "" : "s");
for (uint32_t i = 0; i < script_count; i++) {
pkg_lifecycle_script_t script;
if (pkg_get_lifecycle_script(ctx, i, &script) == PKG_OK) {
printf(" %s•%s %s%s%s %s(%s)%s\n",
C_DIM, C_RESET,
C_CYAN, script.name, C_RESET,
C_DIM, script.script, C_RESET);
}
}
printf("\nRun: %sant trust <pkg>%s or %sant trust --all%s\n", C_DIM, C_RESET, C_DIM, C_RESET);
pkg_free(ctx);
return EXIT_SUCCESS;
}
if (to_run && to_run_count > 0) {
if (pkg_verbose) {
printf("[trust] adding %u packages to trustedDependencies\n", to_run_count);
for (uint32_t i = 0; i < to_run_count; i++) {
printf("[trust] %s\n", to_run[i]);
}
}
pkg_error_t add_err = pkg_add_trusted_dependencies("package.json", to_run, to_run_count);
if (add_err == PKG_OK) {
printf("Added %s%u%s package%s to %strustedDependencies%s in package.json\n",
C_GREEN, to_run_count, C_RESET,
to_run_count == 1 ? "" : "s",
C_BOLD, C_RESET);
} else {
if (pkg_verbose) printf("[trust] failed to add trustedDependencies: error %d\n", add_err);
}
printf("Running lifecycle scripts for %s%u%s package%s...\n",
C_GREEN, to_run_count, C_RESET,
to_run_count == 1 ? "" : "s");
pkg_run_postinstall(ctx, "node_modules", to_run, to_run_count);
struct timespec end_time;
clock_gettime(CLOCK_MONOTONIC, &end_time);
uint64_t elapsed_ms = timespec_diff_ms(&start_time, &end_time);
printf("\n%s%u%s package%s trusted %s[%s",
C_GREEN, to_run_count, C_RESET,
to_run_count == 1 ? "" : "s",
C_DIM, C_RESET);
print_elapsed(elapsed_ms);
printf("%s]%s\n", C_DIM, C_RESET);
free((void *)to_run);
}
pkg_free(ctx);
return EXIT_SUCCESS;
}
static int cmd_init(void) {
FILE *fp = fopen("package.json", "r");
if (fp) {
fclose(fp);
fprintf(stderr, "Error: package.json already exists\n");
return EXIT_FAILURE;
}
char cwd[PATH_MAX];
const char *default_name = "my-project";
if (getcwd(cwd, sizeof(cwd))) {
char *base = strrchr(cwd, '/');
if (base && base[1]) default_name = base + 1;
}
bool interactive = isatty(fileno(stdin));
char name[256] = {0};
char version[64] = {0};
char entry[256] = {0};
if (interactive) {
printf("%sant init%s\n\n", C_BOLD, C_RESET);
prompt_with_default("package name", default_name, name, sizeof(name));
prompt_with_default("version", "1.0.0", version, sizeof(version));
prompt_with_default("entry point", "index.js", entry, sizeof(entry));
printf("\n");
} else {
strncpy(name, default_name, sizeof(name) - 1);
strncpy(version, "1.0.0", sizeof(version) - 1);
strncpy(entry, "index.js", sizeof(entry) - 1);
}
fp = fopen("package.json", "w");
if (!fp) {
fprintf(stderr, "Error: Could not create package.json\n");
return EXIT_FAILURE;
}
yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
yyjson_mut_val *root = yyjson_mut_obj(doc);
yyjson_mut_doc_set_root(doc, root);
yyjson_mut_obj_add_str(doc, root, "name", name);
yyjson_mut_obj_add_str(doc, root, "version", version);
yyjson_mut_obj_add_str(doc, root, "type", "module");
yyjson_mut_obj_add_str(doc, root, "main", entry);
yyjson_mut_val *scripts = yyjson_mut_obj_add_obj(doc, root, "scripts");
char start_cmd[300];
snprintf(start_cmd, sizeof(start_cmd), "ant %s", entry);
yyjson_mut_obj_add_str(doc, scripts, "start", start_cmd);
yyjson_mut_obj_add_obj(doc, root, "dependencies");
yyjson_mut_obj_add_obj(doc, root, "devDependencies");
size_t len; char *json_str = yyjson_mut_write(
doc, YYJSON_WRITE_PRETTY_TWO_SPACES
| YYJSON_WRITE_ESCAPE_UNICODE, &len
);
if (json_str) {
fwrite(json_str, 1, len, fp);
free(json_str);
}
yyjson_mut_doc_free(doc);
fclose(fp);
printf("%s+%s Created %spackage.json%s\n", C_GREEN, C_RESET, C_BOLD, C_RESET);
return EXIT_SUCCESS;
}
static int cmd_why(const char *package_name) {
struct stat st;
if (stat("ant.lockb", &st) != 0) {
fprintf(stderr, "Error: No lockfile found. Run 'ant install' first.\n");
return EXIT_FAILURE;
}
pkg_why_info_t info;
if (pkg_why_info("ant.lockb", package_name, &info) < 0) {
fprintf(stderr, "Error: Failed to read lockfile\n");
return EXIT_FAILURE;
}
if (!info.found) {
printf("\n%s%s%s is not installed\n\n", C_BOLD, package_name, C_RESET);
return EXIT_SUCCESS;
}
const char *type_label = info.is_peer ? " peer" : (info.is_dev ? " dev" : "");
printf("\n%s%s%s@%s%s%s%s%s%s\n", C_BOLD, package_name, C_RESET, C_DIM, info.target_version, C_RESET, C_YELLOW, type_label, C_RESET);
why_ctx_t ctx = { .target = package_name, .count = 0 };
int result = pkg_why("ant.lockb", package_name, print_why_callback, &ctx);
if (result < 0) {
fprintf(stderr, "Error: Failed to read lockfile\n");
return EXIT_FAILURE;
}
if (ctx.count == 0) {
printf(" %s(no dependents)%s\n", C_DIM, C_RESET);
}
printf("\n");
return EXIT_SUCCESS;
}
int pkg_cmd_init(int argc, char **argv) {
struct arg_lit *help = arg_lit0("h", "help", "display help");
struct arg_end *end = arg_end(5);
void *argtable[] = { help, end };
int nerrors = arg_parse(argc, argv, argtable);
int exitcode = EXIT_SUCCESS;
if (help->count > 0) {
printf("Usage: ant init\n\n");
printf("Create a new package.json\n");
} else if (nerrors > 0) {
arg_print_errors(stdout, end, "ant init");
exitcode = EXIT_FAILURE;
} else {
exitcode = cmd_init();
}
arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
return exitcode;
}
int pkg_cmd_install(int argc, char **argv) {
struct arg_str *pkgs = arg_strn(NULL, NULL, "<package[@version]>", 0, 100, NULL);
struct arg_lit *global = arg_lit0("g", "global", "install globally");
struct arg_lit *dev = arg_lit0("D", "save-dev", "add as devDependency");
struct arg_lit *help = arg_lit0("h", "help", "display help");
struct arg_end *end = arg_end(5);
void *argtable[] = { pkgs, global, dev, help, end };
int nerrors = arg_parse(argc, argv, argtable);
int exitcode = EXIT_SUCCESS;
if (help->count > 0) {
printf("Usage: ant install [packages...] [-g] [-D] [--verbose]\n\n");
printf("Install from lockfile, or add packages if specified.\n");
printf("\nOptions:\n -g, --global Install globally to ~/.ant/pkg/global\n");
printf(" -D, --save-dev Add as devDependency\n");
} else if (nerrors > 0) {
arg_print_errors(stdout, end, "ant install");
exitcode = EXIT_FAILURE;
} else if (pkgs->count == 0) {
exitcode = cmd_install();
} else {
bool is_dev = dev->count > 0;
exitcode = global->count > 0
? cmd_add_global(pkgs->sval, pkgs->count)
: cmd_add(pkgs->sval, pkgs->count, is_dev);
}
arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
return exitcode;
}
int pkg_cmd_add(int argc, char **argv) {
struct arg_str *pkgs = arg_strn(NULL, NULL, "<package[@version]>", 1, 100, NULL);
struct arg_lit *global = arg_lit0("g", "global", "install globally");
struct arg_lit *dev = arg_lit0("D", "save-dev", "add as devDependency");
struct arg_lit *help = arg_lit0("h", "help", "display help");
struct arg_end *end = arg_end(5);
void *argtable[] = { pkgs, global, dev, help, end };
int nerrors = arg_parse(argc, argv, argtable);
int exitcode = EXIT_SUCCESS;
if (help->count > 0) {
printf("Usage: ant add <package[@version]>... [options]\n\n");
printf("Add packages to dependencies.\n");
printf("\nOptions:\n -g, --global Install globally to ~/.ant/pkg/global\n");
printf(" -D, --save-dev Add as devDependency\n");
} else if (nerrors > 0) {
arg_print_errors(stdout, end, "ant add");
exitcode = EXIT_FAILURE;
} else {
bool is_dev = dev->count > 0;
exitcode = global->count > 0
? cmd_add_global(pkgs->sval, pkgs->count)
: cmd_add(pkgs->sval, pkgs->count, is_dev);
}
arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
return exitcode;
}
int pkg_cmd_remove(int argc, char **argv) {
struct arg_str *pkgs = arg_strn(NULL, NULL, "<package>", 1, 100, NULL);
struct arg_lit *global = arg_lit0("g", "global", "remove from global packages");
struct arg_lit *help = arg_lit0("h", "help", "display help");
struct arg_end *end = arg_end(5);
void *argtable[] = { pkgs, global, help, end };
int nerrors = arg_parse(argc, argv, argtable);
int exitcode = EXIT_SUCCESS;
if (help->count > 0) {
printf("Usage: ant remove <package>... [-g]\n\n");
printf("Remove packages from dependencies.\n");
printf("\nOptions:\n -g, --global Remove from global packages\n");
} else if (nerrors > 0) {
arg_print_errors(stdout, end, "ant remove");
exitcode = EXIT_FAILURE;
} else {
for (int i = 0; i < pkgs->count && exitcode == EXIT_SUCCESS; i++) {
exitcode = global->count > 0 ? cmd_remove_global(pkgs->sval[i]) : cmd_remove(pkgs->sval[i]);
}
}
arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
return exitcode;
}
int pkg_cmd_trust(int argc, char **argv) {
struct arg_str *pkgs = arg_strn(NULL, NULL, "<package>", 0, 100, NULL);
struct arg_lit *all = arg_lit0("a", "all", "trust all packages with lifecycle scripts");
struct arg_lit *help = arg_lit0("h", "help", "display help");
struct arg_end *end = arg_end(5);
void *argtable[] = { pkgs, all, help, end };
int nerrors = arg_parse(argc, argv, argtable);
int exitcode = EXIT_SUCCESS;
if (help->count > 0) {
printf("Usage: ant trust [packages...] [--all]\n\n");
printf("Run lifecycle scripts for packages.\n");
printf(" --all, -a Trust and run all pending lifecycle scripts\n");
} else if (nerrors > 0) {
arg_print_errors(stdout, end, "ant trust");
exitcode = EXIT_FAILURE;
} else {
exitcode = cmd_trust(pkgs->sval, pkgs->count, all->count > 0);
}
arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
return exitcode;
}
int pkg_cmd_run(int argc, char **argv) {
if (argc < 2) {
printf("Usage: ant run <script> [-- args...]\n\n");
printf("Run a script from package.json\n\n");
printf("Available scripts:\n");
int count = pkg_list_scripts("package.json", NULL, NULL);
if (count < 0) {
printf(" (no package.json found)\n");
} else if (count == 0) {
printf(" (no scripts defined)\n");
} else {
pkg_list_scripts("package.json", print_script, NULL);
}
return EXIT_SUCCESS;
}
const char *script_name = argv[1];
char script_cmd[4096];
int script_len = pkg_get_script("package.json", script_name, script_cmd, sizeof(script_cmd));
if (script_len < 0) {
fprintf(stderr, "Error: script '%s' not found in package.json\n", script_name);
fprintf(stderr, "Try 'ant run' to list available scripts.\n");
return EXIT_FAILURE;
}
char extra_args[4096] = {0};
int extra_args_len = 0;
bool found_separator = false;
for (int i = 2; i < argc; i++) {
if (!found_separator && strcmp(argv[i], "--") == 0) {
found_separator = true;
continue;
}
if (found_separator) {
if (extra_args_len > 0) {
extra_args[extra_args_len++] = ' ';
}
size_t arg_len = strlen(argv[i]);
if ((size_t)extra_args_len + arg_len < sizeof(extra_args) - 1) {
memcpy(extra_args + extra_args_len, argv[i], arg_len);
extra_args_len += (int)arg_len;
}
}
}
extra_args[extra_args_len] = '\0';
printf("%s$%s %s%s%s", C_MAGENTA, C_RESET, C_BOLD, script_cmd, C_RESET);
if (extra_args_len > 0) {
printf(" %s", extra_args);
}
printf("\n");
pkg_script_result_t result = {0};
pkg_error_t err = pkg_run_script(
"package.json", script_name, "node_modules",
extra_args_len > 0 ? extra_args : NULL,
&result
);
if (err != PKG_OK) {
if (err == PKG_NOT_FOUND) {
fprintf(stderr, "Error: script '%s' not found\n", script_name);
} else {
fprintf(stderr, "Error: failed to run script '%s'\n", script_name);
}
return EXIT_FAILURE;
}
if (result.signal != 0) {
fprintf(stderr, "Script '%s' killed by signal %d\n", script_name, result.signal);
return 128 + result.signal;
}
return result.exit_code;
}
int pkg_cmd_exec(int argc, char **argv) {
if (argc < 2) {
printf("Usage: ant x [--ant] <command> [args...]\n\n");
printf("Run a command from node_modules/.bin or download temporarily\n\n");
printf("Options:\n");
printf(" --ant Run with ant instead of node\n\n");
printf("Available commands:\n");
int count = pkg_list_bins("node_modules", NULL, NULL);
if (count < 0) {
printf(" (no binaries found - run 'ant install' first)\n");
} else if (count == 0) {
printf(" (no binaries installed)\n");
} else {
pkg_list_bins("node_modules", print_bin_name, NULL);
}
return EXIT_SUCCESS;
}
bool use_ant = false;
int cmd_idx = 1;
if (strcmp(argv[1], "--ant") == 0) {
use_ant = true;
cmd_idx = 2;
if (argc < 3) {
fprintf(stderr, "Error: missing command after --ant\n");
return EXIT_FAILURE;
}
}
const char *cmd_name = argv[cmd_idx];
char bin_path[4096];
int path_len = pkg_get_bin_path("node_modules", cmd_name, bin_path, sizeof(bin_path));
if (path_len < 0) {
const char *global_dir = get_global_dir();
if (global_dir[0]) {
char global_nm[4096];
snprintf(global_nm, sizeof(global_nm), "%s/node_modules", global_dir);
path_len = pkg_get_bin_path(global_nm, cmd_name, bin_path, sizeof(bin_path));
}
}
if (path_len < 0) {
progress_t progress;
if (!pkg_verbose) {
char msg[256];
snprintf(msg, sizeof(msg), "🔍 Resolving %s", cmd_name);
progress_start(&progress, msg);
}
pkg_options_t opts = {
.progress_callback = pkg_verbose ? NULL : progress_callback,
.user_data = pkg_verbose ? NULL : &progress,
.verbose = pkg_verbose
};
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
if (!pkg_verbose) progress_stop(&progress);
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
pkg_error_t err = pkg_exec_temp(ctx, cmd_name, bin_path, sizeof(bin_path));
if (!pkg_verbose) progress_stop(&progress);
if (err != PKG_OK) {
const char *err_msg = pkg_error_string(ctx);
if (err_msg && err_msg[0]) {
fprintf(stderr, "Error: %s\n", err_msg);
} else {
fprintf(stderr, "Error: '%s' not found\n", cmd_name);
}
pkg_free(ctx);
return EXIT_FAILURE;
}
pkg_free(ctx);
}
const char *runtime = use_ant ? "ant" : "node";
int arg_offset = cmd_idx + 1;
int new_argc = argc - arg_offset + 2;
char **exec_argv = try_oom(sizeof(char*) * (new_argc + 1));
if (!exec_argv) {
fprintf(stderr, "Error: out of memory\n");
return EXIT_FAILURE;
}
exec_argv[0] = (char *)runtime;
exec_argv[1] = bin_path;
for (int i = arg_offset; i < argc; i++) {
exec_argv[i - arg_offset + 2] = argv[i];
}
exec_argv[new_argc] = NULL;
execvp(runtime, exec_argv);
free(exec_argv);
fprintf(stderr, "Error: failed to execute '%s %s' - is %s installed?\n", runtime, bin_path, runtime);
return EXIT_FAILURE;
}
int pkg_cmd_why(int argc, char **argv) {
struct arg_str *pkg = arg_str1(NULL, NULL, "<package>", "package name to query");
struct arg_lit *help = arg_lit0("h", "help", "display help");
struct arg_end *end = arg_end(5);
void *argtable[] = { pkg, help, end };
int nerrors = arg_parse(argc, argv, argtable);
int exitcode = EXIT_SUCCESS;
if (help->count > 0) {
printf("Usage: ant why <package>\n\n");
printf("Show which packages depend on the given package.\n");
} else if (nerrors > 0) {
arg_print_errors(stdout, end, "ant why");
exitcode = EXIT_FAILURE;
} else {
exitcode = cmd_why(pkg->sval[0]);
}
arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
return exitcode;
}
static const char *format_size(uint64_t bytes, char *buf, size_t buf_size) {
if (bytes >= 1024ULL * 1024 * 1024) snprintf(buf, buf_size, "%.2f GB", (double)bytes / (1024.0 * 1024.0 * 1024.0));
else if (bytes >= 1024 * 1024) snprintf(buf, buf_size, "%.2f MB", (double)bytes / (1024.0 * 1024.0));
else if (bytes >= 1024) snprintf(buf, buf_size, "%.2f KB", (double)bytes / 1024.0);
else snprintf(buf, buf_size, "%llu B", (unsigned long long)bytes);
return buf;
}
static int cmd_info(const char *package_spec) {
pkg_options_t opts = { .verbose = false };
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
pkg_info_t info;
pkg_error_t err = pkg_info(ctx, package_spec, &info);
if (err != PKG_OK) {
fprintf(stderr, "Error: %s\n", pkg_error_string(ctx));
pkg_free(ctx);
return EXIT_FAILURE;
}
char size_buf[32];
printf("%s%s%s%s@%s%s%s%s%s", C_BLUE, C_UL, info.name, C_UL_OFF, C_BLUE, C_BOLD, C_UL, info.version, C_RESET);
if (info.license[0]) printf(" | %s%s%s", C_CYAN, info.license, C_RESET);
printf(" | deps: %u | versions: %u\n", info.dep_count, info.version_count);
if (info.description[0]) printf("%s\n", info.description);
if (info.homepage[0]) printf("%s%s%s\n", C_BLUE, info.homepage, C_RESET);
if (info.keywords[0]) printf("keywords: %s\n", info.keywords);
uint32_t dep_count = pkg_info_dependency_count(ctx);
if (dep_count > 0) {
printf("\n%sdependencies%s (%u):\n", C_BOLD, C_RESET, dep_count);
for (uint32_t i = 0; i < dep_count; i++) {
pkg_dependency_t dep;
if (pkg_info_get_dependency(ctx, i, &dep) == PKG_OK) {
printf("- %s%s%s: %s\n", C_CYAN, dep.name, C_RESET, dep.version);
}
}
}
printf("\n%sdist%s\n", C_BOLD, C_RESET);
if (info.tarball[0]) printf(" %s.tarball:%s %s\n", C_DIM, C_RESET, info.tarball);
if (info.shasum[0]) printf(" %s.shasum:%s %s%s%s\n", C_DIM, C_RESET, C_GREEN, info.shasum, C_RESET);
if (info.integrity[0]) printf(" %s.integrity:%s %s%s%s\n", C_DIM, C_RESET, C_GREEN, info.integrity, C_RESET);
if (info.unpacked_size > 0) printf(" %s.unpackedSize:%s %s%s%s\n", C_DIM, C_RESET, C_BLUE, format_size(info.unpacked_size, size_buf, sizeof(size_buf)), C_RESET);
uint32_t tag_count = pkg_info_dist_tag_count(ctx);
if (tag_count > 0) {
printf("\n%sdist-tags:%s\n", C_BOLD, C_RESET);
for (uint32_t i = 0; i < tag_count; i++) {
pkg_dist_tag_t tag;
if (pkg_info_get_dist_tag(ctx, i, &tag) == PKG_OK) {
const char *tag_color = C_MAGENTA;
if (strcmp(tag.tag, "beta") == 0) tag_color = C_BLUE;
else if (strcmp(tag.tag, "latest") == 0) tag_color = C_CYAN;
printf("%s%s%s: %s\n", tag_color, tag.tag, C_RESET, tag.version);
}
}
}
uint32_t maint_count = pkg_info_maintainer_count(ctx);
if (maint_count > 0) {
printf("\n%smaintainers:%s\n", C_BOLD, C_RESET);
for (uint32_t i = 0; i < maint_count; i++) {
pkg_maintainer_t maint;
if (pkg_info_get_maintainer(ctx, i, &maint) == PKG_OK) {
printf("- %s", maint.name);
if (maint.email[0]) printf(" <%s>", maint.email);
printf("\n");
}
}
}
// Published date
if (info.published[0]) printf("\n%sPublished:%s %s\n", C_BOLD, C_RESET, info.published);
pkg_free(ctx);
return EXIT_SUCCESS;
}
int pkg_cmd_info(int argc, char **argv) {
struct arg_str *pkg = arg_str1(NULL, NULL, "<package[@version]>", "package to look up");
struct arg_lit *help = arg_lit0("h", "help", "display help");
struct arg_end *end = arg_end(5);
void *argtable[] = { pkg, help, end };
int nerrors = arg_parse(argc, argv, argtable);
int exitcode = EXIT_SUCCESS;
if (help->count > 0) {
printf("Usage: ant info <package[@version]>\n\n");
printf("Show package information from the npm registry.\n");
} else if (nerrors > 0) {
arg_print_errors(stdout, end, "ant info");
exitcode = EXIT_FAILURE;
} else {
exitcode = cmd_info(pkg->sval[0]);
}
arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
return exitcode;
}
typedef struct {
int count;
bool show_path;
const char *nm_path;
} ls_ctx_t;
static void print_ls_package(const char *name, void *user_data) {
ls_ctx_t *ctx = (ls_ctx_t *)user_data;
char pkg_json_path[4096];
snprintf(pkg_json_path, sizeof(pkg_json_path), "%s/%s/package.json", ctx->nm_path, name);
FILE *f = fopen(pkg_json_path, "r");
if (!f) {
printf(" %s%s%s\n", C_BOLD, name, C_RESET);
ctx->count++;
return;
}
char buf[8192];
size_t len = fread(buf, 1, sizeof(buf) - 1, f);
fclose(f);
buf[len] = '\0';
const char *version = "?";
char version_buf[64] = {0};
char *ver_key = strstr(buf, "\"version\"");
if (ver_key) {
char *colon = strchr(ver_key, ':');
if (colon) {
char *quote1 = strchr(colon, '"');
if (quote1) {
char *quote2 = strchr(quote1 + 1, '"');
if (quote2) {
size_t vlen = (size_t)(quote2 - quote1 - 1);
if (vlen < sizeof(version_buf)) {
memcpy(version_buf, quote1 + 1, vlen);
version_buf[vlen] = '\0';
version = version_buf;
}
}
}
}
}
printf(" %s%s%s@%s%s%s\n", C_BOLD, name, C_RESET, C_DIM, version, C_RESET);
ctx->count++;
}
typedef struct {
int count;
int total;
} pkg_ls_ctx_t;
static void print_pkg_cb(const char *name, const char *version, void *user_data) {
pkg_ls_ctx_t *ctx = (pkg_ls_ctx_t *)user_data;
ctx->count++;
const char *prefix = (ctx->count == ctx->total) ? "└──" : "├──";
printf("%s%s%s %s%s%s@%s\n", C_DIM, prefix, C_RESET, C_BOLD, name, C_RESET, version);
}
static int cmd_ls(bool is_global) {
pkg_options_t opts = { .verbose = pkg_verbose };
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
char path_buf[PATH_MAX];
const char *base_path;
const char *nm_path;
char nm_full_path[PATH_MAX];
if (is_global) {
base_path = get_global_dir();
snprintf(nm_full_path, sizeof(nm_full_path), "%s/node_modules", base_path);
nm_path = nm_full_path;
} else {
if (!getcwd(path_buf, sizeof(path_buf))) {
fprintf(stderr, "Error: Could not get current directory\n");
pkg_free(ctx);
return EXIT_FAILURE;
}
base_path = path_buf;
nm_path = "node_modules";
}
uint32_t direct = is_global ? pkg_count_global(ctx) : pkg_count_local(ctx);
uint32_t total = pkg_count_installed(nm_path);
printf("%s%s/node_modules%s", C_DIM, base_path, C_RESET);
if (direct == 0) {
printf("\n (no package.json)\n");
pkg_free(ctx);
return EXIT_SUCCESS;
}
if (total == 0) {
printf("\n (empty)\n");
pkg_free(ctx);
return EXIT_SUCCESS;
}
printf(" %s(%u)%s\n", C_DIM, total, C_RESET);
pkg_ls_ctx_t ls_ctx = { .count = 0, .total = (int)direct };
if (is_global) pkg_list_global(ctx, print_pkg_cb, &ls_ctx);
else pkg_list_local(ctx, print_pkg_cb, &ls_ctx);
pkg_free(ctx);
return EXIT_SUCCESS;
}
int pkg_cmd_ls(int argc, char **argv) {
struct arg_lit *global = arg_lit0("g", "global", "list global packages");
struct arg_lit *help = arg_lit0("h", "help", "display help");
struct arg_end *end = arg_end(5);
void *argtable[] = { global, help, end };
int nerrors = arg_parse(argc, argv, argtable);
int exitcode = EXIT_SUCCESS;
if (help->count > 0) {
printf("Usage: ant ls [-g]\n\n");
printf("List installed packages.\n");
printf("\nOptions:\n -g, --global List global packages\n");
} else if (nerrors > 0) {
arg_print_errors(stdout, end, "ant ls");
exitcode = EXIT_FAILURE;
} else exitcode = cmd_ls(global->count > 0);
arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
return exitcode;
}
static int cmd_cache_info(void) {
pkg_options_t opts = { .verbose = pkg_verbose };
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
pkg_cache_stats_t stats;
pkg_error_t err = pkg_cache_stats(ctx, &stats);
if (err != PKG_OK) {
fprintf(stderr, "Error: Failed to get cache stats\n");
pkg_free(ctx);
return EXIT_FAILURE;
}
char size_buf[64], db_buf[64];
printf("%sCache location:%s ~/.ant/pkg\n", C_BOLD, C_RESET);
printf("%sPackages:%s %u\n", C_BOLD, C_RESET, stats.package_count);
printf("%sSize:%s %s\n", C_BOLD, C_RESET, format_size(stats.total_size, size_buf, sizeof(size_buf)));
printf("%sDB size:%s %s\n", C_BOLD, C_RESET, format_size(stats.db_size, db_buf, sizeof(db_buf)));
pkg_free(ctx);
return EXIT_SUCCESS;
}
static int cmd_cache_prune(uint32_t max_age_days) {
pkg_options_t opts = { .verbose = pkg_verbose };
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
int32_t pruned = pkg_cache_prune(ctx, max_age_days);
if (pruned < 0) {
fprintf(stderr, "Error: Failed to prune cache\n");
pkg_free(ctx);
return EXIT_FAILURE;
}
if (pruned == 0) {
printf("No packages to prune (all packages newer than %u days)\n", max_age_days);
} else {
printf("%sPruned%s %d package%s older than %u days\n",
C_GREEN, C_RESET, pruned, pruned == 1 ? "" : "s", max_age_days);
}
pkg_free(ctx);
return EXIT_SUCCESS;
}
static int cmd_cache_sync(void) {
pkg_options_t opts = { .verbose = pkg_verbose };
pkg_context_t *ctx = pkg_init(&opts);
if (!ctx) {
fprintf(stderr, "Error: Failed to initialize package manager\n");
return EXIT_FAILURE;
}
pkg_cache_sync(ctx);
printf("%sCache synced%s\n", C_GREEN, C_RESET);
pkg_free(ctx);
return EXIT_SUCCESS;
}
int pkg_cmd_cache(int argc, char **argv) {
if (argc < 2) {
printf("Usage: ant cache <command>\n\n");
printf("Manage the package cache.\n\n");
printf("Commands:\n");
printf(" info Show cache statistics\n");
printf(" prune [days] Remove packages older than N days (default: 30)\n");
printf(" sync Sync cache to disk\n");
return EXIT_SUCCESS;
}
const char *subcmd = argv[1];
if (strcmp(subcmd, "info") == 0) {
return cmd_cache_info();
} else if (strcmp(subcmd, "prune") == 0) {
uint32_t days = 30;
if (argc >= 3) {
days = (uint32_t)atoi(argv[2]);
if (days == 0) days = 30;
}
return cmd_cache_prune(days);
} else if (strcmp(subcmd, "sync") == 0) {
return cmd_cache_sync();
} else {
fprintf(stderr, "Unknown cache command: %s\n", subcmd);
fprintf(stderr, "Run 'ant cache' for usage.\n");
return EXIT_FAILURE;
}
}
int pkg_cmd_create(int argc, char **argv) {
if (argc < 2) {
printf("Usage: ant create <template> [dest] [...flags]\n");
printf(" ant create <github-org/repo> [dest] [...flags]\n\n");
printf("Scaffold a new project from a template.\n\n");
printf("Templates:\n");
printf(" NPM: Runs 'ant x create-<template>' with given arguments\n");
printf(" GitHub: Clones repository contents as template\n\n");
printf("Environment variables:\n");
printf(" GITHUB_TOKEN Supply a token for private repos or higher rate limits\n");
return EXIT_SUCCESS;
}
const char *template = argv[1];
bool is_github = (strchr(template, '/') != NULL);
if (is_github) {
const char *dest = NULL;
for (int i = 2; i < argc; i++) {
if (argv[i][0] != '-') { dest = argv[i]; break; }
}
if (!dest) {
const char *slash = strrchr(template, '/');
dest = slash ? slash + 1 : template;
}
struct stat st;
if (stat(dest, &st) == 0) {
fprintf(stderr, "Error: directory '%s' already exists\n", dest);
return EXIT_FAILURE;
}
char url[1024];
if (strncmp(template, "https://", 8) == 0 || strncmp(template, "git@", 4) == 0) {
snprintf(url, sizeof(url), "%s", template);
} else snprintf(url, sizeof(url), "https://github.com/%s.git", template);
printf("%s+%s Creating project from %s%s%s...\n", C_GREEN, C_RESET, C_BOLD, template, C_RESET);
char cmd[2048];
snprintf(cmd, sizeof(cmd), "git clone --depth 1 %s %s", url, dest);
int ret = system(cmd);
if (ret != 0) {
fprintf(stderr, "Error: failed to clone %s\n", url);
return EXIT_FAILURE;
}
char git_dir[1024];
snprintf(git_dir, sizeof(git_dir), "%s/.git", dest);
char rm_cmd[1024];
snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf %s", git_dir);
system(rm_cmd);
if (stat(dest, &st) == 0) {
char pkg_json[1024];
snprintf(pkg_json, sizeof(pkg_json), "%s/package.json", dest);
if (stat(pkg_json, &st) == 0) {
printf("\n%sDone!%s Created %s%s%s\n", C_GREEN, C_RESET, C_BOLD, dest, C_RESET);
printf("\n cd %s\n ant install\n\n", dest);
} else printf("\n%sDone!%s Created %s%s%s\n", C_GREEN, C_RESET, C_BOLD, dest, C_RESET);
}
return EXIT_SUCCESS;
}
char create_pkg[512];
snprintf(create_pkg, sizeof(create_pkg), "create-%s", template);
int new_argc = argc;
char **new_argv = malloc(sizeof(char*) * (new_argc + 1));
if (!new_argv) {
fprintf(stderr, "Error: out of memory\n");
return EXIT_FAILURE;
}
new_argv[0] = argv[0];
new_argv[1] = create_pkg;
for (int i = 2; i < argc; i++) new_argv[i] = argv[i];
new_argv[new_argc] = NULL;
int ret = pkg_cmd_exec(new_argc, new_argv);
free(new_argv);
return ret;
}
diff --git a/src/cli/version.c b/src/cli/version.c
new file mode 100644
index 0000000..02341aa
--- /dev/null
+++ b/src/cli/version.c
@@ -0,0 +1,74 @@
+#include "utils.h"
+#include "config.h"
+
+#include <string.h>
+#include <argtable3.h>
+#include <pthread.h>
+
+static char ant_semver_buf[32];
+static pthread_once_t ant_semver_once = PTHREAD_ONCE_INIT;
+
+static void ant_semver_init(void) {
+ const char *s = ANT_VERSION;
+ int d = 0, i = 0;
+ while (s[i] && d < 3 && i < 31) {
+ if (s[i] == '.') d++;
+ ant_semver_buf[i] = s[i]; i++;
+ }
+ ant_semver_buf[i - (d == 3)] = '\0';
+}
+
+const char *ant_semver(void) {
+ pthread_once(&ant_semver_once, ant_semver_init);
+ return ant_semver_buf;
+}
+
+int ant_version(void *argtable[]) {
+ time_t build_time = (time_t)ANT_BUILD_TIMESTAMP;
+ time_t now = time(NULL);
+ long diff = (long)difftime(now, build_time);
+
+ struct { long secs; const char *suffix; } units[] = {
+ {86400, "d"}, {3600, "h"}, {60, "m"}, {1, "s"}
+ };
+
+ const char *suffix = "s";
+ long value = diff;
+
+ for (size_t i = 0; i < sizeof(units) / sizeof(units[0]); i++) {
+ if (diff >= units[i].secs) {
+ value = diff / units[i].secs;
+ suffix = units[i].suffix; break;
+ }
+ }
+
+ struct tm *tm = gmtime(&build_time);
+ char date_buf[32];
+ strftime(date_buf, sizeof(date_buf), "%Y-%m-%d", tm);
+
+ #define RED "\033[38;5;197m"
+ #define RESET "\033[0m"
+
+ const char *logo =
+ RED
+ " ___ __ __ _____ _ __\n"
+ " / | ____ / /_ / /___ __ ______ _/ ___/__________(_)___ / /_\n"
+ " / /| | / __ \\/ __/ __ / / __ `/ | / / __ `/\\__ \\/ ___/ ___/ / __ \\/ __/\n"
+ " / ___ |/ / / / /_ / /_/ / /_/ /| |/ / /_/ /___/ / /__/ / / / /_/ / /_\n"
+ "/_/ |_/_/ /_/\\__/ \\____/\\__,_/ |___/\\__,_//____/\\___/_/ /_/ .___/\\__/\n"
+ " /_/" RESET " by @themackabu\n"
+ RESET;
+
+ fputs(logo, stdout);
+
+ printf("%s (released %s, %ld%s ago)\n",
+ ANT_VERSION,
+ date_buf,
+ value, suffix
+ );
+
+ printf("built for %s\n", ANT_TARGET_TRIPLE);
+ arg_freetable(argtable, ARGTABLE_COUNT);
+
+ return EXIT_SUCCESS;
+}
\ No newline at end of file
diff --git a/src/main.c b/src/main.c
index 3514fc6..1bd398c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,434 +1,433 @@
#include <compat.h> // IWYU pragma: keep
#include <arena.h>
#include <oxc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <argtable3.h>
#include "ant.h"
#include "config.h"
#include "repl.h"
#include "utils.h"
#include "reactor.h"
#include "runtime.h"
#include "snapshot.h"
#include "esm/remote.h"
#include "internal.h"
#include "cli/pkg.h"
+#include "cli/misc.h"
+#include "cli/version.h"
#include "cli/cprintf.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/shell.h"
#include "modules/process.h"
#include "modules/path.h"
#include "modules/ffi.h"
#include "modules/events.h"
#include "modules/performance.h"
#include "modules/uri.h"
#include "modules/url.h"
#include "modules/reflect.h"
#include "modules/symbol.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"
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},
{"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 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) {
cprintf("<bold>Commands:</>\n");
for (const subcommand_t *cmd = subcommands; cmd->name; cmd++) {
cprintf(" <pad=18>%s</pad> %s\n", cmd->name, cmd->desc);
}
cprintf("\n <pad=18><command> <bold_cyan>--help</></pad> Print help text for command.\n");
printf("\n");
}
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(struct js *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_mkscope(js);
js_set(js, js_glob(js), "__dirname", js_mkstr(js, ".", 1));
js_set(js, js_glob(js), "__filename", js_mkstr(js, tag, strlen(tag)));
jsval_t result = js_eval(js, script, len);
js_run_event_loop(js);
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(struct js *js, const char *filename) {
char *buffer = NULL;
size_t len = 0;
char abs_path[PATH_MAX];
const char *use_path = filename;
if (esm_is_url(filename)) {
char *error = NULL;
buffer = esm_fetch_url(filename, &len, &error);
if (!buffer) {
fprintf(stderr, "Error: Could not fetch '%s': %s\n", 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) {
fprintf(stderr, "Error: Could not open file '%s'\n", 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);
if (realpath(filename, abs_path)) use_path = abs_path;
}
char *js_code = buffer;
size_t js_len = len;
if (is_typescript_file(filename)) {
int result = OXC_strip_types(buffer, filename, buffer, len + 1);
if (result < 0) {
fprintf(stderr, "TypeScript error: strip failed (%d)\n", result);
free(buffer);
return EXIT_FAILURE;
}
js_len = (size_t)result;
}
js_set_filename(js, use_path);
js_set(js, js_glob(js), "__filename", js_mkstr(js, filename, strlen(filename)));
js_setup_import_meta(js, use_path);
js_mkscope(js);
jsval_t result = js_eval_cached(js, js_code, js_len);
free(js_code);
if (vtype(result) == T_ERR) {
fprintf(stderr, "%s\n", js_str(js, result));
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
int filtered_argc = 0;
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) io_no_color = true;
else filtered_argv[filtered_argc++] = argv[i];
}
if (filtered_argc >= 2 && filtered_argv[1][0] != '-') {
const subcommand_t *cmd = find_subcommand(filtered_argv[1]);
if (cmd) {
int exitcode = cmd->fn(filtered_argc - 1, filtered_argv + 1);
free(filtered_argv);
return exitcode;
}
if (pkg_script_exists("package.json", filtered_argv[1])) {
int exitcode = pkg_cmd_run(filtered_argc, filtered_argv);
free(filtered_argv);
return exitcode;
}
}
argc = filtered_argc;
argv = filtered_argv;
- struct arg_lit *help = arg_lit0("h", "help", "display this help and exit");
- struct arg_lit *version = arg_lit0("v", "version", "display version information and exit");
- struct arg_lit *version_raw = arg_lit0(NULL, "version-raw", "raw version number for scripts");
struct arg_str *eval = arg_str0("e", "eval", "<script>", "evaluate script");
struct arg_lit *print = arg_lit0("p", "print", "evaluate script and print result");
- struct arg_int *initial_mem = arg_int0(NULL, "initial-mem", "<size>", "initial memory size in KB (default: 16kb)");
- struct arg_int *max_mem = arg_int0(NULL, "max-mem", "<size>", "maximum memory size in MB (default: 1024mb)");
+
+ struct arg_file *file = arg_file0(NULL, NULL, NULL, NULL);
struct arg_file *localstorage_file = arg_file0(NULL, "localstorage-file", "<path>", "file path for localStorage persistence");
- struct arg_file *file = arg_file0(NULL, NULL, "<module.js>", "JavaScript module file to execute");
+
+ struct arg_lit *version = arg_lit0("v", "version", "display version information and exit");
+ struct arg_lit *version_raw = arg_lit0(NULL, "version-raw", "raw version number for scripts");
+
+ struct arg_lit *help = arg_lit0("h", "help", "display this help and exit");
struct arg_end *end = arg_end(20);
void *argtable[] = {
- help, version, version_raw,
- eval, print, initial_mem, max_mem,
- localstorage_file, file, end
+ eval, print,
+ localstorage_file, file,
+ version, version_raw,
+ help, end
};
int nerrors = arg_parse(argc, argv, argtable);
if (help->count > 0) {
cprintf("<bold_red>Ant</> is a tiny JavaScript runtime and package manager (%s)\n\n", ANT_VERSION);
- cprintf("<bold>Usage: ant <cyan>[...options]</cyan> <yellow>[module.js]\n</>");
- cprintf("<bold> ant <command> <cyan>[...args]</>\n\n");
- print_subcommands();
+ cprintf("<bold>Usage: ant <yellow>[module.js]</yellow> <cyan>[...flags]\n</>");
+ cprintf("<bold><space=7/>ant <command><space=3/><cyan>[...args]</>\n\n");
printf("If no module file is specified, ant starts in REPL mode.\n\n");
- printf("<bold>Options:</>\n");
- printf(" %-28s %s\n", "--verbose", "enable verbose output");
- printf(" %-28s %s\n", "--no-color", "disable colored output");
- arg_print_glossary(stdout, argtable, " %-28s %s\n");
+ print_subcommands();
+ cprintf("<bold>Flags:</>\n");
+ 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" });
arg_freetable(argtable, ARGTABLE_COUNT);
free(filtered_argv);
return EXIT_SUCCESS;
}
if (version_raw->count > 0) {
fputs(ANT_VERSION "\n", stdout);
arg_freetable(argtable, ARGTABLE_COUNT);
free(filtered_argv); return EXIT_SUCCESS;
}
if (version->count > 0) {
int res = ant_version(argtable);
free(filtered_argv); return res;
}
if (nerrors > 0) {
arg_print_errors(stdout, end, "ant");
printf("Try 'ant --help' for more information.\n");
arg_freetable(argtable, ARGTABLE_COUNT);
free(filtered_argv); return EXIT_FAILURE;
}
bool has_stdin = !isatty(STDIN_FILENO);
bool repl_mode = (file->count == 0 && eval->count == 0 && !has_stdin);
const char *module_file = repl_mode
? NULL
: (file->count > 0 ? file->filename[0] : NULL);
- struct js *js = js_create_dynamic(
- initial_mem->count > 0 ? (size_t)initial_mem->ival[0] * 1024 : 0,
- max_mem->count > 0 ? (size_t)max_mem->ival[0] * 1024 * 1024 : 0
- );
+ ant_t *js;
+ volatile char stack_base;
- if (js == NULL) {
- fprintf(stderr, "Error: Failed to allocate JavaScript runtime\n");
- arg_freetable(argtable, ARGTABLE_COUNT);
- free(filtered_argv);
+ if (!(js = js_create_dynamic())) {
+ cfprintf(stderr, "<bold_red>FATAL</>: Failed to allocate for Ant.\n");
+ arg_freetable(argtable, ARGTABLE_COUNT); free(filtered_argv);
return EXIT_FAILURE;
}
-
- volatile char stack_base;
js_setstackbase(js, (void *)&stack_base);
ant_runtime_init(js, argc, argv, localstorage_file);
init_symbol_module();
init_collections_module();
init_builtin_module();
init_buffer_module();
init_fs_module();
init_atomics_module();
init_crypto_module();
init_fetch_module();
init_console_module();
init_json_module();
init_server_module();
init_timer_module();
init_process_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_standard_library("path", path_library);
ant_standard_library("fs", fs_library);
ant_standard_library("os", os_library);
ant_standard_library("crypto", crypto_library);
ant_standard_library("events", events_library);
ant_standard_library("readline", readline_library);
ant_standard_library("readline/promises", readline_promises_library);
ant_standard_library("child_process", child_process_library);
jsval_t snapshot_result = ant_load_snapshot(js);
if (vtype(snapshot_result) == T_ERR) {
fprintf(stderr, "Warning: Failed to load snapshot: %s\n", 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 (has_stdin && file->count == 0) {
size_t len = 0; char *buf = read_stdin(&len);
if (!buf) {
fprintf(stderr, "Error: Out of memory\n");
js_result = EXIT_FAILURE; goto cleanup;
}
eval_code(js, buf, len, "[stdin]", print->count > 0); free(buf);
}
else {
struct stat path_stat;
char *resolved_file = NULL;
resolved_file = resolve_js_file(module_file);
if (resolved_file) module_file = resolved_file;
if (stat(module_file, &path_stat) == 0 && S_ISDIR(path_stat.st_mode)) {
size_t len = strlen(module_file);
int has_slash = (len > 0 && module_file[len - 1] == '/');
if (resolved_file) free(resolved_file);
resolved_file = try_oom(len + 10 + (has_slash ? 0 : 1));
sprintf(resolved_file, "%s%sindex.js", module_file, has_slash ? "" : "/");
module_file = resolved_file;
}
js_result = execute_module(js, module_file);
js_run_event_loop(js);
if (resolved_file) free(resolved_file);
}
cleanup: {
js_destroy(js);
arg_freetable(argtable, ARGTABLE_COUNT);
free(filtered_argv);
}
return js_result;
}
diff --git a/src/utils.c b/src/utils.c
index 85f7690..6b1f2bb 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -1,184 +1,112 @@
#include "utils.h"
-#include "config.h"
-#include <string.h>
-#include <stdint.h>
+#include <stdio.h>
#include <stdbool.h>
-#include <pthread.h>
-#include <argtable3.h>
#include <sys/stat.h>
-static char ant_semver_buf[32];
-static pthread_once_t ant_semver_once = PTHREAD_ONCE_INIT;
-
static const char *const js_extensions[] = {
".js", ".ts",
".cjs", ".mjs",
".jsx", ".tsx", NULL
};
-static void ant_semver_init(void) {
- const char *s = ANT_VERSION;
- int d = 0, i = 0;
- while (s[i] && d < 3 && i < 31) {
- if (s[i] == '.') d++;
- ant_semver_buf[i] = s[i]; i++;
- }
- ant_semver_buf[i - (d == 3)] = '\0';
-}
-
-const char *ant_semver(void) {
- pthread_once(&ant_semver_once, ant_semver_init);
- return ant_semver_buf;
-}
-
int hex_digit(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return -1;
}
uint64_t hash_key(const char *key, size_t len) {
uint64_t hash = 14695981039346656037ULL;
size_t i = 0;
for (; i + 8 <= len; i += 8) {
uint64_t word;
memcpy(&word, key + i, 8);
hash ^= word;
hash *= 1099511628211ULL;
}
for (; i < len; i++) {
hash ^= (uint8_t)key[i];
hash *= 1099511628211ULL;
}
return hash;
}
int is_typescript_file(const char *filename) {
if (filename == NULL) return 0;
size_t len = strlen(filename);
if (len < 3) return 0;
const char *ext = filename + len;
while (ext > filename && *(ext - 1) != '.' && *(ext - 1) != '/') ext--;
if (ext == filename || *(ext - 1) != '.') return 0;
ext--;
return (strcmp(ext, ".ts") == 0 || strcmp(ext, ".mts") == 0 || strcmp(ext, ".cts") == 0);
}
static bool has_js_extension(const char *filename) {
const char *dot = strrchr(filename, '.');
if (!dot) return false;
for (const char *const *ext = js_extensions; *ext; ext++) {
if (strcmp(dot, *ext) == 0) return true;
}
return false;
}
char *resolve_js_file(const char *filename) {
extern bool esm_is_url(const char *path);
if (esm_is_url(filename)) return strdup(filename);
struct stat st;
if (stat(filename, &st) == 0 && S_ISREG(st.st_mode)) {
return strdup(filename);
}
if (has_js_extension(filename)) return NULL;
size_t base_len = strlen(filename);
for (const char *const *ext = js_extensions; *ext; ext++) {
size_t ext_len = strlen(*ext);
char *test_path = try_oom(base_len + ext_len + 1);
memcpy(test_path, filename, base_len);
memcpy(test_path + base_len, *ext, ext_len + 1);
if (stat(test_path, &st) == 0 && S_ISREG(st.st_mode)) {
return test_path;
} free(test_path);
}
return NULL;
}
-int ant_version(void *argtable[]) {
- time_t build_time = (time_t)ANT_BUILD_TIMESTAMP;
- time_t now = time(NULL);
- long diff = (long)difftime(now, build_time);
-
- struct { long secs; const char *suffix; } units[] = {
- {86400, "d"}, {3600, "h"}, {60, "m"}, {1, "s"}
- };
-
- const char *suffix = "s";
- long value = diff;
-
- for (size_t i = 0; i < sizeof(units) / sizeof(units[0]); i++) {
- if (diff >= units[i].secs) {
- value = diff / units[i].secs;
- suffix = units[i].suffix; break;
- }
- }
-
- struct tm *tm = gmtime(&build_time);
- char date_buf[32];
- strftime(date_buf, sizeof(date_buf), "%Y-%m-%d", tm);
-
- #define RED "\033[38;5;197m"
- #define RESET "\033[0m"
-
- const char *logo =
- RED
- " ___ __ __ _____ _ __\n"
- " / | ____ / /_ / /___ __ ______ _/ ___/__________(_)___ / /_\n"
- " / /| | / __ \\/ __/ __ / / __ `/ | / / __ `/\\__ \\/ ___/ ___/ / __ \\/ __/\n"
- " / ___ |/ / / / /_ / /_/ / /_/ /| |/ / /_/ /___/ / /__/ / / / /_/ / /_\n"
- "/_/ |_/_/ /_/\\__/ \\____/\\__,_/ |___/\\__,_//____/\\___/_/ /_/ .___/\\__/\n"
- " /_/" RESET " by @themackabu\n"
- RESET;
-
- fputs(logo, stdout);
-
- printf("%s (released %s, %ld%s ago)\n",
- ANT_VERSION,
- date_buf,
- value, suffix
- );
-
- printf("built for %s\n", ANT_TARGET_TRIPLE);
- arg_freetable(argtable, ARGTABLE_COUNT);
-
- return EXIT_SUCCESS;
-}
-
void *try_oom(size_t size) {
void *p = malloc(size);
if (!p) {
fputs("Error: out of memory\n", stderr);
exit(EXIT_FAILURE);
} return p;
}
void cstr_free(cstr_buf_t *buf) {
if (buf->heap) free(buf->heap);
}
char *cstr_init(cstr_buf_t *buf, char *stack, size_t stack_size, const char *src, size_t len) {
if (len < stack_size) {
buf->ptr = stack;
buf->heap = NULL;
} else {
buf->heap = malloc(len + 1);
if (!buf->heap) return NULL;
buf->ptr = buf->heap;
}
memcpy(buf->ptr, src, len);
buf->ptr[len] = '\0';
return buf->ptr;
}
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jun 17, 11:59 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
575251
Default Alt Text
(930 KB)

Event Timeline