Skip to main content

atmos/os_lib/js/builtins/
props_methods.rs

1// 分割: builtins.rs より機械的に移動(2026-07-16 リファクタ フェーズ2)。
2// ロジック不変。可視性のみ pub(crate) へ昇格し、親が pub(crate) use で再エクスポート。
3use super::*;
4
5// ============ グローバル関数 ============
6
7/// `parseInt(string, radix)`。以前は `radix` 省略時に常に10進として扱い、また
8/// `radix` を明示していても `"0x"`/`"0X"` 接頭辞を読み飛ばさなかった。仕様上は
9/// `radix` 省略(または `0`)かつ文字列が(符号の後)`0x`/`0X` で始まる場合は自動的に
10/// 16進として解釈し、明示的に `radix=16` の場合も同接頭辞を読み飛ばす必要がある。
11/// 例: `parseInt("0xFF")` は仕様どおりなら `255` だが、以前は `0`("0" の直後の "x" が
12/// 数字でないため即座に打ち切られていた)になっていた。
13pub(crate) fn global_parse_int(_: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
14    let s = arg(a, 0).to_js_string();
15    let radix_arg = arg(a, 1).to_number();
16    let mut radix = if radix_arg.is_finite() && (2.0..=36.0).contains(&radix_arg) {
17        radix_arg as u32
18    } else {
19        0 // 未指定/0: 後段で "0x" 接頭辞の有無を見て 16 か 10 かを決める。
20    };
21    let t = s.trim();
22    let (neg, t) = if let Some(rest) = t.strip_prefix('-') {
23        (true, rest)
24    } else if let Some(rest) = t.strip_prefix('+') {
25        (false, rest)
26    } else {
27        (false, t)
28    };
29    let has_hex_prefix = t.len() > 2 && (t.starts_with("0x") || t.starts_with("0X"));
30    let t = if has_hex_prefix && (radix == 0 || radix == 16) {
31        radix = 16;
32        t.get(2..).unwrap_or("")
33    } else {
34        t
35    };
36    if radix == 0 {
37        radix = 10;
38    }
39    let mut digits = String::new();
40    for c in t.chars() {
41        if c.is_digit(radix) {
42            digits.push(c);
43        } else {
44            break;
45        }
46    }
47    match i64::from_str_radix(&digits, radix) {
48        Ok(n) => Ok(Value::Number(if neg { -n as f64 } else { n as f64 })),
49        Err(_) => Ok(Value::Number(f64::NAN)),
50    }
51}
52pub(crate) fn global_parse_float(_: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
53    let s = arg(a, 0).to_js_string();
54    let t = s.trim();
55    // 先頭から数値として読める最長部分。
56    let mut end = 0;
57    let bytes = t.as_bytes();
58    let mut seen_dot = false;
59    let mut seen_e = false;
60    while end < bytes.len() {
61        let c = bytes[end] as char;
62        if c.is_ascii_digit() {
63            end += 1;
64        } else if c == '.' && !seen_dot && !seen_e {
65            seen_dot = true;
66            end += 1;
67        } else if (c == 'e' || c == 'E') && !seen_e && end > 0 {
68            seen_e = true;
69            end += 1;
70        } else if (c == '+' || c == '-')
71            && end > 0
72            && (bytes[end - 1] == b'e' || bytes[end - 1] == b'E')
73        {
74            end += 1;
75        } else {
76            break;
77        }
78    }
79    match t.get(..end).unwrap_or("").parse::<f64>() {
80        Ok(n) => Ok(Value::Number(n)),
81        Err(_) => Ok(Value::Number(f64::NAN)),
82    }
83}
84pub(crate) fn global_is_nan(_: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
85    Ok(Value::Bool(arg(a, 0).to_number().is_nan()))
86}
87pub(crate) fn global_is_finite(_: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
88    Ok(Value::Bool(arg(a, 0).to_number().is_finite()))
89}
90
91/// `eval(source)`(ES1)。文字列以外の引数は仕様どおり無変換でそのまま返す。
92pub(crate) fn global_eval(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
93    let first = arg(a, 0);
94    match &first {
95        Value::Str(s) => it.eval_source(s),
96        _ => Ok(first),
97    }
98}
99
100/// `new Function(arg1, ..., argN, body)`(ES1)。動的にソースからコードを生成する定番
101/// イディオム。`(function anonymous(arg1,...,argN){ body })` という即時式へ組み立て直し、
102/// 既存の `eval_source`(常にグローバルスコープで実行する簡略実装)へ委譲する。
103pub(crate) fn function_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
104    let (params, body): (String, String) = match a.len() {
105        0 => (String::new(), String::new()),
106        1 => (String::new(), a[0].to_js_string()),
107        n => {
108            let params = a[..n - 1]
109                .iter()
110                .map(|v| v.to_js_string())
111                .collect::<Vec<_>>()
112                .join(",");
113            (params, a[n - 1].to_js_string())
114        }
115    };
116    let source = format!("(function anonymous({}) {{\n{}\n}})", params, body);
117    it.eval_source(&source)
118}
119
120// ============ プロパティ取得(プリミティブ/組込みメソッド) ============
121
122/// 文字列レシーバのプロパティ/メソッド。
123pub fn string_get(s: &str, key: &str) -> Value {
124    if key == "length" {
125        return Value::Number(s.chars().count() as f64);
126    }
127    if let Ok(idx) = key.parse::<usize>() {
128        return match s.chars().nth(idx) {
129            Some(c) => Value::str(c.to_string()),
130            None => Value::Undefined,
131        };
132    }
133    match key {
134        "charAt" => nv("charAt", str_char_at),
135        "charCodeAt" => nv("charCodeAt", str_char_code_at),
136        "codePointAt" => nv("codePointAt", str_code_point_at),
137        "localeCompare" => nv("localeCompare", str_locale_compare),
138        "indexOf" => nv("indexOf", str_index_of),
139        "includes" => nv("includes", str_includes),
140        "startsWith" => nv("startsWith", str_starts_with),
141        "endsWith" => nv("endsWith", str_ends_with),
142        "slice" => nv("slice", str_slice),
143        "substring" => nv("substring", str_substring),
144        "substr" => nv("substr", str_substr),
145        "toUpperCase" => nv("toUpperCase", str_to_upper),
146        "toLowerCase" => nv("toLowerCase", str_to_lower),
147        "trim" => nv("trim", str_trim),
148        "split" => nv("split", str_split),
149        "replace" => nv("replace", str_replace),
150        "replaceAll" => nv("replaceAll", str_replace_all),
151        "match" => nv("match", str_match),
152        "matchAll" => nv("matchAll", str_match_all),
153        "search" => nv("search", str_search),
154        "repeat" => nv("repeat", str_repeat),
155        "concat" => nv("concat", str_concat),
156        "padStart" => nv("padStart", str_pad_start),
157        "padEnd" => nv("padEnd", str_pad_end),
158        "at" => nv("at", str_at),
159        "toString" => nv("toString", str_identity),
160        "trimStart" => nv("trimStart", str_trim_start),
161        "trimEnd" => nv("trimEnd", str_trim_end),
162        // `trimLeft`/`trimRight`(Annex B.2.2。`trimStart`/`trimEnd` の別名だが
163        // 古いコードで今も見かける)が丸ごと未対応だった。
164        "trimLeft" => nv("trimLeft", str_trim_start),
165        "trimRight" => nv("trimRight", str_trim_end),
166        "Symbol(Symbol.iterator)" => nv("[Symbol.iterator]", str_iterator),
167        "isWellFormed" => nv("isWellFormed", str_is_well_formed),
168        "toWellFormed" => nv("toWellFormed", str_to_well_formed),
169        "normalize" => nv("normalize", str_normalize),
170        // Annex B.2.3 の HTML ラッパーメソッド群(`"x".bold()`/`.link(url)` 等、
171        // 古いコードで今も見かける)が丸ごと未対応だった。
172        "anchor" => nv("anchor", str_anchor),
173        "big" => nv("big", str_big),
174        "blink" => nv("blink", str_blink),
175        "bold" => nv("bold", str_bold),
176        "fixed" => nv("fixed", str_fixed),
177        "fontcolor" => nv("fontcolor", str_fontcolor),
178        "fontsize" => nv("fontsize", str_fontsize),
179        "italics" => nv("italics", str_italics),
180        "link" => nv("link", str_link),
181        "small" => nv("small", str_small),
182        "strike" => nv("strike", str_strike),
183        "sub" => nv("sub", str_sub),
184        "sup" => nv("sup", str_sup),
185        _ => Value::Undefined,
186    }
187}
188/// Annex B.2.3 `CreateHTML` の属性なし版(`<tag>this</tag>`)。
189pub(crate) fn html_wrap_tag(s: &str, tag: &str) -> String {
190    alloc::format!("<{0}>{1}</{0}>", tag, s)
191}
192/// Annex B.2.3 `CreateHTML`。属性値の `"` は `&quot;` へエスケープする(仕様どおり)。
193pub(crate) fn html_wrap_attr(s: &str, tag: &str, attr: &str, value: &str) -> String {
194    let escaped = value.replace('"', "&quot;");
195    alloc::format!("<{0} {1}=\"{2}\">{3}</{0}>", tag, attr, escaped, s)
196}
197pub(crate) fn str_anchor(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
198    Ok(Value::str(html_wrap_attr(&this_str(&t), "a", "name", &arg(a, 0).to_js_string())))
199}
200pub(crate) fn str_big(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
201    Ok(Value::str(html_wrap_tag(&this_str(&t), "big")))
202}
203pub(crate) fn str_blink(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
204    Ok(Value::str(html_wrap_tag(&this_str(&t), "blink")))
205}
206pub(crate) fn str_bold(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
207    Ok(Value::str(html_wrap_tag(&this_str(&t), "b")))
208}
209pub(crate) fn str_fixed(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
210    Ok(Value::str(html_wrap_tag(&this_str(&t), "tt")))
211}
212pub(crate) fn str_fontcolor(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
213    Ok(Value::str(html_wrap_attr(&this_str(&t), "font", "color", &arg(a, 0).to_js_string())))
214}
215pub(crate) fn str_fontsize(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
216    Ok(Value::str(html_wrap_attr(&this_str(&t), "font", "size", &arg(a, 0).to_js_string())))
217}
218pub(crate) fn str_italics(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
219    Ok(Value::str(html_wrap_tag(&this_str(&t), "i")))
220}
221pub(crate) fn str_link(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
222    Ok(Value::str(html_wrap_attr(&this_str(&t), "a", "href", &arg(a, 0).to_js_string())))
223}
224pub(crate) fn str_small(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
225    Ok(Value::str(html_wrap_tag(&this_str(&t), "small")))
226}
227pub(crate) fn str_strike(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
228    Ok(Value::str(html_wrap_tag(&this_str(&t), "strike")))
229}
230pub(crate) fn str_sub(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
231    Ok(Value::str(html_wrap_tag(&this_str(&t), "sub")))
232}
233pub(crate) fn str_sup(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
234    Ok(Value::str(html_wrap_tag(&this_str(&t), "sup")))
235}
236
237pub(crate) fn this_str(this: &Value) -> String {
238    this.to_js_string()
239}
240
241pub(crate) fn str_char_at(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
242    let s = this_str(&t);
243    let i = arg(a, 0).to_number() as usize;
244    Ok(Value::str(
245        s.chars().nth(i).map(|c| c.to_string()).unwrap_or_default(),
246    ))
247}
248pub(crate) fn str_char_code_at(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
249    let s = this_str(&t);
250    let i = arg(a, 0).to_number() as usize;
251    match s.chars().nth(i) {
252        Some(c) => Ok(Value::Number(c as u32 as f64)),
253        None => Ok(Value::Number(f64::NAN)),
254    }
255}
256/// `String.prototype.codePointAt()`(ES2015)が丸ごと未実装だった(`charCodeAt` はあった)。
257/// この処理系の文字列は Rust の `String`(既に UTF-8 のコードポイント単位)で保持されており
258/// サロゲートペアの概念自体が無いため、`charCodeAt` と等価な実装で足りる
259/// (範囲外は `NaN` ではなく仕様どおり `undefined` を返す点だけが異なる)。
260pub(crate) fn str_code_point_at(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
261    let s = this_str(&t);
262    let i = arg(a, 0).to_number() as usize;
263    match s.chars().nth(i) {
264        Some(c) => Ok(Value::Number(c as u32 as f64)),
265        None => Ok(Value::Undefined),
266    }
267}
268/// `String.prototype.localeCompare()`(ES3)も未実装だった。この環境には Intl/ロケール
269/// データが無いため、ロケール依存の照合順序ではなく通常の(コードポイント順)比較で近似する。
270pub(crate) fn str_locale_compare(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
271    let s = this_str(&t);
272    let other = arg(a, 0).to_js_string();
273    use core::cmp::Ordering;
274    Ok(Value::Number(match s.as_str().cmp(other.as_str()) {
275        Ordering::Less => -1.0,
276        Ordering::Equal => 0.0,
277        Ordering::Greater => 1.0,
278    }))
279}
280pub(crate) fn str_index_of(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
281    let s = this_str(&t);
282    let needle = arg(a, 0).to_js_string();
283    match s.find(&needle) {
284        Some(byte_idx) => Ok(Value::Number(
285            s.get(..byte_idx).unwrap_or("").chars().count() as f64,
286        )),
287        None => Ok(Value::Number(-1.0)),
288    }
289}
290/// `includes`/`startsWith`/`endsWith` は仕様上、第1引数に `RegExp` を渡すと
291/// `TypeError` を投げる必要がある(`str.includes(/x/)` のような「うっかり正規表現を
292/// 渡した」誤用に対する意図的なガード。`match`/`replace` 等と違いこれらのメソッドは
293/// 正規表現マッチングを一切サポートしないため、無警告でリテラル文字列
294/// `"/x/"` として検索してしまうと気づきにくいバグの温床になる)。以前はこの
295/// チェックが丸ごと欠落しており、常に無言でリテラル文字列比較にフォールバックして
296/// いた。
297pub(crate) fn reject_regexp_arg(it: &Interp, v: &Value, method: &str) -> Result<(), Value> {
298    if as_regexp(v).is_some() {
299        return Err(it.error(alloc::format!(
300            "First argument to String.prototype.{} must not be a regular expression",
301            method
302        )));
303    }
304    Ok(())
305}
306pub(crate) fn str_includes(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
307    reject_regexp_arg(it, &arg(a, 0), "includes")?;
308    Ok(Value::Bool(
309        this_str(&t).contains(&arg(a, 0).to_js_string()),
310    ))
311}
312pub(crate) fn str_starts_with(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
313    reject_regexp_arg(it, &arg(a, 0), "startsWith")?;
314    Ok(Value::Bool(
315        this_str(&t).starts_with(&arg(a, 0).to_js_string()),
316    ))
317}
318pub(crate) fn str_ends_with(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
319    reject_regexp_arg(it, &arg(a, 0), "endsWith")?;
320    Ok(Value::Bool(
321        this_str(&t).ends_with(&arg(a, 0).to_js_string()),
322    ))
323}
324pub(crate) fn str_slice(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
325    let chars: Vec<char> = this_str(&t).chars().collect();
326    let len = chars.len() as i64;
327    let start = norm_index(arg(a, 0).to_number(), len, 0);
328    let end = if matches!(arg(a, 1), Value::Undefined) {
329        len
330    } else {
331        norm_index(arg(a, 1).to_number(), len, len)
332    };
333    let (s, e) = (start.max(0) as usize, end.max(0) as usize);
334    Ok(Value::str(if s < e && s < chars.len() {
335        chars[s..e.min(chars.len())].iter().collect::<String>()
336    } else {
337        String::new()
338    }))
339}
340pub(crate) fn str_substring(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
341    let chars: Vec<char> = this_str(&t).chars().collect();
342    let len = chars.len();
343    let mut s = clamp_idx(arg(a, 0).to_number(), len);
344    let mut e = if matches!(arg(a, 1), Value::Undefined) {
345        len
346    } else {
347        clamp_idx(arg(a, 1).to_number(), len)
348    };
349    if s > e {
350        core::mem::swap(&mut s, &mut e);
351    }
352    Ok(Value::str(chars[s..e].iter().collect::<String>()))
353}
354/// `String.prototype.substr(start, length)`。ES3 由来の非推奨(Annex B)メソッドだが、
355/// 古いコードで依然として広く使われるため実装する。`slice`/`substring` はあったが
356/// `substr` だけが欠落していた。
357pub(crate) fn str_substr(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
358    let chars: Vec<char> = this_str(&t).chars().collect();
359    let len = chars.len() as i64;
360    let raw_start = arg(a, 0).to_number();
361    let start = if raw_start < 0.0 {
362        (len + raw_start as i64).max(0)
363    } else {
364        (raw_start as i64).min(len)
365    };
366    let length = if matches!(arg(a, 1), Value::Undefined) {
367        len - start
368    } else {
369        (arg(a, 1).to_number() as i64).max(0)
370    };
371    let end = (start + length).min(len);
372    let s = start as usize;
373    let e = (end.max(start)) as usize;
374    Ok(Value::str(if s < e {
375        chars[s..e].iter().collect::<String>()
376    } else {
377        String::new()
378    }))
379}
380pub(crate) fn str_to_upper(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
381    Ok(Value::str(this_str(&t).to_uppercase()))
382}
383pub(crate) fn str_to_lower(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
384    Ok(Value::str(this_str(&t).to_lowercase()))
385}
386pub(crate) fn str_trim(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
387    Ok(Value::str(this_str(&t).trim().to_string()))
388}
389pub(crate) fn str_trim_start(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
390    Ok(Value::str(this_str(&t).trim_start().to_string()))
391}
392pub(crate) fn str_trim_end(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
393    Ok(Value::str(this_str(&t).trim_end().to_string()))
394}
395pub(crate) fn str_identity(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
396    Ok(Value::str(this_str(&t)))
397}
398/// `String.prototype[Symbol.iterator]()`(ES2015)が丸ごと欠落していた。呼び出すと
399/// 「メソッドが存在しない」`TypeError` になっていた(`for...of` 自体は文字列を直接
400/// 走査する内部高速経路で動くため気づかれなかった)。コードポイント単位(サロゲート
401/// ペアを1文字として扱う。Rust の `char` が既にスカラー値単位のため自然に対応できる)で
402/// 走査する Iterator オブジェクトを、`Array.prototype[Symbol.iterator]` 等と同じ
403/// `make_iterator()` を再利用して返す。
404pub(crate) fn str_iterator(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
405    let s = this_str(&t);
406    let items: Vec<Value> = s.chars().map(|c| Value::str(c.to_string())).collect();
407    Ok(Value::Object(make_iterator(items)))
408}
409/// `String.prototype.isWellFormed()`(ES2024)。内部表現が Rust の `String`(常に有効な UTF-8)
410/// であるため、単独サロゲート(lone surrogate)は構造上存在し得ず常に true を返す簡略実装。
411pub(crate) fn str_is_well_formed(_: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
412    Ok(Value::Bool(true))
413}
414/// `String.prototype.toWellFormed()`(ES2024)。上記と同じ理由により常に元の文字列をそのまま返す。
415pub(crate) fn str_to_well_formed(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
416    Ok(Value::str(this_str(&t)))
417}
418/// `String.prototype.normalize(form)`(ES2015)。丸ごと欠落しており、呼び出すと
419/// 「メソッドが存在しない」`TypeError` になっていた。本来は Unicode 正規化(NFC/NFD/NFKC/NFKD)
420/// を行うが、この no_std 環境には正規化用の Unicode 分解/合成テーブルが無いため、`form` 引数の
421/// 妥当性検証(無効な値は仕様どおり `RangeError`)だけ行い、内容は元の文字列をそのまま返す
422/// 簡略実装(合成済み文字のみで構成された通常の文字列であれば実質的に無害)。
423/// 西欧言語の合成済みラテン文字 ⇔ (基底文字, 結合分音符) の対応表。
424/// フル Unicode Character Database(分解表・結合クラス・正規順序付け・合成除外
425/// リスト)を持たない no_std 環境では完全な NFC/NFD/NFKC/NFKD 実装は非現実的な
426/// ため、実用頻度の高いラテン語圏の分音符付き文字(フランス語・スペイン語・
427/// ドイツ語・ポルトガル語等)に限定した部分実装とする。
428pub(crate) const NFD_TABLE: &[(char, char, char)] = &[
429    ('À', 'A', '\u{0300}'), ('Á', 'A', '\u{0301}'), ('Â', 'A', '\u{0302}'),
430    ('Ã', 'A', '\u{0303}'), ('Ä', 'A', '\u{0308}'), ('Å', 'A', '\u{030A}'),
431    ('Ç', 'C', '\u{0327}'),
432    ('È', 'E', '\u{0300}'), ('É', 'E', '\u{0301}'), ('Ê', 'E', '\u{0302}'), ('Ë', 'E', '\u{0308}'),
433    ('Ì', 'I', '\u{0300}'), ('Í', 'I', '\u{0301}'), ('Î', 'I', '\u{0302}'), ('Ï', 'I', '\u{0308}'),
434    ('Ñ', 'N', '\u{0303}'),
435    ('Ò', 'O', '\u{0300}'), ('Ó', 'O', '\u{0301}'), ('Ô', 'O', '\u{0302}'),
436    ('Õ', 'O', '\u{0303}'), ('Ö', 'O', '\u{0308}'),
437    ('Ù', 'U', '\u{0300}'), ('Ú', 'U', '\u{0301}'), ('Û', 'U', '\u{0302}'), ('Ü', 'U', '\u{0308}'),
438    ('Ý', 'Y', '\u{0301}'),
439    ('à', 'a', '\u{0300}'), ('á', 'a', '\u{0301}'), ('â', 'a', '\u{0302}'),
440    ('ã', 'a', '\u{0303}'), ('ä', 'a', '\u{0308}'), ('å', 'a', '\u{030A}'),
441    ('ç', 'c', '\u{0327}'),
442    ('è', 'e', '\u{0300}'), ('é', 'e', '\u{0301}'), ('ê', 'e', '\u{0302}'), ('ë', 'e', '\u{0308}'),
443    ('ì', 'i', '\u{0300}'), ('í', 'i', '\u{0301}'), ('î', 'i', '\u{0302}'), ('ï', 'i', '\u{0308}'),
444    ('ñ', 'n', '\u{0303}'),
445    ('ò', 'o', '\u{0300}'), ('ó', 'o', '\u{0301}'), ('ô', 'o', '\u{0302}'),
446    ('õ', 'o', '\u{0303}'), ('ö', 'o', '\u{0308}'),
447    ('ù', 'u', '\u{0300}'), ('ú', 'u', '\u{0301}'), ('û', 'u', '\u{0302}'), ('ü', 'u', '\u{0308}'),
448    ('ý', 'y', '\u{0301}'), ('ÿ', 'y', '\u{0308}'),
449];
450pub(crate) fn japanese_voiced_sound_decompose(c: char) -> Option<(char, char)> {
451    let cp = c as u32;
452    match cp {
453        0x304C | 0x304E | 0x3050 | 0x3052 | 0x3054 |
454        0x3056 | 0x3058 | 0x305A | 0x305C | 0x305E |
455        0x3060 | 0x3062 | 0x3065 | 0x3067 | 0x3069 |
456        0x3070 | 0x3073 | 0x3076 | 0x3079 | 0x307C |
457        0x30AC | 0x30AE | 0x30B0 | 0x30B2 | 0x30B4 |
458        0x30B6 | 0x30B8 | 0x30BA | 0x30BC | 0x30BE |
459        0x30C0 | 0x30C2 | 0x30C5 | 0x30C7 | 0x30C9 |
460        0x30D0 | 0x30D3 | 0x30D6 | 0x30D9 | 0x30DC => {
461            char::from_u32(cp - 1).map(|b| (b, '\u{3099}'))
462        }
463        0x3071 | 0x3074 | 0x3077 | 0x307A | 0x307D |
464        0x30D1 | 0x30D4 | 0x30D7 | 0x30DA | 0x30DD => {
465            char::from_u32(cp - 2).map(|b| (b, '\u{309A}'))
466        }
467        0x30F4 => Some(('ウ', '\u{3099}')),
468        _ => None,
469    }
470}
471
472pub(crate) fn japanese_voiced_sound_compose(base: char, mark: char) -> Option<char> {
473    if mark != '\u{3099}' && mark != '\u{309A}' && mark != '\u{309B}' && mark != '\u{309C}' {
474        return None;
475    }
476    let b = base as u32;
477    let is_dakuten = mark == '\u{3099}' || mark == '\u{309B}';
478    let is_handakuten = mark == '\u{309A}' || mark == '\u{309C}';
479
480    match b {
481        0x304B | 0x304D | 0x304F | 0x3051 | 0x3053 |
482        0x3055 | 0x3057 | 0x3059 | 0x305B | 0x305D |
483        0x305F | 0x3061 | 0x3064 | 0x3066 | 0x3068 |
484        0x306F | 0x3072 | 0x3075 | 0x3078 | 0x307B |
485        0x30AB | 0x30AD | 0x30AF | 0x30B1 | 0x30B3 |
486        0x30B5 | 0x30B7 | 0x30B9 | 0x30BB | 0x30BD |
487        0x30BF | 0x30C1 | 0x30C4 | 0x30C6 | 0x30C8 |
488        0x30CF | 0x30D2 | 0x30D5 | 0x30D8 | 0x30DB if is_dakuten => {
489            char::from_u32(b + 1)
490        }
491        0x306F | 0x3072 | 0x3075 | 0x3078 | 0x307B |
492        0x30CF | 0x30D2 | 0x30D5 | 0x30D8 | 0x30DB if is_handakuten => {
493            char::from_u32(b + 2)
494        }
495        0x30A6 if is_dakuten => Some('ヴ'),
496        _ => None,
497    }
498}
499
500pub(crate) fn nfd_decompose(s: &str) -> String {
501    const HANGUL_S_BASE: u32 = 0xAC00;
502    const HANGUL_L_BASE: u32 = 0x1100;
503    const HANGUL_V_BASE: u32 = 0x1161;
504    const HANGUL_T_BASE: u32 = 0x11A7;
505    const HANGUL_N_COUNT: u32 = 588;
506    const HANGUL_T_COUNT: u32 = 28;
507    const HANGUL_S_COUNT: u32 = 11172;
508
509    let mut out = String::with_capacity(s.len());
510    for c in s.chars() {
511        let cp = c as u32;
512        if (HANGUL_S_BASE..HANGUL_S_BASE + HANGUL_S_COUNT).contains(&cp) {
513            let s_index = cp - HANGUL_S_BASE;
514            let l_index = s_index / HANGUL_N_COUNT;
515            let v_index = (s_index % HANGUL_N_COUNT) / HANGUL_T_COUNT;
516            let t_index = s_index % HANGUL_T_COUNT;
517            if let Some(l) = char::from_u32(HANGUL_L_BASE + l_index) {
518                out.push(l);
519            }
520            if let Some(v) = char::from_u32(HANGUL_V_BASE + v_index) {
521                out.push(v);
522            }
523            if t_index > 0 {
524                if let Some(t) = char::from_u32(HANGUL_T_BASE + t_index) {
525                    out.push(t);
526                }
527            }
528            continue;
529        }
530        if let Some((base, mark)) = japanese_voiced_sound_decompose(c) {
531            out.push(base);
532            out.push(mark);
533            continue;
534        }
535        match NFD_TABLE.iter().find(|(pre, _, _)| *pre == c) {
536            Some((_, base, mark)) => {
537                out.push(*base);
538                out.push(*mark);
539            }
540            None => out.push(c),
541        }
542    }
543    out
544}
545pub(crate) fn nfc_compose(s: &str) -> String {
546    const HANGUL_S_BASE: u32 = 0xAC00;
547    const HANGUL_L_BASE: u32 = 0x1100;
548    const HANGUL_V_BASE: u32 = 0x1161;
549    const HANGUL_T_BASE: u32 = 0x11A7;
550    const HANGUL_L_COUNT: u32 = 19;
551    const HANGUL_V_COUNT: u32 = 21;
552    const HANGUL_T_COUNT: u32 = 28;
553    const HANGUL_N_COUNT: u32 = 588;
554
555    let decomposed = nfd_decompose(s);
556    let mut out = String::with_capacity(decomposed.len());
557    let mut chars = decomposed.chars().peekable();
558    while let Some(c) = chars.next() {
559        let cp = c as u32;
560        if (HANGUL_L_BASE..HANGUL_L_BASE + HANGUL_L_COUNT).contains(&cp) {
561            if let Some(&next) = chars.peek() {
562                let np = next as u32;
563                if (HANGUL_V_BASE..HANGUL_V_BASE + HANGUL_V_COUNT).contains(&np) {
564                    chars.next();
565                    let l_index = cp - HANGUL_L_BASE;
566                    let v_index = np - HANGUL_V_BASE;
567                    let mut s_index = l_index * HANGUL_N_COUNT + v_index * HANGUL_T_COUNT;
568                    if let Some(&t_next) = chars.peek() {
569                        let tp = t_next as u32;
570                        if tp > HANGUL_T_BASE && tp < HANGUL_T_BASE + HANGUL_T_COUNT {
571                            chars.next();
572                            let t_index = tp - HANGUL_T_BASE;
573                            s_index += t_index;
574                        }
575                    }
576                    if let Some(hangul) = char::from_u32(HANGUL_S_BASE + s_index) {
577                        out.push(hangul);
578                        continue;
579                    }
580                }
581            }
582        }
583        if let Some(&next) = chars.peek() {
584            if let Some(composed) = japanese_voiced_sound_compose(c, next) {
585                out.push(composed);
586                chars.next();
587                continue;
588            }
589            if let Some((pre, _, _)) = NFD_TABLE.iter().find(|(_, base, mark)| *base == c && *mark == next) {
590                out.push(*pre);
591                chars.next();
592                continue;
593            }
594        }
595        out.push(c);
596    }
597    out
598}
599pub(crate) fn str_normalize(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
600    let form = if matches!(arg(a, 0), Value::Undefined) {
601        String::from("NFC")
602    } else {
603        arg(a, 0).to_js_string()
604    };
605    let s = this_str(&t);
606    match form.as_str() {
607        "NFC" | "NFKC" => Ok(Value::str(nfc_compose(&s))),
608        "NFD" | "NFKD" => Ok(Value::str(nfd_decompose(&s))),
609        _ => Err(it.error("The normalization form should be one of NFC, NFD, NFKC, NFKD.")),
610    }
611}
612/// `String.prototype.split(separator, limit)`。以前は `limit`(第2引数。結果配列の
613/// 最大要素数)を完全に無視しており、`"a,b,c".split(",", 2)` が仕様の `["a","b"]` ではなく
614/// 全要素 `["a","b","c"]` を返していた。
615pub(crate) fn str_split(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
616    let rx = arg(a, 0);
617    if let Value::Object(_) = &rx {
618        let matcher = it.get_property(&rx, "Symbol(Symbol.split)")?;
619        if !matches!(matcher, Value::Undefined) {
620            return it.call_value(&matcher, rx.clone(), &[t, arg(a, 1)]);
621        }
622    }
623    let s = this_str(&t);
624    let limit = match arg(a, 1) {
625        Value::Undefined => usize::MAX,
626        v => {
627            let n = v.to_number();
628            if n.is_nan() || n < 0.0 {
629                usize::MAX
630            } else {
631                n as usize
632            }
633        }
634    };
635    // RegExp 区切り。
636    if let Some(r) = as_regexp(&arg(a, 0)) {
637        let chars: Vec<char> = s.chars().collect();
638        let mut items = Vec::new();
639        let mut last = 0usize;
640        let mut pos = 0usize;
641        'outer: while pos <= chars.len() && items.len() < limit {
642            match r.borrow().re.find_at(&chars, pos) {
643                Some(m) if m.end > m.start => {
644                    items.push(Value::str(chars[last..m.start].iter().collect::<String>()));
645                    // 区切りに使った正規表現のキャプチャグループは、仕様上その位置に
646                    // 挿入されて結果へ含まれる(未参加のグループは undefined)。以前は
647                    // 区切り文字列を単に読み捨てるだけで、キャプチャ内容が結果から
648                    // 丸ごと欠落していた(`"a1b".split(/(\d)/)` が
649                    // `["a","1","b"]` ではなく `["a","b"]` になっていた)。
650                    for cap in m.captures.iter().skip(1) {
651                        if items.len() >= limit {
652                            break 'outer;
653                        }
654                        let v = match cap {
655                            Some((cs, ce)) => {
656                                Value::str(chars[*cs..*ce].iter().collect::<String>())
657                            }
658                            None => Value::Undefined,
659                        };
660                        items.push(v);
661                    }
662                    last = m.end;
663                    pos = m.end;
664                }
665                _ => break,
666            }
667        }
668        if items.len() < limit {
669            items.push(Value::str(chars[last..].iter().collect::<String>()));
670        }
671        items.truncate(limit);
672        return Ok(Value::Object(Obj::array(items)));
673    }
674    let mut items: Vec<Value> = match arg(a, 0) {
675        Value::Undefined => vec![Value::str(s)],
676        sep => {
677            let sep = sep.to_js_string();
678            if sep.is_empty() {
679                s.chars().map(|c| Value::str(c.to_string())).collect()
680            } else {
681                s.split(&sep as &str)
682                    .map(|p| Value::str(p.to_string()))
683                    .collect()
684            }
685        }
686    };
687    items.truncate(limit);
688    Ok(Value::Object(Obj::array(items)))
689}
690pub(crate) fn str_replace(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
691    let rx = arg(a, 0);
692    if let Value::Object(_) = &rx {
693        let matcher = it.get_property(&rx, "Symbol(Symbol.replace)")?;
694        if !matches!(matcher, Value::Undefined) {
695            return it.call_value(&matcher, rx.clone(), &[t, arg(a, 1)]);
696        }
697    }
698    let s = this_str(&t);
699    // RegExp パターン(g フラグで全置換)。
700    if let Some(r) = as_regexp(&arg(a, 0)) {
701        let global = r.borrow().re.global;
702        return Ok(Value::str(regex_replace(it, &s, &r, arg(a, 1), global)?));
703    }
704    let repl = arg(a, 1);
705    let from = arg(a, 0).to_js_string();
706    // 関数リプレーサ(文字列パターン、最初の一致)。
707    if let Some(f) = callable_or_none(repl.clone()) {
708        if let Some(idx) = s.find(&from) {
709            let before = s.get(..idx).unwrap_or("");
710            let after = s.get(idx + from.len()..).unwrap_or("");
711            let rep = it
712                .call_value(
713                    &f,
714                    Value::Undefined,
715                    &[
716                        Value::str(from.clone()),
717                        Value::Number(s.get(..idx).unwrap_or("").chars().count() as f64),
718                        Value::str(s.clone()),
719                    ],
720                )?
721                .to_js_string();
722            return Ok(Value::str(format!("{}{}{}", before, rep, after)));
723        }
724        return Ok(Value::str(s));
725    }
726    let to = repl.to_js_string();
727    Ok(Value::str(s.replacen(&from, &to, 1)))
728}
729/// `String.prototype.replaceAll(pattern, replacement)`。以前は `pattern` が `RegExp`
730/// でも常に `to_js_string()`(`"/pat/flags"` という文字列表現)として扱われ、
731/// 正規表現として機能していなかった(`"aaa".replaceAll(/a/g, 'b')` が無変換になっていた)。
732/// また関数リプレーサ(`str_replace` は対応済み)も未対応だった。
733pub(crate) fn str_replace_all(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
734    // 【順序が重要】仕様では `g` フラグの検査が**先**で、
735    // `[Symbol.replace]` への委譲はその後。逆にすると、非 g の RegExp が
736    // 委譲側へ流れて TypeError が出なくなる。
737    let rx = arg(a, 0);
738    if let Some(r) = as_regexp(&rx) {
739        if !r.borrow().re.global {
740            return Err(it.error(
741                "String.prototype.replaceAll called with a non-global RegExp argument",
742            ));
743        }
744    }
745    // 独自の `[Symbol.replace]` を持つ相手なら、そちらへ委譲する(仕様)。
746    // `replace` は既にこの形で委譲していたが、ここだけ漏れていた。
747    if let Value::Object(_) = &rx {
748        let matcher = it.get_property(&rx, "Symbol(Symbol.replace)")?;
749        if !matches!(matcher, Value::Undefined) {
750            return it.call_value(&matcher, rx.clone(), &[t, arg(a, 1)]);
751        }
752    }
753    let s = this_str(&t);
754    // RegExp パターン: 仕様上 `g` フラグ必須(無いと TypeError)。
755    if let Some(r) = as_regexp(&arg(a, 0)) {
756        if !r.borrow().re.global {
757            return Err(it.error(
758                "String.prototype.replaceAll called with a non-global RegExp argument",
759            ));
760        }
761        return Ok(Value::str(regex_replace(it, &s, &r, arg(a, 1), true)?));
762    }
763    let from = arg(a, 0).to_js_string();
764    let repl = arg(a, 1);
765    // 関数リプレーサ(全ての一致箇所に適用)。
766    if let Some(f) = callable_or_none(repl.clone()) {
767        if from.is_empty() {
768            // 空パターンは各文字境界(文字数+1箇所)に挿入される仕様。
769            let chars: Vec<char> = s.chars().collect();
770            let mut out = String::new();
771            for (i, c) in chars.iter().enumerate() {
772                let rep = it
773                    .call_value(
774                        &f,
775                        Value::Undefined,
776                        &[Value::str(""), Value::Number(i as f64), Value::str(s.clone())],
777                    )?
778                    .to_js_string();
779                out.push_str(&rep);
780                out.push(*c);
781            }
782            let rep = it
783                .call_value(
784                    &f,
785                    Value::Undefined,
786                    &[
787                        Value::str(""),
788                        Value::Number(chars.len() as f64),
789                        Value::str(s.clone()),
790                    ],
791                )?
792                .to_js_string();
793            out.push_str(&rep);
794            return Ok(Value::str(out));
795        }
796        let mut out = String::new();
797        let mut rest = s.as_str();
798        let mut consumed = 0usize;
799        while let Some(idx) = rest.find(&from) {
800            out.push_str(rest.get(..idx).unwrap_or(""));
801            let char_pos = s.get(..consumed + idx).unwrap_or("").chars().count();
802            let rep = it
803                .call_value(
804                    &f,
805                    Value::Undefined,
806                    &[
807                        Value::str(from.clone()),
808                        Value::Number(char_pos as f64),
809                        Value::str(s.clone()),
810                    ],
811                )?
812                .to_js_string();
813            out.push_str(&rep);
814            consumed += idx + from.len();
815            rest = rest.get(idx + from.len()..).unwrap_or("");
816        }
817        out.push_str(rest);
818        return Ok(Value::str(out));
819    }
820    let to = repl.to_js_string();
821    Ok(Value::str(if from.is_empty() {
822        s
823    } else {
824        s.replace(&from, &to)
825    }))
826}
827/// `String.prototype.repeat(count)`(ES2015)。仕様上 `count` が負値または `+Infinity` の
828/// 場合は `RangeError` を投げる必要があるが、以前は範囲外を空文字列へ黙って丸めていた
829/// (`"x".repeat(-1)` が例外にならず `""` を返す、呼び出し側が引数ミスに気づけないバグ。
830/// `Array.prototype.with` の範囲外 `index` と同種の「RangeError 相当箇所で `it.error()` の
831/// 汎用 Error を投げる」という既存の簡略方針に合わせる)。
832pub(crate) fn str_repeat(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
833    let n = arg(a, 0).to_number();
834    if n.is_nan() {
835        return Ok(Value::str(String::new()));
836    }
837    if n < 0.0 || n == f64::INFINITY {
838        return Err(it.error("Invalid count value"));
839    }
840    Ok(Value::str(this_str(&t).repeat(n as usize)))
841}
842pub(crate) fn str_concat(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
843    let mut s = this_str(&t);
844    for v in a {
845        s.push_str(&v.to_js_string());
846    }
847    Ok(Value::str(s))
848}
849pub(crate) fn str_pad_start(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
850    let s = this_str(&t);
851    let target = arg(a, 0).to_number() as usize;
852    let pad = if matches!(arg(a, 1), Value::Undefined) {
853        String::from(" ")
854    } else {
855        arg(a, 1).to_js_string()
856    };
857    Ok(Value::str(pad_str(&s, target, &pad, true)))
858}
859pub(crate) fn str_pad_end(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
860    let s = this_str(&t);
861    let target = arg(a, 0).to_number() as usize;
862    let pad = if matches!(arg(a, 1), Value::Undefined) {
863        String::from(" ")
864    } else {
865        arg(a, 1).to_js_string()
866    };
867    Ok(Value::str(pad_str(&s, target, &pad, false)))
868}
869
870/// 数値レシーバのメソッド。
871pub fn number_get(n: f64, key: &str) -> Value {
872    match key {
873        "toFixed" => nv("toFixed", num_to_fixed),
874        "toString" => nv("toString", num_to_string),
875        "toExponential" => nv("toExponential", num_to_exponential),
876        "toPrecision" => nv("toPrecision", num_to_precision),
877        "valueOf" => nv("valueOf", num_value_of),
878        "toLocaleString" => nv("toLocaleString", num_to_locale_string),
879        _ => {
880            let _ = n;
881            Value::Undefined
882        }
883    }
884}
885pub(crate) fn num_value_of(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
886    Ok(Value::Number(t.to_number()))
887}
888/// `Boolean.prototype.toString`/`valueOf`。以前は `Value::Bool` へのプロパティアクセスが
889/// `get_property()` で一律 `Value::Undefined` を返しており、`true.toString()`/
890/// `false.valueOf()` のような基本的な呼出しが「関数ではない」`TypeError` になっていた
891/// (プリミティブ中 `Boolean` だけがメソッドを一切持たない状態だった)。`NativeFn` は環境を
892/// 捕獲できない生の関数ポインタのため、値は(他のプリミティブ同様)呼出時の `this` から
893/// 読み取る。
894pub fn boolean_get(_b: bool, key: &str) -> Value {
895    match key {
896        "toString" => nv("toString", bool_to_string),
897        "valueOf" => nv("valueOf", bool_value_of),
898        _ => Value::Undefined,
899    }
900}
901pub(crate) fn bool_to_string(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
902    Ok(Value::str(t.to_js_string()))
903}
904pub(crate) fn bool_value_of(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
905    Ok(t)
906}
907/// 仮数部を `[1,10)` の範囲に正規化した `(仮数, 指数)` を返す(`0` は `(0, 0)`)。
908/// 浮動小数点誤差で `log10`/`pow` の往復が範囲をわずかに外れることがあるので補正する。
909pub(crate) fn normalize_mantissa(abs_n: f64) -> (f64, i32) {
910    if abs_n == 0.0 {
911        return (0.0, 0);
912    }
913    let mut exp = libm::floor(libm::log10(abs_n)) as i32;
914    let mut mantissa = abs_n / libm::pow(10.0, exp as f64);
915    if mantissa >= 10.0 {
916        mantissa /= 10.0;
917        exp += 1;
918    } else if mantissa < 1.0 {
919        mantissa *= 10.0;
920        exp -= 1;
921    }
922    (mantissa, exp)
923}
924/// `Number.prototype.toExponential(fractionDigits)`(ES3)。丸ごと欠落しており、
925/// `n.toExponential(2)`(`"1.50e+2"` 等)を呼ぶと「メソッドが存在しない」`TypeError` に
926/// なっていた。
927pub(crate) fn num_to_exponential(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
928    let n = t.to_number();
929    if n.is_nan() {
930        return Ok(Value::str("NaN"));
931    }
932    if n.is_infinite() {
933        return Ok(Value::str(if n > 0.0 { "Infinity" } else { "-Infinity" }));
934    }
935    let neg = n < 0.0;
936    let (mantissa, mut exp) = normalize_mantissa(libm::fabs(n));
937    let d = if matches!(arg(a, 0), Value::Undefined) {
938        6
939    } else {
940        let dv = arg(a, 0).to_number();
941        if !dv.is_finite() || !(0.0..=100.0).contains(&dv) {
942            return Err(it.error("toExponential() argument must be between 0 and 100"));
943        }
944        dv as usize
945    };
946    let scale = libm::pow(10.0, d as f64);
947    let mut rounded = libm::trunc(mantissa * scale + 0.5) / scale;
948    if rounded >= 10.0 {
949        rounded /= 10.0;
950        exp += 1;
951    }
952    let mantissa_str = if d == 0 {
953        format!("{}", rounded as i64)
954    } else {
955        format_fixed(rounded, d)
956    };
957    let sign = if exp >= 0 { "+" } else { "-" };
958    Ok(Value::str(format!(
959        "{}{}e{}{}",
960        if neg { "-" } else { "" },
961        mantissa_str,
962        sign,
963        if exp >= 0 { exp } else { -exp }
964    )))
965}
966/// `Number.prototype.toPrecision(precision)`(ES3)。丸ごと欠落していた。`precision`
967/// 省略時は `toString()` と同じ、指定時は有効桁数がその値になるよう固定小数/指数表記を
968/// 自動選択する(仕様どおり、指数が `-6` 未満または `precision` 以上なら指数表記)。
969pub(crate) fn num_to_precision(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
970    let n = t.to_number();
971    if matches!(arg(a, 0), Value::Undefined) {
972        return Ok(Value::str(t.to_js_string()));
973    }
974    if n.is_nan() {
975        return Ok(Value::str("NaN"));
976    }
977    if n.is_infinite() {
978        return Ok(Value::str(if n > 0.0 { "Infinity" } else { "-Infinity" }));
979    }
980    let p = arg(a, 0).to_number();
981    if !p.is_finite() || !(1.0..=100.0).contains(&p) {
982        return Err(it.error("toPrecision() argument must be between 1 and 100"));
983    }
984    let p = p as usize;
985    if n == 0.0 {
986        return Ok(Value::str(if p == 1 {
987            String::from("0")
988        } else {
989            format!("0.{}", "0".repeat(p - 1))
990        }));
991    }
992    let neg = n < 0.0;
993    let (_, exp) = normalize_mantissa(libm::fabs(n));
994    if (exp as i64) < -6 || (exp as i64) >= p as i64 {
995        // 指数表記(有効桁数 p は仮数部の桁数として渡す)。
996        return num_to_exponential(it, t, &[Value::Number((p - 1) as f64)]);
997    }
998    // 固定小数表記: 小数点以下の桁数 = p - 1 - exp。
999    let frac_digits = (p as i32 - 1 - exp).max(0) as usize;
1000    let scale = libm::pow(10.0, frac_digits as f64);
1001    let rounded = libm::trunc(libm::fabs(n) * scale + 0.5) / scale;
1002    let s = if frac_digits == 0 {
1003        format!("{}", rounded as i64)
1004    } else {
1005        format_fixed(rounded, frac_digits)
1006    };
1007    Ok(Value::str(if neg { format!("-{}", s) } else { s }))
1008}
1009/// `Number.prototype.toFixed(fractionDigits)`。仕様上 `fractionDigits` が `0`〜`100`
1010/// の範囲外(負値や `101` 以上)なら `RangeError` を投げる必要があるが、以前は範囲外/
1011/// 非有限値を黙って `0` にクランプするだけで上限チェックが無く、`(1).toFixed(500)` の
1012/// ような呼び出しが `10^500`(`Infinity`)を経由して壊れた出力になり得た
1013/// (`toPrecision` には同種のチェックが既にあったが `toFixed` だけ欠けていた)。
1014pub(crate) fn num_to_fixed(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1015    let n = t.to_number();
1016    if n.is_nan() {
1017        return Ok(Value::str("NaN"));
1018    }
1019    let digits = arg(a, 0).to_number();
1020    let d = if matches!(arg(a, 0), Value::Undefined) {
1021        0
1022    } else if !digits.is_finite() || !(0.0..=100.0).contains(&digits) {
1023        return Err(it.error("toFixed() digits argument must be between 0 and 100"));
1024    } else {
1025        digits as usize
1026    };
1027    if n.is_infinite() {
1028        return Ok(Value::str(if n > 0.0 { "Infinity" } else { "-Infinity" }));
1029    }
1030    // 簡易固定小数: 10^d でスケールして丸め。
1031    let scale = libm::pow(10.0, d as f64);
1032    let rounded = libm::trunc(n * scale + if n >= 0.0 { 0.5 } else { -0.5 }) / scale;
1033    if d == 0 {
1034        Ok(Value::str(format!("{}", rounded as i64)))
1035    } else {
1036        Ok(Value::str(format_fixed(rounded, d)))
1037    }
1038}
1039/// `Number.prototype.toString(radix)`。以前は `radix`(第1引数。2進/16進表示等の定番
1040/// イディオム `n.toString(16)`)を完全に無視し常に10進表示だった。
1041pub(crate) fn num_to_string(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1042    let radix = match arg(a, 0) {
1043        Value::Undefined => 10u32,
1044        v => v.to_number() as u32,
1045    };
1046    if radix == 10 {
1047        return Ok(Value::str(t.to_js_string()));
1048    }
1049    if !(2..=36).contains(&radix) {
1050        return Err(it.error("toString() radix must be between 2 and 36"));
1051    }
1052    Ok(Value::str(format_radix(t.to_number(), radix)))
1053}
1054/// `Number.prototype.toLocaleString()` が `toString()` の別名に過ぎず、実際のブラウザなら
1055/// `Intl` 非搭載でも必ず入る3桁区切りのカンマ(`(1234567).toLocaleString()` → `"1,234,567"`)
1056/// が丸ごと欠落していた(常に `"1234567"` になっていた)バグ。`Intl`/ロケール引数自体は
1057/// 非対応(`Array.prototype.toLocaleString`/`normalize`/`localeCompare` と同種の割り切り)
1058/// だが、既定のグループ区切り(整数部のみ、小数部・NaN・Infinity・負号は対象外)だけは行う。
1059pub(crate) fn num_to_locale_string(_it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1060    let n = t.to_number();
1061    if !n.is_finite() {
1062        return Ok(Value::str(t.to_js_string()));
1063    }
1064    let s = t.to_js_string();
1065    let (neg, s) = match s.strip_prefix('-') {
1066        Some(rest) => (true, rest),
1067        None => (false, s.as_str()),
1068    };
1069    let (int_part, frac_part) = match s.split_once('.') {
1070        Some((i, f)) => (i, Some(f)),
1071        None => (s, None),
1072    };
1073    let mut grouped = String::new();
1074    let len = int_part.len();
1075    for (i, c) in int_part.chars().enumerate() {
1076        if i > 0 && (len - i) % 3 == 0 {
1077            grouped.push(',');
1078        }
1079        grouped.push(c);
1080    }
1081    let mut out = String::new();
1082    if neg {
1083        out.push('-');
1084    }
1085    out.push_str(&grouped);
1086    if let Some(f) = frac_part {
1087        out.push('.');
1088        out.push_str(f);
1089    }
1090    Ok(Value::str(out))
1091}
1092/// `new Intl.NumberFormat(locale?, options?)`。ロケール/オプション引数は無視し、
1093/// `.format(n)` は `Number.prototype.toLocaleString` と同じ既定グループ区切りに
1094/// 委譲する最小実装(`Intl` 節参照)。
1095pub(crate) fn intl_number_format_ctor(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
1096    let obj = Obj::plain();
1097    obj.borrow_mut()
1098        .props
1099        .insert("format".into(), nv("format", intl_number_format_format));
1100    Ok(Value::Object(obj))
1101}
1102pub(crate) fn intl_number_format_format(it: &mut Interp, _this: Value, a: &[Value]) -> Result<Value, Value> {
1103    num_to_locale_string(it, arg(a, 0), &[])
1104}
1105pub(crate) const RADIX_DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
1106pub(crate) fn format_radix(num: f64, radix: u32) -> String {
1107    if num.is_nan() {
1108        return String::from("NaN");
1109    }
1110    if num.is_infinite() {
1111        return String::from(if num > 0.0 { "Infinity" } else { "-Infinity" });
1112    }
1113    let neg = num < 0.0;
1114    let n = libm::fabs(num);
1115    let int_n = libm::floor(n);
1116    let mut frac = n - int_n;
1117    let mut int_digits = Vec::new();
1118    let mut i = int_n;
1119    if i == 0.0 {
1120        int_digits.push(b'0');
1121    }
1122    while i >= 1.0 {
1123        let rem = libm::fmod(i, radix as f64) as usize;
1124        int_digits.push(RADIX_DIGITS[rem.min(35)]);
1125        i = libm::floor(i / radix as f64);
1126    }
1127    int_digits.reverse();
1128    let mut s = String::from_utf8(int_digits).unwrap_or_default();
1129    if frac > 0.0 {
1130        s.push('.');
1131        let mut count = 0;
1132        while frac > 0.0 && count < 20 {
1133            frac *= radix as f64;
1134            let d = libm::floor(frac) as usize;
1135            s.push(RADIX_DIGITS[d.min(35)] as char);
1136            frac -= libm::floor(frac);
1137            count += 1;
1138        }
1139    }
1140    if neg {
1141        format!("-{}", s)
1142    } else {
1143        s
1144    }
1145}
1146
1147/// BigInt レシーバのメソッド(toString / valueOf)。
1148pub fn bigint_get(b: &alloc::rc::Rc<super::super::bigint::BigInt>, key: &str) -> Value {
1149    match key {
1150        "toString" => nv("toString", bigint_to_string),
1151        "valueOf" => nv("valueOf", bigint_value_of),
1152        _ => {
1153            let _ = b;
1154            Value::Undefined
1155        }
1156    }
1157}
1158pub(crate) fn bigint_to_string(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1159    Ok(Value::str(t.to_js_string()))
1160}
1161pub(crate) fn bigint_value_of(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1162    Ok(t)
1163}
1164
1165/// BigInt(value): Number / String / Boolean / BigInt から BigInt を生成する。
1166/// `new BigInt()` は不可だが、ここでは関数呼び出しのみ対応。
1167pub(crate) fn bigint_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1168    use super::super::bigint::BigInt;
1169    let arg = a.first().cloned().unwrap_or(Value::Undefined);
1170    let bi = match &arg {
1171        Value::BigInt(b) => (**b).clone(),
1172        Value::Number(n) => match BigInt::from_f64(*n) {
1173            Some(v) => v,
1174            None => {
1175                return Err(it.error("The number is not a safe integer / cannot convert to BigInt"));
1176            }
1177        },
1178        Value::Bool(b) => BigInt::from_i64(if *b { 1 } else { 0 }),
1179        Value::Str(s) => match BigInt::parse_str(s) {
1180            Some(v) => v,
1181            None => return Err(it.error("Cannot convert string to a BigInt")),
1182        },
1183        _ => return Err(it.error("Cannot convert value to a BigInt")),
1184    };
1185    Ok(Value::bigint(bi))
1186}
1187
1188/// 引数から「ビット幅」を取り出す(非負整数へ丸め)。
1189pub(crate) fn bits_arg(it: &mut Interp, a: &[Value]) -> Result<u64, Value> {
1190    let n = a.first().map(|v| v.to_number()).unwrap_or(f64::NAN);
1191    if !n.is_finite() || n < 0.0 {
1192        return Err(it.error("Invalid bits value for BigInt.asIntN/asUintN"));
1193    }
1194    Ok(libm::trunc(n) as u64)
1195}
1196
1197/// 引数2を BigInt として取り出す。
1198pub(crate) fn bigint_arg(it: &mut Interp, a: &[Value]) -> Result<super::super::bigint::BigInt, Value> {
1199    match a.get(1) {
1200        Some(Value::BigInt(b)) => Ok((**b).clone()),
1201        _ => Err(it.error("BigInt.asIntN/asUintN requires a BigInt as the second argument")),
1202    }
1203}
1204
1205/// BigInt.asIntN(bits, bigint)
1206pub(crate) fn bigint_as_int_n(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1207    let bits = bits_arg(it, a)?;
1208    let v = bigint_arg(it, a)?;
1209    Ok(Value::bigint(v.as_int_n(bits)))
1210}
1211
1212/// BigInt.asUintN(bits, bigint)
1213pub(crate) fn bigint_as_uint_n(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1214    let bits = bits_arg(it, a)?;
1215    let v = bigint_arg(it, a)?;
1216    Ok(Value::bigint(v.as_uint_n(bits)))
1217}
1218
1219// ============ オブジェクト/配列メソッド ============
1220
1221/// オブジェクトレシーバのメソッド(配列メソッド含む)。
1222pub fn object_get(o: &ObjRef, key: &str) -> Value {
1223    let is_array = matches!(o.borrow().kind, ObjKind::Array(_));
1224    if is_array {
1225        match key {
1226            "push" => return nv("push", arr_push),
1227            "pop" => return nv("pop", arr_pop),
1228            "shift" => return nv("shift", arr_shift),
1229            "unshift" => return nv("unshift", arr_unshift),
1230            "slice" => return nv("slice", arr_slice),
1231            "indexOf" => return nv("indexOf", arr_index_of),
1232            "includes" => return nv("includes", arr_includes),
1233            "join" => return nv("join", arr_join),
1234            "concat" => return nv("concat", arr_concat),
1235            "reverse" => return nv("reverse", arr_reverse),
1236            "map" => return nv("map", arr_map),
1237            "filter" => return nv("filter", arr_filter),
1238            "forEach" => return nv("forEach", arr_for_each),
1239            "reduce" => return nv("reduce", arr_reduce),
1240            "find" => return nv("find", arr_find),
1241            "findIndex" => return nv("findIndex", arr_find_index),
1242            "some" => return nv("some", arr_some),
1243            "every" => return nv("every", arr_every),
1244            "sort" => return nv("sort", arr_sort),
1245            "fill" => return nv("fill", arr_fill),
1246            "flat" => return nv("flat", arr_flat),
1247            "flatMap" => return nv("flatMap", arr_flat_map),
1248            "at" => return nv("at", arr_at),
1249            "findLast" => return nv("findLast", arr_find_last),
1250            "findLastIndex" => return nv("findLastIndex", arr_find_last_index),
1251            "entries" => return nv("entries", arr_entries),
1252            "keys" => return nv("keys", arr_keys),
1253            "values" => return nv("values", arr_values),
1254            // `Array.prototype[Symbol.iterator]` は仕様上 `values` の別名。`for...of` は
1255            // 内部の高速経路(`iterate_values()`)で配列を直接回すため気づかれなかったが、
1256            // `arr[Symbol.iterator]()` を明示的に呼ぶ(手動イテレータ駆動、
1257            // `yield* arr[Symbol.iterator]()` 等)パターンは未登録のため
1258            // `undefined is not a function` になっていた。
1259            "Symbol(Symbol.iterator)" => return nv("[Symbol.iterator]", arr_values),
1260            "reduceRight" => return nv("reduceRight", arr_reduce_right),
1261            "toString" => return nv("toString", arr_to_string),
1262            "toLocaleString" => return nv("toLocaleString", arr_to_locale_string),
1263            "splice" => return nv("splice", arr_splice),
1264            "lastIndexOf" => return nv("lastIndexOf", arr_last_index_of),
1265            "copyWithin" => return nv("copyWithin", arr_copy_within),
1266            "toSorted" => return nv("toSorted", arr_to_sorted),
1267            "toReversed" => return nv("toReversed", arr_to_reversed),
1268            "toSpliced" => return nv("toSpliced", arr_to_spliced),
1269            "with" => return nv("with", arr_with),
1270            _ => {}
1271        }
1272    }
1273    // 共通オブジェクトメソッド。仕様上 `Object.prototype` は5個の own メソッド
1274    // (`hasOwnProperty`/`isPrototypeOf`/`propertyIsEnumerable`/`toString`/
1275    // `toLocaleString`)を持つが、`toLocaleString` だけ丸ごと欠けていた
1276    // (既定の `Object.prototype.toLocaleString` は単に `this.toString()` を
1277    // 呼ぶだけなので `obj_to_string` を再利用する)。`Array.prototype.toLocaleString`
1278    // は各要素の `toLocaleString` を呼ぼうとするため、この欠落は素のオブジェクトを
1279    // 要素に持つ配列の `toLocaleString()` にも波及していた。
1280    match key {
1281        "hasOwnProperty" => nv("hasOwnProperty", obj_has_own),
1282        "toString" | "toLocaleString" => nv("toString", obj_to_string),
1283        "isPrototypeOf" => nv("isPrototypeOf", obj_is_prototype_of),
1284        "propertyIsEnumerable" => nv("propertyIsEnumerable", obj_property_is_enumerable),
1285        // `__defineGetter__`/`__defineSetter__`/`__lookupGetter__`/
1286        // `__lookupSetter__`(Annex B.3.1。`Object.defineProperty`/
1287        // `getOwnPropertyDescriptor` 登場以前からの遺産だが、古いコードや
1288        // ポリフィルで今も使われる)が丸ごと未対応だった。
1289        "__defineGetter__" => nv("__defineGetter__", obj_define_getter),
1290        "__defineSetter__" => nv("__defineSetter__", obj_define_setter),
1291        "__lookupGetter__" => nv("__lookupGetter__", obj_lookup_getter),
1292        "__lookupSetter__" => nv("__lookupSetter__", obj_lookup_setter),
1293        _ => Value::Undefined,
1294    }
1295}
1296pub(crate) fn obj_define_getter(_: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1297    if let Value::Object(o) = &this {
1298        use super::super::value::Accessor;
1299        let key = arg(a, 0).to_js_string();
1300        let mut b = o.borrow_mut();
1301        let entry = b.accessors.entry(key).or_insert(Accessor { get: None, set: None });
1302        entry.get = Some(arg(a, 1));
1303    }
1304    Ok(Value::Undefined)
1305}
1306pub(crate) fn obj_define_setter(_: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1307    if let Value::Object(o) = &this {
1308        use super::super::value::Accessor;
1309        let key = arg(a, 0).to_js_string();
1310        let mut b = o.borrow_mut();
1311        let entry = b.accessors.entry(key).or_insert(Accessor { get: None, set: None });
1312        entry.set = Some(arg(a, 1));
1313    }
1314    Ok(Value::Undefined)
1315}
1316pub(crate) fn obj_lookup_getter(_: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1317    if let Value::Object(o) = &this {
1318        let key = arg(a, 0).to_js_string();
1319        if let Some(g) = o.borrow().accessors.get(&key).and_then(|acc| acc.get.clone()) {
1320            return Ok(g);
1321        }
1322    }
1323    Ok(Value::Undefined)
1324}
1325pub(crate) fn obj_lookup_setter(_: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1326    if let Value::Object(o) = &this {
1327        let key = arg(a, 0).to_js_string();
1328        if let Some(s) = o.borrow().accessors.get(&key).and_then(|acc| acc.set.clone()) {
1329            return Ok(s);
1330        }
1331    }
1332    Ok(Value::Undefined)
1333}
1334
1335/// 配列プロトタイプメソッドのほぼ全てがこのヘルパ経由で `this` の要素を読む。以前は
1336/// `Proxy` を素通しできず(`ObjKind::Array` に一致しない)、`Array.prototype.map.call(
1337/// new Proxy([1,2,3],{}), f)` のように Proxy をラップした配列へ直接メソッド呼出しすると
1338/// 常に空配列扱いになるバグだった(`for...of`/スプレッドは別経路で既に対応済みだったが、
1339/// メソッド呼出し経由はこのヘルパ止まりで漏れていた)。
1340pub(crate) fn this_items(this: &Value) -> Vec<Value> {
1341    if let Value::Object(o) = this {
1342        let o = unwrap_proxy_target(o);
1343        let b = o.borrow();
1344        if let ObjKind::Array(items) = &b.kind {
1345            return items.clone();
1346        }
1347    }
1348    Vec::new()
1349}
1350/// `this_items` と同じ Proxy 透過性が必要(配列を直接変更するメソッド
1351/// push/pop/sort/splice 等が内部で使う実オブジェクト参照)。
1352pub(crate) fn this_objref(this: &Value) -> Option<ObjRef> {
1353    if let Value::Object(o) = this {
1354        Some(unwrap_proxy_target(o))
1355    } else {
1356        None
1357    }
1358}
1359
1360/// `this` の `length` を読む(array-like 汎用経路用。`array_like_items` と同じ丸め)。
1361pub(crate) fn array_like_length(it: &mut Interp, t: &Value) -> usize {
1362    let len = it
1363        .get_property(t, "length")
1364        .map(|l| l.to_number())
1365        .unwrap_or(0.0);
1366    if len.is_finite() && len > 0.0 {
1367        len as usize
1368    } else {
1369        0
1370    }
1371}
1372
1373/// `push`/`pop`/`shift`/`unshift` は仕様上ジェネリックメソッドで、`length` +
1374/// 添字プロパティを持つだけの非配列 array-like オブジェクトへの `.call()` でも
1375/// 機能すべきだが、以前は `this_objref` で得た `ObjRef` が `ObjKind::Array` で
1376/// なければ何もせず `0`/`undefined` を返す静かな破壊バグだった(`slice`/`join`
1377/// 等の読み取り専用メソッドで修正済みの同型バグの、書き込み系での見落とし)。
1378/// 実配列は高速パス(`Vec` 直接操作)、それ以外は `length`+添字プロパティの
1379/// 読み書きにフォールバックする。
1380pub(crate) fn arr_push(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1381    if let Some(o) = this_objref(&t) {
1382        if let ObjKind::Array(items) = &mut o.borrow_mut().kind {
1383            for v in a {
1384                items.push(v.clone());
1385            }
1386            return Ok(Value::Number(items.len() as f64));
1387        }
1388    }
1389    let mut len = array_like_length(it, &t);
1390    for v in a {
1391        it.set_property(&t, &len.to_string(), v.clone());
1392        len += 1;
1393    }
1394    it.set_property(&t, "length", Value::Number(len as f64));
1395    Ok(Value::Number(len as f64))
1396}
1397pub(crate) fn arr_pop(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1398    if let Some(o) = this_objref(&t) {
1399        if let ObjKind::Array(items) = &mut o.borrow_mut().kind {
1400            return Ok(items.pop().unwrap_or(Value::Undefined));
1401        }
1402    }
1403    let len = array_like_length(it, &t);
1404    if len == 0 {
1405        it.set_property(&t, "length", Value::Number(0.0));
1406        return Ok(Value::Undefined);
1407    }
1408    let new_len = len - 1;
1409    let val = it
1410        .get_property(&t, &new_len.to_string())
1411        .unwrap_or(Value::Undefined);
1412    it.set_property(&t, "length", Value::Number(new_len as f64));
1413    Ok(val)
1414}
1415pub(crate) fn arr_shift(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1416    if let Some(o) = this_objref(&t) {
1417        if let ObjKind::Array(items) = &mut o.borrow_mut().kind {
1418            if items.is_empty() {
1419                return Ok(Value::Undefined);
1420            }
1421            return Ok(items.remove(0));
1422        }
1423    }
1424    let len = array_like_length(it, &t);
1425    if len == 0 {
1426        it.set_property(&t, "length", Value::Number(0.0));
1427        return Ok(Value::Undefined);
1428    }
1429    let first = it.get_property(&t, "0").unwrap_or(Value::Undefined);
1430    for i in 1..len {
1431        let v = it.get_property(&t, &i.to_string()).unwrap_or(Value::Undefined);
1432        it.set_property(&t, &(i - 1).to_string(), v);
1433    }
1434    it.set_property(&t, "length", Value::Number((len - 1) as f64));
1435    Ok(first)
1436}
1437pub(crate) fn arr_unshift(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1438    if let Some(o) = this_objref(&t) {
1439        if let ObjKind::Array(items) = &mut o.borrow_mut().kind {
1440            for (i, v) in a.iter().enumerate() {
1441                items.insert(i, v.clone());
1442            }
1443            return Ok(Value::Number(items.len() as f64));
1444        }
1445    }
1446    let len = array_like_length(it, &t);
1447    let shift = a.len();
1448    // 既存要素を後ろへ retreat させてから新規要素を先頭に書き込む(末尾から
1449    // 埋めることで、まだ読んでいない添字を上書きしてしまう事故を避ける)。
1450    for i in (0..len).rev() {
1451        let v = it.get_property(&t, &i.to_string()).unwrap_or(Value::Undefined);
1452        it.set_property(&t, &(i + shift).to_string(), v);
1453    }
1454    for (i, v) in a.iter().enumerate() {
1455        it.set_property(&t, &i.to_string(), v.clone());
1456    }
1457    let new_len = len + shift;
1458    it.set_property(&t, "length", Value::Number(new_len as f64));
1459    Ok(Value::Number(new_len as f64))
1460}
1461/// `Array.prototype.slice` は仕様上ジェネリックメソッドだが、以前は `this_items`
1462/// (`ObjKind::Array` 以外は無条件で空 `Vec`)を直接使っていたため、
1463/// `[].slice.call(arrayLikeObj, ...)` のような array-like への適用が常に `[]` を
1464/// 返す静かな破壊バグだった(`join`/`concat` で修正済みの同型バグ)。
1465pub(crate) fn arr_slice(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1466    let is_array = matches!(&t, Value::Object(o) if matches!(o.borrow().kind, ObjKind::Array(_)));
1467    let items = if is_array {
1468        this_items(&t)
1469    } else {
1470        array_like_items(it, &t)
1471    };
1472    let len = items.len() as i64;
1473    let start = norm_index(arg(a, 0).to_number(), len, 0).max(0) as usize;
1474    let end = if matches!(arg(a, 1), Value::Undefined) {
1475        len
1476    } else {
1477        norm_index(arg(a, 1).to_number(), len, len)
1478    }
1479    .max(0) as usize;
1480    let out = if start < end && start < items.len() {
1481        items[start..end.min(items.len())].to_vec()
1482    } else {
1483        Vec::new()
1484    };
1485    Ok(Value::Object(Obj::array(out)))
1486}
1487/// `fromIndex`(第2引数)を正規化する。負の値は末尾からのオフセット、範囲外は
1488/// `len`(=以降の探索が即座に空になる)に丸める(`indexOf`/`includes` 共通)。
1489pub(crate) fn arr_from_index(a: &[Value], len: usize) -> usize {
1490    if matches!(arg(a, 1), Value::Undefined) {
1491        return 0;
1492    }
1493    let n = arg(a, 1).to_number();
1494    if n.is_nan() {
1495        return 0;
1496    }
1497    let len = len as i64;
1498    let idx = if n < 0.0 { (len + n as i64).max(0) } else { n as i64 };
1499    idx.min(len) as usize
1500}
1501/// `Array.prototype.indexOf(searchElement, fromIndex)`。以前は `fromIndex`
1502/// (第2引数)を完全に無視し、常に先頭から探索していた。
1503/// `indexOf`/`includes`/`forEach`/`map`/`filter` 等は仕様上ジェネリックメソッドだが、
1504/// 以前は `this_items` を直接使っていたため array-like への `.call()` が常に
1505/// 「何も見つからない/反復しない」結果になる同型のバグが複数箇所にあった
1506/// (`join`/`slice`/`concat`/`forEach` で修正済みの続き)。
1507pub(crate) fn arr_index_of(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1508    let items = generic_array_items(it, &t);
1509    let target = arg(a, 0);
1510    let from = arr_from_index(a, items.len());
1511    for (i, v) in items.iter().enumerate().skip(from) {
1512        if v.strict_eq(&target) {
1513            return Ok(Value::Number(i as f64));
1514        }
1515    }
1516    Ok(Value::Number(-1.0))
1517}
1518/// `Array.prototype.includes` は仕様上 SameValueZero で比較する必要がある
1519/// (`===` と違って `NaN` 同士は等しいと判定される)。以前は `strict_eq`(`===`)を
1520/// 使っており、`[NaN].includes(NaN)` が `false` になるバグだった(`indexOf` が `===` を
1521/// 使うのは仕様どおり正しいので、そちらは変更していない)。
1522pub(crate) fn arr_includes(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1523    let items = generic_array_items(it, &t);
1524    let target = arg(a, 0);
1525    let from = arr_from_index(a, items.len());
1526    Ok(Value::Bool(
1527        items.iter().skip(from).any(|v| same_value_zero(v, &target)),
1528    ))
1529}
1530/// `Array.prototype.join` は仕様上ジェネリックメソッド(`this` の内部 `[[Class]]` を
1531/// 見ず `length` + 添字プロパティだけを見る)だが、以前は `this_items`(`ObjKind::Array`
1532/// 以外は無条件で空 `Vec`)を直接使っていたため、
1533/// `Array.prototype.join.call({0:'a',1:'b',length:2}, '-')` のような array-like への
1534/// 適用が常に `""` を返す静かな破壊バグだった。
1535pub(crate) fn arr_join(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1536    let is_array = matches!(&t, Value::Object(o) if matches!(o.borrow().kind, ObjKind::Array(_)));
1537    let items = if is_array {
1538        this_items(&t)
1539    } else {
1540        array_like_items(it, &t)
1541    };
1542    let sep = if matches!(arg(a, 0), Value::Undefined) {
1543        String::from(",")
1544    } else {
1545        arg(a, 0).to_js_string()
1546    };
1547    let parts: Vec<String> = items
1548        .iter()
1549        .map(|v| match v {
1550            Value::Undefined | Value::Null => String::new(),
1551            _ => v.to_js_string(),
1552        })
1553        .collect();
1554    Ok(Value::str(parts.join(&sep)))
1555}
1556/// `this` が実配列(`ObjKind::Array`)かどうか。`join`/`slice`/`concat` に続き、
1557/// `forEach`/`map`/`filter`/`indexOf`/`includes` 等の仕様上ジェネリックなメソッドで
1558/// `this_items`(非配列は無条件で空 `Vec`)から `array_like_items`
1559/// (`length`+添字プロパティを読む汎用フォールバック)への切り替え判定に使う共通ヘルパ。
1560pub(crate) fn is_real_array(t: &Value) -> bool {
1561    matches!(t, Value::Object(o) if matches!(o.borrow().kind, ObjKind::Array(_)))
1562}
1563/// `this_items` と `array_like_items` を上記の判定で使い分ける便宜関数。
1564pub(crate) fn generic_array_items(it: &mut Interp, t: &Value) -> Vec<Value> {
1565    if is_real_array(t) {
1566        this_items(t)
1567    } else {
1568        array_like_items(it, t)
1569    }
1570}
1571/// 配列でない array-like オブジェクト(`length` + 添字プロパティ)を `Vec<Value>` に
1572/// 展開する。`Array.prototype.concat` が `Symbol.isConcatSpreadable` で true を明示した
1573/// 非配列オブジェクトを展開する際に使う。
1574pub(crate) fn array_like_items(it: &mut Interp, v: &Value) -> Vec<Value> {
1575    let len = it
1576        .get_property(v, "length")
1577        .map(|l| l.to_number())
1578        .unwrap_or(0.0);
1579    let len = if len.is_finite() && len > 0.0 { len as usize } else { 0 };
1580    let mut out = Vec::with_capacity(len);
1581    for i in 0..len {
1582        out.push(
1583            it.get_property(v, &i.to_string())
1584                .unwrap_or(Value::Undefined),
1585        );
1586    }
1587    out
1588}
1589
1590/// `Array.prototype.concat` は仕様上 `Symbol.isConcatSpreadable` を尊重すべきだが、
1591/// 以前は単に `ObjKind::Array` かどうかだけで展開の有無を決めており、
1592/// `arr[Symbol.isConcatSpreadable] = false` な配列も無条件に展開され、逆に
1593/// `Symbol.isConcatSpreadable = true` を明示した非配列 array-like オブジェクトは
1594/// 展開されず単一要素として push されてしまっていた。
1595/// `concat` の各オペランド(`this` 含む)を isConcatSpreadable 判定に従って
1596/// `items` に展開/push する共通ロジック。以前は `this`(第一引数の呼び出し対象)
1597/// にはこのロジックが適用されておらず `this_items` を直接使っていたため、
1598/// `[].concat.call(arrayLikeObj, x)` のような非配列 `this` が結果から丸ごと
1599/// 消えてしまう(本来は spreadable でなければ `this` 自体が単一要素として
1600/// 含まれるべき)静かな破壊バグだった。
1601pub(crate) fn concat_push(it: &mut Interp, v: &Value, items: &mut Vec<Value>) -> Result<(), Value> {
1602    match v {
1603        Value::Object(o) => {
1604            let flag = it.get_property(v, "Symbol(Symbol.isConcatSpreadable)")?;
1605            let is_array = matches!(o.borrow().kind, ObjKind::Array(_));
1606            let spreadable = if matches!(flag, Value::Undefined) {
1607                is_array
1608            } else {
1609                flag.truthy()
1610            };
1611            if !spreadable {
1612                items.push(v.clone());
1613            } else if is_array {
1614                if let ObjKind::Array(other) = &o.borrow().kind {
1615                    items.extend(other.clone());
1616                }
1617            } else {
1618                items.extend(array_like_items(it, v));
1619            }
1620        }
1621        other => items.push(other.clone()),
1622    }
1623    Ok(())
1624}
1625pub(crate) fn arr_concat(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1626    let mut items = Vec::new();
1627    concat_push(it, &t, &mut items)?;
1628    for v in a {
1629        concat_push(it, v, &mut items)?;
1630    }
1631    Ok(Value::Object(Obj::array(items)))
1632}
1633pub(crate) fn arr_reverse(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1634    if let Some(o) = this_objref(&t) {
1635        if let ObjKind::Array(items) = &mut o.borrow_mut().kind {
1636            items.reverse();
1637        }
1638    }
1639    Ok(t)
1640}
1641pub(crate) fn arr_to_string(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1642    Ok(Value::str(t.to_js_string()))
1643}
1644/// `Array.prototype.toLocaleString()`(ES3)が丸ごと欠落していた。各要素の
1645/// `toLocaleString()` を呼び出し `,` で連結する(`Intl` 非搭載のためロケール依存の書式化は
1646/// 行わず、要素が `toLocaleString` を持たなければ通常の文字列化にフォールバックする簡略実装。
1647/// `Date.prototype.toLocaleString` 等、要素側の実装があればそちらが優先される)。
1648pub(crate) fn arr_to_locale_string(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1649    let items = generic_array_items(it, &t);
1650    let mut parts = Vec::with_capacity(items.len());
1651    for v in items {
1652        if matches!(v, Value::Undefined | Value::Null) {
1653            parts.push(String::new());
1654            continue;
1655        }
1656        let method = it.get_property(&v, "toLocaleString").unwrap_or(Value::Undefined);
1657        let s = if matches!(&method, Value::Object(f) if f.borrow().is_callable()) {
1658            it.call_value(&method, v.clone(), &[])?.to_js_string()
1659        } else {
1660            v.to_js_string()
1661        };
1662        parts.push(s);
1663    }
1664    Ok(Value::str(parts.join(",")))
1665}
1666/// 高階配列メソッドのコールバックは第2引数 `thisArg` を受け付ける(`map`/`filter`/
1667/// `forEach`/`find` 系すべてに共通の仕様だが、以前はどれも `Value::Undefined` を
1668/// 決め打ちしており `thisArg` が完全に無視されていた)。
1669pub(crate) fn arr_map(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1670    let items = generic_array_items(it, &t);
1671    let f = arg(a, 0);
1672    let this_arg = arg(a, 1);
1673    let mut out = Vec::with_capacity(items.len());
1674    for (i, v) in items.iter().enumerate() {
1675        let r = it.call_value(
1676            &f,
1677            this_arg.clone(),
1678            &[v.clone(), Value::Number(i as f64), t.clone()],
1679        )?;
1680        out.push(r);
1681    }
1682    // `%TypedArray%.prototype.map` は仕様上、結果も同じ種別の TypedArray になる
1683    // (callback の戻り値は内部で数値変換される)。以前は `this` が TypedArray でも
1684    // 常に普通の配列を返すバグだった(`toSorted`/`with` 等と同種)。
1685    Ok(same_kind_as(it, &t, out))
1686}
1687pub(crate) fn arr_filter(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1688    let items = generic_array_items(it, &t);
1689    let f = arg(a, 0);
1690    let this_arg = arg(a, 1);
1691    let mut out = Vec::new();
1692    for (i, v) in items.iter().enumerate() {
1693        if it
1694            .call_value(
1695                &f,
1696                this_arg.clone(),
1697                &[v.clone(), Value::Number(i as f64), t.clone()],
1698            )?
1699            .truthy()
1700        {
1701            out.push(v.clone());
1702        }
1703    }
1704    // `%TypedArray%.prototype.filter` も同種の型情報保持バグだった。
1705    Ok(same_kind_as(it, &t, out))
1706}
1707/// `Array.prototype.forEach` は仕様上ジェネリックメソッドだが、以前は `this_items`
1708/// (`ObjKind::Array` 以外は無条件で空 `Vec`)を直接使っていたため、`.call(arrayLikeObj,
1709/// fn)`(`arguments`/`NodeList` 等への定番イディオム)が何も反復しない静かな破壊
1710/// バグだった(`join`/`slice`/`concat` で修正済みの同型バグ)。
1711pub(crate) fn arr_for_each(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1712    let items = generic_array_items(it, &t);
1713    let f = arg(a, 0);
1714    let this_arg = arg(a, 1);
1715    for (i, v) in items.iter().enumerate() {
1716        it.call_value(
1717            &f,
1718            this_arg.clone(),
1719            &[v.clone(), Value::Number(i as f64), t.clone()],
1720        )?;
1721    }
1722    Ok(Value::Undefined)
1723}
1724/// `reduce`/`find`/`findIndex`/`some`/`every` にも `forEach`/`map`/`filter`/`indexOf`/
1725/// `includes` と同型のジェネリックメソッド未対応バグがあった(`this_items` を直接
1726/// 使っており array-like への `.call()` が常に「何もしない/見つからない」になっていた)。
1727pub(crate) fn arr_reduce(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1728    let items = generic_array_items(it, &t);
1729    let f = arg(a, 0);
1730    let mut idx = 0;
1731    let mut acc = if a.len() >= 2 {
1732        arg(a, 1)
1733    } else {
1734        if items.is_empty() {
1735            return Err(it.error("Reduce of empty array with no initial value"));
1736        }
1737        idx = 1;
1738        items[0].clone()
1739    };
1740    while idx < items.len() {
1741        acc = it.call_value(
1742            &f,
1743            Value::Undefined,
1744            &[
1745                acc,
1746                items[idx].clone(),
1747                Value::Number(idx as f64),
1748                t.clone(),
1749            ],
1750        )?;
1751        idx += 1;
1752    }
1753    Ok(acc)
1754}
1755pub(crate) fn arr_find(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1756    let items = generic_array_items(it, &t);
1757    let f = arg(a, 0);
1758    let this_arg = arg(a, 1);
1759    for (i, v) in items.iter().enumerate() {
1760        if it
1761            .call_value(
1762                &f,
1763                this_arg.clone(),
1764                &[v.clone(), Value::Number(i as f64), t.clone()],
1765            )?
1766            .truthy()
1767        {
1768            return Ok(v.clone());
1769        }
1770    }
1771    Ok(Value::Undefined)
1772}
1773pub(crate) fn arr_find_index(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1774    let items = generic_array_items(it, &t);
1775    let f = arg(a, 0);
1776    let this_arg = arg(a, 1);
1777    for (i, v) in items.iter().enumerate() {
1778        if it
1779            .call_value(
1780                &f,
1781                this_arg.clone(),
1782                &[v.clone(), Value::Number(i as f64), t.clone()],
1783            )?
1784            .truthy()
1785        {
1786            return Ok(Value::Number(i as f64));
1787        }
1788    }
1789    Ok(Value::Number(-1.0))
1790}
1791pub(crate) fn arr_some(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1792    let items = generic_array_items(it, &t);
1793    let f = arg(a, 0);
1794    let this_arg = arg(a, 1);
1795    for (i, v) in items.iter().enumerate() {
1796        if it
1797            .call_value(
1798                &f,
1799                this_arg.clone(),
1800                &[v.clone(), Value::Number(i as f64), t.clone()],
1801            )?
1802            .truthy()
1803        {
1804            return Ok(Value::Bool(true));
1805        }
1806    }
1807    Ok(Value::Bool(false))
1808}
1809pub(crate) fn arr_every(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1810    let items = generic_array_items(it, &t);
1811    let f = arg(a, 0);
1812    let this_arg = arg(a, 1);
1813    for (i, v) in items.iter().enumerate() {
1814        if !it
1815            .call_value(
1816                &f,
1817                this_arg.clone(),
1818                &[v.clone(), Value::Number(i as f64), t.clone()],
1819            )?
1820            .truthy()
1821        {
1822            return Ok(Value::Bool(false));
1823        }
1824    }
1825    Ok(Value::Bool(true))
1826}
1827/// 仕様上 `sort`/`toSorted` は `undefined` 要素を比較関数に一切渡さず、常に配列末尾に
1828/// 送る特別扱いが必要(ECMA-262 `SortCompare`)。以前はこの特別扱いが無く `undefined` も
1829/// 通常の要素として文字列比較/比較関数に渡していたため、例えば
1830/// `[undefined, 5].sort((a,b)=>a-b)` は `a-b` が `NaN` になり順序が保証されない
1831/// (常に末尾に来るはずが来ない)バグがあった。
1832pub(crate) fn insertion_sort_excluding_undefined(
1833    it: &mut Interp,
1834    mut items: alloc::vec::Vec<Value>,
1835    cmp: &Value,
1836) -> Result<alloc::vec::Vec<Value>, Value> {
1837    let undefined_count = items.iter().filter(|v| matches!(v, Value::Undefined)).count();
1838    items.retain(|v| !matches!(v, Value::Undefined));
1839    let n = items.len();
1840    for i in 1..n {
1841        let mut j = i;
1842        while j > 0 {
1843            let ord = if matches!(cmp, Value::Undefined) {
1844                items[j - 1].to_js_string().cmp(&items[j].to_js_string())
1845            } else {
1846                let r = it
1847                    .call_value(
1848                        cmp,
1849                        Value::Undefined,
1850                        &[items[j - 1].clone(), items[j].clone()],
1851                    )?
1852                    .to_number();
1853                if r > 0.0 {
1854                    core::cmp::Ordering::Greater
1855                } else if r < 0.0 {
1856                    core::cmp::Ordering::Less
1857                } else {
1858                    core::cmp::Ordering::Equal
1859                }
1860            };
1861            if ord == core::cmp::Ordering::Greater {
1862                items.swap(j - 1, j);
1863                j -= 1;
1864            } else {
1865                break;
1866            }
1867        }
1868    }
1869    for _ in 0..undefined_count {
1870        items.push(Value::Undefined);
1871    }
1872    Ok(items)
1873}
1874pub(crate) fn arr_sort(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1875    let items = this_items(&t);
1876    let cmp = arg(a, 0);
1877    let items = insertion_sort_excluding_undefined(it, items, &cmp)?;
1878    if let Some(o) = this_objref(&t) {
1879        if let ObjKind::Array(slot) = &mut o.borrow_mut().kind {
1880            *slot = items;
1881        }
1882    }
1883    Ok(t)
1884}
1885/// `Array.prototype.fill(value, start, end)`。以前は `start`/`end`(第2/第3引数)を
1886/// 完全に無視して常に配列全体を上書きしており、`[1,2,3,4,5].fill(0,1,3)` のような
1887/// 部分埋めの定番パターンが機能していなかった。
1888pub(crate) fn arr_fill(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1889    if let Some(o) = this_objref(&t) {
1890        if let ObjKind::Array(items) = &mut o.borrow_mut().kind {
1891            let v = arg(a, 0);
1892            let len = items.len() as i64;
1893            let start = norm_index(arg(a, 1).to_number(), len, 0).max(0) as usize;
1894            let end = if matches!(arg(a, 2), Value::Undefined) {
1895                len
1896            } else {
1897                norm_index(arg(a, 2).to_number(), len, len)
1898            }
1899            .max(0) as usize;
1900            let end = end.min(items.len());
1901            for slot in items.iter_mut().take(end).skip(start) {
1902                *slot = v.clone();
1903            }
1904        }
1905    }
1906    Ok(t)
1907}
1908pub(crate) fn arr_splice(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1909    let o = match this_objref(&t) {
1910        Some(o) => o,
1911        None => return Ok(Value::Object(Obj::array(alloc::vec![]))),
1912    };
1913    let mut items = if let ObjKind::Array(v) = &o.borrow().kind {
1914        v.clone()
1915    } else {
1916        return Ok(Value::Object(Obj::array(alloc::vec![])));
1917    };
1918    let len = items.len() as i64;
1919    let start_raw = arg(a, 0).to_number() as i64;
1920    let start = if start_raw < 0 {
1921        (len + start_raw).max(0) as usize
1922    } else {
1923        start_raw.min(len) as usize
1924    };
1925    let delete_count = if a.len() < 2 {
1926        items.len() - start
1927    } else {
1928        let dc = arg(a, 1).to_number() as i64;
1929        dc.max(0).min((len - start as i64).max(0)) as usize
1930    };
1931    let insert: alloc::vec::Vec<Value> = a.iter().skip(2).cloned().collect();
1932    let removed: alloc::vec::Vec<Value> = items.drain(start..start + delete_count).collect();
1933    for (i, v) in insert.into_iter().enumerate() {
1934        items.insert(start + i, v);
1935    }
1936    if let ObjKind::Array(slot) = &mut o.borrow_mut().kind {
1937        *slot = items;
1938    }
1939    Ok(Value::Object(Obj::array(removed)))
1940}
1941
1942/// `Array.prototype.lastIndexOf(searchElement, fromIndex)`。同じ `fromIndex` 無視バグが
1943/// こちらにもあった(`indexOf`/`includes` と同じ調査で発見)。既定は末尾(`length-1`)から、
1944/// 負の値は末尾からのオフセットとして解釈し、その位置から先頭へ向かって探索する。
1945/// `Array.prototype.lastIndexOf(target, fromIndex)`。仕様の `ToIntegerOrInfinity` は
1946/// `NaN` を `0` として扱うため、`fromIndex` に `NaN` を渡した場合は「index 0 のみを見る」
1947/// (`fromIndex=0` と同じ)挙動になるはずだが、以前は `NaN` を検知すると無条件に `-1` を
1948/// 返しており、`[5,1,2].lastIndexOf(5, NaN)` が本来の `0` ではなく `-1` になっていた。
1949pub(crate) fn arr_last_index_of(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1950    let items = generic_array_items(it, &t);
1951    let target = arg(a, 0);
1952    let len = items.len() as i64;
1953    if len == 0 {
1954        return Ok(Value::Number(-1.0));
1955    }
1956    let start = if matches!(arg(a, 1), Value::Undefined) {
1957        len - 1
1958    } else {
1959        let n = arg(a, 1).to_number();
1960        let n = if n.is_nan() { 0.0 } else { n };
1961        if n < 0.0 {
1962            len + n as i64
1963        } else {
1964            (n as i64).min(len - 1)
1965        }
1966    };
1967    if start < 0 {
1968        return Ok(Value::Number(-1.0));
1969    }
1970    for i in (0..=start as usize).rev() {
1971        if items[i].strict_eq(&target) {
1972            return Ok(Value::Number(i as f64));
1973        }
1974    }
1975    Ok(Value::Number(-1.0))
1976}
1977
1978pub(crate) fn arr_copy_within(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1979    let o = match this_objref(&t) {
1980        Some(o) => o,
1981        None => return Ok(t),
1982    };
1983    let mut items = if let ObjKind::Array(v) = &o.borrow().kind {
1984        v.clone()
1985    } else {
1986        return Ok(t);
1987    };
1988    let len = items.len() as i64;
1989    let to_raw = arg(a, 0).to_number() as i64;
1990    let to = if to_raw < 0 {
1991        (len + to_raw).max(0) as usize
1992    } else {
1993        to_raw.min(len) as usize
1994    };
1995    let from_raw = if matches!(arg(a, 1), Value::Undefined) { 0i64 } else { arg(a, 1).to_number() as i64 };
1996    let from = if from_raw < 0 {
1997        (len + from_raw).max(0) as usize
1998    } else {
1999        from_raw.min(len) as usize
2000    };
2001    let end_raw = if matches!(arg(a, 2), Value::Undefined) { len } else { arg(a, 2).to_number() as i64 };
2002    let end = if end_raw < 0 {
2003        (len + end_raw).max(0) as usize
2004    } else {
2005        end_raw.min(len) as usize
2006    };
2007    let count = if end > from { (end - from).min(len as usize - to) } else { 0 };
2008    let src: alloc::vec::Vec<Value> = items[from..from + count].to_vec();
2009    for (i, v) in src.into_iter().enumerate() {
2010        if to + i < items.len() {
2011            items[to + i] = v;
2012        }
2013    }
2014    if let ObjKind::Array(slot) = &mut o.borrow_mut().kind {
2015        *slot = items;
2016    }
2017    Ok(t)
2018}
2019
2020/// `Array.prototype.toSorted/toReversed/toSpliced/with`(ES2023)は %TypedArray%
2021/// にも同名メソッドが仕様化されているが、この処理系ではどれも `this` が TypedArray
2022/// (`ObjKind::Array` を流用し `_ta_kind` タグで区別)かどうかを見ずに常に素の
2023/// `Obj::array(items)` を返しており、`new Int8Array([3,1,2]).toSorted()` が
2024/// `Int8Array` ではなく普通の配列に化けるバグだった(`structuredClone(TypedArray)`
2025/// と同種)。`this` の `_ta_kind` を引き継いで返す共通ヘルパーで対応する。
2026pub(crate) fn same_kind_as(it: &mut Interp, t: &Value, items: alloc::vec::Vec<Value>) -> Value {
2027    let ta_kind_tag = if let Value::Object(o) = t {
2028        o.borrow().props.get("_ta_kind").map(|v| v.to_js_string())
2029    } else {
2030        None
2031    };
2032    match ta_kind_tag {
2033        Some(tag) => {
2034            // `with()` の第2引数のような、まだ型変換(ラップ/クランプ)を経ていない
2035            // 生の値が混ざっている場合があるため、コンストラクタ/`set`/索引代入と
2036            // 同じ `TaKind::convert()` を通す(既に有効域内の値には無害)。
2037            let kind = TaKind::from_name(&tag);
2038            let values: alloc::vec::Vec<f64> = items.iter().map(|v| kind.convert(v)).collect();
2039            make_typed_array(it, values, kind)
2040        }
2041        None => Value::Object(Obj::array(items)),
2042    }
2043}
2044pub(crate) fn arr_to_sorted(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2045    let items = generic_array_items(it, &t);
2046    let cmp = arg(a, 0);
2047    let items = insertion_sort_excluding_undefined(it, items, &cmp)?;
2048    Ok(same_kind_as(it, &t, items))
2049}
2050
2051pub(crate) fn arr_to_reversed(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
2052    let mut items = generic_array_items(it, &t);
2053    items.reverse();
2054    Ok(same_kind_as(it, &t, items))
2055}
2056
2057pub(crate) fn arr_to_spliced(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2058    let mut items = generic_array_items(it, &t);
2059    let len = items.len() as i64;
2060    let start_raw = arg(a, 0).to_number() as i64;
2061    let start = if start_raw < 0 {
2062        (len + start_raw).max(0) as usize
2063    } else {
2064        start_raw.min(len) as usize
2065    };
2066    let delete_count = if a.len() < 2 {
2067        items.len() - start
2068    } else {
2069        let dc = arg(a, 1).to_number() as i64;
2070        dc.max(0).min((len - start as i64).max(0)) as usize
2071    };
2072    let insert: alloc::vec::Vec<Value> = a.iter().skip(2).cloned().collect();
2073    items.drain(start..start + delete_count);
2074    for (i, v) in insert.into_iter().enumerate() {
2075        items.insert(start + i, v);
2076    }
2077    Ok(same_kind_as(it, &t, items))
2078}
2079
2080/// `Array.prototype.with(index, value)`(ES2023)。範囲外 `index` は仕様上 `RangeError` だが、
2081/// 以前は黙って無視して元と同じ配列を返していた(呼び出し側が範囲外に気づけないバグ)。
2082pub(crate) fn arr_with(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2083    let mut items = generic_array_items(it, &t);
2084    let len = items.len() as i64;
2085    let idx_raw = arg(a, 0).to_number() as i64;
2086    let idx = if idx_raw < 0 { len + idx_raw } else { idx_raw };
2087    if idx < 0 || idx >= len {
2088        return Err(it.error("Invalid index"));
2089    }
2090    items[idx as usize] = arg(a, 1);
2091    Ok(same_kind_as(it, &t, items))
2092}
2093
2094/// 再帰的に `depth` 段だけネスト配列を展開する(`arr_flat` のヘルパ)。
2095/// `depth=Infinity`(`arr.flat(Infinity)`)は定番イディオムだが、`depth - 1.0`
2096/// は `Infinity` のままなので、循環参照する配列(`a.push(a)`)に対しては
2097/// 祖先追跡ガードが無いと無限再帰になり、この no_std 環境ではスタック
2098/// オーバーフロー(クラッシュ/ハング)に直結し得る(`Value::to_js_string`
2099/// で修正済みの「循環参照でスタックオーバーフロー」系と同種のバグ。
2100/// 2026-07-13 の横断監査で発見)。既に祖先に現れた配列を再訪した時点で、
2101/// それ以上展開せずそのまま(配列のまま)結果へ積んで打ち切る。
2102pub(crate) fn flatten_depth(items: Vec<Value>, depth: f64, out: &mut Vec<Value>, seen: &mut Vec<ObjRef>) {
2103    for v in items {
2104        match v {
2105            Value::Object(o) if depth > 0.0 && matches!(o.borrow().kind, ObjKind::Array(_)) => {
2106                if seen.iter().any(|s| Rc::ptr_eq(s, &o)) {
2107                    out.push(Value::Object(o));
2108                    continue;
2109                }
2110                seen.push(o.clone());
2111                let inner = match &o.borrow().kind {
2112                    ObjKind::Array(inner) => inner.clone(),
2113                    _ => Vec::new(),
2114                };
2115                flatten_depth(inner, depth - 1.0, out, seen);
2116                seen.pop();
2117            }
2118            other => out.push(other),
2119        }
2120    }
2121}
2122/// `Array.prototype.flat(depth = 1)`。以前は `depth` 引数を完全に無視して常に1段だけ
2123/// 展開する実装になっており、`arr.flat(2)` や、深いネスト解除の定番イディオムである
2124/// `arr.flat(Infinity)` が仕様どおりに動かない(1段しか展開されない)バグだった。
2125pub(crate) fn arr_flat(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2126    let items = generic_array_items(it, &t);
2127    let depth = if matches!(arg(a, 0), Value::Undefined) {
2128        1.0
2129    } else {
2130        arg(a, 0).to_number()
2131    };
2132    let mut out = Vec::new();
2133    flatten_depth(items, depth, &mut out, &mut Vec::new());
2134    Ok(Value::Object(Obj::array(out)))
2135}
2136/// `hasOwnProperty`/`Object.hasOwn` の実体。以前は `props.contains_key` だけを見ており、
2137/// (1) `accessors`(getter/setter 限定プロパティ)専用マップに存在するプロパティ
2138/// (`{get x(){}}.hasOwnProperty('x')`)と (2) 配列の数値インデックス/`length`
2139/// (`ObjKind::Array` 側にあり `props` には無い。`[1,2].hasOwnProperty(0)`)の
2140/// 2種類を「自身にあるのに無い」と誤判定するバグだった(`in`/`for...in` 等と同種)。
2141pub(crate) fn has_own_property(o: &super::super::value::ObjRef, key: &str) -> bool {
2142    let b = o.borrow();
2143    if let ObjKind::Array(items) = &b.kind {
2144        if key == "length" {
2145            return true;
2146        }
2147        if let Ok(idx) = key.parse::<usize>() {
2148            return idx < items.len();
2149        }
2150    }
2151    b.props.contains_key(key) || b.accessors.contains_key(key)
2152}
2153pub(crate) fn obj_has_own(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2154    let key = arg(a, 0).to_js_string();
2155    if let Value::Object(o) = &t {
2156        return Ok(Value::Bool(has_own_property(o, &key)));
2157    }
2158    Ok(Value::Bool(false))
2159}
2160/// `Object.prototype.toString.call(x)`(`{}.toString()` 含む)。以前は `this` の
2161/// 実際の種別を一切見ず常に `"[object Object]"` を返しており、`lodash` 等の
2162/// ライブラリで広く使われる型判定イディオム(`Object.prototype.toString.call(x)
2163/// === '[object Array]'` 等)が配列/Map/Set/Date/RegExp/関数のいずれに対しても
2164/// 常に `"[object Object]"` になってしまう、実用上かなり影響の大きいバグだった。
2165pub(crate) fn obj_to_string(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
2166    // `Symbol.toStringTag`(クラスで `get [Symbol.toStringTag](){ return 'Foo'; }` の
2167    // ように定義するカスタムタグ)が優先される仕様だが、以前は一切考慮していなかった。
2168    // プロトタイプ連鎖を辿ってデータプロパティ/アクセサ(getter)どちらの定義方法も見る。
2169    if let Value::Object(o) = &t {
2170        let mut cur = Some(o.clone());
2171        while let Some(c) = cur {
2172            let (own_tag, getter, next) = {
2173                let b = c.borrow();
2174                (
2175                    b.props.get("Symbol(Symbol.toStringTag)").cloned(),
2176                    b.accessors
2177                        .get("Symbol(Symbol.toStringTag)")
2178                        .and_then(|acc| acc.get.clone()),
2179                    b.proto.clone(),
2180                )
2181            };
2182            if let Some(v) = own_tag {
2183                return Ok(Value::str(format!("[object {}]", v.to_js_string())));
2184            }
2185            if let Some(g) = getter {
2186                let v = it.call_value(&g, t.clone(), &[])?;
2187                return Ok(Value::str(format!("[object {}]", v.to_js_string())));
2188            }
2189            cur = next;
2190        }
2191    }
2192    let tag = match &t {
2193        Value::Undefined => "Undefined",
2194        Value::Null => "Null",
2195        Value::Object(o) => {
2196            let b = o.borrow();
2197            if b.is_callable() {
2198                "Function"
2199            } else {
2200                match &b.kind {
2201                    ObjKind::Array(_) => "Array",
2202                    ObjKind::DateObj(_) => "Date",
2203                    ObjKind::RegExpObj(_) => "RegExp",
2204                    ObjKind::MapObj(_) => "Map",
2205                    ObjKind::SetObj(_) => "Set",
2206                    ObjKind::PromiseObj(_) => "Promise",
2207                    ObjKind::Generator(_) => "Generator",
2208                    _ => "Object",
2209                }
2210            }
2211        }
2212        // 【2026-09-05 バグ修正】プリミティブが全部 `[object Object]` になっていた。
2213        //
2214        // 仕様では種別ごとに `[object String]` / `[object Number]` /
2215        // `[object Boolean]` / `[object BigInt]` / `[object Symbol]` を返す。
2216        //
2217        // これは見た目の問題では済まない。jQuery 1.8.2 は
2218        // `class2type[Object.prototype.toString.call(x)]` で型を判定し、
2219        // `jQuery.extend(true, ...)` の深いマージで「素のオブジェクトなら
2220        // 再帰的にマージ」する。文字列が `[object Object]` を返すと
2221        // **文字列が `{}` に置き換わる**。実際に `s.type`("GET")が
2222        // 空オブジェクトになり、`s.type.toUpperCase()` で
2223        // `$.getJSON` が落ちていた。
2224        Value::Str(_) => "String",
2225        Value::Number(_) => "Number",
2226        Value::Bool(_) => "Boolean",
2227        Value::BigInt(_) => "BigInt",
2228    };
2229    Ok(Value::str(format!("[object {}]", tag)))
2230}
2231/// `Object.prototype.isPrototypeOf(obj)`: `this` が `obj` のプロトタイプ連鎖上に
2232/// 存在するか(`instanceof` の `r.prototype` 版に相当。こちらは呼び出し元自身と比較する)。
2233pub(crate) fn obj_is_prototype_of(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2234    let (Value::Object(target), Value::Object(candidate)) = (&t, &arg(a, 0)) else {
2235        return Ok(Value::Bool(false));
2236    };
2237    let mut cur = candidate.borrow().proto.clone();
2238    let mut guard = 0;
2239    while let Some(p) = cur {
2240        if Rc::ptr_eq(&p, target) {
2241            return Ok(Value::Bool(true));
2242        }
2243        cur = p.borrow().proto.clone();
2244        guard += 1;
2245        if guard > 1000 {
2246            break;
2247        }
2248    }
2249    Ok(Value::Bool(false))
2250}
2251/// `Object.prototype.propertyIsEnumerable(key)`: この処理系は独自プロパティに enumerable
2252/// フラグを持たないため、「自身の直接プロパティかどうか」で近似する(`hasOwnProperty` と
2253/// 同じ判定だが、プロトタイプ継承プロパティに対しては仕様通り false になる点は共通)。
2254pub(crate) fn obj_property_is_enumerable(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2255    obj_has_own(it, t, a)
2256}
2257
2258// ===== Function.prototype.call/apply/bind =====
2259// `Reflect.apply`/`Reflect.construct` は既にあったが、実用上はるかに一般的なこちらの
2260// インスタンスメソッド(`fn.call(this, ...)` 等)が長期間丸ごと欠落していた。
2261pub fn function_method(key: &str) -> Value {
2262    match key {
2263        "call" => nv("call", fn_call),
2264        "apply" => nv("apply", fn_apply),
2265        "bind" => nv("bind", fn_bind),
2266        _ => Value::Undefined,
2267    }
2268}
2269pub(crate) fn fn_call(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2270    let this_arg = arg(a, 0);
2271    let rest: Vec<Value> = a.get(1..).map(|s| s.to_vec()).unwrap_or_default();
2272    it.call_value(&t, this_arg, &rest)
2273}
2274// `Function.prototype.apply` の第2引数も `Reflect.apply` と同じく
2275// `CreateListFromArrayLike`(array-like 全般)を経由すべきだが、`&mut Interp` を
2276// 持たない `iterable_values` を使っており素の array-like(実配列ではない)を渡すと
2277// 引数が丸ごと消える同型のバグだった。
2278pub(crate) fn fn_apply(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2279    let this_arg = arg(a, 0);
2280    let args_vec = match arg(a, 1) {
2281        Value::Undefined | Value::Null => Vec::new(),
2282        v => array_like_items(it, &v),
2283    };
2284    it.call_value(&t, this_arg, &args_vec)
2285}
2286pub(crate) fn fn_bind(_it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2287    let bound_this = arg(a, 0);
2288    let bound_args: Vec<Value> = a.get(1..).map(|s| s.to_vec()).unwrap_or_default();
2289    Ok(Value::Object(Obj::bound(t, bound_this, bound_args)))
2290}
2291
2292// ============ 小ヘルパ ============
2293
2294pub(crate) fn norm_index(v: f64, len: i64, default: i64) -> i64 {
2295    if v.is_nan() {
2296        return default;
2297    }
2298    let i = libm::trunc(v) as i64;
2299    if i < 0 {
2300        (len + i).max(0)
2301    } else {
2302        i.min(len)
2303    }
2304}
2305pub(crate) fn clamp_idx(v: f64, len: usize) -> usize {
2306    if v.is_nan() || v < 0.0 {
2307        0
2308    } else {
2309        (v as usize).min(len)
2310    }
2311}
2312pub(crate) fn pad_str(s: &str, target: usize, pad: &str, start: bool) -> String {
2313    let cur = s.chars().count();
2314    if cur >= target || pad.is_empty() {
2315        return String::from(s);
2316    }
2317    let mut padding = String::new();
2318    let pad_chars: Vec<char> = pad.chars().collect();
2319    let mut i = 0;
2320    while padding.chars().count() < target - cur {
2321        padding.push(pad_chars[i % pad_chars.len()]);
2322        i += 1;
2323    }
2324    if start {
2325        format!("{}{}", padding, s)
2326    } else {
2327        format!("{}{}", s, padding)
2328    }
2329}
2330pub(crate) fn format_fixed(n: f64, digits: usize) -> String {
2331    let neg = n < 0.0;
2332    let n = libm::fabs(n);
2333    let int_part = libm::trunc(n) as i64;
2334    let scale = libm::pow(10.0, digits as f64);
2335    let frac = libm::trunc((n - libm::trunc(n)) * scale + 0.5) as i64;
2336    let frac_str = format!("{:0width$}", frac, width = digits);
2337    format!("{}{}.{}", if neg { "-" } else { "" }, int_part, frac_str)
2338}
2339