diff --git a/.gitignore b/.gitignore index f186cff..c9f6e45 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ /javascript-zoo /game-of-life +node_modules +!/tests/node_modules + /wpt /tools /zoo.sh @@ -33,7 +36,6 @@ *.todo *.trace -node_modules bun.lock pnpm-lock.yaml package-lock.json diff --git a/examples/spec/console.js b/examples/spec/console.js index e0e33dd..e0a3bd1 100644 --- a/examples/spec/console.js +++ b/examples/spec/console.js @@ -1,4 +1,5 @@ -import { test, summary } from './helpers.js'; +import consoleModule, { Console } from 'node:console'; +import { test, testDeep, summary } from './helpers.js'; console.log('Console Tests\n'); @@ -10,7 +11,84 @@ test('console.debug exists', typeof console.debug, 'function'); test('console.trace exists', typeof console.trace, 'function'); test('console.time exists', typeof console.time, 'function'); test('console.timeEnd exists', typeof console.timeEnd, 'function'); +test('console.timeLog exists', typeof console.timeLog, 'function'); test('console.assert exists', typeof console.assert, 'function'); test('console.clear exists', typeof console.clear, 'function'); +test('console.count exists', typeof console.count, 'function'); +test('console.countReset exists', typeof console.countReset, 'function'); +test('console.group exists', typeof console.group, 'function'); +test('console.groupCollapsed exists', typeof console.groupCollapsed, 'function'); +test('console.groupEnd exists', typeof console.groupEnd, 'function'); +test('console.dir exists', typeof console.dir, 'function'); +test('console.dirxml exists', typeof console.dirxml, 'function'); +test('console.table exists', typeof console.table, 'function'); +test('console.Console exists', typeof console.Console, 'function'); + +test('node:console default export is object', typeof consoleModule, 'object'); +test('node:console Console export exists', typeof Console, 'function'); +test('node:console default Console matches named export', consoleModule.Console, Console); + +function makeSink() { + return { + chunks: [], + write(chunk) { + this.chunks.push(String(chunk)); + return true; + } + }; +} + +const stdoutSink = makeSink(); +const stderrSink = makeSink(); +const instance = new Console({ stdout: stdoutSink, stderr: stderrSink, groupIndentation: 2 }); + +test('Console instance log exists', typeof instance.log, 'function'); +test('Console instance error exists', typeof instance.error, 'function'); +test('Console instance warn exists', typeof instance.warn, 'function'); +test('Console instance info exists', typeof instance.info, 'function'); +test('Console instance debug exists', typeof instance.debug, 'function'); +test('Console instance assert exists', typeof instance.assert, 'function'); +test('Console instance trace exists', typeof instance.trace, 'function'); +test('Console instance count exists', typeof instance.count, 'function'); +test('Console instance countReset exists', typeof instance.countReset, 'function'); +test('Console instance time exists', typeof instance.time, 'function'); +test('Console instance timeLog exists', typeof instance.timeLog, 'function'); +test('Console instance timeEnd exists', typeof instance.timeEnd, 'function'); +test('Console instance group exists', typeof instance.group, 'function'); +test('Console instance groupCollapsed exists', typeof instance.groupCollapsed, 'function'); +test('Console instance groupEnd exists', typeof instance.groupEnd, 'function'); +test('Console instance clear exists', typeof instance.clear, 'function'); +test('Console instance dir exists', typeof instance.dir, 'function'); +test('Console instance dirxml exists', typeof instance.dirxml, 'function'); +test('Console instance table exists', typeof instance.table, 'function'); + +instance.log('hello', 'world'); +instance.info('info'); +instance.group('parent'); +instance.log('child'); +instance.groupEnd(); +instance.count('hits'); +instance.count('hits'); +instance.countReset('hits'); +instance.count('hits'); +instance.error('problem'); +instance.warn('warning'); +instance.assert(false, 'boom'); + +testDeep('Console instance stdout routing', stdoutSink.chunks, [ + 'hello world\n', + 'info\n', + 'parent\n', + ' child\n', + 'hits: 1\n', + 'hits: 2\n', + 'hits: 1\n' +]); + +testDeep('Console instance stderr routing', stderrSink.chunks, [ + 'problem\n', + 'warning\n', + 'Assertion failed: boom\n' +]); summary(); diff --git a/examples/spec/events.js b/examples/spec/events.js index 66af0c8..0e0f4d0 100644 --- a/examples/spec/events.js +++ b/examples/spec/events.js @@ -281,6 +281,16 @@ const disposable = addAbortListener(addAbortController.signal, () => { addAbortController.abort(); test('events.addAbortListener fires on abort', addAbortValue, 1); test('events.addAbortListener returns disposable', typeof disposable.dispose, 'function'); + +let disposedAbortValue = 0; +const disposedAbortController = new AbortController(); +const disposedAbort = addAbortListener(disposedAbortController.signal, () => { + disposedAbortValue++; +}); +disposedAbort.dispose(); +disposedAbortController.abort(); +test('events.addAbortListener dispose removes listener', disposedAbortValue, 0); + test('events.getMaxListeners default', getMaxListeners(eeNodeOnce), 10); test('events.setMaxListeners no-op return', setMaxListeners(20, eeNodeOnce), undefined); diff --git a/examples/spec/objects.js b/examples/spec/objects.js index 671e8da..1bf8bdb 100644 --- a/examples/spec/objects.js +++ b/examples/spec/objects.js @@ -1,4 +1,4 @@ -import { test, testDeep, summary } from './helpers.js'; +import { test, testDeep, testThrows, summary } from './helpers.js'; console.log('Object Tests\n'); @@ -116,5 +116,8 @@ test('descriptors symbol value', descriptors[descriptorSymbol].value, 'symbol va const arrayDescriptors = Object.getOwnPropertyDescriptors(['item']); test('array descriptor index value', arrayDescriptors[0].value, 'item'); test('array descriptor length value', arrayDescriptors.length.value, 1); +testThrows('Object.getOwnPropertyDescriptors throws without argument', () => Object.getOwnPropertyDescriptors()); +testThrows('Object.getOwnPropertyDescriptors throws on null', () => Object.getOwnPropertyDescriptors(null)); +test('Object.getOwnPropertyDescriptors boxes number primitive', typeof Object.getOwnPropertyDescriptors(1), 'object'); summary(); diff --git a/include/common.h b/include/common.h index cc8e1b3..f805ef1 100644 --- a/include/common.h +++ b/include/common.h @@ -27,9 +27,16 @@ X(SLOT_BUILTIN) \ X(SLOT_BRAND) \ X(SLOT_DATA) \ + X(SLOT_EVENT_MAX_LISTENERS) \ X(SLOT_CTOR) \ X(SLOT_FS_FLAGS) \ X(SLOT_DEFAULT) \ + X(SLOT_CONSOLE_STDOUT) \ + X(SLOT_CONSOLE_STDERR) \ + X(SLOT_CONSOLE_COUNTS) \ + X(SLOT_CONSOLE_TIMERS) \ + X(SLOT_CONSOLE_GROUP_INDENT) \ + X(SLOT_CONSOLE_GROUP_LEVEL) \ X(SLOT_ERROR_BRAND) \ X(SLOT_ERR_TYPE) \ X(SLOT_OBSERVABLE_SUBSCRIBER) \ diff --git a/include/debug.h b/include/debug.h index 9b3bd83..3d845a3 100644 --- a/include/debug.h +++ b/include/debug.h @@ -7,6 +7,8 @@ typedef enum { SV_DEBUG_DUMP_BYTECODE = 1u << 0, SV_DEBUG_DUMP_JIT = 1u << 1, SV_DEBUG_JIT_WARN = 1u << 2, + SV_DEBUG_PARSE = 1u << 3, + SV_DEBUG_COMPILE = 1u << 4, } sv_debug_flag_t; bool sv_debug_enabled(sv_debug_flag_t flag); @@ -18,5 +20,7 @@ void sv_debug_set(sv_debug_flag_t flag, bool enabled); #define sv_dump_bytecode_unlikely sv_debug_unlikely(SV_DEBUG_DUMP_BYTECODE) #define sv_dump_jit_unlikely sv_debug_unlikely(SV_DEBUG_DUMP_JIT) #define sv_jit_warn_unlikely sv_debug_unlikely(SV_DEBUG_JIT_WARN) +#define sv_parse_trace_unlikely sv_debug_unlikely(SV_DEBUG_PARSE) +#define sv_compile_trace_unlikely sv_debug_unlikely(SV_DEBUG_COMPILE) #endif diff --git a/include/esm/loader.h b/include/esm/loader.h index 751df15..45b24ff 100644 --- a/include/esm/loader.h +++ b/include/esm/loader.h @@ -22,18 +22,19 @@ typedef struct ant_module_t { void js_esm_cleanup_module_cache(void); -ant_value_t js_esm_import_sync(ant_t *js, ant_value_t specifier); ant_value_t js_esm_make_file_url(ant_t *js, const char *path); -ant_value_t js_esm_import_sync_from(ant_t *js, ant_value_t specifier, const char *base_path); - -ant_value_t js_esm_eval_module_source( - ant_t *js, const char *resolved_path, - const char *js_code, size_t js_len, ant_value_t ns -); +ant_value_t js_esm_import_sync(ant_t *js, ant_value_t specifier); ant_value_t js_esm_import_sync_cstr(ant_t *js, const char *specifier, size_t spec_len); +ant_value_t js_esm_import_sync_from(ant_t *js, ant_value_t specifier, const char *base_path); ant_value_t js_esm_resolve_specifier(ant_t *js, ant_value_t specifier, const char *base_path); + +ant_value_t js_esm_import_sync_from_require(ant_t *js, ant_value_t specifier, const char *base_path); +ant_value_t js_esm_resolve_specifier_require(ant_t *js, ant_value_t specifier, const char *base_path); + ant_value_t js_esm_import_sync_cstr_from(ant_t *js, const char *specifier, size_t spec_len, const char *base_path); ant_value_t js_esm_import_dynamic(ant_t *js, ant_value_t specifier, const char *base_path, ant_value_t *out_tla_promise); +ant_value_t js_esm_import_sync_cstr_from_require(ant_t *js, const char *specifier, size_t spec_len, const char *base_path); +ant_value_t js_esm_eval_module_source(ant_t *js, const char *resolved_path, const char *js_code, size_t js_len, ant_value_t ns); #endif diff --git a/include/internal.h b/include/internal.h index cf5890c..809aeb2 100644 --- a/include/internal.h +++ b/include/internal.h @@ -329,6 +329,7 @@ static inline bool is_callable(ant_value_t v) { bool is_internal_prop(const char *key, ant_offset_t klen); size_t uint_to_str(char *buf, size_t bufsize, uint64_t val); +int extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_args); ant_value_t tov(double d); double tod(ant_value_t v); @@ -387,6 +388,7 @@ ant_value_t js_delete_sym_prop(ant_t *js, ant_value_t obj, ant_value_t sym); bool is_proxy(ant_value_t obj); bool strict_eq_values(ant_t *js, ant_value_t l, ant_value_t r); +bool js_deep_equal(ant_t *js, ant_value_t a, ant_value_t b, bool strict); ant_value_t js_proxy_apply(ant_t *js, ant_value_t proxy, ant_value_t this_arg, ant_value_t *args, int argc); ant_value_t js_proxy_construct(ant_t *js, ant_value_t proxy, ant_value_t *args, int argc, ant_value_t new_target); diff --git a/include/modules/events.h b/include/modules/events.h index 81fc565..dea2dc6 100644 --- a/include/modules/events.h +++ b/include/modules/events.h @@ -4,6 +4,8 @@ #include #include "types.h" +#define EVENTS_DEFAULT_MAX_LISTENERS 10 + ant_value_t events_library(ant_t *js); ant_value_t eventemitter_prototype(ant_t *js); diff --git a/include/modules/io.h b/include/modules/io.h index 44807e5..d6169f6 100644 --- a/include/modules/io.h +++ b/include/modules/io.h @@ -47,9 +47,16 @@ void inspect_value( inspect_visited_t *visited ); -ant_value_t console_print( - ant_t *js, ant_value_t *args, - int nargs, const char *color, FILE *stream +ant_value_t console_emit( + ant_t *js, + bool use_stderr, const char *prefix, + ant_value_t *args, int nargs +); + +ant_value_t console_emit_current( + ant_t *js, + bool use_stderr, const char *prefix, + ant_value_t *args, int nargs ); #endif diff --git a/include/modules/stream.h b/include/modules/stream.h index 56e53b4..41226b5 100644 --- a/include/modules/stream.h +++ b/include/modules/stream.h @@ -7,6 +7,7 @@ void stream_init_constructors(ant_t *js); ant_value_t stream_library(ant_t *js); ant_value_t stream_promises_library(ant_t *js); +ant_value_t stream_web_library(ant_t *js); ant_value_t stream_readable_constructor(ant_t *js); ant_value_t stream_writable_constructor(ant_t *js); diff --git a/include/silver/compile_ctx.h b/include/silver/compile_ctx.h new file mode 100644 index 0000000..d51c538 --- /dev/null +++ b/include/silver/compile_ctx.h @@ -0,0 +1,42 @@ +#ifndef SILVER_COMPILE_CTX_H +#define SILVER_COMPILE_CTX_H + +#include +#include "silver/compiler.h" + +void sv_compile_ctx_init_root( + sv_compiler_t *ctx, + ant_t *js, + const char *filename, + const char *source, + ant_offset_t source_len, + sv_compile_mode_t mode, + bool is_strict, + sv_line_table_t *line_table +); + +void sv_compile_ctx_init_child( + sv_compiler_t *ctx, + sv_compiler_t *enclosing, + sv_ast_t *node, + sv_compile_mode_t mode +); + +void sv_compile_ctx_line_table_lookup( + sv_line_table_t *lt, + uint32_t off, + uint32_t *out_line, + uint32_t *out_col +); + +uint32_t sv_compile_ctx_hash_local_name(const char *name, uint32_t len); +sv_line_table_t *sv_compile_ctx_build_line_table(const char *source, ant_offset_t source_len); + +void sv_compile_ctx_cleanup(sv_compiler_t *ctx); +void sv_compile_ctx_free_line_table(sv_line_table_t *lt); + +void sv_compile_ctx_ensure_local_lookup_capacity(sv_compiler_t *ctx, int next_count); +void sv_compile_ctx_local_lookup_insert(sv_compiler_t *ctx, int idx); +void sv_compile_ctx_local_lookup_remove(sv_compiler_t *ctx, int idx); + +#endif diff --git a/include/silver/compiler.h b/include/silver/compiler.h index 19ca216..9e3bbd4 100644 --- a/include/silver/compiler.h +++ b/include/silver/compiler.h @@ -3,6 +3,7 @@ #include "silver/ast.h" #include "silver/vm.h" +#include "silver/engine.h" typedef enum { SV_COMPILE_SCRIPT = 0, @@ -11,11 +12,135 @@ typedef enum { SV_COMPILE_REPL = 3, } sv_compile_mode_t; -typedef struct { +typedef struct { const char *name; size_t len; } sv_param_t; +typedef struct { + const char *name; + uint32_t name_len; + uint32_t name_hash; + int lookup_next; + int depth; + bool is_const; + bool captured; + bool is_tdz; + uint8_t inferred_type; +} sv_local_t; + +typedef struct { + int *offsets; + int count; + int cap; +} sv_patch_list_t; + +typedef struct { + const char *name; + uint32_t len; +} sv_deferred_export_t; + +typedef struct { + int loop_start; + sv_patch_list_t breaks; + sv_patch_list_t continues; + int scope_depth; + const char *label; + uint32_t label_len; + bool is_switch; +} sv_loop_t; + +typedef struct const_dedup_entry { + const char *str; + size_t len; + int index; + UT_hash_handle hh; +} const_dedup_entry_t; + +typedef struct sv_line_table { + uint32_t *offsets; + int count; +} sv_line_table_t; + +typedef struct sv_compiler { + ant_t *js; + const char *filename; + const char *source; + ant_offset_t source_len; + + uint8_t *code; + int code_len; + int code_cap; + + ant_value_t *constants; + int const_count; + int const_cap; + + sv_atom_t *atoms; + int atom_count; + int atom_cap; + int ic_count; + + sv_local_t *locals; + int local_count; + int local_cap; + int *local_lookup_heads; + int local_lookup_cap; + int max_local_count; + int scope_depth; + int param_locals; + + sv_upval_desc_t *upval_descs; + int upvalue_count; + int upvalue_cap; + + sv_loop_t *loops; + int loop_count; + int loop_cap; + + struct sv_compiler *enclosing; + sv_ast_t **field_inits; + + int field_init_count; + int *computed_key_locals; + int param_count; + + bool is_arrow; + bool is_async; + bool is_strict; + sv_compile_mode_t mode; + + bool is_tla; + int try_depth; + int with_depth; + int strict_args_local; + int new_target_local; + int super_local; + + const char *pending_label; + uint32_t pending_label_len; + + const char *inferred_name; + uint32_t inferred_name_len; + + sv_srcpos_t *srcpos; + int srcpos_count; + int srcpos_cap; + uint32_t last_srcpos_off; + uint32_t last_srcpos_end; + + sv_type_info_t *slot_types; + int slot_type_cap; + + sv_deferred_export_t *deferred_exports; + int deferred_export_count; + int deferred_export_cap; + + const_dedup_entry_t *const_dedup; + sv_line_table_t *line_table; +} sv_compiler_t; + + #define SV_PARAM(name_literal) \ ((sv_param_t){ (name_literal), sizeof(name_literal) - 1 }) @@ -36,4 +161,50 @@ sv_func_t *sv_compile_function_with_params( size_t body_len, bool is_async ); +sv_func_t *compile_function_body( + sv_compiler_t *enclosing, + sv_ast_t *node, + sv_compile_mode_t mode +); + +void compile_array(sv_compiler_t *c, sv_ast_t *node); +void compile_array_destructure(sv_compiler_t *c, sv_ast_t *pat, bool keep); +void compile_assign(sv_compiler_t *c, sv_ast_t *node); +void compile_binary(sv_compiler_t *c, sv_ast_t *node); +void compile_break(sv_compiler_t *c, sv_ast_t *node); +void compile_call(sv_compiler_t *c, sv_ast_t *node); +void compile_class(sv_compiler_t *c, sv_ast_t *node); +void compile_continue(sv_compiler_t *c, sv_ast_t *node); +void compile_delete(sv_compiler_t *c, sv_ast_t *node); +void compile_destructure_binding(sv_compiler_t *c, sv_ast_t *pat, sv_var_kind_t kind); +void compile_do_while(sv_compiler_t *c, sv_ast_t *node); +void compile_export_decl(sv_compiler_t *c, sv_ast_t *node); +void compile_expr(sv_compiler_t *c, sv_ast_t *node); +void compile_for_in(sv_compiler_t *c, sv_ast_t *node); +void compile_for_of(sv_compiler_t *c, sv_ast_t *node); +void compile_for(sv_compiler_t *c, sv_ast_t *node); +void compile_func_expr(sv_compiler_t *c, sv_ast_t *node); +void compile_if(sv_compiler_t *c, sv_ast_t *node); +void compile_import_decl(sv_compiler_t *c, sv_ast_t *node); +void compile_label(sv_compiler_t *c, sv_ast_t *node); +void compile_lhs_set(sv_compiler_t *c, sv_ast_t *target, bool keep); +void compile_member(sv_compiler_t *c, sv_ast_t *node); +void compile_new(sv_compiler_t *c, sv_ast_t *node); +void compile_object_destructure(sv_compiler_t *c, sv_ast_t *pat, bool keep); +void compile_object(sv_compiler_t *c, sv_ast_t *node); +void compile_optional_get(sv_compiler_t *c, sv_ast_t *node); +void compile_optional(sv_compiler_t *c, sv_ast_t *node); +void compile_stmt(sv_compiler_t *c, sv_ast_t *node); +void compile_stmts(sv_compiler_t *c, sv_ast_list_t *list); +void compile_switch(sv_compiler_t *c, sv_ast_t *node); +void compile_tail_return_expr(sv_compiler_t *c, sv_ast_t *expr); +void compile_template(sv_compiler_t *c, sv_ast_t *node); +void compile_ternary(sv_compiler_t *c, sv_ast_t *node); +void compile_try(sv_compiler_t *c, sv_ast_t *node); +void compile_typeof(sv_compiler_t *c, sv_ast_t *node); +void compile_unary(sv_compiler_t *c, sv_ast_t *node); +void compile_update(sv_compiler_t *c, sv_ast_t *node); +void compile_var_decl(sv_compiler_t *c, sv_ast_t *node); +void compile_while(sv_compiler_t *c, sv_ast_t *node); + #endif diff --git a/src/ant.c b/src/ant.c index ea08ab4..51d697a 100644 --- a/src/ant.c +++ b/src/ant.c @@ -4787,7 +4787,7 @@ static ant_value_t builtin_function_call(ant_t *js, ant_value_t *args, int nargs return sv_vm_call_explicit_this(js->vm, js, func, this_arg, call_args, call_nargs); } -static int extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_args) { +int extract_array_args(ant_t *js, ant_value_t arr, ant_value_t **out_args) { int len = (int) get_array_length(js, arr); if (len <= 0) return 0; @@ -5686,6 +5686,153 @@ static ant_value_t builtin_proto_setter(ant_t *js, ant_value_t *args, int nargs) return js_mkundef(); } +static ant_value_t legacy_accessor_this_obj(ant_t *js, ant_value_t this_val) { + uint8_t t = vtype(this_val); + if (t == T_UNDEF || t == T_NULL) { + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert undefined or null to object"); + } + if (t == T_CFUNC) return js_cfunc_promote(js, this_val); + if (t == T_OBJ || t == T_ARR || t == T_FUNC) return js_as_obj(this_val); + return js_mkerr_typed(js, JS_ERR_TYPE, "Legacy accessor methods require an object receiver"); +} + +static bool legacy_accessor_key( + ant_t *js, ant_value_t key, + ant_value_t *key_val_out, + const char **key_str_out, + ant_offset_t *key_len_out, + ant_offset_t *sym_off_out +) { + if (key_val_out) *key_val_out = key; + if (key_str_out) *key_str_out = NULL; + if (key_len_out) *key_len_out = 0; + if (sym_off_out) *sym_off_out = 0; + + if (vtype(key) == T_SYMBOL) { + if (sym_off_out) *sym_off_out = (ant_offset_t)vdata(key); + return true; + } + + ant_value_t key_val = (vtype(key) == T_STR) ? key : coerce_to_str(js, key); + if (is_err(key_val)) return false; + if (key_val_out) *key_val_out = key_val; + + ant_offset_t key_len = 0; + ant_offset_t key_off = vstr(js, key_val, &key_len); + + if (key_str_out) *key_str_out = (const char *)(uintptr_t)key_off; + if (key_len_out) *key_len_out = key_len; + + return true; +} + +static ant_value_t builtin_object___defineGetter__(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t obj = legacy_accessor_this_obj(js, js->this_val); + if (is_err(obj)) return obj; + if (nargs < 2) return js_mkundef(); + + ant_value_t getter = args[1]; + uint8_t gt = vtype(getter); + + if (gt != T_FUNC && gt != T_CFUNC) { + return js_mkerr_typed(js, JS_ERR_TYPE, "Object.prototype.__defineGetter__: Expecting function"); + } + + ant_value_t key_val = js_mkundef(); + const char *key_str = NULL; + ant_offset_t key_len = 0; + ant_offset_t sym_off = 0; + + if (!legacy_accessor_key(js, args[0], &key_val, &key_str, &key_len, &sym_off)) { + return key_val; + } + + if (vtype(key_val) == T_SYMBOL) { + js_set_sym_getter_desc(js, js_as_obj(obj), key_val, getter, JS_DESC_E | JS_DESC_C); + } else js_set_getter_desc(js, js_as_obj(obj), key_str, (size_t)key_len, getter, JS_DESC_E | JS_DESC_C); + + return js_mkundef(); +} + +static ant_value_t builtin_object___defineSetter__(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t obj = legacy_accessor_this_obj(js, js->this_val); + if (is_err(obj)) return obj; + if (nargs < 2) return js_mkundef(); + + ant_value_t setter = args[1]; + uint8_t st = vtype(setter); + + if (st != T_FUNC && st != T_CFUNC) { + return js_mkerr_typed(js, JS_ERR_TYPE, "Object.prototype.__defineSetter__: Expecting function"); + } + + ant_value_t key_val = js_mkundef(); + const char *key_str = NULL; + ant_offset_t key_len = 0; + ant_offset_t sym_off = 0; + + if (!legacy_accessor_key(js, args[0], &key_val, &key_str, &key_len, &sym_off)) { + return key_val; + } + + if (vtype(key_val) == T_SYMBOL) { + js_set_sym_setter_desc(js, js_as_obj(obj), key_val, setter, JS_DESC_E | JS_DESC_C); + } else js_set_setter_desc(js, js_as_obj(obj), key_str, (size_t)key_len, setter, JS_DESC_E | JS_DESC_C); + + return js_mkundef(); +} + +static ant_value_t legacy_lookup_accessor(ant_t *js, ant_value_t this_val, ant_value_t key, bool want_getter) { + ant_value_t obj = legacy_accessor_this_obj(js, this_val); + if (is_err(obj)) return obj; + + ant_value_t key_val = js_mkundef(); + const char *key_str = NULL; + ant_offset_t key_len = 0; + ant_offset_t sym_off = 0; + + if (!legacy_accessor_key(js, key, &key_val, &key_str, &key_len, &sym_off)) { + return key_val; + } + + for (ant_value_t cur = obj; is_object_type(cur); cur = get_proto(js, cur)) { + prop_meta_t meta; + bool has_meta = (vtype(key_val) == T_SYMBOL) + ? lookup_symbol_prop_meta(cur, sym_off, &meta) + : lookup_string_prop_meta(js, cur, key_str, (size_t)key_len, &meta); + if (has_meta) { + if (want_getter) return meta.has_getter ? meta.getter : js_mkundef(); + return meta.has_setter ? meta.setter : js_mkundef(); + } + + if (vtype(key_val) == T_SYMBOL) { + if (lkp_sym(js, cur, sym_off) != 0) return js_mkundef(); + } else { + if (array_obj_ptr(cur) && is_length_key(key_str, key_len)) return js_mkundef(); + if (array_obj_ptr(cur) && is_array_index(key_str, key_len)) { + unsigned long idx = 0; + if ( + parse_array_index(key_str, key_len, get_array_length(js, cur), &idx) && + arr_has(js, cur, (ant_offset_t)idx) + ) return js_mkundef(); + } + if (lkp(js, cur, key_str, key_len) != 0) return js_mkundef(); + } + } + + return js_mkundef(); +} + +static ant_value_t builtin_object___lookupGetter__(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkundef(); + return legacy_lookup_accessor(js, js->this_val, args[0], true); +} + +static ant_value_t builtin_object___lookupSetter__(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkundef(); + return legacy_lookup_accessor(js, js->this_val, args[0], false); +} + static ant_value_t builtin_object_create(ant_t *js, ant_value_t *args, int nargs) { if (nargs == 0) return js_mkerr(js, "Object.create requires a prototype argument"); @@ -6526,19 +6673,29 @@ static ant_value_t object_add_descriptors_for_keys( static ant_value_t builtin_object_getOwnPropertyDescriptors(ant_t *js, ant_value_t *args, int nargs) { ant_value_t result = js_mkobj(js); - if (nargs == 0) return result; + if (nargs == 0 || vtype(args[0]) == T_NULL || vtype(args[0]) == T_UNDEF) { + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert undefined or null to object"); + } ant_value_t obj = args[0]; uint8_t t = vtype(obj); + + if (t != T_OBJ && t != T_ARR && t != T_FUNC) { + obj = builtin_Object(js, &obj, 1); + if (is_err(obj)) return obj; + t = vtype(obj); + } + if (t != T_OBJ && t != T_ARR && t != T_FUNC) return result; - ant_value_t names = builtin_object_getOwnPropertyNames(js, &obj, 1); if (is_err(names)) return names; + ant_value_t err = object_add_descriptors_for_keys(js, result, obj, names); if (is_err(err)) return err; - + ant_value_t symbols = builtin_object_getOwnPropertySymbols(js, &obj, 1); if (is_err(symbols)) return symbols; + err = object_add_descriptors_for_keys(js, result, obj, symbols); if (is_err(err)) return err; @@ -6939,37 +7096,49 @@ static ant_value_t builtin_array_pop(ant_t *js, ant_value_t *args, int nargs) { static ant_value_t builtin_array_slice(ant_t *js, ant_value_t *args, int nargs) { ant_value_t arr = js->this_val; - if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) { + ant_value_t string_val = unwrap_primitive(js, arr); + bool string_like = (vtype(string_val) == T_STR); + + if (!string_like && vtype(arr) != T_ARR && vtype(arr) != T_OBJ) { return js_mkerr(js, "slice called on non-array"); } - ant_offset_t len = get_array_length(js, arr); + ant_offset_t len = 0; + ant_offset_t string_byte_len = 0; + ant_offset_t string_off = 0; + + if (string_like) { + string_off = vstr(js, string_val, &string_byte_len); + len = (ant_offset_t)utf16_strlen((const char *)(uintptr_t)string_off, string_byte_len); + } else len = get_array_length(js, arr); ant_offset_t start = 0, end = len; double dlen = D(len); if (nargs >= 1 && vtype(args[0]) == T_NUM) { double d = tod(args[0]); - if (d < 0) { - start = (ant_offset_t) (d + dlen < 0 ? 0 : d + dlen); - } else start = (ant_offset_t) (d > dlen ? dlen : d); + if (d < 0) start = (ant_offset_t) (d + dlen < 0 ? 0 : d + dlen); + else start = (ant_offset_t) (d > dlen ? dlen : d); } if (nargs >= 2 && vtype(args[1]) == T_NUM) { double d = tod(args[1]); - if (d < 0) { - end = (ant_offset_t) (d + dlen < 0 ? 0 : d + dlen); - } else { - end = (ant_offset_t) (d > dlen ? dlen : d); - } + if (d < 0) end = (ant_offset_t) (d + dlen < 0 ? 0 : d + dlen); + else end = (ant_offset_t) (d > dlen ? dlen : d); } if (start > end) start = end; ant_value_t result = array_alloc_like(js, arr); + if (is_err(result)) return result; ant_offset_t result_idx = 0; for (ant_offset_t i = start; i < end; i++) { - ant_value_t elem = arr_get(js, arr, i); + ant_value_t elem = js_mkundef(); + if (string_like) { + uint32_t code_unit = utf16_code_unit_at((const char *)(uintptr_t)string_off, string_byte_len, i); + if (code_unit == 0xFFFFFFFF) break; + elem = js_string_from_utf16_code_unit(js, code_unit); + } else elem = arr_get(js, arr, i); arr_set(js, result, result_idx, elem); result_idx++; } @@ -12569,6 +12738,10 @@ ant_t *js_create(void *buf, size_t len) { defmethod(js, object_proto, "hasOwnProperty", 14, js_mkfun(builtin_object_hasOwnProperty)); defmethod(js, object_proto, "isPrototypeOf", 13, js_mkfun(builtin_object_isPrototypeOf)); defmethod(js, object_proto, "propertyIsEnumerable", 20, js_mkfun(builtin_object_propertyIsEnumerable)); + defmethod(js, object_proto, "__defineGetter__", 16, js_mkfun(builtin_object___defineGetter__)); + defmethod(js, object_proto, "__defineSetter__", 16, js_mkfun(builtin_object___defineSetter__)); + defmethod(js, object_proto, "__lookupGetter__", 16, js_mkfun(builtin_object___lookupGetter__)); + defmethod(js, object_proto, "__lookupSetter__", 16, js_mkfun(builtin_object___lookupSetter__)); ant_value_t proto_getter = js_mkfun(builtin_proto_getter); ant_value_t proto_setter = js_mkfun(builtin_proto_setter); diff --git a/src/builtins/node/http.mjs b/src/builtins/node/http.mjs index c71debb..2f194e6 100644 --- a/src/builtins/node/http.mjs +++ b/src/builtins/node/http.mjs @@ -110,6 +110,76 @@ function clientNotImplemented() { throw new Error('node:http client transport is not implemented yet'); } +// compatibility stub only +function createAgentState() { + return Object.create(null); +} + +export class OutgoingMessage extends EventEmitter {} + +export class ClientRequest extends OutgoingMessage { + constructor() { + super(); + clientNotImplemented(); + } +} + +export class Agent extends EventEmitter { + constructor(options = {}) { + super(); + this.options = options && typeof options === 'object' ? options : {}; + this.requests = createAgentState(); + this.sockets = createAgentState(); + this.freeSockets = createAgentState(); + this.keepAlive = !!this.options.keepAlive; + this.keepAliveMsecs = this.options.keepAliveMsecs ?? 1000; + this.maxSockets = this.options.maxSockets ?? Infinity; + this.maxFreeSockets = this.options.maxFreeSockets ?? 256; + this.maxTotalSockets = this.options.maxTotalSockets ?? Infinity; + this.totalSocketCount = 0; + this.defaultPort = 80; + this.protocol = 'http:'; + this.scheduling = this.options.scheduling ?? 'lifo'; + } + + createConnection() { + clientNotImplemented(); + } + + createSocket(options, callback) { + const error = new Error('node:http Agent client transport is not implemented yet'); + if (typeof callback === 'function') callback(error); + else throw error; + } + + addRequest() { + clientNotImplemented(); + } + + removeSocket() {} + + keepSocketAlive() { + return false; + } + + reuseSocket() {} + + destroy() { + return this; + } + + getName(options = {}) { + if (options.socketPath) return String(options.socketPath); + const host = options.host ?? 'localhost'; + const port = options.port ?? this.defaultPort; + const localAddress = options.localAddress ? `:${options.localAddress}` : ''; + const family = options.family ? `:${options.family}` : ''; + return `${host}:${port}${localAddress}${family}`; + } +} + +export const globalAgent = new Agent(); + export class IncomingMessage extends EventEmitter { constructor(socket, parsed) { super(); diff --git a/src/builtins/node/http2.mjs b/src/builtins/node/http2.mjs index 32ea74c..f279005 100644 --- a/src/builtins/node/http2.mjs +++ b/src/builtins/node/http2.mjs @@ -9,6 +9,15 @@ function clientNotImplemented() { export class Http2Server extends http.Server {} export class Http2SecureServer extends https.Server {} +export class Http2ServerRequest { + constructor() { + this.stream = undefined; + this.authority = undefined; + } +} + +export class Http2ServerResponse {} + export class ClientHttp2Session {} export class ClientHttp2Stream {} export class ServerHttp2Session {} @@ -38,13 +47,21 @@ export function getUnpackedSettings() { return {}; } -export const constants = Object.freeze({}); +export const constants = Object.freeze({ + NGHTTP2_NO_ERROR: 0, + HTTP_STATUS_OK: 200, + HTTP_STATUS_NO_CONTENT: 204, + HTTP_STATUS_NOT_MODIFIED: 304 +}); + export const sensitiveHeaders = Object.freeze([]); export default { ClientHttp2Session, ClientHttp2Stream, Http2Server, + Http2ServerRequest, + Http2ServerResponse, Http2SecureServer, ServerHttp2Session, ServerHttp2Stream, diff --git a/src/builtins/node/https.mjs b/src/builtins/node/https.mjs index e2e5f36..f23a8b4 100644 --- a/src/builtins/node/https.mjs +++ b/src/builtins/node/https.mjs @@ -105,6 +105,18 @@ export class Server extends http.Server { } } +export class Agent extends http.Agent { + constructor(options) { + super(options); + this.defaultPort = 443; + this.protocol = 'https:'; + this.maxCachedSessions = this.options.maxCachedSessions ?? 100; + this._sessionCache = { map: Object.create(null), list: [] }; + } +} + +export const globalAgent = new Agent(); + export function createSecureContext(options) { return tls.createContext(options); } diff --git a/src/core/features.ts b/src/core/features.ts index b7319bb..52787a2 100644 --- a/src/core/features.ts +++ b/src/core/features.ts @@ -4,12 +4,27 @@ process.features = { typescript: 'transform' }; +// TODO: make most of these dynamic // polyfill process.versions to satisfy modules that rely on node -// runtime detection. values mirror node 25.2.0 defaults +// runtime detection. values mirror node 25.9.0 defaults +process.version = 'v25.9.0'; + process.versions = { - node: '25.2.0', ant: import.meta.env.VERSION, - v8: '14.1.146.11-node.14', + node: '25.9.0', + brotli: '1.1.0', + llhttp: '9.3.1', + nghttp2: '1.68.0', + simdjson: '0.12.0', + pcre2: '10.47', + libffi: '3.5.2', + lmdb: '0.9.33', + utf8proc: '2.10.0', + zlib: '2.3.3', + v8: '14.1.146.11-node.25', uv: '1.52.0', - modules: '141' + modules: '141', + napi: '10', + wamr: '92f40918bbfad35546a1512b10bd25eaa31add4d', + boringssl: '297b11798a0ed6bc7736aa57328909a4afbbf67a' }; diff --git a/src/esm/commonjs.c b/src/esm/commonjs.c index 0c6e498..26d9404 100644 --- a/src/esm/commonjs.c +++ b/src/esm/commonjs.c @@ -26,7 +26,7 @@ static ant_value_t esm_cjs_require(ant_t *js, ant_value_t *args, int nargs) { base_path = (const char *)(uintptr_t)(path_off); } - ant_value_t ns = js_esm_import_sync_from(js, args[0], base_path); + ant_value_t ns = js_esm_import_sync_from_require(js, args[0], base_path); if (is_err(ns)) return ns; if (vtype(ns) == T_OBJ) { @@ -52,7 +52,7 @@ static ant_value_t esm_cjs_require_resolve(ant_t *js, ant_value_t *args, int nar base_path = (const char *)(uintptr_t)(data_off); } - ant_value_t resolved = js_esm_resolve_specifier(js, args[0], base_path); + ant_value_t resolved = js_esm_resolve_specifier_require(js, args[0], base_path); if (is_err(resolved)) return resolved; if (vtype(resolved) != T_STR) return resolved; diff --git a/src/esm/loader.c b/src/esm/loader.c index 05d3dcc..b25fd42 100644 --- a/src/esm/loader.c +++ b/src/esm/loader.c @@ -388,7 +388,8 @@ static char *esm_resolve_exports_target( const char *capture, size_t capture_len, const char *base_path, - bool allow_bare_specifiers + bool allow_bare_specifiers, + bool prefer_require ) { if (!target) return NULL; @@ -417,7 +418,7 @@ static char *esm_resolve_exports_target( yyjson_arr_foreach(target, idx, max, item) { char *resolved = esm_resolve_exports_target( item, package_dir, capture, - capture_len, base_path, allow_bare_specifiers + capture_len, base_path, allow_bare_specifiers, prefer_require ); if (resolved) return resolved; } @@ -425,14 +426,23 @@ static char *esm_resolve_exports_target( } if (yyjson_is_obj(target)) { - static const char *const conditions[] = {"import", "node", "default"}; - - for (size_t i = 0; i < sizeof(conditions) / sizeof(conditions[0]); i++) { + static const char *const import_conditions[] = {"import", "node", "default"}; + static const char *const require_conditions[] = {"require", "node", "default"}; + + const char *const *conditions = prefer_require + ? require_conditions + : import_conditions; + + size_t condition_count = prefer_require + ? sizeof(require_conditions) / sizeof(require_conditions[0]) + : sizeof(import_conditions) / sizeof(import_conditions[0]); + + for (size_t i = 0; i < condition_count; i++) { yyjson_val *cond_target = yyjson_obj_get(target, conditions[i]); if (!cond_target) continue; char *resolved = esm_resolve_exports_target( cond_target, package_dir, capture, - capture_len, base_path, allow_bare_specifiers + capture_len, base_path, allow_bare_specifiers, prefer_require ); if (resolved) return resolved; } @@ -446,14 +456,15 @@ static char *esm_resolve_package_map( const char *request_key, const char *package_dir, const char *base_path, - bool allow_bare_specifiers + bool allow_bare_specifiers, + bool prefer_require ) { if (!map_obj || !yyjson_is_obj(map_obj)) return NULL; yyjson_val *exact = yyjson_obj_get(map_obj, request_key); if (exact) return esm_resolve_exports_target( exact, package_dir, "", 0, - base_path, allow_bare_specifiers + base_path, allow_bare_specifiers, prefer_require ); const char *best_capture = NULL; @@ -487,7 +498,7 @@ static char *esm_resolve_package_map( if (!best_target) return NULL; return esm_resolve_exports_target( best_target, package_dir, best_capture, - best_capture_len, base_path, allow_bare_specifiers + best_capture_len, base_path, allow_bare_specifiers, prefer_require ); } @@ -505,7 +516,7 @@ static char *esm_resolve_package_main_entry(yyjson_val *root, const char *packag return resolved; } -static char *esm_resolve_package_entrypoint(const char *package_dir, const char *subpath, const char *base_path) { +static char *esm_resolve_package_entrypoint(const char *package_dir, const char *subpath, const char *base_path, bool prefer_require) { char pkg_json_path[PATH_MAX]; snprintf(pkg_json_path, sizeof(pkg_json_path), "%s/package.json", package_dir); @@ -528,9 +539,9 @@ static char *esm_resolve_package_entrypoint(const char *package_dir, const char const char *key = yyjson_get_str(k); if (key && key[0] == '.') { has_subpath_keys = true; break; } } - if (has_subpath_keys) resolved = esm_resolve_package_map(exports, subpath_key, package_dir, base_path, false); - else if (!subpath || !subpath[0]) resolved = esm_resolve_exports_target(exports, package_dir, "", 0, base_path, false); - } else if (!subpath || !subpath[0]) resolved = esm_resolve_exports_target(exports, package_dir, "", 0, base_path, false); + if (has_subpath_keys) resolved = esm_resolve_package_map(exports, subpath_key, package_dir, base_path, false, prefer_require); + else if (!subpath || !subpath[0]) resolved = esm_resolve_exports_target(exports, package_dir, "", 0, base_path, false, prefer_require); + } else if (!subpath || !subpath[0]) resolved = esm_resolve_exports_target(exports, package_dir, "", 0, base_path, false, prefer_require); if (doc) yyjson_doc_free(doc); return resolved; @@ -552,7 +563,7 @@ static char *esm_resolve_package_entrypoint(const char *package_dir, const char return resolved; } -static char *esm_resolve_package_imports(const char *specifier, const char *base_path) { +static char *esm_resolve_package_imports(const char *specifier, const char *base_path, bool prefer_require) { char *start_dir = esm_get_base_dir(base_path); if (!start_dir) return NULL; @@ -570,7 +581,10 @@ static char *esm_resolve_package_imports(const char *specifier, const char *base yyjson_val *imports = (root && yyjson_is_obj(root)) ? yyjson_obj_get(root, "imports") : NULL; char *resolved = NULL; if (imports && yyjson_is_obj(imports)) { - resolved = esm_resolve_package_map(imports, specifier, current, base_path, true); + resolved = esm_resolve_package_map( + imports, specifier, current, + base_path, true, prefer_require + ); } yyjson_doc_free(doc); return resolved; @@ -586,7 +600,7 @@ static char *esm_resolve_package_imports(const char *specifier, const char *base return NULL; } -static char *esm_resolve_node_module(const char *specifier, const char *base_path) { +static char *esm_resolve_node_module_cond(const char *specifier, const char *base_path, bool prefer_require) { char package_name[PATH_MAX]; const char *subpath = NULL; if (!esm_split_package_specifier(specifier, package_name, sizeof(package_name), &subpath)) { @@ -600,11 +614,15 @@ static char *esm_resolve_node_module(const char *specifier, const char *base_pat free(start_dir); if (!package_dir) return NULL; - char *resolved = esm_resolve_package_entrypoint(package_dir, subpath, base_path); + char *resolved = esm_resolve_package_entrypoint(package_dir, subpath, base_path, prefer_require); free(package_dir); return resolved; } +static char *esm_resolve_node_module(const char *specifier, const char *base_path) { + return esm_resolve_node_module_cond(specifier, base_path, false); +} + static char *esm_resolve_relative_path(const char *specifier, const char *base_path) { char *base_copy = strdup(base_path); if (!base_copy) return NULL; @@ -640,7 +658,7 @@ static char *esm_resolve_relative_path(const char *specifier, const char *base_p } } -static char *esm_resolve_path(const char *specifier, const char *base_path) { +static char *esm_resolve_path_cond(const char *specifier, const char *base_path, bool prefer_require) { if (!specifier || !specifier[0]) return NULL; if (specifier[0] == '/') { @@ -652,10 +670,18 @@ static char *esm_resolve_path(const char *specifier, const char *base_path) { } if (specifier[0] == '#') { - return esm_resolve_package_imports(specifier, base_path); + return esm_resolve_package_imports(specifier, base_path, prefer_require); } - return esm_resolve_node_module(specifier, base_path); + return esm_resolve_node_module_cond(specifier, base_path, prefer_require); +} + +static char *esm_resolve_path(const char *specifier, const char *base_path) { + return esm_resolve_path_cond(specifier, base_path, false); +} + +static char *esm_resolve_path_require(const char *specifier, const char *base_path) { + return esm_resolve_path_cond(specifier, base_path, true); } static bool esm_has_suffix(const char *path, const char *ext) { @@ -1262,6 +1288,75 @@ ant_value_t js_esm_import_sync_cstr_from( return ns; } +ant_value_t js_esm_import_sync_cstr_from_require( + ant_t *js, + const char *specifier, + size_t spec_len, + const char *base_path +) { + const ant_builtin_bundle_alias_t *bundle = NULL; + const ant_builtin_bundle_module_t *module = NULL; + + char *spec_copy = strndup(specifier, spec_len); + if (!spec_copy) return js_mkerr(js, "oom"); + + char *file_url_path = esm_file_url_to_path(js, spec_copy); + if (file_url_path) { + free(spec_copy); + spec_copy = file_url_path; + spec_len = strlen(spec_copy); + } + + bundle = esm_lookup_builtin_alias(spec_copy, spec_len); + if (bundle) { + module = esm_lookup_builtin_module(bundle->module_id); + if (!module) { + free(spec_copy); + return js_mkerr(js, "Invalid builtin module id"); + } + + ant_value_t ns = esm_get_or_load( + js, spec_copy, + bundle->source_name, + bundle->source_name, + module->format, + module->code, + module->code_len + ); + + free(spec_copy); + return ns; + } + + bool loaded = false; + ant_value_t lib = js_esm_load_registered_library(js, spec_copy, spec_len, &loaded); + if (loaded) { + free(spec_copy); + return lib; + } + + if (!base_path || !base_path[0]) base_path = esm_default_base_path(js); + char *resolved_path = esm_resolve(spec_copy, base_path, esm_resolve_path_require); + if (!resolved_path) { + ant_value_t err = js_mkerr(js, "Cannot resolve module: %s", spec_copy); + free(spec_copy); + return err; + } + + ant_value_t ns = esm_get_or_load( + js, spec_copy, + resolved_path, + resolved_path, + MODULE_EVAL_FORMAT_UNKNOWN, + NULL, 0 + ); + + free(resolved_path); + free(spec_copy); + + return ns; +} + ant_value_t js_esm_import_sync_cstr(ant_t *js, const char *specifier, size_t spec_len) { return js_esm_import_sync_cstr_from(js, specifier, spec_len, NULL); } @@ -1277,6 +1372,17 @@ ant_value_t js_esm_import_sync_from(ant_t *js, ant_value_t specifier, const char return js_esm_import_sync_cstr_from(js, spec_str, (size_t)spec_len, base_path); } +ant_value_t js_esm_import_sync_from_require(ant_t *js, ant_value_t specifier, const char *base_path) { + if (vtype(specifier) != T_STR) + return js_mkerr(js, "require() expects a string specifier"); + + ant_offset_t spec_len = 0; + ant_offset_t spec_off = vstr(js, specifier, &spec_len); + const char *spec_str = (const char *)(uintptr_t)(spec_off); + + return js_esm_import_sync_cstr_from_require(js, spec_str, (size_t)spec_len, base_path); +} + ant_value_t js_esm_import_sync(ant_t *js, ant_value_t specifier) { return js_esm_import_sync_from(js, specifier, NULL); } @@ -1360,3 +1466,42 @@ ant_value_t js_esm_resolve_specifier(ant_t *js, ant_value_t specifier, const cha free(resolved_path); return result; } + +ant_value_t js_esm_resolve_specifier_require(ant_t *js, ant_value_t specifier, const char *base_path) { + const ant_builtin_bundle_alias_t *bundle = NULL; + if (vtype(specifier) != T_STR) { + return js_mkerr(js, "require.resolve() expects a string specifier"); + } + + ant_offset_t spec_len = 0; + ant_offset_t spec_off = vstr(js, specifier, &spec_len); + const char *spec_str = (const char *)(uintptr_t)(spec_off); + char *spec_copy = strndup(spec_str, (size_t)spec_len); + if (!spec_copy) return js_mkerr(js, "oom"); + + bundle = esm_lookup_builtin_alias(spec_copy, (size_t)spec_len); + if (bundle) { + ant_value_t result = js_mkstr(js, bundle->source_name, strlen(bundle->source_name)); + free(spec_copy); + return result; + } + + if (!base_path || !base_path[0]) base_path = esm_default_base_path(js); + char *resolved_path = esm_resolve(spec_copy, base_path, esm_resolve_path_require); + free(spec_copy); + + if (!resolved_path) { + return js_mkerr(js, "Cannot resolve module"); + } + + if (esm_is_url(resolved_path)) { + ant_value_t result = js_mkstr(js, resolved_path, strlen(resolved_path)); + free(resolved_path); + return result; + } + + ant_value_t result = js_esm_make_file_url(js, resolved_path); + free(resolved_path); + + return result; +} diff --git a/src/main.c b/src/main.c index c675b53..43c8601 100644 --- a/src/main.c +++ b/src/main.c @@ -128,12 +128,20 @@ static void ant_debug_apply(const char *key, const char *val) { if (strcmp(key, "gc") == 0) { if (strcmp(val, "disable") == 0) gc_disabled = true; } - - if (strcmp(key, "dump/crprintf") == 0) { + + else if (strcmp(key, "dump/parse") == 0) { + if (strcmp(val, "trace") == 0) sv_debug_enable(SV_DEBUG_PARSE); + } + + else if (strcmp(key, "dump/compile") == 0) { + if (strcmp(val, "trace") == 0) sv_debug_enable(SV_DEBUG_COMPILE); + } + + else if (strcmp(key, "dump/crprintf") == 0) { if (strcmp(val, "bytecode") == 0 || strcmp(val, "all") == 0) crprintf_set_debug(true); if (strcmp(val, "hex") == 0 || strcmp(val, "all") == 0) crprintf_set_debug_hex(true); - } - + } + else if (strcmp(key, "dump/vm") == 0) { if (strcmp(val, "bytecode") == 0 || strcmp(val, "all") == 0) sv_debug_enable(SV_DEBUG_DUMP_BYTECODE); if (strcmp(val, "jit") == 0 || strcmp(val, "all") == 0) sv_debug_enable(SV_DEBUG_DUMP_JIT); @@ -668,6 +676,7 @@ int main(int argc, char *argv[]) { ant_standard_library("timers/promises", timers_promises_library); ant_standard_library("readline/promises", readline_promises_library); ant_standard_library("stream/promises", stream_promises_library); + ant_standard_library("stream/web", stream_web_library); ant_value_t snapshot_result = ant_load_snapshot(js); if (vtype(snapshot_result) == T_ERR) { diff --git a/src/modules/assert.c b/src/modules/assert.c index 8dcfae6..e87248c 100644 --- a/src/modules/assert.c +++ b/src/modules/assert.c @@ -42,7 +42,6 @@ static ant_value_t assert_if_error(ant_t *js, ant_value_t *args, int nargs) { return js_mkerr(js, "ifError got unwanted exception: %s", msg ? msg : "(unknown)"); } -// TODO: make into global helper static bool values_strict_equal(ant_t *js, ant_value_t a, ant_value_t b) { uint8_t ta = vtype(a), tb = vtype(b); if (ta != tb) return false; @@ -76,8 +75,7 @@ static bool values_loose_equal(ant_t *js, ant_value_t a, ant_value_t b) { return false; } -// TODO: make into global helper -static bool deep_equal(ant_t *js, ant_value_t a, ant_value_t b, bool strict, int depth) { +static bool deep_equal_impl(ant_t *js, ant_value_t a, ant_value_t b, bool strict, int depth) { if (depth > 64) return false; uint8_t ta = vtype(a), tb = vtype(b); @@ -85,7 +83,7 @@ static bool deep_equal(ant_t *js, ant_value_t a, ant_value_t b, bool strict, int ant_offset_t la = js_arr_len(js, a), lb = js_arr_len(js, b); if (la != lb) return false; for (ant_offset_t i = 0; i < la; i++) { - if (!deep_equal(js, js_arr_get(js, a, i), js_arr_get(js, b, i), strict, depth + 1)) + if (!deep_equal_impl(js, js_arr_get(js, a, i), js_arr_get(js, b, i), strict, depth + 1)) return false; } return true; @@ -97,7 +95,7 @@ static bool deep_equal(ant_t *js, ant_value_t a, ant_value_t b, bool strict, int const char *key; size_t key_len; ant_value_t va; while (js_prop_iter_next(&iter, &key, &key_len, &va)) { ant_value_t vb = js_get(js, b, key); - if (!deep_equal(js, va, vb, strict, depth + 1)) { + if (!deep_equal_impl(js, va, vb, strict, depth + 1)) { js_prop_iter_end(&iter); return false; } @@ -118,6 +116,10 @@ static bool deep_equal(ant_t *js, ant_value_t a, ant_value_t b, bool strict, int return strict ? values_strict_equal(js, a, b) : values_loose_equal(js, a, b); } +bool js_deep_equal(ant_t *js, ant_value_t a, ant_value_t b, bool strict) { + return deep_equal_impl(js, a, b, strict, 0); +} + static ant_value_t assert_equal(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkundef(); if (!values_loose_equal(js, args[0], args[1])) @@ -148,28 +150,28 @@ static ant_value_t assert_not_strict_equal(ant_t *js, ant_value_t *args, int nar static ant_value_t assert_deep_equal(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkundef(); - if (!deep_equal(js, args[0], args[1], false, 0)) + if (!js_deep_equal(js, args[0], args[1], false)) return assertion_error(js, "Expected values to be deeply equal", nargs >= 3 ? args[2] : js_mkundef()); return js_mkundef(); } static ant_value_t assert_not_deep_equal(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkundef(); - if (deep_equal(js, args[0], args[1], false, 0)) + if (js_deep_equal(js, args[0], args[1], false)) return assertion_error(js, "Expected values to not be deeply equal", nargs >= 3 ? args[2] : js_mkundef()); return js_mkundef(); } static ant_value_t assert_deep_strict_equal(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkundef(); - if (!deep_equal(js, args[0], args[1], true, 0)) + if (!js_deep_equal(js, args[0], args[1], true)) return assertion_error(js, "Expected values to be deeply strictly equal", nargs >= 3 ? args[2] : js_mkundef()); return js_mkundef(); } static ant_value_t assert_not_deep_strict_equal(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkundef(); - if (deep_equal(js, args[0], args[1], true, 0)) + if (js_deep_equal(js, args[0], args[1], true)) return assertion_error(js, "Expected values to not be deeply strictly equal", nargs >= 3 ? args[2] : js_mkundef()); return js_mkundef(); } diff --git a/src/modules/buffer.c b/src/modules/buffer.c index 231a105..33b61da 100644 --- a/src/modules/buffer.c +++ b/src/modules/buffer.c @@ -2207,6 +2207,130 @@ static ant_value_t js_buffer_write(ant_t *js, ant_value_t *args, int nargs) { return js_mknum((double)to_write); } +static ant_value_t js_buffer_copy(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "copy requires a target buffer"); + + TypedArrayData *src = buffer_get_typedarray_data(js_getthis(js)); + TypedArrayData *dst = buffer_get_typedarray_data(args[0]); + if (!src || !dst) return js_mkerr(js, "copy requires Buffer arguments"); + + size_t target_start = (nargs > 1 && vtype(args[1]) == T_NUM) ? (size_t)js_getnum(args[1]) : 0; + size_t source_start = (nargs > 2 && vtype(args[2]) == T_NUM) ? (size_t)js_getnum(args[2]) : 0; + size_t source_end = (nargs > 3 && vtype(args[3]) == T_NUM) ? (size_t)js_getnum(args[3]) : src->byte_length; + + if (target_start > dst->byte_length) target_start = dst->byte_length; + if (source_start > src->byte_length) source_start = src->byte_length; + if (source_end > src->byte_length) source_end = src->byte_length; + if (source_end < source_start) source_end = source_start; + + size_t src_len = source_end - source_start; + size_t dst_len = dst->byte_length - target_start; + size_t copy_len = src_len < dst_len ? src_len : dst_len; + if (copy_len == 0) return js_mknum(0); + + uint8_t *src_ptr = src->buffer->data + src->byte_offset + source_start; + uint8_t *dst_ptr = dst->buffer->data + dst->byte_offset + target_start; + memmove(dst_ptr, src_ptr, copy_len); + + return js_mknum((double)copy_len); +} + +static ant_value_t js_buffer_writeInt16BE(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "writeInt16BE requires a value"); + + TypedArrayData *ta = buffer_get_typedarray_data(js_getthis(js)); + if (!ta) return js_mkerr(js, "Invalid Buffer"); + + int16_t value = (int16_t)js_to_int32(js_getnum(args[0])); + size_t offset = (nargs > 1 && vtype(args[1]) == T_NUM) ? (size_t)js_getnum(args[1]) : 0; + if (offset + 2 > ta->byte_length) return js_mkerr(js, "Offset out of bounds"); + + uint8_t *ptr = ta->buffer->data + ta->byte_offset + offset; + ptr[0] = (uint8_t)((value >> 8) & 0xff); + ptr[1] = (uint8_t)(value & 0xff); + + return js_mknum((double)(offset + 2)); +} + +static ant_value_t js_buffer_writeInt32BE(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "writeInt32BE requires a value"); + + TypedArrayData *ta = buffer_get_typedarray_data(js_getthis(js)); + if (!ta) return js_mkerr(js, "Invalid Buffer"); + + int32_t value = js_to_int32(js_getnum(args[0])); + size_t offset = (nargs > 1 && vtype(args[1]) == T_NUM) ? (size_t)js_getnum(args[1]) : 0; + if (offset + 4 > ta->byte_length) return js_mkerr(js, "Offset out of bounds"); + + uint8_t *ptr = ta->buffer->data + ta->byte_offset + offset; + ptr[0] = (uint8_t)((value >> 24) & 0xff); + ptr[1] = (uint8_t)((value >> 16) & 0xff); + ptr[2] = (uint8_t)((value >> 8) & 0xff); + ptr[3] = (uint8_t)(value & 0xff); + + return js_mknum((double)(offset + 4)); +} + +static ant_value_t js_buffer_writeUInt32BE(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "writeUInt32BE requires a value"); + + TypedArrayData *ta = buffer_get_typedarray_data(js_getthis(js)); + if (!ta) return js_mkerr(js, "Invalid Buffer"); + + uint32_t value = js_to_uint32(js_getnum(args[0])); + size_t offset = (nargs > 1 && vtype(args[1]) == T_NUM) ? (size_t)js_getnum(args[1]) : 0; + if (offset + 4 > ta->byte_length) return js_mkerr(js, "Offset out of bounds"); + + uint8_t *ptr = ta->buffer->data + ta->byte_offset + offset; + ptr[0] = (uint8_t)((value >> 24) & 0xff); + ptr[1] = (uint8_t)((value >> 16) & 0xff); + ptr[2] = (uint8_t)((value >> 8) & 0xff); + ptr[3] = (uint8_t)(value & 0xff); + + return js_mknum((double)(offset + 4)); +} + +static ant_value_t js_buffer_readInt16BE(ant_t *js, ant_value_t *args, int nargs) { + TypedArrayData *ta = buffer_get_typedarray_data(js_getthis(js)); + if (!ta) return js_mkerr(js, "Invalid Buffer"); + + size_t offset = (nargs > 0 && vtype(args[0]) == T_NUM) ? (size_t)js_getnum(args[0]) : 0; + if (offset + 2 > ta->byte_length) return js_mkerr(js, "Offset out of bounds"); + + uint8_t *ptr = ta->buffer->data + ta->byte_offset + offset; + int16_t value = (int16_t)((ptr[0] << 8) | ptr[1]); + + return js_mknum((double)value); +} + +static ant_value_t js_buffer_readInt32BE(ant_t *js, ant_value_t *args, int nargs) { + TypedArrayData *ta = buffer_get_typedarray_data(js_getthis(js)); + if (!ta) return js_mkerr(js, "Invalid Buffer"); + + size_t offset = (nargs > 0 && vtype(args[0]) == T_NUM) ? (size_t)js_getnum(args[0]) : 0; + if (offset + 4 > ta->byte_length) return js_mkerr(js, "Offset out of bounds"); + + uint8_t *ptr = ta->buffer->data + ta->byte_offset + offset; + int32_t value = (int32_t)(((uint32_t)ptr[0] << 24) | ((uint32_t)ptr[1] << 16) | + ((uint32_t)ptr[2] << 8) | (uint32_t)ptr[3]); + + return js_mknum((double)value); +} + +static ant_value_t js_buffer_readUInt32BE(ant_t *js, ant_value_t *args, int nargs) { + TypedArrayData *ta = buffer_get_typedarray_data(js_getthis(js)); + if (!ta) return js_mkerr(js, "Invalid Buffer"); + + size_t offset = (nargs > 0 && vtype(args[0]) == T_NUM) ? (size_t)js_getnum(args[0]) : 0; + if (offset + 4 > ta->byte_length) return js_mkerr(js, "Offset out of bounds"); + + uint8_t *ptr = ta->buffer->data + ta->byte_offset + offset; + uint32_t value = ((uint32_t)ptr[0] << 24) | ((uint32_t)ptr[1] << 16) | + ((uint32_t)ptr[2] << 8) | (uint32_t)ptr[3]; + + return js_mknum((double)value); +} + // Buffer.isBuffer(obj) static ant_value_t js_buffer_isBuffer(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_false; @@ -2539,6 +2663,13 @@ void init_buffer_module() { js_set(js, buffer_proto, "toString", js_mkfun(js_buffer_toString)); js_set(js, buffer_proto, "toBase64", js_mkfun(js_buffer_toBase64)); js_set(js, buffer_proto, "write", js_mkfun(js_buffer_write)); + js_set(js, buffer_proto, "copy", js_mkfun(js_buffer_copy)); + js_set(js, buffer_proto, "writeInt16BE", js_mkfun(js_buffer_writeInt16BE)); + js_set(js, buffer_proto, "writeInt32BE", js_mkfun(js_buffer_writeInt32BE)); + js_set(js, buffer_proto, "writeUInt32BE", js_mkfun(js_buffer_writeUInt32BE)); + js_set(js, buffer_proto, "readInt16BE", js_mkfun(js_buffer_readInt16BE)); + js_set(js, buffer_proto, "readInt32BE", js_mkfun(js_buffer_readInt32BE)); + js_set(js, buffer_proto, "readUInt32BE", js_mkfun(js_buffer_readUInt32BE)); js_set_sym(js, buffer_proto, get_toStringTag_sym(), js_mkstr(js, "Buffer", 6)); js_set_sym(js, buffer_proto, get_iterator_sym(), ta_values_fn); diff --git a/src/modules/crypto.c b/src/modules/crypto.c index 9d2a4c7..86cc878 100644 --- a/src/modules/crypto.c +++ b/src/modules/crypto.c @@ -409,6 +409,84 @@ static ant_value_t js_crypto_random_fill_sync(ant_t *js, ant_value_t *args, int return args[0]; } +// TODO: extend subtle +static ant_value_t crypto_subtle_get_algorithm_name(ant_t *js, ant_value_t algorithm) { + if (vtype(algorithm) == T_STR) return js_tostring_val(js, algorithm); + if (is_object_type(algorithm)) { + ant_value_t name = js_get(js, algorithm, "name"); + if (is_err(name)) return name; + return js_tostring_val(js, name); + } + return js_mkerr_typed(js, JS_ERR_TYPE, "Algorithm must be a string or object with a name"); +} + +static ant_value_t crypto_subtle_digest_impl( + ant_t *js, ant_value_t algorithm, ant_value_t data +) { + ant_value_t algo_val = crypto_subtle_get_algorithm_name(js, algorithm); + if (is_err(algo_val)) return algo_val; + + size_t algo_len = 0; + const char *algo = js_getstr(js, algo_val, &algo_len); + if (!algo || algo_len == 0) { + return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid digest algorithm"); + } + + char algo_name[16]; + if (algo_len >= sizeof(algo_name)) { + return js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported digest algorithm"); + } + + for (size_t i = 0; i < algo_len; i++) { + char ch = algo[i]; + if (ch >= 'a' && ch <= 'z') ch = (char)(ch - ('a' - 'A')); + algo_name[i] = ch; + } + algo_name[algo_len] = '\0'; + + const char *evp_name = NULL; + if (strcmp(algo_name, "SHA-1") == 0 || strcmp(algo_name, "SHA1") == 0) evp_name = "sha1"; + else if (strcmp(algo_name, "SHA-256") == 0 || strcmp(algo_name, "SHA256") == 0) evp_name = "sha256"; + else if (strcmp(algo_name, "SHA-384") == 0 || strcmp(algo_name, "SHA384") == 0) evp_name = "sha384"; + else if (strcmp(algo_name, "SHA-512") == 0 || strcmp(algo_name, "SHA512") == 0) evp_name = "sha512"; + else return js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported digest algorithm"); + + const EVP_MD *md = EVP_get_digestbyname(evp_name); + if (!md) return js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported digest algorithm"); + + const uint8_t *bytes = NULL; + size_t len = 0; + if (!buffer_source_get_bytes(js, data, &bytes, &len)) { + return js_mkerr_typed(js, JS_ERR_TYPE, "digest data must be an ArrayBuffer, TypedArray, DataView, or Buffer"); + } + + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int digest_len = 0; + if (EVP_Digest(bytes, len, digest, &digest_len, md, NULL) != 1) { + return js_mkerr(js, "Digest failed"); + } + + ArrayBufferData *buffer = create_array_buffer_data(digest_len); + if (!buffer) return js_mkerr(js, "Out of memory"); + if (digest_len > 0) memcpy(buffer->data, digest, digest_len); + + return create_arraybuffer_obj(js, buffer); +} + +static ant_value_t js_crypto_subtle_digest(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t promise = js_mkpromise(js); + if (nargs < 2) { + js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "subtle.digest requires algorithm and data")); + return promise; + } + + ant_value_t result = crypto_subtle_digest_impl(js, args[0], args[1]); + if (is_err(result)) js_reject_promise(js, promise, result); + else js_resolve_promise(js, promise, result); + + return promise; +} + static ant_value_t js_hash_update(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_val = js_getthis(js); ant_hash_state_t *state = NULL; @@ -547,6 +625,7 @@ cleanup: static ant_value_t create_crypto_obj(ant_t *js) { ant_value_t crypto_obj = js_mkobj(js); + ant_value_t subtle_obj = js_mkobj(js); js_set(js, crypto_obj, "random", js_mkfun(js_crypto_random)); js_set(js, crypto_obj, "randomBytes", js_mkfun(js_crypto_random_bytes)); @@ -554,6 +633,9 @@ static ant_value_t create_crypto_obj(ant_t *js) { js_set(js, crypto_obj, "randomUUID", js_mkfun(js_crypto_random_uuid)); js_set(js, crypto_obj, "randomUUIDv7", js_mkfun(js_crypto_random_uuidv7)); js_set(js, crypto_obj, "getRandomValues", js_mkfun(js_crypto_get_random_values)); + js_set(js, subtle_obj, "digest", js_mkfun(js_crypto_subtle_digest)); + js_set_sym(js, subtle_obj, get_toStringTag_sym(), js_mkstr(js, "SubtleCrypto", 12)); + js_set(js, crypto_obj, "subtle", subtle_obj); js_set_sym(js, crypto_obj, get_toStringTag_sym(), js_mkstr(js, "Crypto", 6)); return crypto_obj; diff --git a/src/modules/events.c b/src/modules/events.c index aeb79f1..82cbceb 100644 --- a/src/modules/events.c +++ b/src/modules/events.c @@ -69,6 +69,7 @@ typedef struct { ant_value_t js_key; unsigned char *hash_key; size_t hash_key_len; + bool warned_max_listeners; UT_hash_handle hh; } EventType; @@ -215,6 +216,32 @@ static bool is_eventtarget_instance(ant_value_t target) { return js_check_brand(target, BRAND_EVENTTARGET); } +static int eventemitter_get_max_listeners_impl(ant_value_t target) { + ant_value_t slot = js_get_slot(target, SLOT_EVENT_MAX_LISTENERS); + if (vtype(slot) == T_NUM) { + int n = (int)js_getnum(slot); + return n >= 0 ? n : EVENTS_DEFAULT_MAX_LISTENERS; + } + return EVENTS_DEFAULT_MAX_LISTENERS; +} + +static ant_value_t eventemitter_get_listeners_array(ant_t *js, ant_value_t target, ant_value_t key) { + ant_value_t result = js_mkarr(js); + EventType *evt = NULL; + + if (!is_object_type(target) || !key) return result; + evt = find_emitter_event_type(js, target, key); + if (!evt) return result; + + for (unsigned int i = 0; i < utarray_len(evt->listeners); i++) { + EventListenerEntry *entry = (EventListenerEntry *)utarray_eltptr(evt->listeners, i); + if (!entry) continue; + js_arr_push(js, result, entry->callback); + } + + return result; +} + static void js_init_event_obj(ant_t *js, ant_value_t obj, ant_value_t type_val, bool bubbles, bool cancelable) { js_set(js, obj, "type", type_val); js_set(js, obj, "target", js_mknull()); @@ -663,6 +690,31 @@ static bool eventemitter_add_listener_impl( items[0] = entry; } + int max_listeners = eventemitter_get_max_listeners_impl(target); + if ( + max_listeners > 0 && + !evt->warned_max_listeners && + (int)utarray_len(evt->listeners) > max_listeners + ) { + evt->warned_max_listeners = true; + if (vtype(key) == T_STR) { + fprintf( + stderr, + "Warning: Possible EventEmitter memory leak detected. " + "%u '%s' listeners added. Use emitter.setMaxListeners() to increase limit.\n", + (unsigned)utarray_len(evt->listeners), + js_str(js, key) + ); + } else { + fprintf( + stderr, + "Warning: Possible EventEmitter memory leak detected. " + "%u listeners added for a Symbol event. Use emitter.setMaxListeners() to increase limit.\n", + (unsigned)utarray_len(evt->listeners) + ); + } + } + return true; } @@ -884,6 +936,32 @@ static ant_value_t js_eventemitter_listenerCount(ant_t *js, ant_value_t *args, i return js_mknum((double)utarray_len(evt->listeners)); } +static ant_value_t js_eventemitter_setMaxListeners(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "setMaxListeners requires 1 argument"); + if (vtype(args[0]) != T_NUM) return js_mkerr(js, "n must be a number"); + + int n = (int)js_getnum(args[0]); + if (n < 0) return js_mkerr(js, "n must be non-negative"); + + ant_value_t this_obj = js_getthis(js); + if (!is_object_type(this_obj)) return js_mkerr_typed(js, JS_ERR_TYPE, "setMaxListeners requires an object receiver"); + js_set_slot(this_obj, SLOT_EVENT_MAX_LISTENERS, js_mknum(n)); + return this_obj; +} + +static ant_value_t js_eventemitter_getMaxListeners(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); + if (!is_object_type(this_obj)) return js_mknum(EVENTS_DEFAULT_MAX_LISTENERS); + return js_mknum(eventemitter_get_max_listeners_impl(this_obj)); +} + +static ant_value_t js_eventemitter_rawListeners(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkarr(js); + ant_value_t key = evt_key_from_arg(args[0]); + if (!key) return js_mkarr(js); + return eventemitter_get_listeners_array(js, js_getthis(js), key); +} + static ant_value_t js_eventemitter_eventNames(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); ant_value_t result = js_mkarr(js); @@ -1017,8 +1095,19 @@ static ant_value_t js_events_once(ant_t *js, ant_value_t *args, int nargs) { return js_events_once_attach(js, promise, target, key, listener, signal); } -// TODO: fix stub static ant_value_t js_events_disposable_dispose(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); + if (!is_object_type(state)) return js_mkundef(); + + ant_value_t disposed = js_get_slot(state, SLOT_SETTLED); + if (vtype(disposed) == T_BOOL && disposed == js_true) return js_mkundef(); + js_set_slot(state, SLOT_SETTLED, js_true); + + ant_value_t signal = js_get(js, state, "signal"); + ant_value_t listener = js_get(js, state, "listener"); + if (abort_signal_is_signal(signal) && is_callable(listener)) + abort_signal_remove_listener(js, signal, listener); + return js_mkundef(); } @@ -1026,26 +1115,43 @@ static ant_value_t js_events_add_abort_listener(ant_t *js, ant_value_t *args, in if (nargs < 2 || !abort_signal_is_signal(args[0]) || !is_callable(args[1])) return js_mkerr_typed(js, JS_ERR_TYPE, "events.addAbortListener requires an AbortSignal and listener"); - if (abort_signal_is_aborted(args[0])) { + bool already_aborted = abort_signal_is_aborted(args[0]); + if (already_aborted) { ant_value_t event = js_mkobj(js); js_set(js, event, "type", js_mkstr(js, "abort", 5)); sv_vm_call(js->vm, js, args[1], args[0], &event, 1, NULL, false); } else abort_signal_add_listener(js, args[0], args[1]); + ant_value_t state = js_mkobj(js); + js_set_slot(state, SLOT_SETTLED, js_bool(already_aborted)); + js_set(js, state, "signal", args[0]); + js_set(js, state, "listener", args[1]); + ant_value_t disposable = js_mkobj(js); - js_set(js, disposable, "dispose", js_mkfun(js_events_disposable_dispose)); + js_set(js, disposable, "dispose", js_heavy_mkfun(js, js_events_disposable_dispose, state)); return disposable; } -// TODO: fix stub static ant_value_t js_events_set_max_listeners(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "setMaxListeners requires at least 1 argument"); + if (vtype(args[0]) != T_NUM) return js_mkerr(js, "n must be a number"); + + int n = (int)js_getnum(args[0]); + if (n < 0) return js_mkerr(js, "n must be non-negative"); + + for (int i = 1; i < nargs; i++) { + if (!is_object_type(args[i])) continue; + js_set_slot(args[i], SLOT_EVENT_MAX_LISTENERS, js_mknum(n)); + } + return js_mkundef(); } -// TODO: fix stub static ant_value_t js_events_get_max_listeners(ant_t *js, ant_value_t *args, int nargs) { - return js_mknum(10); + if (nargs < 1) return js_mknum(EVENTS_DEFAULT_MAX_LISTENERS); + if (!is_object_type(args[0])) return js_mknum(EVENTS_DEFAULT_MAX_LISTENERS); + return js_mknum(eventemitter_get_max_listeners_impl(args[0])); } // TODO: fix stub @@ -1084,6 +1190,9 @@ ant_value_t eventemitter_prototype(ant_t *js) { js_set(js, eventemitter_proto, "emit", js_mkfun(js_eventemitter_emit)); js_set(js, eventemitter_proto, "removeAllListeners", js_mkfun(js_eventemitter_removeAllListeners)); js_set(js, eventemitter_proto, "listenerCount", js_mkfun(js_eventemitter_listenerCount)); + js_set(js, eventemitter_proto, "setMaxListeners", js_mkfun(js_eventemitter_setMaxListeners)); + js_set(js, eventemitter_proto, "getMaxListeners", js_mkfun(js_eventemitter_getMaxListeners)); + js_set(js, eventemitter_proto, "rawListeners", js_mkfun(js_eventemitter_rawListeners)); js_set(js, eventemitter_proto, "eventNames", js_mkfun(js_eventemitter_eventNames)); js_set_sym(js, eventemitter_proto, get_toStringTag_sym(), js_mkstr(js, "EventEmitter", 12)); diff --git a/src/modules/fs.c b/src/modules/fs.c index bcc50b2..ebe6116 100644 --- a/src/modules/fs.c +++ b/src/modules/fs.c @@ -61,7 +61,8 @@ typedef enum { FS_OP_OPEN, FS_OP_CLOSE, FS_OP_MKDTEMP, - FS_OP_CHMOD + FS_OP_CHMOD, + FS_OP_RENAME } fs_op_type_t; typedef struct fs_request_s { @@ -72,6 +73,7 @@ typedef struct fs_request_s { ant_value_t callback_fn; char *path; + char *path2; char *data; char *error_msg; size_t data_len; @@ -201,6 +203,11 @@ static ant_value_t fs_mk_errno_error( const char *syscall, const char *path, const char *dest ); +static ant_value_t fs_mk_uv_error( + ant_t *js, int uv_code, + const char *syscall, const char *path, const char *dest +); + static bool fs_parse_mode(ant_t *js, ant_value_t arg, mode_t *out) { if (vtype(arg) == T_NUM) { double mode = js_getnum(arg); @@ -1314,6 +1321,7 @@ static void free_fs_request(fs_request_t *req) { if (!req) return; if (req->path) free(req->path); + if (req->path2) free(req->path2); if (req->data) free(req->data); if (req->error_msg) free(req->error_msg); @@ -1354,6 +1362,7 @@ static void complete_request(fs_request_t *req) { js_set(req->js, props, "code", js_mkstr(req->js, code, strlen(code))); js_set(req->js, props, "errno", js_mknum((double)req->error_code)); if (req->path) js_set(req->js, props, "path", js_mkstr(req->js, req->path, strlen(req->path))); + if (req->path2) js_set(req->js, props, "dest", js_mkstr(req->js, req->path2, strlen(req->path2))); reject_value = js_mkerr_props(req->js, JS_ERR_TYPE, props, "%s", err_msg); } else reject_value = js_mkerr(req->js, "%s", err_msg); @@ -1514,6 +1523,18 @@ static void on_unlink_complete(uv_fs_t *uv_req) { complete_request(req); } +static void on_rename_complete(uv_fs_t *uv_req) { + fs_request_t *req = (fs_request_t *)uv_req->data; + + if (uv_req->result < 0) { + fs_request_fail(req, (int)uv_req->result); + } + + uv_fs_req_cleanup(uv_req); + req->completed = 1; + complete_request(req); +} + static int mkdirp(const char *path, mode_t mode) { int result = -1; char *tmp = strdup(path); @@ -1885,39 +1906,47 @@ static ant_value_t builtin_fs_readBytes(ant_t *js, ant_value_t *args, int nargs) return req->promise; } -static ant_value_t builtin_fs_writeFileSync(ant_t *js, ant_value_t *args, int nargs) { - if (nargs < 2) return js_mkerr(js, "writeFileSync() requires path and data arguments"); - - if (vtype(args[0]) != T_STR) return js_mkerr(js, "writeFileSync() path must be a string"); - if (vtype(args[1]) != T_STR) return js_mkerr(js, "writeFileSync() data must be a string"); - +static ant_value_t fs_write_file_sync_impl( + ant_t *js, + ant_value_t *args, + int nargs, + const char *fn_name, + const char *mode +) { + if (nargs < 2) return js_mkerr(js, "%s() requires path and data arguments", fn_name); + if (vtype(args[0]) != T_STR) return js_mkerr(js, "%s() path must be a string", fn_name); + if (vtype(args[1]) != T_STR) return js_mkerr(js, "%s() data must be a string", fn_name); + size_t path_len, data_len; char *path = js_getstr(js, args[0], &path_len); char *data = js_getstr(js, args[1], &data_len); - if (!path || !data) return js_mkerr(js, "Failed to get arguments"); - + char *path_cstr = strndup(path, path_len); if (!path_cstr) return js_mkerr(js, "Out of memory"); - - FILE *file = fopen(path_cstr, "wb"); + + FILE *file = fopen(path_cstr, mode); if (!file) { ant_value_t err = fs_mk_errno_error(js, errno, "open", path_cstr, NULL); free(path_cstr); return err; } - + size_t bytes_written = fwrite(data, 1, data_len, file); fclose(file); free(path_cstr); - + if (bytes_written != data_len) { return js_mkerr(js, "Failed to write entire file"); } - + return js_mkundef(); } +static ant_value_t builtin_fs_writeFileSync(ant_t *js, ant_value_t *args, int nargs) { + return fs_write_file_sync_impl(js, args, nargs, "writeFileSync", "wb"); +} + static ant_value_t builtin_fs_copyFileSync(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkerr(js, "copyFileSync() requires src and dest arguments"); @@ -1966,10 +1995,233 @@ static ant_value_t builtin_fs_copyFileSync(ant_t *js, ant_value_t *args, int nar fclose(in); fclose(out); free(src_cstr); free(dest_cstr); + + return js_mkundef(); +} + +typedef struct { + bool recursive; + bool force; + bool error_on_exist; +} fs_cp_options_t; + +static void fs_parse_cp_options(ant_t *js, ant_value_t value, fs_cp_options_t *opts) { + opts->recursive = false; + opts->force = true; + opts->error_on_exist = false; + + if (vtype(value) != T_OBJ) return; + + ant_value_t recursive = js_get(js, value, "recursive"); + ant_value_t force = js_get(js, value, "force"); + ant_value_t error_on_exist = js_get(js, value, "errorOnExist"); + + if (!is_undefined(recursive)) opts->recursive = js_truthy(js, recursive); + if (!is_undefined(force)) opts->force = js_truthy(js, force); + if (!is_undefined(error_on_exist)) opts->error_on_exist = js_truthy(js, error_on_exist); +} + +static char *fs_join_path(const char *base, const char *name) { + size_t base_len = strlen(base); + size_t name_len = strlen(name); + bool need_sep = base_len > 0 && base[base_len - 1] != '/'; + size_t total_len = base_len + (need_sep ? 1 : 0) + name_len; + + char *joined = calloc(total_len + 1, 1); + if (!joined) return NULL; + + memcpy(joined, base, base_len); + if (need_sep) joined[base_len++] = '/'; + memcpy(joined + base_len, name, name_len); + joined[total_len] = '\0'; + return joined; +} + +static ant_value_t fs_copy_file_sync_impl( + ant_t *js, + const char *src_cstr, + const char *dest_cstr, + const fs_cp_options_t *opts, + const char *op_name +) { + struct stat dest_st; + if (!opts->force && stat(dest_cstr, &dest_st) == 0) { + if (opts->error_on_exist) return fs_mk_errno_error(js, EEXIST, op_name, src_cstr, dest_cstr); + return js_mkundef(); + } + + FILE *in = fopen(src_cstr, "rb"); + if (!in) return fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr); + + FILE *out = fopen(dest_cstr, "wb"); + if (!out) { + ant_value_t err = fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr); + fclose(in); + return err; + } + + char buf[8192]; + size_t n = 0; + while ((n = fread(buf, 1, sizeof(buf), in)) > 0) { + if (fwrite(buf, 1, n, out) != n) { + ant_value_t err = fs_mk_errno_error(js, errno ? errno : EIO, op_name, src_cstr, dest_cstr); + fclose(in); + fclose(out); + return err; + }} + + fclose(in); + fclose(out); return js_mkundef(); } +static ant_value_t builtin_fs_copyFile(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 2) return js_mkerr(js, "copyFile() requires src and dest arguments"); + if (vtype(args[0]) != T_STR) return js_mkerr(js, "copyFile() src must be a string"); + if (vtype(args[1]) != T_STR) return js_mkerr(js, "copyFile() dest must be a string"); + + size_t src_len = 0, dest_len = 0; + const char *src = js_getstr(js, args[0], &src_len); + const char *dest = js_getstr(js, args[1], &dest_len); + if (!src || !dest) return js_mkerr(js, "Failed to get arguments"); + + char *src_cstr = strndup(src, src_len); + char *dest_cstr = strndup(dest, dest_len); + if (!src_cstr || !dest_cstr) { + free(src_cstr); + free(dest_cstr); + return js_mkerr(js, "Out of memory"); + } + + fs_cp_options_t opts = { + .recursive = false, + .force = true, + .error_on_exist = false, + }; + + ant_value_t promise = js_mkpromise(js); + ant_value_t result = fs_copy_file_sync_impl(js, src_cstr, dest_cstr, &opts, "copyFile"); + free(src_cstr); + free(dest_cstr); + + if (is_err(result)) js_reject_promise(js, promise, result); + else js_resolve_promise(js, promise, js_mkundef()); + + return promise; +} + +static ant_value_t fs_copy_path_sync_impl( + ant_t *js, + const char *src_cstr, + const char *dest_cstr, + const fs_cp_options_t *opts, + const char *op_name +) { + struct stat src_st; + if (stat(src_cstr, &src_st) != 0) return fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr); + + if ((src_st.st_mode & S_IFMT) == S_IFDIR) { + if (!opts->recursive) { + return js_mkerr(js, "%s() recursive option is required to copy directories", op_name); + } + + struct stat dest_st; + if (stat(dest_cstr, &dest_st) != 0) { + if (errno != ENOENT) return fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr); + #ifdef _WIN32 + if (_mkdir(dest_cstr) != 0) return fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr); + #else + if (mkdir(dest_cstr, (mode_t)(src_st.st_mode & 0777)) != 0) { + return fs_mk_errno_error(js, errno, op_name, src_cstr, dest_cstr); + } + #endif + } else if ((dest_st.st_mode & S_IFMT) != S_IFDIR) { + return fs_mk_errno_error(js, EEXIST, op_name, src_cstr, dest_cstr); + } + + uv_fs_t req; + int rc = uv_fs_scandir(NULL, &req, src_cstr, 0, NULL); + if (rc < 0) { + ant_value_t err = fs_mk_uv_error(js, rc, "scandir", src_cstr, dest_cstr); + uv_fs_req_cleanup(&req); + return err; + } + + uv_dirent_t dirent; + ant_value_t result = js_mkundef(); + while (uv_fs_scandir_next(&req, &dirent) != UV_EOF) { + if ( + strcmp(dirent.name, ".") == 0 || + strcmp(dirent.name, "..") == 0 + ) continue; + + char *child_src = fs_join_path(src_cstr, dirent.name); + char *child_dest = fs_join_path(dest_cstr, dirent.name); + if (!child_src || !child_dest) { + free(child_src); + free(child_dest); + result = js_mkerr(js, "Out of memory"); + break; + } + + result = fs_copy_path_sync_impl(js, child_src, child_dest, opts, op_name); + free(child_src); + free(child_dest); + if (is_err(result)) break; + } + + uv_fs_req_cleanup(&req); + return result; + } + + return fs_copy_file_sync_impl(js, src_cstr, dest_cstr, opts, op_name); +} + +static ant_value_t fs_cp_sync_common( + ant_t *js, + ant_value_t *args, + int nargs, + const char *fn_name +) { + if (nargs < 2) return js_mkerr(js, "%s() requires src and dest arguments", fn_name); + if (vtype(args[0]) != T_STR) return js_mkerr(js, "%s() src must be a string", fn_name); + if (vtype(args[1]) != T_STR) return js_mkerr(js, "%s() dest must be a string", fn_name); + + size_t src_len = 0, dest_len = 0; + const char *src = js_getstr(js, args[0], &src_len); + const char *dest = js_getstr(js, args[1], &dest_len); + if (!src || !dest) return js_mkerr(js, "Failed to get arguments"); + + fs_cp_options_t opts; + fs_parse_cp_options(js, nargs >= 3 ? args[2] : js_mkundef(), &opts); + + char *src_cstr = strndup(src, src_len); + char *dest_cstr = strndup(dest, dest_len); + if (!src_cstr || !dest_cstr) { + free(src_cstr); + free(dest_cstr); + return js_mkerr(js, "Out of memory"); + } + + ant_value_t result = fs_copy_path_sync_impl(js, src_cstr, dest_cstr, &opts, fn_name); + free(src_cstr); + free(dest_cstr); + return result; +} + +static ant_value_t builtin_fs_cpSync(ant_t *js, ant_value_t *args, int nargs) { + return fs_cp_sync_common(js, args, nargs, "cpSync"); +} + +static ant_value_t builtin_fs_cp(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t promise = js_mkpromise(js); + ant_value_t result = fs_cp_sync_common(js, args, nargs, "cp"); + if (is_err(result)) js_reject_promise(js, promise, result); + else js_resolve_promise(js, promise, js_mkundef()); + return promise; +} + static ant_value_t builtin_fs_renameSync(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkerr(js, "renameSync() requires oldPath and newPath arguments"); @@ -2005,6 +2257,43 @@ static ant_value_t builtin_fs_renameSync(ant_t *js, ant_value_t *args, int nargs return js_mkundef(); } +static ant_value_t builtin_fs_rename(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 2) return js_mkerr(js, "rename() requires oldPath and newPath arguments"); + if (vtype(args[0]) != T_STR) return js_mkerr(js, "rename() oldPath must be a string"); + if (vtype(args[1]) != T_STR) return js_mkerr(js, "rename() newPath must be a string"); + + size_t old_len = 0, new_len = 0; + const char *old_path = js_getstr(js, args[0], &old_len); + const char *new_path = js_getstr(js, args[1], &new_len); + if (!old_path || !new_path) return js_mkerr(js, "Failed to get arguments"); + + fs_request_t *req = calloc(1, sizeof(fs_request_t)); + if (!req) return js_mkerr(js, "Out of memory"); + + req->js = js; + req->op_type = FS_OP_RENAME; + req->promise = js_mkpromise(js); + req->path = strndup(old_path, old_len); + req->path2 = strndup(new_path, new_len); + req->uv_req.data = req; + + if (!req->path || !req->path2) { + free_fs_request(req); + return js_mkerr(js, "Out of memory"); + } + + utarray_push_back(pending_requests, &req); + int result = uv_fs_rename(uv_default_loop(), &req->uv_req, req->path, req->path2, on_rename_complete); + + if (result < 0) { + fs_request_fail(req, result); + req->completed = 1; + complete_request(req); + } + + return req->promise; +} + static double fs_time_arg_to_seconds(ant_t *js, ant_value_t v) { if (is_date_instance(v)) { ant_value_t t = js_get_slot(js_as_obj(v), SLOT_DATA); @@ -2052,36 +2341,15 @@ static ant_value_t builtin_fs_futimes(ant_t *js, ant_value_t *args, int nargs) { } static ant_value_t builtin_fs_appendFileSync(ant_t *js, ant_value_t *args, int nargs) { - if (nargs < 2) return js_mkerr(js, "appendFileSync() requires path and data arguments"); - - if (vtype(args[0]) != T_STR) return js_mkerr(js, "appendFileSync() path must be a string"); - if (vtype(args[1]) != T_STR) return js_mkerr(js, "appendFileSync() data must be a string"); - - size_t path_len, data_len; - char *path = js_getstr(js, args[0], &path_len); - char *data = js_getstr(js, args[1], &data_len); - - if (!path || !data) return js_mkerr(js, "Failed to get arguments"); - - char *path_cstr = strndup(path, path_len); - if (!path_cstr) return js_mkerr(js, "Out of memory"); - - FILE *file = fopen(path_cstr, "ab"); - if (!file) { - ant_value_t err = fs_mk_errno_error(js, errno, "open", path_cstr, NULL); - free(path_cstr); - return err; - } - - size_t bytes_written = fwrite(data, 1, data_len, file); - fclose(file); - free(path_cstr); - - if (bytes_written != data_len) { - return js_mkerr(js, "Failed to write entire file"); - } - - return js_mkundef(); + return fs_write_file_sync_impl(js, args, nargs, "appendFileSync", "ab"); +} + +static ant_value_t builtin_fs_appendFile(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t promise = js_mkpromise(js); + ant_value_t result = fs_write_file_sync_impl(js, args, nargs, "appendFile", "ab"); + if (is_err(result)) js_reject_promise(js, promise, result); + else js_resolve_promise(js, promise, js_mkundef()); + return promise; } static ant_value_t builtin_fs_writeFile(ant_t *js, ant_value_t *args, int nargs) { @@ -2704,6 +2972,50 @@ static ant_value_t builtin_fs_lstat(ant_t *js, ant_value_t *args, int nargs) { return req->promise; } +static ant_value_t builtin_fs_fstatSync(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "fstatSync() requires an fd argument"); + if (vtype(args[0]) != T_NUM) return js_mkerr(js, "fstatSync() fd must be a number"); + + uv_file fd = (uv_file)(int)js_getnum(args[0]); + uv_fs_t req; + int result = uv_fs_fstat(NULL, &req, fd, NULL); + + if (result < 0) { + ant_value_t err = fs_mk_uv_error(js, result, "fstat", NULL, NULL); + uv_fs_req_cleanup(&req); + return err; + } + + ant_value_t stat_obj = fs_stats_object_from_uv(js, &req.statbuf); + uv_fs_req_cleanup(&req); + return stat_obj; +} + +static ant_value_t builtin_fs_fstat(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "fstat() requires an fd argument"); + if (vtype(args[0]) != T_NUM) return js_mkerr(js, "fstat() fd must be a number"); + + fs_request_t *req = calloc(1, sizeof(fs_request_t)); + if (!req) return js_mkerr(js, "Out of memory"); + + req->js = js; + req->op_type = FS_OP_STAT; + req->promise = js_mkpromise(js); + req->fd = (uv_file)(int)js_getnum(args[0]); + req->uv_req.data = req; + + utarray_push_back(pending_requests, &req); + int result = uv_fs_fstat(uv_default_loop(), &req->uv_req, req->fd, on_stat_complete); + + if (result < 0) { + fs_request_fail(req, result); + req->completed = 1; + complete_request(req); + } + + return req->promise; +} + static ant_value_t builtin_fs_existsSync(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "existsSync() requires a path argument"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "existsSync() path must be a string"); @@ -3971,12 +4283,16 @@ static ant_value_t fs_make_callback_wrapper(ant_t *js, ant_value_t original, boo } static void fs_set_promise_methods(ant_t *js, ant_value_t lib) { + js_set(js, lib, "appendFile", js_mkfun(builtin_fs_appendFile)); + js_set(js, lib, "cp", js_mkfun(builtin_fs_cp)); + js_set(js, lib, "copyFile", js_mkfun(builtin_fs_copyFile)); js_set(js, lib, "readFile", js_mkfun(builtin_fs_readFile)); js_set(js, lib, "open", js_mkfun(builtin_fs_open_fd)); js_set(js, lib, "close", js_mkfun(builtin_fs_close_fd)); js_set(js, lib, "writeFile", js_mkfun(builtin_fs_writeFile)); js_set(js, lib, "write", js_mkfun(builtin_fs_write_fd)); js_set(js, lib, "writev", js_mkfun(builtin_fs_writev_fd)); + js_set(js, lib, "rename", js_mkfun(builtin_fs_rename)); js_set(js, lib, "rm", js_mkfun(builtin_fs_rm)); js_set(js, lib, "unlink", js_mkfun(builtin_fs_unlink)); js_set(js, lib, "mkdir", js_mkfun(builtin_fs_mkdir)); @@ -3984,6 +4300,7 @@ static void fs_set_promise_methods(ant_t *js, ant_value_t lib) { js_set(js, lib, "rmdir", js_mkfun(builtin_fs_rmdir)); js_set(js, lib, "stat", js_mkfun(builtin_fs_stat)); js_set(js, lib, "lstat", js_mkfun(builtin_fs_lstat)); + js_set(js, lib, "fstat", js_mkfun(builtin_fs_fstat)); js_set(js, lib, "utimes", js_mkfun(builtin_fs_utimes)); js_set(js, lib, "futimes", js_mkfun(builtin_fs_futimes)); js_set(js, lib, "exists", js_mkfun(builtin_fs_exists)); @@ -3997,12 +4314,16 @@ static void fs_set_promise_methods(ant_t *js, ant_value_t lib) { static void fs_set_callback_compatible_methods(ant_t *js, ant_value_t lib) { ant_value_t realpath = fs_make_callback_wrapper(js, js_mkfun(builtin_fs_realpath), false); + js_set(js, lib, "appendFile", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_appendFile), false)); + js_set(js, lib, "cp", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_cp), false)); + js_set(js, lib, "copyFile", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_copyFile), false)); js_set(js, lib, "readFile", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_readFile), false)); js_set(js, lib, "open", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_open_fd), false)); js_set(js, lib, "close", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_close_fd), false)); js_set(js, lib, "writeFile", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_writeFile), false)); js_set(js, lib, "write", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_write_fd), false)); js_set(js, lib, "writev", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_writev_fd), false)); + js_set(js, lib, "rename", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_rename), false)); js_set(js, lib, "rm", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_rm), false)); js_set(js, lib, "unlink", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_unlink), false)); js_set(js, lib, "mkdir", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_mkdir), false)); @@ -4010,6 +4331,7 @@ static void fs_set_callback_compatible_methods(ant_t *js, ant_value_t lib) { js_set(js, lib, "rmdir", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_rmdir), false)); js_set(js, lib, "stat", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_stat), false)); js_set(js, lib, "lstat", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_lstat), false)); + js_set(js, lib, "fstat", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_fstat), false)); js_set(js, lib, "utimes", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_utimes), false)); js_set(js, lib, "futimes", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_futimes), false)); js_set(js, lib, "exists", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_exists), true)); @@ -4086,6 +4408,7 @@ ant_value_t fs_library(ant_t *js) { js_set(js, lib, "writeSync", js_mkfun(builtin_fs_writeSync)); js_set(js, lib, "writevSync", js_mkfun(builtin_fs_writevSync)); js_set(js, lib, "appendFileSync", js_mkfun(builtin_fs_appendFileSync)); + js_set(js, lib, "cpSync", js_mkfun(builtin_fs_cpSync)); js_set(js, lib, "copyFileSync", js_mkfun(builtin_fs_copyFileSync)); js_set(js, lib, "renameSync", js_mkfun(builtin_fs_renameSync)); js_set(js, lib, "rmSync", js_mkfun(builtin_fs_rmSync)); @@ -4095,6 +4418,7 @@ ant_value_t fs_library(ant_t *js) { js_set(js, lib, "rmdirSync", js_mkfun(builtin_fs_rmdirSync)); js_set(js, lib, "statSync", js_mkfun(builtin_fs_statSync)); js_set(js, lib, "lstatSync", js_mkfun(builtin_fs_lstatSync)); + js_set(js, lib, "fstatSync", js_mkfun(builtin_fs_fstatSync)); js_set(js, lib, "utimesSync", js_mkfun(builtin_fs_utimesSync)); js_set(js, lib, "futimesSync", js_mkfun(builtin_fs_futimesSync)); js_set(js, lib, "existsSync", js_mkfun(builtin_fs_existsSync)); diff --git a/src/modules/globals.c b/src/modules/globals.c index 767dbf2..6ab1c68 100644 --- a/src/modules/globals.c +++ b/src/modules/globals.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "ant.h" @@ -150,6 +151,108 @@ static ant_value_t intl_dtf_constructor(ant_t *js, ant_value_t *args, int nargs) return this; } +static size_t intl_utf8_segment_len(const char *input, size_t remaining) { + if (remaining == 0) return 0; + + const unsigned char *s = (const unsigned char *)input; + unsigned char c = s[0]; + size_t len = 1; + + if ((c & 0x80) == 0) return 1; + if ((c & 0xe0) == 0xc0) len = 2; + else if ((c & 0xf0) == 0xe0) len = 3; + else if ((c & 0xf8) == 0xf0) len = 4; + + if (len > remaining) return 1; + for (size_t i = 1; i < len; i++) if ((s[i] & 0xc0) != 0x80) return 1; + + return len; +} + +static bool intl_ascii_is_word_byte(const char *segment, size_t len) { + if (len != 1) return true; + + unsigned char c = (unsigned char)segment[0]; + return + (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + c == '_'; +} + +static const char *intl_segmenter_granularity(ant_t *js, ant_value_t segmenter, size_t *len) { + ant_value_t granularity = js_get(js, segmenter, "granularity"); + if (vtype(granularity) != T_STR) { + if (len) *len = 8; + return "grapheme"; + } + + return js_getstr(js, granularity, len); +} + +static ant_value_t intl_segmenter_segment(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t input = nargs > 0 ? js_tostring_val(js, args[0]) : js_mkstr(js, "", 0); + if (is_err(input)) return input; + + size_t input_len = 0; + char *input_str = js_getstr(js, input, &input_len); + ant_value_t segments = js_mkarr(js); + + ant_value_t this = js_getthis(js); + size_t granularity_len = 0; + const char *granularity = intl_segmenter_granularity(js, this, &granularity_len); + bool word_granularity = granularity_len == 4 && memcmp(granularity, "word", 4) == 0; + + for (size_t offset = 0; offset < input_len;) { + size_t segment_len = intl_utf8_segment_len(input_str + offset, input_len - offset); + ant_value_t record = js_mkobj(js); + + js_set(js, record, "segment", js_mkstr(js, input_str + offset, segment_len)); + js_set(js, record, "index", js_mknum((double)offset)); + js_set(js, record, "input", input); + + if (word_granularity) js_set( + js, record, "isWordLike", + js_bool(intl_ascii_is_word_byte(input_str + offset, segment_len)) + ); + + js_arr_push(js, segments, record); + offset += segment_len; + } + + return segments; +} + +static ant_value_t intl_segmenter_resolvedOptions(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t obj = js_mkobj(js); + ant_value_t this = js_getthis(js); + + size_t granularity_len = 0; + const char *granularity = intl_segmenter_granularity(js, this, &granularity_len); + + js_set(js, obj, "locale", js_mkstr(js, "en-US", 5)); + js_set(js, obj, "granularity", js_mkstr(js, granularity, granularity_len)); + + return obj; +} + +static ant_value_t intl_segmenter_constructor(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this = js_getthis(js); + const char *granularity = "grapheme"; + size_t granularity_len = 8; + + if (nargs >= 2 && vtype(args[1]) == T_OBJ) { + ant_value_t option = js_get(js, args[1], "granularity"); + if (vtype(option) == T_STR) granularity = js_getstr(js, option, &granularity_len); + } + + js_set(js, this, "granularity", js_mkstr(js, granularity, granularity_len)); + js_set(js, this, "segment", js_mkfun(intl_segmenter_segment)); + js_set(js, this, "resolvedOptions", js_mkfun(intl_segmenter_resolvedOptions)); + + return this; +} + void init_globals_module(void) { ant_t *js = rt->js; ant_value_t global = js_glob(js); @@ -159,8 +262,12 @@ void init_globals_module(void) { ant_value_t intl = js_mkobj(js); ant_value_t dtf_ctor = js_heavy_mkfun(js, intl_dtf_constructor, js_mkundef()); + ant_value_t segmenter_ctor = js_heavy_mkfun(js, intl_segmenter_constructor, js_mkundef()); js_mark_constructor(dtf_ctor, true); + js_mark_constructor(segmenter_ctor, true); + js_set(js, intl, "DateTimeFormat", dtf_ctor); + js_set(js, intl, "Segmenter", segmenter_ctor); js_set(js, global, "Intl", intl); } diff --git a/src/modules/io.c b/src/modules/io.c index 27b0be4..916cf84 100644 --- a/src/modules/io.c +++ b/src/modules/io.c @@ -13,12 +13,16 @@ #include "output.h" #include "internal.h" #include "runtime.h" +#include "gc/roots.h" #include "silver/engine.h" #include "modules/io.h" #include "modules/symbol.h" bool io_no_color = false; +static ant_value_t g_console_proto = 0; +static ant_value_t g_console_ctor = 0; + #define JSON_KEY "\x1b[0m" #define JSON_STRING "\x1b[32m" #define JSON_NUMBER "\x1b[33m" @@ -30,8 +34,8 @@ bool io_no_color = false; #define JSON_REF "\x1b[90m" #define JSON_WHITE "\x1b[97m" -static inline bool io_is_digit_ascii(char c) { - return c >= '0' && c <= '9'; +static inline bool io_is_digit_ascii(char c) { + return c >= '0' && c <= '9'; } static bool io_print_to_output(const char *str, ant_output_stream_t *out) { @@ -68,7 +72,7 @@ static bool io_print_to_output(const char *str, ant_output_stream_t *out) { if (c != 'm' && !ant_output_stream_putc(out, c)) return false; goto *states[0]; } - + done: return true; } @@ -152,7 +156,7 @@ static int io_iso_utc_token_len(const char *p) { while (*p && *p != end_char) ant_output_stream_putc(out, *p++); \ if (*p == end_char) ant_output_stream_putc(out, *p++); \ ant_output_stream_append_cstr(out, C_RESET); goto next; - + #define EMIT_TYPE(tag, len, color) \ if (!(is_key && brace_depth > 0) && memcmp(p, tag, len) == 0) { \ ant_output_stream_append_cstr(out, color); ant_output_stream_append_cstr(out, tag); ant_output_stream_append_cstr(out, C_RESET); \ @@ -274,7 +278,7 @@ lt: if (memcmp(p, "', JSON_REF) } if (memcmp(p, "', C_CYAN) } if (memcmp(p, "', C_CYAN) } - + if (p[1] == '>' || (isxdigit((unsigned char)p[1]) && isxdigit((unsigned char)p[2]))) { ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); ant_output_stream_append_cstr(out, JSON_WHITE); @@ -283,7 +287,7 @@ lt: if (*p == '>') { ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); ant_output_stream_append_cstr(out, C_RESET); } goto next; } - + ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); ant_output_stream_append_cstr(out, C_RESET); goto next; @@ -294,7 +298,7 @@ gt: alpha: if (memcmp(p, "Object [", 8) == 0) { EMIT_UNTIL(']', JSON_TAG) } if (memcmp(p, "Symbol(", 7) == 0) { EMIT_UNTIL(')', JSON_STRING) } - + EMIT_TYPE("Map", 3, JSON_STRING) EMIT_TYPE("Set", 3, JSON_STRING) @@ -356,7 +360,7 @@ void print_repl_value(ant_t *js, ant_value_t val, FILE *stream) { if (vtype(val) == T_OBJ && vtype(js_get_slot(val, SLOT_ERR_TYPE)) != T_UNDEF) { const char *stack = get_str_prop(js, val, "stack", 5, NULL); - + if (stack) { ant_output_stream_begin(out); io_print_to_output(stack, out); @@ -384,35 +388,194 @@ void print_repl_value(ant_t *js, ant_value_t val, FILE *stream) { if (cstr.needs_free) free((void *)cstr.ptr); } -ant_value_t console_print(ant_t *js, ant_value_t *args, int nargs, const char *color, FILE *stream) { - ant_output_stream_t *out = ant_output_stream(stream); +static ant_value_t console_call_value( + ant_t *js, ant_value_t fn, + ant_value_t this_val, + ant_value_t *args, int nargs +) { + ant_value_t saved_this = js->this_val; + ant_value_t result = js_mkundef(); + + js->this_val = this_val; + if (vtype(fn) == T_CFUNC) result = ((ant_cfunc_t)vdata(fn))(js, args, nargs); + else result = sv_vm_call(js->vm, js, fn, this_val, args, nargs, NULL, false); + js->this_val = saved_this; + + return result; +} + +static ant_value_t console_get_process_stream(ant_t *js, const char *name) { + ant_value_t process_obj = js_get(js, js_glob(js), "process"); + return js_get(js, process_obj, name); +} + +static ant_value_t console_get_effective_this(ant_t *js, ant_value_t this_obj) { + if (is_special_object(this_obj)) return this_obj; + ant_value_t console_obj = js_get(js, js_glob(js), "console"); + if (is_special_object(console_obj)) return console_obj; + return this_obj; +} + +static ant_value_t console_get_target_stream(ant_t *js, ant_value_t this_obj, bool use_stderr) { + if (is_special_object(this_obj)) { + ant_value_t direct = js_get_slot(this_obj, use_stderr ? SLOT_CONSOLE_STDERR : SLOT_CONSOLE_STDOUT); + if (is_special_object(direct)) return direct; + } + return console_get_process_stream(js, use_stderr ? "stderr" : "stdout"); +} + +static bool console_write_to_stream_obj(ant_t *js, ant_value_t stream_obj, const char *data, size_t len) { + if (!is_special_object(stream_obj)) return false; + + ant_value_t write_fn = js_get(js, stream_obj, "write"); + if (!is_callable(write_fn)) return false; + + ant_value_t argv[1] = { js_mkstr(js, data, len) }; + ant_value_t result = console_call_value(js, write_fn, stream_obj, argv, 1); + if (is_err(result) || js->thrown_exists) return false; + return true; +} + +static bool console_write_string( + ant_t *js, ant_value_t this_obj, + bool use_stderr, const char *data, size_t len +) { + ant_value_t stream_obj = console_get_target_stream(js, this_obj, use_stderr); + if (console_write_to_stream_obj(js, stream_obj, data, len)) return true; + + ant_output_stream_t *out = ant_output_stream(use_stderr ? stderr : stdout); ant_output_stream_begin(out); - if (color && !io_no_color) ant_output_stream_append_cstr(out, color); - + if (!ant_output_stream_append(out, data, len)) return false; + return ant_output_stream_flush(out); +} + +static bool console_output_put_indent(ant_output_stream_t *out, int total) { + for (int i = 0; i < total; i++) if (!ant_output_stream_putc(out, ' ')) return false; + return true; +} + +static int console_get_group_indentation(ant_t *js, ant_value_t this_obj) { + ant_value_t value = is_special_object(this_obj) ? js_get_slot(this_obj, SLOT_CONSOLE_GROUP_INDENT) : js_mkundef(); + return vtype(value) == T_NUM ? (int)js_getnum(value) : 2; +} + +static int console_get_group_level(ant_t *js, ant_value_t this_obj) { + ant_value_t value = is_special_object(this_obj) ? js_get_slot(this_obj, SLOT_CONSOLE_GROUP_LEVEL) : js_mkundef(); + return vtype(value) == T_NUM ? (int)js_getnum(value) : 0; +} + +static void console_set_group_level(ant_t *js, ant_value_t this_obj, int level) { + if (!is_special_object(this_obj)) return; + if (level < 0) level = 0; + js_set_slot(this_obj, SLOT_CONSOLE_GROUP_LEVEL, js_mknum((double)level)); +} + +static ant_value_t console_get_state_map(ant_t *js, ant_value_t this_obj, const char *name) { + internal_slot_t slot = SLOT_NONE; + + if (!is_special_object(this_obj)) return js_mkundef(); + if (strcmp(name, "counts") == 0) slot = SLOT_CONSOLE_COUNTS; + else if (strcmp(name, "timers") == 0) slot = SLOT_CONSOLE_TIMERS; + else return js_mkundef(); + + ant_value_t map = js_get_slot(this_obj, slot); + if (is_special_object(map)) return map; + + map = js_mkobj(js); + js_set_slot_wb(js, this_obj, slot, map); + + return map; +} + +static bool console_write_args_to_stream( + ant_t *js, ant_output_stream_t *out, + ant_value_t *args, int nargs, bool color_values +) { for (int i = 0; i < nargs; i++) { - if (i) ant_output_stream_putc(out, ' '); + if (i && !ant_output_stream_putc(out, ' ')) return false; + if (vtype(args[i]) == T_OBJ) { - const char *stack = get_str_prop(js, args[i], "stack", 5, NULL); - if (stack) { io_print_to_output(stack, out); continue; } - } - + const char *stack = get_str_prop(js, args[i], "stack", 5, NULL); + if (stack) { + if (!io_print_to_output(stack, out)) return false; + continue; + }} + char cbuf[512]; js_cstr_t cstr = js_to_cstr(js, args[i], cbuf, sizeof(cbuf)); - - if (vtype(args[i]) == T_STR) io_print_to_output(cstr.ptr, out); else { - if (color && !io_no_color) ant_output_stream_append_cstr(out, C_RESET); - print_value_colored_to_output(cstr.ptr, out); - if (color && !io_no_color) ant_output_stream_append_cstr(out, color); + bool ok = true; + + if (vtype(args[i]) == T_STR) ok = io_print_to_output(cstr.ptr, out); + else { + bool saved_no_color = io_no_color; + io_no_color = saved_no_color || !color_values; + if (ok) print_value_colored_to_output(cstr.ptr, out); + io_no_color = saved_no_color; } - + if (cstr.needs_free) free((void *)cstr.ptr); + if (!ok) return false; } - - if (color && !io_no_color) ant_output_stream_append_cstr(out, C_RESET); - ant_output_stream_putc(out, '\n'); - ant_output_stream_flush(out); - + + return true; +} + +static ant_value_t console_emit_to_output( + ant_t *js, ant_value_t this_obj, + const char *prefix, ant_value_t *args, + int nargs, + ant_output_stream_t *out +) { + bool color_values = !io_no_color; + + int group_level = console_get_group_level(js, this_obj); + int indent = console_get_group_indentation(js, this_obj); + int total_indent = group_level * indent; + + if (!console_output_put_indent(out, total_indent)) goto oom; + if (prefix && !ant_output_stream_append_cstr(out, prefix)) goto oom; + if (prefix && nargs > 0 && !ant_output_stream_putc(out, ' ')) goto oom; + if (!console_write_args_to_stream(js, out, args, nargs, color_values)) goto oom; + if (!ant_output_stream_putc(out, '\n')) goto oom; + return js_mkundef(); + oom: return js_mkerr(js, "Out of memory"); +} + +static inline ant_value_t console_emit_with_this( + ant_t *js, + ant_value_t this_obj, + bool use_stderr, const char *prefix, + ant_value_t *args, int nargs +) { + this_obj = console_get_effective_this(js, this_obj); + ant_output_stream_t out = {0}; + ant_output_stream_begin(&out); + + ant_value_t result = console_emit_to_output(js, this_obj, prefix, args, nargs, &out); + if (!is_err(result)) console_write_string( + js, this_obj, use_stderr, + out.buffer.data ? out.buffer.data : "", out.buffer.len + ); + + free(out.buffer.data); + return result; +} + +ant_value_t console_emit( + ant_t *js, + bool use_stderr, const char *prefix, + ant_value_t *args, int nargs +) { + return console_emit_with_this(js, js_mkundef(), use_stderr, prefix, args, nargs); +} + +ant_value_t console_emit_current( + ant_t *js, + bool use_stderr, const char *prefix, + ant_value_t *args, int nargs +) { + return console_emit_with_this(js, js_getthis(js), use_stderr, prefix, args, nargs); } static void console_write_args_to_output(ant_t *js, ant_output_stream_t *out, ant_value_t *args, int nargs) { @@ -436,139 +599,161 @@ for (int i = 0; i < nargs; i++) { }} static ant_value_t js_console_log(ant_t *js, ant_value_t *args, int nargs) { - return console_print(js, args, nargs, NULL, stdout); + return console_emit_current(js, false, NULL, args, nargs); } static ant_value_t js_console_error(ant_t *js, ant_value_t *args, int nargs) { - return console_print(js, args, nargs, C_RED, stderr); + return console_emit_current(js, true, NULL, args, nargs); } static ant_value_t js_console_warn(ant_t *js, ant_value_t *args, int nargs) { - return console_print(js, args, nargs, C_YELLOW, stderr); + return console_emit_current(js, true, NULL, args, nargs); } -static ant_value_t js_console_assert(ant_t *js, ant_value_t *args, int nargs) { - ant_output_stream_t *out = ant_output_stream(stderr); +static ant_value_t js_console_info(ant_t *js, ant_value_t *args, int nargs) { + return console_emit_current(js, false, NULL, args, nargs); +} + +static ant_value_t js_console_debug(ant_t *js, ant_value_t *args, int nargs) { + return console_emit_current(js, false, NULL, args, nargs); +} +static ant_value_t js_console_assert(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkundef(); - bool is_truthy = js_truthy(js, args[0]); if (is_truthy) return js_mkundef(); - - ant_output_stream_begin(out); - ant_output_stream_append_cstr(out, "Assertion failed"); - - if (nargs > 1) { - ant_output_stream_append_cstr(out, ": "); - console_write_args_to_output(js, out, args + 1, nargs - 1); - ant_output_stream_putc(out, '\n'); - ant_output_stream_flush(out); - return js_mkundef(); - } - - ant_output_stream_putc(out, '\n'); - ant_output_stream_flush(out); - - return js_mkundef(); + return console_emit_current(js, true, "Assertion failed:", args + 1, nargs - 1); } static ant_value_t js_console_trace(ant_t *js, ant_value_t *args, int nargs) { - ant_output_stream_t *out = ant_output_stream(stderr); - - ant_output_stream_begin(out); - ant_output_stream_append_cstr(out, "Trace"); - - if (nargs > 0) { - ant_output_stream_append_cstr(out, ": "); - console_write_args_to_output(js, out, args, nargs); - ant_output_stream_putc(out, '\n'); - } else ant_output_stream_putc(out, '\n'); - - ant_output_stream_flush(out); - js_print_stack_trace_vm(js, stderr); - + ant_value_t this_obj = console_get_effective_this(js, js_getthis(js)); + console_emit_current(js, true, "Trace:", args, nargs); + if (console_get_target_stream(js, this_obj, true) == js_mkundef()) js_print_stack_trace_vm(js, stderr); + else js_print_stack_trace_vm(js, stderr); return js_mkundef(); } -static ant_value_t js_console_info(ant_t *js, ant_value_t *args, int nargs) { - return console_print(js, args, nargs, C_CYAN, stdout); -} - -static ant_value_t js_console_debug(ant_t *js, ant_value_t *args, int nargs) { - return console_print(js, args, nargs, C_MAGENTA, stdout); -} - static ant_value_t js_console_clear(ant_t *js, ant_value_t *args, int nargs) { - if (!io_no_color) { - ant_output_stream_t *out = ant_output_stream(stdout); - ant_output_stream_begin(out); - ant_output_stream_append_cstr(out, "\033[2J\033[H"); - ant_output_stream_flush(out); - } + ant_value_t this_obj = js_getthis(js); + if (!io_no_color) console_write_string(js, this_obj, false, "\033[2J\033[H", 7); return js_mkundef(); } -static struct { char *label; double start_time; } console_timers[64]; -static int console_timer_count = 0; - static ant_value_t js_console_time(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); const char *label = "default"; - if (nargs > 0 && vtype(args[0]) == T_STR) { - label = js_getstr(js, args[0], NULL); - } - for (int i = 0; i < console_timer_count; i++) { - if (strcmp(console_timers[i].label, label) == 0) { - ant_output_stream_t *out = ant_output_stream(stderr); - ant_output_stream_begin(out); - ant_output_stream_appendf(out, "Timer '%s' already exists\n", label); - ant_output_stream_flush(out); - return js_mkundef(); - }} - - if (console_timer_count < 64) { - console_timers[console_timer_count].label = strdup(label); - console_timers[console_timer_count].start_time = (double)uv_hrtime() / 1e6; - console_timer_count++; + if (nargs > 0 && vtype(args[0]) == T_STR) label = js_getstr(js, args[0], NULL); + ant_value_t timers = console_get_state_map(js, this_obj, "timers"); + if (is_special_object(timers) && vtype(js_get(js, timers, label)) != T_UNDEF) { + ant_value_t warn_args[1] = { js_mkstr(js, "Timer already exists", 20) }; + return console_emit_current(js, true, NULL, warn_args, 1); } + js_set(js, timers, label, js_mknum((double)uv_hrtime() / 1e6)); return js_mkundef(); } static ant_value_t js_console_timeEnd(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); + const char *label = "default"; + + if (nargs > 0 && vtype(args[0]) == T_STR) label = js_getstr(js, args[0], NULL); + ant_value_t timers = console_get_state_map(js, this_obj, "timers"); + ant_value_t start = is_special_object(timers) ? js_get(js, timers, label) : js_mkundef(); + + if (vtype(start) != T_NUM) { + ant_value_t warn_args[1] = { js_mkstr(js, "Timer does not exist", 19) }; + return console_emit_current(js, true, NULL, warn_args, 1); + } + + double elapsed = ((double)uv_hrtime() / 1e6) - js_getnum(start); + js_delete_prop(js, timers, label, strlen(label)); + char buf[256]; + + int len = snprintf(buf, sizeof(buf), "%s: %.3fms", label, elapsed); + ant_value_t out_args[1] = { js_mkstr(js, buf, (size_t)(len > 0 ? len : 0)) }; + + return console_emit_current(js, false, NULL, out_args, 1); +} + +static ant_value_t js_console_timeLog(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); const char *label = "default"; + int extra_start = 0; + if (nargs > 0 && vtype(args[0]) == T_STR) { label = js_getstr(js, args[0], NULL); + extra_start = 1; } - for (int i = 0; i < console_timer_count; i++) { - if (strcmp(console_timers[i].label, label) == 0) { - double elapsed = ((double)uv_hrtime() / 1e6) - console_timers[i].start_time; - ant_output_stream_t *out = ant_output_stream(stdout); - - ant_output_stream_begin(out); - ant_output_stream_appendf(out, "%s: %.3fms\n", label, elapsed); - ant_output_stream_flush(out); - free(console_timers[i].label); - - for (int j = i; j < console_timer_count - 1; j++) { - console_timers[j] = console_timers[j + 1]; - } - - console_timer_count--; - return js_mkundef(); - }} + ant_value_t timers = console_get_state_map(js, this_obj, "timers"); + ant_value_t start = is_special_object(timers) ? js_get(js, timers, label) : js_mkundef(); - { - ant_output_stream_t *out = ant_output_stream(stderr); - ant_output_stream_begin(out); - ant_output_stream_appendf(out, "Timer '%s' does not exist\n", label); - ant_output_stream_flush(out); + if (vtype(start) != T_NUM) { + ant_value_t warn_args[1] = { js_mkstr(js, "Timer does not exist", 19) }; + return console_emit_current(js, true, NULL, warn_args, 1); } + char buf[256]; + double elapsed = ((double)uv_hrtime() / 1e6) - js_getnum(start); + int len = snprintf(buf, sizeof(buf), "%s: %.3fms", label, elapsed); + + ant_value_t *out_args = malloc((size_t)(nargs - extra_start + 1) * sizeof(ant_value_t)); + if (!out_args) return js_mkerr(js, "Out of memory"); + + out_args[0] = js_mkstr(js, buf, (size_t)(len > 0 ? len : 0)); + for (int i = extra_start; i < nargs; i++) out_args[i - extra_start + 1] = args[i]; + ant_value_t result = console_emit_current(js, false, NULL, out_args, nargs - extra_start + 1); + free(out_args); + + return result; +} + +static ant_value_t js_console_count(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); + const char *label = "default"; + + if (nargs > 0 && vtype(args[0]) == T_STR) label = js_getstr(js, args[0], NULL); + ant_value_t counts = console_get_state_map(js, this_obj, "counts"); + ant_value_t current = is_special_object(counts) ? js_get(js, counts, label) : js_mkundef(); + + double next = vtype(current) == T_NUM ? js_getnum(current) + 1 : 1; + js_set(js, counts, label, js_mknum(next)); + + char buf[256]; + int len = snprintf(buf, sizeof(buf), "%s: %.0f", label, next); + ant_value_t out_args[1] = { js_mkstr(js, buf, (size_t)(len > 0 ? len : 0)) }; + + return console_emit_current(js, false, NULL, out_args, 1); +} + +static ant_value_t js_console_countReset(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); + const char *label = "default"; + if (nargs > 0 && vtype(args[0]) == T_STR) label = js_getstr(js, args[0], NULL); + ant_value_t counts = console_get_state_map(js, this_obj, "counts"); + js_delete_prop(js, counts, label, strlen(label)); + return js_mkundef(); +} + +static ant_value_t js_console_group(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); + if (nargs > 0) console_emit_current(js, false, NULL, args, nargs); + console_set_group_level(js, this_obj, console_get_group_level(js, this_obj) + 1); return js_mkundef(); } +static ant_value_t js_console_group_end(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); + console_set_group_level(js, this_obj, console_get_group_level(js, this_obj) - 1); + return js_mkundef(); +} + +static ant_value_t js_console_group_collapsed(ant_t *js, ant_value_t *args, int nargs) { + return js_console_group(js, args, nargs); +} + static const char *get_slot_name(internal_slot_t slot) { #define ANT_SLOT_NAME(name) [name] = &#name[5], static const char *slot_names[] = { @@ -682,7 +867,6 @@ void inspect_object(ant_t *js, ant_value_t obj, FILE *stream, int depth, inspect fprintf(stream, "<%s @%llu> {\n", type == T_FUNC ? "Function" : (type == T_PROMISE ? "Promise" : "Object"), (u64)obj_off); int inner_depth = depth + 1; - inspect_print_indent(stream, inner_depth); fprintf(stream, "[[Slots]]: {\n"); @@ -761,29 +945,104 @@ static ant_value_t js_console_inspect(ant_t *js, ant_value_t *args, int nargs) { if (i > 0) fprintf(stream, " "); inspect_value(js, args[i], stream, 0, &visited); } - + fprintf(stream, "\n"); if (visited.visited) free(visited.visited); - + return js_mkundef(); } -ant_value_t console_library(ant_t *js) { - ant_value_t console_obj = js_mkobj(js); - +// TODO: replace stub with real +static ant_value_t js_console_dir(ant_t *js, ant_value_t *args, int nargs) { + return js_console_log(js, args, nargs); +} + +// TODO: replace stub with real +static ant_value_t js_console_dirxml(ant_t *js, ant_value_t *args, int nargs) { + return js_console_log(js, args, nargs); +} + +// TODO: replace stub with real +static ant_value_t js_console_table(ant_t *js, ant_value_t *args, int nargs) { + return js_console_log(js, args, nargs); +} + +static void console_apply_methods(ant_t *js, ant_value_t console_obj) { js_set(js, console_obj, "log", js_mkfun(js_console_log)); js_set(js, console_obj, "error", js_mkfun(js_console_error)); js_set(js, console_obj, "warn", js_mkfun(js_console_warn)); js_set(js, console_obj, "info", js_mkfun(js_console_info)); js_set(js, console_obj, "debug", js_mkfun(js_console_debug)); js_set(js, console_obj, "assert", js_mkfun(js_console_assert)); + js_set(js, console_obj, "dir", js_mkfun(js_console_dir)); + js_set(js, console_obj, "dirxml", js_mkfun(js_console_dirxml)); + js_set(js, console_obj, "table", js_mkfun(js_console_table)); js_set(js, console_obj, "trace", js_mkfun(js_console_trace)); + js_set(js, console_obj, "count", js_mkfun(js_console_count)); + js_set(js, console_obj, "countReset", js_mkfun(js_console_countReset)); js_set(js, console_obj, "time", js_mkfun(js_console_time)); + js_set(js, console_obj, "timeLog", js_mkfun(js_console_timeLog)); js_set(js, console_obj, "timeEnd", js_mkfun(js_console_timeEnd)); + js_set(js, console_obj, "group", js_mkfun(js_console_group)); + js_set(js, console_obj, "groupCollapsed", js_mkfun(js_console_group_collapsed)); + js_set(js, console_obj, "groupEnd", js_mkfun(js_console_group_end)); js_set(js, console_obj, "clear", js_mkfun(js_console_clear)); js_set(js, console_obj, "inspect", js_mkfun(js_console_inspect)); +} + +static ant_value_t js_console_constructor(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t proto = js_instance_proto_from_new_target(js, g_console_proto); + ant_value_t console_obj = js_mkobj(js); + js_set_proto_init(console_obj, is_special_object(proto) ? proto : g_console_proto); + + ant_value_t stdout_obj = js_mkundef(); + ant_value_t stderr_obj = js_mkundef(); + int group_indentation = 2; + + if (nargs >= 1 && is_special_object(args[0]) && nargs == 1) { + ant_value_t options = args[0]; + stdout_obj = js_get(js, options, "stdout"); + stderr_obj = js_get(js, options, "stderr"); + ant_value_t gi = js_get(js, options, "groupIndentation"); + if (vtype(gi) == T_NUM) group_indentation = (int)js_getnum(gi); + } else { + if (nargs >= 1) stdout_obj = args[0]; + if (nargs >= 2) stderr_obj = args[1]; + } + + if (vtype(stderr_obj) == T_UNDEF) stderr_obj = stdout_obj; + js_set_slot_wb(js, console_obj, SLOT_CONSOLE_STDOUT, stdout_obj); + js_set_slot_wb(js, console_obj, SLOT_CONSOLE_STDERR, stderr_obj); + js_set_slot_wb(js, console_obj, SLOT_CONSOLE_COUNTS, js_mkobj(js)); + js_set_slot_wb(js, console_obj, SLOT_CONSOLE_TIMERS, js_mkobj(js)); + + js_set_slot(console_obj, SLOT_CONSOLE_GROUP_INDENT, js_mknum((double)group_indentation)); + js_set_slot(console_obj, SLOT_CONSOLE_GROUP_LEVEL, js_mknum(0)); + return console_obj; +} + +ant_value_t console_library(ant_t *js) { + if (!g_console_ctor) { + g_console_proto = js_mkobj(js); + console_apply_methods(js, g_console_proto); + js_set_sym(js, g_console_proto, get_toStringTag_sym(), js_mkstr(js, "console", 7)); + g_console_ctor = js_make_ctor(js, js_console_constructor, g_console_proto, "Console", 7); + gc_register_root(&g_console_proto); + gc_register_root(&g_console_ctor); + } + + ant_value_t console_obj = js_mkobj(js); + js_set_proto_init(console_obj, g_console_proto); + js_set_slot_wb(js, console_obj, SLOT_CONSOLE_COUNTS, js_mkobj(js)); + js_set_slot_wb(js, console_obj, SLOT_CONSOLE_TIMERS, js_mkobj(js)); + js_set_slot(console_obj, SLOT_CONSOLE_GROUP_INDENT, js_mknum(2)); + js_set_slot(console_obj, SLOT_CONSOLE_GROUP_LEVEL, js_mknum(0)); + + js_set(js, console_obj, "Console", g_console_ctor); + js_set(js, console_obj, "default", console_obj); js_set_sym(js, console_obj, get_toStringTag_sym(), js_mkstr(js, "console", 7)); + return console_obj; } diff --git a/src/modules/napi.c b/src/modules/napi.c index 904840c..91a802b 100644 --- a/src/modules/napi.c +++ b/src/modules/napi.c @@ -24,9 +24,20 @@ #include #include #include +#include #include #include +#if defined(__has_include) +#if __has_include() +#include +#else +typedef uint16_t char16_t; +#endif +#else +typedef uint16_t char16_t; +#endif + #include "ant.h" #include "descriptors.h" #include "errors.h" @@ -34,9 +45,11 @@ #include "silver/engine.h" #include "modules/buffer.h" +#include "modules/date.h" #include "modules/napi.h" #include "gc/roots.h" #include "gc/modules.h" +#include "utf8.h" typedef struct napi_cleanup_hook_entry { napi_cleanup_hook hook; @@ -51,6 +64,9 @@ typedef struct ant_napi_env__ { bool has_pending_exception; napi_value pending_exception; uint32_t version; + void *instance_data; + node_api_basic_finalize instance_data_finalize_cb; + void *instance_data_finalize_hint; napi_cleanup_hook_entry_t *cleanup_hooks; struct napi_ref__ *refs; struct napi_deferred__ *deferreds; @@ -128,6 +144,10 @@ typedef struct napi_wrap_entry { void *native_object; node_api_basic_finalize finalize_cb; void *finalize_hint; + void *attached_data; + node_api_basic_finalize attached_finalize_cb; + void *attached_finalize_hint; + bool has_wrap; UT_hash_handle hh; } napi_wrap_entry_t; @@ -175,6 +195,32 @@ typedef struct napi_native_lib { struct napi_native_lib *next; } napi_native_lib_t; +typedef enum { + napi_key_include_prototypes = 0, + napi_key_own_only = 1, +} napi_key_collection_mode; + +typedef enum { + napi_key_all_properties = 0, + napi_key_writable = 1 << 0, + napi_key_enumerable = 1 << 1, + napi_key_configurable = 1 << 2, + napi_key_skip_strings = 1 << 3, + napi_key_skip_symbols = 1 << 4, +} napi_key_filter; + +typedef enum { + napi_key_keep_numbers = 0, + napi_key_numbers_to_strings = 1, +} napi_key_conversion; + +typedef struct { + uint8_t sign; + uint8_t pad[3]; + uint32_t limb_count; + uint32_t limbs[]; +} napi_bigint_payload_t; + static ant_napi_env_t *g_napi_env = NULL; static napi_external_entry_t *g_napi_externals = NULL; static napi_wrap_entry_t *g_napi_wraps = NULL; @@ -220,7 +266,7 @@ switch (status) { default: return "unknown"; }} -static napi_status napi_set_last(napi_env env, napi_status status, const char *message) { +static napi_status napi_set_last_raw(napi_env env, napi_status status, const char *message) { ant_napi_env_t *nenv = (ant_napi_env_t *)env; if (!nenv) return status; @@ -238,6 +284,16 @@ static napi_status napi_set_last(napi_env env, napi_status status, const char *m return status; } +static napi_status napi_set_last(napi_env env, napi_status status, const char *message) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (status == napi_ok && nenv && ( + nenv->has_pending_exception + || (nenv->js && nenv->js->thrown_exists) + ) + ) return napi_set_last_raw(env, napi_pending_exception, "pending exception"); + return napi_set_last_raw(env, status, message); +} + static ant_napi_env_t *napi_get_or_create_env(ant_t *js) { if (!g_napi_env) { g_napi_env = (ant_napi_env_t *)calloc(1, sizeof(*g_napi_env)); @@ -297,6 +353,14 @@ static void napi_mark_pending_exception(napi_env env, napi_value exception) { napi_set_last(env, napi_pending_exception, "pending exception"); } +NAPI_EXTERN napi_status NAPI_CDECL napi_throw(napi_env env, napi_value error) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv || !nenv->js) return napi_set_last(env, napi_invalid_arg, "invalid env"); + js_throw(nenv->js, (ant_value_t)error); + napi_mark_pending_exception(env, error); + return napi_pending_exception; +} + static napi_status napi_check_pending_from_result(napi_env env, ant_value_t result) { ant_napi_env_t *nenv = (ant_napi_env_t *)env; if (!nenv || !nenv->js) return napi_set_last(env, napi_invalid_arg, "invalid env"); @@ -311,6 +375,23 @@ static napi_status napi_check_pending_from_result(napi_env env, ant_value_t resu return napi_set_last(env, napi_ok, NULL); } +static napi_status napi_return_pending_if_any(napi_env env) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv || !nenv->js) return napi_set_last(env, napi_invalid_arg, "invalid env"); + + if (nenv->has_pending_exception) { + napi_set_last(env, napi_pending_exception, "pending exception"); + return napi_pending_exception; + } + + if (nenv->js->thrown_exists) { + napi_mark_pending_exception(env, (napi_value)nenv->js->thrown_value); + return napi_pending_exception; + } + + return napi_set_last(env, napi_ok, NULL); +} + static bool napi_slot_get_u64(ant_t *js, ant_value_t obj, internal_slot_t slot, uint64_t *out) { ant_value_t value = js_get_slot(obj, slot); if (vtype(value) != T_NUM) return false; @@ -424,6 +505,10 @@ static ant_value_t napi_callback_trampoline(ant_t *js, ant_value_t *args, int na return js_throw(js, ex); } + if (js->thrown_exists) { + return js_throw(js, js->thrown_value); + } + if ((ant_value_t)ret == 0) return js_mkundef(); return (ant_value_t)ret; } @@ -465,6 +550,173 @@ static ant_value_t napi_make_string(ant_t *js, const char *s, size_t len) { return js_mkstr(js, s, len); } +static bool napi_checked_add_size(size_t a, size_t b, size_t *out) { + if (a > SIZE_MAX - b) return false; + *out = a + b; + return true; +} + +static bool napi_checked_mul_size(size_t a, size_t b, size_t *out) { + if (a != 0 && b > SIZE_MAX / a) return false; + *out = a * b; + return true; +} + +static bool napi_make_bigint_limbs( + ant_t *js, + const uint32_t *limbs, + size_t count, + bool negative, + ant_value_t *out +) { + uint32_t zero = 0; + if (!out) return false; + + if (!limbs || count == 0) { + limbs = &zero; + count = 1; + } + + while (count > 1 && limbs[count - 1] == 0) count--; + if (count == 1 && limbs[0] == 0) negative = false; + if (count > UINT32_MAX) return false; + + size_t limbs_bytes = 0; + if (!napi_checked_mul_size(count, sizeof(uint32_t), &limbs_bytes)) return false; + + size_t payload_size = 0; + if (!napi_checked_add_size(offsetof(napi_bigint_payload_t, limbs), limbs_bytes, &payload_size)) { + return false; + } + + napi_bigint_payload_t *payload = (napi_bigint_payload_t *)js_type_alloc( + js, ANT_ALLOC_BIGINT, payload_size, + _Alignof(napi_bigint_payload_t) + ); + + if (!payload) return false; + payload->sign = negative ? 1 : 0; + payload->pad[0] = 0; + payload->pad[1] = 0; + payload->pad[2] = 0; + payload->limb_count = (uint32_t)count; + memcpy(payload->limbs, limbs, limbs_bytes); + *out = mkval(T_BIGINT, (uint64_t)(uintptr_t)payload); + + return true; +} + +static const napi_bigint_payload_t *napi_bigint_payload(napi_value value) { + return (const napi_bigint_payload_t *)(uintptr_t)vdata((ant_value_t)value); +} + +static const uint32_t *napi_bigint_limbs(napi_value value, size_t *count) { + const napi_bigint_payload_t *payload = napi_bigint_payload(value); + if (!payload) { + if (count) *count = 0; + return NULL; + } + + size_t limb_count = payload->limb_count; + if (limb_count == 0) limb_count = 1; + + while (limb_count > 1 && payload->limbs[limb_count - 1] == 0) limb_count--; + if (count) *count = limb_count; + + return payload->limbs; +} + +static bool napi_bigint_is_negative(napi_value value) { + const napi_bigint_payload_t *payload = napi_bigint_payload(value); + return payload && payload->sign == 1; +} + +static bool napi_bigint_limbs_is_zero(const uint32_t *limbs, size_t count) { + return count <= 1 && (!limbs || limbs[0] == 0); +} + +static uint64_t napi_bigint_low_u64(const uint32_t *limbs, size_t count) { + uint64_t out = 0; + if (count > 0 && limbs) out |= (uint64_t)limbs[0]; + if (count > 1 && limbs) out |= ((uint64_t)limbs[1] << 32); + return out; +} + +static bool napi_parse_index_key(const char *str, size_t len, uint32_t *out) { + if (!str || len == 0) return false; + if (len > 1 && str[0] == '0') return false; + + uint64_t acc = 0; + for (size_t i = 0; i < len; i++) { + if (str[i] < '0' || str[i] > '9') return false; + acc = (acc * 10) + (uint64_t)(str[i] - '0'); + if (acc > UINT32_MAX) return false; + } + + if (out) *out = (uint32_t)acc; + return true; +} + +static bool napi_seen_has_key(ant_t *js, ant_value_t seen, ant_value_t key) { + if (vtype(key) == T_SYMBOL) { + return lkp_sym(js, seen, (ant_offset_t)vdata(key)) != 0; + } + + size_t len = 0; + const char *str = js_getstr(js, key, &len); + return str && lkp(js, seen, str, len) != 0; +} + +static bool napi_seen_add_key(ant_t *js, ant_value_t seen, ant_value_t key) { + ant_value_t res = js_setprop(js, seen, key, js_true); + return !is_err(res); +} + +static bool napi_key_passes_filter(const ant_shape_prop_t *prop, napi_key_filter key_filter) { + if (!prop) return false; + if ((key_filter & napi_key_writable) && !(prop->attrs & ANT_PROP_ATTR_WRITABLE)) return false; + if ((key_filter & napi_key_enumerable) && !(prop->attrs & ANT_PROP_ATTR_ENUMERABLE)) return false; + if ((key_filter & napi_key_configurable) && !(prop->attrs & ANT_PROP_ATTR_CONFIGURABLE)) return false; + return true; +} + +static ant_value_t napi_convert_property_key( + ant_t *js, + ant_value_t key, + napi_key_conversion key_conversion +) { + if (key_conversion != napi_key_keep_numbers || vtype(key) != T_STR) return key; + + size_t len = 0; + const char *str = js_getstr(js, key, &len); + uint32_t idx = 0; + if (!str || !napi_parse_index_key(str, len, &idx)) return key; + return js_mknum((double)idx); +} + +static napi_status napi_create_date_common(napi_env env, double time, napi_value *result) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv || !nenv->js || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); + + ant_t *js = nenv->js; + ant_value_t ctor = js_get(js, js_glob(js), "Date"); + if (!is_callable(ctor)) return napi_set_last(env, napi_generic_failure, "Date constructor unavailable"); + + ant_value_t obj = js_mkobj(js); + ant_value_t proto = js_get(js, ctor, "prototype"); + if (is_object_type(proto)) js_set_proto_init(obj, proto); + + ant_value_t argv[1] = {js_mknum(time)}; + ant_value_t saved = js->new_target; + js->new_target = ctor; + ant_value_t out = sv_vm_call(js->vm, js, ctor, obj, argv, 1, NULL, true); + js->new_target = saved; + + if (is_err(out) || js->thrown_exists) return napi_check_pending_from_result(env, out); + *result = NAPI_RETURN(nenv, is_object_type(out) ? out : obj); + return napi_set_last(env, napi_ok, NULL); +} + static napi_status napi_make_error_object( napi_env env, const char *name, @@ -507,7 +759,8 @@ static napi_status napi_throw_with_message( napi_mark_pending_exception(env, (napi_value)nenv->js->thrown_value); return napi_pending_exception; } - return napi_set_last(env, napi_generic_failure, "failed to throw"); + if (is_err(error)) return napi_set_last(env, napi_generic_failure, "failed to create error"); + return napi_throw(env, (napi_value)error); } static void napi_tsfn_maybe_finish(struct napi_threadsafe_function__ *tsfn); @@ -599,29 +852,26 @@ static ant_value_t napi_dlopen_common(ant_t *js, ant_value_t module_obj, const c if (!is_object_type(module_obj)) return js_mkerr(js, "process.dlopen module must be an object"); if (!filename || !filename[0]) return js_mkerr(js, "process.dlopen filename must be a non-empty string"); + g_pending_napi_module = NULL; void *handle = NAPI_DLOPEN(filename, NAPI_RTLD_NOW | NAPI_RTLD_GLOBAL | NAPI_RTLD_LOCAL); if (!handle) { const char *msg = NAPI_DLERROR(); return js_mkerr(js, "Failed to load native module '%s': %s", filename, msg ? msg : "unknown"); } - g_pending_napi_module = NULL; napi_register_module_v1_fn reg_fn = (napi_register_module_v1_fn)NAPI_DLSYM(handle, "napi_register_module_v1"); - ant_value_t exports = js_get(js, module_obj, "exports"); + if (!is_object_type(exports)) { exports = js_mkobj(js); js_set(js, module_obj, "exports", exports); } ant_value_t ret = js_mkundef(); - if (reg_fn) { - ret = (ant_value_t)reg_fn(env, (napi_value)exports); - } else if (g_pending_napi_module && g_pending_napi_module->nm_register_func) { + if (reg_fn) ret = (ant_value_t)reg_fn(env, (napi_value)exports); + else if (g_pending_napi_module && g_pending_napi_module->nm_register_func) { ret = (ant_value_t)g_pending_napi_module->nm_register_func(env, (napi_value)exports); - } else { - return js_mkerr(js, "No N-API registration entrypoint found in '%s'", filename); - } + } else return js_mkerr(js, "No N-API registration entrypoint found in '%s'", filename); ant_napi_env_t *nenv = (ant_napi_env_t *)env; if (nenv->has_pending_exception || js->thrown_exists) { @@ -765,7 +1015,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_array_with_length( js_mkstr(nenv->js, "length", 6), js_mknum((double)length) ); - if (is_err(r)) return napi_check_pending_from_result(env, r); + if (is_err(r) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, r); *result = NAPI_RETURN(nenv, arr); return napi_set_last(env, napi_ok, NULL); } @@ -818,6 +1068,99 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf8( return napi_set_last(env, napi_ok, NULL); } +NAPI_EXTERN napi_status NAPI_CDECL napi_create_date( + napi_env env, + double time, + napi_value *result +) { + return napi_create_date_common(env, time, result); +} + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_bigint_int64( + napi_env env, + int64_t value, + napi_value *result +) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv || !nenv->js || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); + + uint64_t magnitude = value < 0 + ? (uint64_t)(-(value + 1)) + 1 + : (uint64_t)value; + + uint32_t limbs[2] = { + (uint32_t)(magnitude & 0xffffffffu), + (uint32_t)(magnitude >> 32) + }; + + size_t count = limbs[1] == 0 ? 1 : 2; + ant_value_t out = js_mkundef(); + + if (!napi_make_bigint_limbs(nenv->js, limbs, count, value < 0, &out)) { + return napi_set_last(env, napi_generic_failure, "out of memory"); + } + + *result = NAPI_RETURN(nenv, out); + return napi_set_last(env, napi_ok, NULL); +} + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_bigint_uint64( + napi_env env, + uint64_t value, + napi_value *result +) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv || !nenv->js || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); + + uint32_t limbs[2] = { + (uint32_t)(value & 0xffffffffu), + (uint32_t)(value >> 32) + }; + size_t count = limbs[1] == 0 ? 1 : 2; + + ant_value_t out = js_mkundef(); + if (!napi_make_bigint_limbs(nenv->js, limbs, count, false, &out)) { + return napi_set_last(env, napi_generic_failure, "out of memory"); + } + + *result = NAPI_RETURN(nenv, out); + return napi_set_last(env, napi_ok, NULL); +} + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_bigint_words( + napi_env env, + int sign_bit, + size_t word_count, + const uint64_t *words, + napi_value *result +) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv || !nenv->js || !result || (word_count > 0 && !words)) { + return napi_set_last(env, napi_invalid_arg, "invalid argument"); + } + + size_t limb_count = 1; + if (word_count > 0 && !napi_checked_mul_size(word_count, 2, &limb_count)) { + return napi_set_last(env, napi_invalid_arg, "word count overflow"); + } + uint32_t *limbs = (uint32_t *)calloc(limb_count, sizeof(uint32_t)); + if (!limbs) return napi_set_last(env, napi_generic_failure, "out of memory"); + + if (word_count == 0) limbs[0] = 0; + else for (size_t i = 0; i < word_count; i++) { + limbs[i * 2] = (uint32_t)(words[i] & 0xffffffffu); + limbs[(i * 2) + 1] = (uint32_t)(words[i] >> 32); + } + + ant_value_t out = js_mkundef(); + bool ok = napi_make_bigint_limbs(nenv->js, limbs, limb_count, sign_bit != 0, &out); + free(limbs); + if (!ok) return napi_set_last(env, napi_generic_failure, "out of memory"); + + *result = NAPI_RETURN(nenv, out); + return napi_set_last(env, napi_ok, NULL); +} + NAPI_EXTERN napi_status NAPI_CDECL napi_create_symbol( napi_env env, napi_value description, @@ -831,7 +1174,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_symbol( desc = js_getstr(nenv->js, (ant_value_t)description, NULL); } else if (!is_undefined((ant_value_t)description) && !is_null((ant_value_t)description)) { ant_value_t s = coerce_to_str(nenv->js, (ant_value_t)description); - if (is_err(s)) return napi_check_pending_from_result(env, s); + if (is_err(s) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, s); desc = js_getstr(nenv->js, s, NULL); } @@ -856,7 +1199,6 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_error( napi_value msg, napi_value *result ) { - (void)code; return napi_make_error_object(env, "Error", msg, result); } @@ -866,7 +1208,6 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_type_error( napi_value msg, napi_value *result ) { - (void)code; return napi_make_error_object(env, "TypeError", msg, result); } @@ -876,7 +1217,6 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_range_error( napi_value msg, napi_value *result ) { - (void)code; return napi_make_error_object(env, "RangeError", msg, result); } @@ -1066,6 +1406,32 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_external_arraybuffer( return napi_set_last(env, napi_ok, NULL); } +NAPI_EXTERN napi_status NAPI_CDECL napi_set_instance_data( + napi_env env, + void *data, + node_api_basic_finalize finalize_cb, + void *finalize_hint +) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv) return napi_set_last(env, napi_invalid_arg, "invalid env"); + + nenv->instance_data = data; + nenv->instance_data_finalize_cb = finalize_cb; + nenv->instance_data_finalize_hint = finalize_hint; + return napi_set_last(env, napi_ok, NULL); +} + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_instance_data( + napi_env env, + void **data +) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv || !data) return napi_set_last(env, napi_invalid_arg, "invalid argument"); + + *data = nenv->instance_data; + return napi_set_last(env, napi_ok, NULL); +} + NAPI_EXTERN napi_status NAPI_CDECL napi_create_typedarray( napi_env env, napi_typedarray_type type, @@ -1310,6 +1676,117 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_latin1( return napi_get_string_common(env, value, buf, bufsize, result); } +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf16( + napi_env env, + napi_value value, + char16_t *buf, + size_t bufsize, + size_t *result +) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv || !nenv->js) return napi_set_last(env, napi_invalid_arg, "invalid env"); + if (vtype((ant_value_t)value) != T_STR) return napi_set_last(env, napi_string_expected, "string expected"); + + size_t byte_len = 0; + const char *str = js_getstr(nenv->js, (ant_value_t)value, &byte_len); + if (!str) return napi_set_last(env, napi_string_expected, "string expected"); + + size_t utf16_len = utf16_strlen(str, byte_len); + if (result) *result = utf16_len; + if (!buf || bufsize == 0) return napi_set_last(env, napi_ok, NULL); + + size_t n = utf16_len < (bufsize - 1) ? utf16_len : (bufsize - 1); + for (size_t i = 0; i < n; i++) buf[i] = (char16_t)utf16_code_unit_at(str, byte_len, i); + buf[n] = 0; + return napi_set_last(env, napi_ok, NULL); +} + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_date_value( + napi_env env, + napi_value value, + double *result +) { + if (!env || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); + if (!is_date_instance((ant_value_t)value)) return napi_set_last(env, napi_date_expected, "date expected"); + + ant_value_t time_val = js_get_slot((ant_value_t)value, SLOT_DATA); + *result = vtype(time_val) == T_NUM ? js_getnum(time_val) : JS_NAN; + return napi_set_last(env, napi_ok, NULL); +} + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_int64( + napi_env env, + napi_value value, + int64_t *result, + bool *lossless +) { + if (!env || !result || !lossless) return napi_set_last(env, napi_invalid_arg, "invalid argument"); + if (vtype((ant_value_t)value) != T_BIGINT) return napi_set_last(env, napi_bigint_expected, "bigint expected"); + + size_t limb_count = 0; + const uint32_t *limbs = napi_bigint_limbs(value, &limb_count); + uint64_t magnitude = napi_bigint_low_u64(limbs, limb_count); + bool negative = napi_bigint_is_negative(value) && !napi_bigint_limbs_is_zero(limbs, limb_count); + uint64_t bits = negative ? (uint64_t)(~magnitude + 1) : magnitude; + + *result = (int64_t)bits; + *lossless = limb_count <= 2 + && ((!negative && magnitude <= (uint64_t)INT64_MAX) + || (negative && magnitude <= (UINT64_C(1) << 63))); + return napi_set_last(env, napi_ok, NULL); +} + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_uint64( + napi_env env, + napi_value value, + uint64_t *result, + bool *lossless +) { + if (!env || !result || !lossless) return napi_set_last(env, napi_invalid_arg, "invalid argument"); + if (vtype((ant_value_t)value) != T_BIGINT) return napi_set_last(env, napi_bigint_expected, "bigint expected"); + + size_t limb_count = 0; + const uint32_t *limbs = napi_bigint_limbs(value, &limb_count); + uint64_t magnitude = napi_bigint_low_u64(limbs, limb_count); + bool negative = napi_bigint_is_negative(value) && !napi_bigint_limbs_is_zero(limbs, limb_count); + + *result = negative ? (uint64_t)(~magnitude + 1) : magnitude; + *lossless = !negative && limb_count <= 2; + return napi_set_last(env, napi_ok, NULL); +} + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_words( + napi_env env, + napi_value value, + int *sign_bit, + size_t *word_count, + uint64_t *words +) { + if (!env || !word_count) return napi_set_last(env, napi_invalid_arg, "invalid argument"); + if (vtype((ant_value_t)value) != T_BIGINT) return napi_set_last(env, napi_bigint_expected, "bigint expected"); + + size_t limb_count = 0; + const uint32_t *limbs = napi_bigint_limbs(value, &limb_count); + size_t actual_words = limb_count == 0 ? 0 : (limb_count + 1) / 2; + size_t capacity = words ? *word_count : 0; + + if (sign_bit) { + bool negative = napi_bigint_is_negative(value) && !napi_bigint_limbs_is_zero(limbs, limb_count); + *sign_bit = negative ? 1 : 0; + } + + if (words) { + size_t n = capacity < actual_words ? capacity : actual_words; + for (size_t i = 0; i < n; i++) { + uint64_t lo = i * 2 < limb_count ? (uint64_t)limbs[i * 2] : 0; + uint64_t hi = (i * 2 + 1) < limb_count ? (uint64_t)limbs[i * 2 + 1] : 0; + words[i] = lo | (hi << 32); + }} + + *word_count = actual_words; + return napi_set_last(env, napi_ok, NULL); +} + NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external( napi_env env, napi_value value, @@ -1377,6 +1854,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_array_length( } ant_value_t len = js_get(nenv->js, v, "length"); + if (is_err(len) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, len); if (vtype(len) != T_NUM) return napi_set_last(env, napi_array_expected, "array expected"); *result = (uint32_t)js_getnum(len); return napi_set_last(env, napi_ok, NULL); @@ -1439,7 +1917,11 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_typedarray_info( if (type) *type = napi_from_ant_typedarray_type(ta->type); if (length) *length = ta->length; if (data) *data = ta->buffer->data + ta->byte_offset; - if (arraybuffer) *arraybuffer = (napi_value)js_get(nenv->js, (ant_value_t)typedarray, "buffer"); + if (arraybuffer) { + ant_value_t buffer = js_get(nenv->js, (ant_value_t)typedarray, "buffer"); + if (is_err(buffer) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, buffer); + *arraybuffer = NAPI_RETURN(nenv, buffer); + } if (byte_offset) *byte_offset = ta->byte_offset; return napi_set_last(env, napi_ok, NULL); } @@ -1451,7 +1933,9 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_prototype( ) { ant_napi_env_t *nenv = (ant_napi_env_t *)env; if (!nenv || !nenv->js || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); - *result = NAPI_RETURN(nenv, js_get_proto(nenv->js, (ant_value_t)object)); + ant_value_t proto = js_get_proto(nenv->js, (ant_value_t)object); + if (is_err(proto) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, proto); + *result = NAPI_RETURN(nenv, proto); return napi_set_last(env, napi_ok, NULL); } @@ -1479,6 +1963,60 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_property_names( return napi_set_last(env, napi_ok, NULL); } +NAPI_EXTERN napi_status NAPI_CDECL napi_get_all_property_names( + napi_env env, + napi_value object, + napi_key_collection_mode key_mode, + napi_key_filter key_filter, + napi_key_conversion key_conversion, + napi_value *result +) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv || !nenv->js || !result || !is_object_type((ant_value_t)object)) { + return napi_set_last(env, napi_invalid_arg, "invalid argument"); + } + + ant_t *js = nenv->js; + ant_value_t out = js_mkarr(js); + ant_value_t seen = js_mkobj(js); + ant_value_t current = (ant_value_t)object; + + while (is_object_type(current)) { + ant_iter_t iter = js_prop_iter_begin(js, current); + ant_value_t key = js_mkundef(); + + while (js_prop_iter_next_val(&iter, &key, NULL)) { + ant_object_t *obj_ptr = js_obj_ptr(js_as_obj(current)); + if (!obj_ptr || !obj_ptr->shape || iter.off == 0) continue; + + const ant_shape_prop_t *prop = ant_shape_prop_at(obj_ptr->shape, (uint32_t)(iter.off - 1)); + if (!napi_key_passes_filter(prop, key_filter)) continue; + + uint8_t key_type = vtype(key); + if ((key_filter & napi_key_skip_strings) && key_type != T_SYMBOL) continue; + if ((key_filter & napi_key_skip_symbols) && key_type == T_SYMBOL) continue; + if (napi_seen_has_key(js, seen, key)) continue; + if (!napi_seen_add_key(js, seen, key)) { + js_prop_iter_end(&iter); + return napi_set_last(env, napi_generic_failure, "failed to collect property names"); + } + + js_arr_push(js, out, napi_convert_property_key(js, key, key_conversion)); + if (js->thrown_exists) { + js_prop_iter_end(&iter); + return napi_check_pending_from_result(env, js_mkundef()); + }} + + js_prop_iter_end(&iter); + if (key_mode == napi_key_own_only) break; + current = js_get_proto(js, current); + if (is_err(current) || js->thrown_exists) return napi_check_pending_from_result(env, current); + } + + *result = NAPI_RETURN(nenv, out); + return napi_set_last(env, napi_ok, NULL); +} + NAPI_EXTERN napi_status NAPI_CDECL napi_get_version( node_api_basic_env env, uint32_t *result @@ -1508,7 +2046,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_set_property( return napi_set_last(env, napi_invalid_arg, "invalid argument"); } ant_value_t r = js_setprop(nenv->js, (ant_value_t)object, (ant_value_t)key, (ant_value_t)value); - if (is_err(r)) return napi_check_pending_from_result(env, r); + if (is_err(r) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, r); return napi_set_last(env, napi_ok, NULL); } @@ -1526,7 +2064,9 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_property( ant_value_t k = (ant_value_t)key; if (vtype(k) == T_SYMBOL) { ant_offset_t off = lkp_sym_proto(nenv->js, (ant_value_t)object, (ant_offset_t)vdata(k)); - *result = NAPI_RETURN(nenv, (off ? js_propref_load(nenv->js, off) : js_mkundef())); + ant_value_t out = off ? js_propref_load(nenv->js, off) : js_mkundef(); + if (is_err(out) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, out); + *result = NAPI_RETURN(nenv, out); return napi_set_last(env, napi_ok, NULL); } @@ -1542,9 +2082,10 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_property( memcpy(name, ks, klen); name[klen] = '\0'; - *result = NAPI_RETURN(nenv, js_getprop_fallback(nenv->js, (ant_value_t)object, name)); + ant_value_t out = js_getprop_fallback(nenv->js, (ant_value_t)object, name); free(name); - + if (is_err(out) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, out); + *result = NAPI_RETURN(nenv, out); return napi_set_last(env, napi_ok, NULL); } @@ -1566,7 +2107,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_has_property( } ant_value_t kstr = coerce_to_str(nenv->js, k); - if (is_err(kstr)) return napi_check_pending_from_result(env, kstr); + if (is_err(kstr) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, kstr); size_t len = 0; const char *s = js_getstr(nenv->js, kstr, &len); *result = s && lkp_proto(nenv->js, (ant_value_t)object, s, len) != 0; @@ -1591,13 +2132,13 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_delete_property( del_result = js_delete_sym_prop(nenv->js, (ant_value_t)object, k); } else { ant_value_t kstr = coerce_to_str(nenv->js, k); - if (is_err(kstr)) return napi_check_pending_from_result(env, kstr); + if (is_err(kstr) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, kstr); size_t len = 0; const char *s = js_getstr(nenv->js, kstr, &len); del_result = js_delete_prop(nenv->js, (ant_value_t)object, s, len); } - if (is_err(del_result)) return napi_check_pending_from_result(env, del_result); + if (is_err(del_result) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, del_result); *result = js_truthy(nenv->js, del_result); return napi_set_last(env, napi_ok, NULL); } @@ -1613,7 +2154,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_set_named_property( return napi_set_last(env, napi_invalid_arg, "invalid argument"); } js_set(nenv->js, (ant_value_t)object, utf8name, (ant_value_t)value); - return napi_set_last(env, napi_ok, NULL); + return napi_return_pending_if_any(env); } NAPI_EXTERN napi_status NAPI_CDECL napi_get_named_property( @@ -1626,7 +2167,9 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_named_property( if (!nenv || !nenv->js || !result || !utf8name || !is_object_type((ant_value_t)object)) { return napi_set_last(env, napi_invalid_arg, "invalid argument"); } - *result = (napi_value)js_getprop_fallback(nenv->js, (ant_value_t)object, utf8name); + ant_value_t out = js_getprop_fallback(nenv->js, (ant_value_t)object, utf8name); + if (is_err(out) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, out); + *result = NAPI_RETURN(nenv, out); return napi_set_last(env, napi_ok, NULL); } @@ -1662,7 +2205,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_set_element( nenv->js, (ant_value_t)object, key, (ant_value_t)value ); - if (is_err(r)) return napi_check_pending_from_result(env, r); + if (is_err(r) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, r); return napi_set_last(env, napi_ok, NULL); } @@ -1678,7 +2221,9 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_element( } char idx[32]; snprintf(idx, sizeof(idx), "%u", index); - *result = NAPI_RETURN(nenv, js_get(nenv->js, (ant_value_t)object, idx)); + ant_value_t out = js_get(nenv->js, (ant_value_t)object, idx); + if (is_err(out) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, out); + *result = NAPI_RETURN(nenv, out); return napi_set_last(env, napi_ok, NULL); } @@ -1711,7 +2256,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_delete_element( char idx[32]; snprintf(idx, sizeof(idx), "%u", index); ant_value_t del = js_delete_prop(nenv->js, (ant_value_t)object, idx, strlen(idx)); - if (is_err(del)) return napi_check_pending_from_result(env, del); + if (is_err(del) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, del); *result = js_truthy(nenv->js, del); return napi_set_last(env, napi_ok, NULL); } @@ -1749,22 +2294,21 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_define_properties( if (p->method) { napi_value fn = 0; napi_status st = napi_create_function_common( - env, - p->utf8name, + env, p->utf8name, NAPI_AUTO_LENGTH, - p->method, - p->data, - &fn + p->method, p->data, &fn ); if (st != napi_ok) return st; value = (ant_value_t)fn; } else if (p->getter || p->setter) { napi_value getter_fn = 0; napi_value setter_fn = 0; + if (p->getter) { napi_status st = napi_create_function_common(env, p->utf8name, NAPI_AUTO_LENGTH, p->getter, p->data, &getter_fn); if (st != napi_ok) return st; } + if (p->setter) { napi_status st = napi_create_function_common(env, p->utf8name, NAPI_AUTO_LENGTH, p->setter, p->data, &setter_fn); if (st != napi_ok) return st; @@ -1772,31 +2316,29 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_define_properties( int flags = napi_desc_flags(p->attributes); ant_value_t desc_obj = js_as_obj((ant_value_t)object); + if (is_symbol) { if (p->getter) js_set_sym_getter_desc(nenv->js, desc_obj, key, (ant_value_t)getter_fn, flags); if (p->setter) js_set_sym_setter_desc(nenv->js, desc_obj, key, (ant_value_t)setter_fn, flags); - } else { - js_set_accessor_desc( - nenv->js, - desc_obj, - key_str, - key_len, - p->getter ? (ant_value_t)getter_fn : js_mkundef(), - p->setter ? (ant_value_t)setter_fn : js_mkundef(), - flags - ); - } + } else js_set_accessor_desc( + nenv->js, desc_obj, + key_str, key_len, + p->getter ? (ant_value_t)getter_fn : js_mkundef(), + p->setter ? (ant_value_t)setter_fn : js_mkundef(), + flags + ); + + if (nenv->js->thrown_exists) return napi_check_pending_from_result(env, js_mkundef()); continue; - } else { - value = (ant_value_t)p->value; - } + } else value = (ant_value_t)p->value; - if (is_symbol) { - js_set_sym(nenv->js, (ant_value_t)object, key, value); - } else { + if (is_symbol) js_set_sym(nenv->js, (ant_value_t)object, key, value); + else { js_set(nenv->js, (ant_value_t)object, key_str, value); js_set_descriptor(nenv->js, js_as_obj((ant_value_t)object), key_str, key_len, napi_desc_flags(p->attributes)); } + + if (nenv->js->thrown_exists) return napi_check_pending_from_result(env, js_mkundef()); } return napi_set_last(env, napi_ok, NULL); @@ -1826,9 +2368,11 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_define_class( js_mark_constructor(ctor, true); ant_value_t proto = js_get(nenv->js, ctor, "prototype"); + if (is_err(proto) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, proto); if (!is_object_type(proto)) { proto = js_mkobj(nenv->js); js_set(nenv->js, ctor, "prototype", proto); + if (nenv->js->thrown_exists) return napi_check_pending_from_result(env, js_mkundef()); } for (size_t i = 0; i < property_count; i++) { @@ -1860,7 +2404,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_has_own_property( } ant_value_t kstr = coerce_to_str(nenv->js, k); - if (is_err(kstr)) return napi_check_pending_from_result(env, kstr); + if (is_err(kstr) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, kstr); size_t len = 0; const char *s = js_getstr(nenv->js, kstr, &len); *result = s && lkp(nenv->js, (ant_value_t)object, s, len) != 0; @@ -1909,6 +2453,16 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_is_array( return napi_set_last(env, napi_ok, NULL); } +NAPI_EXTERN napi_status NAPI_CDECL napi_is_date( + napi_env env, + napi_value value, + bool *result +) { + if (!env || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); + *result = is_date_instance((ant_value_t)value); + return napi_set_last(env, napi_ok, NULL); +} + NAPI_EXTERN napi_status NAPI_CDECL napi_is_arraybuffer( napi_env env, napi_value value, @@ -2004,13 +2558,11 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_instanceof( ant_napi_env_t *nenv = (ant_napi_env_t *)env; if (!nenv || !nenv->js || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); ant_value_t r = do_instanceof(nenv->js, (ant_value_t)object, (ant_value_t)constructor); - if (is_err(r)) return napi_check_pending_from_result(env, r); + if (is_err(r) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, r); *result = js_truthy(nenv->js, r); return napi_set_last(env, napi_ok, NULL); } -static _Thread_local int napi_strict_eq_call_count = 0; - NAPI_EXTERN napi_status NAPI_CDECL napi_strict_equals( napi_env env, napi_value lhs, @@ -2020,16 +2572,6 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_strict_equals( ant_napi_env_t *nenv = (ant_napi_env_t *)env; if (!nenv || !nenv->js || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); *result = strict_eq_values(nenv->js, (ant_value_t)lhs, (ant_value_t)rhs); - if (getenv("ANT_NAPI_TRACE")) { - napi_strict_eq_call_count++; - if (napi_strict_eq_call_count >= 2000 && napi_strict_eq_call_count < 2030) { - fprintf(stderr, "[napi-seq] call#%d lhs=0x%llx(vt=%d) rhs=0x%llx(vt=%d) => %d\n", - napi_strict_eq_call_count, - (unsigned long long)(ant_value_t)lhs, vtype((ant_value_t)lhs), - (unsigned long long)(ant_value_t)rhs, vtype((ant_value_t)rhs), - *result); - } - } return napi_set_last(env, napi_ok, NULL); } @@ -2105,7 +2647,9 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_bool( ) { ant_napi_env_t *nenv = (ant_napi_env_t *)env; if (!nenv || !nenv->js || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); - *result = NAPI_RETURN(nenv, js_bool(js_truthy(nenv->js, (ant_value_t)value))); + bool truthy = js_truthy(nenv->js, (ant_value_t)value); + if (nenv->js->thrown_exists) return napi_check_pending_from_result(env, js_mkundef()); + *result = NAPI_RETURN(nenv, js_bool(truthy)); return napi_set_last(env, napi_ok, NULL); } @@ -2116,7 +2660,9 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_number( ) { ant_napi_env_t *nenv = (ant_napi_env_t *)env; if (!nenv || !nenv->js || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); - *result = NAPI_RETURN(nenv, js_mknum(js_to_number(nenv->js, (ant_value_t)value))); + double num = js_to_number(nenv->js, (ant_value_t)value); + if (nenv->js->thrown_exists) return napi_check_pending_from_result(env, js_mkundef()); + *result = NAPI_RETURN(nenv, js_mknum(num)); return napi_set_last(env, napi_ok, NULL); } @@ -2134,6 +2680,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_object( } ant_value_t obj_ctor = js_get(nenv->js, js_glob(nenv->js), "Object"); + if (is_err(obj_ctor) || nenv->js->thrown_exists) return napi_check_pending_from_result(env, obj_ctor); if (!is_callable(obj_ctor)) return napi_set_last(env, napi_generic_failure, "Object constructor missing"); ant_value_t arg = (ant_value_t)value; ant_value_t out = sv_vm_call(nenv->js->vm, nenv->js, obj_ctor, js_mkundef(), &arg, 1, NULL, false); @@ -2155,14 +2702,6 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_string( return napi_set_last(env, napi_ok, NULL); } -NAPI_EXTERN napi_status NAPI_CDECL napi_throw(napi_env env, napi_value error) { - ant_napi_env_t *nenv = (ant_napi_env_t *)env; - if (!nenv || !nenv->js) return napi_set_last(env, napi_invalid_arg, "invalid env"); - js_throw(nenv->js, (ant_value_t)error); - napi_mark_pending_exception(env, error); - return napi_pending_exception; -} - NAPI_EXTERN napi_status NAPI_CDECL napi_throw_error( napi_env env, const char *code, @@ -2197,7 +2736,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_is_exception_pending( ant_napi_env_t *nenv = (ant_napi_env_t *)env; if (!nenv || !nenv->js || !result) return napi_set_last(env, napi_invalid_arg, "invalid argument"); *result = nenv->has_pending_exception || nenv->js->thrown_exists; - return napi_set_last(env, napi_ok, NULL); + return napi_set_last_raw(env, napi_ok, NULL); } NAPI_EXTERN napi_status NAPI_CDECL napi_get_and_clear_last_exception( @@ -2269,6 +2808,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_wrap( entry->native_object = native_object; entry->finalize_cb = finalize_cb; entry->finalize_hint = finalize_hint; + entry->has_wrap = true; if (result) { return napi_create_reference(env, js_object, 0, result); @@ -2276,6 +2816,36 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_wrap( return napi_set_last(env, napi_ok, NULL); } +NAPI_EXTERN napi_status NAPI_CDECL napi_add_finalizer( + napi_env env, + napi_value js_object, + void *finalize_data, + node_api_basic_finalize finalize_cb, + void *finalize_hint, + napi_ref *result +) { + ant_napi_env_t *nenv = (ant_napi_env_t *)env; + if (!nenv || !nenv->js || !is_object_type((ant_value_t)js_object)) { + return napi_set_last(env, napi_invalid_arg, "invalid argument"); + } + + napi_wrap_entry_t *entry = napi_find_wrap(nenv->js, js_object); + if (!entry) { + entry = (napi_wrap_entry_t *)calloc(1, sizeof(*entry)); + if (!entry) return napi_set_last(env, napi_generic_failure, "out of memory"); + entry->id = g_napi_wrap_next_id++; + HASH_ADD(hh, g_napi_wraps, id, sizeof(entry->id), entry); + napi_slot_set_u64(nenv->js, (ant_value_t)js_object, SLOT_NAPI_WRAP_ID, entry->id); + } + + entry->attached_data = finalize_data; + entry->attached_finalize_cb = finalize_cb; + entry->attached_finalize_hint = finalize_hint; + + if (result) return napi_create_reference(env, js_object, 0, result); + return napi_set_last(env, napi_ok, NULL); +} + NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap( napi_env env, napi_value js_object, @@ -2286,7 +2856,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap( return napi_set_last(env, napi_invalid_arg, "invalid argument"); } napi_wrap_entry_t *entry = napi_find_wrap(nenv->js, js_object); - if (!entry) return napi_set_last(env, napi_invalid_arg, "object not wrapped"); + if (!entry || !entry->has_wrap) return napi_set_last(env, napi_invalid_arg, "object not wrapped"); *result = entry->native_object; return napi_set_last(env, napi_ok, NULL); } @@ -2302,12 +2872,19 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_remove_wrap( } napi_wrap_entry_t *entry = napi_find_wrap(nenv->js, js_object); - if (!entry) return napi_set_last(env, napi_invalid_arg, "object not wrapped"); + if (!entry || !entry->has_wrap) return napi_set_last(env, napi_invalid_arg, "object not wrapped"); if (result) *result = entry->native_object; - HASH_DEL(g_napi_wraps, entry); - free(entry); - js_set_slot((ant_value_t)js_object, SLOT_NAPI_WRAP_ID, js_mkundef()); + entry->native_object = NULL; + entry->finalize_cb = NULL; + entry->finalize_hint = NULL; + entry->has_wrap = false; + + if (!entry->attached_finalize_cb) { + HASH_DEL(g_napi_wraps, entry); + free(entry); + js_set_slot((ant_value_t)js_object, SLOT_NAPI_WRAP_ID, js_mkundef()); + } return napi_set_last(env, napi_ok, NULL); } diff --git a/src/modules/path.c b/src/modules/path.c index 1bc2c8b..1180f90 100644 --- a/src/modules/path.c +++ b/src/modules/path.c @@ -853,6 +853,7 @@ static ant_value_t path_make_variant(ant_t *js, const path_api_t *api, path_styl js_set(js, variant, "sep", js_mkstr(js, path_sep_str(style), 1)); js_set(js, variant, "delimiter", js_mkstr(js, path_delimiter_str(style), 1)); + js_set(js, variant, "default", variant); return variant; } @@ -869,6 +870,7 @@ ant_value_t path_library(ant_t *js) { js_set(js, lib, "posix", posix); js_set(js, lib, "win32", win32); js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "path", 4)); + js_set(js, lib, "default", lib); return lib; } diff --git a/src/modules/process.c b/src/modules/process.c index 764dcab..81d48ed 100644 --- a/src/modules/process.c +++ b/src/modules/process.c @@ -1819,6 +1819,7 @@ static void process_set_methods(ant_t *js, ant_value_t obj, bool include_event_m ant_value_t process_library(ant_t *js) { ant_value_t process_obj = js_get(js, js_glob(js), "process"); + js_set(js, process_obj, "default", process_obj); js_set_slot_wb(js, process_obj, SLOT_DEFAULT, process_obj); return process_obj; } @@ -1862,11 +1863,7 @@ void init_process_module() { js_set(js, process_obj, "pid", js_mknum((double)getpid())); js_set(js, process_obj, "ppid", js_mknum((double)getppid())); - - char version_str[128]; - snprintf(version_str, sizeof(version_str), "v%s", ANT_VERSION); - js_set(js, process_obj, "version", js_mkstr(js, version_str, strlen(version_str))); - + ant_value_t versions_obj = js_mkobj(js); js_set(js, versions_obj, "ant", js_mkstr(js, ANT_VERSION, strlen(ANT_VERSION))); char uv_ver[32]; diff --git a/src/modules/reflect.c b/src/modules/reflect.c index 73f99b6..99ccd01 100644 --- a/src/modules/reflect.c +++ b/src/modules/reflect.c @@ -171,6 +171,23 @@ static ant_value_t reflect_apply(ant_t *js, ant_value_t *args, int nargs) { if (vtype(target) != T_FUNC && vtype(target) != T_CFUNC) { return js_mkerr(js, "Reflect.apply: first argument must be a function"); } + + if (vtype(args_arr) == T_UNDEF || vtype(args_arr) == T_NULL) return js_mkerr_typed( + js, JS_ERR_TYPE, + "Reflect.apply: third argument must be an array-like object" + ); + + ant_value_t result; + if (vtype(args_arr) == T_ARR) { + ant_value_t *call_args = NULL; + int arg_count = extract_array_args(js, args_arr, &call_args); + result = sv_vm_call_explicit_this( + js->vm, js, target, this_arg, + call_args, arg_count + ); + if (call_args) free(call_args); + return result; + } ant_value_t length_val = js_get(js, args_arr, "length"); int arg_count = 0; @@ -190,7 +207,10 @@ static ant_value_t reflect_apply(ant_t *js, ant_value_t *args, int nargs) { } } - ant_value_t result = sv_vm_call(js->vm, js, target, this_arg, call_args, arg_count, NULL, false); + result = sv_vm_call_explicit_this( + js->vm, js, target, this_arg, + call_args, arg_count + ); if (call_args) free(call_args); return result; diff --git a/src/modules/stream.c b/src/modules/stream.c index cc256b3..7be3736 100644 --- a/src/modules/stream.c +++ b/src/modules/stream.c @@ -1619,6 +1619,30 @@ static ant_value_t js_stream_set_default_high_water_mark(ant_t *js, ant_value_t return js_mkundef(); } +static void stream_web_copy_global(ant_t *js, ant_value_t obj, const char *name) { + ant_value_t value = js_get(js, js->global, name); + if (is_err(value)) return; + js_set(js, obj, name, value); +} + +// TODO: remove copy-on-start +static void stream_web_define_common(ant_t *js, ant_value_t obj) { + stream_web_copy_global(js, obj, "ReadableStream"); + stream_web_copy_global(js, obj, "ReadableStreamDefaultReader"); + stream_web_copy_global(js, obj, "ReadableStreamDefaultController"); + stream_web_copy_global(js, obj, "WritableStream"); + stream_web_copy_global(js, obj, "WritableStreamDefaultWriter"); + stream_web_copy_global(js, obj, "WritableStreamDefaultController"); + stream_web_copy_global(js, obj, "TransformStream"); + stream_web_copy_global(js, obj, "TransformStreamDefaultController"); + stream_web_copy_global(js, obj, "ByteLengthQueuingStrategy"); + stream_web_copy_global(js, obj, "CountQueuingStrategy"); + stream_web_copy_global(js, obj, "TextEncoderStream"); + stream_web_copy_global(js, obj, "TextDecoderStream"); + stream_web_copy_global(js, obj, "CompressionStream"); + stream_web_copy_global(js, obj, "DecompressionStream"); +} + ant_value_t stream_library(ant_t *js) { ant_value_t lib = js_mkobj(js); ant_value_t promises = js_mkobj(js); @@ -1670,3 +1694,14 @@ ant_value_t stream_promises_library(ant_t *js) { return promises; } + +ant_value_t stream_web_library(ant_t *js) { + ant_value_t lib = js_mkobj(js); + + stream_web_define_common(js, lib); + js_set(js, lib, "default", lib); + js_set_slot_wb(js, lib, SLOT_DEFAULT, lib); + js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "stream/web", 10)); + + return lib; +} diff --git a/src/modules/tty.c b/src/modules/tty.c index c1d11ba..69910ab 100644 --- a/src/modules/tty.c +++ b/src/modules/tty.c @@ -1,6 +1,7 @@ #include // IWYU pragma: keep #include +#include #include #include #include @@ -17,6 +18,7 @@ #define ANT_STDOUT_FD 1 #define ANT_STDERR_FD 2 #else +#include #include #include #include @@ -264,6 +266,23 @@ static struct { bool active; struct termios saved; } raw_state = { .fd = -1, .active = false }; + +static int tty_tcsetattr_no_sigtou(int fd, int optional_actions, const struct termios *tio) { +#ifdef SIGTTOU + sigset_t block_set; + sigset_t prev_set; + if (sigemptyset(&block_set) == 0 + && sigaddset(&block_set, SIGTTOU) == 0 + && sigprocmask(SIG_BLOCK, &block_set, &prev_set) == 0) { + int rc = tcsetattr(fd, optional_actions, tio); + int saved_errno = errno; + sigprocmask(SIG_SETMASK, &prev_set, NULL); + errno = saved_errno; + return rc; + } +#endif + return tcsetattr(fd, optional_actions, tio); +} #endif bool tty_set_raw_mode(int fd, bool enable) { @@ -302,7 +321,7 @@ bool tty_set_raw_mode(int fd, bool enable) { #ifdef VLNEXT raw.c_cc[VLNEXT] = _POSIX_VDISABLE; #endif - if (tcsetattr(fd, TCSANOW, &raw) == -1) return false; + if (tty_tcsetattr_no_sigtou(fd, TCSANOW, &raw) == -1) return false; raw_state.fd = fd; raw_state.saved = saved; raw_state.active = true; @@ -310,7 +329,7 @@ bool tty_set_raw_mode(int fd, bool enable) { } if (!(raw_state.active && raw_state.fd == fd)) return true; - if (tcsetattr(fd, TCSANOW, &raw_state.saved) == -1) return false; + if (tty_tcsetattr_no_sigtou(fd, TCSANOW, &raw_state.saved) == -1) return false; raw_state.fd = -1; raw_state.active = false; return true; diff --git a/src/modules/url.c b/src/modules/url.c index ca61157..59f35b9 100644 --- a/src/modules/url.c +++ b/src/modules/url.c @@ -1339,6 +1339,261 @@ static ant_value_t builtin_pathToFileURL(ant_t *js, ant_value_t *args, int nargs return make_url_obj(js, s); } +typedef struct { + char *buf; + size_t len; + size_t cap; +} url_fmt_buf_t; + +static bool url_fmt_reserve(url_fmt_buf_t *b, size_t extra) { + if (extra <= b->cap - b->len) return true; + + size_t needed = b->len + extra + 1; + size_t next = b->cap ? b->cap : 128; + while (next < needed) next *= 2; + + char *buf = realloc(b->buf, next); + if (!buf) return false; + b->buf = buf; + b->cap = next; + + return true; +} + +static bool url_fmt_append_n(url_fmt_buf_t *b, const char *s, size_t n) { + if (!s || n == 0) return true; + if (!url_fmt_reserve(b, n)) return false; + memcpy(b->buf + b->len, s, n); + b->len += n; + b->buf[b->len] = '\0'; + return true; +} + +static bool url_fmt_append(url_fmt_buf_t *b, const char *s) { + return url_fmt_append_n(b, s, s ? strlen(s) : 0); +} + +static bool url_fmt_append_c(url_fmt_buf_t *b, char c) { + if (!url_fmt_reserve(b, 1)) return false; + b->buf[b->len++] = c; + b->buf[b->len] = '\0'; + return true; +} + +static bool url_fmt_append_value_string(ant_t *js, url_fmt_buf_t *b, ant_value_t value) { + ant_value_t str_val = vtype(value) == T_STR ? value : js_tostring_val(js, value); + if (is_err(str_val)) return false; + + size_t len = 0; + const char *str = js_getstr(js, str_val, &len); + return str && url_fmt_append_n(b, str, len); +} + +static bool url_fmt_get_string_prop( + ant_t *js, + ant_value_t obj, + const char *name, + ant_value_t *out, + const char **str, + size_t *len +) { + *out = js_get(js, obj, name); + if (is_undefined(*out) || is_null(*out)) return false; + + ant_value_t str_val = vtype(*out) == T_STR ? *out : js_tostring_val(js, *out); + if (is_err(str_val)) return false; + + *out = str_val; + *str = js_getstr(js, str_val, len); + return *str != NULL; +} + +static bool url_fmt_is_query_unescaped(unsigned char c) { + return isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~'; +} + +static bool url_fmt_append_query_component(url_fmt_buf_t *b, const char *s, size_t len) { + static const char hex[] = "0123456789ABCDEF"; + + for (size_t i = 0; i < len; i++) { + unsigned char c = (unsigned char)s[i]; + if (url_fmt_is_query_unescaped(c)) { + if (!url_fmt_append_c(b, (char)c)) return false; + } else { + char esc[3] = { '%', hex[c >> 4], hex[c & 0x0f] }; + if (!url_fmt_append_n(b, esc, sizeof(esc))) return false; + }} + return true; +} + +static bool url_fmt_append_query_object(ant_t *js, url_fmt_buf_t *b, ant_value_t query) { + if (!is_special_object(query)) return true; + + bool first = true; + ant_iter_t it = js_prop_iter_begin(js, query); + + const char *key; + size_t key_len; + ant_value_t val; + + while (js_prop_iter_next(&it, &key, &key_len, &val)) { + if (!first && !url_fmt_append_c(b, '&')) { + js_prop_iter_end(&it); + return false; + } + first = false; + + if (!url_fmt_append_query_component(b, key, key_len)) { + js_prop_iter_end(&it); + return false; + } + + if (!url_fmt_append_c(b, '=')) { + js_prop_iter_end(&it); + return false; + } + + ant_value_t str_val = vtype(val) == T_STR ? val : js_tostring_val(js, val); + if (is_err(str_val)) { + js_prop_iter_end(&it); + return false; + } + + size_t val_len = 0; + const char *val_str = js_getstr(js, str_val, &val_len); + if (!val_str || !url_fmt_append_query_component(b, val_str, val_len)) { + js_prop_iter_end(&it); + return false; + } + } + + js_prop_iter_end(&it); + return true; +} + +static bool url_fmt_protocol_needs_slashes(const char *protocol, size_t len) { + if (len > 0 && protocol[len - 1] == ':') len--; + return + (len == 4 && memcmp(protocol, "http", 4) == 0) || + (len == 5 && memcmp(protocol, "https", 5) == 0) || + (len == 3 && memcmp(protocol, "ftp", 3) == 0) || + (len == 4 && memcmp(protocol, "file", 4) == 0) || + (len == 2 && memcmp(protocol, "ws", 2) == 0) || + (len == 3 && memcmp(protocol, "wss", 3) == 0); +} + +static ant_value_t builtin_url_format(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1 || !is_object_type(args[0])) + return js_mkerr_typed(js, JS_ERR_TYPE, "url.format() requires a URL or object argument"); + + url_state_t *state = url_get_state(args[0]); + if (state) { + char *href = build_href(state); + ant_value_t ret = js_mkstr(js, href, strlen(href)); + free(href); + return ret; + } + + ant_value_t obj = args[0]; + ant_value_t tmp; + + const char *protocol = NULL, *auth = NULL, *host = NULL, *hostname = NULL; + const char *port = NULL, *pathname = NULL, *search = NULL, *hash = NULL; + + size_t protocol_len = 0, auth_len = 0, host_len = 0, hostname_len = 0; + size_t port_len = 0, pathname_len = 0, search_len = 0, hash_len = 0; + + url_fmt_get_string_prop(js, obj, "protocol", &tmp, &protocol, &protocol_len); + url_fmt_get_string_prop(js, obj, "auth", &tmp, &auth, &auth_len); + url_fmt_get_string_prop(js, obj, "host", &tmp, &host, &host_len); + url_fmt_get_string_prop(js, obj, "hostname", &tmp, &hostname, &hostname_len); + url_fmt_get_string_prop(js, obj, "port", &tmp, &port, &port_len); + url_fmt_get_string_prop(js, obj, "pathname", &tmp, &pathname, &pathname_len); + url_fmt_get_string_prop(js, obj, "search", &tmp, &search, &search_len); + url_fmt_get_string_prop(js, obj, "hash", &tmp, &hash, &hash_len); + + url_fmt_buf_t b = {0}; + + if (protocol && protocol_len > 0) { + if (!url_fmt_append_n(&b, protocol, protocol_len)) goto oom; + if (protocol[protocol_len - 1] != ':' && !url_fmt_append_c(&b, ':')) goto oom; + } + + bool has_host = (host && host_len > 0) || (hostname && hostname_len > 0); + ant_value_t slashes_val = js_get(js, obj, "slashes"); + + bool needs_slashes = + js_truthy(js, slashes_val) || + (protocol && url_fmt_protocol_needs_slashes(protocol, protocol_len)); + + if (needs_slashes && (has_host || (protocol && protocol_len >= 4 && memcmp(protocol, "file", 4) == 0))) { + if (!url_fmt_append(&b, "//")) goto oom; + } + + if (auth && auth_len > 0) { + if (!url_fmt_append_n(&b, auth, auth_len)) goto oom; + if (!url_fmt_append_c(&b, '@')) goto oom; + } + + if (host && host_len > 0) { + if (!url_fmt_append_n(&b, host, host_len)) goto oom; + } else if (hostname && hostname_len > 0) { + if (!url_fmt_append_n(&b, hostname, hostname_len)) goto oom; + if (port && port_len > 0) { + if (!url_fmt_append_c(&b, ':')) goto oom; + if (!url_fmt_append_n(&b, port, port_len)) goto oom; + }} + + if (pathname && pathname_len > 0) { + if (has_host && pathname[0] != '/' && !url_fmt_append_c(&b, '/')) goto oom; + if (!url_fmt_append_n(&b, pathname, pathname_len)) goto oom; + } + + if (search && search_len > 0) { + if (search[0] != '?' && !url_fmt_append_c(&b, '?')) goto oom; + if (!url_fmt_append_n(&b, search, search_len)) goto oom; + } else { + ant_value_t query = js_get(js, obj, "query"); + if (vtype(query) == T_STR) { + size_t qlen = 0; + const char *q = js_getstr(js, query, &qlen); + if (q && qlen > 0) { + if (!url_fmt_append_c(&b, '?')) goto oom; + if (!url_fmt_append_n(&b, q, qlen)) goto oom; + } + } else if (is_special_object(query)) { + url_fmt_buf_t qb = {0}; + if (!url_fmt_append_query_object(js, &qb, query)) { + free(qb.buf); + goto oom; + } + if (qb.len > 0) { + if (!url_fmt_append_c(&b, '?')) { + free(qb.buf); + goto oom; + } + if (!url_fmt_append_n(&b, qb.buf, qb.len)) { + free(qb.buf); + goto oom; + }} + free(qb.buf); + } + } + + if (hash && hash_len > 0) { + if (hash[0] != '#' && !url_fmt_append_c(&b, '#')) goto oom; + if (!url_fmt_append_n(&b, hash, hash_len)) goto oom; + } + + ant_value_t ret = js_mkstr(js, b.buf ? b.buf : "", b.len); + free(b.buf); + return ret; + +oom: + free(b.buf); + return js_mkerr(js, "allocation failure"); +} + ant_value_t url_library(ant_t *js) { ant_value_t lib = js_mkobj(js); ant_value_t glob = js_glob(js); @@ -1347,6 +1602,7 @@ ant_value_t url_library(ant_t *js) { js_set(js, lib, "URLSearchParams",js_get(js, glob, "URLSearchParams")); js_set(js, lib, "fileURLToPath", js_mkfun(builtin_fileURLToPath)); js_set(js, lib, "pathToFileURL", js_mkfun(builtin_pathToFileURL)); + js_set(js, lib, "format", js_mkfun(builtin_url_format)); js_set(js, lib, "default", lib); return lib; diff --git a/src/modules/util.c b/src/modules/util.c index 84570fc..9281d42 100644 --- a/src/modules/util.c +++ b/src/modules/util.c @@ -34,6 +34,19 @@ typedef struct { const char *close; } util_style_entry_t; +typedef enum { + UTIL_PARSE_ARG_TYPE_BOOLEAN = 0, + UTIL_PARSE_ARG_TYPE_STRING, +} util_parse_arg_type_t; + +typedef struct { + char *name; + char short_name; + util_parse_arg_type_t type; + bool multiple; + ant_value_t default_value; +} util_parse_arg_option_t; + // TODO: migrate to crprintf static const util_style_entry_t util_styles[] = { {"bold", "\x1b[1m", "\x1b[22m"}, @@ -113,6 +126,288 @@ static bool util_sb_append_json(ant_t *js, util_sb_t *sb, ant_value_t v) { return s ? util_sb_append_n(sb, s, len) : util_sb_append_n(sb, "[Circular]", 10); } +static bool util_set_named_value( + ant_t *js, + ant_value_t values, + const char *name, + ant_value_t value, + bool multiple +) { + if (!multiple) { + js_set(js, values, name, value); + return !js->thrown_exists; + } + + ant_value_t existing = js_get(js, values, name); + if (is_err(existing)) return false; + + if (vtype(existing) == T_UNDEF) { + ant_value_t arr = js_mkarr(js); + js_arr_push(js, arr, value); + js_set(js, values, name, arr); + return !js->thrown_exists; + } + + if (vtype(existing) == T_ARR) { + js_arr_push(js, existing, value); + return !js->thrown_exists; + } + + ant_value_t arr = js_mkarr(js); + js_arr_push(js, arr, existing); + js_arr_push(js, arr, value); + js_set(js, values, name, arr); + return !js->thrown_exists; +} + +static util_parse_arg_option_t *util_find_option_by_name( + util_parse_arg_option_t *options, + size_t option_count, + const char *name +) { + for (size_t i = 0; i < option_count; i++) { + if (strcmp(options[i].name, name) == 0) return &options[i]; + } + return NULL; +} + +static util_parse_arg_option_t *util_find_option_by_short( + util_parse_arg_option_t *options, + size_t option_count, + char short_name +) { + for (size_t i = 0; i < option_count; i++) { + if (options[i].short_name == short_name) return &options[i]; + } + return NULL; +} + +static void util_free_parse_options(util_parse_arg_option_t *options, size_t option_count) { + if (!options) return; + for (size_t i = 0; i < option_count; i++) free(options[i].name); + free(options); +} + +static ant_value_t util_parse_args(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t config = nargs > 0 ? args[0] : js_mkundef(); + if (!is_object_type(config)) { + return js_mkerr_typed(js, JS_ERR_TYPE, "parseArgs(config) requires an options object"); + } + + ant_value_t args_list = js_get(js, config, "args"); + if (is_err(args_list)) return args_list; + if (vtype(args_list) == T_UNDEF) { + ant_value_t process_obj = js_get(js, js_glob(js), "process"); + if (is_err(process_obj)) return process_obj; + args_list = js_get(js, process_obj, "argv"); + if (is_err(args_list)) return args_list; + } + + ant_value_t options_obj = js_get(js, config, "options"); + if (is_err(options_obj)) return options_obj; + if (!is_object_type(options_obj)) options_obj = js_mkobj(js); + + bool strict = js_truthy(js, js_get(js, config, "strict")); + if (vtype(js_get(js, config, "strict")) == T_UNDEF) strict = true; + bool allow_positionals = js_truthy(js, js_get(js, config, "allowPositionals")); + + size_t option_count = 0; + { + ant_iter_t iter = js_prop_iter_begin(js, options_obj); + const char *key = NULL; + size_t key_len = 0; + while (js_prop_iter_next(&iter, &key, &key_len, NULL)) option_count++; + js_prop_iter_end(&iter); + } + + util_parse_arg_option_t *options = NULL; + if (option_count > 0) { + options = calloc(option_count, sizeof(*options)); + if (!options) return js_mkerr(js, "Out of memory"); + + ant_iter_t iter = js_prop_iter_begin(js, options_obj); + const char *key = NULL; + size_t key_len = 0; + size_t idx = 0; + + while (idx < option_count && js_prop_iter_next(&iter, &key, &key_len, NULL)) { + ant_value_t spec = js_get(js, options_obj, key); + ant_value_t type_val = is_object_type(spec) ? js_get(js, spec, "type") : js_mkundef(); + ant_value_t short_val = is_object_type(spec) ? js_get(js, spec, "short") : js_mkundef(); + ant_value_t multiple_val = is_object_type(spec) ? js_get(js, spec, "multiple") : js_mkundef(); + ant_value_t default_val = is_object_type(spec) ? js_get(js, spec, "default") : js_mkundef(); + + options[idx].name = strndup(key, key_len); + if (!options[idx].name) { + js_prop_iter_end(&iter); + util_free_parse_options(options, option_count); + return js_mkerr(js, "Out of memory"); + } + + options[idx].type = UTIL_PARSE_ARG_TYPE_BOOLEAN; + if (vtype(type_val) == T_STR) { + size_t type_len = 0; + const char *type_str = js_getstr(js, type_val, &type_len); + if (type_str && type_len == 6 && memcmp(type_str, "string", 6) == 0) { + options[idx].type = UTIL_PARSE_ARG_TYPE_STRING; + } + } + + if (vtype(short_val) == T_STR) { + size_t short_len = 0; + const char *short_str = js_getstr(js, short_val, &short_len); + if (short_str && short_len > 0) options[idx].short_name = short_str[0]; + } + + options[idx].multiple = js_truthy(js, multiple_val); + options[idx].default_value = default_val; + idx++; + } + + js_prop_iter_end(&iter); + } + + ant_value_t values = js_mkobj(js); + ant_value_t positionals = js_mkarr(js); + ant_value_t out = js_mkobj(js); + + for (size_t i = 0; i < option_count; i++) { + if (vtype(options[i].default_value) != T_UNDEF) { + if (!util_set_named_value(js, values, options[i].name, options[i].default_value, options[i].multiple)) { + util_free_parse_options(options, option_count); + return js->thrown_exists ? js->thrown_value : js_mkerr(js, "parseArgs failed to set default"); + }}} + + ant_offset_t arg_len = js_arr_len(js, args_list); + bool stop_parsing = false; + + for (ant_offset_t i = 0; i < arg_len; i++) { + ant_value_t arg_val = js_arr_get(js, args_list, i); + if (vtype(arg_val) != T_STR) continue; + + size_t arg_slen = 0; + const char *arg = js_getstr(js, arg_val, &arg_slen); + if (!arg) continue; + + if (stop_parsing) { + js_arr_push(js, positionals, arg_val); + continue; + } + + if (arg_slen == 2 && memcmp(arg, "--", 2) == 0) { + stop_parsing = true; + continue; + } + + if (arg_slen > 2 && arg[0] == '-' && arg[1] == '-') { + const char *name = arg + 2; + size_t name_len = arg_slen - 2; + const char *inline_value = NULL; + size_t inline_len = 0; + const char *eq = memchr(name, '=', name_len); + + if (eq) { + name_len = (size_t)(eq - name); + inline_value = eq + 1; + inline_len = (size_t)(arg + arg_slen - inline_value); + } + + char *name_buf = strndup(name, name_len); + if (!name_buf) { + util_free_parse_options(options, option_count); + return js_mkerr(js, "Out of memory"); + } + + util_parse_arg_option_t *opt = util_find_option_by_name(options, option_count, name_buf); + if (!opt) { + if (strict) { + free(name_buf); + util_free_parse_options(options, option_count); + return js_mkerr_typed(js, JS_ERR_TYPE, "Unknown option '--%.*s'", (int)name_len, name); + } + + ant_value_t unknown = inline_value + ? js_mkstr(js, inline_value, inline_len) + : js_true; + + js_set(js, values, name_buf, unknown); + free(name_buf); + + continue; + } + + ant_value_t parsed_value = js_true; + if (opt->type == UTIL_PARSE_ARG_TYPE_STRING) { + if (inline_value) parsed_value = js_mkstr(js, inline_value, inline_len); + else if (i + 1 < arg_len) parsed_value = js_arr_get(js, args_list, ++i); + else { + free(name_buf); + util_free_parse_options(options, option_count); + return js_mkerr_typed(js, JS_ERR_TYPE, "Option '--%s' requires a value", opt->name); + } + } else if (inline_value) parsed_value = js_mkstr(js, inline_value, inline_len); + + if (!util_set_named_value(js, values, opt->name, parsed_value, opt->multiple)) { + free(name_buf); + util_free_parse_options(options, option_count); + return js->thrown_exists ? js->thrown_value : js_mkerr(js, "parseArgs failed"); + } + + free(name_buf); + continue; + } + + if (arg_slen > 1 && arg[0] == '-') { + for (size_t j = 1; j < arg_slen; j++) { + util_parse_arg_option_t *opt = util_find_option_by_short(options, option_count, arg[j]); + if (!opt) { + if (strict) { + util_free_parse_options(options, option_count); + return js_mkerr_typed(js, JS_ERR_TYPE, "Unknown option '-%c'", arg[j]); + } + char key[2] = {arg[j], '\0'}; + js_set(js, values, key, js_true); + continue; + } + + ant_value_t parsed_value = js_true; + if (opt->type == UTIL_PARSE_ARG_TYPE_STRING) { + if (j + 1 < arg_slen) { + parsed_value = js_mkstr(js, arg + j + 1, arg_slen - (j + 1)); + j = arg_slen; + } else if (i + 1 < arg_len) { + parsed_value = js_arr_get(js, args_list, ++i); + j = arg_slen; + } else { + util_free_parse_options(options, option_count); + return js_mkerr_typed(js, JS_ERR_TYPE, "Option '-%c' requires a value", arg[j]); + } + } + + if (!util_set_named_value(js, values, opt->name, parsed_value, opt->multiple)) { + util_free_parse_options(options, option_count); + return js->thrown_exists ? js->thrown_value : js_mkerr(js, "parseArgs failed"); + } + + if (opt->type == UTIL_PARSE_ARG_TYPE_STRING) break; + } + continue; + } + + if (!allow_positionals) { + util_free_parse_options(options, option_count); + return js_mkerr_typed(js, JS_ERR_TYPE, "Unexpected positional argument '%s'", arg); + } + js_arr_push(js, positionals, arg_val); + } + + util_free_parse_options(options, option_count); + js_set(js, out, "values", values); + js_set(js, out, "positionals", positionals); + + return out; +} + static ant_value_t util_format_impl(ant_t *js, ant_value_t *args, int nargs, int fmt_index) { util_sb_t sb = {0}; if (fmt_index >= nargs) { @@ -1024,6 +1319,11 @@ static ant_value_t util_inherits(ant_t *js, ant_value_t *args, int nargs) { return js_mkundef(); } +static ant_value_t util_is_deep_strict_equal(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 2) return js_bool(true); + return js_bool(js_deep_equal(js, args[0], args[1], true)); +} + ant_value_t util_library(ant_t *js) { ant_value_t lib = js_mkobj(js); ant_value_t types = util_get_types_object(js); @@ -1034,6 +1334,8 @@ ant_value_t util_library(ant_t *js) { js_set(js, lib, "inspect", js_mkfun(util_inspect)); js_set(js, lib, "deprecate", js_mkfun(util_deprecate)); js_set(js, lib, "inherits", js_mkfun(util_inherits)); + js_set(js, lib, "isDeepStrictEqual", js_mkfun(util_is_deep_strict_equal)); + js_set(js, lib, "parseArgs", js_mkfun(util_parse_args)); js_set(js, lib, "parseEnv", js_mkfun(util_parse_env)); js_set(js, lib, "promisify", js_mkfun(util_promisify)); js_set(js, lib, "callbackify", js_mkfun(util_callbackify)); diff --git a/src/pkg/resolver.zig b/src/pkg/resolver.zig index 2c29258..a572f07 100644 --- a/src/pkg/resolver.zig +++ b/src/pkg/resolver.zig @@ -769,6 +769,7 @@ pub const Resolver = struct { self.http.initiateTarballConnectionsAsync(); const ConstraintInfo = struct { + package_name: []const u8, constraint: Constraint, constraint_str: []const u8, requester: []const u8, @@ -780,6 +781,7 @@ pub const Resolver = struct { var iter = all_constraints.iterator(); while (iter.next()) |entry| { for (entry.value_ptr.items) |info| { + self.allocator.free(info.package_name); self.allocator.free(info.constraint_str); self.allocator.free(info.requester); } @@ -955,12 +957,13 @@ pub const Resolver = struct { const spec = dependencySpec(item.name, item.constraint_str); const constraint = Constraint.parse(spec.constraint) catch continue; - const gop = try all_constraints.getOrPut(spec.package_name); + const gop = try all_constraints.getOrPut(spec.install_name); if (!gop.found_existing) { - gop.key_ptr.* = try self.allocator.dupe(u8, spec.package_name); + gop.key_ptr.* = try self.allocator.dupe(u8, spec.install_name); gop.value_ptr.* = .{}; } try gop.value_ptr.append(self.allocator, .{ + .package_name = try self.allocator.dupe(u8, spec.package_name), .constraint = constraint, .constraint_str = try self.allocator.dupe(u8, spec.constraint), .requester = try self.allocator.dupe(u8, item.requester), @@ -1024,10 +1027,12 @@ pub const Resolver = struct { var pkg_iter = all_constraints.iterator(); while (pkg_iter.next()) |entry| { - const pkg_name = entry.key_ptr.*; + const install_name = entry.key_ptr.*; const constraint_list = entry.value_ptr.items; + if (constraint_list.len == 0) continue; - if (self.metadata_cache.get(pkg_name)) |metadata| { + const package_name = constraint_list[0].package_name; + if (self.metadata_cache.get(package_name)) |metadata| { var plain_constraints = try self.allocator.alloc(Constraint, constraint_list.len); defer self.allocator.free(plain_constraints); for (constraint_list, 0..) |info, i| { @@ -1035,22 +1040,22 @@ pub const Resolver = struct { } if (self.selectBestVersionForConstraints(&metadata, plain_constraints)) |best| { - try optimal_versions.put(pkg_name, best); + try optimal_versions.put(install_name, best); } else { const best = self.selectVersionSatisfyingMost(&metadata, constraint_list); if (best) |b| { if (b.version.prerelease) |pre| { debug.log(" {s}: optimal={d}.{d}.{d}-{s} (satisfies {d}/{d} constraints)", .{ - pkg_name, b.version.major, b.version.minor, b.version.patch, pre, + install_name, b.version.major, b.version.minor, b.version.patch, pre, self.countSatisfied(&metadata, b, constraint_list), constraint_list.len, }); } else { debug.log(" {s}: optimal={d}.{d}.{d} (satisfies {d}/{d} constraints)", .{ - pkg_name, b.version.major, b.version.minor, b.version.patch, + install_name, b.version.major, b.version.minor, b.version.patch, self.countSatisfied(&metadata, b, constraint_list), constraint_list.len, }); } - try optimal_versions.put(pkg_name, b); + try optimal_versions.put(install_name, b); } } } @@ -1245,7 +1250,7 @@ pub const Resolver = struct { var metadata = try self.fetchMetadata(dep_spec.package_name); const version_info = blk: { - if (optimal_versions.get(dep_spec.package_name)) |optimal| { + if (optimal_versions.get(dep_spec.install_name)) |optimal| { if (constraint.satisfies(optimal.version) and optimal.matchesPlatform()) break :blk optimal; } break :blk self.selectBestVersion(&metadata, constraint) orelse return error.NoMatchingVersion; @@ -1253,9 +1258,9 @@ pub const Resolver = struct { if (!version_info.matchesPlatform()) return error.PlatformMismatch; - const cons_gop = try self.constraints.getOrPut(dep_spec.package_name); + const cons_gop = try self.constraints.getOrPut(dep_spec.install_name); if (!cons_gop.found_existing) { - cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.package_name); + cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.install_name); cons_gop.value_ptr.* = .{}; } try cons_gop.value_ptr.append(self.allocator, constraint); @@ -1332,9 +1337,9 @@ pub const Resolver = struct { return existing_pkg; } - const cons_gop = try self.constraints.getOrPut(dep_spec.package_name); + const cons_gop = try self.constraints.getOrPut(dep_spec.install_name); if (!cons_gop.found_existing) { - cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.package_name); + cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.install_name); cons_gop.value_ptr.* = .{}; } try cons_gop.value_ptr.append(self.allocator, constraint); @@ -1400,9 +1405,9 @@ pub const Resolver = struct { } } - const cons_gop = try self.constraints.getOrPut(dep_spec.package_name); + const cons_gop = try self.constraints.getOrPut(dep_spec.install_name); if (!cons_gop.found_existing) { - cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.package_name); + cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.install_name); cons_gop.value_ptr.* = .{}; } try cons_gop.value_ptr.append(self.allocator, constraint); @@ -1563,10 +1568,10 @@ pub const Resolver = struct { const dep_spec = dependencySpec(name, constraint_str); const constraint = try Constraint.parse(dep_spec.constraint); - const cons_gop = try self.constraints.getOrPut(dep_spec.package_name); + const cons_gop = try self.constraints.getOrPut(dep_spec.install_name); if (!cons_gop.found_existing) { - cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.package_name); + cons_gop.key_ptr.* = try self.allocator.dupe(u8, dep_spec.install_name); cons_gop.value_ptr.* = .{}; } try cons_gop.value_ptr.append(self.allocator, constraint); diff --git a/src/repl.c b/src/repl.c index 712f32f..d0b462d 100644 --- a/src/repl.c +++ b/src/repl.c @@ -425,7 +425,7 @@ static cmd_result_t cmd_save(ant_t *js, history_t *history, const char *arg) { static cmd_result_t cmd_stats(ant_t *js, history_t *history, const char *arg) { ant_value_t stats_fn = js_get(js, rt->ant_obj, "stats"); ant_value_t result = sv_vm_call(js->vm, js, stats_fn, js_mkundef(), NULL, 0, NULL, false); - console_print(js, &result, 1, NULL, stdout); + console_emit(js, false, NULL, &result, 1); return CMD_OK; } diff --git a/src/runtime.c b/src/runtime.c index 1d7f4cb..d348a08 100644 --- a/src/runtime.c +++ b/src/runtime.c @@ -36,6 +36,31 @@ static code_block_t *code_arena_head = NULL; static code_block_t *code_arena_current = NULL; static intern_entry_t *code_interns = NULL; +static void code_interns_prune_for_block_range( + const code_block_t *block, + size_t start_offset +) { + if (!block) return; + + const char *start = block->data + start_offset; + const char *end = block->data + block->capacity; + intern_entry_t *entry = NULL; + intern_entry_t *tmp = NULL; + + HASH_ITER(hh, code_interns, entry, tmp) + if (entry->ptr >= start && entry->ptr < end) { + HASH_DEL(code_interns, entry); + free(entry); + } +} + +static void code_interns_prune_for_blocks(code_block_t *first) { + for ( + code_block_t *block = first; + block; block = block->next + ) code_interns_prune_for_block_range(block, 0); +} + static code_block_t *code_arena_new_block(size_t min_size) { size_t capacity = CODE_ARENA_BLOCK_SIZE; if (min_size > capacity) capacity = min_size; @@ -60,10 +85,8 @@ const char *code_arena_alloc(const char *code, size_t len) { if (!code_arena_current || code_arena_current->used + alloc_size > code_arena_current->capacity) { code_block_t *new_block = code_arena_new_block(alloc_size); if (!new_block) return NULL; - if (!code_arena_head) code_arena_head = new_block; else if (code_arena_current) code_arena_current->next = new_block; - code_arena_current = new_block; } @@ -114,6 +137,7 @@ void code_arena_rewind(code_arena_mark_t mark) { code_block_t *target = (code_block_t *)mark.block; if (!target) { + code_interns_prune_for_blocks(code_arena_head); code_block_t *block = code_arena_head; while (block) { @@ -127,6 +151,10 @@ void code_arena_rewind(code_arena_mark_t mark) { return; } + size_t clamped_used = mark.used <= target->capacity ? mark.used : target->capacity; + code_interns_prune_for_block_range(target, clamped_used); + code_interns_prune_for_blocks(target->next); + if (mark.used <= target->capacity) target->used = mark.used; code_block_t *b = target->next; diff --git a/src/silver/ast.c b/src/silver/ast.c index 09f7d8b..5d39694 100644 --- a/src/silver/ast.c +++ b/src/silver/ast.c @@ -3,11 +3,13 @@ #include "silver/directives.h" #include "escape.h" +#include "debug.h" #include "errors.h" #include "internal.h" #include "tokens.h" #include +#include #include #include @@ -1293,7 +1295,7 @@ static sv_ast_t *parse_func(P) { fn->flags |= FN_GENERATOR; } - if (NEXT() == TOK_IDENTIFIER) { + if (is_ident_like_tok(NEXT())) { fn->str = tok_ident_str(p, &fn->len); sv_strict_check_binding_ident(p, fn->str, fn->len); CONSUME(); @@ -1334,7 +1336,7 @@ static sv_ast_t *parse_func(P) { static sv_ast_t *parse_class(P) { sv_ast_t *cls = mk(N_CLASS); - if (NEXT() == TOK_IDENTIFIER && + if (is_ident_like_tok(NEXT()) && !(TLEN == 7 && memcmp(tok_str(p), "extends", 7) == 0)) { cls->str = tok_ident_str(p, &cls->len); CONSUME(); @@ -2107,14 +2109,33 @@ static sv_ast_t *parse_stmt(P) { } sv_ast_t *sv_parse(ant_t *js, const char *code, ant_offset_t clen, bool strict){ + if (sv_parse_trace_unlikely) { + fprintf(stderr, "[parse] start len=%u strict=%d\n", (unsigned)clen, strict ? 1 : 0); + } + sv_parser_t parser = { .js = js }; sv_parser_t *p = &parser; sv_lexer_init(&p->lx, js, code, clen, strict); sv_ast_t *program = mk(N_PROGRAM); sv_parse_stmt_list(p, &program->args, false, true); - if (js->thrown_exists) return NULL; + if (sv_parse_trace_unlikely) fprintf( + stderr, "[parse] after-stmt-list thrown=%d strict=%d body=%d\n", + js->thrown_exists ? 1 : 0, + p->lx.strict ? 1 : 0, + program ? program->args.count : -1 + ); + + if (js->thrown_exists) { + if (sv_parse_trace_unlikely) fprintf(stderr, "[parse] return null\n"); + return NULL; + } + if (p->lx.strict) program->flags |= FN_PARSE_STRICT; + if (sv_parse_trace_unlikely) fprintf(stderr, + "[parse] return program strict=%d body=%d\n", + p->lx.strict ? 1 : 0, program->args.count + ); return program; } diff --git a/src/silver/compile_ctx.c b/src/silver/compile_ctx.c new file mode 100644 index 0000000..33df2e5 --- /dev/null +++ b/src/silver/compile_ctx.c @@ -0,0 +1,186 @@ +#include "silver/compiler.h" +#include "silver/compile_ctx.h" + +#include +#include + +static void sv_compile_ctx_rebuild_local_lookup(sv_compiler_t *ctx, int cap) { + if (cap < 16) cap = 16; + int *heads = malloc((size_t)cap * sizeof(int)); + if (!heads) return; + + for (int i = 0; i < cap; i++) heads[i] = -1; + for (int i = 0; i < ctx->local_count; i++) { + sv_local_t *loc = &ctx->locals[i]; + int bucket = (int)(loc->name_hash & (uint32_t)(cap - 1)); + loc->lookup_next = heads[bucket]; + heads[bucket] = i; + } + + free(ctx->local_lookup_heads); + ctx->local_lookup_heads = heads; + ctx->local_lookup_cap = cap; +} + +void sv_compile_ctx_init_root( + sv_compiler_t *ctx, ant_t *js, + const char *filename, + const char *source, + ant_offset_t source_len, + sv_compile_mode_t mode, + bool is_strict, + sv_line_table_t *line_table +) { + memset(ctx, 0, sizeof(*ctx)); + ctx->js = js; + ctx->filename = filename; + ctx->source = source; + ctx->source_len = source_len; + ctx->mode = mode; + ctx->is_strict = is_strict; + ctx->strict_args_local = -1; + ctx->new_target_local = -1; + ctx->super_local = -1; + ctx->line_table = line_table; +} + +void sv_compile_ctx_init_child( + sv_compiler_t *ctx, + sv_compiler_t *enclosing, + sv_ast_t *node, + sv_compile_mode_t mode +) { + memset(ctx, 0, sizeof(*ctx)); + ctx->js = enclosing->js; + ctx->filename = enclosing->filename; + ctx->source = enclosing->source; + ctx->source_len = enclosing->source_len; + ctx->line_table = enclosing->line_table; + ctx->enclosing = enclosing; + ctx->scope_depth = 0; + ctx->is_arrow = node && !!(node->flags & FN_ARROW); + ctx->is_async = node && !!(node->flags & FN_ASYNC); + ctx->is_strict = enclosing->is_strict; + ctx->mode = mode; + ctx->strict_args_local = -1; + ctx->new_target_local = -1; + ctx->super_local = -1; + ctx->param_count = node ? node->args.count : 0; +} + +void sv_compile_ctx_cleanup(sv_compiler_t *ctx) { + free(ctx->code); + free(ctx->constants); + free(ctx->atoms); + free(ctx->locals); + free(ctx->local_lookup_heads); + free(ctx->upval_descs); + free(ctx->loops); + free(ctx->srcpos); + free(ctx->slot_types); + free(ctx->deferred_exports); + + const_dedup_entry_t *entry; + const_dedup_entry_t *tmp; + HASH_ITER(hh, ctx->const_dedup, entry, tmp) { + HASH_DEL(ctx->const_dedup, entry); + free(entry); + } +} + +sv_line_table_t *sv_compile_ctx_build_line_table(const char *source, ant_offset_t source_len) { + if (!source || source_len <= 0) return NULL; + + sv_line_table_t *lt = malloc(sizeof(sv_line_table_t)); + if (!lt) return NULL; + + int cap = (int)(source_len / 32) + 64; + lt->offsets = malloc((size_t)cap * sizeof(uint32_t)); + lt->count = 0; + lt->offsets[lt->count++] = 0; + + for (ant_offset_t i = 0; i < source_len; i++) { + if (source[i] == '\n') { + if (lt->count >= cap) { + cap *= 2; + lt->offsets = realloc(lt->offsets, (size_t)cap * sizeof(uint32_t)); + } + lt->offsets[lt->count++] = (uint32_t)(i + 1); + }} + + return lt; +} + +void sv_compile_ctx_free_line_table(sv_line_table_t *lt) { + if (!lt) return; + free(lt->offsets); + free(lt); +} + +void sv_compile_ctx_line_table_lookup( + sv_line_table_t *lt, + uint32_t off, + uint32_t *out_line, + uint32_t *out_col +) { + int lo = 0; + int hi = lt->count - 1; + + while (lo < hi) { + int mid = lo + (hi - lo + 1) / 2; + if (lt->offsets[mid] <= off) lo = mid; + else hi = mid - 1; + } + + *out_line = (uint32_t)(lo + 1); + *out_col = off - lt->offsets[lo] + 1; +} + +uint32_t sv_compile_ctx_hash_local_name(const char *name, uint32_t len) { + uint32_t hash = 2166136261u; + for (uint32_t i = 0; i < len; i++) { + hash ^= (uint8_t)name[i]; + hash *= 16777619u; + } + return hash; +} + +void sv_compile_ctx_ensure_local_lookup_capacity(sv_compiler_t *ctx, int next_count) { + int needed = 16; + while (needed < next_count * 2) needed <<= 1; + if (needed <= ctx->local_lookup_cap) return; + sv_compile_ctx_rebuild_local_lookup(ctx, needed); +} + +void sv_compile_ctx_local_lookup_insert(sv_compiler_t *ctx, int idx) { + if (idx < 0 || idx >= ctx->local_count) return; + if (!ctx->local_lookup_heads || ctx->local_lookup_cap <= 0) return; + + sv_local_t *loc = &ctx->locals[idx]; + int bucket = (int)(loc->name_hash & (uint32_t)(ctx->local_lookup_cap - 1)); + loc->lookup_next = ctx->local_lookup_heads[bucket]; + ctx->local_lookup_heads[bucket] = idx; +} + +void sv_compile_ctx_local_lookup_remove(sv_compiler_t *ctx, int idx) { + if ( + !ctx->local_lookup_heads || ctx->local_lookup_cap <= 0 || + idx < 0 || idx >= ctx->local_count + ) return; + + sv_local_t *loc = &ctx->locals[idx]; + int bucket = (int)(loc->name_hash & (uint32_t)(ctx->local_lookup_cap - 1)); + int cur = ctx->local_lookup_heads[bucket]; + int prev = -1; + + while (cur != -1) { + if (cur == idx) { + int next = ctx->locals[cur].lookup_next; + if (prev == -1) ctx->local_lookup_heads[bucket] = next; + else ctx->locals[prev].lookup_next = next; + break; + } + prev = cur; + cur = ctx->locals[cur].lookup_next; + } +} diff --git a/src/silver/compiler.c b/src/silver/compiler.c index e229eea..1451ffc 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -1,9 +1,11 @@ #include "silver/ast.h" +#include "silver/compile_ctx.h" #include "silver/engine.h" #include "silver/compiler.h" #include "silver/directives.h" #include "internal.h" +#include "debug.h" #include "tokens.h" #include "runtime.h" #include "ops/coercion.h" @@ -18,211 +20,6 @@ enum { SV_ITER_HINT_STRING = 4, }; -typedef struct { - const char *name; - uint32_t name_len; - int depth; - bool is_const; - bool captured; - bool is_tdz; - uint8_t inferred_type; -} sv_local_t; - -typedef struct { - int *offsets; - int count; - int cap; -} sv_patch_list_t; - -typedef struct { - int loop_start; - sv_patch_list_t breaks; - sv_patch_list_t continues; - int scope_depth; - const char *label; - uint32_t label_len; - bool is_switch; -} sv_loop_t; - -typedef struct { - const char *name; - uint32_t len; -} sv_deferred_export_t; - -typedef struct sv_compiler { - ant_t *js; - const char *filename; - const char *source; - ant_offset_t source_len; - - uint8_t *code; - int code_len; - int code_cap; - - ant_value_t *constants; - int const_count; - int const_cap; - - sv_atom_t *atoms; - int atom_count; - int atom_cap; - int ic_count; - - sv_local_t *locals; - int local_count; - int local_cap; - int max_local_count; - int scope_depth; - int param_locals; - - sv_upval_desc_t *upval_descs; - int upvalue_count; - int upvalue_cap; - - sv_loop_t *loops; - int loop_count; - int loop_cap; - - struct sv_compiler *enclosing; - sv_ast_t **field_inits; - - int field_init_count; - int *computed_key_locals; - int param_count; - - bool is_arrow; - bool is_async; - bool is_strict; - sv_compile_mode_t mode; - - bool is_tla; - int try_depth; - int with_depth; - int strict_args_local; - int new_target_local; - int super_local; - - const char *pending_label; - uint32_t pending_label_len; - - const char *inferred_name; - uint32_t inferred_name_len; - - sv_srcpos_t *srcpos; - int srcpos_count; - int srcpos_cap; - uint32_t last_srcpos_off; - uint32_t last_srcpos_end; - - sv_type_info_t *slot_types; - int slot_type_cap; - - sv_deferred_export_t *deferred_exports; - int deferred_export_count; - int deferred_export_cap; - - struct const_dedup_entry *const_dedup; - struct sv_line_table *line_table; -} sv_compiler_t; - -typedef struct const_dedup_entry { - const char *str; - size_t len; - int index; - UT_hash_handle hh; -} const_dedup_entry_t; - -typedef struct sv_line_table { - uint32_t *offsets; - int count; -} sv_line_table_t; - -static sv_line_table_t *build_line_table(const char *source, ant_offset_t source_len) { - if (!source || source_len <= 0) return NULL; - sv_line_table_t *lt = malloc(sizeof(sv_line_table_t)); - if (!lt) return NULL; - - int cap = (int)(source_len / 32) + 64; - lt->offsets = malloc((size_t)cap * sizeof(uint32_t)); - lt->count = 0; - lt->offsets[lt->count++] = 0; - - for (ant_offset_t i = 0; i < source_len; i++) { - if (source[i] == '\n') { - if (lt->count >= cap) { - cap *= 2; - lt->offsets = realloc(lt->offsets, (size_t)cap * sizeof(uint32_t)); - } - lt->offsets[lt->count++] = (uint32_t)(i + 1); - } - } - return lt; -} - -static void free_line_table(sv_line_table_t *lt) { - if (!lt) return; - free(lt->offsets); - free(lt); -} - -static void line_table_lookup(sv_line_table_t *lt, uint32_t off, uint32_t *out_line, uint32_t *out_col) { - int lo = 0, hi = lt->count - 1; - while (lo < hi) { - int mid = lo + (hi - lo + 1) / 2; - if (lt->offsets[mid] <= off) lo = mid; - else hi = mid - 1; - } - *out_line = (uint32_t)(lo + 1); - *out_col = off - lt->offsets[lo] + 1; -} - -static sv_func_t *compile_function_body( - sv_compiler_t *enclosing, - sv_ast_t *node, sv_compile_mode_t mode -); - -static void compile_expr(sv_compiler_t *c, sv_ast_t *node); -static void compile_stmt(sv_compiler_t *c, sv_ast_t *node); -static void compile_stmts(sv_compiler_t *c, sv_ast_list_t *list); -static void compile_binary(sv_compiler_t *c, sv_ast_t *node); -static void compile_unary(sv_compiler_t *c, sv_ast_t *node); -static void compile_update(sv_compiler_t *c, sv_ast_t *node); -static void compile_assign(sv_compiler_t *c, sv_ast_t *node); -static void compile_lhs_set(sv_compiler_t *c, sv_ast_t *target, bool keep); -static void compile_ternary(sv_compiler_t *c, sv_ast_t *node); -static void compile_tail_return_expr(sv_compiler_t *c, sv_ast_t *expr); -static void compile_typeof(sv_compiler_t *c, sv_ast_t *node); -static void compile_delete(sv_compiler_t *c, sv_ast_t *node); -static void compile_template(sv_compiler_t *c, sv_ast_t *node); -static void compile_call(sv_compiler_t *c, sv_ast_t *node); -static void compile_new(sv_compiler_t *c, sv_ast_t *node); -static void compile_member(sv_compiler_t *c, sv_ast_t *node); -static void compile_optional(sv_compiler_t *c, sv_ast_t *node); -static void compile_optional_get(sv_compiler_t *c, sv_ast_t *node); -static void compile_array(sv_compiler_t *c, sv_ast_t *node); -static void compile_object(sv_compiler_t *c, sv_ast_t *node); -static void compile_func_expr(sv_compiler_t *c, sv_ast_t *node); -static void compile_array_destructure(sv_compiler_t *c, sv_ast_t *pat, bool keep); -static void compile_object_destructure(sv_compiler_t *c, sv_ast_t *pat, bool keep); -static void compile_var_decl(sv_compiler_t *c, sv_ast_t *node); -static void compile_import_decl(sv_compiler_t *c, sv_ast_t *node); -static void compile_export_decl(sv_compiler_t *c, sv_ast_t *node); -static void compile_destructure_binding(sv_compiler_t *c, sv_ast_t *pat, sv_var_kind_t kind); -static void compile_if(sv_compiler_t *c, sv_ast_t *node); -static void compile_while(sv_compiler_t *c, sv_ast_t *node); -static void compile_do_while(sv_compiler_t *c, sv_ast_t *node); -static void compile_for(sv_compiler_t *c, sv_ast_t *node); -static void compile_for_in(sv_compiler_t *c, sv_ast_t *node); -static void compile_for_of(sv_compiler_t *c, sv_ast_t *node); -static void compile_break(sv_compiler_t *c, sv_ast_t *node); -static void compile_continue(sv_compiler_t *c, sv_ast_t *node); -static void compile_try(sv_compiler_t *c, sv_ast_t *node); -static void compile_switch(sv_compiler_t *c, sv_ast_t *node); -static void compile_label(sv_compiler_t *c, sv_ast_t *node); -static void compile_class(sv_compiler_t *c, sv_ast_t *node); - -static uint8_t infer_expr_type(sv_compiler_t *c, sv_ast_t *node); - static const char *pin_source_text(const char *source, ant_offset_t source_len) { if (!source || source_len <= 0) return source; const char *pinned = code_arena_alloc(source, (size_t)source_len); @@ -276,9 +73,8 @@ static void emit_srcpos(sv_compiler_t *c, sv_ast_t *node) { if (c->srcpos_count > 0 && c->last_srcpos_off == off && c->last_srcpos_end == end) return; uint32_t line, col; - if (c->line_table) { - line_table_lookup(c->line_table, off, &line, &col); - } else if (c->srcpos_count > 0 && off >= c->last_srcpos_off) { + if (c->line_table) sv_compile_ctx_line_table_lookup(c->line_table, off, &line, &col); + else if (c->srcpos_count > 0 && off >= c->last_srcpos_off) { line = c->srcpos[c->srcpos_count - 1].line; col = c->srcpos[c->srcpos_count - 1].col; for (uint32_t i = c->last_srcpos_off; i < off; i++) { @@ -600,11 +396,25 @@ static inline bool has_implicit_arguments_obj(const sv_compiler_t *c) { } static int resolve_local(sv_compiler_t *c, const char *name, uint32_t len) { + if (c->local_lookup_heads && c->local_lookup_cap > 0) { + uint32_t hash = sv_compile_ctx_hash_local_name(name, len); + int bucket = (int)(hash & (uint32_t)(c->local_lookup_cap - 1)); + for (int i = c->local_lookup_heads[bucket]; i != -1; i = c->locals[i].lookup_next) { + sv_local_t *loc = &c->locals[i]; + if ( + loc->name_hash == hash && + loc->name_len == len && + memcmp(loc->name, name, len) == 0 + ) return i; + } + return -1; + } + for (int i = c->local_count - 1; i >= 0; i--) { sv_local_t *loc = &c->locals[i]; - if (loc->name_len == len && memcmp(loc->name, name, len) == 0) - return i; + if (loc->name_len == len && memcmp(loc->name, name, len) == 0) return i; } + return -1; } @@ -612,6 +422,7 @@ static int add_local( sv_compiler_t *c, const char *name, uint32_t len, bool is_const, int depth ) { + sv_compile_ctx_ensure_local_lookup_capacity(c, c->local_count + 1); if (c->local_count >= c->local_cap) { c->local_cap = c->local_cap ? c->local_cap * 2 : 16; c->locals = realloc(c->locals, (size_t)c->local_cap * sizeof(sv_local_t)); @@ -621,9 +432,12 @@ static int add_local( c->max_local_count = c->local_count; c->locals[idx] = (sv_local_t){ .name = name, .name_len = len, + .name_hash = sv_compile_ctx_hash_local_name(name, len), + .lookup_next = -1, .depth = depth, .is_const = is_const, .captured = false, .inferred_type = SV_TI_UNKNOWN, }; + sv_compile_ctx_local_lookup_insert(c, idx); return idx; } @@ -875,6 +689,7 @@ static void end_scope(sv_compiler_t *c) { emit_op(c, OP_CLOSE_UPVAL); emit_u16(c, (uint16_t)frame_slot); } + sv_compile_ctx_local_lookup_remove(c, c->local_count - 1); c->local_count--; } c->scope_depth--; @@ -1406,7 +1221,7 @@ static void compile_yield_star_expr(sv_compiler_t *c, sv_ast_t *node) { emit_u16(c, base_slot); } -static void compile_expr(sv_compiler_t *c, sv_ast_t *node) { +void compile_expr(sv_compiler_t *c, sv_ast_t *node) { if (!node) { emit_op(c, OP_UNDEF); return; } emit_srcpos(c, node); @@ -1648,7 +1463,7 @@ static void compile_expr(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_binary(sv_compiler_t *c, sv_ast_t *node) { +void compile_binary(sv_compiler_t *c, sv_ast_t *node) { uint8_t op = node->op; if (op == TOK_LAND) { @@ -1734,7 +1549,7 @@ static void compile_binary(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_unary(sv_compiler_t *c, sv_ast_t *node) { +void compile_unary(sv_compiler_t *c, sv_ast_t *node) { compile_expr(c, node->right); switch (node->op) { case TOK_NOT: emit_op(c, OP_NOT); break; @@ -1746,7 +1561,7 @@ static void compile_unary(sv_compiler_t *c, sv_ast_t *node) { } -static void compile_update(sv_compiler_t *c, sv_ast_t *node) { +void compile_update(sv_compiler_t *c, sv_ast_t *node) { bool prefix = (node->flags & 1); bool is_inc = (node->op == TOK_POSTINC); sv_ast_t *target = node->right; @@ -1794,7 +1609,7 @@ static void compile_update(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_assign(sv_compiler_t *c, sv_ast_t *node) { +void compile_assign(sv_compiler_t *c, sv_ast_t *node) { sv_ast_t *target = node->left; uint8_t op = node->op; @@ -1978,7 +1793,7 @@ static void compile_assign(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_lhs_set(sv_compiler_t *c, sv_ast_t *target, bool keep) { +void compile_lhs_set(sv_compiler_t *c, sv_ast_t *target, bool keep) { if (target->type == N_IDENT) { emit_set_var(c, target->str, target->len, keep); } else if (target->type == N_MEMBER && !(target->flags & 1)) { @@ -1999,7 +1814,7 @@ static void compile_lhs_set(sv_compiler_t *c, sv_ast_t *target, bool keep) { } } -static void compile_ternary(sv_compiler_t *c, sv_ast_t *node) { +void compile_ternary(sv_compiler_t *c, sv_ast_t *node) { compile_expr(c, node->cond); int else_jump = emit_jump(c, OP_JMP_FALSE); compile_expr(c, node->left); @@ -2009,7 +1824,7 @@ static void compile_ternary(sv_compiler_t *c, sv_ast_t *node) { patch_jump(c, end_jump); } -static void compile_typeof(sv_compiler_t *c, sv_ast_t *node) { +void compile_typeof(sv_compiler_t *c, sv_ast_t *node) { sv_ast_t *arg = node->right; if (arg->type == N_IDENT) { int local = resolve_local(c, arg->str, arg->len); @@ -2068,7 +1883,7 @@ static void compile_delete_optional(sv_compiler_t *c, sv_ast_t *arg) { patch_jump(c, end_jump); } -static void compile_delete(sv_compiler_t *c, sv_ast_t *node) { +void compile_delete(sv_compiler_t *c, sv_ast_t *node) { sv_ast_t *arg = node->right; if (arg->type == N_OPTIONAL) { compile_delete_optional(c, arg); @@ -2092,7 +1907,7 @@ static void compile_delete(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_template(sv_compiler_t *c, sv_ast_t *node) { +void compile_template(sv_compiler_t *c, sv_ast_t *node) { int n = node->args.count; if (n == 0) { emit_constant(c, js_mkstr_permanent(c->js, "", 0)); @@ -2302,7 +2117,7 @@ static void compile_call_optional( compile_optional_call_after_setup(c, node, kind, has_spread); } -static void compile_call(sv_compiler_t *c, sv_ast_t *node) { +void compile_call(sv_compiler_t *c, sv_ast_t *node) { sv_ast_t *callee = node->left; bool has_spread = call_has_spread_arg(node); @@ -2371,7 +2186,7 @@ static void compile_call(sv_compiler_t *c, sv_ast_t *node) { compile_call_emit_invoke(c, node, kind, has_spread); } -static void compile_new(sv_compiler_t *c, sv_ast_t *node) { +void compile_new(sv_compiler_t *c, sv_ast_t *node) { compile_expr(c, node->left); emit_op(c, OP_DUP); if (call_has_spread_arg(node)) { @@ -2387,7 +2202,7 @@ static void compile_new(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_member(sv_compiler_t *c, sv_ast_t *node) { +void compile_member(sv_compiler_t *c, sv_ast_t *node) { if (is_ident_name(node->left, "super")) { emit_op(c, OP_THIS); emit_get_var(c, "super", 5); @@ -2423,7 +2238,7 @@ static void compile_member(sv_compiler_t *c, sv_ast_t *node) { if (end_jump >= 0) patch_jump(c, end_jump); } -static void compile_optional_get(sv_compiler_t *c, sv_ast_t *node) { +void compile_optional_get(sv_compiler_t *c, sv_ast_t *node) { if (node->flags & 1) { compile_expr(c, node->right); emit_op(c, OP_GET_ELEM_OPT); @@ -2433,12 +2248,12 @@ static void compile_optional_get(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_optional(sv_compiler_t *c, sv_ast_t *node) { +void compile_optional(sv_compiler_t *c, sv_ast_t *node) { compile_expr(c, node->left); compile_optional_get(c, node); } -static void compile_array(sv_compiler_t *c, sv_ast_t *node) { +void compile_array(sv_compiler_t *c, sv_ast_t *node) { int count = node->args.count; bool has_spread = false; for (int i = 0; i < count; i++) { @@ -2471,7 +2286,7 @@ static void compile_array(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_object(sv_compiler_t *c, sv_ast_t *node) { +void compile_object(sv_compiler_t *c, sv_ast_t *node) { emit_op(c, OP_OBJECT); for (int i = 0; i < node->args.count; i++) { sv_ast_t *prop = node->args.items[i]; @@ -2528,7 +2343,7 @@ static void compile_object(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_func_expr(sv_compiler_t *c, sv_ast_t *node) { +void compile_func_expr(sv_compiler_t *c, sv_ast_t *node) { bool has_name = node->str && node->len > 0 && !(node->flags & FN_ARROW); int name_local = -1; @@ -2753,12 +2568,12 @@ static void compile_destructure_pattern( if (consume_source) emit_op(c, OP_POP); } -static void compile_array_destructure(sv_compiler_t *c, sv_ast_t *pat, +void compile_array_destructure(sv_compiler_t *c, sv_ast_t *pat, bool keep) { compile_destructure_pattern(c, pat, keep, true, DESTRUCTURE_ASSIGN, SV_VAR_LET); } -static void compile_object_destructure(sv_compiler_t *c, sv_ast_t *pat, +void compile_object_destructure(sv_compiler_t *c, sv_ast_t *pat, bool keep) { compile_destructure_pattern(c, pat, keep, true, DESTRUCTURE_ASSIGN, SV_VAR_LET); } @@ -2792,7 +2607,7 @@ static void compile_tail_call(sv_compiler_t *c, sv_ast_t *node) { emit_u16(c, (uint16_t)argc); } -static void compile_tail_return_expr(sv_compiler_t *c, sv_ast_t *expr) { +void compile_tail_return_expr(sv_compiler_t *c, sv_ast_t *expr) { if (expr->type == N_TERNARY) { compile_expr(c, expr->cond); int else_jump = emit_jump(c, OP_JMP_FALSE); @@ -2819,12 +2634,12 @@ static void compile_tail_return_expr(sv_compiler_t *c, sv_ast_t *expr) { emit_op(c, OP_RETURN); } -static void compile_stmts(sv_compiler_t *c, sv_ast_list_t *list) { +void compile_stmts(sv_compiler_t *c, sv_ast_list_t *list) { for (int i = 0; i < list->count; i++) compile_stmt(c, list->items[i]); } -static void compile_stmt(sv_compiler_t *c, sv_ast_t *node) { +void compile_stmt(sv_compiler_t *c, sv_ast_t *node) { if (!node) return; emit_srcpos(c, node); @@ -2944,7 +2759,7 @@ enum { IMPORT_BIND_NAMESPACE = 1 << 1, }; -static void compile_import_decl(sv_compiler_t *c, sv_ast_t *node) { +void compile_import_decl(sv_compiler_t *c, sv_ast_t *node) { if (!node->right) return; bool repl_top = is_repl_top_level(c); @@ -3065,7 +2880,7 @@ static void compile_export_pattern(sv_compiler_t *c, sv_ast_t *pat) { } } -static void compile_export_decl(sv_compiler_t *c, sv_ast_t *node) { +void compile_export_decl(sv_compiler_t *c, sv_ast_t *node) { if (!node) return; if (node->flags & EX_DEFAULT) { @@ -3151,7 +2966,7 @@ static void compile_export_decl(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_var_decl(sv_compiler_t *c, sv_ast_t *node) { +void compile_var_decl(sv_compiler_t *c, sv_ast_t *node) { sv_var_kind_t kind = node->var_kind; bool is_const = (kind == SV_VAR_CONST); bool repl_top = is_repl_top_level(c); @@ -3215,7 +3030,7 @@ static void compile_var_decl(sv_compiler_t *c, sv_ast_t *node) { } } -static void compile_destructure_binding(sv_compiler_t *c, sv_ast_t *pat, +void compile_destructure_binding(sv_compiler_t *c, sv_ast_t *pat, sv_var_kind_t kind) { compile_destructure_pattern(c, pat, false, false, DESTRUCTURE_BIND, kind); } @@ -3255,7 +3070,7 @@ static bool fold_static_typeof_compare( } -static void compile_if(sv_compiler_t *c, sv_ast_t *node) { +void compile_if(sv_compiler_t *c, sv_ast_t *node) { bool folded_truth = false; if (fold_static_typeof_compare(c, node->cond, &folded_truth)) { if (folded_truth) compile_stmt(c, node->left); @@ -3277,7 +3092,7 @@ static void compile_if(sv_compiler_t *c, sv_ast_t *node) { } -static void compile_while(sv_compiler_t *c, sv_ast_t *node) { +void compile_while(sv_compiler_t *c, sv_ast_t *node) { int loop_start = c->code_len; push_loop(c, loop_start, NULL, 0, false); @@ -3295,7 +3110,7 @@ static void compile_while(sv_compiler_t *c, sv_ast_t *node) { } -static void compile_do_while(sv_compiler_t *c, sv_ast_t *node) { +void compile_do_while(sv_compiler_t *c, sv_ast_t *node) { int loop_start = c->code_len; push_loop(c, loop_start, NULL, 0, false); @@ -3378,7 +3193,7 @@ static void for_collect_var_decl_slots(sv_compiler_t *c, sv_ast_t *init_var, } } -static void compile_for(sv_compiler_t *c, sv_ast_t *node) { +void compile_for(sv_compiler_t *c, sv_ast_t *node) { begin_scope(c); int *iter_slots = NULL; @@ -3408,20 +3223,17 @@ static void compile_for(sv_compiler_t *c, sv_ast_t *node) { int iter_inner_start = -1; if (iter_count > 0) { - begin_scope(c); - iter_inner_start = c->local_count; - for (int i = 0; i < iter_count; i++) { - int outer_idx = iter_slots[i]; - sv_local_t outer = c->locals[outer_idx]; - emit_get_local(c, outer_idx); - int inner_idx = add_local(c, outer.name, outer.name_len, - outer.is_const, c->scope_depth); - emit_put_local(c, inner_idx); - } - } + begin_scope(c); + iter_inner_start = c->local_count; + for (int i = 0; i < iter_count; i++) { + int outer_idx = iter_slots[i]; + sv_local_t outer = c->locals[outer_idx]; + emit_get_local(c, outer_idx); + int inner_idx = add_local(c, outer.name, outer.name_len, outer.is_const, c->scope_depth); + emit_put_local(c, inner_idx); + }} compile_stmt(c, node->body); - sv_loop_t *loop = &c->loops[c->loop_count - 1]; for (int i = 0; i < loop->continues.count; i++) patch_jump(c, loop->continues.offsets[i]); @@ -3479,12 +3291,12 @@ static void compile_for(sv_compiler_t *c, sv_ast_t *node) { static void compile_for_each(sv_compiler_t *c, sv_ast_t *node, bool is_for_of); -static void compile_for_in(sv_compiler_t *c, sv_ast_t *node) { +void compile_for_in(sv_compiler_t *c, sv_ast_t *node) { compile_for_each(c, node, false); } -static void compile_for_of(sv_compiler_t *c, sv_ast_t *node) { +void compile_for_of(sv_compiler_t *c, sv_ast_t *node) { compile_for_each(c, node, true); } @@ -3709,7 +3521,7 @@ for (int i = c->local_count - 1; i >= 0; i--) { } }} -static void compile_break(sv_compiler_t *c, sv_ast_t *node) { +void compile_break(sv_compiler_t *c, sv_ast_t *node) { if (c->loop_count == 0) return; int target = c->loop_count - 1; @@ -3730,7 +3542,7 @@ static void compile_break(sv_compiler_t *c, sv_ast_t *node) { } -static void compile_continue(sv_compiler_t *c, sv_ast_t *node) { +void compile_continue(sv_compiler_t *c, sv_ast_t *node) { for (int i = c->loop_count - 1; i >= 0; i--) { if (node->str) { if ( @@ -3758,20 +3570,18 @@ static void compile_finally_block(sv_compiler_t *c, sv_ast_t *finally_body) { static void compile_catch_body(sv_compiler_t *c, sv_ast_t *node) { begin_scope(c); if (node->catch_param && node->catch_param->type == N_IDENT) { - int loc = add_local(c, node->catch_param->str, - node->catch_param->len, false, c->scope_depth); + int loc = add_local(c, node->catch_param->str, node->catch_param->len, false, c->scope_depth); emit_put_local(c, loc); } else if (node->catch_param && is_destructure_pattern_node(node->catch_param)) { compile_destructure_binding(c, node->catch_param, SV_VAR_LET); emit_op(c, OP_POP); - } else { - emit_op(c, OP_POP); - } + } else emit_op(c, OP_POP); + compile_stmt(c, node->catch_body); end_scope(c); } -static void compile_try(sv_compiler_t *c, sv_ast_t *node) { +void compile_try(sv_compiler_t *c, sv_ast_t *node) { c->try_depth++; int try_jump = emit_jump(c, OP_TRY_PUSH); @@ -3829,7 +3639,7 @@ static void compile_try(sv_compiler_t *c, sv_ast_t *node) { } -static void compile_switch(sv_compiler_t *c, sv_ast_t *node) { +void compile_switch(sv_compiler_t *c, sv_ast_t *node) { int case_count = node->args.count; int default_case = -1; int *match_to_stub = NULL; @@ -3899,12 +3709,13 @@ static void compile_switch(sv_compiler_t *c, sv_ast_t *node) { static inline bool is_loop_node(sv_ast_t *n) { - return n && (n->type == N_WHILE || n->type == N_DO_WHILE || - n->type == N_FOR || n->type == N_FOR_IN || - n->type == N_FOR_OF || n->type == N_FOR_AWAIT_OF); + return n && + (n->type == N_WHILE || n->type == N_DO_WHILE || + n->type == N_FOR || n->type == N_FOR_IN || + n->type == N_FOR_OF || n->type == N_FOR_AWAIT_OF); } -static void compile_label(sv_compiler_t *c, sv_ast_t *node) { +void compile_label(sv_compiler_t *c, sv_ast_t *node) { if (is_loop_node(node->body)) { c->pending_label = node->str; c->pending_label_len = node->len; @@ -4003,11 +3814,13 @@ static inline int compile_class_precompute_key(sv_compiler_t *c, sv_ast_t *key_e return loc; } -static void compile_class(sv_compiler_t *c, sv_ast_t *node) { +void compile_class(sv_compiler_t *c, sv_ast_t *node) { int outer_name_local = -1; bool class_repl_top = is_repl_top_level(c); + sv_ast_t *ctor_method = NULL; bool has_static_name = false; + int field_count = 0; int computed_method_count = 0; @@ -4089,16 +3902,20 @@ static void compile_class(sv_compiler_t *c, sv_ast_t *node) { c->computed_key_locals = NULL; } else if (field_count > 0) { c->computed_key_locals = computed_key_locals; - sv_compiler_t comp = {0}; - comp.js = c->js; - comp.filename = c->filename; - comp.source = c->source; - comp.source_len = c->source_len; - comp.line_table = c->line_table; - comp.enclosing = c; - comp.scope_depth = 0; - comp.is_strict = c->is_strict; - comp.param_count = 0; + sv_compiler_t comp; + sv_compile_ctx_init_child(&comp, c, NULL, c->mode); + + if (node->left) { + emit_op(&comp, OP_THIS); + emit_op(&comp, OP_SPECIAL_OBJ); + emit(&comp, 2); + emit_op(&comp, OP_SWAP); + emit_op(&comp, OP_SPECIAL_OBJ); + emit(&comp, 0); + emit_op(&comp, OP_SUPER_APPLY); + emit_u16(&comp, 1); + emit_op(&comp, OP_POP); + } emit_field_inits(&comp, field_inits, field_count); emit_op(&comp, OP_RETURN_UNDEF); @@ -4153,9 +3970,7 @@ static void compile_class(sv_compiler_t *c, sv_ast_t *node) { name[node->len] = '\0'; fn->name = name; } - free(comp.code); free(comp.constants); free(comp.atoms); - free(comp.locals); free(comp.upval_descs); free(comp.loops); - free(comp.slot_types); + sv_compile_ctx_cleanup(&comp); int idx = add_constant(c, mkval(T_CFUNC, (uintptr_t)fn)); emit_op(c, OP_CLOSURE); @@ -4182,6 +3997,11 @@ static void compile_class(sv_compiler_t *c, sv_ast_t *node) { emit_put_local(c, proto_local); emit_put_local(c, ctor_local); + if (inner_name_local >= 0) { + emit_get_local(c, ctor_local); + emit_put_local(c, inner_name_local); + } + for (int i = 0; i < node->args.count; i++) { sv_ast_t *m = node->args.items[i]; if (m->type == N_STATIC_BLOCK) { @@ -4206,11 +4026,6 @@ static void compile_class(sv_compiler_t *c, sv_ast_t *node) { free(method_comp_keys); emit_get_local(c, ctor_local); - if (inner_name_local >= 0) { - emit_op(c, OP_DUP); - emit_put_local(c, inner_name_local); - } - if (class_repl_top && node->str) { emit_op(c, OP_DUP); emit_atom_op(c, OP_PUT_GLOBAL, node->str, node->len); @@ -4223,27 +4038,13 @@ static void compile_class(sv_compiler_t *c, sv_ast_t *node) { if (node->str) end_scope(c); } -static sv_func_t *compile_function_body( +sv_func_t *compile_function_body( sv_compiler_t *enclosing, sv_ast_t *node, sv_compile_mode_t mode ) { - sv_compiler_t comp = {0}; - comp.js = enclosing->js; - comp.filename = enclosing->filename; - comp.source = enclosing->source; - comp.source_len = enclosing->source_len; - comp.line_table = enclosing->line_table; - comp.enclosing = enclosing; - comp.scope_depth = 0; - comp.is_arrow = !!(node->flags & FN_ARROW); - comp.is_async = !!(node->flags & FN_ASYNC); - comp.is_strict = enclosing->is_strict; - comp.mode = mode; - comp.strict_args_local = -1; - comp.new_target_local = -1; - comp.super_local = -1; - comp.param_count = node->args.count; + sv_compiler_t comp; + sv_compile_ctx_init_child(&comp, enclosing, node, mode); for (int i = 0; i < node->args.count; i++) { sv_ast_t *p = node->args.items[i]; @@ -4473,16 +4274,17 @@ static sv_func_t *compile_function_body( } free(param_bind_locals); + sv_ast_t *body = node->body; - if (node->body) { - if (node->body->type == N_BLOCK) { - if (!repl_top) { - for (int i = 0; i < node->body->args.count; i++) - hoist_var_decls(&comp, node->body->args.items[i]); - hoist_lexical_decls(&comp, &node->body->args); - } - hoist_func_decls(&comp, &node->body->args); - } else if (!repl_top) hoist_var_decls(&comp, node->body); + if (body && body->type != N_BLOCK) { + if (!repl_top) hoist_var_decls(&comp, body); + } else if (body) { + if (!repl_top) { + for (int i = 0; i < body->args.count; i++) + hoist_var_decls(&comp, body->args.items[i]); + hoist_lexical_decls(&comp, &body->args); + } + hoist_func_decls(&comp, &body->args); } } @@ -4630,35 +4432,20 @@ static sv_func_t *compile_function_body( } if (func->is_async) { - const uint8_t *ip = func->code; - const uint8_t *end = func->code + func->code_len; - while (ip < end) { - sv_op_t op = (sv_op_t)*ip; - if (op == OP_AWAIT || op == OP_FOR_AWAIT_OF || op == OP_AWAIT_ITER_NEXT) { - func->has_await = true; - break; - } - int sz = sv_op_size[op]; - if (sz <= 0) break; - ip += sz; + const uint8_t *ip = func->code; + const uint8_t *end = func->code + func->code_len; + while (ip < end) { + sv_op_t op = (sv_op_t)*ip; + if (op == OP_AWAIT || op == OP_FOR_AWAIT_OF || op == OP_AWAIT_ITER_NEXT) { + func->has_await = true; + break; } - } - - free(comp.code); - free(comp.constants); - free(comp.atoms); - free(comp.locals); - free(comp.upval_descs); - free(comp.loops); - free(comp.srcpos); - free(comp.slot_types); - - { - const_dedup_entry_t *e, *tmp; - HASH_ITER(hh, comp.const_dedup, e, tmp) { - HASH_DEL(comp.const_dedup, e); free(e); + int sz = sv_op_size[op]; + if (sz <= 0) break; + ip += sz; }} + sv_compile_ctx_cleanup(&comp); return func; } @@ -4830,17 +4617,21 @@ void sv_disasm(ant_t *js, sv_func_t *func, const char *label) { fprintf(stderr, "\n"); for (int i = 0; i < func->const_count; i++) { - if (vtype(func->constants[i]) == T_CFUNC) { - sv_func_t *child = (sv_func_t *)(uintptr_t)vdata(func->constants[i]); - char child_label[256]; - snprintf(child_label, sizeof(child_label), "%s/closure[%d]", label, i); - sv_disasm(js, child, child_label); - } - } + if (vtype(func->constants[i]) == T_CFUNC) { + sv_func_t *child = (sv_func_t *)(uintptr_t)vdata(func->constants[i]); + char child_label[256]; + snprintf(child_label, sizeof(child_label), "%s/closure[%d]", label, i); + sv_disasm(js, child, child_label); + }} } sv_func_t *sv_compile(ant_t *js, sv_ast_t *program, sv_compile_mode_t mode, const char *source, ant_offset_t source_len) { if (!program || program->type != N_PROGRAM) return NULL; + if (sv_compile_trace_unlikely) fprintf( + stderr, "[compile] start kind=program mode=%d len=%u body=%d strict=%d\n", + (int)mode, (unsigned)source_len, + program->args.count, (program->flags & FN_PARSE_STRICT) != 0 ? 1 : 0 + ); static const char *k_top_name_script = "