///|
/// Convert a MoonBit string (sequence of Unicode code points) to an array
/// of UTF-16 code units. Supplementary characters (code point > 0xFFFF) are
/// split into a surrogate pair (high surrogate + low surrogate).
pub fn string_to_utf16(s : String) -> Array[Int] {
let units : Array[Int] = []
let cps = s.to_array()
for ch in cps {
let cp = ch.to_int()
if cp > 0xFFFF {
// Encode as surrogate pair
let adjusted = cp - 0x10000
let high = 0xD800 + (adjusted >> 10)
let low = 0xDC00 + (adjusted & 0x3FF)
units.push(high)
units.push(low)
} else {
units.push(cp)
}
}
units
}
///|
/// Get the UTF-16 length of a string (counting code units, not code points).
pub fn utf16_length(s : String) -> Int {
let cps = s.to_array()
let mut len = 0
for ch in cps {
let cp = ch.to_int()
if cp > 0xFFFF {
len += 2
} else {
len += 1
}
}
len
}
///|
/// Check if a class name corresponds to a TypedArray type.
pub fn is_typedarray_class(name : String) -> Bool {
match name {
"Int8Array"
| "Uint8Array"
| "Uint8ClampedArray"
| "Int16Array"
| "Uint16Array"
| "Int32Array"
| "Uint32Array"
| "Float32Array"
| "Float64Array"
| "BigInt64Array"
| "BigUint64Array" => true
_ => false
}
}
///|
/// Classify a string property key for TypedArray indexed-element interception
/// per ES §10.4.5 IntegerIndexedExoticObject + §7.1.21 CanonicalNumericIndexString.
///
/// Returns:
/// - `Some(idx)` with `idx >= 0`: a valid in-range integer index. The caller
/// should perform the indexed read/write.
/// - `Some(-1)`: a canonical numeric string (per §7.1.21) that is NOT a
/// valid integer index — `"-0"`, `"NaN"`, `"Infinity"`, `"-Infinity"`,
/// fractional like `"1.5"`, or negative. Per §10.4.5 these are still
/// intercepted (read returns `undefined`; write is a no-op) — they must
/// not fall through to ordinary property creation.
/// - `None`: not a canonical numeric string — caller should fall through to
/// ordinary property lookup / write.
///
/// Package-private: the `-1` sentinel is an implementation detail. Downstream
/// code outside `interpreter/runtime` must not depend on this shape.
///
/// Performance: every string-keyed TypedArray access (`ta.length`,
/// `ta.byteLength`, `ta.set(...)`, `ta.subarray(...)`) passes through here.
/// The ASCII digit / `-` / `N` / `I` prefix gate short-circuits non-numeric
/// keys before invoking `@string.parse_double`, whose error path on
/// non-numeric input is throw-and-catch and was a hot-path allocation source.
fn classify_typedarray_string_key(s : String) -> Int? {
if s.length() == 0 {
return None
}
// Fast reject: keys not starting with an ASCII digit, `-` (for `-0`/
// `-Infinity`), `N` (for `NaN`), or `I` (for `Infinity`) are never
// canonical numeric strings per §7.1.21. This skips the throw path in
// `@string.parse_double` for the common method/property-name case.
let c = s.get_char(0).unwrap_or(' ')
let is_candidate = (c >= '0' && c <= '9') || c == '-' || c == 'N' || c == 'I'
if !is_candidate {
return None
}
if s == "-0" {
return Some(-1)
}
try {
let n = @string.parse_double(s)
if n.to_string() == s {
let idx = n.to_int()
if idx.to_double() == n && idx >= 0 {
Some(idx)
} else {
Some(-1)
}
} else {
None
}
} catch {
_ => None
}
}