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    if is_typed_array(&t) {
1486        return Ok(same_kind_as(it, &t, out));
1487    }
1488    let a_obj = array_species_create(it, &t, out.len())?;
1489    populate_array_species(it, &a_obj, out)
1490}
1491/// `fromIndex`(第2引数)を正規化する。負の値は末尾からのオフセット、範囲外は
1492/// `len`(=以降の探索が即座に空になる)に丸める(`indexOf`/`includes` 共通)。
1493pub(crate) fn arr_from_index(a: &[Value], len: usize) -> usize {
1494    if matches!(arg(a, 1), Value::Undefined) {
1495        return 0;
1496    }
1497    let n = arg(a, 1).to_number();
1498    if n.is_nan() {
1499        return 0;
1500    }
1501    let len = len as i64;
1502    let idx = if n < 0.0 { (len + n as i64).max(0) } else { n as i64 };
1503    idx.min(len) as usize
1504}
1505/// `Array.prototype.indexOf(searchElement, fromIndex)`。以前は `fromIndex`
1506/// (第2引数)を完全に無視し、常に先頭から探索していた。
1507/// `indexOf`/`includes`/`forEach`/`map`/`filter` 等は仕様上ジェネリックメソッドだが、
1508/// 以前は `this_items` を直接使っていたため array-like への `.call()` が常に
1509/// 「何も見つからない/反復しない」結果になる同型のバグが複数箇所にあった
1510/// (`join`/`slice`/`concat`/`forEach` で修正済みの続き)。
1511pub(crate) fn arr_index_of(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1512    let items = generic_array_items(it, &t);
1513    let target = arg(a, 0);
1514    let from = arr_from_index(a, items.len());
1515    for (i, v) in items.iter().enumerate().skip(from) {
1516        if v.strict_eq(&target) {
1517            return Ok(Value::Number(i as f64));
1518        }
1519    }
1520    Ok(Value::Number(-1.0))
1521}
1522/// `Array.prototype.includes` は仕様上 SameValueZero で比較する必要がある
1523/// (`===` と違って `NaN` 同士は等しいと判定される)。以前は `strict_eq`(`===`)を
1524/// 使っており、`[NaN].includes(NaN)` が `false` になるバグだった(`indexOf` が `===` を
1525/// 使うのは仕様どおり正しいので、そちらは変更していない)。
1526pub(crate) fn arr_includes(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1527    let items = generic_array_items(it, &t);
1528    let target = arg(a, 0);
1529    let from = arr_from_index(a, items.len());
1530    Ok(Value::Bool(
1531        items.iter().skip(from).any(|v| same_value_zero(v, &target)),
1532    ))
1533}
1534/// `Array.prototype.join` は仕様上ジェネリックメソッド(`this` の内部 `[[Class]]` を
1535/// 見ず `length` + 添字プロパティだけを見る)だが、以前は `this_items`(`ObjKind::Array`
1536/// 以外は無条件で空 `Vec`)を直接使っていたため、
1537/// `Array.prototype.join.call({0:'a',1:'b',length:2}, '-')` のような array-like への
1538/// 適用が常に `""` を返す静かな破壊バグだった。
1539pub(crate) fn arr_join(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1540    let is_array = matches!(&t, Value::Object(o) if matches!(o.borrow().kind, ObjKind::Array(_)));
1541    let items = if is_array {
1542        this_items(&t)
1543    } else {
1544        array_like_items(it, &t)
1545    };
1546    let sep = if matches!(arg(a, 0), Value::Undefined) {
1547        String::from(",")
1548    } else {
1549        arg(a, 0).to_js_string()
1550    };
1551    let parts: Vec<String> = items
1552        .iter()
1553        .map(|v| match v {
1554            Value::Undefined | Value::Null => String::new(),
1555            _ => v.to_js_string(),
1556        })
1557        .collect();
1558    Ok(Value::str(parts.join(&sep)))
1559}
1560/// `this` が実配列(`ObjKind::Array`)かどうか。`join`/`slice`/`concat` に続き、
1561/// `forEach`/`map`/`filter`/`indexOf`/`includes` 等の仕様上ジェネリックなメソッドで
1562/// `this_items`(非配列は無条件で空 `Vec`)から `array_like_items`
1563/// (`length`+添字プロパティを読む汎用フォールバック)への切り替え判定に使う共通ヘルパ。
1564pub(crate) fn is_real_array(t: &Value) -> bool {
1565    matches!(t, Value::Object(o) if matches!(o.borrow().kind, ObjKind::Array(_)))
1566}
1567/// `this_items` と `array_like_items` を上記の判定で使い分ける便宜関数。
1568pub(crate) fn generic_array_items(it: &mut Interp, t: &Value) -> Vec<Value> {
1569    if is_real_array(t) {
1570        this_items(t)
1571    } else {
1572        array_like_items(it, t)
1573    }
1574}
1575/// 配列でない array-like オブジェクト(`length` + 添字プロパティ)を `Vec<Value>` に
1576/// 展開する。`Array.prototype.concat` が `Symbol.isConcatSpreadable` で true を明示した
1577/// 非配列オブジェクトを展開する際に使う。
1578pub(crate) fn array_like_items(it: &mut Interp, v: &Value) -> Vec<Value> {
1579    let len = it
1580        .get_property(v, "length")
1581        .map(|l| l.to_number())
1582        .unwrap_or(0.0);
1583    let len = if len.is_finite() && len > 0.0 { len as usize } else { 0 };
1584    let mut out = Vec::with_capacity(len);
1585    for i in 0..len {
1586        out.push(
1587            it.get_property(v, &i.to_string())
1588                .unwrap_or(Value::Undefined),
1589        );
1590    }
1591    out
1592}
1593
1594/// `Array.prototype.concat` は仕様上 `Symbol.isConcatSpreadable` を尊重すべきだが、
1595/// 以前は単に `ObjKind::Array` かどうかだけで展開の有無を決めており、
1596/// `arr[Symbol.isConcatSpreadable] = false` な配列も無条件に展開され、逆に
1597/// `Symbol.isConcatSpreadable = true` を明示した非配列 array-like オブジェクトは
1598/// 展開されず単一要素として push されてしまっていた。
1599/// `concat` の各オペランド(`this` 含む)を isConcatSpreadable 判定に従って
1600/// `items` に展開/push する共通ロジック。以前は `this`(第一引数の呼び出し対象)
1601/// にはこのロジックが適用されておらず `this_items` を直接使っていたため、
1602/// `[].concat.call(arrayLikeObj, x)` のような非配列 `this` が結果から丸ごと
1603/// 消えてしまう(本来は spreadable でなければ `this` 自体が単一要素として
1604/// 含まれるべき)静かな破壊バグだった。
1605pub(crate) fn concat_push(it: &mut Interp, v: &Value, items: &mut Vec<Value>) -> Result<(), Value> {
1606    match v {
1607        Value::Object(o) => {
1608            let flag = it.get_property(v, "Symbol(Symbol.isConcatSpreadable)")?;
1609            let is_array = matches!(o.borrow().kind, ObjKind::Array(_));
1610            let spreadable = if matches!(flag, Value::Undefined) {
1611                is_array
1612            } else {
1613                flag.truthy()
1614            };
1615            if !spreadable {
1616                items.push(v.clone());
1617            } else if is_array {
1618                if let ObjKind::Array(other) = &o.borrow().kind {
1619                    items.extend(other.clone());
1620                }
1621            } else {
1622                items.extend(array_like_items(it, v));
1623            }
1624        }
1625        other => items.push(other.clone()),
1626    }
1627    Ok(())
1628}
1629pub(crate) fn arr_concat(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1630    let mut items = Vec::new();
1631    concat_push(it, &t, &mut items)?;
1632    for v in a {
1633        concat_push(it, v, &mut items)?;
1634    }
1635    let a_obj = array_species_create(it, &t, 0)?;
1636    populate_array_species(it, &a_obj, items)
1637}
1638pub(crate) fn arr_reverse(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1639    if let Some(o) = this_objref(&t) {
1640        if let ObjKind::Array(items) = &mut o.borrow_mut().kind {
1641            items.reverse();
1642        }
1643    }
1644    Ok(t)
1645}
1646pub(crate) fn arr_to_string(_: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1647    Ok(Value::str(t.to_js_string()))
1648}
1649/// `Array.prototype.toLocaleString()`(ES3)が丸ごと欠落していた。各要素の
1650/// `toLocaleString()` を呼び出し `,` で連結する(`Intl` 非搭載のためロケール依存の書式化は
1651/// 行わず、要素が `toLocaleString` を持たなければ通常の文字列化にフォールバックする簡略実装。
1652/// `Date.prototype.toLocaleString` 等、要素側の実装があればそちらが優先される)。
1653pub(crate) fn arr_to_locale_string(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
1654    let items = generic_array_items(it, &t);
1655    let mut parts = Vec::with_capacity(items.len());
1656    for v in items {
1657        if matches!(v, Value::Undefined | Value::Null) {
1658            parts.push(String::new());
1659            continue;
1660        }
1661        let method = it.get_property(&v, "toLocaleString").unwrap_or(Value::Undefined);
1662        let s = if matches!(&method, Value::Object(f) if f.borrow().is_callable()) {
1663            it.call_value(&method, v.clone(), &[])?.to_js_string()
1664        } else {
1665            v.to_js_string()
1666        };
1667        parts.push(s);
1668    }
1669    Ok(Value::str(parts.join(",")))
1670}
1671/// 高階配列メソッドのコールバックは第2引数 `thisArg` を受け付ける(`map`/`filter`/
1672/// `forEach`/`find` 系すべてに共通の仕様だが、以前はどれも `Value::Undefined` を
1673/// 決め打ちしており `thisArg` が完全に無視されていた)。
1674pub(crate) fn arr_map(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1675    let items = generic_array_items(it, &t);
1676    let f = arg(a, 0);
1677    let this_arg = arg(a, 1);
1678    let mut out = Vec::with_capacity(items.len());
1679    for (i, v) in items.iter().enumerate() {
1680        let r = it.call_value(
1681            &f,
1682            this_arg.clone(),
1683            &[v.clone(), Value::Number(i as f64), t.clone()],
1684        )?;
1685        out.push(r);
1686    }
1687    if is_typed_array(&t) {
1688        return Ok(same_kind_as(it, &t, out));
1689    }
1690    let a_obj = array_species_create(it, &t, items.len())?;
1691    populate_array_species(it, &a_obj, out)
1692}
1693pub(crate) fn arr_filter(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1694    let items = generic_array_items(it, &t);
1695    let f = arg(a, 0);
1696    let this_arg = arg(a, 1);
1697    let mut out = Vec::new();
1698    for (i, v) in items.iter().enumerate() {
1699        if it
1700            .call_value(
1701                &f,
1702                this_arg.clone(),
1703                &[v.clone(), Value::Number(i as f64), t.clone()],
1704            )?
1705            .truthy()
1706        {
1707            out.push(v.clone());
1708        }
1709    }
1710    if is_typed_array(&t) {
1711        return Ok(same_kind_as(it, &t, out));
1712    }
1713    let a_obj = array_species_create(it, &t, 0)?;
1714    populate_array_species(it, &a_obj, out)
1715}
1716/// `Array.prototype.forEach` は仕様上ジェネリックメソッドだが、以前は `this_items`
1717/// (`ObjKind::Array` 以外は無条件で空 `Vec`)を直接使っていたため、`.call(arrayLikeObj,
1718/// fn)`(`arguments`/`NodeList` 等への定番イディオム)が何も反復しない静かな破壊
1719/// バグだった(`join`/`slice`/`concat` で修正済みの同型バグ)。
1720pub(crate) fn arr_for_each(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1721    let items = generic_array_items(it, &t);
1722    let f = arg(a, 0);
1723    let this_arg = arg(a, 1);
1724    for (i, v) in items.iter().enumerate() {
1725        it.call_value(
1726            &f,
1727            this_arg.clone(),
1728            &[v.clone(), Value::Number(i as f64), t.clone()],
1729        )?;
1730    }
1731    Ok(Value::Undefined)
1732}
1733/// `reduce`/`find`/`findIndex`/`some`/`every` にも `forEach`/`map`/`filter`/`indexOf`/
1734/// `includes` と同型のジェネリックメソッド未対応バグがあった(`this_items` を直接
1735/// 使っており array-like への `.call()` が常に「何もしない/見つからない」になっていた)。
1736pub(crate) fn arr_reduce(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1737    let items = generic_array_items(it, &t);
1738    let f = arg(a, 0);
1739    let mut idx = 0;
1740    let mut acc = if a.len() >= 2 {
1741        arg(a, 1)
1742    } else {
1743        if items.is_empty() {
1744            return Err(it.error("Reduce of empty array with no initial value"));
1745        }
1746        idx = 1;
1747        items[0].clone()
1748    };
1749    while idx < items.len() {
1750        acc = it.call_value(
1751            &f,
1752            Value::Undefined,
1753            &[
1754                acc,
1755                items[idx].clone(),
1756                Value::Number(idx as f64),
1757                t.clone(),
1758            ],
1759        )?;
1760        idx += 1;
1761    }
1762    Ok(acc)
1763}
1764pub(crate) fn arr_find(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1765    let items = generic_array_items(it, &t);
1766    let f = arg(a, 0);
1767    let this_arg = arg(a, 1);
1768    for (i, v) in items.iter().enumerate() {
1769        if it
1770            .call_value(
1771                &f,
1772                this_arg.clone(),
1773                &[v.clone(), Value::Number(i as f64), t.clone()],
1774            )?
1775            .truthy()
1776        {
1777            return Ok(v.clone());
1778        }
1779    }
1780    Ok(Value::Undefined)
1781}
1782pub(crate) fn arr_find_index(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1783    let items = generic_array_items(it, &t);
1784    let f = arg(a, 0);
1785    let this_arg = arg(a, 1);
1786    for (i, v) in items.iter().enumerate() {
1787        if it
1788            .call_value(
1789                &f,
1790                this_arg.clone(),
1791                &[v.clone(), Value::Number(i as f64), t.clone()],
1792            )?
1793            .truthy()
1794        {
1795            return Ok(Value::Number(i as f64));
1796        }
1797    }
1798    Ok(Value::Number(-1.0))
1799}
1800pub(crate) fn arr_some(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1801    let items = generic_array_items(it, &t);
1802    let f = arg(a, 0);
1803    let this_arg = arg(a, 1);
1804    for (i, v) in items.iter().enumerate() {
1805        if it
1806            .call_value(
1807                &f,
1808                this_arg.clone(),
1809                &[v.clone(), Value::Number(i as f64), t.clone()],
1810            )?
1811            .truthy()
1812        {
1813            return Ok(Value::Bool(true));
1814        }
1815    }
1816    Ok(Value::Bool(false))
1817}
1818pub(crate) fn arr_every(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1819    let items = generic_array_items(it, &t);
1820    let f = arg(a, 0);
1821    let this_arg = arg(a, 1);
1822    for (i, v) in items.iter().enumerate() {
1823        if !it
1824            .call_value(
1825                &f,
1826                this_arg.clone(),
1827                &[v.clone(), Value::Number(i as f64), t.clone()],
1828            )?
1829            .truthy()
1830        {
1831            return Ok(Value::Bool(false));
1832        }
1833    }
1834    Ok(Value::Bool(true))
1835}
1836/// 仕様上 `sort`/`toSorted` は `undefined` 要素を比較関数に一切渡さず、常に配列末尾に
1837/// 送る特別扱いが必要(ECMA-262 `SortCompare`)。以前はこの特別扱いが無く `undefined` も
1838/// 通常の要素として文字列比較/比較関数に渡していたため、例えば
1839/// `[undefined, 5].sort((a,b)=>a-b)` は `a-b` が `NaN` になり順序が保証されない
1840/// (常に末尾に来るはずが来ない)バグがあった。
1841pub(crate) fn insertion_sort_excluding_undefined(
1842    it: &mut Interp,
1843    mut items: alloc::vec::Vec<Value>,
1844    cmp: &Value,
1845) -> Result<alloc::vec::Vec<Value>, Value> {
1846    let undefined_count = items.iter().filter(|v| matches!(v, Value::Undefined)).count();
1847    items.retain(|v| !matches!(v, Value::Undefined));
1848    let n = items.len();
1849    for i in 1..n {
1850        let mut j = i;
1851        while j > 0 {
1852            let ord = if matches!(cmp, Value::Undefined) {
1853                items[j - 1].to_js_string().cmp(&items[j].to_js_string())
1854            } else {
1855                let r = it
1856                    .call_value(
1857                        cmp,
1858                        Value::Undefined,
1859                        &[items[j - 1].clone(), items[j].clone()],
1860                    )?
1861                    .to_number();
1862                if r > 0.0 {
1863                    core::cmp::Ordering::Greater
1864                } else if r < 0.0 {
1865                    core::cmp::Ordering::Less
1866                } else {
1867                    core::cmp::Ordering::Equal
1868                }
1869            };
1870            if ord == core::cmp::Ordering::Greater {
1871                items.swap(j - 1, j);
1872                j -= 1;
1873            } else {
1874                break;
1875            }
1876        }
1877    }
1878    for _ in 0..undefined_count {
1879        items.push(Value::Undefined);
1880    }
1881    Ok(items)
1882}
1883pub(crate) fn arr_sort(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1884    let items = this_items(&t);
1885    let cmp = arg(a, 0);
1886    let items = insertion_sort_excluding_undefined(it, items, &cmp)?;
1887    if let Some(o) = this_objref(&t) {
1888        if let ObjKind::Array(slot) = &mut o.borrow_mut().kind {
1889            *slot = items;
1890        }
1891    }
1892    Ok(t)
1893}
1894/// `Array.prototype.fill(value, start, end)`。以前は `start`/`end`(第2/第3引数)を
1895/// 完全に無視して常に配列全体を上書きしており、`[1,2,3,4,5].fill(0,1,3)` のような
1896/// 部分埋めの定番パターンが機能していなかった。
1897pub(crate) fn arr_fill(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1898    if let Some(o) = this_objref(&t) {
1899        if let ObjKind::Array(items) = &mut o.borrow_mut().kind {
1900            let v = arg(a, 0);
1901            let len = items.len() as i64;
1902            let start = norm_index(arg(a, 1).to_number(), len, 0).max(0) as usize;
1903            let end = if matches!(arg(a, 2), Value::Undefined) {
1904                len
1905            } else {
1906                norm_index(arg(a, 2).to_number(), len, len)
1907            }
1908            .max(0) as usize;
1909            let end = end.min(items.len());
1910            for slot in items.iter_mut().take(end).skip(start) {
1911                *slot = v.clone();
1912            }
1913        }
1914    }
1915    Ok(t)
1916}
1917pub(crate) fn arr_splice(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1918    let o = match this_objref(&t) {
1919        Some(o) => o,
1920        None => return Ok(Value::Object(Obj::array(alloc::vec![]))),
1921    };
1922    let mut items = if let ObjKind::Array(v) = &o.borrow().kind {
1923        v.clone()
1924    } else {
1925        return Ok(Value::Object(Obj::array(alloc::vec![])));
1926    };
1927    let len = items.len() as i64;
1928    let start_raw = arg(a, 0).to_number() as i64;
1929    let start = if start_raw < 0 {
1930        (len + start_raw).max(0) as usize
1931    } else {
1932        start_raw.min(len) as usize
1933    };
1934    let delete_count = if a.len() < 2 {
1935        items.len() - start
1936    } else {
1937        let dc = arg(a, 1).to_number() as i64;
1938        dc.max(0).min((len - start as i64).max(0)) as usize
1939    };
1940    let insert: alloc::vec::Vec<Value> = a.iter().skip(2).cloned().collect();
1941    let removed: alloc::vec::Vec<Value> = items.drain(start..start + delete_count).collect();
1942    for (i, v) in insert.into_iter().enumerate() {
1943        items.insert(start + i, v);
1944    }
1945    if let ObjKind::Array(slot) = &mut o.borrow_mut().kind {
1946        *slot = items;
1947    }
1948    let a_obj = array_species_create(it, &t, removed.len())?;
1949    populate_array_species(it, &a_obj, removed)
1950}
1951
1952/// `Array.prototype.lastIndexOf(searchElement, fromIndex)`。同じ `fromIndex` 無視バグが
1953/// こちらにもあった(`indexOf`/`includes` と同じ調査で発見)。既定は末尾(`length-1`)から、
1954/// 負の値は末尾からのオフセットとして解釈し、その位置から先頭へ向かって探索する。
1955/// `Array.prototype.lastIndexOf(target, fromIndex)`。仕様の `ToIntegerOrInfinity` は
1956/// `NaN` を `0` として扱うため、`fromIndex` に `NaN` を渡した場合は「index 0 のみを見る」
1957/// (`fromIndex=0` と同じ)挙動になるはずだが、以前は `NaN` を検知すると無条件に `-1` を
1958/// 返しており、`[5,1,2].lastIndexOf(5, NaN)` が本来の `0` ではなく `-1` になっていた。
1959pub(crate) fn arr_last_index_of(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1960    let items = generic_array_items(it, &t);
1961    let target = arg(a, 0);
1962    let len = items.len() as i64;
1963    if len == 0 {
1964        return Ok(Value::Number(-1.0));
1965    }
1966    let start = if matches!(arg(a, 1), Value::Undefined) {
1967        len - 1
1968    } else {
1969        let n = arg(a, 1).to_number();
1970        let n = if n.is_nan() { 0.0 } else { n };
1971        if n < 0.0 {
1972            len + n as i64
1973        } else {
1974            (n as i64).min(len - 1)
1975        }
1976    };
1977    if start < 0 {
1978        return Ok(Value::Number(-1.0));
1979    }
1980    for i in (0..=start as usize).rev() {
1981        if items[i].strict_eq(&target) {
1982            return Ok(Value::Number(i as f64));
1983        }
1984    }
1985    Ok(Value::Number(-1.0))
1986}
1987
1988pub(crate) fn arr_copy_within(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
1989    let o = match this_objref(&t) {
1990        Some(o) => o,
1991        None => return Ok(t),
1992    };
1993    let mut items = if let ObjKind::Array(v) = &o.borrow().kind {
1994        v.clone()
1995    } else {
1996        return Ok(t);
1997    };
1998    let len = items.len() as i64;
1999    let to_raw = arg(a, 0).to_number() as i64;
2000    let to = if to_raw < 0 {
2001        (len + to_raw).max(0) as usize
2002    } else {
2003        to_raw.min(len) as usize
2004    };
2005    let from_raw = if matches!(arg(a, 1), Value::Undefined) { 0i64 } else { arg(a, 1).to_number() as i64 };
2006    let from = if from_raw < 0 {
2007        (len + from_raw).max(0) as usize
2008    } else {
2009        from_raw.min(len) as usize
2010    };
2011    let end_raw = if matches!(arg(a, 2), Value::Undefined) { len } else { arg(a, 2).to_number() as i64 };
2012    let end = if end_raw < 0 {
2013        (len + end_raw).max(0) as usize
2014    } else {
2015        end_raw.min(len) as usize
2016    };
2017    let count = if end > from { (end - from).min(len as usize - to) } else { 0 };
2018    let src: alloc::vec::Vec<Value> = items[from..from + count].to_vec();
2019    for (i, v) in src.into_iter().enumerate() {
2020        if to + i < items.len() {
2021            items[to + i] = v;
2022        }
2023    }
2024    if let ObjKind::Array(slot) = &mut o.borrow_mut().kind {
2025        *slot = items;
2026    }
2027    Ok(t)
2028}
2029
2030/// `Array.prototype.toSorted/toReversed/toSpliced/with`(ES2023)は %TypedArray%
2031/// にも同名メソッドが仕様化されているが、この処理系ではどれも `this` が TypedArray
2032/// (`ObjKind::Array` を流用し `_ta_kind` タグで区別)かどうかを見ずに常に素の
2033/// `Obj::array(items)` を返しており、`new Int8Array([3,1,2]).toSorted()` が
2034/// `Int8Array` ではなく普通の配列に化けるバグだった(`structuredClone(TypedArray)`
2035/// と同種)。`this` の `_ta_kind` を引き継いで返す共通ヘルパーで対応する。
2036pub(crate) fn is_typed_array(t: &Value) -> bool {
2037    if let Value::Object(o) = t {
2038        o.borrow().props.contains_key("_ta_kind")
2039    } else {
2040        false
2041    }
2042}
2043
2044/// `ArraySpeciesCreate(originalArray, length)` (ECMAScript 9.4.2.3)。
2045///
2046/// `map`/`filter`/`slice`/`splice`/`concat`/`flatMap` 等の派生配列生成で、
2047/// `this.constructor[Symbol.species]` を参照して結果オブジェクトを槃築する。
2048pub(crate) fn array_species_create(
2049    it: &mut Interp,
2050    original: &Value,
2051    length: usize,
2052) -> Result<Value, Value> {
2053    let is_array = match original {
2054        Value::Object(o) => matches!(o.borrow().kind, ObjKind::Array(_)),
2055        _ => false,
2056    };
2057    if !is_array {
2058        return Ok(Value::Object(Obj::array(alloc::vec::Vec::with_capacity(length))));
2059    }
2060
2061    let ctor = match it.get_property(original, "constructor") {
2062        Ok(v) => v,
2063        Err(_) => return Ok(Value::Object(Obj::array(alloc::vec::Vec::with_capacity(length)))),
2064    };
2065
2066    let ctor_is_obj = matches!(ctor, Value::Object(_));
2067    if !ctor_is_obj {
2068        return Ok(Value::Object(Obj::array(alloc::vec::Vec::with_capacity(length))));
2069    }
2070
2071    // @@species を取得
2072    let species_val = match it.get_property(&ctor, "Symbol(Symbol.species)") {
2073        Ok(v) => v,
2074        Err(_) => ctor.clone(),
2075    };
2076
2077    if matches!(species_val, Value::Null | Value::Undefined) {
2078        return Ok(Value::Object(Obj::array(alloc::vec::Vec::with_capacity(length))));
2079    }
2080
2081    // もし標準の Array コンストラクタなら高速路
2082    if let Value::Object(ref so) = species_val {
2083        if let Some(Value::Object(ref ao)) = it.global.borrow().vars.get("Array") {
2084            if core::ptr::eq(so.as_ref(), ao.as_ref()) {
2085                return Ok(Value::Object(Obj::array(alloc::vec::Vec::with_capacity(length))));
2086            }
2087        }
2088        if so.borrow().is_callable() {
2089            return it.construct_value(&species_val, &[Value::Number(length as f64)]);
2090        }
2091    }
2092
2093    Err(it.type_error("Symbol.species is not a constructor"))
2094}
2095
2096pub(crate) fn populate_array_species(
2097    it: &mut Interp,
2098    target: &Value,
2099    items: alloc::vec::Vec<Value>,
2100) -> Result<Value, Value> {
2101    if let Value::Object(o) = target {
2102        let is_plain_array = matches!(o.borrow().kind, ObjKind::Array(_));
2103        if is_plain_array {
2104            let len = items.len();
2105            o.borrow_mut().kind = ObjKind::Array(items);
2106            o.borrow_mut().props.insert("length".into(), Value::Number(len as f64));
2107            return Ok(target.clone());
2108        }
2109    }
2110    let len = items.len();
2111    for (i, v) in items.into_iter().enumerate() {
2112        let _ = it.set_property(target, &i.to_string(), v);
2113    }
2114    let _ = it.set_property(target, "length", Value::Number(len as f64));
2115    Ok(target.clone())
2116}
2117
2118pub(crate) fn same_kind_as(it: &mut Interp, t: &Value, items: alloc::vec::Vec<Value>) -> Value {
2119    let ta_kind_tag = if let Value::Object(o) = t {
2120        o.borrow().props.get("_ta_kind").map(|v| v.to_js_string())
2121    } else {
2122        None
2123    };
2124    match ta_kind_tag {
2125        Some(tag) => {
2126            // `with()` の第2引数のような、まだ型変換(ラップ/クランプ)を経ていない
2127            // 生の値が混ざっている場合があるため、コンストラクタ/`set`/索引代入と
2128            // 同じ `TaKind::convert()` を通す(既に有効域内の値には無害)。
2129            let kind = TaKind::from_name(&tag);
2130            let values: alloc::vec::Vec<f64> = items.iter().map(|v| kind.convert(v)).collect();
2131            make_typed_array(it, values, kind)
2132        }
2133        None => Value::Object(Obj::array(items)),
2134    }
2135}
2136pub(crate) fn arr_to_sorted(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2137    let items = generic_array_items(it, &t);
2138    let cmp = arg(a, 0);
2139    let items = insertion_sort_excluding_undefined(it, items, &cmp)?;
2140    Ok(same_kind_as(it, &t, items))
2141}
2142
2143pub(crate) fn arr_to_reversed(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
2144    let mut items = generic_array_items(it, &t);
2145    items.reverse();
2146    Ok(same_kind_as(it, &t, items))
2147}
2148
2149pub(crate) fn arr_to_spliced(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2150    let mut items = generic_array_items(it, &t);
2151    let len = items.len() as i64;
2152    let start_raw = arg(a, 0).to_number() as i64;
2153    let start = if start_raw < 0 {
2154        (len + start_raw).max(0) as usize
2155    } else {
2156        start_raw.min(len) as usize
2157    };
2158    let delete_count = if a.len() < 2 {
2159        items.len() - start
2160    } else {
2161        let dc = arg(a, 1).to_number() as i64;
2162        dc.max(0).min((len - start as i64).max(0)) as usize
2163    };
2164    let insert: alloc::vec::Vec<Value> = a.iter().skip(2).cloned().collect();
2165    items.drain(start..start + delete_count);
2166    for (i, v) in insert.into_iter().enumerate() {
2167        items.insert(start + i, v);
2168    }
2169    Ok(same_kind_as(it, &t, items))
2170}
2171
2172/// `Array.prototype.with(index, value)`(ES2023)。範囲外 `index` は仕様上 `RangeError` だが、
2173/// 以前は黙って無視して元と同じ配列を返していた(呼び出し側が範囲外に気づけないバグ)。
2174pub(crate) fn arr_with(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2175    let mut items = generic_array_items(it, &t);
2176    let len = items.len() as i64;
2177    let idx_raw = arg(a, 0).to_number() as i64;
2178    let idx = if idx_raw < 0 { len + idx_raw } else { idx_raw };
2179    if idx < 0 || idx >= len {
2180        return Err(it.error("Invalid index"));
2181    }
2182    items[idx as usize] = arg(a, 1);
2183    Ok(same_kind_as(it, &t, items))
2184}
2185
2186/// 再帰的に `depth` 段だけネスト配列を展開する(`arr_flat` のヘルパ)。
2187/// `depth=Infinity`(`arr.flat(Infinity)`)は定番イディオムだが、`depth - 1.0`
2188/// は `Infinity` のままなので、循環参照する配列(`a.push(a)`)に対しては
2189/// 祖先追跡ガードが無いと無限再帰になり、この no_std 環境ではスタック
2190/// オーバーフロー(クラッシュ/ハング)に直結し得る(`Value::to_js_string`
2191/// で修正済みの「循環参照でスタックオーバーフロー」系と同種のバグ。
2192/// 2026-07-13 の横断監査で発見)。既に祖先に現れた配列を再訪した時点で、
2193/// それ以上展開せずそのまま(配列のまま)結果へ積んで打ち切る。
2194pub(crate) fn flatten_depth(items: Vec<Value>, depth: f64, out: &mut Vec<Value>, seen: &mut Vec<ObjRef>) {
2195    for v in items {
2196        match v {
2197            Value::Object(o) if depth > 0.0 && matches!(o.borrow().kind, ObjKind::Array(_)) => {
2198                if seen.iter().any(|s| Rc::ptr_eq(s, &o)) {
2199                    out.push(Value::Object(o));
2200                    continue;
2201                }
2202                seen.push(o.clone());
2203                let inner = match &o.borrow().kind {
2204                    ObjKind::Array(inner) => inner.clone(),
2205                    _ => Vec::new(),
2206                };
2207                flatten_depth(inner, depth - 1.0, out, seen);
2208                seen.pop();
2209            }
2210            other => out.push(other),
2211        }
2212    }
2213}
2214/// `Array.prototype.flat(depth = 1)`。以前は `depth` 引数を完全に無視して常に1段だけ
2215/// 展開する実装になっており、`arr.flat(2)` や、深いネスト解除の定番イディオムである
2216/// `arr.flat(Infinity)` が仕様どおりに動かない(1段しか展開されない)バグだった。
2217pub(crate) fn arr_flat(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2218    let items = generic_array_items(it, &t);
2219    let depth = if matches!(arg(a, 0), Value::Undefined) {
2220        1.0
2221    } else {
2222        arg(a, 0).to_number()
2223    };
2224    let mut out = Vec::new();
2225    flatten_depth(items, depth, &mut out, &mut Vec::new());
2226    Ok(Value::Object(Obj::array(out)))
2227}
2228/// `hasOwnProperty`/`Object.hasOwn` の実体。以前は `props.contains_key` だけを見ており、
2229/// (1) `accessors`(getter/setter 限定プロパティ)専用マップに存在するプロパティ
2230/// (`{get x(){}}.hasOwnProperty('x')`)と (2) 配列の数値インデックス/`length`
2231/// (`ObjKind::Array` 側にあり `props` には無い。`[1,2].hasOwnProperty(0)`)の
2232/// 2種類を「自身にあるのに無い」と誤判定するバグだった(`in`/`for...in` 等と同種)。
2233pub(crate) fn has_own_property(o: &super::super::value::ObjRef, key: &str) -> bool {
2234    let b = o.borrow();
2235    if let ObjKind::Array(items) = &b.kind {
2236        if key == "length" {
2237            return true;
2238        }
2239        if let Ok(idx) = key.parse::<usize>() {
2240            return idx < items.len();
2241        }
2242    }
2243    b.props.contains_key(key) || b.accessors.contains_key(key)
2244}
2245pub(crate) fn obj_has_own(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2246    let key = arg(a, 0).to_js_string();
2247    if let Value::Object(o) = &t {
2248        return Ok(Value::Bool(has_own_property(o, &key)));
2249    }
2250    Ok(Value::Bool(false))
2251}
2252/// `Object.prototype.toString.call(x)`(`{}.toString()` 含む)。以前は `this` の
2253/// 実際の種別を一切見ず常に `"[object Object]"` を返しており、`lodash` 等の
2254/// ライブラリで広く使われる型判定イディオム(`Object.prototype.toString.call(x)
2255/// === '[object Array]'` 等)が配列/Map/Set/Date/RegExp/関数のいずれに対しても
2256/// 常に `"[object Object]"` になってしまう、実用上かなり影響の大きいバグだった。
2257pub(crate) fn obj_to_string(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
2258    // `Symbol.toStringTag`(クラスで `get [Symbol.toStringTag](){ return 'Foo'; }` の
2259    // ように定義するカスタムタグ)が優先される仕様だが、以前は一切考慮していなかった。
2260    // プロトタイプ連鎖を辿ってデータプロパティ/アクセサ(getter)どちらの定義方法も見る。
2261    if let Value::Object(o) = &t {
2262        let mut cur = Some(o.clone());
2263        while let Some(c) = cur {
2264            let (own_tag, getter, next) = {
2265                let b = c.borrow();
2266                (
2267                    b.props.get("Symbol(Symbol.toStringTag)").cloned(),
2268                    b.accessors
2269                        .get("Symbol(Symbol.toStringTag)")
2270                        .and_then(|acc| acc.get.clone()),
2271                    b.proto.clone(),
2272                )
2273            };
2274            if let Some(v) = own_tag {
2275                return Ok(Value::str(format!("[object {}]", v.to_js_string())));
2276            }
2277            if let Some(g) = getter {
2278                let v = it.call_value(&g, t.clone(), &[])?;
2279                return Ok(Value::str(format!("[object {}]", v.to_js_string())));
2280            }
2281            cur = next;
2282        }
2283    }
2284    let tag = match &t {
2285        Value::Undefined => "Undefined",
2286        Value::Null => "Null",
2287        Value::Object(o) => {
2288            let b = o.borrow();
2289            if b.is_callable() {
2290                "Function"
2291            } else {
2292                match &b.kind {
2293                    ObjKind::Array(_) => "Array",
2294                    ObjKind::DateObj(_) => "Date",
2295                    ObjKind::RegExpObj(_) => "RegExp",
2296                    ObjKind::MapObj(_) => "Map",
2297                    ObjKind::SetObj(_) => "Set",
2298                    ObjKind::PromiseObj(_) => "Promise",
2299                    ObjKind::Generator(_) => "Generator",
2300                    _ => "Object",
2301                }
2302            }
2303        }
2304        // 【2026-09-05 バグ修正】プリミティブが全部 `[object Object]` になっていた。
2305        //
2306        // 仕様では種別ごとに `[object String]` / `[object Number]` /
2307        // `[object Boolean]` / `[object BigInt]` / `[object Symbol]` を返す。
2308        //
2309        // これは見た目の問題では済まない。jQuery 1.8.2 は
2310        // `class2type[Object.prototype.toString.call(x)]` で型を判定し、
2311        // `jQuery.extend(true, ...)` の深いマージで「素のオブジェクトなら
2312        // 再帰的にマージ」する。文字列が `[object Object]` を返すと
2313        // **文字列が `{}` に置き換わる**。実際に `s.type`("GET")が
2314        // 空オブジェクトになり、`s.type.toUpperCase()` で
2315        // `$.getJSON` が落ちていた。
2316        Value::Str(_) => "String",
2317        Value::Number(_) => "Number",
2318        Value::Bool(_) => "Boolean",
2319        Value::BigInt(_) => "BigInt",
2320    };
2321    Ok(Value::str(format!("[object {}]", tag)))
2322}
2323/// `Object.prototype.isPrototypeOf(obj)`: `this` が `obj` のプロトタイプ連鎖上に
2324/// 存在するか(`instanceof` の `r.prototype` 版に相当。こちらは呼び出し元自身と比較する)。
2325pub(crate) fn obj_is_prototype_of(_: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2326    let (Value::Object(target), Value::Object(candidate)) = (&t, &arg(a, 0)) else {
2327        return Ok(Value::Bool(false));
2328    };
2329    let mut cur = candidate.borrow().proto.clone();
2330    let mut guard = 0;
2331    while let Some(p) = cur {
2332        if Rc::ptr_eq(&p, target) {
2333            return Ok(Value::Bool(true));
2334        }
2335        cur = p.borrow().proto.clone();
2336        guard += 1;
2337        if guard > 1000 {
2338            break;
2339        }
2340    }
2341    Ok(Value::Bool(false))
2342}
2343/// `Object.prototype.propertyIsEnumerable(key)`: この処理系は独自プロパティに enumerable
2344/// フラグを持たないため、「自身の直接プロパティかどうか」で近似する(`hasOwnProperty` と
2345/// 同じ判定だが、プロトタイプ継承プロパティに対しては仕様通り false になる点は共通)。
2346pub(crate) fn obj_property_is_enumerable(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2347    obj_has_own(it, t, a)
2348}
2349
2350// ===== Function.prototype.call/apply/bind =====
2351// `Reflect.apply`/`Reflect.construct` は既にあったが、実用上はるかに一般的なこちらの
2352// インスタンスメソッド(`fn.call(this, ...)` 等)が長期間丸ごと欠落していた。
2353pub fn function_method(key: &str) -> Value {
2354    match key {
2355        "call" => nv("call", fn_call),
2356        "apply" => nv("apply", fn_apply),
2357        "bind" => nv("bind", fn_bind),
2358        _ => Value::Undefined,
2359    }
2360}
2361/// `Function.prototype.toString`: `this`(関数)のソース表現/`[native code]` を返す。
2362/// 関数でなければ TypeError(仕様どおり)。
2363pub(crate) fn fn_to_string(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
2364    match &t {
2365        Value::Object(o) if o.borrow().is_callable() => Ok(Value::str(t.to_js_string())),
2366        _ => Err(it.type_error("Function.prototype.toString requires that 'this' be a Function")),
2367    }
2368}
2369pub(crate) fn fn_call(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2370    let this_arg = arg(a, 0);
2371    let rest: Vec<Value> = a.get(1..).map(|s| s.to_vec()).unwrap_or_default();
2372    it.call_value(&t, this_arg, &rest)
2373}
2374// `Function.prototype.apply` の第2引数も `Reflect.apply` と同じく
2375// `CreateListFromArrayLike`(array-like 全般)を経由すべきだが、`&mut Interp` を
2376// 持たない `iterable_values` を使っており素の array-like(実配列ではない)を渡すと
2377// 引数が丸ごと消える同型のバグだった。
2378pub(crate) fn fn_apply(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2379    let this_arg = arg(a, 0);
2380    let args_vec = match arg(a, 1) {
2381        Value::Undefined | Value::Null => Vec::new(),
2382        v => array_like_items(it, &v),
2383    };
2384    it.call_value(&t, this_arg, &args_vec)
2385}
2386pub(crate) fn fn_bind(_it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
2387    let bound_this = arg(a, 0);
2388    let bound_args: Vec<Value> = a.get(1..).map(|s| s.to_vec()).unwrap_or_default();
2389    Ok(Value::Object(Obj::bound(t, bound_this, bound_args)))
2390}
2391
2392// ============ 小ヘルパ ============
2393
2394pub(crate) fn norm_index(v: f64, len: i64, default: i64) -> i64 {
2395    if v.is_nan() {
2396        return default;
2397    }
2398    let i = libm::trunc(v) as i64;
2399    if i < 0 {
2400        (len + i).max(0)
2401    } else {
2402        i.min(len)
2403    }
2404}
2405pub(crate) fn clamp_idx(v: f64, len: usize) -> usize {
2406    if v.is_nan() || v < 0.0 {
2407        0
2408    } else {
2409        (v as usize).min(len)
2410    }
2411}
2412pub(crate) fn pad_str(s: &str, target: usize, pad: &str, start: bool) -> String {
2413    let cur = s.chars().count();
2414    if cur >= target || pad.is_empty() {
2415        return String::from(s);
2416    }
2417    let mut padding = String::new();
2418    let pad_chars: Vec<char> = pad.chars().collect();
2419    let mut i = 0;
2420    while padding.chars().count() < target - cur {
2421        padding.push(pad_chars[i % pad_chars.len()]);
2422        i += 1;
2423    }
2424    if start {
2425        format!("{}{}", padding, s)
2426    } else {
2427        format!("{}{}", s, padding)
2428    }
2429}
2430pub(crate) fn format_fixed(n: f64, digits: usize) -> String {
2431    let neg = n < 0.0;
2432    let n = libm::fabs(n);
2433    let int_part = libm::trunc(n) as i64;
2434    let scale = libm::pow(10.0, digits as f64);
2435    let frac = libm::trunc((n - libm::trunc(n)) * scale + 0.5) as i64;
2436    let frac_str = format!("{:0width$}", frac, width = digits);
2437    format!("{}{}.{}", if neg { "-" } else { "" }, int_part, frac_str)
2438}
2439