mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-09 20:38:11 +08:00
Merge main into EventTarget
This commit is contained in:
@@ -596,11 +596,11 @@ ScrDyn *scr_await_dyn_value_settled(ScrDyn *value) {
|
||||
|
||||
/* ── process warnings (emitWarning + the 'warning' event) ─────────────
|
||||
* Gated with the rest of this TU (a deprecation-emitting unit's gate
|
||||
* must imply the dynAsync link). Listeners are dyn functions; emission
|
||||
* is SYNCHRONOUS at the
|
||||
* call (Node defers a tick through nextTick — the MaxListenersExceeded
|
||||
* precedent, SEMANTICS.md 138) and the default stderr report always
|
||||
* prints (Node's own bootstrap listener; a compiled binary has no
|
||||
* must imply the dynAsync link). Listeners are dyn functions; the
|
||||
* warning is built at the call and emitted on the next tick, as Node's
|
||||
* emitWarning does (scr_warning_emit_deferred). The default stderr report
|
||||
* prints for every warning except deprecations under process.noDeprecation
|
||||
* (Node's own bootstrap listener; a compiled binary has no
|
||||
* --no-warnings). The warning VALUE is the dyn error encoding built over
|
||||
* an ScrError (identity-cached, so a listener comparing two deliveries
|
||||
* of one warning sees one object); a string `detail` joins the dyn node
|
||||
@@ -649,14 +649,71 @@ void scr_process_off_warning(ScrDyn *fn) {
|
||||
}
|
||||
}
|
||||
|
||||
/* process.noDeprecation (0) and process.throwDeprecation (1): ordinary
|
||||
* writable process properties, undefined until the program assigns them.
|
||||
* A compiled binary takes no Node command-line flags, so only those
|
||||
* assignments set them. */
|
||||
static SCR_TL ScrDyn *scr_deprecation_flags[2] = {NULL, NULL};
|
||||
static SCR_TL bool scr_deprecation_flags_registered = false;
|
||||
|
||||
static void scr_deprecation_flags_teardown(void) {
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
scr_dyn_release(scr_deprecation_flags[i]);
|
||||
scr_deprecation_flags[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ScrDyn *scr_process_deprecation_flag(double which) {
|
||||
ScrDyn *value = scr_deprecation_flags[which != 0];
|
||||
return scr_dyn_retain(value ? value : scr_dyn_undefined());
|
||||
}
|
||||
|
||||
void scr_process_set_deprecation_flag(double which, ScrDyn *value) {
|
||||
if (!scr_deprecation_flags_registered) {
|
||||
scr_deprecation_flags_registered = true;
|
||||
scr_atexit(scr_deprecation_flags_teardown);
|
||||
}
|
||||
size_t slot = which != 0;
|
||||
ScrDyn *previous = scr_deprecation_flags[slot];
|
||||
scr_deprecation_flags[slot] = scr_dyn_retain(value);
|
||||
scr_dyn_release(previous);
|
||||
}
|
||||
|
||||
bool scr_process_deprecation_flag_on(int which) {
|
||||
const ScrDyn *value = scr_deprecation_flags[which != 0];
|
||||
return value != NULL && scr_dyn_truthy(value);
|
||||
}
|
||||
|
||||
/* The warning's name as warning.toString() reads it: the own property, or
|
||||
* the Error class name an emitted Error instance inherits. +1 or NULL. */
|
||||
static ScrStr *scr_warning_name(const ScrDyn *w) {
|
||||
const ScrDyn *en = scr_dyn_obj_get(w, "name", 4);
|
||||
if (en) return en->kind == SCR_DYN_STR ? scr_str_retain(en->v.str) : NULL;
|
||||
ScrError *e = scr_errdyn_err_of(w);
|
||||
if (!e) return NULL;
|
||||
ScrStr *name = e->name ? scr_str_retain(e->name) : scr_error_default_name(e);
|
||||
scr_error_release(e);
|
||||
return name;
|
||||
}
|
||||
|
||||
static bool scr_warning_is_deprecation(const ScrDyn *w) {
|
||||
ScrStr *name = scr_warning_name(w);
|
||||
bool deprecation = name && name->len == 18 && memcmp(name->data, "DeprecationWarning", 18) == 0;
|
||||
scr_str_release(name);
|
||||
return deprecation;
|
||||
}
|
||||
|
||||
/* The default stderr report + dispatch over a built warning dyn node.
|
||||
* Borrowed. A listener throw propagates (the dc publish stance). */
|
||||
static void scr_warning_dispatch(ScrDyn *w) {
|
||||
/* "(node:pid) [CODE] Name: message" + "\n<detail>" — Node's
|
||||
* onWarning report, written as ONE console.error line (so the global
|
||||
* console's group indentation applies to each of its lines). Node
|
||||
* registers that listener at startup, so it runs before the program's. */
|
||||
const ScrDyn *en = scr_dyn_obj_get(w, "name", 4);
|
||||
* registers that listener at startup, so it runs before the program's,
|
||||
* and skips deprecations while process.noDeprecation is set. */
|
||||
bool deprecation = scr_warning_is_deprecation(w);
|
||||
if (deprecation && scr_process_deprecation_flag_on(0)) goto listeners;
|
||||
ScrStr *name = scr_warning_name(w);
|
||||
const ScrDyn *em = scr_dyn_obj_get(w, "message", 7);
|
||||
const ScrDyn *ec = scr_dyn_obj_get(w, "code", 4);
|
||||
const ScrDyn *ed = scr_dyn_obj_get(w, "detail", 6);
|
||||
@@ -670,24 +727,29 @@ static void scr_warning_dispatch(ScrDyn *w) {
|
||||
scr_jb_put_str(&report, ec->v.str);
|
||||
scr_jb_puts(&report, "] ");
|
||||
}
|
||||
if (en && en->kind == SCR_DYN_STR) scr_jb_put_str(&report, en->v.str);
|
||||
if (name) scr_jb_put_str(&report, name);
|
||||
else scr_jb_puts(&report, "Warning");
|
||||
scr_str_release(name);
|
||||
scr_jb_puts(&report, ": ");
|
||||
if (em && em->kind == SCR_DYN_STR) scr_jb_put_str(&report, em->v.str);
|
||||
if (ed && ed->kind == SCR_DYN_STR) {
|
||||
scr_jb_puts(&report, "\n");
|
||||
scr_jb_put_str(&report, ed->v.str);
|
||||
}
|
||||
/* Node's one-time trace hint, after the first report. */
|
||||
/* Node's one-time trace hint, after the first report; deprecations
|
||||
* name their own trace flag. */
|
||||
static SCR_TL bool hinted = false;
|
||||
if (!hinted) {
|
||||
hinted = true;
|
||||
scr_jb_puts(&report, "\n(Use `node --trace-warnings ...` to show where the warning was created)");
|
||||
scr_jb_puts(&report, deprecation
|
||||
? "\n(Use `node --trace-deprecation ...` to show where the warning was created)"
|
||||
: "\n(Use `node --trace-warnings ...` to show where the warning was created)");
|
||||
}
|
||||
ScrLogArg line = {.tag = SCR_ARG_STR};
|
||||
line.v.s = scr_jb_finish(&report);
|
||||
scr_console_error(1, &line);
|
||||
scr_str_release(line.v.s);
|
||||
listeners:
|
||||
for (size_t i = 0; i < scr_nwarn; i++) {
|
||||
ScrDyn *r = scr_dyn_call(scr_warn_listeners[i], &w, 1, "listener");
|
||||
if (r == NULL) return; /* pending exception propagates */
|
||||
@@ -695,20 +757,63 @@ static void scr_warning_dispatch(ScrDyn *w) {
|
||||
}
|
||||
}
|
||||
|
||||
/* A warning built from C parts (the runtime deprecation sites): name
|
||||
* defaults to "Warning"; code/detail optional. Borrows message. */
|
||||
void scr_emit_warning(const char *name, const char *code, ScrStr *message) {
|
||||
/* A string warning with a type and an optional code, as
|
||||
* process.emitWarning(message, type, code) builds it. Borrows; +1. */
|
||||
ScrDyn *scr_warning_new(ScrStr *message, const char *type, const ScrStr *code) {
|
||||
ScrError *e = scr_error_new(SCR_ERR_ERROR, message);
|
||||
scr_str_release(e->name);
|
||||
e->name = scr_str_new(name ? name : "Warning", strlen(name ? name : "Warning"));
|
||||
e->name = scr_str_new(type, strlen(type));
|
||||
e->name_present = true;
|
||||
if (code) e->code = scr_str_new(code, strlen(code));
|
||||
if (code) e->code = scr_str_retain((ScrStr *)code);
|
||||
ScrDyn *w = scr_dyn_from_error(e);
|
||||
scr_error_release(e);
|
||||
return w;
|
||||
}
|
||||
|
||||
/* A warning built from C parts (the runtime deprecation sites): name
|
||||
* defaults to "Warning"; code/detail optional. Reported synchronously.
|
||||
* Borrows message. */
|
||||
void scr_emit_warning(const char *name, const char *code, ScrStr *message) {
|
||||
ScrStr *code_text = code ? scr_str_new(code, strlen(code)) : NULL;
|
||||
ScrDyn *w = scr_warning_new(message, name ? name : "Warning", code_text);
|
||||
scr_str_release(code_text);
|
||||
scr_warning_dispatch(w);
|
||||
scr_dyn_release(w);
|
||||
}
|
||||
|
||||
/* Node's emitWarning tail for runtime-built warnings: deprecations are
|
||||
* dropped while process.noDeprecation is set and thrown from their own
|
||||
* tick while process.throwDeprecation is set; every other warning reaches
|
||||
* the report and the 'warning' listeners on the next tick. */
|
||||
static void scr_warning_tick(ScrClosure *c) {
|
||||
ScrDyn *w = scr_box_get_ref(c->caps[0]);
|
||||
scr_warning_dispatch(w);
|
||||
scr_dyn_release(w);
|
||||
}
|
||||
|
||||
static void scr_warning_throw_tick(ScrClosure *c) {
|
||||
ScrDyn *w = scr_box_get_ref(c->caps[0]);
|
||||
scr_throw_ref(w, scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v);
|
||||
}
|
||||
|
||||
static void scr_warning_defer(ScrDyn *w, void (*fire)(ScrClosure *)) {
|
||||
ScrClosure *c = scr_closure_new((void *)fire, 1);
|
||||
c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v);
|
||||
scr_box_set_ref(c->caps[0], scr_dyn_retain(w));
|
||||
scr_next_tick(c);
|
||||
}
|
||||
|
||||
void scr_warning_emit_deferred(ScrDyn *w) {
|
||||
if (scr_warning_is_deprecation(w)) {
|
||||
if (scr_process_deprecation_flag_on(0)) return;
|
||||
if (scr_process_deprecation_flag_on(1)) {
|
||||
scr_warning_defer(w, scr_warning_throw_tick);
|
||||
return;
|
||||
}
|
||||
}
|
||||
scr_warning_defer(w, scr_warning_tick);
|
||||
}
|
||||
|
||||
/* The console members' warnings. Node's emitWarning emits on
|
||||
* process.nextTick, so these queue in order and each rides one raw tick
|
||||
* on the shared tick queue; any left at exit are dropped with it. */
|
||||
@@ -761,7 +866,8 @@ static void scr_warn_bad_arg(const char *arg, const char *want) {
|
||||
* values: (warning: string | Error), then for string warnings a type
|
||||
* string / ctor function / options object ({type, code, detail}) second
|
||||
* and a code string / ctor function third. Wrong kinds throw Node's
|
||||
* ERR_INVALID_ARG_TYPE TypeErrors; non-string details are ignored. */
|
||||
* ERR_INVALID_ARG_TYPE TypeErrors at the call; non-string details are
|
||||
* ignored. The warning itself is emitted on the next tick. */
|
||||
void scr_process_emit_warning(ScrDyn *args) {
|
||||
size_t argc = args->kind == SCR_DYN_ARR ? args->v.arr.len : 0;
|
||||
ScrDyn *const *items = args->kind == SCR_DYN_ARR ? args->v.arr.items : NULL;
|
||||
@@ -769,7 +875,7 @@ void scr_process_emit_warning(ScrDyn *args) {
|
||||
/* An Error-encoded warning: type/code arguments are ignored (Node). */
|
||||
if (warning != NULL && warning->kind == SCR_DYN_OBJ &&
|
||||
scr_dyn_obj_get(warning, "%error", 6) != NULL) {
|
||||
scr_warning_dispatch(warning);
|
||||
scr_warning_emit_deferred(warning);
|
||||
return;
|
||||
}
|
||||
if (warning == NULL || warning->kind != SCR_DYN_STR) {
|
||||
@@ -824,7 +930,7 @@ void scr_process_emit_warning(ScrDyn *args) {
|
||||
if (detail) {
|
||||
scr_dyn_obj_set(w, "detail", 6, scr_dyn_new_str((ScrStr *)detail)); /* retains */
|
||||
}
|
||||
scr_warning_dispatch(w);
|
||||
scr_warning_emit_deferred(w);
|
||||
scr_dyn_release(w);
|
||||
}
|
||||
|
||||
|
||||
@@ -2973,7 +2973,16 @@ const char *scr_dyn_specific_type(const ScrDyn *cb, char *detail, size_t cap) {
|
||||
switch (cb->kind) {
|
||||
case SCR_DYN_NULL: d = "null"; break;
|
||||
case SCR_DYN_UNDEF: d = "undefined"; break;
|
||||
case SCR_DYN_OBJ: d = "an instance of Object"; break;
|
||||
case SCR_DYN_OBJ:
|
||||
if (cb->null_proto) {
|
||||
/* No constructor name: Node renders inspect(value, { depth: -1 }). */
|
||||
bool keys = false;
|
||||
for (size_t i = 0; i < cb->v.obj.len && !keys; i++) keys = cb->v.obj.entries[i].enumerable;
|
||||
d = keys ? "[Object: null prototype]" : "[Object: null prototype] {}";
|
||||
} else {
|
||||
d = "an instance of Object";
|
||||
}
|
||||
break;
|
||||
case SCR_DYN_ARR: d = "an instance of Array"; break;
|
||||
case SCR_DYN_BYTES:
|
||||
snprintf(detail, cap, "an instance of %s", cb->buffer ? "Buffer" : cb->v.bytes->is_data_view ? "DataView" : scr_bytes_elem_name(cb->v.bytes->elem));
|
||||
|
||||
@@ -738,10 +738,41 @@ ScrStr *scr_env_get(const ScrStr *name) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* util.debuglog matches sections against NODE_DEBUG as the process
|
||||
* started, so the first write to that variable records the value it
|
||||
* replaces. */
|
||||
static SCR_TL bool scr_node_debug_captured = false;
|
||||
static SCR_TL char *scr_node_debug_value = NULL;
|
||||
|
||||
static void scr_node_debug_teardown(void) {
|
||||
free(scr_node_debug_value);
|
||||
scr_node_debug_value = NULL;
|
||||
}
|
||||
|
||||
const char *scr_node_debug_initial(void) {
|
||||
if (!scr_node_debug_captured) {
|
||||
scr_node_debug_captured = true;
|
||||
const char *value = scr_getenv("NODE_DEBUG");
|
||||
if (value) {
|
||||
size_t len = strlen(value);
|
||||
scr_node_debug_value = malloc(len + 1);
|
||||
if (!scr_node_debug_value) scr_trap("scriptc: out of memory\n");
|
||||
memcpy(scr_node_debug_value, value, len + 1);
|
||||
scr_atexit(scr_node_debug_teardown);
|
||||
}
|
||||
}
|
||||
return scr_node_debug_value;
|
||||
}
|
||||
|
||||
static void scr_node_debug_capture(const ScrStr *name) {
|
||||
if (name->len == 10 && memcmp(name->data, "NODE_DEBUG", 10) == 0) (void)scr_node_debug_initial();
|
||||
}
|
||||
|
||||
/* process.env.NAME = v — setenv(3): later scr_env_get reads and spawned
|
||||
* children (posix_spawn inherits environ) observe the write, like Node.
|
||||
* Both args borrowed (NUL-terminated ScrStr data); setenv copies. */
|
||||
void scr_env_set(const ScrStr *name, const ScrStr *value) {
|
||||
scr_node_debug_capture(name);
|
||||
/* The environment holds UTF-8: a lone surrogate is stored as U+FFFD. */
|
||||
char *name_owned, *value_owned;
|
||||
const char *k = scr_str_utf8(name, &name_owned), *v = scr_str_utf8(value, &value_owned);
|
||||
@@ -765,6 +796,7 @@ done:
|
||||
/* `delete process.env.NAME` — unsetenv(3): later reads answer absent and
|
||||
* spawned children lose the variable, like Node. Borrowed. */
|
||||
void scr_env_unset(const ScrStr *name) {
|
||||
scr_node_debug_capture(name);
|
||||
#ifdef SCR_WORKERS
|
||||
if (scr_context_env_set(name->data, NULL)) return;
|
||||
#endif
|
||||
|
||||
@@ -1634,6 +1634,17 @@ void scr_qs_parse_into(ScrMap *out, const ScrStr *qs, const ScrStr *sep,
|
||||
* Borrows config; returns +1, or NULL with a Node-coded TypeError pending. */
|
||||
struct ScrDyn *scr_util_parse_args(const struct ScrDyn *config);
|
||||
ScrStr *scr_util_style_text(const struct ScrDyn *format, const struct ScrDyn *text, const struct ScrDyn *options);
|
||||
/* Node's terminal color decision (FORCE_COLOR, NO_COLOR, TERM, CI hosts)
|
||||
* once the stream is a terminal (scr_util_style.c). */
|
||||
bool scr_util_terminal_colors(void);
|
||||
/* util.inherits, util.deprecate, util.callbackify and util.debuglog
|
||||
* (scr_util_callable.c). Arguments are borrowed; function results are +1
|
||||
* native dyn functions, or NULL with Node's TypeError pending. */
|
||||
void scr_util_inherits(struct ScrDyn *ctor, struct ScrDyn *super_ctor);
|
||||
struct ScrDyn *scr_util_deprecate(struct ScrDyn *fn, struct ScrDyn *msg, struct ScrDyn *code,
|
||||
struct ScrDyn *options);
|
||||
struct ScrDyn *scr_util_callbackify(struct ScrDyn *original);
|
||||
struct ScrDyn *scr_util_debuglog(struct ScrDyn *section, struct ScrDyn *callback);
|
||||
|
||||
/* Node/libuv system error utilities. Arguments are checked without coercion;
|
||||
* entries is a fresh array of [number, [name, message]] pairs (+1). */
|
||||
@@ -3501,6 +3512,8 @@ ScrStr *scr_env_get(const ScrStr *name);
|
||||
* children observe the write, like Node. Both borrowed. Never throws. */
|
||||
void scr_env_set(const ScrStr *name, const ScrStr *value);
|
||||
void scr_env_unset(const ScrStr *name); /* delete process.env.NAME */
|
||||
/* NODE_DEBUG as the process started (NULL when unset), for util.debuglog. */
|
||||
const char *scr_node_debug_initial(void);
|
||||
/* The whole environment as alternating [k, v, k, v, ...] strings in
|
||||
* environ order — the compiler's process.env snapshot builds its record
|
||||
* from this. +1 fresh array. Never throws. */
|
||||
@@ -6108,11 +6121,11 @@ void scr_promise_mark_handled(ScrPromise *p);
|
||||
ScrDyn *scr_promise_reason_dyn(const ScrPromise *p);
|
||||
/* process warnings (scr_lib.c — always linked so any unit can emit a
|
||||
* deprecation): dyn listeners plus Node's default stderr report
|
||||
* ("(node:pid) [CODE] Name: message" and a detail second line). Emission
|
||||
* is SYNCHRONOUS at the call (Node defers a tick — SEMANTICS.md 138's
|
||||
* precedent). scr_process_emit_warning takes the ARGUMENT VECTOR as one
|
||||
* dyn array and applies Node's full grammar/TypeErrors; scr_emit_warning
|
||||
* is the C-side deprecation entry. */
|
||||
* ("(node:pid) [CODE] Name: message" and a detail second line).
|
||||
* scr_process_emit_warning takes the ARGUMENT VECTOR as one dyn array,
|
||||
* applies Node's full grammar/TypeErrors at the call and emits on the
|
||||
* next tick, as Node does; scr_emit_warning is the C-side entry and
|
||||
* reports synchronously. */
|
||||
void scr_process_on_warning(ScrDyn *fn);
|
||||
void scr_process_off_warning(ScrDyn *fn);
|
||||
void scr_process_emit_warning(ScrDyn *args);
|
||||
@@ -6121,6 +6134,19 @@ void scr_emit_warning(const char *name, const char *code, ScrStr *message);
|
||||
* listener registration): units that cannot link the dyn-async unit route
|
||||
* their warnings through it, and print the default report otherwise. */
|
||||
extern SCR_TL void (*scr_warning_emit_hook)(const char *name, const char *code, ScrStr *message);
|
||||
/* process.noDeprecation (which 0) and process.throwDeprecation (which 1):
|
||||
* the stored value (+1; undefined until assigned), the assignment
|
||||
* (borrowed), and the truthiness the warning paths test. */
|
||||
ScrDyn *scr_process_deprecation_flag(double which);
|
||||
void scr_process_set_deprecation_flag(double which, ScrDyn *value);
|
||||
bool scr_process_deprecation_flag_on(int which);
|
||||
/* Node's emitWarning tail on the next tick (util.deprecate): a
|
||||
* DeprecationWarning is dropped under noDeprecation and thrown from its
|
||||
* own tick under throwDeprecation; otherwise the report and the 'warning'
|
||||
* listeners run. scr_warning_new builds the string form's warning value
|
||||
* (borrows; +1). */
|
||||
void scr_warning_emit_deferred(ScrDyn *warning);
|
||||
ScrDyn *scr_warning_new(ScrStr *message, const char *type, const ScrStr *code);
|
||||
/* The promise-or-absent await's unit arm: one microtask hop, like JS's
|
||||
* await of any non-thenable (and like awaiting a settled promise). */
|
||||
void scr_await_hop(void);
|
||||
|
||||
@@ -0,0 +1,894 @@
|
||||
/* node:util's function helpers over the checked-dynamic tree: inherits,
|
||||
* deprecate, callbackify and debuglog. The returned wrappers are native
|
||||
* dyn functions whose closures hold the wrapped values, so they keep
|
||||
* Node's receiver forwarding, own properties and prototype links through
|
||||
* every boxing of the same function. The compiled call site converts a
|
||||
* wrapper back to the program's static function type when it has one.
|
||||
*
|
||||
* Warnings follow Node's emitWarning: deprecations are built at the call
|
||||
* and reported on the next tick (scr_warning_emit_deferred), which also
|
||||
* applies process.noDeprecation and process.throwDeprecation. debuglog
|
||||
* matches sections against NODE_DEBUG as the process started and writes
|
||||
* Node's "SECTION PID: message" lines to stderr. All arguments are
|
||||
* borrowed. */
|
||||
#include "scr_runtime.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static ScrBox *uc_box(ScrDyn *value /* moves in */) {
|
||||
ScrBox *box = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v);
|
||||
scr_box_set_ref(box, value);
|
||||
return box;
|
||||
}
|
||||
|
||||
static ScrDyn *uc_cap(ScrClosure *closure, size_t index) {
|
||||
return scr_box_get_ref(closure->caps[index]); /* +1 */
|
||||
}
|
||||
|
||||
static void uc_set_cap(ScrClosure *closure, size_t index, ScrDyn *value /* moves in */) {
|
||||
scr_box_set_ref(closure->caps[index], value);
|
||||
}
|
||||
|
||||
static bool uc_nullish(const ScrDyn *value) {
|
||||
return value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL;
|
||||
}
|
||||
|
||||
static bool uc_object(const ScrDyn *value) {
|
||||
switch (value->kind) {
|
||||
case SCR_DYN_OBJ:
|
||||
case SCR_DYN_ARR:
|
||||
case SCR_DYN_FUNC:
|
||||
case SCR_DYN_BYTES:
|
||||
case SCR_DYN_HANDLE:
|
||||
case SCR_DYN_PROMISE:
|
||||
case SCR_DYN_TYPED_REF:
|
||||
case SCR_DYN_PROXY:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void uc_type_error(const char *message) {
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message));
|
||||
}
|
||||
|
||||
static void uc_fence(const char *message) {
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "SC2020");
|
||||
}
|
||||
|
||||
/* `value[key]`: own and inherited members of object kinds, undefined for
|
||||
* primitives (whose prototypes carry none of the keys read here). +1, or
|
||||
* NULL with an exception pending. */
|
||||
static ScrDyn *uc_get(const ScrDyn *value, const char *key) {
|
||||
size_t len = strlen(key);
|
||||
ScrDyn *result = NULL;
|
||||
if (value->kind == SCR_DYN_FUNC) {
|
||||
result = scr_dyn_fn_get(value, key, len);
|
||||
} else if (value->kind == SCR_DYN_TYPED_REF) {
|
||||
ScrDyn *view = scr_dyn_typed_ref_materialize(value);
|
||||
if (scr_exc_pending()) {
|
||||
scr_dyn_release(view);
|
||||
return NULL;
|
||||
}
|
||||
result = uc_get(view, key);
|
||||
scr_dyn_release(view);
|
||||
return result;
|
||||
} else if (value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_PROXY) {
|
||||
result = scr_dyn_obj_read(value, key, len);
|
||||
} else {
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
if (!result && !scr_exc_pending()) result = scr_dyn_retain(scr_dyn_undefined());
|
||||
return result;
|
||||
}
|
||||
|
||||
/* V8's rendering of a value inside a TypeError message: primitives as
|
||||
* strings, objects as #<Object>. */
|
||||
static ScrStr *uc_v8_value(const ScrDyn *value) {
|
||||
if (value->kind == SCR_DYN_SYMBOL) return scr_insp_dyn((ScrDyn *)value, 0, 0);
|
||||
if (uc_object(value)) return scr_str_new("#<Object>", 9);
|
||||
ScrStr *text = scr_dyn_string_coerce_js(value);
|
||||
return text ? text : scr_str_new("", 0);
|
||||
}
|
||||
|
||||
static void uc_v8_value_error(const char *prefix, const ScrDyn *value, const char *suffix) {
|
||||
ScrStr *text = uc_v8_value(value);
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, prefix);
|
||||
scr_jb_put_str(&b, text);
|
||||
scr_jb_puts(&b, suffix);
|
||||
scr_str_release(text);
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
||||
}
|
||||
|
||||
/* Reflect.apply(fn, receiver, args) — the receiver is bound for the call's
|
||||
* synchronous extent, as for every dyn method call. +1 or NULL. */
|
||||
static ScrDyn *uc_apply(ScrDyn *fn, ScrDyn *receiver, ScrDyn *const *args, size_t argc) {
|
||||
if (!scr_dyn_is_callable(fn)) {
|
||||
const char *kind = uc_object(fn) ? "an object" : fn->kind == SCR_DYN_NUM ? "a number"
|
||||
: fn->kind == SCR_DYN_STR ? "a string" : fn->kind == SCR_DYN_BOOL ? "a boolean"
|
||||
: fn->kind == SCR_DYN_NULL ? "an object" : fn->kind == SCR_DYN_SYMBOL ? "a symbol"
|
||||
: fn->kind == SCR_DYN_BIGINT ? "a bigint" : "a undefined";
|
||||
ScrStr *text = fn->kind == SCR_DYN_NULL ? scr_str_new("null", 4) : uc_v8_value(fn);
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, "Function.prototype.apply was called on ");
|
||||
scr_jb_put_str(&b, text);
|
||||
scr_jb_puts(&b, ", which is ");
|
||||
scr_jb_puts(&b, kind);
|
||||
scr_jb_puts(&b, " and not a function");
|
||||
scr_str_release(text);
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
|
||||
return NULL;
|
||||
}
|
||||
scr_dyn_this_push_dyn(receiver);
|
||||
ScrDyn *result = scr_dyn_call(fn, args, argc, "function");
|
||||
scr_dyn_this_pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Object.defineProperty(target, key, descriptor) over a fresh descriptor;
|
||||
* the descriptor moves in. False with an exception pending on failure. */
|
||||
static bool uc_define(ScrDyn *target, const char *key, ScrDyn *descriptor) {
|
||||
ScrStr *name = scr_str_new(key, strlen(key));
|
||||
ScrDyn *property = scr_dyn_new_str(name);
|
||||
scr_str_release(name);
|
||||
ScrDyn *defined = scr_dyn_define_property(target, property, descriptor);
|
||||
scr_dyn_release(property);
|
||||
scr_dyn_release(descriptor);
|
||||
if (!defined) return false;
|
||||
scr_dyn_release(defined);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ── util.inherits ─────────────────────────────────────────────────────
|
||||
* Node 24's lib/util.js: nullish arguments and an undefined
|
||||
* superCtor.prototype throw ERR_INVALID_ARG_TYPE; then super_ becomes a
|
||||
* writable, configurable, non-enumerable own property and
|
||||
* ctor.prototype's prototype becomes superCtor.prototype. */
|
||||
void scr_util_inherits(ScrDyn *ctor, ScrDyn *super_ctor) {
|
||||
if (uc_nullish(ctor)) {
|
||||
scr_dyn_arg_type_fail("ctor", "of type function", ctor);
|
||||
return;
|
||||
}
|
||||
if (uc_nullish(super_ctor)) {
|
||||
scr_dyn_arg_type_fail("superCtor", "of type function", super_ctor);
|
||||
return;
|
||||
}
|
||||
if ((ctor->kind == SCR_DYN_FUNC && ctor->v.fn.class_obj) ||
|
||||
(super_ctor->kind == SCR_DYN_FUNC && super_ctor->v.fn.class_obj)) {
|
||||
uc_fence("util.inherits over class constructors has no lowering; declare the subclass with extends");
|
||||
return;
|
||||
}
|
||||
ScrDyn *super_prototype = uc_get(super_ctor, "prototype");
|
||||
if (!super_prototype) return;
|
||||
if (super_prototype->kind == SCR_DYN_UNDEF) {
|
||||
scr_dyn_prop_type_fail("superCtor.prototype", "of type object", super_prototype);
|
||||
scr_dyn_release(super_prototype);
|
||||
return;
|
||||
}
|
||||
if (!uc_object(ctor)) {
|
||||
uc_type_error("Object.defineProperty called on non-object");
|
||||
scr_dyn_release(super_prototype);
|
||||
return;
|
||||
}
|
||||
ScrDyn *descriptor = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(descriptor, "value", 5, scr_dyn_retain(super_ctor));
|
||||
scr_dyn_obj_set(descriptor, "writable", 8, scr_dyn_new_bool(true));
|
||||
scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool(true));
|
||||
if (!uc_define(ctor, "super_", descriptor)) {
|
||||
scr_dyn_release(super_prototype);
|
||||
return;
|
||||
}
|
||||
ScrDyn *prototype = uc_get(ctor, "prototype");
|
||||
if (prototype) {
|
||||
if (uc_nullish(prototype)) {
|
||||
uc_type_error("Object.setPrototypeOf called on null or undefined");
|
||||
} else if (!uc_object(super_prototype) && super_prototype->kind != SCR_DYN_NULL) {
|
||||
uc_v8_value_error("Object prototype may only be an Object or null: ", super_prototype, "");
|
||||
} else if (uc_object(prototype)) {
|
||||
scr_dyn_release(scr_dyn_set_prototype(prototype, super_prototype));
|
||||
}
|
||||
scr_dyn_release(prototype);
|
||||
}
|
||||
scr_dyn_release(super_prototype);
|
||||
}
|
||||
|
||||
/* ── util.deprecate ────────────────────────────────────────────────────
|
||||
* The wrapper warns once (and once per code across wrappers) before each
|
||||
* call reaches fn with the call's receiver and arguments; construction
|
||||
* reaches fn with the fresh receiver. codesWarned is process-wide, like
|
||||
* Node's. */
|
||||
static SCR_TL ScrDyn *uc_codes_warned = NULL;
|
||||
|
||||
static void uc_codes_warned_teardown(void) {
|
||||
scr_dyn_release(uc_codes_warned);
|
||||
uc_codes_warned = NULL;
|
||||
}
|
||||
|
||||
enum { UC_DEP_FN, UC_DEP_MSG, UC_DEP_CODE, UC_DEP_WARNED, UC_DEP_CAPS };
|
||||
|
||||
/* process.emitWarning(msg, type, code) as deprecate's emitter calls it:
|
||||
* a string message builds the warning, an Error is emitted as itself,
|
||||
* anything else throws Node's ERR_INVALID_ARG_TYPE at the call. */
|
||||
static bool uc_emit(ScrDyn *msg, const char *type, const ScrStr *code) {
|
||||
if (!strcmp(type, "DeprecationWarning") && scr_process_deprecation_flag_on(0)) return true;
|
||||
ScrDyn *warning = NULL;
|
||||
if (msg->kind == SCR_DYN_STR) {
|
||||
warning = scr_warning_new(msg->v.str, type, code);
|
||||
} else if (scr_dyn_ref_is_error(msg)) {
|
||||
warning = scr_dyn_retain(msg);
|
||||
} else {
|
||||
scr_dyn_arg_type_fail("warning", "of type string or an instance of Error", msg);
|
||||
return false;
|
||||
}
|
||||
scr_warning_emit_deferred(warning);
|
||||
scr_dyn_release(warning);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool uc_deprecation_warn(ScrClosure *closure) {
|
||||
if (scr_box_get_f64(closure->caps[UC_DEP_WARNED]) != 0) return true;
|
||||
scr_box_set_f64(closure->caps[UC_DEP_WARNED], 1);
|
||||
ScrDyn *msg = uc_cap(closure, UC_DEP_MSG);
|
||||
ScrDyn *code = uc_cap(closure, UC_DEP_CODE);
|
||||
bool ok = true;
|
||||
if (code->kind == SCR_DYN_STR && code->v.str->len == 19 &&
|
||||
memcmp(code->v.str->data, "ExperimentalWarning", 19) == 0) {
|
||||
ok = uc_emit(msg, "ExperimentalWarning", NULL);
|
||||
} else if (code->kind == SCR_DYN_STR) {
|
||||
if (!uc_codes_warned) {
|
||||
uc_codes_warned = scr_dyn_new_obj_null_proto();
|
||||
scr_atexit(uc_codes_warned_teardown);
|
||||
}
|
||||
const ScrStr *key = code->v.str;
|
||||
if (!scr_dyn_obj_get(uc_codes_warned, key->data, key->len)) {
|
||||
ok = uc_emit(msg, "DeprecationWarning", key);
|
||||
if (ok) scr_dyn_obj_set(uc_codes_warned, key->data, key->len, scr_dyn_new_bool(true));
|
||||
}
|
||||
} else {
|
||||
ok = uc_emit(msg, "DeprecationWarning", NULL);
|
||||
}
|
||||
scr_dyn_release(code);
|
||||
scr_dyn_release(msg);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static ScrDyn *uc_deprecated_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
if (!scr_process_deprecation_flag_on(0) && !uc_deprecation_warn(closure)) return NULL;
|
||||
ScrDyn *fn = uc_cap(closure, UC_DEP_FN);
|
||||
ScrDyn *receiver = scr_dyn_this_get();
|
||||
ScrDyn *result = uc_apply(fn, receiver, args, argc);
|
||||
scr_dyn_release(receiver);
|
||||
scr_dyn_release(fn);
|
||||
return result;
|
||||
}
|
||||
|
||||
ScrDyn *scr_util_deprecate(ScrDyn *fn, ScrDyn *msg, ScrDyn *code, ScrDyn *options) {
|
||||
/* The `{ modifyPrototype } = {}` parameter binds before the body runs. */
|
||||
bool modify_prototype = true;
|
||||
if (options->kind == SCR_DYN_NULL) {
|
||||
uc_type_error("Cannot destructure property 'modifyPrototype' of "
|
||||
"'(intermediate value)(intermediate value)(intermediate value)' as it is null.");
|
||||
return NULL;
|
||||
}
|
||||
if (uc_object(options)) {
|
||||
ScrDyn *modify = uc_get(options, "modifyPrototype");
|
||||
if (!modify) return NULL;
|
||||
if (modify->kind != SCR_DYN_UNDEF) modify_prototype = scr_dyn_truthy(modify);
|
||||
scr_dyn_release(modify);
|
||||
}
|
||||
if (code->kind != SCR_DYN_UNDEF && code->kind != SCR_DYN_STR) {
|
||||
scr_dyn_arg_type_fail("code", "of type string", code);
|
||||
return NULL;
|
||||
}
|
||||
if (fn->kind == SCR_DYN_FUNC && fn->v.fn.class_obj) {
|
||||
uc_fence("util.deprecate over class constructors has no lowering; deprecate a factory function instead");
|
||||
return NULL;
|
||||
}
|
||||
ScrClosure *closure = scr_closure_new(NULL, UC_DEP_CAPS);
|
||||
closure->function_kind = 4; /* an ordinary function with its own prototype */
|
||||
closure->caps[UC_DEP_FN] = uc_box(scr_dyn_retain(fn));
|
||||
closure->caps[UC_DEP_MSG] = uc_box(scr_dyn_retain(msg));
|
||||
closure->caps[UC_DEP_CODE] = uc_box(scr_dyn_retain(code));
|
||||
closure->caps[UC_DEP_WARNED] = scr_box_new(SCR_BOX_F64);
|
||||
ScrDyn *deprecated = scr_dyn_new_func(closure, uc_deprecated_call, 0, "native:util.deprecate", "deprecated");
|
||||
if (!modify_prototype) return deprecated;
|
||||
/* ObjectSetPrototypeOf(deprecated, fn), then the shared prototype and
|
||||
* fn's own length descriptor. */
|
||||
if (fn->kind != SCR_DYN_NULL && !uc_object(fn)) {
|
||||
uc_v8_value_error("Object prototype may only be an Object or null: ", fn, "");
|
||||
scr_dyn_release(deprecated);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *linked = scr_dyn_set_prototype(deprecated, fn);
|
||||
if (!linked) {
|
||||
scr_dyn_release(deprecated);
|
||||
return NULL;
|
||||
}
|
||||
scr_dyn_release(linked);
|
||||
if (fn->kind == SCR_DYN_NULL) {
|
||||
uc_type_error("Cannot read properties of null (reading 'prototype')");
|
||||
scr_dyn_release(deprecated);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *prototype = uc_get(fn, "prototype");
|
||||
if (!prototype) {
|
||||
scr_dyn_release(deprecated);
|
||||
return NULL;
|
||||
}
|
||||
if (scr_dyn_truthy(prototype)) {
|
||||
ScrStr *key = scr_str_new("prototype", 9);
|
||||
scr_dyn_key_set(deprecated, key, prototype);
|
||||
scr_str_release(key);
|
||||
}
|
||||
scr_dyn_release(prototype);
|
||||
if (scr_exc_pending()) {
|
||||
scr_dyn_release(deprecated);
|
||||
return NULL;
|
||||
}
|
||||
ScrStr *length_name = scr_str_new("length", 6);
|
||||
ScrDyn *length_key = scr_dyn_new_str(length_name);
|
||||
scr_str_release(length_name);
|
||||
ScrDyn *length = scr_dyn_get_own_property_descriptor(fn, length_key);
|
||||
scr_dyn_release(length_key);
|
||||
if (!length || (length->kind == SCR_DYN_OBJ && !uc_define(deprecated, "length", scr_dyn_retain(length)))) {
|
||||
scr_dyn_release(length);
|
||||
scr_dyn_release(deprecated);
|
||||
return NULL;
|
||||
}
|
||||
scr_dyn_release(length);
|
||||
return deprecated;
|
||||
}
|
||||
|
||||
/* ── util.callbackify ──────────────────────────────────────────────────
|
||||
* The wrapper pops the callback, binds it to the call's receiver and
|
||||
* calls original; the promise's settlement schedules the callback on the
|
||||
* next tick, so a throwing callback is an uncaught exception there. A
|
||||
* falsy rejection reason arrives wrapped in ERR_FALSY_VALUE_REJECTION
|
||||
* whose `reason` is the original value. */
|
||||
/* process.nextTick(cb, null, value) or the rejection's cb(err). */
|
||||
static void uc_callback_tick(ScrClosure *closure) {
|
||||
ScrDyn *cb = uc_cap(closure, 0);
|
||||
ScrDyn *err = uc_cap(closure, 1);
|
||||
ScrDyn *value = uc_cap(closure, 2);
|
||||
ScrDyn *args[2] = {err, value};
|
||||
size_t argc = (size_t)scr_box_get_f64(closure->caps[3]);
|
||||
ScrDyn *result = scr_dyn_call(cb, args, argc, "callback");
|
||||
scr_dyn_release(result);
|
||||
scr_dyn_release(value);
|
||||
scr_dyn_release(err);
|
||||
scr_dyn_release(cb);
|
||||
}
|
||||
|
||||
static void uc_schedule_callback(ScrDyn *cb, ScrDyn *err /* moves in */, ScrDyn *value /* moves in */,
|
||||
size_t argc) {
|
||||
ScrClosure *tick = scr_closure_new((void *)uc_callback_tick, 4);
|
||||
tick->caps[0] = uc_box(scr_dyn_retain(cb));
|
||||
tick->caps[1] = uc_box(err);
|
||||
tick->caps[2] = uc_box(value);
|
||||
tick->caps[3] = scr_box_new(SCR_BOX_F64);
|
||||
scr_box_set_f64(tick->caps[3], (double)argc);
|
||||
scr_next_tick(tick);
|
||||
}
|
||||
|
||||
static ScrDyn *uc_callbackify_fulfilled(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
ScrDyn *cb = uc_cap(closure, 0);
|
||||
uc_schedule_callback(cb, scr_dyn_new_null(), scr_dyn_retain(argc ? args[0] : scr_dyn_undefined()), 2);
|
||||
scr_dyn_release(cb);
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
static ScrDyn *uc_callbackify_rejected(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
ScrDyn *cb = uc_cap(closure, 0);
|
||||
ScrDyn *reason = argc ? args[0] : scr_dyn_undefined();
|
||||
ScrDyn *err;
|
||||
if (scr_dyn_truthy(reason)) {
|
||||
err = scr_dyn_retain(reason);
|
||||
} else {
|
||||
static const char message[] = "Promise was rejected with falsy value";
|
||||
ScrStr *text = scr_str_new(message, sizeof message - 1);
|
||||
ScrError *e = scr_error_new(SCR_ERR_ERROR, text);
|
||||
scr_str_release(text);
|
||||
scr_error_set_code(e, "ERR_FALSY_VALUE_REJECTION");
|
||||
err = scr_dyn_from_error(e);
|
||||
scr_error_release(e);
|
||||
scr_dyn_obj_set(err, "reason", 6, scr_dyn_retain(reason));
|
||||
}
|
||||
uc_schedule_callback(cb, err, scr_dyn_retain(scr_dyn_undefined()), 1);
|
||||
scr_dyn_release(cb);
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
static ScrDyn *uc_reaction(ScrDyn *cb, ScrDynThunk thunk) {
|
||||
ScrClosure *closure = scr_closure_new(NULL, 1);
|
||||
closure->caps[0] = uc_box(scr_dyn_retain(cb));
|
||||
return scr_dyn_new_func(closure, thunk, 1, "native:util.callbackify", "");
|
||||
}
|
||||
|
||||
static void uc_last_argument_fail(const ScrDyn *got) {
|
||||
char detail[256];
|
||||
const char *received = scr_dyn_specific_type(got, detail, sizeof detail);
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, "The last argument must be of type function. Received ");
|
||||
scr_jb_puts(&b, received);
|
||||
ScrStr *message = scr_jb_finish(&b);
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, message->data, message->len, "ERR_INVALID_ARG_TYPE");
|
||||
scr_str_release(message);
|
||||
}
|
||||
|
||||
static ScrDyn *uc_callbackified_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
ScrDyn *maybe_cb = argc ? args[argc - 1] : scr_dyn_undefined();
|
||||
if (maybe_cb->kind == SCR_DYN_PROXY && scr_dyn_is_callable(maybe_cb)) {
|
||||
uc_fence("util.callbackify callbacks that are Proxy values have no lowering");
|
||||
return NULL;
|
||||
}
|
||||
if (maybe_cb->kind != SCR_DYN_FUNC) {
|
||||
uc_last_argument_fail(maybe_cb);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *receiver = scr_dyn_this_get();
|
||||
ScrDyn *bind_args[1] = {receiver};
|
||||
ScrDyn *cb = scr_dyn_bind(maybe_cb, bind_args, 1);
|
||||
ScrDyn *original = uc_cap(closure, 0);
|
||||
ScrDyn *returned = uc_apply(original, receiver, args, argc ? argc - 1 : 0);
|
||||
scr_dyn_release(original);
|
||||
scr_dyn_release(receiver);
|
||||
if (!returned) {
|
||||
scr_dyn_release(cb);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *on_fulfilled = uc_reaction(cb, uc_callbackify_fulfilled);
|
||||
ScrDyn *on_rejected = uc_reaction(cb, uc_callbackify_rejected);
|
||||
scr_dyn_release(cb);
|
||||
ScrDyn *chained = NULL;
|
||||
ScrPromise *promise = scr_dyn_promise_of(returned);
|
||||
if (promise) {
|
||||
chained = scr_dyn_promise_then(promise, on_fulfilled, on_rejected, NULL);
|
||||
} else if (uc_nullish(returned)) {
|
||||
uc_type_error(returned->kind == SCR_DYN_NULL ? "Cannot read properties of null (reading 'then')"
|
||||
: "Cannot read properties of undefined (reading 'then')");
|
||||
} else {
|
||||
ScrDyn *then = uc_get(returned, "then");
|
||||
if (then && scr_dyn_is_callable(then)) {
|
||||
ScrDyn *reactions[2] = {on_fulfilled, on_rejected};
|
||||
chained = uc_apply(then, returned, reactions, 2);
|
||||
} else if (then) {
|
||||
uc_type_error("ReflectApply(...).then is not a function");
|
||||
}
|
||||
scr_dyn_release(then);
|
||||
}
|
||||
scr_dyn_release(on_rejected);
|
||||
scr_dyn_release(on_fulfilled);
|
||||
scr_dyn_release(returned);
|
||||
if (!chained) return NULL;
|
||||
scr_dyn_release(chained);
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
ScrDyn *scr_util_callbackify(ScrDyn *original) {
|
||||
if (!scr_dyn_is_callable(original)) {
|
||||
scr_dyn_arg_type_fail("original", "of type function", original);
|
||||
return NULL;
|
||||
}
|
||||
ScrClosure *closure = scr_closure_new(NULL, 1);
|
||||
closure->function_kind = 4; /* an ordinary function with its own prototype */
|
||||
closure->caps[0] = uc_box(scr_dyn_retain(original));
|
||||
ScrDyn *callbackified =
|
||||
scr_dyn_new_func(closure, uc_callbackified_call, 0, "native:util.callbackify", "callbackified");
|
||||
/* original's own descriptors, with a numeric length one longer and a
|
||||
* string name suffixed "Callbackified". */
|
||||
ScrDyn *descriptors = scr_dyn_get_own_property_descriptors(original);
|
||||
if (!descriptors) {
|
||||
scr_dyn_release(callbackified);
|
||||
return NULL;
|
||||
}
|
||||
if (descriptors->kind == SCR_DYN_OBJ) {
|
||||
ScrDyn *length = scr_dyn_obj_get(descriptors, "length", 6);
|
||||
const ScrDyn *length_value = length && length->kind == SCR_DYN_OBJ ? scr_dyn_obj_get(length, "value", 5) : NULL;
|
||||
if (length_value && length_value->kind == SCR_DYN_NUM)
|
||||
scr_dyn_obj_set(length, "value", 5, scr_dyn_new_num(length_value->v.num + 1));
|
||||
ScrDyn *name = scr_dyn_obj_get(descriptors, "name", 4);
|
||||
const ScrDyn *name_value = name && name->kind == SCR_DYN_OBJ ? scr_dyn_obj_get(name, "value", 5) : NULL;
|
||||
if (name_value && name_value->kind == SCR_DYN_STR) {
|
||||
ScrStr *suffix = scr_str_new("Callbackified", 13);
|
||||
ScrStr *joined = scr_str_concat(name_value->v.str, suffix);
|
||||
scr_str_release(suffix);
|
||||
scr_dyn_obj_set(name, "value", 5, scr_dyn_new_str(joined));
|
||||
scr_str_release(joined);
|
||||
}
|
||||
}
|
||||
ScrDyn *defined = scr_dyn_define_props(callbackified, descriptors);
|
||||
scr_dyn_release(descriptors);
|
||||
if (!defined) {
|
||||
scr_dyn_release(callbackified);
|
||||
return NULL;
|
||||
}
|
||||
scr_dyn_release(defined);
|
||||
return callbackified;
|
||||
}
|
||||
|
||||
/* ── util.format over dyn arguments ────────────────────────────────────
|
||||
* formatWithOptionsInternal for debuglog's lines: the %-substitutions
|
||||
* (%s %d %i %f %j %o %O %c %%) with Node's argument cursor, then the
|
||||
* remaining arguments space-joined, strings verbatim and everything else
|
||||
* inspected. With terminal colors, primitives take inspect's default
|
||||
* styles; colored object renderings have no lowering. */
|
||||
static bool uc_primitive(const ScrDyn *value) {
|
||||
return !uc_object(value);
|
||||
}
|
||||
|
||||
static ScrStr *uc_inspect(ScrDyn *value, double depth, bool colors) {
|
||||
if (!colors) return scr_insp_dyn(value, 0, depth);
|
||||
if (!uc_primitive(value)) {
|
||||
uc_fence("util.debuglog output of objects on a color terminal has no lowering");
|
||||
return NULL;
|
||||
}
|
||||
const char *open = "\x1b[33m", *close = "\x1b[39m";
|
||||
if (value->kind == SCR_DYN_UNDEF) open = "\x1b[90m";
|
||||
else if (value->kind == SCR_DYN_NULL) open = "\x1b[1m", close = "\x1b[22m";
|
||||
else if (value->kind == SCR_DYN_STR || value->kind == SCR_DYN_SYMBOL) open = "\x1b[32m";
|
||||
ScrStr *text = scr_insp_dyn(value, 0, depth);
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, open);
|
||||
scr_jb_put_str(&b, text);
|
||||
scr_jb_puts(&b, close);
|
||||
scr_str_release(text);
|
||||
return scr_jb_finish(&b);
|
||||
}
|
||||
|
||||
/* hasBuiltInToString's negative answer for checked objects: an own or
|
||||
* inherited toString the program defined. Symbol-keyed members (where
|
||||
* Symbol.toPrimitive would also count) are reported as unknown. */
|
||||
static int uc_custom_to_string(const ScrDyn *value) {
|
||||
for (const ScrDyn *current = value; current && current->kind == SCR_DYN_OBJ; current = current->prototype) {
|
||||
if (current->symbol_properties) return -1;
|
||||
const ScrDyn *member = scr_dyn_obj_get(current, "toString", 8);
|
||||
if (member) return member->kind == SCR_DYN_FUNC;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ScrStr *uc_format_s(ScrDyn *value) {
|
||||
if (value->kind == SCR_DYN_STR) return scr_str_retain(value->v.str);
|
||||
if (value->kind == SCR_DYN_NUM) return scr_insp_f64(value->v.num);
|
||||
if (value->kind == SCR_DYN_FUNC) {
|
||||
uc_fence("util.debuglog %s of a function prints its source text, which a compiled binary does not keep");
|
||||
return NULL;
|
||||
}
|
||||
if (value->kind == SCR_DYN_OBJ && scr_dyn_obj_get(value, "%error", 6) == NULL) {
|
||||
int custom = uc_custom_to_string(value);
|
||||
if (custom < 0) {
|
||||
uc_fence("util.debuglog %s of objects with symbol-keyed members has no lowering");
|
||||
return NULL;
|
||||
}
|
||||
if (custom) return scr_dyn_string_coerce_js(value);
|
||||
}
|
||||
return scr_insp_dyn_s(value, 0);
|
||||
}
|
||||
|
||||
static ScrStr *uc_format_number(ScrDyn *value, int spec) {
|
||||
if (value->kind == SCR_DYN_BIGINT && spec != 'f') return scr_bigint_inspect(value->v.bigint);
|
||||
if (value->kind == SCR_DYN_SYMBOL) return scr_str_new("NaN", 3);
|
||||
double number;
|
||||
if (spec == 'd') {
|
||||
if (value->kind == SCR_DYN_BIGINT) return scr_bigint_inspect(value->v.bigint);
|
||||
if (!scr_dyn_number_coerce_js(value, &number)) return NULL;
|
||||
} else {
|
||||
ScrStr *text = scr_dyn_string_coerce_js(value);
|
||||
if (!text) return NULL;
|
||||
number = spec == 'i' ? scr_parse_int(text, 0) : scr_parse_float(text);
|
||||
scr_str_release(text);
|
||||
}
|
||||
return scr_insp_f64(number);
|
||||
}
|
||||
|
||||
static ScrStr *uc_format(ScrDyn *const *args, size_t argc, bool colors) {
|
||||
if (argc == 0) return scr_str_new("", 0);
|
||||
ScrJsonBuf out;
|
||||
scr_jb_init(&out);
|
||||
size_t a = 0;
|
||||
const char *join = "";
|
||||
if (args[0]->kind == SCR_DYN_STR) {
|
||||
const ScrStr *first = args[0]->v.str;
|
||||
if (argc == 1) {
|
||||
scr_str_release(scr_jb_finish(&out));
|
||||
return scr_str_retain((ScrStr *)first);
|
||||
}
|
||||
const char *text = first->data;
|
||||
size_t last = 0;
|
||||
for (size_t i = 0; first->len > 0 && i < first->len - 1; i++) {
|
||||
if (text[i] != '%') continue;
|
||||
int next = (unsigned char)text[++i];
|
||||
if (a + 1 != argc) {
|
||||
ScrStr *piece = NULL;
|
||||
switch (next) {
|
||||
case 's': piece = uc_format_s(args[++a]); break;
|
||||
case 'j': piece = scr_dyn_format_j(args[++a]); break;
|
||||
case 'd':
|
||||
case 'i':
|
||||
case 'f': piece = uc_format_number(args[++a], next); break;
|
||||
case 'O': piece = uc_inspect(args[++a], 2, colors); break;
|
||||
case 'o':
|
||||
if (!uc_primitive(args[a + 1])) {
|
||||
uc_fence("util.debuglog %o of objects renders hidden properties, which have no lowering; use %O");
|
||||
break;
|
||||
}
|
||||
piece = uc_inspect(args[++a], 4, colors);
|
||||
break;
|
||||
case 'c':
|
||||
a += 1;
|
||||
piece = scr_str_new("", 0);
|
||||
break;
|
||||
case '%':
|
||||
scr_jb_write(&out, text + last, i - last);
|
||||
last = i + 1;
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (!piece) {
|
||||
scr_str_release(scr_jb_finish(&out));
|
||||
return NULL;
|
||||
}
|
||||
if (last != i - 1) scr_jb_write(&out, text + last, i - 1 - last);
|
||||
scr_jb_put_str(&out, piece);
|
||||
scr_str_release(piece);
|
||||
last = i + 1;
|
||||
} else if (next == '%') {
|
||||
scr_jb_write(&out, text + last, i - last);
|
||||
last = i + 1;
|
||||
}
|
||||
}
|
||||
if (last != 0) {
|
||||
a++;
|
||||
join = " ";
|
||||
if (last < first->len) scr_jb_write(&out, text + last, first->len - last);
|
||||
}
|
||||
}
|
||||
for (; a < argc; a++) {
|
||||
scr_jb_puts(&out, join);
|
||||
if (args[a]->kind == SCR_DYN_STR) {
|
||||
scr_jb_put_str(&out, args[a]->v.str);
|
||||
} else {
|
||||
ScrStr *piece = uc_inspect(args[a], 2, colors);
|
||||
if (!piece) {
|
||||
scr_str_release(scr_jb_finish(&out));
|
||||
return NULL;
|
||||
}
|
||||
scr_jb_put_str(&out, piece);
|
||||
scr_str_release(piece);
|
||||
}
|
||||
join = " ";
|
||||
}
|
||||
return scr_jb_finish(&out);
|
||||
}
|
||||
|
||||
/* ── util.debuglog ─────────────────────────────────────────────────────
|
||||
* The logger defers its NODE_DEBUG test to its first call or `enabled`
|
||||
* read. The first call also selects the section's shared implementation
|
||||
* (a writer for enabled sections, a no-op otherwise), hands it to the
|
||||
* callback with its own `enabled` getter, and forwards every later call
|
||||
* while the section is enabled. */
|
||||
enum { UC_LOG_SET, UC_LOG_CB, UC_LOG_ENABLED, UC_LOG_DEBUG, UC_LOG_TESTED, UC_LOG_CAPS };
|
||||
|
||||
static SCR_TL ScrDyn *uc_debug_impls = NULL;
|
||||
static SCR_TL ScrRegex *uc_debug_pattern = NULL;
|
||||
static SCR_TL bool uc_debug_pattern_ready = false;
|
||||
|
||||
static void uc_debug_teardown(void) {
|
||||
scr_dyn_release(uc_debug_impls);
|
||||
uc_debug_impls = NULL;
|
||||
scr_regex_release(uc_debug_pattern);
|
||||
uc_debug_pattern = NULL;
|
||||
uc_debug_pattern_ready = false;
|
||||
}
|
||||
|
||||
static void uc_debug_register(void) {
|
||||
static SCR_TL bool registered = false;
|
||||
if (registered) return;
|
||||
registered = true;
|
||||
scr_atexit(uc_debug_teardown);
|
||||
}
|
||||
|
||||
/* initializeDebugEnv: ^name$ alternatives over the comma list, with '*'
|
||||
* as a wildcard and the other regexp syntax escaped, case-insensitive. */
|
||||
static bool uc_debug_test(const ScrStr *section) {
|
||||
if (!uc_debug_pattern_ready) {
|
||||
uc_debug_pattern_ready = true;
|
||||
uc_debug_register();
|
||||
const char *env = scr_node_debug_initial();
|
||||
if (env && *env) {
|
||||
ScrJsonBuf pattern;
|
||||
scr_jb_init(&pattern);
|
||||
scr_jb_putc(&pattern, '^');
|
||||
for (const char *p = env; *p; p++) {
|
||||
if (strchr("|\\{}()[]^$+?.", *p)) {
|
||||
scr_jb_putc(&pattern, '\\');
|
||||
scr_jb_putc(&pattern, *p);
|
||||
} else if (*p == '*') {
|
||||
scr_jb_puts(&pattern, ".*");
|
||||
} else if (*p == ',') {
|
||||
scr_jb_puts(&pattern, "$|^");
|
||||
} else {
|
||||
scr_jb_putc(&pattern, *p);
|
||||
}
|
||||
}
|
||||
scr_jb_putc(&pattern, '$');
|
||||
ScrStr *source = scr_jb_finish(&pattern);
|
||||
ScrStr *flags = scr_str_new("i", 1);
|
||||
uc_debug_pattern = scr_regex_new(source, flags);
|
||||
scr_str_release(flags);
|
||||
scr_str_release(source);
|
||||
if (scr_exc_pending()) return false;
|
||||
}
|
||||
}
|
||||
return uc_debug_pattern != NULL && scr_regex_test(uc_debug_pattern, (ScrStr *)section);
|
||||
}
|
||||
|
||||
static ScrDyn *uc_debug_write(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
bool colors = scr_getenv("FORCE_COLOR") ? scr_util_terminal_colors()
|
||||
: scr_process_is_tty(2) && scr_util_terminal_colors();
|
||||
ScrStr *message = uc_format(args, argc, colors);
|
||||
if (!message) return NULL;
|
||||
ScrDyn *set = uc_cap(closure, 0);
|
||||
char pid[64];
|
||||
long id = (long)scr_process_pid();
|
||||
if (colors) snprintf(pid, sizeof pid, "\x1b[33m%ld\x1b[39m", id);
|
||||
else snprintf(pid, sizeof pid, "%ld", id);
|
||||
ScrJsonBuf line;
|
||||
scr_jb_init(&line);
|
||||
scr_jb_put_str(&line, set->v.str);
|
||||
scr_jb_putc(&line, ' ');
|
||||
scr_jb_puts(&line, pid);
|
||||
scr_jb_puts(&line, ": ");
|
||||
scr_jb_put_str(&line, message);
|
||||
scr_jb_putc(&line, '\n');
|
||||
ScrStr *text = scr_jb_finish(&line);
|
||||
scr_process_stderr_write(text);
|
||||
scr_str_release(text);
|
||||
scr_str_release(message);
|
||||
scr_dyn_release(set);
|
||||
return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
static ScrDyn *uc_debug_noop(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure; (void)args; (void)argc;
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
/* debuglogImpl: one implementation per upper-cased section. */
|
||||
static ScrDyn *uc_debug_impl(bool enabled, ScrDyn *set) {
|
||||
uc_debug_register();
|
||||
if (!uc_debug_impls) uc_debug_impls = scr_dyn_new_obj_null_proto();
|
||||
const ScrStr *key = set->v.str;
|
||||
ScrDyn *impl = scr_dyn_obj_get(uc_debug_impls, key->data, key->len);
|
||||
if (impl) return scr_dyn_retain(impl);
|
||||
if (enabled) {
|
||||
if ((key->len == 4 && !memcmp(key->data, "HTTP", 4)) || (key->len == 5 && !memcmp(key->data, "HTTP2", 5))) {
|
||||
ScrJsonBuf warning;
|
||||
scr_jb_init(&warning);
|
||||
scr_jb_puts(&warning, "Setting the NODE_DEBUG environment variable to '");
|
||||
scr_jb_puts(&warning, key->len == 4 ? "http" : "http2");
|
||||
scr_jb_puts(&warning, "' can expose sensitive data (such as passwords, tokens and authentication headers) in the resulting log.");
|
||||
ScrStr *text = scr_jb_finish(&warning);
|
||||
scr_process_emit_warning_text(text);
|
||||
scr_str_release(text);
|
||||
}
|
||||
ScrClosure *closure = scr_closure_new(NULL, 1);
|
||||
closure->function_kind = 4;
|
||||
closure->caps[0] = uc_box(scr_dyn_retain(set));
|
||||
impl = scr_dyn_new_func(closure, uc_debug_write, 0, "native:util.debuglog", "debug");
|
||||
} else {
|
||||
impl = scr_dyn_new_func(scr_closure_new(NULL, 0), uc_debug_noop, 0, "native:util.debuglog", "noop");
|
||||
}
|
||||
scr_dyn_obj_set(uc_debug_impls, key->data, key->len, scr_dyn_retain(impl));
|
||||
return impl;
|
||||
}
|
||||
|
||||
/* init(): upper-case the section and test it. */
|
||||
static bool uc_debug_init(ScrClosure *closure) {
|
||||
ScrDyn *set = uc_cap(closure, UC_LOG_SET);
|
||||
if (uc_nullish(set)) {
|
||||
scr_dyn_release(set);
|
||||
uc_type_error("String.prototype.toUpperCase called on null or undefined");
|
||||
return false;
|
||||
}
|
||||
ScrStr *text = scr_dyn_string_coerce_js(set);
|
||||
scr_dyn_release(set);
|
||||
if (!text) return false;
|
||||
ScrStr *upper = scr_str_to_upper(text);
|
||||
scr_str_release(text);
|
||||
uc_set_cap(closure, UC_LOG_SET, scr_dyn_new_str(upper));
|
||||
bool enabled = uc_debug_test(upper);
|
||||
scr_str_release(upper);
|
||||
if (scr_exc_pending()) return false;
|
||||
uc_set_cap(closure, UC_LOG_ENABLED, scr_dyn_new_bool(enabled));
|
||||
return true;
|
||||
}
|
||||
|
||||
static ScrDyn *uc_enabled_getter(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)args; (void)argc;
|
||||
return uc_cap(closure, 0);
|
||||
}
|
||||
|
||||
static ScrDyn *uc_enabled_accessor(ScrBox *enabled) {
|
||||
ScrClosure *closure = scr_closure_new(NULL, 1);
|
||||
closure->caps[0] = scr_box_retain(enabled);
|
||||
return scr_dyn_new_func(closure, uc_enabled_getter, 0, "native:util.debuglog", "get");
|
||||
}
|
||||
|
||||
static bool uc_define_enabled(ScrDyn *target, ScrDyn *getter /* moves in */) {
|
||||
ScrDyn *descriptor = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(descriptor, "get", 3, getter);
|
||||
scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool(true));
|
||||
scr_dyn_obj_set(descriptor, "enumerable", 10, scr_dyn_new_bool(true));
|
||||
return uc_define(target, "enabled", descriptor);
|
||||
}
|
||||
|
||||
static ScrDyn *uc_logger_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
ScrDyn *enabled = uc_cap(closure, UC_LOG_ENABLED);
|
||||
bool disabled = enabled->kind == SCR_DYN_BOOL && !enabled->v.b;
|
||||
scr_dyn_release(enabled);
|
||||
if (disabled) return scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *debug = uc_cap(closure, UC_LOG_DEBUG);
|
||||
if (debug->kind == SCR_DYN_UNDEF) {
|
||||
/* The first call: select the implementation, then the callback. */
|
||||
if (!uc_debug_init(closure)) return NULL;
|
||||
enabled = uc_cap(closure, UC_LOG_ENABLED);
|
||||
ScrDyn *set = uc_cap(closure, UC_LOG_SET);
|
||||
debug = uc_debug_impl(enabled->v.b, set);
|
||||
scr_dyn_release(set);
|
||||
scr_dyn_release(enabled);
|
||||
uc_set_cap(closure, UC_LOG_DEBUG, scr_dyn_retain(debug));
|
||||
ScrDyn *cb = uc_cap(closure, UC_LOG_CB);
|
||||
if (cb->kind == SCR_DYN_FUNC) {
|
||||
bool ok = uc_define_enabled(debug, uc_enabled_accessor(closure->caps[UC_LOG_ENABLED]));
|
||||
ScrDyn *result = ok ? scr_dyn_call(cb, &debug, 1, "callback") : NULL;
|
||||
scr_dyn_release(result);
|
||||
if (!result) {
|
||||
scr_dyn_release(cb);
|
||||
scr_dyn_release(debug);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
scr_dyn_release(cb);
|
||||
}
|
||||
ScrDyn *result = scr_dyn_call(debug, args, argc, "debug");
|
||||
scr_dyn_release(debug);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ScrDyn *uc_logger_enabled(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)args; (void)argc;
|
||||
if (scr_box_get_f64(closure->caps[UC_LOG_TESTED]) == 0) {
|
||||
if (!uc_debug_init(closure)) return NULL;
|
||||
scr_box_set_f64(closure->caps[UC_LOG_TESTED], 1);
|
||||
}
|
||||
return uc_cap(closure, UC_LOG_ENABLED);
|
||||
}
|
||||
|
||||
ScrDyn *scr_util_debuglog(ScrDyn *section, ScrDyn *callback) {
|
||||
ScrClosure *closure = scr_closure_new(NULL, UC_LOG_CAPS);
|
||||
closure->caps[UC_LOG_SET] = uc_box(scr_dyn_retain(section));
|
||||
closure->caps[UC_LOG_CB] = uc_box(scr_dyn_retain(callback));
|
||||
closure->caps[UC_LOG_ENABLED] = uc_box(scr_dyn_retain(scr_dyn_undefined()));
|
||||
closure->caps[UC_LOG_DEBUG] = uc_box(scr_dyn_retain(scr_dyn_undefined()));
|
||||
closure->caps[UC_LOG_TESTED] = scr_box_new(SCR_BOX_F64);
|
||||
/* The `enabled` getter shares the logger's state boxes. */
|
||||
ScrClosure *getter = scr_closure_new(NULL, UC_LOG_CAPS);
|
||||
for (size_t i = 0; i < UC_LOG_CAPS; i++) getter->caps[i] = scr_box_retain(closure->caps[i]);
|
||||
ScrDyn *logger = scr_dyn_new_func(closure, uc_logger_call, 0, "native:util.debuglog", "logger");
|
||||
ScrDyn *get = scr_dyn_new_func(getter, uc_logger_enabled, 0, "native:util.debuglog", "get");
|
||||
if (!uc_define_enabled(logger, get)) {
|
||||
scr_dyn_release(logger);
|
||||
return NULL;
|
||||
}
|
||||
return logger;
|
||||
}
|
||||
@@ -173,7 +173,7 @@ done:
|
||||
|
||||
/* styleText only needs the color/no-color distinction, rather than the
|
||||
* full 4/8/24-bit depth. Follow the ordering of Node 24 internal/tty. */
|
||||
static bool util_style_terminal_colors(void) {
|
||||
bool scr_util_terminal_colors(void) {
|
||||
const char *force = scr_getenv("FORCE_COLOR");
|
||||
if (force) {
|
||||
bool enabled = !strcmp(force, "") || !strcmp(force, "true") ||
|
||||
@@ -294,8 +294,8 @@ ScrStr *scr_util_style_text(const ScrDyn *format, const ScrDyn *text, const ScrD
|
||||
scr_dyn_arg_type_fail("stream", "an instance of ReadableStream, WritableStream, or Stream", stream); goto done;
|
||||
}
|
||||
}
|
||||
colorize = scr_getenv("FORCE_COLOR") ? util_style_terminal_colors() :
|
||||
scr_process_is_tty(fd) && util_style_terminal_colors();
|
||||
colorize = scr_getenv("FORCE_COLOR") ? scr_util_terminal_colors() :
|
||||
scr_process_is_tty(fd) && scr_util_terminal_colors();
|
||||
}
|
||||
}
|
||||
scr_dyn_release(view); view = util_style_view(format);
|
||||
|
||||
Reference in New Issue
Block a user