use crate::ast::{
Argument, ArrayElement, ArrayPatternElement, Arrow, ArrowBody, AssignOp, BinaryOp,
BindingTarget, Class, ClassMember, Expr, ForInit, Function, Ident, LogicalOp, MethodKind,
ObjectMember, Param, Program, PropertyKey, Stmt, UnaryOp, VarDecl,
};
use crate::env::Scope;
use crate::heap::Handle;
use crate::nanbox::{NanBox, Unpacked};
use crate::realm::Realm;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ExecError {
Unsupported(&'static str),
NotDefined(String),
NotCallable,
Throw(NanBox),
OptShortCircuit,
}
#[derive(Clone)]
pub(crate) enum Flow {
Normal(NanBox),
Return(NanBox),
Break(Option<String>, NanBox),
Continue(Option<String>, NanBox),
}
enum LoopAction {
Next,
Stop,
Propagate(Flow),
}
fn empty_to_undefined(flow: Flow) -> Flow {
match flow {
Flow::Normal(v) if v.is_empty_completion() => Flow::Normal(NanBox::undefined()),
Flow::Break(l, v) if v.is_empty_completion() => Flow::Break(l, NanBox::undefined()),
Flow::Continue(l, v) if v.is_empty_completion() => Flow::Continue(l, NanBox::undefined()),
other => other,
}
}
fn loop_step(flow: Flow, label: &Option<String>, v: &mut NanBox) -> LoopAction {
let matches = |l: &Option<String>| l.is_none() || l.as_deref() == label.as_deref();
match flow {
Flow::Normal(bv) => {
if !bv.is_empty_completion() {
*v = bv;
}
LoopAction::Next
}
Flow::Continue(l, bv) if matches(&l) => {
if !bv.is_empty_completion() {
*v = bv;
}
LoopAction::Next
}
Flow::Break(l, bv) if matches(&l) => {
*v = update_empty(bv, *v);
LoopAction::Stop
}
other => LoopAction::Propagate(other),
}
}
fn update_empty(value: NanBox, fallback: NanBox) -> NanBox {
if value.is_empty_completion() {
fallback
} else {
value
}
}
#[derive(Clone, Copy)]
pub(crate) enum Body<'a> {
Block(&'a [Stmt]),
Expr(&'a Expr),
}
#[derive(Clone, Copy)]
pub(crate) struct FnDef<'a> {
params: &'a [Param],
body: Body<'a>,
is_async: bool,
is_generator: bool,
is_arrow: bool,
is_strict: bool,
name: &'a str,
home_class: Option<u32>,
home_static: bool,
lexical_class: Option<u32>,
}
const fn splitmix64(mut z: u64) -> u64 {
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[allow(unsafe_code)]
fn cycle_counter() -> u64 {
#[cfg(target_arch = "x86_64")]
{
unsafe { core::arch::x86_64::_rdtsc() }
}
#[cfg(target_arch = "aarch64")]
{
let v: u64;
unsafe { core::arch::asm!("mrs {}, cntvct_el0", out(reg) v, options(nomem, nostack)) };
v
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
0
}
}
fn math_random_seed() -> [u64; 2] {
#[cfg(feature = "crypto")]
{
use purecrypto::rng::{OsRng, RngCore};
#[cfg(feature = "std")]
let drawn: Result<[u64; 2], _> = std::panic::catch_unwind(|| {
let mut rng = OsRng;
[rng.next_u64(), rng.next_u64()]
});
#[cfg(not(feature = "std"))]
let drawn: Result<[u64; 2], ()> = {
let mut rng = OsRng;
Ok([rng.next_u64(), rng.next_u64()])
};
if let Ok(s) = drawn
&& s[0] | s[1] != 0
{
return s;
}
}
let mut acc: u64 = 0x9E37_79B9_7F4A_7C15;
acc ^= cycle_counter();
#[cfg(feature = "std")]
{
if let Ok(d) =
std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH)
{
acc ^= (d.as_nanos() as u64).rotate_left(32);
}
acc ^= u64::from(std::process::id()).wrapping_mul(0x2545_F491_4F6C_DD1D);
}
let here = 0u8;
acc ^= (&here as *const u8 as u64).rotate_left(17);
let s0 = splitmix64(acc) | 1;
let s1 = splitmix64(acc.wrapping_add(0x9E37_79B9_7F4A_7C15));
[s0, s1]
}
pub struct Interp<'a> {
realm: Realm,
current: Scope,
var_scope: Scope,
annexb_block_fns: Vec<String>,
functions: Vec<FnDef<'a>>,
classes: Vec<&'a Class>,
class_member_keys: Vec<alloc::collections::BTreeMap<usize, String>>,
builtin_iter_protos: alloc::collections::BTreeMap<&'static str, Handle>,
class_statics: Vec<alloc::collections::BTreeMap<String, NanBox>>,
class_static_fields: Vec<Vec<String>>,
class_static_get: Vec<alloc::collections::BTreeMap<String, NanBox>>,
class_static_set: Vec<alloc::collections::BTreeMap<String, NanBox>>,
class_envs: Vec<Scope>,
class_native_super: Vec<Option<u16>>,
class_fn_super: Vec<Option<NanBox>>,
class_handles: Vec<NanBox>,
private_method_cache: alloc::collections::BTreeMap<(u32, String), NanBox>,
class_lexical_parent: Vec<Option<u32>>,
class_private_names: Vec<alloc::collections::BTreeSet<alloc::boxed::Box<str>>>,
pending_class_name: Option<&'a str>,
call_depth: usize,
new_target_in_scope: bool,
eval_depth: usize,
rng_state: [u64; 2],
this_val: NanBox,
new_target: NanBox,
pending_new_target: Option<NanBox>,
reflect_new_target: Option<NanBox>,
array_proto_generic: bool,
array_like_present: Option<alloc::vec::Vec<bool>>,
wasm_states: alloc::collections::BTreeMap<u32, crate::wasm_rt::InstanceState>,
wasm_modules: alloc::collections::BTreeMap<u32, crate::wasm_rt::Module>,
wasm_mem_objs: alloc::collections::BTreeMap<u32, crate::heap::Handle>,
wasm_next_id: u32,
gen_sink: Option<Vec<NanBox>>,
gen_frames: Vec<Option<generator::GenFrame<'a>>>,
pending_async_start: Option<(usize, Handle)>,
gen_is_async: bool,
symbol_registry: alloc::collections::BTreeMap<String, NanBox>,
well_known_symbols: alloc::collections::BTreeMap<&'static str, NanBox>,
tagged_template_cache: alloc::collections::BTreeMap<usize, NanBox>,
regexp_proto: Option<Handle>,
regexp_ctor: Option<Handle>,
#[cfg(feature = "intl")]
intl_intern: alloc::collections::BTreeMap<String, &'static str>,
method_name_intern: alloc::collections::BTreeMap<String, &'static str>,
pending_super: Option<(u32, Scope)>,
pending_super_native: Option<u16>,
pending_super_fn: Option<NanBox>,
current_home: Option<u32>,
current_lexical_home: Option<u32>,
current_home_object: Option<Handle>,
current_home_static: bool,
pending_this_init: Option<(NanBox, u32)>,
eval_param_names: Option<Vec<String>>,
pending_label: Option<String>,
microtasks: Vec<Job>,
macrotasks: Vec<Timer>,
timer_next_id: u64,
timer_seq: u64,
strict: bool,
global_this: NanBox,
output: String,
global_scope: Scope,
shadow_realm_scopes: Vec<Scope>,
eval_programs: alloc::collections::BTreeMap<String, &'static Program>,
#[cfg(all(feature = "module", feature = "std"))]
modules: module::ModuleRegistry,
#[cfg(all(feature = "module", feature = "std"))]
module_imports: alloc::rc::Rc<alloc::collections::BTreeMap<String, (Scope, String)>>,
#[cfg(all(feature = "module", feature = "std"))]
import_meta: Option<NanBox>,
#[cfg(all(feature = "module", feature = "std"))]
script_import_base: Option<String>,
#[cfg(all(feature = "module", feature = "std"))]
module_namespaces:
alloc::collections::BTreeMap<u64, alloc::collections::BTreeMap<String, (Scope, String)>>,
#[cfg(all(feature = "module", feature = "std"))]
deferred_namespaces: alloc::collections::BTreeMap<u64, String>,
#[cfg(all(feature = "module", feature = "std"))]
active_module_key: Option<String>,
}
struct Job {
handler: NanBox,
value: NanBox,
result: Handle,
fulfilled: bool,
finally: bool,
thenable: Option<(NanBox, NanBox)>,
}
struct Timer {
id: u64,
delay: f64,
seq: u64,
callback: NanBox,
args: Vec<NanBox>,
}
impl Default for Interp<'_> {
fn default() -> Self {
Self::new()
}
}
const N_MATH_MAX: u16 = 0;
const N_MATH_MIN: u16 = 1;
const N_MATH_ABS: u16 = 2;
const N_STRING: u16 = 3;
const N_NUMBER: u16 = 4;
const N_BOOLEAN: u16 = 5;
const N_PARSE_INT: u16 = 6;
const N_CONSOLE_LOG: u16 = 7;
const N_JSON_STRINGIFY: u16 = 8;
const N_JSON_PARSE: u16 = 27;
const N_OBJECT_KEYS: u16 = 9;
const N_OBJECT_VALUES: u16 = 10;
const N_ARRAY_IS_ARRAY: u16 = 11;
const N_MATH_FLOOR: u16 = 12;
const N_MATH_CEIL: u16 = 13;
const N_MATH_ROUND: u16 = 14;
const N_MATH_SQRT: u16 = 15;
const N_MAP: u16 = 16;
const N_SET: u16 = 17;
const N_MAP_SIZE: u16 = 580;
const N_SET_SIZE: u16 = 581;
const N_OBJECT_ASSIGN: u16 = 18;
const N_OBJECT_ENTRIES: u16 = 19;
const N_ARRAY_FROM: u16 = 20;
const N_ARRAY_OF: u16 = 21;
const N_PROMISE: u16 = 22;
const N_DATE: u16 = 25;
const N_REGEXP: u16 = 26;
const N_MATH_POW: u16 = 28;
const N_MATH_SIGN: u16 = 29;
const N_MATH_TRUNC: u16 = 30;
const N_OBJECT_FROM_ENTRIES: u16 = 34;
const N_OBJECT_FREEZE: u16 = 35;
const N_OBJECT_IS_FROZEN: u16 = 36;
const N_OBJECT_SEAL: u16 = 125;
const N_OBJECT_IS_SEALED: u16 = 126;
const N_OBJECT_PREVENT_EXT: u16 = 127;
const N_OBJECT_IS_EXTENSIBLE: u16 = 128;
const N_OBJECT_GET_OWN_NAMES: u16 = 37;
const N_OBJECT_GET_OWN_SYMBOLS: u16 = 158;
const N_ENCODE_URI_COMPONENT: u16 = 159;
const N_DECODE_URI_COMPONENT: u16 = 160;
const N_ENCODE_URI: u16 = 161;
const N_DECODE_URI: u16 = 162;
const N_STRUCTURED_CLONE: u16 = 163;
const N_BTOA: u16 = 164;
const N_ATOB: u16 = 165;
const N_INTL_NUMBER_FORMAT: u16 = 166;
const N_INTL_DATETIME_FORMAT: u16 = 167;
const N_SET_TIMEOUT: u16 = 211;
const N_CLEAR_TIMEOUT: u16 = 212;
const N_QUEUE_MICROTASK: u16 = 213;
const N_ARRAY_BUFFER_IS_VIEW: u16 = 214;
const N_INTL_FORMAT: u16 = 215;
const N_EVAL: u16 = 216;
const N_INTL_RESOLVED_OPTIONS: u16 = 217;
const N_INTL_SUPPORTED_LOCALES: u16 = 218;
const N_INTL_FORMAT_TO_PARTS: u16 = 219;
const N_STATIC_METHOD: u16 = 220;
const N_INTL_COLLATOR: u16 = 207;
const N_INTL_PLURAL_RULES: u16 = 208;
const N_INTL_COMPARE: u16 = 209;
const N_INTL_PLURAL_SELECT: u16 = 210;
const N_INTL_PLURAL_SELECT_RANGE: u16 = 671;
const N_INTL_LIST_FORMAT: u16 = 221;
const N_INTL_LIST_FORMAT_FORMAT: u16 = 222;
const N_INTL_REL_TIME: u16 = 223;
const N_INTL_REL_TIME_FORMAT: u16 = 224;
const N_INTL_DISPLAY_NAMES: u16 = 225;
const N_INTL_DISPLAY_NAMES_OF: u16 = 226;
const N_INTL_SEGMENTER: u16 = 227;
const N_INTL_SEGMENTER_SEGMENT: u16 = 228;
const N_INTL_GET_CANONICAL_LOCALES: u16 = 460;
const N_INTL_SUPPORTED_VALUES_OF: u16 = 461;
const N_INTL_LOCALE: u16 = 500;
const N_INTL_LOCALE_ACCESSOR: u16 = 501;
const N_INTL_LOCALE_METHOD: u16 = 502;
const N_INTL_DURATION_FORMAT: u16 = 503;
const N_INTL_DURATION_METHOD: u16 = 504;
const N_INTL_PROTO_METHOD: u16 = 505;
const N_INTL_BOUND_GETTER: u16 = 506;
const N_INTL_BOUND_CALL: u16 = 507;
const N_TYPED_ARRAY_ABSTRACT: u16 = 229;
const N_TYPED_ARRAY_FROM: u16 = 230;
const N_TYPED_ARRAY_OF: u16 = 231;
const N_TYPED_ARRAY_SPECIES: u16 = 232;
const N_TYPED_ARRAY_TO_STRING_TAG: u16 = 241;
const N_TYPED_ARRAY_ACCESSOR: u16 = 247;
const N_DATA_VIEW_ACCESSOR: u16 = 300;
const N_AB_ACCESSOR: u16 = 301;
const N_DATA_VIEW_PROTO_FN: u16 = 302;
const N_DETACH_ARRAY_BUFFER: u16 = 360;
const N_ESCAPE: u16 = 420;
const N_UNESCAPE: u16 = 421;
const N_REGEXP_LEGACY_GET: u16 = 422;
const N_REGEXP_LEGACY_SET: u16 = 423;
const N_BASE_ARRAY: u16 = 480;
const N_BASE_OBJECT: u16 = 481;
const N_ITER_HELPER_NEXT: u16 = 340;
const N_ITER_HELPER_RETURN: u16 = 341;
const N_ITER_WRAP_NEXT: u16 = 342;
const N_ITER_WRAP_RETURN: u16 = 343;
const N_ITERATOR_CONCAT: u16 = 344;
const N_ITER_CONCAT_NEXT: u16 = 345;
const N_GEN_ITER_NEXT: u16 = 346;
const N_GEN_ITER_RETURN: u16 = 347;
const N_GEN_NEXT: u16 = 520;
const N_GEN_RETURN: u16 = 521;
const N_GEN_THROW: u16 = 522;
const N_ASYNC_RESUME_FULFILL: u16 = 560;
const N_ASYNC_RESUME_REJECT: u16 = 561;
const N_OBJ_DEFINE_GETTER: u16 = 348;
const N_OBJ_DEFINE_SETTER: u16 = 349;
const N_OBJ_LOOKUP_GETTER: u16 = 350;
const N_OBJ_LOOKUP_SETTER: u16 = 351;
const N_OBJ_PROTO_GET: u16 = 352;
const N_OBJ_PROTO_SET: u16 = 353;
const N_ITER_CONCAT_RETURN: u16 = 354;
const N_ITERATOR_ZIP: u16 = 355;
const N_ITERATOR_ZIP_KEYED: u16 = 356;
const N_ITER_ZIP_NEXT: u16 = 357;
const N_ITER_ZIP_RETURN: u16 = 358;
const N_ITERATOR_DISPOSE: u16 = 359;
const N_THROW_TYPE_ERROR: u16 = 400;
const N_FN_HAS_INSTANCE: u16 = 401;
const TYPED_ARRAY_PROTO_METHODS: &[(&str, u32)] = &[
("at", 1),
("copyWithin", 2),
("entries", 0),
("every", 1),
("fill", 1),
("filter", 1),
("find", 1),
("findIndex", 1),
("findLast", 1),
("findLastIndex", 1),
("forEach", 1),
("includes", 1),
("indexOf", 1),
("join", 1),
("keys", 0),
("lastIndexOf", 1),
("map", 1),
("reduce", 1),
("reduceRight", 1),
("reverse", 0),
("set", 1),
("slice", 2),
("some", 1),
("sort", 1),
("subarray", 2),
("toLocaleString", 0),
("toReversed", 0),
("toSorted", 1),
("toString", 0),
("values", 0),
("with", 2),
];
const N_TYPED_ARRAY_BASE: u16 = 168;
const TYPED_ARRAY_KINDS: [(&str, u8); 11] = [
("Int8Array", 1),
("Uint8Array", 1),
("Uint8ClampedArray", 1),
("Int16Array", 2),
("Uint16Array", 2),
("Int32Array", 4),
("Uint32Array", 4),
("Float32Array", 4),
("Float64Array", 8),
("BigInt64Array", 8),
("BigUint64Array", 8),
];
#[must_use]
pub(crate) fn is_bigint_kind(kind: u8) -> bool {
kind == 9 || kind == 10
}
const N_ARRAY_BUFFER: u16 = 234;
const N_DATA_VIEW: u16 = 235;
const N_WASM_VALIDATE: u16 = 184;
const N_WASM_INSTANTIATE: u16 = 185;
const N_WASM_CALL: u16 = 186;
const N_FUNCTION: u16 = 187;
const N_WASM_MODULE: u16 = 188;
const N_WASM_INSTANCE: u16 = 189;
const N_WASM_COMPILE: u16 = 190;
const N_WASM_GLOBAL: u16 = 191;
const N_WASM_GLOBAL_GET: u16 = 192;
const N_WASM_GLOBAL_SET: u16 = 193;
const N_WASM_MEMORY: u16 = 194;
const N_WASM_MEM_BUFFER_GET: u16 = 195;
const N_WASM_MEM_GROW: u16 = 196;
const WASM_PAGE: usize = 65536;
const N_WASM_TABLE: u16 = 197;
const N_WASM_TABLE_LEN: u16 = 198;
const N_WASM_TABLE_GET: u16 = 199;
const N_WASM_TABLE_SET: u16 = 200;
const N_WASM_TABLE_GROW: u16 = 201;
const N_WASM_MODULE_EXPORTS: u16 = 202;
const N_WASM_MODULE_IMPORTS: u16 = 203;
const WASM_BYTES: &str = "\u{0}wbytes";
const WASM_EXPORT: &str = "\u{0}wexport";
const WASM_IMPORTS: &str = "\u{0}wimports";
const WASM_IS_MODULE: &str = "\u{0}wmodule";
const WASM_INSTANCE_ID: &str = "\u{0}winst";
const WASM_GLOBAL_VALUE: &str = "\u{0}gval";
const WASM_GLOBAL_TYPE: &str = "\u{0}gtype";
const WASM_GLOBAL_MUTABLE: &str = "\u{0}gmut";
const WASM_MEM_BUFFER: &str = "\u{0}mbuf";
const WASM_MEM_PAGES: &str = "\u{0}mpages";
const WASM_MEM_MAX: &str = "\u{0}mmax";
const WASM_TABLE_ELEMS: &str = "\u{0}telems";
const WASM_TABLE_MAX: &str = "\u{0}tmax";
const N_OBJ_PROTO_TOSTRING: u16 = 179;
const N_OBJ_PROTO_VALUEOF: u16 = 180;
const N_OBJ_PROTO_HASOWN: u16 = 181;
const N_OBJ_PROTO_ISPROTOTYPEOF: u16 = 182;
const N_OBJ_PROTO_PROPISENUM: u16 = 183;
const N_OBJECT_CREATE: u16 = 107;
const N_OBJECT_GET_PROTO: u16 = 108;
const N_OBJECT_SET_PROTO: u16 = 109;
const N_OBJECT_DEFINE_PROP: u16 = 110;
const N_OBJECT_GET_OWN_DESC: u16 = 111;
const N_WEAKMAP: u16 = 112;
const N_OBJECT_IS: u16 = 123;
const N_OBJECT_HAS_OWN: u16 = 129;
const N_OBJECT_GROUP_BY: u16 = 138;
const N_OBJECT_GET_OWN_DESCS: u16 = 130;
const N_WEAKREF: u16 = 131;
const N_FINALIZATION_REGISTRY: u16 = 132;
const N_ITERATOR: u16 = 236;
const N_ITERATOR_FROM: u16 = 237;
const N_ITERATOR_PROTO_SELF: u16 = 238;
const N_ITERATOR_PROTO_FN: u16 = 239;
const ITERATOR_PROTO_METHODS: &[&str] = &[
"map", "filter", "take", "drop", "flatMap", "reduce", "toArray", "forEach", "some", "every",
"find",
];
const N_OBJECT_DEFINE_PROPS: u16 = 124;
const N_WEAKSET: u16 = 113;
const N_REFLECT_GET: u16 = 114;
const N_REFLECT_SET: u16 = 115;
const N_REFLECT_HAS: u16 = 116;
const N_REFLECT_OWN_KEYS: u16 = 117;
const N_REFLECT_DELETE: u16 = 118;
const N_REFLECT_APPLY: u16 = 119;
const N_REFLECT_CONSTRUCT: u16 = 120;
const N_REFLECT_DEFINE_PROP: u16 = 135;
const N_REFLECT_GET_OWN_DESC: u16 = 136;
const N_REFLECT_GET_PROTO: u16 = 137;
const N_REFLECT_SET_PROTO: u16 = 204;
const N_REFLECT_PREVENT_EXT: u16 = 205;
const N_REFLECT_IS_EXTENSIBLE: u16 = 252;
const N_ARRAY_PROTO_FN: u16 = 206;
const N_AB_PROTO_FN: u16 = 233;
const N_BIGINT_PROTO_FN: u16 = 240;
const N_DATE_PROTO_FN: u16 = 242;
const DATE_PROTO_METHODS: &[&str] = &[
"getTime",
"valueOf",
"getFullYear",
"getUTCFullYear",
"getMonth",
"getUTCMonth",
"getDate",
"getUTCDate",
"getDay",
"getUTCDay",
"getHours",
"getUTCHours",
"getMinutes",
"getUTCMinutes",
"getSeconds",
"getUTCSeconds",
"getMilliseconds",
"getUTCMilliseconds",
"getTimezoneOffset",
"toISOString",
"toDateString",
"toTimeString",
"toString",
"toUTCString",
"toLocaleDateString",
"toLocaleTimeString",
"toLocaleString",
"setTime",
"setFullYear",
"setUTCFullYear",
"setMonth",
"setUTCMonth",
"setDate",
"setUTCDate",
"setHours",
"setUTCHours",
"setMinutes",
"setUTCMinutes",
"setSeconds",
"setUTCSeconds",
"setMilliseconds",
"setUTCMilliseconds",
"getYear",
"setYear",
];
const N_TYPED_ARRAY_PROTO_FN: u16 = 246;
const N_REGEXP_PROTO_FN: u16 = 280;
const N_REGEXP_ACCESSOR: u16 = 281;
const N_REGEXP_SPECIES: u16 = 282;
const REGEXP_PROTO_METHODS: &[&str] = &["exec", "test", "compile", "toString"];
const REGEXP_ACCESSORS: &[&str] = &[
"source",
"flags",
"global",
"ignoreCase",
"multiline",
"dotAll",
"sticky",
"unicode",
"unicodeSets",
"hasIndices",
];
const REGEXP_SYMBOL_METHODS: &[(&str, &str)] = &[
("match", "match"),
("matchAll", "matchAll"),
("replace", "replace"),
("search", "search"),
("split", "split"),
];
const ARRAY_PROTO_METHODS: &[&str] = &[
"slice",
"splice",
"map",
"filter",
"forEach",
"reduce",
"reduceRight",
"indexOf",
"lastIndexOf",
"includes",
"find",
"findIndex",
"findLast",
"findLastIndex",
"some",
"every",
"join",
"concat",
"reverse",
"sort",
"fill",
"copyWithin",
"flat",
"flatMap",
"at",
"push",
"pop",
"shift",
"unshift",
"keys",
"values",
"entries",
"toString",
"toLocaleString",
"with",
"toReversed",
"toSorted",
"toSpliced",
];
const STRING_PROTO_METHODS: &[&str] = &[
"slice",
"substring",
"substr",
"charAt",
"charCodeAt",
"codePointAt",
"indexOf",
"lastIndexOf",
"includes",
"startsWith",
"endsWith",
"split",
"replace",
"replaceAll",
"match",
"matchAll",
"search",
"toUpperCase",
"toLowerCase",
"toLocaleUpperCase",
"toLocaleLowerCase",
"trim",
"trimStart",
"trimEnd",
"padStart",
"padEnd",
"repeat",
"concat",
"at",
"normalize",
"localeCompare",
"toString",
"valueOf",
"isWellFormed",
"toWellFormed",
"anchor",
"big",
"blink",
"bold",
"fixed",
"fontcolor",
"fontsize",
"italics",
"link",
"small",
"strike",
"sub",
"sup",
];
const NUMBER_PROTO_METHODS: &[&str] = &[
"toFixed",
"toPrecision",
"toExponential",
"toString",
"valueOf",
"toLocaleString",
];
const BOOLEAN_PROTO_METHODS: &[&str] = &["toString", "valueOf"];
const BIGINT_PROTO_METHODS: &[&str] = &["toString", "toLocaleString", "valueOf"];
const SET_PROTO_METHODS: &[&str] = &[
"add",
"has",
"delete",
"clear",
"forEach",
"keys",
"values",
"entries",
"union",
"intersection",
"difference",
"symmetricDifference",
"isSubsetOf",
"isSupersetOf",
"isDisjointFrom",
];
const MAP_PROTO_METHODS: &[&str] = &[
"set",
"get",
"has",
"delete",
"clear",
"forEach",
"keys",
"values",
"entries",
"getOrInsert",
"getOrInsertComputed",
];
const WEAKMAP_PROTO_METHODS: &[&str] = &[
"set",
"get",
"has",
"delete",
"getOrInsert",
"getOrInsertComputed",
];
const WEAKSET_PROTO_METHODS: &[&str] = &["add", "has", "delete"];
const PROMISE_PROTO_METHODS: &[&str] = &["then", "catch", "finally"];
const FUNCTION_PROTO_METHODS: &[&str] = &["call", "apply", "bind", "toString"];
const AB_PROTO_METHODS: &[&str] = &[
"slice",
"resize",
"transfer",
"transferToFixedLength",
"transferToImmutable",
"sliceToImmutable",
];
const DATA_VIEW_METHODS: &[&str] = &[
"getInt8",
"getUint8",
"getInt16",
"getUint16",
"getInt32",
"getUint32",
"getFloat16",
"getFloat32",
"getFloat64",
"getBigInt64",
"getBigUint64",
"setInt8",
"setUint8",
"setInt16",
"setUint16",
"setInt32",
"setUint32",
"setFloat16",
"setFloat32",
"setFloat64",
"setBigInt64",
"setBigUint64",
];
#[must_use]
fn builtin_method_arity(name: &str) -> u32 {
match name {
"pop" | "shift" | "reverse" | "keys" | "values" | "entries" | "toString"
| "toLocaleString" | "valueOf" | "flat" | "clear" | "trim" | "trimStart" | "trimEnd"
| "toUpperCase" | "toLowerCase" | "toLocaleUpperCase" | "toLocaleLowerCase"
| "toReversed" | "toSorted" | "isWellFormed" | "toWellFormed" | "getInt8" | "getUint8"
| "toArray" | "normalize"
| "big" | "blink" | "bold" | "fixed" | "italics" | "small"
| "strike" | "sub" | "sup"
| "getTime" | "getFullYear" | "getUTCFullYear" | "getMonth" | "getUTCMonth"
| "getDate" | "getUTCDate" | "getDay" | "getUTCDay" | "getHours" | "getUTCHours"
| "getMinutes" | "getUTCMinutes" | "getSeconds" | "getUTCSeconds"
| "getMilliseconds" | "getUTCMilliseconds" | "getTimezoneOffset"
| "toISOString" | "toDateString" | "toTimeString" | "toUTCString"
| "toLocaleDateString" | "toLocaleTimeString"
| "getYear"
| "transfer" | "transferToFixedLength" | "transferToImmutable"
| "now" => 0,
"slice" | "sliceToImmutable" | "substring" | "substr" | "splice" | "copyWithin" | "split" | "replace"
| "replaceAll" | "padStart" | "padEnd" | "with" | "setInt8" | "setUint8" | "asIntN"
| "asUintN" | "setMonth" | "setUTCMonth" | "setSeconds" | "setUTCSeconds" | "subarray"
| "getOrInsert" | "getOrInsertComputed"
| "then"
| "apply" => 2,
"setFullYear" | "setUTCFullYear" | "setMinutes" | "setUTCMinutes" => 3,
"setHours" | "setUTCHours" => 4,
"UTC" => 7,
_ => 1,
}
}
#[must_use]
fn builtin_native_arity(id: u16) -> u32 {
match id {
N_MATH_RANDOM
| N_TYPED_ARRAY_ABSTRACT
| N_TYPED_ARRAY_OF
| N_TYPED_ARRAY_SPECIES
| N_MAP_SIZE
| N_SET_SIZE
| N_TYPED_ARRAY_TO_STRING_TAG
| N_INTL_DURATION_FORMAT
| N_INTL_LIST_FORMAT
| N_INTL_REL_TIME
| N_INTL_PLURAL_RULES
| N_DISPOSABLE_STACK
| N_ASYNC_DISPOSABLE_STACK
| N_SHADOW_REALM
| N_UINT8_TO_BASE64
| N_UINT8_TO_HEX
| N_WEAKREF_DEREF
| N_SYMBOL_PROTO_TOSTRING
| N_SYMBOL_PROTO_VALUEOF
| N_SYMBOL_PROTO_DESC_GET
| N_ERROR_PROTO_STACK_GET => 0,
N_FINREG_REGISTER
| N_PROXY
| N_OBJECT_SET_PROTO
| N_OBJECT_IS
| N_OBJECT_HAS_OWN
| N_OBJECT_DEFINE_PROPS
| N_REFLECT_GET
| N_REFLECT_HAS
| N_REFLECT_GET_OWN_DESC
| N_REFLECT_SET_PROTO
| N_REFLECT_DELETE
| N_REFLECT_CONSTRUCT
| N_PARSE_INT
| N_OBJECT_GET_OWN_DESC
| N_MATH_MAX
| N_MATH_MIN
| N_MATH_POW
| N_MATH_ATAN2
| N_MATH_HYPOT
| N_MATH_IMUL => 2,
N_OBJECT_DEFINE_PROP | N_REFLECT_SET | N_REFLECT_DEFINE_PROP | N_REFLECT_APPLY
| N_SUPPRESSED_ERROR => 3,
id if (N_TYPED_ARRAY_BASE..N_TYPED_ARRAY_BASE + TYPED_ARRAY_KINDS.len() as u16)
.contains(&id) =>
{
3
}
N_DATE => 7,
_ => 1,
}
}
#[must_use]
fn is_native_constructor(id: u16) -> bool {
if (N_TYPED_ARRAY_BASE..N_TYPED_ARRAY_BASE + TYPED_ARRAY_KINDS.len() as u16).contains(&id) {
return true;
}
if (N_ERROR_BASE..N_ERROR_BASE + ERROR_NAMES.len() as u16).contains(&id) {
return true;
}
matches!(
id,
N_STRING
| N_NUMBER
| N_BOOLEAN
| N_SYMBOL
| N_BIGINT
| N_MAP
| N_SET
| N_WEAKMAP
| N_WEAKSET
| N_WEAKREF
| N_FINALIZATION_REGISTRY
| N_PROMISE
| N_PROXY
| N_DATE
| N_REGEXP
| N_FUNCTION
| N_ARRAY_BUFFER
| N_DATA_VIEW
| N_WASM_MODULE
| N_WASM_INSTANCE
| N_WASM_GLOBAL
| N_WASM_MEMORY
| N_WASM_TABLE
| N_INTL_NUMBER_FORMAT
| N_INTL_DATETIME_FORMAT
| N_INTL_COLLATOR
| N_INTL_PLURAL_RULES
| N_INTL_LIST_FORMAT
| N_INTL_REL_TIME
| N_INTL_DISPLAY_NAMES
| N_INTL_SEGMENTER
| N_INTL_LOCALE
| N_INTL_DURATION_FORMAT
| N_DISPOSABLE_STACK
| N_ASYNC_DISPOSABLE_STACK
| N_SHADOW_REALM
| N_SUPPRESSED_ERROR
)
}
const N_PROXY_REVOKE: u16 = 122;
const N_SYMBOL: u16 = 38;
const N_BIGINT: u16 = 39;
const N_PROXY: u16 = 106;
const N_PARSE_FLOAT: u16 = 31;
const N_IS_NAN: u16 = 32;
const N_IS_FINITE: u16 = 33;
const N_ERROR_BASE: u16 = 40;
const WEEKDAYS: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const MONTHS: [&str; 12] = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];
const ERROR_NAMES: [&str; 11] = [
"Error",
"TypeError",
"RangeError",
"SyntaxError",
"ReferenceError",
"AggregateError",
"CompileError",
"LinkError",
"RuntimeError",
"URIError",
"EvalError",
];
const N_GLOBAL_ERROR_COUNT: usize = 6;
const N_TYPE_ERROR: u16 = N_ERROR_BASE + 1;
const N_RANGE_ERROR: u16 = N_ERROR_BASE + 2;
const N_SYNTAX_ERROR: u16 = N_ERROR_BASE + 3;
const N_REFERENCE_ERROR: u16 = N_ERROR_BASE + 4;
const N_WASM_COMPILE_ERROR: u16 = N_ERROR_BASE + 6;
const N_WASM_LINK_ERROR: u16 = N_ERROR_BASE + 7;
const N_WASM_RUNTIME_ERROR: u16 = N_ERROR_BASE + 8;
const N_URI_ERROR: u16 = N_ERROR_BASE + 9;
const N_EVAL_ERROR: u16 = N_ERROR_BASE + 10;
const CTOR_KEY: &str = "\u{0}ctor";
const ERROR_DATA: &str = "\u{0}errordata";
const HOME_OBJECT: &str = "\u{0}home";
const ARROW_THIS: &str = "\u{0}athis";
const ARROW_NEW_TARGET: &str = "\u{0}antgt";
const ARROW_HOME_OBJ: &str = "\u{0}ahome";
const ARROW_HOME_CLASS: &str = "\u{0}ahcls";
const ARROW_HOME_STATIC: &str = "\u{0}ahsta";
const SYMBOL_NO_DESC: &str = "\u{0}nodesc";
const WEAKREF_TARGET: &str = "\u{0}wrtarget";
const FINREG_TAG: &str = "\u{0}finreg";
const GEN_BUF: &str = "\u{0}gbuf";
const GEN_IDX: &str = "\u{0}gidx";
const GEN_RET: &str = "\u{0}gret";
const GEN_FRAME: &str = "\u{0}gframe";
const ASYNC_PROMISE: &str = "\u{0}aprom";
const HELPER_KIND: &str = "\u{0}hkind";
const HELPER_SOURCE: &str = "\u{0}hsrc";
const HELPER_NEXT: &str = "\u{0}hnext";
const HELPER_FN: &str = "\u{0}hfn";
const HELPER_LIMIT: &str = "\u{0}hlimit";
const HELPER_COUNTER: &str = "\u{0}hcounter";
const HELPER_DONE: &str = "\u{0}hdone";
const HELPER_INNER: &str = "\u{0}hinner";
const HELPER_INNER_NEXT: &str = "\u{0}hinnext";
const ITER_HELPER_PROTO_SLOT: &str = "\u{0}ihproto";
const ITER_WRAP_PROTO_SLOT: &str = "\u{0}iwproto";
const ITER_CONCAT_PROTO_SLOT: &str = "\u{0}icproto";
const ITER_ZIP_PROTO_SLOT: &str = "\u{0}izproto";
const ZIP_ITERS: &str = "\u{0}zits";
const ZIP_NEXTS: &str = "\u{0}znexts";
const ZIP_MODE: &str = "\u{0}zmode";
const ZIP_PADDING: &str = "\u{0}zpad";
const ZIP_KEYS: &str = "\u{0}zkeys";
const ZIP_DONE: &str = "\u{0}zdone";
const ZIP_FINISHED: &str = "\u{0}zfin";
const PRIM_WRAP: &str = "\u{0}prim";
const PRIM_WRAP_TYPE: &str = "\u{0}primtype";
const ARGS_MARKER: &str = "\u{0}args";
const ARRAY_BUFFER_BYTES: &str = "\u{0}abytes";
const ARRAY_BUFFER_DETACHED: &str = "\u{0}abdetached";
const ARRAY_BUFFER_MAXLEN: &str = "\u{0}abmaxlen";
const ARRAY_BUFFER_IMMUTABLE: &str = "\u{0}abimmutable";
const DATA_VIEW_BUF: &str = "\u{0}dvbuf";
const DATA_VIEW_OFF: &str = "\u{0}dvoff";
const DATA_VIEW_LEN: &str = "\u{0}dvlen";
const ARRAY_BUFFER_PROTO_BRAND: &str = "\u{0}abproto";
const DATA_VIEW_PROTO_BRAND: &str = "\u{0}dvproto";
const TYPED_ARRAY_PROTO_BRAND: &str = "\u{0}taproto";
const REGEXP_PROTO_BRAND: &str = "\u{0}reproto";
const BOUND_TARGET: &str = "\u{0}bnd_t";
const BOUND_THIS: &str = "\u{0}bnd_this";
const BOUND_ARGS: &str = "\u{0}bnd_args";
const DYN_FN_MARKER: &str = "\u{0}dynfn";
const GEN_CAP: usize = 1_000_000;
const N_RESOLVE: u16 = 100;
const N_REJECT: u16 = 101;
const N_MATH_HYPOT: u16 = 102;
const N_MATH_CBRT: u16 = 103;
const N_MATH_LOG2: u16 = 104;
const N_MATH_LOG10: u16 = 105;
const N_MATH_EXP: u16 = 133;
const N_MATH_LOG: u16 = 134;
const N_MATH_RANDOM: u16 = 139;
const N_MATH_SIN: u16 = 140;
const N_MATH_COS: u16 = 141;
const N_MATH_TAN: u16 = 142;
const N_MATH_ASIN: u16 = 143;
const N_MATH_ACOS: u16 = 144;
const N_MATH_ATAN: u16 = 145;
const N_MATH_ATAN2: u16 = 146;
const N_MATH_SINH: u16 = 147;
const N_MATH_COSH: u16 = 148;
const N_MATH_TANH: u16 = 149;
const N_MATH_ASINH: u16 = 150;
const N_MATH_ACOSH: u16 = 151;
const N_MATH_ATANH: u16 = 152;
const N_MATH_EXPM1: u16 = 153;
const N_MATH_LOG1P: u16 = 154;
const N_MATH_FROUND: u16 = 155;
const N_MATH_CLZ32: u16 = 156;
const N_MATH_IMUL: u16 = 157;
const N_MATH_F16ROUND: u16 = 248;
const N_DATE_TO_PRIMITIVE: u16 = 243;
const N_DATE_TO_JSON: u16 = 244;
const N_STRING_PROTO_FN: u16 = 245;
const N_NUMBER_PROTO_FN: u16 = 249;
const N_BOOLEAN_PROTO_FN: u16 = 250;
const N_ERROR_PROTO_TOSTRING: u16 = 251;
const N_SET_PROTO_FN: u16 = 253;
const N_MAP_PROTO_FN: u16 = 254;
const N_WEAKMAP_PROTO_FN: u16 = 255;
const N_WEAKSET_PROTO_FN: u16 = 256;
const N_DISPOSABLE_STACK: u16 = 540;
const N_DISPOSABLE_STACK_PROTO: u16 = 541;
const N_DISPOSABLE_STACK_DISPOSED: u16 = 542;
const N_ASYNC_DISPOSABLE_STACK: u16 = 543;
const N_ASYNC_DISPOSABLE_STACK_PROTO: u16 = 544;
const N_ASYNC_DISPOSABLE_STACK_DISPOSED: u16 = 545;
const N_SHADOW_REALM: u16 = 546;
const N_SHADOW_REALM_PROTO: u16 = 547;
const N_SHADOW_REALM_WRAPPED: u16 = 548;
const N_DSTACK_ADOPT_CALL: u16 = 549;
const N_SUPPRESSED_ERROR: u16 = 550;
const N_PROMISE_ALL_ELEMENT: u16 = 640;
const N_PROMISE_ALLSETTLED_FULFILL: u16 = 641;
const N_PROMISE_ALLSETTLED_REJECT: u16 = 642;
const N_PROMISE_ANY_ELEMENT: u16 = 643;
const N_PROMISE_ALLKEYED_ELEMENT: u16 = 672;
const N_PROMISE_ALLSETTLEDKEYED_FULFILL: u16 = 673;
const N_PROMISE_ALLSETTLEDKEYED_REJECT: u16 = 674;
const N_PROMISE_THEN_FINALLY: u16 = 644;
const N_PROMISE_CATCH_FINALLY: u16 = 645;
const N_PROMISE_VALUE_THUNK: u16 = 646;
const N_PROMISE_THROW_THUNK: u16 = 647;
const PFIN_ONFINALLY: &str = "\u{0}pf_onf";
const PFIN_CTOR: &str = "\u{0}pf_ctor";
const PFIN_VALUE: &str = "\u{0}pf_val";
const N_PROMISE_CAPABILITY_EXECUTOR: u16 = 649;
const N_JSON_RAW: u16 = 650;
const N_JSON_IS_RAW: u16 = 651;
const N_ARRAY_FROM_ASYNC: u16 = 652;
const N_REGEXP_ESCAPE: u16 = 653;
const N_UINT8_TO_BASE64: u16 = 654;
const N_UINT8_TO_HEX: u16 = 655;
const N_UINT8_SET_FROM_BASE64: u16 = 656;
const N_UINT8_SET_FROM_HEX: u16 = 657;
const N_UINT8_FROM_BASE64: u16 = 658;
const N_UINT8_FROM_HEX: u16 = 659;
const N_ERROR_IS_ERROR: u16 = 660;
const N_MATH_SUM_PRECISE: u16 = 661;
const N_WEAKREF_DEREF: u16 = 662;
const N_FINREG_REGISTER: u16 = 663;
const N_FINREG_UNREGISTER: u16 = 664;
const N_SYMBOL_PROTO_TOSTRING: u16 = 665;
const N_SYMBOL_PROTO_VALUEOF: u16 = 666;
const N_SYMBOL_PROTO_DESC_GET: u16 = 667;
const N_SYMBOL_PROTO_TOPRIMITIVE: u16 = 668;
const N_ERROR_PROTO_STACK_GET: u16 = 669;
const N_ERROR_PROTO_STACK_SET: u16 = 670;
const FINREG_CELLS: &str = "\u{0}finregcells";
const RAW_JSON_BRAND: &str = "\u{0}rawjson";
const PCAP_RESOLVE: &str = "\u{0}pcap_res";
const PCAP_REJECT: &str = "\u{0}pcap_rej";
const PCOMB_REMAINING: &str = "\u{0}pc_rem";
const PCOMB_VALUES: &str = "\u{0}pc_vals";
const PCOMB_CAP: &str = "\u{0}pc_cap";
const PCOMB_RESOLVE: &str = "\u{0}pc_res";
const PCOMB_REJECT: &str = "\u{0}pc_rej";
const PCOMB_INDEX: &str = "\u{0}pc_idx";
const PCOMB_CALLED: &str = "\u{0}pc_called";
mod base64;
mod call;
mod class;
mod convert;
mod expr;
mod generator;
mod intl_fmt;
mod iterator;
mod json;
mod method_dispatch;
#[cfg(all(feature = "module", feature = "std"))]
pub mod module;
mod native_dispatch;
mod object;
mod promise;
mod regexp;
mod resource;
mod stmt;
mod typed_array;
mod wasm;
impl<'a> Interp<'a> {
#[must_use]
pub fn new() -> Self {
Self::new_with_limits(crate::limits::Limits::default())
}
#[must_use]
pub fn new_with_limits(limits: crate::limits::Limits) -> Self {
let mut interp = Self {
realm: Realm::with_limits(limits),
current: Scope::root(),
var_scope: Scope::root(),
annexb_block_fns: Vec::new(),
functions: Vec::new(),
classes: Vec::new(),
pending_this_init: None,
class_member_keys: Vec::new(),
builtin_iter_protos: alloc::collections::BTreeMap::new(),
class_statics: Vec::new(),
class_static_fields: Vec::new(),
class_static_get: Vec::new(),
class_static_set: Vec::new(),
class_envs: Vec::new(),
class_native_super: Vec::new(),
class_fn_super: Vec::new(),
class_handles: Vec::new(),
private_method_cache: alloc::collections::BTreeMap::new(),
class_lexical_parent: Vec::new(),
class_private_names: Vec::new(),
pending_class_name: None,
call_depth: 0,
new_target_in_scope: false,
eval_depth: 0,
rng_state: math_random_seed(),
this_val: NanBox::undefined(),
new_target: NanBox::undefined(),
pending_new_target: None,
reflect_new_target: None,
array_proto_generic: false,
array_like_present: None,
wasm_states: alloc::collections::BTreeMap::new(),
wasm_modules: alloc::collections::BTreeMap::new(),
wasm_mem_objs: alloc::collections::BTreeMap::new(),
wasm_next_id: 0,
gen_sink: None,
gen_frames: Vec::new(),
pending_async_start: None,
gen_is_async: false,
symbol_registry: alloc::collections::BTreeMap::new(),
well_known_symbols: alloc::collections::BTreeMap::new(),
tagged_template_cache: alloc::collections::BTreeMap::new(),
regexp_proto: None,
regexp_ctor: None,
#[cfg(feature = "intl")]
intl_intern: alloc::collections::BTreeMap::new(),
method_name_intern: alloc::collections::BTreeMap::new(),
pending_super: None,
pending_super_native: None,
pending_super_fn: None,
current_home: None,
current_lexical_home: None,
current_home_object: None,
eval_param_names: None,
current_home_static: false,
pending_label: None,
microtasks: Vec::new(),
macrotasks: Vec::new(),
timer_next_id: 1,
timer_seq: 0,
strict: false,
global_this: NanBox::undefined(),
output: String::new(),
global_scope: Scope::root(),
shadow_realm_scopes: Vec::new(),
eval_programs: alloc::collections::BTreeMap::new(),
#[cfg(all(feature = "module", feature = "std"))]
modules: module::ModuleRegistry::new(),
#[cfg(all(feature = "module", feature = "std"))]
module_imports: alloc::rc::Rc::new(alloc::collections::BTreeMap::new()),
#[cfg(all(feature = "module", feature = "std"))]
import_meta: None,
#[cfg(all(feature = "module", feature = "std"))]
script_import_base: None,
#[cfg(all(feature = "module", feature = "std"))]
module_namespaces: alloc::collections::BTreeMap::new(),
#[cfg(all(feature = "module", feature = "std"))]
deferred_namespaces: alloc::collections::BTreeMap::new(),
#[cfg(all(feature = "module", feature = "std"))]
active_module_key: None,
};
interp.global_scope = interp.current.clone();
interp.var_scope = interp.current.clone();
interp.install_globals();
interp
}
#[must_use]
pub fn output(&self) -> &str {
&self.output
}
#[must_use]
pub fn display(&self, value: NanBox) -> String {
self.realm.to_display_string(value)
}
pub(crate) fn install_fn_name_length(&mut self, f: Handle, name: &str, length: u32) {
self.realm
.set_property(f, "length", NanBox::number(f64::from(length)));
self.realm.mark_hidden(f, "length");
self.realm.set_readonly_property(f, "length");
let name_v = self.new_str(name);
self.realm.set_property(f, "name", name_v);
self.realm.mark_hidden(f, "name");
self.realm.set_readonly_property(f, "name");
}
fn install_method_meta(&mut self, f: NanBox, name: &str, params: &'a [Param]) {
let Some(raw) = f.as_handle() else { return };
let handle = Handle::from_raw(raw);
if let Some((func_id, _)) = self.realm.function_at(handle)
&& self.functions[func_id as usize].name.is_empty()
{
self.functions[func_id as usize].name = self.intern_method_name(name);
}
if self.realm.has_own(handle, "name") {
return;
}
let len = params
.iter()
.take_while(|p| p.default.is_none() && !p.rest)
.count() as u32;
self.install_fn_name_length(handle, name, len);
}
fn intern_method_name(&mut self, s: &str) -> &'a str {
if let Some(&v) = self.method_name_intern.get(s) {
return v;
}
let leaked: &'static str = alloc::boxed::Box::leak(String::from(s).into_boxed_str());
self.method_name_intern.insert(String::from(s), leaked);
leaked
}
fn new_named_native(&mut self, name: &str, id: u16) -> Handle {
let f = self.realm.new_native(id);
self.install_fn_name_length(f, name, builtin_native_arity(id));
f
}
fn setup_first_class_prototype(&mut self, ctor_name: &str, methods: &[&str]) {
self.setup_first_class_prototype_id(ctor_name, methods, N_ARRAY_PROTO_FN);
}
fn install_to_string_tag(&mut self, obj: Handle, tag: &str) {
let sym = self.well_known_symbol("toStringTag");
let key = self.member_key(sym);
let val = self.new_str(tag);
self.realm.set_property(obj, &key, val);
self.realm.mark_hidden(obj, &key);
self.realm.set_readonly_property(obj, &key);
}
fn install_collection_accessors(&mut self, ctor_name: &str, size_native: u16) {
let Some(ctor) = self
.current
.get(ctor_name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
else {
return;
};
let Some(proto) = self
.realm
.get_property(ctor, "prototype")
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
else {
return;
};
let size_get = self.new_named_native("get size", size_native);
self.install_fn_name_length(size_get, "get size", 0);
self.realm.define_accessor(
proto,
"size",
NanBox::handle(size_get.to_raw()),
NanBox::undefined(),
);
self.realm.mark_hidden(proto, "size");
let species_sym = self.well_known_symbol("species");
let species_key = self.member_key(species_sym);
let species_get = self.new_named_native("get [Symbol.species]", N_TYPED_ARRAY_SPECIES);
self.install_fn_name_length(species_get, "get [Symbol.species]", 0);
self.realm.define_accessor(
ctor,
&species_key,
NanBox::handle(species_get.to_raw()),
NanBox::undefined(),
);
self.realm.mark_hidden(ctor, &species_key);
}
fn install_ctor_species(&mut self, ctor_name: &str) {
let Some(ctor) = self
.current
.get(ctor_name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
else {
return;
};
let species_sym = self.well_known_symbol("species");
let species_key = self.member_key(species_sym);
let species_get = self.new_named_native("get [Symbol.species]", N_TYPED_ARRAY_SPECIES);
self.install_fn_name_length(species_get, "get [Symbol.species]", 0);
self.realm.define_accessor(
ctor,
&species_key,
NanBox::handle(species_get.to_raw()),
NanBox::undefined(),
);
self.realm.mark_hidden(ctor, &species_key);
}
fn install_proto_to_string_tag(&mut self, ctor_name: &str, tag: &str) {
if let Some(proto) = self
.current
.get(ctor_name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|c| self.realm.get_property(c, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
{
self.install_to_string_tag(proto, tag);
}
}
fn object_prototype(&self) -> Option<Handle> {
self.current
.get("Object")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|c| self.realm.get_property(c, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
}
fn setup_first_class_prototype_id(
&mut self,
ctor_name: &str,
methods: &[&str],
native_id: u16,
) {
let Some(ns) = self
.current
.get(ctor_name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
else {
return;
};
let obj_proto = self.object_prototype();
let proto = self.realm.new_object_with_proto(obj_proto);
for &name in methods {
let name_h = self.realm.new_string(name);
let f = self.realm.new_bound_native(native_id, name_h);
self.install_fn_name_length(f, name, builtin_method_arity(name));
self.realm
.set_property(proto, name, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(proto, name);
}
self.realm
.set_property(ns, "prototype", NanBox::handle(proto.to_raw()));
self.realm.mark_hidden(ns, "prototype");
self.realm.set_readonly_property(ns, "prototype");
self.realm.set_non_configurable_property(ns, "prototype");
self.realm
.set_hidden_property(proto, "constructor", NanBox::handle(ns.to_raw()));
}
fn setup_direct_prototype(&mut self, ctor_name: &str, methods: &[(&str, u16)]) {
let Some(ns) = self
.current
.get(ctor_name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
else {
return;
};
let obj_proto = self.object_prototype();
let proto = self.realm.new_object_with_proto(obj_proto);
for &(name, id) in methods {
let f = self.new_named_native(name, id);
self.realm
.set_property(proto, name, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(proto, name);
}
self.realm
.set_property(ns, "prototype", NanBox::handle(proto.to_raw()));
self.realm.mark_hidden(ns, "prototype");
self.realm.set_readonly_property(ns, "prototype");
self.realm.set_non_configurable_property(ns, "prototype");
self.realm
.set_hidden_property(proto, "constructor", NanBox::handle(ns.to_raw()));
}
fn setup_typed_array_intrinsic(&mut self, obj_proto: Handle) {
let ta = self.new_named_native("TypedArray", N_TYPED_ARRAY_ABSTRACT);
self.realm
.set_hidden_property(ta, "length", NanBox::number(0.0));
self.realm.set_readonly_property(ta, "length");
self.realm.set_typed_array_intrinsic(ta);
if let Some(func_proto) = self
.current
.get("Function")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|f| self.realm.get_property(f, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
{
self.realm.set_native_proto(ta, func_proto);
}
let ta_proto = self.realm.new_object_with_proto(Some(obj_proto));
self.realm
.set_hidden_property(ta_proto, "constructor", NanBox::handle(ta.to_raw()));
self.realm
.set_hidden_property(ta_proto, TYPED_ARRAY_PROTO_BRAND, NanBox::boolean(true));
self.realm
.set_property(ta, "prototype", NanBox::handle(ta_proto.to_raw()));
self.realm.mark_hidden(ta, "prototype");
let from_fn = self.new_named_native("from", N_TYPED_ARRAY_FROM);
self.realm
.set_hidden_property(from_fn, "length", NanBox::number(1.0));
self.realm.set_readonly_property(from_fn, "length");
self.realm
.set_property(ta, "from", NanBox::handle(from_fn.to_raw()));
self.realm.mark_hidden(ta, "from");
let of_fn = self.new_named_native("of", N_TYPED_ARRAY_OF);
self.realm
.set_hidden_property(of_fn, "length", NanBox::number(0.0));
self.realm.set_readonly_property(of_fn, "length");
self.realm
.set_property(ta, "of", NanBox::handle(of_fn.to_raw()));
self.realm.mark_hidden(ta, "of");
let species_sym = self.well_known_symbol("species");
let species_key = self.member_key(species_sym);
let species_get = self.new_named_native("get [Symbol.species]", N_TYPED_ARRAY_SPECIES);
self.realm.define_accessor(
ta,
&species_key,
NanBox::handle(species_get.to_raw()),
NanBox::undefined(),
);
for &(name, arity) in TYPED_ARRAY_PROTO_METHODS {
let name_h = self.realm.new_string(name);
let f = self.realm.new_bound_native(N_TYPED_ARRAY_PROTO_FN, name_h);
self.install_fn_name_length(f, name, arity);
self.realm
.set_property(ta_proto, name, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(ta_proto, name);
}
let values_fn = self
.realm
.get_property(ta_proto, "values")
.unwrap_or(NanBox::undefined());
let iter_sym = self.well_known_symbol("iterator");
let iter_key = self.member_key(iter_sym);
self.realm.set_property(ta_proto, &iter_key, values_fn);
self.realm.mark_hidden(ta_proto, &iter_key);
let tag_sym = self.well_known_symbol("toStringTag");
let tag_key = self.member_key(tag_sym);
let tag_get =
self.new_named_native("get [Symbol.toStringTag]", N_TYPED_ARRAY_TO_STRING_TAG);
self.realm.define_accessor(
ta_proto,
&tag_key,
NanBox::handle(tag_get.to_raw()),
NanBox::undefined(),
);
for accessor in ["buffer", "byteLength", "byteOffset", "length"] {
let name_h = self.realm.new_string(accessor);
let getter = self.realm.new_bound_native(N_TYPED_ARRAY_ACCESSOR, name_h);
self.install_fn_name_length(getter, &alloc::format!("get {accessor}"), 0);
self.realm.define_accessor(
ta_proto,
accessor,
NanBox::handle(getter.to_raw()),
NanBox::undefined(),
);
self.realm.mark_hidden(ta_proto, accessor);
}
for (i, (name, _)) in TYPED_ARRAY_KINDS.iter().enumerate() {
let Some(ctor) = self
.current
.get(name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
else {
continue;
};
self.realm.set_native_proto(ctor, ta);
let kind_proto = self.realm.new_object_with_proto(Some(ta_proto));
self.realm.set_hidden_property(
kind_proto,
"constructor",
NanBox::handle(ctor.to_raw()),
);
self.realm
.set_property(ctor, "prototype", NanBox::handle(kind_proto.to_raw()));
self.realm.mark_hidden(ctor, "prototype");
self.realm.set_readonly_property(ctor, "prototype");
self.realm.set_non_configurable_property(ctor, "prototype");
let bpe = f64::from(TYPED_ARRAY_KINDS[i].1);
for target in [ctor, kind_proto] {
self.realm
.set_property(target, "BYTES_PER_ELEMENT", NanBox::number(bpe));
self.realm.mark_hidden(target, "BYTES_PER_ELEMENT");
self.realm
.set_readonly_property(target, "BYTES_PER_ELEMENT");
self.realm
.set_non_configurable_property(target, "BYTES_PER_ELEMENT");
}
}
for name in ["ArrayBuffer", "DataView"] {
let Some(ctor) = self
.current
.get(name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
else {
continue;
};
let proto = self.realm.new_object_with_proto(Some(obj_proto));
self.realm
.set_hidden_property(proto, "constructor", NanBox::handle(ctor.to_raw()));
let brand = if name == "ArrayBuffer" {
ARRAY_BUFFER_PROTO_BRAND
} else {
DATA_VIEW_PROTO_BRAND
};
self.realm
.set_hidden_property(proto, brand, NanBox::boolean(true));
let (accessor_id, accessors): (u16, &[&str]) = if name == "ArrayBuffer" {
(
N_AB_ACCESSOR,
&[
"byteLength",
"maxByteLength",
"resizable",
"detached",
"immutable",
],
)
} else {
(
N_DATA_VIEW_ACCESSOR,
&["buffer", "byteLength", "byteOffset"],
)
};
for accessor in accessors {
let name_h = self.realm.new_string(accessor);
let getter = self.realm.new_bound_native(accessor_id, name_h);
self.install_fn_name_length(getter, &alloc::format!("get {accessor}"), 0);
self.realm.define_accessor(
proto,
accessor,
NanBox::handle(getter.to_raw()),
NanBox::undefined(),
);
self.realm.mark_hidden(proto, accessor);
}
if name == "DataView" {
for &m in DATA_VIEW_METHODS {
let m_h = self.realm.new_string(m);
let f = self.realm.new_bound_native(N_DATA_VIEW_PROTO_FN, m_h);
let arity = if m.starts_with("set") { 2 } else { 1 };
self.install_fn_name_length(f, m, arity);
self.realm
.set_property(proto, m, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(proto, m);
}
let tag_sym = self.well_known_symbol("toStringTag");
let tag_key = self.member_key(tag_sym);
let tag_val = self.realm.new_string("DataView");
self.realm
.set_property(proto, &tag_key, NanBox::handle(tag_val.to_raw()));
self.realm.mark_hidden(proto, &tag_key);
self.realm.set_readonly_property(proto, &tag_key);
}
if name == "ArrayBuffer" {
for &m in AB_PROTO_METHODS {
let f = self.readable_ab_method(m);
self.realm.set_property(proto, m, f);
self.realm.mark_hidden(proto, m);
}
let tag_sym = self.well_known_symbol("toStringTag");
let tag_key = self.member_key(tag_sym);
let tag_val = self.realm.new_string("ArrayBuffer");
self.realm
.set_property(proto, &tag_key, NanBox::handle(tag_val.to_raw()));
self.realm.mark_hidden(proto, &tag_key);
self.realm.set_readonly_property(proto, &tag_key);
}
self.realm
.set_property(ctor, "prototype", NanBox::handle(proto.to_raw()));
self.realm.mark_hidden(ctor, "prototype");
self.realm.set_readonly_property(ctor, "prototype");
self.realm.set_non_configurable_property(ctor, "prototype");
}
}
fn readable_native_method(&mut self, name: &str) -> NanBox {
let name_h = self.realm.new_string(name);
let f = self.realm.new_bound_native(N_ARRAY_PROTO_FN, name_h);
self.install_fn_name_length(f, name, builtin_method_arity(name));
NanBox::handle(f.to_raw())
}
fn readable_ab_method(&mut self, name: &str) -> NanBox {
let name_h = self.realm.new_string(name);
let f = self.realm.new_bound_native(N_AB_PROTO_FN, name_h);
self.install_fn_name_length(f, name, builtin_method_arity(name));
NanBox::handle(f.to_raw())
}
fn setup_static_methods(&mut self, ctor_name: &str, methods: &[&str]) {
let Some(ns) = self
.current
.get(ctor_name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
else {
return;
};
for &name in methods {
let name_h = self.realm.new_string(name);
let pair = self.realm.new_array(alloc::vec![
NanBox::handle(ns.to_raw()),
NanBox::handle(name_h.to_raw()),
]);
let f = self.realm.new_bound_native(N_STATIC_METHOD, pair);
self.install_fn_name_length(f, name, builtin_method_arity(name));
self.realm
.set_property(ns, name, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(ns, name);
}
}
fn install_uint8array_base64(&mut self) {
let Some(ctor) = self
.current
.get("Uint8Array")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
else {
return;
};
let proto = self
.realm
.get_property(ctor, "prototype")
.and_then(|p| p.as_handle())
.map(Handle::from_raw);
let install = |this: &mut Self, target: Handle, name: &str, id: u16| {
let f = this.new_named_native(name, id);
this.realm
.set_property(target, name, NanBox::handle(f.to_raw()));
this.realm.mark_hidden(target, name);
};
if let Some(proto) = proto {
install(self, proto, "toBase64", N_UINT8_TO_BASE64);
install(self, proto, "toHex", N_UINT8_TO_HEX);
install(self, proto, "setFromBase64", N_UINT8_SET_FROM_BASE64);
install(self, proto, "setFromHex", N_UINT8_SET_FROM_HEX);
}
install(self, ctor, "fromBase64", N_UINT8_FROM_BASE64);
install(self, ctor, "fromHex", N_UINT8_FROM_HEX);
}
fn install_globals(&mut self) {
let install_namespace = |this: &mut Self, global_name: &str, methods: &[(&str, u16)]| {
let obj = this.realm.new_object();
for (name, id) in methods {
let f = this.new_named_native(name, *id);
this.realm
.set_property(obj, name, NanBox::handle(f.to_raw()));
this.realm.mark_hidden(obj, name);
}
this.current
.declare(global_name, NanBox::handle(obj.to_raw()));
};
let install_ctor_namespace =
|this: &mut Self, global_name: &str, ctor_id: u16, methods: &[(&str, u16)]| {
let obj = this.new_named_native(global_name, ctor_id);
for (name, id) in methods {
let f = this.new_named_native(name, *id);
this.realm
.set_property(obj, name, NanBox::handle(f.to_raw()));
this.realm.mark_hidden(obj, name);
}
this.current
.declare(global_name, NanBox::handle(obj.to_raw()));
};
install_namespace(
self,
"Math",
&[
("max", N_MATH_MAX),
("min", N_MATH_MIN),
("abs", N_MATH_ABS),
("floor", N_MATH_FLOOR),
("ceil", N_MATH_CEIL),
("round", N_MATH_ROUND),
("sqrt", N_MATH_SQRT),
("pow", N_MATH_POW),
("sign", N_MATH_SIGN),
("hypot", N_MATH_HYPOT),
("cbrt", N_MATH_CBRT),
("log2", N_MATH_LOG2),
("log10", N_MATH_LOG10),
("exp", N_MATH_EXP),
("log", N_MATH_LOG),
("random", N_MATH_RANDOM),
("trunc", N_MATH_TRUNC),
("sin", N_MATH_SIN),
("cos", N_MATH_COS),
("tan", N_MATH_TAN),
("asin", N_MATH_ASIN),
("acos", N_MATH_ACOS),
("atan", N_MATH_ATAN),
("atan2", N_MATH_ATAN2),
("sinh", N_MATH_SINH),
("cosh", N_MATH_COSH),
("tanh", N_MATH_TANH),
("asinh", N_MATH_ASINH),
("acosh", N_MATH_ACOSH),
("atanh", N_MATH_ATANH),
("expm1", N_MATH_EXPM1),
("log1p", N_MATH_LOG1P),
("fround", N_MATH_FROUND),
("f16round", N_MATH_F16ROUND),
("clz32", N_MATH_CLZ32),
("imul", N_MATH_IMUL),
("sumPrecise", N_MATH_SUM_PRECISE),
],
);
if let Some(mh) = self.current.get("Math").and_then(NanBox::as_handle) {
let math = Handle::from_raw(mh);
for (name, value) in [
("PI", core::f64::consts::PI),
("E", core::f64::consts::E),
("LN2", core::f64::consts::LN_2),
("LN10", core::f64::consts::LN_10),
("LOG2E", core::f64::consts::LOG2_E),
("LOG10E", core::f64::consts::LOG10_E),
("SQRT2", core::f64::consts::SQRT_2),
("SQRT1_2", core::f64::consts::FRAC_1_SQRT_2),
] {
self.realm.set_property(math, name, NanBox::number(value));
self.realm.mark_hidden(math, name);
self.realm.set_readonly_property(math, name);
self.realm.set_non_configurable_property(math, name);
}
let tag_sym = self.well_known_symbol("toStringTag");
let tag_key = self.member_key(tag_sym);
let tag_val = self.new_str("Math");
self.realm.set_property(math, &tag_key, tag_val);
self.realm.mark_hidden(math, &tag_key);
self.realm.set_readonly_property(math, &tag_key);
}
install_namespace(self, "console", &[("log", N_CONSOLE_LOG)]);
let promise_ctor = self.new_named_native("Promise", N_PROMISE);
self.current
.declare("Promise", NanBox::handle(promise_ctor.to_raw()));
let date_ctor = self.new_named_native("Date", N_DATE);
self.current
.declare("Date", NanBox::handle(date_ctor.to_raw()));
let regexp_ctor = self.new_named_native("RegExp", N_REGEXP);
self.current
.declare("RegExp", NanBox::handle(regexp_ctor.to_raw()));
let escape_fn = self.new_named_native("escape", N_REGEXP_ESCAPE);
self.realm
.set_property(regexp_ctor, "escape", NanBox::handle(escape_fn.to_raw()));
self.realm.mark_hidden(regexp_ctor, "escape");
for (i, name) in ERROR_NAMES.iter().enumerate().take(N_GLOBAL_ERROR_COUNT) {
let ctor = self.new_named_native(name, N_ERROR_BASE + i as u16);
self.current.declare(name, NanBox::handle(ctor.to_raw()));
if i == 0 {
let is_error_fn = self.new_named_native("isError", N_ERROR_IS_ERROR);
self.realm
.set_property(ctor, "isError", NanBox::handle(is_error_fn.to_raw()));
self.realm.mark_hidden(ctor, "isError");
}
}
install_namespace(
self,
"JSON",
&[
("stringify", N_JSON_STRINGIFY),
("parse", N_JSON_PARSE),
("rawJSON", N_JSON_RAW),
("isRawJSON", N_JSON_IS_RAW),
],
);
install_ctor_namespace(
self,
"Object",
N_BASE_OBJECT,
&[
("keys", N_OBJECT_KEYS),
("values", N_OBJECT_VALUES),
("assign", N_OBJECT_ASSIGN),
("entries", N_OBJECT_ENTRIES),
("fromEntries", N_OBJECT_FROM_ENTRIES),
("freeze", N_OBJECT_FREEZE),
("isFrozen", N_OBJECT_IS_FROZEN),
("seal", N_OBJECT_SEAL),
("isSealed", N_OBJECT_IS_SEALED),
("preventExtensions", N_OBJECT_PREVENT_EXT),
("isExtensible", N_OBJECT_IS_EXTENSIBLE),
("getOwnPropertyNames", N_OBJECT_GET_OWN_NAMES),
("getOwnPropertySymbols", N_OBJECT_GET_OWN_SYMBOLS),
("create", N_OBJECT_CREATE),
("getPrototypeOf", N_OBJECT_GET_PROTO),
("setPrototypeOf", N_OBJECT_SET_PROTO),
("defineProperty", N_OBJECT_DEFINE_PROP),
("defineProperties", N_OBJECT_DEFINE_PROPS),
("getOwnPropertyDescriptor", N_OBJECT_GET_OWN_DESC),
("getOwnPropertyDescriptors", N_OBJECT_GET_OWN_DESCS),
("is", N_OBJECT_IS),
("hasOwn", N_OBJECT_HAS_OWN),
("groupBy", N_OBJECT_GROUP_BY),
],
);
install_ctor_namespace(
self,
"Array",
N_BASE_ARRAY,
&[
("isArray", N_ARRAY_IS_ARRAY),
("from", N_ARRAY_FROM),
("fromAsync", N_ARRAY_FROM_ASYNC),
("of", N_ARRAY_OF),
],
);
install_namespace(
self,
"Reflect",
&[
("get", N_REFLECT_GET),
("set", N_REFLECT_SET),
("has", N_REFLECT_HAS),
("ownKeys", N_REFLECT_OWN_KEYS),
("defineProperty", N_REFLECT_DEFINE_PROP),
("getOwnPropertyDescriptor", N_REFLECT_GET_OWN_DESC),
("getPrototypeOf", N_REFLECT_GET_PROTO),
("setPrototypeOf", N_REFLECT_SET_PROTO),
("deleteProperty", N_REFLECT_DELETE),
("apply", N_REFLECT_APPLY),
("construct", N_REFLECT_CONSTRUCT),
("isExtensible", N_REFLECT_IS_EXTENSIBLE),
("preventExtensions", N_REFLECT_PREVENT_EXT),
],
);
for (name, id) in [
("String", N_STRING),
("Number", N_NUMBER),
("Boolean", N_BOOLEAN),
("parseInt", N_PARSE_INT),
("parseFloat", N_PARSE_FLOAT),
("isNaN", N_IS_NAN),
("isFinite", N_IS_FINITE),
("Map", N_MAP),
("Set", N_SET),
("Symbol", N_SYMBOL),
("BigInt", N_BIGINT),
("Function", N_FUNCTION),
("Proxy", N_PROXY),
("WeakMap", N_WEAKMAP),
("WeakSet", N_WEAKSET),
("WeakRef", N_WEAKREF),
("FinalizationRegistry", N_FINALIZATION_REGISTRY),
("encodeURIComponent", N_ENCODE_URI_COMPONENT),
("decodeURIComponent", N_DECODE_URI_COMPONENT),
("encodeURI", N_ENCODE_URI),
("decodeURI", N_DECODE_URI),
("escape", N_ESCAPE),
("unescape", N_UNESCAPE),
("structuredClone", N_STRUCTURED_CLONE),
("setTimeout", N_SET_TIMEOUT),
("clearTimeout", N_CLEAR_TIMEOUT),
("queueMicrotask", N_QUEUE_MICROTASK),
("btoa", N_BTOA),
("atob", N_ATOB),
("URIError", N_URI_ERROR),
("EvalError", N_EVAL_ERROR),
("eval", N_EVAL),
("$262_detachArrayBuffer", N_DETACH_ARRAY_BUFFER),
] {
let f = self.new_named_native(name, id);
self.current.declare(name, NanBox::handle(f.to_raw()));
}
let intl = self.realm.new_object();
for (name, id) in [
("NumberFormat", N_INTL_NUMBER_FORMAT),
("DateTimeFormat", N_INTL_DATETIME_FORMAT),
("Collator", N_INTL_COLLATOR),
("PluralRules", N_INTL_PLURAL_RULES),
("ListFormat", N_INTL_LIST_FORMAT),
("RelativeTimeFormat", N_INTL_REL_TIME),
("DisplayNames", N_INTL_DISPLAY_NAMES),
("Segmenter", N_INTL_SEGMENTER),
("DurationFormat", N_INTL_DURATION_FORMAT),
] {
let f = self.new_named_native(name, id);
let sl = self.new_named_native("supportedLocalesOf", N_INTL_SUPPORTED_LOCALES);
self.realm
.set_hidden_property(f, "supportedLocalesOf", NanBox::handle(sl.to_raw()));
self.realm
.set_property(intl, name, NanBox::handle(f.to_raw()));
}
self.realm.mark_hidden(intl, "ListFormat");
self.realm.mark_hidden(intl, "RelativeTimeFormat");
self.realm.mark_hidden(intl, "DurationFormat");
self.realm.mark_hidden(intl, "PluralRules");
{
let f = self.new_named_native("Locale", N_INTL_LOCALE);
self.realm
.set_property(intl, "Locale", NanBox::handle(f.to_raw()));
}
for (name, id) in [
("getCanonicalLocales", N_INTL_GET_CANONICAL_LOCALES),
("supportedValuesOf", N_INTL_SUPPORTED_VALUES_OF),
] {
let f = self.new_named_native(name, id);
self.realm
.set_property(intl, name, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(intl, name);
}
self.current.declare("Intl", NanBox::handle(intl.to_raw()));
for (i, (name, _)) in TYPED_ARRAY_KINDS.iter().enumerate() {
let f = self.new_named_native(name, N_TYPED_ARRAY_BASE + i as u16);
self.current.declare(name, NanBox::handle(f.to_raw()));
}
for (name, id) in [("ArrayBuffer", N_ARRAY_BUFFER), ("DataView", N_DATA_VIEW)] {
let f = self.new_named_native(name, id);
if id == N_ARRAY_BUFFER {
let isview = self.realm.new_native(N_ARRAY_BUFFER_IS_VIEW);
self.realm
.set_hidden_property(f, "isView", NanBox::handle(isview.to_raw()));
}
self.current.declare(name, NanBox::handle(f.to_raw()));
}
let iterator_ctor = self.new_named_native("Iterator", N_ITERATOR);
let from_fn = self.new_named_native("from", N_ITERATOR_FROM);
self.realm
.set_hidden_property(iterator_ctor, "from", NanBox::handle(from_fn.to_raw()));
let iter_proto = self.realm.new_object();
let self_iter = self.realm.new_native(N_ITERATOR_PROTO_SELF);
self.install_fn_name_length(self_iter, "[Symbol.iterator]", 0);
let iter_sym = self.well_known_symbol("iterator");
let iter_key = self.member_key(iter_sym);
self.realm
.set_hidden_property(iter_proto, &iter_key, NanBox::handle(self_iter.to_raw()));
let dispose_native = self.realm.new_native(N_ITERATOR_DISPOSE);
self.install_fn_name_length(dispose_native, "[Symbol.dispose]", 0);
let dispose_sym = self.well_known_symbol("dispose");
let dispose_key = self.member_key(dispose_sym);
self.realm.set_property(
iter_proto,
&dispose_key,
NanBox::handle(dispose_native.to_raw()),
);
self.realm.mark_hidden(iter_proto, &dispose_key);
for &name in ITERATOR_PROTO_METHODS {
let name_h = self.realm.new_string(name);
let f = self.realm.new_bound_native(N_ITERATOR_PROTO_FN, name_h);
self.install_fn_name_length(f, name, builtin_method_arity(name));
self.realm
.set_property(iter_proto, name, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(iter_proto, name);
}
self.realm.set_hidden_property(
iter_proto,
"constructor",
NanBox::handle(iterator_ctor.to_raw()),
);
self.realm.set_hidden_property(
iterator_ctor,
"prototype",
NanBox::handle(iter_proto.to_raw()),
);
self.realm.mark_hidden(iterator_ctor, "prototype");
let concat_fn = self.realm.new_native(N_ITERATOR_CONCAT);
self.install_fn_name_length(concat_fn, "concat", 0);
self.realm
.set_property(iterator_ctor, "concat", NanBox::handle(concat_fn.to_raw()));
self.realm.mark_hidden(iterator_ctor, "concat");
let zip_fn = self.realm.new_native(N_ITERATOR_ZIP);
self.install_fn_name_length(zip_fn, "zip", 1);
self.realm
.set_property(iterator_ctor, "zip", NanBox::handle(zip_fn.to_raw()));
self.realm.mark_hidden(iterator_ctor, "zip");
let zipk_fn = self.realm.new_native(N_ITERATOR_ZIP_KEYED);
self.install_fn_name_length(zipk_fn, "zipKeyed", 1);
self.realm
.set_property(iterator_ctor, "zipKeyed", NanBox::handle(zipk_fn.to_raw()));
self.realm.mark_hidden(iterator_ctor, "zipKeyed");
let helper_proto = self.realm.new_object_with_proto(Some(iter_proto));
let hn = self.realm.new_native(N_ITER_HELPER_NEXT);
self.install_fn_name_length(hn, "next", 0);
self.realm
.set_property(helper_proto, "next", NanBox::handle(hn.to_raw()));
self.realm.mark_hidden(helper_proto, "next");
let hr = self.realm.new_native(N_ITER_HELPER_RETURN);
self.install_fn_name_length(hr, "return", 0);
self.realm
.set_property(helper_proto, "return", NanBox::handle(hr.to_raw()));
self.realm.mark_hidden(helper_proto, "return");
let tag = self.new_str("Iterator Helper");
let tt_sym = self.well_known_symbol("toStringTag");
let tt_key = self.member_key(tt_sym);
self.realm.set_property(helper_proto, &tt_key, tag);
self.realm.mark_hidden(helper_proto, &tt_key);
self.realm.set_readonly_property(helper_proto, &tt_key);
let wrap_proto = self.realm.new_object_with_proto(Some(iter_proto));
let wn = self.realm.new_native(N_ITER_WRAP_NEXT);
self.install_fn_name_length(wn, "next", 0);
self.realm
.set_property(wrap_proto, "next", NanBox::handle(wn.to_raw()));
self.realm.mark_hidden(wrap_proto, "next");
let wr = self.realm.new_native(N_ITER_WRAP_RETURN);
self.install_fn_name_length(wr, "return", 0);
self.realm
.set_property(wrap_proto, "return", NanBox::handle(wr.to_raw()));
self.realm.mark_hidden(wrap_proto, "return");
let concat_proto = self.realm.new_object_with_proto(Some(iter_proto));
let cn = self.realm.new_native(N_ITER_CONCAT_NEXT);
self.install_fn_name_length(cn, "next", 0);
self.realm
.set_property(concat_proto, "next", NanBox::handle(cn.to_raw()));
self.realm.mark_hidden(concat_proto, "next");
let cr = self.realm.new_native(N_ITER_CONCAT_RETURN);
self.install_fn_name_length(cr, "return", 0);
self.realm
.set_property(concat_proto, "return", NanBox::handle(cr.to_raw()));
self.realm.mark_hidden(concat_proto, "return");
let ctag = self.new_str("Iterator Helper");
self.realm.set_property(concat_proto, &tt_key, ctag);
self.realm.mark_hidden(concat_proto, &tt_key);
self.realm.set_readonly_property(concat_proto, &tt_key);
let zip_proto = self.realm.new_object_with_proto(Some(iter_proto));
let zn = self.realm.new_native(N_ITER_ZIP_NEXT);
self.install_fn_name_length(zn, "next", 0);
self.realm
.set_property(zip_proto, "next", NanBox::handle(zn.to_raw()));
self.realm.mark_hidden(zip_proto, "next");
let zr = self.realm.new_native(N_ITER_ZIP_RETURN);
self.install_fn_name_length(zr, "return", 0);
self.realm
.set_property(zip_proto, "return", NanBox::handle(zr.to_raw()));
self.realm.mark_hidden(zip_proto, "return");
let ztag = self.new_str("Iterator Helper");
self.realm.set_property(zip_proto, &tt_key, ztag);
self.realm.mark_hidden(zip_proto, &tt_key);
self.realm.set_readonly_property(zip_proto, &tt_key);
self.realm.set_hidden_property(
iterator_ctor,
ITER_ZIP_PROTO_SLOT,
NanBox::handle(zip_proto.to_raw()),
);
self.realm.set_hidden_property(
iterator_ctor,
ITER_HELPER_PROTO_SLOT,
NanBox::handle(helper_proto.to_raw()),
);
self.realm.set_hidden_property(
iterator_ctor,
ITER_WRAP_PROTO_SLOT,
NanBox::handle(wrap_proto.to_raw()),
);
self.realm.set_hidden_property(
iterator_ctor,
ITER_CONCAT_PROTO_SLOT,
NanBox::handle(concat_proto.to_raw()),
);
self.current
.declare("Iterator", NanBox::handle(iterator_ctor.to_raw()));
install_namespace(
self,
"WebAssembly",
&[
("validate", N_WASM_VALIDATE),
("instantiate", N_WASM_INSTANTIATE),
("Module", N_WASM_MODULE),
("Instance", N_WASM_INSTANCE),
("compile", N_WASM_COMPILE),
("Global", N_WASM_GLOBAL),
("Memory", N_WASM_MEMORY),
("Table", N_WASM_TABLE),
("CompileError", N_WASM_COMPILE_ERROR),
("LinkError", N_WASM_LINK_ERROR),
("RuntimeError", N_WASM_RUNTIME_ERROR),
],
);
if let Some(module_ctor) = self
.current
.get("WebAssembly")
.and_then(|ns| ns.as_handle())
.map(Handle::from_raw)
.and_then(|ns| self.realm.get_property(ns, "Module"))
.and_then(|m| m.as_handle())
.map(Handle::from_raw)
{
for (name, id) in [
("exports", N_WASM_MODULE_EXPORTS),
("imports", N_WASM_MODULE_IMPORTS),
] {
let f = self.realm.new_native(id);
self.realm
.set_property(module_ctor, name, NanBox::handle(f.to_raw()));
}
}
let obj_proto = self.realm.new_object();
for (name, id, arity) in [
("toString", N_OBJ_PROTO_TOSTRING, 0u32),
("toLocaleString", N_OBJ_PROTO_TOSTRING, 0),
("valueOf", N_OBJ_PROTO_VALUEOF, 0),
("hasOwnProperty", N_OBJ_PROTO_HASOWN, 1),
("isPrototypeOf", N_OBJ_PROTO_ISPROTOTYPEOF, 1),
("propertyIsEnumerable", N_OBJ_PROTO_PROPISENUM, 1),
("__defineGetter__", N_OBJ_DEFINE_GETTER, 2),
("__defineSetter__", N_OBJ_DEFINE_SETTER, 2),
("__lookupGetter__", N_OBJ_LOOKUP_GETTER, 1),
("__lookupSetter__", N_OBJ_LOOKUP_SETTER, 1),
] {
let f = self.realm.new_native(id);
self.install_fn_name_length(f, name, arity);
self.realm
.set_property(obj_proto, name, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(obj_proto, name);
}
{
let getter = self.realm.new_native(N_OBJ_PROTO_GET);
self.install_fn_name_length(getter, "get __proto__", 0);
let setter = self.realm.new_native(N_OBJ_PROTO_SET);
self.install_fn_name_length(setter, "set __proto__", 1);
self.realm.define_accessor(
obj_proto,
"__proto__",
NanBox::handle(getter.to_raw()),
NanBox::handle(setter.to_raw()),
);
self.realm.mark_hidden(obj_proto, "__proto__");
}
if let Some(obj_ns) = self
.current
.get("Object")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
{
self.realm
.set_property(obj_ns, "prototype", NanBox::handle(obj_proto.to_raw()));
self.realm.mark_hidden(obj_ns, "prototype");
self.realm.set_readonly_property(obj_ns, "prototype");
self.realm
.set_non_configurable_property(obj_ns, "prototype");
self.realm.set_hidden_property(
obj_proto,
"constructor",
NanBox::handle(obj_ns.to_raw()),
);
let name = self.new_str("Object");
self.realm.set_hidden_property(obj_ns, "name", name);
self.realm.set_readonly_property(obj_ns, "name");
}
self.setup_first_class_prototype("Array", ARRAY_PROTO_METHODS);
if let Some(arr_proto) = self
.current
.get("Array")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|c| self.realm.get_property(c, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
{
self.realm.set_array_proto_intrinsic(arr_proto);
if let Some(values) = self.realm.get_property(arr_proto, "values") {
let iter_sym = self.well_known_symbol("iterator");
let iter_key = self.member_key(iter_sym);
self.realm.set_hidden_property(arr_proto, &iter_key, values);
}
}
if let Some(arr_proto) = self
.current
.get("Array")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|c| self.realm.get_property(c, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
{
let unscopables = self.realm.new_object_with_proto(None);
for name in [
"at",
"copyWithin",
"entries",
"fill",
"find",
"findIndex",
"findLast",
"findLastIndex",
"flat",
"flatMap",
"includes",
"keys",
"toReversed",
"toSorted",
"toSpliced",
"values",
] {
self.realm
.set_property(unscopables, name, NanBox::boolean(true));
}
let sym = self.well_known_symbol("unscopables");
let key = self.member_key(sym);
self.realm
.set_property(arr_proto, &key, NanBox::handle(unscopables.to_raw()));
self.realm.mark_hidden(arr_proto, &key);
self.realm.set_readonly_property(arr_proto, &key);
}
self.setup_first_class_prototype_id("String", STRING_PROTO_METHODS, N_STRING_PROTO_FN);
if let Some(str_proto) = self
.current
.get("String")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|s| self.realm.get_property(s, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
{
for (alias, original) in [("trimLeft", "trimStart"), ("trimRight", "trimEnd")] {
if let Some(f) = self.realm.get_property(str_proto, original) {
self.realm.set_property(str_proto, alias, f);
self.realm.mark_hidden(str_proto, alias);
}
}
}
self.setup_first_class_prototype_id("Number", NUMBER_PROTO_METHODS, N_NUMBER_PROTO_FN);
if let Some(num_ctor) = self
.current
.get("Number")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
{
let consts: &[(&str, f64)] = &[
("MAX_SAFE_INTEGER", 9_007_199_254_740_991.0),
("MIN_SAFE_INTEGER", -9_007_199_254_740_991.0),
("MAX_VALUE", f64::MAX),
("MIN_VALUE", f64::from_bits(1)),
("EPSILON", f64::EPSILON),
("POSITIVE_INFINITY", f64::INFINITY),
("NEGATIVE_INFINITY", f64::NEG_INFINITY),
("NaN", f64::NAN),
];
for &(name, value) in consts {
self.realm
.set_property(num_ctor, name, NanBox::number(value));
self.realm.mark_hidden(num_ctor, name);
self.realm.set_readonly_property(num_ctor, name);
self.realm.set_non_configurable_property(num_ctor, name);
}
}
self.setup_first_class_prototype_id("Boolean", BOOLEAN_PROTO_METHODS, N_BOOLEAN_PROTO_FN);
for (ctor, prim) in [
("Number", NanBox::number(0.0)),
("Boolean", NanBox::boolean(false)),
] {
if let Some(proto) = self
.current
.get(ctor)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|c| self.realm.get_property(c, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
{
self.realm.set_hidden_property(proto, PRIM_WRAP, prim);
let id = if ctor == "Number" {
N_NUMBER
} else {
N_BOOLEAN
};
self.realm.set_hidden_property(
proto,
PRIM_WRAP_TYPE,
NanBox::number(f64::from(id)),
);
}
}
self.setup_first_class_prototype_id("BigInt", BIGINT_PROTO_METHODS, N_BIGINT_PROTO_FN);
self.setup_first_class_prototype_id("Date", DATE_PROTO_METHODS, N_DATE_PROTO_FN);
if let Some(date_proto) = self
.current
.get("Date")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|d| self.realm.get_property(d, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
&& let Some(f) = self.realm.get_property(date_proto, "toUTCString")
{
self.realm.set_property(date_proto, "toGMTString", f);
self.realm.mark_hidden(date_proto, "toGMTString");
}
if let Some(date_proto) = self
.current
.get("Date")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|c| self.realm.get_property(c, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
{
let f = self.new_named_native("[Symbol.toPrimitive]", N_DATE_TO_PRIMITIVE);
self.install_fn_name_length(f, "[Symbol.toPrimitive]", 1);
let sym = self.well_known_symbol("toPrimitive");
let key = self.member_key(sym);
self.realm
.set_property(date_proto, &key, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(date_proto, &key);
self.realm.set_readonly_property(date_proto, &key);
let to_json = self.new_named_native("toJSON", N_DATE_TO_JSON);
self.install_fn_name_length(to_json, "toJSON", 1);
self.realm
.set_property(date_proto, "toJSON", NanBox::handle(to_json.to_raw()));
self.realm.mark_hidden(date_proto, "toJSON");
}
if let Some(bi_proto) = self
.current
.get("BigInt")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|f| self.realm.get_property(f, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
{
let tag_sym = self.well_known_symbol("toStringTag");
let tag_key = self.member_key(tag_sym);
let tag_val = self.new_str("BigInt");
self.realm.set_property(bi_proto, &tag_key, tag_val);
self.realm.mark_hidden(bi_proto, &tag_key);
self.realm.set_readonly_property(bi_proto, &tag_key);
}
self.setup_regexp_prototype();
self.setup_first_class_prototype("Function", FUNCTION_PROTO_METHODS);
if let Some(func_proto) = self
.current
.get("Function")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|f| self.realm.get_property(f, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
{
self.realm.set_function_proto_intrinsic(func_proto);
let thrower = NanBox::handle(self.realm.new_native(N_THROW_TYPE_ERROR).to_raw());
for key in ["caller", "arguments"] {
self.realm
.define_accessor(func_proto, key, thrower, thrower);
self.realm.mark_hidden(func_proto, key);
}
let has_instance = NanBox::handle(self.realm.new_native(N_FN_HAS_INSTANCE).to_raw());
let sym = self.well_known_symbol("hasInstance");
let key = self.member_key(sym);
self.realm
.set_hidden_property(func_proto, &key, has_instance);
self.realm.set_readonly_property(func_proto, &key);
self.realm.set_non_configurable_property(func_proto, &key);
}
self.setup_first_class_prototype_id("Set", SET_PROTO_METHODS, N_SET_PROTO_FN);
self.setup_first_class_prototype_id("Map", MAP_PROTO_METHODS, N_MAP_PROTO_FN);
self.setup_first_class_prototype_id("WeakMap", WEAKMAP_PROTO_METHODS, N_WEAKMAP_PROTO_FN);
self.setup_first_class_prototype_id("WeakSet", WEAKSET_PROTO_METHODS, N_WEAKSET_PROTO_FN);
self.setup_direct_prototype("WeakRef", &[("deref", N_WEAKREF_DEREF)]);
self.setup_direct_prototype(
"FinalizationRegistry",
&[
("register", N_FINREG_REGISTER),
("unregister", N_FINREG_UNREGISTER),
],
);
self.setup_first_class_prototype("Promise", PROMISE_PROTO_METHODS);
self.install_proto_to_string_tag("Set", "Set");
self.install_proto_to_string_tag("Map", "Map");
self.install_collection_accessors("Map", N_MAP_SIZE);
self.install_collection_accessors("Set", N_SET_SIZE);
self.install_ctor_species("Array");
self.install_proto_to_string_tag("WeakMap", "WeakMap");
self.install_proto_to_string_tag("WeakSet", "WeakSet");
self.install_proto_to_string_tag("Promise", "Promise");
self.install_proto_to_string_tag("WeakRef", "WeakRef");
self.install_proto_to_string_tag("FinalizationRegistry", "FinalizationRegistry");
if let Some(sym_ctor) = self
.current
.get("Symbol")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
{
let proto = self.realm.new_object_with_proto(Some(obj_proto));
for (name, nid) in [
("toString", N_SYMBOL_PROTO_TOSTRING),
("valueOf", N_SYMBOL_PROTO_VALUEOF),
] {
let f = self.new_named_native(name, nid);
self.realm
.set_property(proto, name, NanBox::handle(f.to_raw()));
self.realm.mark_hidden(proto, name);
}
let desc_get = self.new_named_native("get description", N_SYMBOL_PROTO_DESC_GET);
self.realm.define_accessor(
proto,
"description",
NanBox::handle(desc_get.to_raw()),
NanBox::undefined(),
);
self.realm.mark_hidden(proto, "description");
let to_prim = self.new_named_native("[Symbol.toPrimitive]", N_SYMBOL_PROTO_TOPRIMITIVE);
let to_prim_sym = self.well_known_symbol("toPrimitive");
let to_prim_key = self.member_key(to_prim_sym);
self.realm
.set_property(proto, &to_prim_key, NanBox::handle(to_prim.to_raw()));
self.realm.mark_hidden(proto, &to_prim_key);
self.realm.set_readonly_property(proto, &to_prim_key);
self.install_to_string_tag(proto, "Symbol");
self.realm
.set_hidden_property(proto, "constructor", NanBox::handle(sym_ctor.to_raw()));
self.realm
.set_property(sym_ctor, "prototype", NanBox::handle(proto.to_raw()));
self.realm.mark_hidden(sym_ctor, "prototype");
self.realm.set_readonly_property(sym_ctor, "prototype");
self.realm
.set_non_configurable_property(sym_ctor, "prototype");
self.realm.set_symbol_proto_intrinsic(proto);
}
for (ns_name, tag) in [("Reflect", "Reflect"), ("JSON", "JSON"), ("Math", "Math")] {
if let Some(ns) = self
.current
.get(ns_name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
{
self.install_to_string_tag(ns, tag);
self.realm.set_object_proto(ns, Some(obj_proto));
}
}
if let Some(error_proto) = self
.current
.get("Error")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.map(|ctor| {
let proto = self.realm.new_object_with_proto(Some(obj_proto));
self.realm
.set_hidden_property(proto, "constructor", NanBox::handle(ctor.to_raw()));
let nm = self.new_str("Error");
self.realm.set_property(proto, "name", nm);
self.realm.mark_hidden(proto, "name");
let msg = self.new_str("");
self.realm.set_property(proto, "message", msg);
self.realm.mark_hidden(proto, "message");
let ts = self.new_named_native("toString", N_ERROR_PROTO_TOSTRING);
self.install_fn_name_length(ts, "toString", 0);
self.realm
.set_property(proto, "toString", NanBox::handle(ts.to_raw()));
self.realm.mark_hidden(proto, "toString");
let stack_get = self.new_named_native("get stack", N_ERROR_PROTO_STACK_GET);
let stack_set = self.new_named_native("set stack", N_ERROR_PROTO_STACK_SET);
self.realm.define_accessor(
proto,
"stack",
NanBox::handle(stack_get.to_raw()),
NanBox::handle(stack_set.to_raw()),
);
self.realm.mark_hidden(proto, "stack");
self.realm
.set_property(ctor, "prototype", NanBox::handle(proto.to_raw()));
self.realm.mark_hidden(ctor, "prototype");
proto
})
{
let mut subclass_names: Vec<&str> = ERROR_NAMES[1..N_GLOBAL_ERROR_COUNT].to_vec();
subclass_names.push("URIError");
subclass_names.push("EvalError");
for name in subclass_names {
if let Some(ctor) = self
.current
.get(name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
{
let proto = self.realm.new_object_with_proto(Some(error_proto));
self.realm.set_hidden_property(
proto,
"constructor",
NanBox::handle(ctor.to_raw()),
);
let nm = self.new_str(name);
self.realm.set_property(proto, "name", nm);
self.realm.mark_hidden(proto, "name");
let msg = self.new_str("");
self.realm.set_property(proto, "message", msg);
self.realm.mark_hidden(proto, "message");
self.realm
.set_property(ctor, "prototype", NanBox::handle(proto.to_raw()));
self.realm.mark_hidden(ctor, "prototype");
if let Some(error_ctor) = self
.current
.get("Error")
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
{
self.realm.set_native_proto(ctor, error_ctor);
}
}
}
}
self.setup_typed_array_intrinsic(obj_proto);
self.install_uint8array_base64();
self.setup_static_methods(
"Promise",
&[
"resolve",
"reject",
"all",
"race",
"allSettled",
"any",
"allKeyed",
"allSettledKeyed",
"withResolvers",
"try",
],
);
self.setup_static_methods("Map", &["groupBy"]);
self.setup_static_methods(
"Number",
&[
"isInteger",
"isFinite",
"isNaN",
"isSafeInteger",
"parseFloat",
"parseInt",
],
);
self.setup_static_methods("String", &["fromCharCode", "fromCodePoint", "raw"]);
self.setup_static_methods("Symbol", &["for", "keyFor"]);
self.setup_static_methods("Date", &["now", "parse", "UTC"]);
self.setup_static_methods("BigInt", &["asIntN", "asUintN"]);
for (ctor, ctor_name) in [("Array", "Array"), ("Reflect", "Reflect")] {
if let Some(h) = self.current.get(ctor).and_then(NanBox::as_handle) {
let h = Handle::from_raw(h);
let nv = self.new_str(ctor_name);
self.realm.set_hidden_property(h, "name", nv);
self.realm.set_readonly_property(h, "name");
}
}
for ctor in ["Object", "Array"] {
if let Some(h) = self.current.get(ctor).and_then(NanBox::as_handle) {
let h = Handle::from_raw(h);
self.realm
.set_hidden_property(h, "length", NanBox::number(1.0));
self.realm.set_readonly_property(h, "length");
}
}
self.realm.set_default_object_proto(obj_proto);
self.install_intl_prototypes();
self.install_resource_management();
let global = self.realm.new_object();
let bindings = self.global_scope.local_bindings();
for (name, value, _is_const) in &bindings {
self.realm.set_property(global, name, *value);
self.realm.mark_hidden(global, name);
}
for (name, value) in [
("NaN", NanBox::number(f64::NAN)),
("Infinity", NanBox::number(f64::INFINITY)),
("undefined", NanBox::undefined()),
] {
self.realm.set_property(global, name, value);
self.realm.mark_hidden(global, name);
self.realm.set_readonly_property(global, name);
self.realm.set_non_configurable_property(global, name);
}
let gbox = NanBox::handle(global.to_raw());
self.realm.set_property(global, "globalThis", gbox);
self.realm.mark_hidden(global, "globalThis");
self.current.declare("globalThis", gbox);
self.global_this = gbox;
}
pub(crate) fn global_object(&self) -> Option<Handle> {
self.global_this.as_handle().map(Handle::from_raw)
}
#[must_use]
pub fn realm(&self) -> &Realm {
&self.realm
}
pub fn realm_mut(&mut self) -> &mut Realm {
&mut self.realm
}
pub fn declare_global(&mut self, name: &str, value: NanBox) {
self.current.declare(name, value);
if let Some(g) = self.global_this.as_handle().map(Handle::from_raw) {
self.realm.set_property(g, name, value);
}
}
pub(crate) fn declare_sloppy_global(&mut self, name: &str, value: NanBox) {
self.global_scope.declare(name, value);
if let Some(g) = self.global_this.as_handle().map(Handle::from_raw) {
self.realm.set_property(g, name, value);
}
}
fn publish_global_var(&mut self, name: &str, value: NanBox) {
if !self.current.ptr_eq(&self.global_scope) {
return;
}
if let Some(g) = self.global_this.as_handle().map(Handle::from_raw) {
self.realm.set_property(g, name, value);
}
}
#[must_use]
pub fn snapshot(&self, roots: &[NanBox]) -> Vec<u8> {
let handles: Vec<Handle> = roots
.iter()
.filter_map(|v| v.as_handle().map(Handle::from_raw))
.collect();
crate::snapshot::serialize(&crate::snapshot::capture(&self.realm, &handles))
}
pub fn restore_snapshot(
&mut self,
bytes: &[u8],
) -> Result<Vec<NanBox>, crate::snapshot::SnapError> {
let snap = crate::snapshot::deserialize(bytes)?;
let handles = crate::snapshot::restore(&mut self.realm, &snap);
Ok(handles
.into_iter()
.map(|h| NanBox::handle(h.to_raw()))
.collect())
}
pub fn run(&mut self, program: &'a Program) -> Result<NanBox, ExecError> {
self.strict = self.strict || has_use_strict(&program.body);
if matches!(self.this_val.unpack(), Unpacked::Undefined) {
self.this_val = self.global_this;
}
self.hoist_with(&program.body, true)?;
let mut last = NanBox::undefined();
for stmt in &program.body {
match self.exec(stmt)? {
Flow::Normal(v) => {
if !v.is_empty_completion() {
last = v;
}
}
Flow::Return(v) => {
self.run_event_loop()?;
return Ok(v);
}
Flow::Break(..) | Flow::Continue(..) => {}
}
}
self.run_event_loop()?;
Ok(last)
}
fn parse_eval_program(
&mut self,
source: &str,
allow_super_property: bool,
allow_super_call: bool,
allow_new_target: bool,
inherited_strict: bool,
) -> Result<&'a Program, ExecError> {
let key = alloc::format!(
"{}{}{}{}\0{source}",
u8::from(allow_super_property),
u8::from(allow_super_call),
u8::from(allow_new_target),
u8::from(inherited_strict),
);
if let Some(p) = self.eval_programs.get(&key) {
return Ok(p);
}
match crate::parser::Parser::parse_eval_program(
source,
allow_super_property,
allow_super_call,
allow_new_target,
inherited_strict,
) {
Ok(program) => {
let leaked: &'static Program =
alloc::boxed::Box::leak(alloc::boxed::Box::new(program));
self.eval_programs.insert(key, leaked);
Ok(leaked)
}
Err(e) => {
let m = self.new_str(&alloc::format!("{e}"));
Err(ExecError::Throw(self.make_error(N_SYNTAX_ERROR, Some(m))))
}
}
}
fn run_eval_body(&mut self, program: &'a Program) -> Result<NanBox, ExecError> {
self.hoist_with_kind(&program.body, true, true)?;
let mut last = NanBox::undefined();
for stmt in &program.body {
match self.exec(stmt)? {
Flow::Normal(v) => {
if !v.is_empty_completion() {
last = v;
}
}
Flow::Return(v) => return Ok(v),
Flow::Break(..) | Flow::Continue(..) => {}
}
}
Ok(last)
}
fn eval_string(&mut self, source: &str, direct: bool) -> Result<NanBox, ExecError> {
let allow_super_property =
direct && (self.current_home.is_some() || self.current_home_object.is_some());
let allow_new_target = direct && self.new_target_in_scope;
let inherited_strict = direct && self.strict;
let program = self.parse_eval_program(
source,
allow_super_property,
false,
allow_new_target,
inherited_strict,
)?;
let code_strict = has_use_strict(&program.body);
if direct
&& !self.strict
&& !code_strict
&& let Some(param_names) = self.eval_param_names.take()
{
let mut var_names: Vec<&str> = Vec::new();
collect_var_names(&program.body, &mut var_names);
let mut block_fns: Vec<&str> = Vec::new();
collect_block_function_names(&program.body, &mut block_fns);
if var_names
.iter()
.chain(block_fns.iter())
.any(|n| param_names.iter().any(|p| p == n))
{
let m =
self.new_str("Identifier declared by `var` in eval conflicts with a parameter");
return Err(ExecError::Throw(self.make_error(N_SYNTAX_ERROR, Some(m))));
}
self.eval_param_names = Some(param_names);
}
if self.eval_depth >= self.realm.limits.max_eval_depth {
let msg = self.new_str("Maximum call stack size exceeded");
return Err(ExecError::Throw(
self.make_error(N_ERROR_BASE + 2, Some(msg)),
));
}
let saved_strict = self.strict;
let saved_scope = self.current.clone();
let saved_var_scope = self.var_scope.clone();
let saved_annexb = core::mem::take(&mut self.annexb_block_fns);
let (saved_this, saved_new_target) = (self.this_val, self.new_target);
if !direct {
self.strict = code_strict;
self.current = if code_strict {
self.global_scope.child()
} else {
self.global_scope.clone()
};
self.this_val = self.global_this;
self.new_target = NanBox::undefined();
} else {
self.strict = saved_strict || code_strict;
if self.strict {
self.current = saved_scope.child();
}
}
self.eval_depth += 1;
let result = self.run_eval_body(program);
self.eval_depth -= 1;
self.current = saved_scope;
self.var_scope = saved_var_scope;
self.annexb_block_fns = saved_annexb;
self.strict = saved_strict;
self.this_val = saved_this;
self.new_target = saved_new_target;
result
}
fn build_function_constructor(&mut self, args: &[NanBox]) -> Result<NanBox, ExecError> {
let (params, body) = match args.split_last() {
Some((last, rest)) => {
let parts: Vec<String> = rest
.iter()
.map(|a| self.realm.to_display_string(*a))
.collect();
(parts.join(","), self.realm.to_display_string(*last))
}
None => (String::new(), String::new()),
};
let source = alloc::format!("(function anonymous({params}\n) {{\n{body}\n}})");
let program = self.parse_eval_program(&source, false, false, false, false)?;
let func = program.body.iter().find_map(|s| match s {
Stmt::Expr { expression, .. } => match &**expression {
Expr::Function(f) => Some(f),
_ => None,
},
_ => None,
});
let Some(func) = func else {
let m = self.new_str("Function constructor produced invalid source");
return Err(ExecError::Throw(self.make_error(N_SYNTAX_ERROR, Some(m))));
};
let saved_scope = core::mem::replace(&mut self.current, self.global_scope.clone());
let saved_strict = self.strict;
self.strict = has_use_strict(&func.body);
let f = self.make_function(
&func.params,
Body::Block(&func.body),
func.is_async,
func.is_generator,
);
self.strict = saved_strict;
self.current = saved_scope;
self.set_fn_name(f, "anonymous");
if let Some(h) = f.as_handle().map(Handle::from_raw) {
self.realm
.set_hidden_property(h, DYN_FN_MARKER, NanBox::boolean(true));
let len = func
.params
.iter()
.take_while(|p| !p.rest && p.default.is_none())
.count();
self.install_fn_name_length(h, "anonymous", len as u32);
}
Ok(f)
}
fn hoist_with(&mut self, stmts: &'a [Stmt], hoist_vars: bool) -> Result<(), ExecError> {
self.hoist_with_kind(stmts, hoist_vars, false)
}
fn hoist_with_kind(
&mut self,
stmts: &'a [Stmt],
hoist_vars: bool,
eval_code: bool,
) -> Result<(), ExecError> {
if hoist_vars {
self.var_scope = self.current.clone();
self.annexb_block_fns = Vec::new();
let mut var_names: Vec<&str> = Vec::new();
collect_var_names(stmts, &mut var_names);
let mut block_fn_names: Vec<&str> = Vec::new();
collect_block_function_names(stmts, &mut block_fn_names);
for name in &block_fn_names {
if eval_code || !self.current.has_local(name) {
self.annexb_block_fns.push(String::from(*name));
}
}
var_names.extend_from_slice(&block_fn_names);
let at_global = self.current.ptr_eq(&self.global_scope);
let global_obj = self.global_this.as_handle().map(Handle::from_raw);
for name in var_names {
let global_has =
at_global && global_obj.is_some_and(|g| self.realm.has_own(g, name));
if !self.current.has_local(name) && !global_has {
self.current.declare(name, NanBox::undefined());
}
if at_global
&& !global_has
&& let Some(g) = global_obj
{
self.realm.set_property(g, name, NanBox::undefined());
if !eval_code {
self.realm.set_non_configurable_property(g, name);
}
}
}
}
for stmt in stmts {
let stmt = unwrap_exported_function(stmt);
if let Stmt::Function(func) = stmt
&& let Some(id) = &func.id
{
let value = self.make_function(
&func.params,
Body::Block(&func.body),
func.is_async,
func.is_generator,
);
self.set_fn_name(value, &id.name);
if hoist_vars {
self.current.declare(&id.name, value);
self.publish_global_var(&id.name, value);
} else {
self.current.declare(&id.name, value);
}
}
}
Ok(())
}
fn hoist(&mut self, stmts: &'a [Stmt]) -> Result<(), ExecError> {
self.hoist_with(stmts, false)
}
fn make_function(
&mut self,
params: &'a [Param],
body: Body<'a>,
is_async: bool,
is_generator: bool,
) -> NanBox {
self.make_method(params, body, is_async, is_generator, None, false)
}
fn make_method(
&mut self,
params: &'a [Param],
body: Body<'a>,
is_async: bool,
is_generator: bool,
home_class: Option<u32>,
home_static: bool,
) -> NanBox {
let is_strict = self.strict
|| home_class.is_some()
|| matches!(body, Body::Block(stmts) if has_use_strict(stmts));
let func_id = self.functions.len() as u32;
let lexical_class = home_class.or(self.current_lexical_home);
self.functions.push(FnDef {
params,
body,
is_async,
is_generator,
is_arrow: false,
is_strict,
name: "",
home_class,
home_static,
lexical_class,
});
let handle = self.realm.new_function(func_id, self.current.clone());
NanBox::handle(handle.to_raw())
}
fn call(&mut self, callee: NanBox, args: &[NanBox]) -> Result<NanBox, ExecError> {
self.call_with_this(callee, NanBox::undefined(), args)
}
#[cfg(feature = "intl")]
fn intern_static(&mut self, s: &str) -> &'static str {
if let Some(&v) = self.intl_intern.get(s) {
return v;
}
let leaked: &'static str = alloc::boxed::Box::leak(String::from(s).into_boxed_str());
self.intl_intern.insert(String::from(s), leaked);
leaked
}
fn type_error(&mut self, message: &str) -> ExecError {
let m = self.new_str(message);
ExecError::Throw(self.make_error(N_TYPE_ERROR, Some(m)))
}
fn make_error(&mut self, id: u16, message: Option<NanBox>) -> NanBox {
let name = ERROR_NAMES[(id - N_ERROR_BASE) as usize];
let obj = self.realm.new_object();
if let Some(proto) = self
.current
.get(name)
.and_then(|v| v.as_handle())
.map(Handle::from_raw)
.and_then(|c| self.realm.get_property(c, "prototype"))
.and_then(|p| p.as_handle())
.map(Handle::from_raw)
{
self.realm.set_object_proto(obj, Some(proto));
}
let name_v = self.new_str(name);
self.realm.set_property(obj, "name", name_v);
let msg_str = match message {
Some(m) if !matches!(m.unpack(), Unpacked::Undefined) => {
self.realm.to_display_string(m)
}
_ => String::new(),
};
let msg = self.new_str(&msg_str);
self.realm.set_property(obj, "message", msg);
self.realm.mark_hidden(obj, "name");
self.realm.mark_hidden(obj, "message");
self.realm
.set_hidden_property(obj, ERROR_DATA, NanBox::boolean(true));
NanBox::handle(obj.to_raw())
}
fn eval_args(&mut self, arguments: &'a [Argument]) -> Result<Vec<NanBox>, ExecError> {
let mut args = Vec::with_capacity(arguments.len());
for a in arguments {
match a {
Argument::Item(e) => args.push(self.eval(e)?),
Argument::Spread(e) => {
let v = self.eval(e)?;
args.extend(self.iterate_values(v)?);
}
}
}
Ok(args)
}
fn new_str(&mut self, s: &str) -> NanBox {
NanBox::handle(self.realm.new_string(s).to_raw())
}
fn new_str_bytes(&mut self, bytes: alloc::vec::Vec<u8>) -> NanBox {
NanBox::handle(self.realm.new_string_wtf8(bytes).to_raw())
}
fn arg_string_bytes(&self, v: NanBox) -> alloc::vec::Vec<u8> {
if let Some(raw) = v.as_handle()
&& let Some(b) = self.realm.string_bytes(Handle::from_raw(raw))
{
return b;
}
self.realm.to_display_string(v).into_bytes()
}
fn arg_string_bytes_fallible(&mut self, v: NanBox) -> Result<alloc::vec::Vec<u8>, ExecError> {
if let Some(raw) = v.as_handle()
&& let Some(b) = self.realm.string_bytes(Handle::from_raw(raw))
{
return Ok(b);
}
let prim = self.coerce_primitive(v, "string")?;
if let Some(h) = prim.as_handle().map(Handle::from_raw) {
if self.realm.symbol_at(h).is_some() {
return Err(self.type_error("Cannot convert a Symbol value to a string"));
}
if let Some(b) = self.realm.string_bytes(h) {
return Ok(b);
}
}
Ok(self.realm.to_display_string(prim).into_bytes())
}
}
#[cfg(all(feature = "module", feature = "std"))]
fn unwrap_exported_function(stmt: &Stmt) -> &Stmt {
use crate::ast::ExportDecl;
match stmt {
Stmt::Export(ExportDecl::Decl { declaration, .. })
| Stmt::Export(ExportDecl::Default { declaration, .. }) => declaration,
_ => stmt,
}
}
#[cfg(not(all(feature = "module", feature = "std")))]
fn unwrap_exported_function(stmt: &Stmt) -> &Stmt {
stmt
}
fn collect_var_names<'a>(stmts: &'a [Stmt], out: &mut Vec<&'a str>) {
use crate::ast::VarDeclKind;
fn from_decl<'a>(decl: &'a crate::ast::VarDecl, out: &mut Vec<&'a str>) {
if matches!(decl.kind, VarDeclKind::Var) {
for d in &decl.declarations {
if let BindingTarget::Ident(id) = &d.target {
out.push(&id.name);
}
}
}
}
for stmt in stmts {
match stmt {
Stmt::Var(decl) => from_decl(decl, out),
Stmt::Export(crate::ast::ExportDecl::Decl { declaration, .. }) => {
if let Stmt::Var(decl) = &**declaration {
from_decl(decl, out);
}
}
Stmt::Block { body, .. } => collect_var_names(body, out),
Stmt::If {
consequent,
alternate,
..
} => {
collect_var_names(core::slice::from_ref(consequent), out);
if let Some(alt) = alternate {
collect_var_names(core::slice::from_ref(alt), out);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::Labeled { body, .. }
| Stmt::With { body, .. } => {
collect_var_names(core::slice::from_ref(body), out);
}
Stmt::For { init, body, .. } => {
if let Some(crate::ast::ForInit::Var(decl)) = init {
from_decl(decl, out);
}
collect_var_names(core::slice::from_ref(body), out);
}
Stmt::ForIn { left, body, .. } | Stmt::ForOf { left, body, .. } => {
if let crate::ast::ForLeft::Decl {
kind: VarDeclKind::Var,
target: BindingTarget::Ident(id),
..
} = left
{
out.push(&id.name);
}
collect_var_names(core::slice::from_ref(body), out);
}
Stmt::Switch { cases, .. } => {
for c in cases {
collect_var_names(&c.body, out);
}
}
Stmt::Try {
block,
handler,
finalizer,
..
} => {
collect_var_names(block, out);
if let Some(h) = handler {
collect_var_names(&h.body, out);
}
if let Some(f) = finalizer {
collect_var_names(f, out);
}
}
_ => {}
}
}
}
fn has_use_strict(stmts: &[Stmt]) -> bool {
for stmt in stmts {
match stmt {
Stmt::Expr { expression, .. } => match &**expression {
Expr::Str { value, .. } => {
if &**value == b"use strict" {
return true;
}
}
_ => return false,
},
_ => return false,
}
}
false
}
fn collect_lexical_names<'a>(stmts: &'a [Stmt], out: &mut Vec<&'a str>) {
use crate::ast::VarDeclKind;
for stmt in stmts {
match stmt {
Stmt::Var(decl) if matches!(decl.kind, VarDeclKind::Let | VarDeclKind::Const) => {
for d in &decl.declarations {
collect_binding_idents(&d.target, out);
}
}
Stmt::Class(c) => {
if let Some(id) = &c.id {
out.push(&id.name);
}
}
_ => {}
}
}
}
fn collect_binding_idents<'a>(target: &'a BindingTarget, out: &mut Vec<&'a str>) {
use crate::ast::ArrayPatternElement;
match target {
BindingTarget::Ident(id) => out.push(&id.name),
BindingTarget::Array(arr) => {
for el in &arr.elements {
match el {
ArrayPatternElement::Item { target, .. }
| ArrayPatternElement::Rest { target, .. } => {
collect_binding_idents(target, out)
}
ArrayPatternElement::Hole => {}
}
}
}
BindingTarget::Object(obj) => {
for p in &obj.properties {
collect_binding_idents(&p.value, out);
}
if let Some(rest) = &obj.rest {
collect_binding_idents(rest, out);
}
}
}
}
fn collect_block_function_names<'a>(stmts: &'a [Stmt], out: &mut Vec<&'a str>) {
use core::slice::from_ref;
fn walk<'a>(stmts: &'a [Stmt], out: &mut Vec<&'a str>, in_block: bool, blocked: &[&'a str]) {
let mut blocked_here: Vec<&str> = blocked.to_vec();
collect_lexical_names(stmts, &mut blocked_here);
for stmt in stmts {
match stmt {
Stmt::Function(f) if in_block => {
if let Some(id) = &f.id
&& !blocked.contains(&&*id.name)
{
out.push(&id.name);
}
}
Stmt::Block { body, .. } => walk(body, out, true, &blocked_here),
Stmt::If {
consequent,
alternate,
..
} => {
walk(from_ref(consequent), out, true, &blocked_here);
if let Some(a) = alternate {
walk(from_ref(a), out, true, &blocked_here);
}
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::Labeled { body, .. } => walk(from_ref(body), out, true, &blocked_here),
Stmt::For { init, body, .. } => {
let mut for_lex: Vec<&str> = blocked_here.clone();
if let Some(crate::ast::ForInit::Var(decl)) = init
&& matches!(
decl.kind,
crate::ast::VarDeclKind::Let | crate::ast::VarDeclKind::Const
)
{
for d in &decl.declarations {
collect_binding_idents(&d.target, &mut for_lex);
}
}
walk(from_ref(body), out, true, &for_lex);
}
Stmt::ForIn { left, body, .. } | Stmt::ForOf { left, body, .. } => {
let mut for_lex: Vec<&str> = blocked_here.clone();
if let crate::ast::ForLeft::Decl {
kind: crate::ast::VarDeclKind::Let | crate::ast::VarDeclKind::Const,
target,
..
} = left
{
collect_binding_idents(target, &mut for_lex);
}
walk(from_ref(body), out, true, &for_lex);
}
Stmt::Try {
block,
handler,
finalizer,
..
} => {
walk(block, out, true, &blocked_here);
if let Some(h) = handler {
let mut catch_blocked: Vec<&str> = blocked_here.clone();
if let Some(p @ (BindingTarget::Object(_) | BindingTarget::Array(_))) =
&h.param
{
collect_binding_idents(p, &mut catch_blocked);
}
walk(&h.body, out, true, &catch_blocked);
}
if let Some(f) = finalizer {
walk(f, out, true, &blocked_here);
}
}
Stmt::Switch { cases, .. } => {
let mut switch_lex: Vec<&str> = blocked_here.clone();
for case in cases {
collect_lexical_names(&case.body, &mut switch_lex);
}
for case in cases {
walk(&case.body, out, true, &switch_lex);
}
}
_ => {}
}
}
}
let mut top_lex: Vec<&str> = Vec::new();
collect_lexical_names(stmts, &mut top_lex);
walk(stmts, out, false, &top_lex);
}
fn compound_op(op: AssignOp) -> Result<BinaryOp, ExecError> {
Ok(match op {
AssignOp::AddAssign => BinaryOp::Add,
AssignOp::SubAssign => BinaryOp::Sub,
AssignOp::MulAssign => BinaryOp::Mul,
AssignOp::DivAssign => BinaryOp::Div,
AssignOp::ModAssign => BinaryOp::Mod,
AssignOp::ExpAssign => BinaryOp::Exp,
AssignOp::ShlAssign => BinaryOp::Shl,
AssignOp::ShrAssign => BinaryOp::Shr,
AssignOp::UshrAssign => BinaryOp::Ushr,
AssignOp::BitAndAssign => BinaryOp::BitAnd,
AssignOp::BitOrAssign => BinaryOp::BitOr,
AssignOp::BitXorAssign => BinaryOp::BitXor,
_ => return Err(ExecError::Unsupported("logical assignment")),
})
}
fn unit_to_byte(bytes: &[u8], unit: usize) -> usize {
let mut units = 0;
for (cp, off, len) in wtf8_code_point_iter(bytes) {
if units >= unit {
return off;
}
units += if cp >= 0x1_0000 { 2 } else { 1 };
let _ = len;
}
bytes.len()
}
fn byte_to_unit(bytes: &[u8], byte_off: usize) -> usize {
crate::wtf8::utf16_len(&bytes[..byte_off.min(bytes.len())])
}
fn wtf8_code_point_iter(bytes: &[u8]) -> impl Iterator<Item = (u32, usize, usize)> + '_ {
let mut off = 0usize;
core::iter::from_fn(move || {
if off >= bytes.len() {
return None;
}
let start = off;
let b0 = bytes[off];
let len = if b0 < 0x80 {
1
} else if b0 < 0xE0 {
2
} else if b0 < 0xF0 {
3
} else {
4
}
.min(bytes.len() - off);
let cp = crate::wtf8::code_points(&bytes[off..off + len])
.next()
.unwrap_or(0xFFFD);
off += len;
Some((cp, start, len))
})
}
fn index_of_units(hay: &[u8], needle: &[u8], from: usize) -> f64 {
let start_byte = unit_to_byte(hay, from);
if needle.is_empty() {
return byte_to_unit(hay, start_byte) as f64;
}
let mut i = start_byte;
while i + needle.len() <= hay.len() {
if &hay[i..i + needle.len()] == needle {
return byte_to_unit(hay, i) as f64;
}
i += 1;
}
-1.0
}
fn last_index_of_units(hay: &[u8], needle: &[u8], from: usize) -> f64 {
let limit_byte = if from == usize::MAX {
hay.len()
} else {
unit_to_byte(hay, from)
};
if needle.is_empty() {
return byte_to_unit(hay, limit_byte.min(hay.len())) as f64;
}
if needle.len() > hay.len() {
return -1.0;
}
let max_start = hay.len() - needle.len();
let upper = limit_byte.min(max_start);
for i in (0..=upper).rev() {
if &hay[i..i + needle.len()] == needle {
return byte_to_unit(hay, i) as f64;
}
}
-1.0
}
fn split_units(hay: &[u8], sep: &[u8]) -> Vec<Vec<u8>> {
let mut out = Vec::new();
let mut start = 0usize;
let mut i = 0usize;
while i + sep.len() <= hay.len() {
if &hay[i..i + sep.len()] == sep {
out.push(hay[start..i].to_vec());
i += sep.len();
start = i;
} else {
i += 1;
}
}
out.push(hay[start..].to_vec());
out
}
fn slice_bounds(start: f64, end_arg: NanBox, realm: &Realm, len: usize) -> (usize, usize) {
let clamp = |n: f64| -> usize {
if n < 0.0 {
(len as f64 + n).max(0.0) as usize
} else {
(n as usize).min(len)
}
};
let a = clamp(start);
let b = match end_arg.unpack() {
Unpacked::Undefined => len,
_ => clamp(realm.to_number(end_arg)),
};
(a, b.max(a))
}
fn pad_units(s: &[u8], target: usize, pad: &[u8], at_start: bool) -> Vec<u8> {
let len = crate::wtf8::utf16_len(s);
if len >= target || pad.is_empty() {
return s.to_vec();
}
let need = target - len;
let pad_unit_count = crate::wtf8::utf16_len(pad);
let pad_units: Vec<u16> = crate::wtf8::utf16_units(pad).collect();
let mut units: Vec<u16> = Vec::with_capacity(need);
let mut idx = 0usize;
while units.len() < need {
units.push(pad_units[idx % pad_unit_count]);
idx += 1;
}
let filler = crate::wtf8::from_utf16(&units);
let mut out = Vec::with_capacity(filler.len() + s.len());
if at_start {
out.extend_from_slice(&filler);
out.extend_from_slice(s);
} else {
out.extend_from_slice(s);
out.extend_from_slice(&filler);
}
out
}
fn format_exponential(n: f64, frac: Option<usize>) -> String {
debug_assert!(n.is_finite());
let neg = n.is_sign_negative() && n != 0.0;
let abs = n.abs();
let (mantissa, exp) = match frac {
Some(f) => {
let mantissa_full = alloc::format!("{:.*e}", f + 25, abs);
let mut exp: i32 = mantissa_full
.rfind('e')
.and_then(|i| mantissa_full[i + 1..].parse().ok())
.unwrap_or(0);
let m = round_exp_mantissa(&mantissa_full, f, &mut exp);
(m, exp)
}
None => {
let sci = alloc::format!("{abs:e}");
let exp: i32 = sci
.rfind('e')
.and_then(|i| sci[i + 1..].parse().ok())
.unwrap_or(0);
(String::from(sci.split('e').next().unwrap_or("0")), exp)
}
};
let sign = if exp < 0 { '-' } else { '+' };
if neg {
alloc::format!("-{mantissa}e{sign}{}", exp.abs())
} else {
alloc::format!("{mantissa}e{sign}{}", exp.abs())
}
}
fn round_exp_mantissa(mantissa_full: &str, f: usize, exp: &mut i32) -> String {
let core = mantissa_full.split('e').next().unwrap_or(mantissa_full);
let digits: Vec<u8> = core.bytes().filter(u8::is_ascii_digit).collect();
let keep = f + 1;
let mut kept: Vec<u8> = digits.iter().take(keep).copied().collect();
while kept.len() < keep {
kept.push(b'0');
}
let round_up = digits.get(keep).is_some_and(|&d| d >= b'5');
if round_up {
let mut i = kept.len();
loop {
if i == 0 {
kept.insert(0, b'1');
kept.pop();
*exp += 1;
break;
}
i -= 1;
if kept[i] == b'9' {
kept[i] = b'0';
} else {
kept[i] += 1;
break;
}
}
}
let int_part = kept[0] as char;
if f == 0 {
alloc::format!("{int_part}")
} else {
let frac_part: String = kept[1..=f].iter().map(|&b| b as char).collect();
alloc::format!("{int_part}.{frac_part}")
}
}
fn format_precision(n: f64, p: usize) -> String {
if n == 0.0 {
return alloc::format!("{:.*}", p - 1, 0.0);
}
if !n.is_finite() {
return if n.is_nan() {
String::from("NaN")
} else if n > 0.0 {
String::from("Infinity")
} else {
String::from("-Infinity")
};
}
let sci = alloc::format!("{:e}", n.abs());
let e: i32 = sci
.rfind('e')
.and_then(|i| sci[i + 1..].parse().ok())
.unwrap_or(0);
if e < -6 || e >= p as i32 {
let s = alloc::format!("{:.*e}", p - 1, n);
return match s.find('e') {
Some(epos) if s.as_bytes().get(epos + 1) != Some(&b'-') => {
alloc::format!("{}e+{}", &s[..epos], &s[epos + 1..])
}
_ => s,
};
}
let decimals = (p as i32 - 1 - e).max(0) as usize;
alloc::format!("{:.*}", decimals, n)
}
fn json_quote(s: &str) -> String {
json_quote_wtf8(s.as_bytes())
}
fn json_quote_wtf8(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len() + 2);
out.push('"');
for cp in crate::wtf8::code_points(bytes) {
match cp {
0x22 => out.push_str("\\\""),
0x5C => out.push_str("\\\\"),
0x0A => out.push_str("\\n"),
0x0D => out.push_str("\\r"),
0x09 => out.push_str("\\t"),
cp if cp < 0x20 || crate::wtf8::is_surrogate(cp) => {
out.push_str(&alloc::format!("\\u{cp:04x}"));
}
cp => {
if let Some(c) = char::from_u32(cp) {
out.push(c);
}
}
}
}
out.push('"');
out
}
fn json_hex4(c: &[char], at: usize) -> Option<u16> {
let hex: String = c.get(at..at + 4)?.iter().collect();
u16::from_str_radix(&hex, 16).ok()
}
fn dataview_method(method: &str) -> Option<(bool, usize, bool, bool, bool)> {
let (is_set, t) = if let Some(t) = method.strip_prefix("get") {
(false, t)
} else if let Some(t) = method.strip_prefix("set") {
(true, t)
} else {
return None;
};
let (size, signed, is_float, is_bigint) = match t {
"Int8" => (1, true, false, false),
"Uint8" => (1, false, false, false),
"Int16" => (2, true, false, false),
"Uint16" => (2, false, false, false),
"Int32" => (4, true, false, false),
"Uint32" => (4, false, false, false),
"Float16" => (2, false, true, false),
"Float32" => (4, false, true, false),
"Float64" => (8, false, true, false),
"BigInt64" => (8, true, false, true),
"BigUint64" => (8, false, false, true),
_ => return None,
};
Some((is_set, size, signed, is_float, is_bigint))
}
fn wasm_extern_kind(kind: u8) -> &'static str {
match kind {
0 => "function",
1 => "table",
2 => "memory",
_ => "global",
}
}
pub(crate) fn coerce_typed(kind: u16, n: f64) -> f64 {
match kind {
7 => f64::from(n as f32), 8 => n, 2 => {
if n.is_nan() || n <= 0.0 {
0.0
} else if n >= 255.0 {
255.0
} else {
let fl = n as i64;
let frac = n - fl as f64;
let r = if frac < 0.5 {
fl
} else if frac > 0.5 || fl % 2 != 0 {
fl + 1
} else {
fl
};
r as f64
}
}
_ => {
if !n.is_finite() {
return 0.0;
}
let i = n as i64;
let (bits, signed) = match kind {
0 => (8u32, true), 1 => (8, false), 3 => (16, true), 4 => (16, false), 5 => (32, true), _ => (32, false), };
let modulus = 1i64 << bits;
let mut u = i.rem_euclid(modulus);
if signed && u >= modulus / 2 {
u -= modulus;
}
u as f64
}
}
}
fn rel_time_unit_display(unit: &str, style: &str, plural: bool) -> &'static str {
let (ls, lp, ss, sp): (&str, &str, &str, &str) = match unit {
"second" => ("second", "seconds", "sec.", "sec."),
"minute" => ("minute", "minutes", "min.", "min."),
"hour" => ("hour", "hours", "hr.", "hr."),
"day" => ("day", "days", "day", "days"),
"week" => ("week", "weeks", "wk.", "wk."),
"month" => ("month", "months", "mo.", "mo."),
"quarter" => ("quarter", "quarters", "qtr.", "qtrs."),
"year" => ("year", "years", "yr.", "yr."),
_ => ("", "", "", ""),
};
match (style, plural) {
("long", false) => ls,
("long", true) => lp,
(_, false) => ss,
(_, true) => sp,
}
}
fn rel_time_auto_phrase(unit: &str, v: i64) -> Option<&'static str> {
Some(match (unit, v) {
("year", -1) => "last year",
("year", 0) => "this year",
("year", 1) => "next year",
("quarter", -1) => "last quarter",
("quarter", 0) => "this quarter",
("quarter", 1) => "next quarter",
("month", -1) => "last month",
("month", 0) => "this month",
("month", 1) => "next month",
("week", -1) => "last week",
("week", 0) => "this week",
("week", 1) => "next week",
("day", -1) => "yesterday",
("day", 0) => "today",
("day", 1) => "tomorrow",
("hour", 0) => "this hour",
("minute", 0) => "this minute",
("second", 0) => "now",
_ => return None,
})
}
fn rel_time_number_parts(n: f64) -> alloc::vec::Vec<(&'static str, alloc::string::String, bool)> {
let s = alloc::format!("{n}");
let (int_str, frac_str) = match s.split_once('.') {
Some((i, f)) => (i, f),
None => (s.as_str(), ""),
};
let mut parts: alloc::vec::Vec<(&'static str, alloc::string::String, bool)> =
alloc::vec::Vec::new();
let digits: alloc::vec::Vec<char> = int_str.chars().collect();
let len = digits.len();
let first = len % 3;
let first = if first == 0 && len > 0 { 3 } else { first };
let emit =
|slice: &[char],
parts: &mut alloc::vec::Vec<(&'static str, alloc::string::String, bool)>| {
let g: alloc::string::String = slice.iter().collect();
parts.push(("integer", g, true));
};
if len > 0 {
emit(&digits[..first], &mut parts);
let mut idx = first;
while idx < len {
parts.push(("group", alloc::string::String::from(","), true));
emit(&digits[idx..idx + 3], &mut parts);
idx += 3;
}
}
if !frac_str.is_empty() {
parts.push(("decimal", alloc::string::String::from("."), true));
parts.push(("fraction", alloc::string::String::from(frac_str), true));
}
parts
}
fn rel_time_parts(
value: f64,
unit: &str,
numeric: &str,
style: &str,
) -> alloc::vec::Vec<(&'static str, alloc::string::String, bool)> {
if numeric == "auto"
&& style == "long"
&& value == (value as i64) as f64
&& let Some(phrase) = rel_time_auto_phrase(unit, value as i64)
{
return alloc::vec![("literal", alloc::string::String::from(phrase), false)];
}
let n = value.abs();
let plural = n != 1.0;
let unit_disp = rel_time_unit_display(unit, style, plural);
let is_past = value.is_sign_negative();
let mut parts: alloc::vec::Vec<(&'static str, alloc::string::String, bool)> =
alloc::vec::Vec::new();
if is_past {
parts.extend(rel_time_number_parts(n));
parts.push(("literal", alloc::format!(" {unit_disp} ago"), false));
} else {
parts.push(("literal", alloc::string::String::from("in "), false));
parts.extend(rel_time_number_parts(n));
parts.push(("literal", alloc::format!(" {unit_disp}"), false));
}
parts
}
#[cfg(feature = "intl")]
fn segment_text(
input: &str,
granularity: &str,
) -> Vec<(usize, alloc::string::String, Option<bool>)> {
use intl::unicode::segment;
let mut out: Vec<(usize, alloc::string::String, Option<bool>)> = Vec::new();
let mut index = 0usize;
let push = |seg: &str, is_word_like: Option<bool>, out: &mut Vec<_>, index: &mut usize| {
out.push((*index, alloc::string::String::from(seg), is_word_like));
*index += seg.chars().count();
};
match granularity {
"word" => {
for w in segment::words(input) {
let wl = Some(w.chars().any(char::is_alphanumeric));
push(w, wl, &mut out, &mut index);
}
}
"sentence" => {
for s in segment::sentences(input) {
push(s, None, &mut out, &mut index);
}
}
_ => {
for g in segment::graphemes(input) {
push(g, None, &mut out, &mut index);
}
}
}
out
}
#[cfg(not(feature = "intl"))]
fn segment_text(
input: &str,
granularity: &str,
) -> Vec<(usize, alloc::string::String, Option<bool>)> {
let chars: Vec<char> = input.chars().collect();
let mut out: Vec<(usize, alloc::string::String, Option<bool>)> = Vec::new();
let class = |c: char| -> u8 {
if c.is_alphanumeric() {
0
} else if c.is_whitespace() {
1
} else {
2
}
};
match granularity {
"word" => {
let mut i = 0;
while i < chars.len() {
let cls = class(chars[i]);
let start = i;
while i < chars.len() && class(chars[i]) == cls {
i += 1;
}
out.push((start, chars[start..i].iter().collect(), Some(cls == 0)));
}
}
"sentence" => {
let mut start = 0;
let mut i = 0;
while i < chars.len() {
let c = chars[i];
i += 1;
if matches!(c, '.' | '!' | '?') {
while i < chars.len() && chars[i].is_whitespace() {
i += 1;
}
out.push((start, chars[start..i].iter().collect(), None));
start = i;
}
}
if start < chars.len() {
out.push((start, chars[start..].iter().collect(), None));
}
}
_ => {
for (i, c) in chars.iter().enumerate() {
out.push((i, alloc::string::String::from(*c), None));
}
}
}
out
}
fn display_name(ty: &str, code: &str) -> alloc::string::String {
let owned;
let name: &str = match ty {
"language" => {
let primary = code.split(['-', '_']).next().unwrap_or(code);
owned = primary.to_ascii_lowercase();
match owned.as_str() {
"en" => "English",
"fr" => "French",
"de" => "German",
"es" => "Spanish",
"it" => "Italian",
"pt" => "Portuguese",
"nl" => "Dutch",
"ru" => "Russian",
"ja" => "Japanese",
"zh" => "Chinese",
"ko" => "Korean",
"ar" => "Arabic",
"hi" => "Hindi",
"tr" => "Turkish",
"pl" => "Polish",
"sv" => "Swedish",
"el" => "Greek",
"he" => "Hebrew",
"th" => "Thai",
"vi" => "Vietnamese",
_ => code,
}
}
"region" => {
owned = code.to_ascii_uppercase();
match owned.as_str() {
"US" => "United States",
"GB" => "United Kingdom",
"FR" => "France",
"DE" => "Germany",
"ES" => "Spain",
"IT" => "Italy",
"PT" => "Portugal",
"NL" => "Netherlands",
"RU" => "Russia",
"JP" => "Japan",
"CN" => "China",
"KR" => "South Korea",
"IN" => "India",
"BR" => "Brazil",
"CA" => "Canada",
"AU" => "Australia",
"MX" => "Mexico",
"CH" => "Switzerland",
"SE" => "Sweden",
"GR" => "Greece",
_ => code,
}
}
"currency" => {
owned = code.to_ascii_uppercase();
match owned.as_str() {
"USD" => "US Dollar",
"EUR" => "Euro",
"GBP" => "British Pound",
"JPY" => "Japanese Yen",
"CNY" => "Chinese Yuan",
"CHF" => "Swiss Franc",
"CAD" => "Canadian Dollar",
"AUD" => "Australian Dollar",
"INR" => "Indian Rupee",
"BRL" => "Brazilian Real",
"RUB" => "Russian Ruble",
"KRW" => "South Korean Won",
"MXN" => "Mexican Peso",
_ => code,
}
}
_ => code,
};
alloc::string::String::from(name)
}
fn unit_symbol(unit: &str) -> &str {
match unit {
"kilometer" => "km",
"meter" => "m",
"centimeter" => "cm",
"millimeter" => "mm",
"mile" => "mi",
"foot" => "ft",
"inch" => "in",
"yard" => "yd",
"kilogram" => "kg",
"gram" => "g",
"milligram" => "mg",
"pound" => "lb",
"ounce" => "oz",
"liter" => "L",
"milliliter" => "mL",
"gallon" => "gal",
"second" => "s",
"millisecond" => "ms",
"minute" => "min",
"hour" => "h",
"day" => "d",
"week" => "wk",
"month" => "mth",
"year" => "yr",
"celsius" => "°C",
"fahrenheit" => "°F",
"byte" => "byte",
"kilobyte" => "kB",
"megabyte" => "MB",
"gigabyte" => "GB",
"terabyte" => "TB",
"bit" => "bit",
"percent" => "%",
"degree" => "deg",
"liter-per-100-kilometer" => "L/100km",
other => other,
}
}
pub(crate) fn encode_typed_element(kind: u8, v: f64) -> ([u8; 8], usize) {
let mut out = [0u8; 8];
let n = match kind {
0 => {
out[0] = (v as i64 as i8) as u8; 1
}
1 => {
out[0] = v as i64 as u8; 1
}
2 => {
out[0] = v.clamp(0.0, 255.0) as u8; 1
}
3 => {
out[..2].copy_from_slice(&(v as i64 as i16).to_le_bytes()); 2
}
4 => {
out[..2].copy_from_slice(&(v as i64 as u16).to_le_bytes()); 2
}
5 => {
out[..4].copy_from_slice(&(v as i64 as i32).to_le_bytes()); 4
}
6 => {
out[..4].copy_from_slice(&(v as i64 as u32).to_le_bytes()); 4
}
7 => {
out[..4].copy_from_slice(&(v as f32).to_le_bytes()); 4
}
8 => {
out.copy_from_slice(&v.to_le_bytes()); 8
}
_ => 0,
};
(out, n)
}
pub(crate) fn decode_typed_element(kind: u8, bytes: &[u8]) -> f64 {
let b = |i: usize| bytes.get(i).copied().unwrap_or(0);
match kind {
0 => f64::from(b(0) as i8), 1 | 2 => f64::from(b(0)), 3 => f64::from(i16::from_le_bytes([b(0), b(1)])), 4 => f64::from(u16::from_le_bytes([b(0), b(1)])), 5 => f64::from(i32::from_le_bytes([b(0), b(1), b(2), b(3)])), 6 => f64::from(u32::from_le_bytes([b(0), b(1), b(2), b(3)])), 7 => f64::from(f32::from_le_bytes([b(0), b(1), b(2), b(3)])), 8 => f64::from_le_bytes([b(0), b(1), b(2), b(3), b(4), b(5), b(6), b(7)]), _ => 0.0,
}
}
pub(crate) fn decode_bigint_element(kind: u8, bytes: &[u8]) -> crate::bignum::BigInt {
use crate::bignum::BigInt;
let b = |i: usize| bytes.get(i).copied().unwrap_or(0);
let raw = u64::from_le_bytes([b(0), b(1), b(2), b(3), b(4), b(5), b(6), b(7)]);
if kind == 9 {
BigInt::from_i128(i128::from(raw as i64)) } else {
BigInt::from_i128(i128::from(raw)) }
}
pub(crate) fn encode_bigint_element(value: &crate::bignum::BigInt) -> [u8; 8] {
value.to_u64_wrapping().to_le_bytes()
}
fn currency_symbol(code: &str) -> String {
let sym = match code {
"USD" | "AUD" | "CAD" | "NZD" | "HKD" | "SGD" | "MXN" => "$",
"EUR" => "€",
"GBP" => "£",
"JPY" | "CNY" => "¥",
"INR" => "₹",
"KRW" => "₩",
"RUB" => "₽",
"BRL" => "R$",
"CHF" => "CHF\u{00a0}",
"" => "",
other => return alloc::format!("{other}\u{00a0}"),
};
String::from(sym)
}
fn group_thousands(n: f64) -> String {
if n.is_nan() {
return String::from("NaN");
}
if n.is_infinite() {
return String::from(if n > 0.0 { "∞" } else { "-∞" });
}
let neg = n.is_sign_negative() && n != 0.0;
let base = alloc::format!("{}", n.abs());
let grouped = group_thousands_str(&base);
if neg {
alloc::format!("-{grouped}")
} else {
grouped
}
}
fn group_thousands_str(s: &str) -> String {
let (neg, rest) = match s.strip_prefix('-') {
Some(r) => (true, r),
None => (false, s),
};
let (int_part, frac_part) = match rest.split_once('.') {
Some((i, f)) => (i, Some(f)),
None => (rest, None),
};
let bytes = int_part.as_bytes();
let len = bytes.len();
let mut out = String::new();
if neg {
out.push('-');
}
for (i, b) in bytes.iter().enumerate() {
if i > 0 && (len - i) % 3 == 0 {
out.push(',');
}
out.push(*b as char);
}
if let Some(f) = frac_part {
out.push('.');
out.push_str(f);
}
out
}
fn case_map_wtf8(bytes: &[u8], upper: bool) -> alloc::vec::Vec<u8> {
if let Some(s) = crate::wtf8::as_str(bytes) {
let mapped = if upper {
s.to_uppercase()
} else {
s.to_lowercase()
};
return mapped.into_bytes();
}
let mut out = alloc::vec::Vec::with_capacity(bytes.len());
for cp in crate::wtf8::code_points(bytes) {
match char::from_u32(cp) {
Some(c) => {
if upper {
for u in c.to_uppercase() {
crate::wtf8::encode_code_point(u32::from(u), &mut out);
}
} else {
for u in c.to_lowercase() {
crate::wtf8::encode_code_point(u32::from(u), &mut out);
}
}
}
None => crate::wtf8::encode_code_point(cp, &mut out),
}
}
out
}
#[cfg(feature = "intl")]
fn normalize_wtf8(bytes: &[u8], form: &str) -> alloc::vec::Vec<u8> {
use intl::unicode::normalize;
let norm = |chars: core::str::Chars<'_>| -> String {
match form {
"NFC" => normalize::nfc(chars).collect(),
"NFD" => normalize::nfd(chars).collect(),
"NFKC" => normalize::nfkc(chars).collect(),
_ => normalize::nfkd(chars).collect(),
}
};
if let Some(s) = crate::wtf8::as_str(bytes) {
return norm(s.chars()).into_bytes();
}
let mut out: alloc::vec::Vec<u8> = alloc::vec::Vec::with_capacity(bytes.len());
let mut run = String::new();
for cp in crate::wtf8::code_points(bytes) {
match char::from_u32(cp) {
Some(c) => run.push(c),
None => {
if !run.is_empty() {
out.extend_from_slice(norm(run.chars()).as_bytes());
run.clear();
}
crate::wtf8::encode_code_point(cp, &mut out);
}
}
}
if !run.is_empty() {
out.extend_from_slice(norm(run.chars()).as_bytes());
}
out
}
fn u16_slice(units: &[u16], st: usize, en: usize) -> alloc::vec::Vec<u8> {
let st = st.min(units.len());
let en = en.min(units.len()).max(st);
crate::wtf8::from_utf16(&units[st..en])
}
fn u16_slice_from(units: &[u16], st: usize) -> alloc::vec::Vec<u8> {
crate::wtf8::from_utf16(&units[st.min(units.len())..])
}
fn advance_index_u16(units: &[u16], i: usize, unicode: bool) -> usize {
if unicode
&& i + 1 < units.len()
&& (0xD800..=0xDBFF).contains(&units[i])
&& (0xDC00..=0xDFFF).contains(&units[i + 1])
{
i + 2
} else {
i + 1
}
}
fn f64_to_f16_bits(value: f64) -> u16 {
let bits = value.to_bits();
let sign = ((bits >> 48) & 0x8000) as u16;
if value.is_nan() {
return sign | 0x7E00; }
let abs = f64::from_bits(bits & 0x7FFF_FFFF_FFFF_FFFF);
if abs.is_infinite() {
return sign | 0x7C00;
}
if abs == 0.0 {
return sign;
}
let exp = ((bits >> 52) & 0x7FF) as i64 - 1023;
let signif = 0x0010_0000_0000_0000u64 | (bits & 0x000F_FFFF_FFFF_FFFF);
if exp + 15 >= 0x1F {
return sign | 0x7C00; }
let drop: i64 = if exp + 15 >= 1 {
42
} else {
42 + (1 - (exp + 15))
};
if drop >= 64 {
return sign; }
let drop = drop as u32;
let q = signif >> drop;
let rem = signif & ((1u64 << drop) - 1);
let half = 1u64 << (drop - 1);
let mut out = q;
if rem > half || (rem == half && (q & 1) == 1) {
out += 1;
}
if exp + 15 >= 1 {
let exp_field = (exp + 15) as u64;
let combined = (exp_field << 10) + (out - 0x400);
sign | combined as u16
} else {
sign | out as u16
}
}
fn f16_to_f64(h: u16) -> f64 {
fn pow2(n: i32) -> f64 {
f64::from_bits(((1023 + n) as u64) << 52)
}
let sign = if (h & 0x8000) != 0 { -1.0 } else { 1.0 };
let exp = (h >> 10) & 0x1F;
let mant = (h & 0x03FF) as f64;
match exp {
0 => sign * mant * pow2(-24), 0x1F => {
if mant == 0.0 {
sign * f64::INFINITY
} else {
f64::NAN
}
}
_ => sign * (1.0 + mant / 1024.0) * pow2(exp as i32 - 15),
}
}
fn format_date_year(y: i64) -> String {
if y < 0 {
alloc::format!("-{:04}", -y)
} else {
alloc::format!("{y:04}")
}
}
fn trunc_toward_zero(n: f64) -> f64 {
if !n.is_finite() || n.abs() >= 9_223_372_036_854_775_808.0 {
n
} else {
n as i64 as f64
}
}
fn time_clip(t: f64) -> f64 {
if !t.is_finite() || t.abs() > 8.64e15 {
return f64::NAN;
}
let truncated = trunc_toward_zero(t);
if truncated == 0.0 { 0.0 } else { truncated }
}
fn int_to_radix(n: f64, radix: u32) -> String {
const DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
let neg = n < 0.0;
let abs = if neg { -n } else { n };
let mut v = abs as u64;
let mut ibuf = Vec::new();
if v == 0 {
ibuf.push(b'0');
}
while v > 0 {
ibuf.push(DIGITS[(v % radix as u64) as usize]);
v /= radix as u64;
}
ibuf.reverse();
let mut out = String::new();
if neg {
out.push('-');
}
out.push_str(&String::from_utf8(ibuf).unwrap_or_default());
let mut frac = abs - (abs as u64) as f64;
if frac > 0.0 {
out.push('.');
for _ in 0..20 {
if frac <= 0.0 {
break;
}
frac *= radix as f64;
let digit = (frac as usize).min(radix as usize - 1);
out.push(DIGITS[digit] as char);
frac -= digit as f64;
}
}
out
}
const B64_ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
fn base64_encode(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
for chunk in bytes.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = *chunk.get(1).unwrap_or(&0) as u32;
let b2 = *chunk.get(2).unwrap_or(&0) as u32;
let n = (b0 << 16) | (b1 << 8) | b2;
out.push(B64_ALPHABET[(n >> 18 & 0x3f) as usize] as char);
out.push(B64_ALPHABET[(n >> 12 & 0x3f) as usize] as char);
out.push(if chunk.len() > 1 {
B64_ALPHABET[(n >> 6 & 0x3f) as usize] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
B64_ALPHABET[(n & 0x3f) as usize] as char
} else {
'='
});
}
out
}
fn base64_decode(s: &str) -> Option<Vec<u8>> {
let val = |c: u8| -> Option<u32> {
match c {
b'A'..=b'Z' => Some((c - b'A') as u32),
b'a'..=b'z' => Some((c - b'a' + 26) as u32),
b'0'..=b'9' => Some((c - b'0' + 52) as u32),
b'+' => Some(62),
b'/' => Some(63),
_ => None,
}
};
let cleaned: Vec<u8> = s
.bytes()
.filter(|b| !b.is_ascii_whitespace() && *b != b'=')
.collect();
let mut out = Vec::with_capacity(cleaned.len() / 4 * 3);
for chunk in cleaned.chunks(4) {
if chunk.len() < 2 {
return None;
}
let mut n = 0u32;
for &c in chunk {
n = (n << 6) | val(c)?;
}
n <<= 6 * (4 - chunk.len());
out.push((n >> 16 & 0xff) as u8);
if chunk.len() > 2 {
out.push((n >> 8 & 0xff) as u8);
}
if chunk.len() > 3 {
out.push((n & 0xff) as u8);
}
}
Some(out)
}
fn uri_encode(s: &str, extra: &str) -> String {
let mut out = String::new();
let mut buf = [0u8; 4];
for ch in s.chars() {
let keep = ch.is_ascii_alphanumeric() || "-_.!~*'()".contains(ch) || extra.contains(ch);
if keep {
out.push(ch);
} else {
for b in ch.encode_utf8(&mut buf).bytes() {
out.push('%');
out.push(
char::from_digit((b >> 4) as u32, 16)
.unwrap()
.to_ascii_uppercase(),
);
out.push(
char::from_digit((b & 0xf) as u32, 16)
.unwrap()
.to_ascii_uppercase(),
);
}
}
}
out
}
fn uri_decode(s: &str) -> Option<String> {
let bytes = s.as_bytes();
let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' {
let hi = (*bytes.get(i + 1)? as char).to_digit(16)?;
let lo = (*bytes.get(i + 2)? as char).to_digit(16)?;
out.push((hi * 16 + lo) as u8);
i += 3;
} else {
out.push(bytes[i]);
i += 1;
}
}
String::from_utf8(out).ok()
}
fn legacy_escape(bytes: &[u8]) -> Vec<u8> {
fn hex(n: u32) -> u8 {
char::from_digit(n, 16).unwrap().to_ascii_uppercase() as u8
}
let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
for u in crate::wtf8::utf16_units(bytes) {
let keep = matches!(u, 0x30..=0x39 | 0x41..=0x5A | 0x61..=0x7A)
|| matches!(u as u8 as char, '@' | '*' | '_' | '+' | '-' | '.' | '/') && u < 0x80;
if keep {
out.push(u as u8);
} else if u < 256 {
out.push(b'%');
out.push(hex((u as u32) >> 4));
out.push(hex((u as u32) & 0xF));
} else {
out.push(b'%');
out.push(b'u');
out.push(hex((u as u32) >> 12));
out.push(hex(((u as u32) >> 8) & 0xF));
out.push(hex(((u as u32) >> 4) & 0xF));
out.push(hex((u as u32) & 0xF));
}
}
out
}
fn legacy_unescape(bytes: &[u8]) -> Vec<u8> {
let units: Vec<u16> = crate::wtf8::utf16_units(bytes).collect();
let hex4 = |s: &[u16]| -> Option<u16> {
let mut v: u32 = 0;
for &u in s {
let d = char::from_u32(u32::from(u)).and_then(|c| c.to_digit(16))?;
v = v * 16 + d;
}
Some(v as u16)
};
let mut out: Vec<u16> = Vec::with_capacity(units.len());
let mut i = 0;
while i < units.len() {
if units[i] == u16::from(b'%') {
if i + 5 < units.len()
&& units[i + 1] == u16::from(b'u')
&& let Some(v) = hex4(&units[i + 2..i + 6])
{
out.push(v);
i += 6;
continue;
}
if i + 2 < units.len()
&& let Some(v) = hex4(&units[i + 1..i + 3])
{
out.push(v);
i += 3;
continue;
}
}
out.push(units[i]);
i += 1;
}
crate::wtf8::from_utf16(&out)
}
fn parse_float_prefix(s: &str) -> f64 {
let (sign, rest) = match s.strip_prefix('-') {
Some(r) => (-1.0, r),
None => (1.0, s.strip_prefix('+').unwrap_or(s)),
};
if rest.starts_with("Infinity") {
return sign * f64::INFINITY;
}
let bytes = s.as_bytes();
let mut end = 0;
let mut seen_dot = false;
let mut seen_e = false;
while end < bytes.len() {
let ch = bytes[end] as char;
let ok = match ch {
'0'..='9' => true,
'+' | '-' if end == 0 || matches!(bytes[end - 1] as char, 'e' | 'E') => true,
'.' if !seen_dot && !seen_e => {
seen_dot = true;
true
}
'e' | 'E' if !seen_e && end > 0 => {
seen_e = true;
true
}
_ => false,
};
if !ok {
break;
}
end += 1;
}
s[..end].parse::<f64>().unwrap_or(f64::NAN)
}
fn skip_ws(c: &[char], pos: &mut usize) {
while c
.get(*pos)
.is_some_and(|ch| matches!(ch, ' ' | '\t' | '\n' | '\r'))
{
*pos += 1;
}
}
pub fn eval_source(source: &str) -> Result<(String, String), String> {
eval_source_with_limits(source, crate::limits::Limits::default())
}
pub fn eval_source_with_limits(
source: &str,
limits: crate::limits::Limits,
) -> Result<(String, String), String> {
let program =
crate::parser::Parser::parse_program(source).map_err(|e| alloc::format!("{e}"))?;
let mut interp = Interp::new_with_limits(limits);
let value = match interp.run(&program) {
Ok(v) => v,
Err(ExecError::Throw(thrown)) => return Err(format_thrown(&interp, thrown)),
Err(other) => return Err(alloc::format!("{other:?}")),
};
let completion = interp.display(value);
Ok((String::from(interp.output()), completion))
}
fn format_thrown(interp: &Interp, thrown: NanBox) -> String {
if let Some((name, message)) = error_name_message(interp, thrown) {
return if message.is_empty() {
name
} else {
alloc::format!("{name}: {message}")
};
}
if let Some(raw) = thrown.as_handle() {
let h = Handle::from_raw(raw);
if let Some(m) = interp.realm().get_property(h, "message") {
let s = interp.realm().to_display_string(m);
if !s.is_empty() {
return alloc::format!("Test262Error: {s}");
}
}
}
interp.display(thrown)
}
pub(crate) fn error_name_message(interp: &Interp, thrown: NanBox) -> Option<(String, String)> {
let raw = thrown.as_handle()?;
let h = Handle::from_raw(raw);
let realm = interp.realm();
let name = realm.get_property(h, "name")?;
let name = realm.to_display_string(name);
let message = realm
.get_property(h, "message")
.map(|m| realm.to_display_string(m))
.unwrap_or_default();
Some((name, message))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorPhase {
Parse,
Runtime,
}
#[derive(Debug, Clone)]
pub struct Thrown {
pub phase: ErrorPhase,
pub name: String,
pub message: String,
}
pub fn eval_source_typed(
source: &str,
limits: crate::limits::Limits,
) -> Result<(String, String), Thrown> {
let program = match crate::parser::Parser::parse_program(source) {
Ok(p) => p,
Err(e) => {
return Err(Thrown {
phase: ErrorPhase::Parse,
name: String::from("SyntaxError"),
message: alloc::format!("{e}"),
});
}
};
let mut interp = Interp::new_with_limits(limits);
match interp.run(&program) {
Ok(value) => {
let completion = interp.display(value);
Ok((String::from(interp.output()), completion))
}
Err(ExecError::Throw(thrown)) => {
let (name, message) = error_name_message(&interp, thrown).unwrap_or_else(|| {
if let Some(raw) = thrown.as_handle()
&& let Some(m) = interp
.realm()
.get_property(Handle::from_raw(raw), "message")
{
let s = interp.realm().to_display_string(m);
if !s.is_empty() {
return (String::from("Test262Error"), s);
}
}
(interp.display(thrown), String::new())
});
Err(Thrown {
phase: ErrorPhase::Runtime,
name,
message,
})
}
Err(other) => Err(Thrown {
phase: ErrorPhase::Runtime,
name: String::from("Error"),
message: alloc::format!("{other:?}"),
}),
}
}
fn now_ms() -> f64 {
#[cfg(feature = "std")]
{
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as f64)
.unwrap_or(0.0)
}
#[cfg(not(feature = "std"))]
{
0.0
}
}
fn parse_int(s: &str, radix: u32) -> f64 {
let mut t = s.trim_start();
let mut neg = false;
if let Some(rest) = t.strip_prefix('-') {
neg = true;
t = rest;
} else if let Some(rest) = t.strip_prefix('+') {
t = rest;
}
let mut radix = radix;
if (radix == 0 || radix == 16)
&& let Some(rest) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X"))
{
t = rest;
radix = 16;
}
if radix == 0 {
radix = 10;
}
if !(2..=36).contains(&radix) {
return f64::NAN;
}
let mut value: f64 = 0.0;
let mut any = false;
for c in t.chars() {
match c.to_digit(radix) {
Some(d) => {
value = value * f64::from(radix) + f64::from(d);
any = true;
}
None => break,
}
}
if !any {
return f64::NAN;
}
if neg { -value } else { value }
}
fn bigint_to_radix(n: &crate::bignum::BigInt, radix: u32) -> String {
let radix = if (2..=36).contains(&radix) { radix } else { 10 };
n.to_str_radix(radix)
}
fn parse_bigint(digits: &str) -> crate::bignum::BigInt {
let (radix, body) = match digits.get(0..2) {
Some("0x" | "0X") => (16, &digits[2..]),
Some("0o" | "0O") => (8, &digits[2..]),
Some("0b" | "0B") => (2, &digits[2..]),
_ => (10, digits),
};
crate::bignum::BigInt::from_str_radix(body, radix).unwrap_or_else(crate::bignum::BigInt::zero)
}
fn str_char_index(n: f64) -> Option<usize> {
let n = if n.is_nan() { 0.0 } else { n };
(n >= 0.0).then_some(n as usize)
}
fn as_index(n: f64) -> Option<usize> {
if n >= 0.0 && n <= u32::MAX as f64 && (n as u64) as f64 == n {
Some(n as usize)
} else {
None
}
}
fn canonical_numeric_index(key: &str) -> Option<f64> {
if key == "-0" {
return Some(-0.0);
}
let n = match key {
"Infinity" => f64::INFINITY,
"-Infinity" => f64::NEG_INFINITY,
"NaN" => f64::NAN,
_ => key.parse::<f64>().ok()?,
};
(crate::realm::js_number_string(n) == key).then_some(n)
}
fn expand_dollar(template: &str, m: &str, before: &str, after: &str) -> String {
let chars: Vec<char> = template.chars().collect();
let mut out = String::new();
let mut i = 0;
while i < chars.len() {
if chars[i] == '$'
&& i + 1 < chars.len()
&& let Some(rep) = match chars[i + 1] {
'$' => Some("$"),
'&' => Some(m),
'`' => Some(before),
'\'' => Some(after),
_ => None,
}
{
out.push_str(rep);
i += 2;
continue;
}
out.push(chars[i]);
i += 1;
}
out
}
fn static_key(key: &PropertyKey) -> Result<String, ExecError> {
match key {
PropertyKey::Ident(s) | PropertyKey::Str(s) => Ok(String::from(&**s)),
PropertyKey::Number(n) => Ok(alloc::format!("{n}")),
PropertyKey::Private(_) => Err(ExecError::Unsupported("private key in static_key")),
PropertyKey::Computed(_) => Err(ExecError::Unsupported("computed key")),
}
}
pub(crate) fn private_storage_key(name: &str, scope: u32) -> String {
alloc::format!("\u{0}#{name}@{scope}")
}
#[cfg(test)]
mod tests;