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