Skip to main content

atmos/os_lib/js/
value.rs

1//! JS ランタイム値の表現。
2//!
3//! ヒープオブジェクトは `Rc<RefCell<Obj>>` で表現する(参照カウントGC)。
4//! 循環参照はリークするが、ページ遷移ごとに `JsRuntime` を破棄するため許容する。
5
6use alloc::boxed::Box;
7use indexmap::IndexMap;
8use alloc::format;
9use alloc::rc::Rc;
10use alloc::string::String;
11use alloc::vec::Vec;
12use core::cell::RefCell;
13
14use super::ast::Param;
15use super::bigint::BigInt;
16use super::interp::{Interp, Scope};
17
18/// FNV-1a ハッシャー。`IndexMap`(`props`の列挙順序保持のため`BTreeMap`から
19/// 移行。ES仕様の`OrdinaryOwnPropertyKeys`が要求する挿入順保持のため)の
20/// デフォルトハッシャー`RandomState`は`std`のOS乱数取得に依存しており、
21/// `default-features = false`にした`no_std`ビルドでは提供されない
22/// (`IndexMap::new()`が使えずビルド不能になっていた)。プロパティ名の
23/// ハッシュ化にセキュリティ上のDoS耐性(乱数シードによるハッシュ衝突
24/// 攻撃対策)は不要なため、決定的な軽量ハッシュ関数で足りる。
25#[derive(Default)]
26pub struct FnvHasher(u64);
27impl core::hash::Hasher for FnvHasher {
28    fn finish(&self) -> u64 {
29        self.0
30    }
31    fn write(&mut self, bytes: &[u8]) {
32        const FNV_OFFSET: u64 = 0xcbf29ce484222325;
33        const FNV_PRIME: u64 = 0x100000001b3;
34        let mut hash = if self.0 == 0 { FNV_OFFSET } else { self.0 };
35        for &b in bytes {
36            hash ^= b as u64;
37            hash = hash.wrapping_mul(FNV_PRIME);
38        }
39        self.0 = hash;
40    }
41}
42/// `Obj::props`/`.accessors`/`.attrs`で使う`IndexMap`の型省略用エイリアス。
43pub type PropMap<V> = IndexMap<String, V, core::hash::BuildHasherDefault<FnvHasher>>;
44
45/// 空のうちは確保しない `PropMap`(8 バイト)。
46///
47/// 【2026-10-19】`Obj` は `props`/`accessors`/`attrs` の 3 つの `IndexMap`(各 56 バイト)を
48/// 持ち 256 バイトあった。getter/setter と属性はほとんどのオブジェクトで空なので、
49/// 中身ができたときだけ確保する。YouTube の kevlar_base(10.8MB)の実行で
50/// 512MB のヒープを使い切っていた。
51pub struct LazyPropMap<V>(Option<Box<PropMap<V>>>);
52
53impl<V> Default for LazyPropMap<V> {
54    fn default() -> Self {
55        LazyPropMap(None)
56    }
57}
58
59impl<V: Clone> Clone for LazyPropMap<V> {
60    fn clone(&self) -> Self {
61        LazyPropMap(self.0.clone())
62    }
63}
64
65impl<V> LazyPropMap<V> {
66    fn map_mut(&mut self) -> &mut PropMap<V> {
67        self.0.get_or_insert_with(|| Box::new(PropMap::default()))
68    }
69    pub fn get(&self, k: &str) -> Option<&V> {
70        self.0.as_ref()?.get(k)
71    }
72    pub fn get_mut(&mut self, k: &str) -> Option<&mut V> {
73        self.0.as_mut()?.get_mut(k)
74    }
75    pub fn contains_key(&self, k: &str) -> bool {
76        self.0.as_ref().is_some_and(|m| m.contains_key(k))
77    }
78    pub fn insert(&mut self, k: String, v: V) -> Option<V> {
79        self.map_mut().insert(k, v)
80    }
81    pub fn entry(&mut self, k: String) -> indexmap::map::Entry<'_, String, V> {
82        self.map_mut().entry(k)
83    }
84    /// `IndexMap::remove` と同じ(末尾と入れ替えて取り除く)。
85    pub fn remove(&mut self, k: &str) -> Option<V> {
86        self.0.as_mut()?.swap_remove(k)
87    }
88    pub fn shift_remove(&mut self, k: &str) -> Option<V> {
89        self.0.as_mut()?.shift_remove(k)
90    }
91    pub fn clear(&mut self) {
92        self.0 = None;
93    }
94    pub fn len(&self) -> usize {
95        self.0.as_ref().map_or(0, |m| m.len())
96    }
97    pub fn is_empty(&self) -> bool {
98        self.len() == 0
99    }
100    pub fn keys(&self) -> impl Iterator<Item = &String> {
101        self.0.iter().flat_map(|m| m.keys())
102    }
103    pub fn iter(&self) -> impl Iterator<Item = (&String, &V)> {
104        self.0.iter().flat_map(|m| m.iter())
105    }
106}
107
108impl<V> IntoIterator for LazyPropMap<V> {
109    type Item = (String, V);
110    type IntoIter = core::iter::Flatten<core::option::IntoIter<PropMap<V>>>;
111    fn into_iter(self) -> Self::IntoIter {
112        self.0.map(|b| *b).into_iter().flatten()
113    }
114}
115
116/// `&LazyPropMap` の反復子(空なら何も返さない)。
117pub struct LazyPropIter<'a, V>(Option<indexmap::map::Iter<'a, String, V>>);
118
119impl<'a, V> Iterator for LazyPropIter<'a, V> {
120    type Item = (&'a String, &'a V);
121    fn next(&mut self) -> Option<Self::Item> {
122        self.0.as_mut()?.next()
123    }
124}
125
126impl<'a, V> IntoIterator for &'a LazyPropMap<V> {
127    type Item = (&'a String, &'a V);
128    type IntoIter = LazyPropIter<'a, V>;
129    fn into_iter(self) -> Self::IntoIter {
130        LazyPropIter(self.0.as_ref().map(|m| m.iter()))
131    }
132}
133
134impl Drop for Obj {
135    fn drop(&mut self) {
136        #[cfg(target_os = "none")]
137        super::cycle_gc::untrack_obj(self.gc_slot);
138    }
139}
140
141/// 循環ゴミ回収の台帳に未登録であることを表す番号。
142pub const NO_GC_SLOT: u32 = u32::MAX;
143
144/// 作った `Obj` を循環ゴミ回収の台帳に載せる(カーネルのみ。ホストの試験では何もしない)。
145fn gc_track(rc: ObjRef) -> ObjRef {
146    #[cfg(target_os = "none")]
147    {
148        super::cycle_gc::track_obj(rc)
149    }
150    #[cfg(not(target_os = "none"))]
151    {
152        rc
153    }
154}
155
156/// ヒープ上のオブジェクトへの共有参照。
157pub type ObjRef = Rc<RefCell<Obj>>;
158
159/// ネイティブ関数のシグネチャ。`Err(Value)` は JS の throw を表す。
160pub type NativeFn = fn(&mut Interp, this: Value, args: &[Value]) -> Result<Value, Value>;
161
162/// JS の値。プリミティブ + オブジェクト参照。
163#[derive(Clone)]
164pub enum Value {
165    Undefined,
166    Null,
167    Bool(bool),
168    Number(f64),
169    Str(Rc<String>),
170    /// 任意精度整数 BigInt。
171    BigInt(Rc<BigInt>),
172    Object(ObjRef),
173}
174
175/// ユーザー定義関数のデータ。
176#[derive(Clone)]
177pub struct FunctionData {
178    pub name: String,
179    pub params: Rc<Vec<Param>>,
180    pub body: Rc<super::ast::FnBody>,
181    /// クロージャが捕捉した定義時スコープ。
182    pub closure: Rc<RefCell<Scope>>,
183    /// アロー関数か(`this` を捕捉し、自身の `this`/`arguments` を持たない)。
184    pub is_arrow: bool,
185    /// async 関数か(呼び出すと Promise を返す)。
186    pub is_async: bool,
187    /// generator 関数か(呼び出すと Generator を返す)。
188    pub is_generator: bool,
189    /// アロー関数が捕捉した `this`。
190    pub bound_this: Option<Box<Value>>,
191}
192
193/// オブジェクトの種別。Plain 以外は内部表現を持つ。
194/// 1 バイト型 TypedArray の記憶域(共有のバイト列のうち `off` から `len` バイト)。
195#[derive(Clone)]
196pub struct ByteStore {
197    pub buf: Rc<RefCell<Vec<u8>>>,
198    pub off: usize,
199    pub len: usize,
200    /// 0 = Uint8、1 = Int8、2 = Uint8Clamped。
201    pub kind: u8,
202}
203
204impl ByteStore {
205    pub fn new(bytes: Vec<u8>, kind: u8) -> Self {
206        let len = bytes.len();
207        ByteStore { buf: Rc::new(RefCell::new(bytes)), off: 0, len, kind }
208    }
209    /// 同じ記憶域の `[start, start+len)` を指す(`subarray`)。
210    pub fn view(&self, start: usize, len: usize, kind: u8) -> Self {
211        let start = start.min(self.len);
212        let len = len.min(self.len - start);
213        ByteStore { buf: self.buf.clone(), off: self.off + start, len, kind }
214    }
215    pub fn get(&self, i: usize) -> Option<f64> {
216        if i >= self.len {
217            return None;
218        }
219        let b = *self.buf.borrow().get(self.off + i)?;
220        Some(if self.kind == 1 { b as i8 as f64 } else { b as f64 })
221    }
222    /// 要素型に合わせて変換して書く(Uint8: ToUint8、Int8: ToInt8、Clamped: 丸めて 0..255)。
223    pub fn set(&self, i: usize, v: f64) {
224        if i >= self.len {
225            return;
226        }
227        let byte = Self::coerce(v, self.kind);
228        if let Some(slot) = self.buf.borrow_mut().get_mut(self.off + i) {
229            *slot = byte;
230        }
231    }
232    pub fn coerce(v: f64, kind: u8) -> u8 {
233        if kind == 2 {
234            if v.is_nan() {
235                return 0;
236            }
237            // 偶数への丸め(Uint8ClampedArray の仕様)。
238            let c = v.clamp(0.0, 255.0);
239            let f = libm::floor(c);
240            let r = if c - f > 0.5 || (c - f == 0.5 && (f as u32) % 2 == 1) { f + 1.0 } else { f };
241            return r as u8;
242        }
243        if !v.is_finite() {
244            return 0;
245        }
246        (libm::trunc(v) as i64) as u8
247    }
248    pub fn to_vec(&self) -> Vec<u8> {
249        let b = self.buf.borrow();
250        b.get(self.off..self.off + self.len).map(|s| s.to_vec()).unwrap_or_default()
251    }
252    pub fn values(&self) -> Vec<Value> {
253        (0..self.len).map(|i| Value::Number(self.get(i).unwrap_or(0.0))).collect()
254    }
255}
256
257#[derive(Clone)]
258pub enum ObjKind {
259    Plain,
260    Array(Vec<Value>),
261    Function(FunctionData),
262    Native {
263        name: String,
264        func: NativeFn,
265    },
266    /// 1 バイト型の TypedArray(Uint8Array / Int8Array / Uint8ClampedArray)。実バイト列を
267    /// 共有の記憶域に持つ(`subarray`・`.buffer`・DataView が同じ記憶域を指す)。
268    /// 【2026-10-18】従来は `ObjKind::Array`(要素ごとに `Value`)を流用しており、1 バイトに
269    /// 16 バイト以上使い、`subarray` も複写だったため、YouTube の動画データ(数百 KB〜MB)の
270    /// 解析でヒープが尽きた。
271    Bytes(ByteStore),
272    /// DOM 要素ハンドル(DOMアリーナのノード index を保持)。
273    DomElement(usize),
274    /// `document` などのホストオブジェクト識別子。
275    Host(String),
276    /// Map: 挿入順を保つ (key, value) リスト(キー等価は SameValueZero 近似)。
277    MapObj(Vec<(Value, Value)>),
278    /// Set: 挿入順を保つ値リスト。
279    SetObj(Vec<Value>),
280    /// Promise。状態は共有可変(then/await で参照される)。
281    PromiseObj(Rc<RefCell<PromiseState>>),
282    /// resolve/reject 関数(Promise 状態を束縛したクロージャ相当)。
283    Resolver {
284        state: Rc<RefCell<PromiseState>>,
285        reject: bool,
286    },
287    /// Generator(遅延評価。resume のたびに本体を replay して次の yield まで進める)。
288    Generator(Rc<RefCell<GenState>>),
289    /// RegExp(コンパイル済みパターン + lastIndex)。
290    RegExpObj(Rc<RefCell<super::regex::RegExpData>>),
291    /// Proxy(target とハンドラ。トラップ未定義時は target にフォワード)。
292    Proxy {
293        target: ObjRef,
294        handler: ObjRef,
295    },
296    /// `Function.prototype.bind()` が返す束縛済み関数。呼び出し時は
297    /// `bound_args ++ 実引数` を `bound_this` を this として `target` に渡す。
298    Bound {
299        target: Value,
300        bound_this: Value,
301        bound_args: Vec<Value>,
302    },
303    /// `Date`。内部値は UTC エポックミリ秒(不正な日時は `f64::NAN`。仕様の Invalid Date 相当)。
304    DateObj(f64),
305}
306
307/// Generator の完了・再開種別。
308#[derive(Clone)]
309pub enum GenCompletion {
310    Normal(Value),
311    Throw(Value),
312    Return(Value),
313}
314
315/// Generator の状態(遅延評価 / replay 方式)。
316///
317/// 真のコルーチン(スタック退避)は no_std ツリーウォーク実装では困難なため、
318/// resume のたびに関数本体を先頭から再実行(replay)し、N 番目の yield で中断する。
319/// 既に通過した yield には `sent`(`.next(v)` で渡された値)を返すことで
320/// 双方向の値受け渡しと無限ジェネレータを実現する。
321///
322/// 限界: replay のため本体の副作用(console.log 等)は resume ごとに再実行される。
323pub struct GenState {
324    /// generator 関数本体(params/body/closure を保持)。
325    pub func: FunctionData,
326    /// 呼び出し時の実引数。
327    pub args: Vec<Value>,
328    /// 呼び出し時の `this`。
329    pub this: Value,
330    /// 既に通過した各 yield に `.next(v)`/`.throw(e)`/`.return(v)` で渡された結果。
331    pub sent: Vec<GenCompletion>,
332    /// 最初の `.next()` を消費済みか(最初の next の引数は捨てられる)。
333    pub started: bool,
334    /// 完了済みか(以降の next は常に done:true)。
335    pub done: bool,
336    /// 完了時の return 値(done:true の value)。
337    pub returned: Value,
338}
339
340#[derive(Debug, Clone, Copy, PartialEq)]
341pub struct PropertyAttributes {
342    pub writable: bool,
343    pub configurable: bool,
344    pub enumerable: bool,
345}
346
347impl Default for PropertyAttributes {
348    fn default() -> Self {
349        PropertyAttributes {
350            writable: true,
351            configurable: true,
352            enumerable: true,
353        }
354    }
355}
356
357/// Promise の状態。
358#[derive(Clone, Copy, PartialEq)]
359pub enum PromiseStatus {
360    Pending,
361    Fulfilled,
362    Rejected,
363}
364
365/// settle 時に消費する then 反応。
366pub struct Reaction {
367    pub on_fulfilled: Option<Value>,
368    pub on_rejected: Option<Value>,
369    /// この反応の結果を反映する派生 Promise。
370    pub result: Rc<RefCell<PromiseState>>,
371}
372
373pub struct PromiseState {
374    pub status: PromiseStatus,
375    /// fulfilled の値、または rejected の理由。
376    pub value: Value,
377    /// pending 中に登録された反応(settle 時にマイクロタスクへ)。
378    pub reactions: Vec<Reaction>,
379}
380
381impl PromiseState {
382    pub fn pending() -> Self {
383        PromiseState {
384            status: PromiseStatus::Pending,
385            value: Value::Undefined,
386            reactions: Vec::new(),
387        }
388    }
389}
390
391/// マイクロタスク(then 反応の遅延実行単位)。
392pub struct Job {
393    /// 実行するハンドラ(None なら値をそのまま伝播)。
394    pub handler: Option<Value>,
395    pub arg: Value,
396    pub is_fulfill: bool,
397    pub result: Rc<RefCell<PromiseState>>,
398}
399
400/// アクセサ記述子(get/set の一方または両方)。
401pub struct Accessor {
402    pub get: Option<Value>,
403    pub set: Option<Value>,
404}
405
406/// ヒープオブジェクト本体。名前付きプロパティ + プロトタイプ + 種別。
407pub struct Obj {
408    /// 配列の穴(`Array(n)` 等で作られ、まだ書かれていない要素)の印。
409    /// 印があり、かつ値が `undefined` の要素だけを穴とみなす(書き込めば自然に穴でなくなる)。
410    /// 添字をずらす操作(shift/unshift/splice/sort/reverse)では捨てる。
411    /// 【2026-10-19】YouTube の kevlar_base が同梱する D3 の配色表
412    /// `Array(3).concat("…").map(DE)` で、穴の 3 要素にも `DE(undefined)` を呼んで落ちていた。
413    pub holes: Option<alloc::boxed::Box<Vec<bool>>>,
414    /// 循環ゴミ回収(`cycle_gc`)の台帳の番号(未登録は `NO_GC_SLOT`)。
415    pub gc_slot: u32,
416    pub props: PropMap<Value>,
417    /// Object.defineProperty で登録された getter/setter。
418    pub accessors: LazyPropMap<Accessor>,
419    pub attrs: LazyPropMap<PropertyAttributes>,
420    pub proto: Option<ObjRef>,
421    pub kind: ObjKind,
422    /// `Object.freeze()` 済みか。真なら `set_property`/`delete` によるプロパティの
423    /// 追加・変更・削除を無視する(非 strict モード相当の黙殺。`Object.isFrozen()` が読む)。
424    pub frozen: bool,
425    /// `Object.seal()` 済みか。真なら新規プロパティの追加・既存プロパティの削除を無視する
426    /// (既存プロパティの値の変更は許可。`frozen` はこれも自動的に含意する)。
427    pub sealed: bool,
428    /// `Object.preventExtensions()` 済みか。真なら新規プロパティの追加のみを禁止する
429    /// (削除・既存値の変更は許可。`sealed`/`frozen` はこれも自動的に含意する)。
430    pub non_extensible: bool,
431    /// `Object.create(null)`/`Object.groupBy()`等、仕様上`[[Prototype]]`が
432    /// `null`と明示されたオブジェクトか。`proto: None`だけでは「明示的に
433    /// nullプロトタイプ」なのか「単に通常オブジェクトでprotoチェーンが
434    /// 尽きた」なのかを`get_property`の共通メソッドフォールバック
435    /// (`interp/properties.rs`の`object_get`呼び出し)が区別できず、
436    /// `Object.create(null).hasOwnProperty`等がフォールバック経由で
437    /// 誤って動いてしまうバグがあった(2026-07-18 発見・実装)。
438    pub null_proto: bool,
439}
440
441impl Obj {
442    pub fn plain() -> ObjRef {
443        gc_track(Rc::new(RefCell::new(Obj {
444            props: PropMap::default(),
445            accessors: LazyPropMap::default(),
446            attrs: LazyPropMap::default(),
447            proto: None,
448            frozen: false,
449            sealed: false,
450            non_extensible: false,
451            null_proto: false,
452            holes: None,
453            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
454            kind: ObjKind::Plain,
455        })))
456    }
457    pub fn bytes(store: ByteStore) -> ObjRef {
458        gc_track(Rc::new(RefCell::new(Obj {
459            props: PropMap::default(),
460            accessors: LazyPropMap::default(),
461            attrs: LazyPropMap::default(),
462            proto: None,
463            frozen: false,
464            sealed: false,
465            non_extensible: false,
466            null_proto: false,
467            holes: None,
468            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
469            kind: ObjKind::Bytes(store),
470        })))
471    }
472    /// 配列の `i` 番目が穴か。
473    pub fn is_hole(&self, i: usize) -> bool {
474        match (&self.holes, &self.kind) {
475            (Some(h), ObjKind::Array(items)) => {
476                h.get(i).copied().unwrap_or(false) && matches!(items.get(i), Some(Value::Undefined))
477            }
478            _ => false,
479        }
480    }
481    /// 配列の穴の一覧(`len` 要素。穴が無ければ空)。
482    pub fn hole_mask(&self) -> Vec<bool> {
483        match (&self.holes, &self.kind) {
484            (Some(_), ObjKind::Array(items)) => (0..items.len()).map(|i| self.is_hole(i)).collect(),
485            _ => Vec::new(),
486        }
487    }
488    pub fn array(items: Vec<Value>) -> ObjRef {
489        gc_track(Rc::new(RefCell::new(Obj {
490            props: PropMap::default(),
491            accessors: LazyPropMap::default(),
492            attrs: LazyPropMap::default(),
493            proto: None,
494            frozen: false,
495            sealed: false,
496            non_extensible: false,
497            null_proto: false,
498            holes: None,
499            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
500            kind: ObjKind::Array(items),
501        })))
502    }
503    pub fn function(data: FunctionData) -> ObjRef {
504        gc_track(Rc::new(RefCell::new(Obj {
505            props: PropMap::default(),
506            accessors: LazyPropMap::default(),
507            attrs: LazyPropMap::default(),
508            proto: None,
509            frozen: false,
510            sealed: false,
511            non_extensible: false,
512            null_proto: false,
513            holes: None,
514            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
515            kind: ObjKind::Function(data),
516        })))
517    }
518    pub fn native(name: &str, func: NativeFn) -> ObjRef {
519        gc_track(Rc::new(RefCell::new(Obj {
520            props: PropMap::default(),
521            accessors: LazyPropMap::default(),
522            attrs: LazyPropMap::default(),
523            proto: None,
524            frozen: false,
525            sealed: false,
526            non_extensible: false,
527            null_proto: false,
528            holes: None,
529            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
530            kind: ObjKind::Native {
531                name: String::from(name),
532                func,
533            },
534        })))
535    }
536    pub fn host(tag: &str) -> ObjRef {
537        gc_track(Rc::new(RefCell::new(Obj {
538            props: PropMap::default(),
539            accessors: LazyPropMap::default(),
540            attrs: LazyPropMap::default(),
541            proto: None,
542            frozen: false,
543            sealed: false,
544            non_extensible: false,
545            null_proto: false,
546            holes: None,
547            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
548            kind: ObjKind::Host(String::from(tag)),
549        })))
550    }
551    pub fn dom(node_idx: usize) -> ObjRef {
552        gc_track(Rc::new(RefCell::new(Obj {
553            props: PropMap::default(),
554            accessors: LazyPropMap::default(),
555            attrs: LazyPropMap::default(),
556            proto: None,
557            frozen: false,
558            sealed: false,
559            non_extensible: false,
560            null_proto: false,
561            holes: None,
562            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
563            kind: ObjKind::DomElement(node_idx),
564        })))
565    }
566    pub fn map_obj(entries: Vec<(Value, Value)>) -> ObjRef {
567        gc_track(Rc::new(RefCell::new(Obj {
568            props: PropMap::default(),
569            accessors: LazyPropMap::default(),
570            attrs: LazyPropMap::default(),
571            proto: None,
572            frozen: false,
573            sealed: false,
574            non_extensible: false,
575            null_proto: false,
576            holes: None,
577            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
578            kind: ObjKind::MapObj(entries),
579        })))
580    }
581    pub fn set_obj(items: Vec<Value>) -> ObjRef {
582        gc_track(Rc::new(RefCell::new(Obj {
583            props: PropMap::default(),
584            accessors: LazyPropMap::default(),
585            attrs: LazyPropMap::default(),
586            proto: None,
587            frozen: false,
588            sealed: false,
589            non_extensible: false,
590            null_proto: false,
591            holes: None,
592            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
593            kind: ObjKind::SetObj(items),
594        })))
595    }
596    pub fn promise(state: Rc<RefCell<PromiseState>>) -> ObjRef {
597        gc_track(Rc::new(RefCell::new(Obj {
598            props: PropMap::default(),
599            accessors: LazyPropMap::default(),
600            attrs: LazyPropMap::default(),
601            proto: None,
602            frozen: false,
603            sealed: false,
604            non_extensible: false,
605            null_proto: false,
606            holes: None,
607            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
608            kind: ObjKind::PromiseObj(state),
609        })))
610    }
611    pub fn resolver(state: Rc<RefCell<PromiseState>>, reject: bool) -> ObjRef {
612        gc_track(Rc::new(RefCell::new(Obj {
613            props: PropMap::default(),
614            accessors: LazyPropMap::default(),
615            attrs: LazyPropMap::default(),
616            proto: None,
617            frozen: false,
618            sealed: false,
619            non_extensible: false,
620            null_proto: false,
621            holes: None,
622            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
623            kind: ObjKind::Resolver { state, reject },
624        })))
625    }
626    pub fn generator(state: GenState) -> ObjRef {
627        gc_track(Rc::new(RefCell::new(Obj {
628            props: PropMap::default(),
629            accessors: LazyPropMap::default(),
630            attrs: LazyPropMap::default(),
631            proto: None,
632            frozen: false,
633            sealed: false,
634            non_extensible: false,
635            null_proto: false,
636            holes: None,
637            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
638            kind: ObjKind::Generator(Rc::new(RefCell::new(state))),
639        })))
640    }
641    pub fn regexp(data: super::regex::RegExpData) -> ObjRef {
642        gc_track(Rc::new(RefCell::new(Obj {
643            props: PropMap::default(),
644            accessors: LazyPropMap::default(),
645            attrs: LazyPropMap::default(),
646            proto: None,
647            frozen: false,
648            sealed: false,
649            non_extensible: false,
650            null_proto: false,
651            holes: None,
652            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
653            kind: ObjKind::RegExpObj(Rc::new(RefCell::new(data))),
654        })))
655    }
656    pub fn proxy(target: ObjRef, handler: ObjRef) -> ObjRef {
657        gc_track(Rc::new(RefCell::new(Obj {
658            props: PropMap::default(),
659            accessors: LazyPropMap::default(),
660            attrs: LazyPropMap::default(),
661            proto: None,
662            frozen: false,
663            sealed: false,
664            non_extensible: false,
665            null_proto: false,
666            holes: None,
667            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
668            kind: ObjKind::Proxy { target, handler },
669        })))
670    }
671    pub fn date_obj(epoch_ms: f64) -> ObjRef {
672        gc_track(Rc::new(RefCell::new(Obj {
673            props: PropMap::default(),
674            accessors: LazyPropMap::default(),
675            attrs: LazyPropMap::default(),
676            proto: None,
677            frozen: false,
678            sealed: false,
679            non_extensible: false,
680            null_proto: false,
681            holes: None,
682            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
683            kind: ObjKind::DateObj(epoch_ms),
684        })))
685    }
686    pub fn bound(target: Value, bound_this: Value, bound_args: Vec<Value>) -> ObjRef {
687        gc_track(Rc::new(RefCell::new(Obj {
688            props: PropMap::default(),
689            accessors: LazyPropMap::default(),
690            attrs: LazyPropMap::default(),
691            proto: None,
692            frozen: false,
693            sealed: false,
694            non_extensible: false,
695            null_proto: false,
696            holes: None,
697            gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
698            kind: ObjKind::Bound {
699                target,
700                bound_this,
701                bound_args,
702            },
703        })))
704    }
705    pub fn clear_references(&mut self) {
706        self.props.clear();
707        self.accessors.clear();
708        self.proto = None;
709        match &mut self.kind {
710            ObjKind::Array(items) => items.clear(),
711            ObjKind::Function(f) => {
712                f.bound_this = None;
713                f.closure = Scope::new_root();
714            }
715            ObjKind::MapObj(entries) => entries.clear(),
716            ObjKind::SetObj(items) => items.clear(),
717            ObjKind::PromiseObj(p) => {
718                if let Ok(mut p) = p.try_borrow_mut() {
719                    p.reactions.clear();
720                    p.value = Value::Undefined;
721                }
722            }
723            ObjKind::Resolver { state, .. } => {
724                if let Ok(mut s) = state.try_borrow_mut() {
725                    s.reactions.clear();
726                    s.value = Value::Undefined;
727                }
728            }
729            ObjKind::Bound { target, bound_this, bound_args } => {
730                *target = Value::Undefined;
731                *bound_this = Value::Undefined;
732                bound_args.clear();
733            }
734            _ => {}
735        }
736    }
737
738    pub fn is_callable(&self) -> bool {
739        match &self.kind {
740            ObjKind::Function(_) | ObjKind::Native { .. } | ObjKind::Resolver { .. } => true,
741            ObjKind::Bound { .. } => true,
742            // Proxy は target が callable なら callable。
743            ObjKind::Proxy { target, .. } => target.borrow().is_callable(),
744            _ => false,
745        }
746    }
747}
748
749impl Value {
750    pub fn str(s: impl Into<String>) -> Value {
751        Value::Str(Rc::new(s.into()))
752    }
753    pub fn bigint(b: BigInt) -> Value {
754        Value::BigInt(Rc::new(b))
755    }
756    pub fn object(o: ObjRef) -> Value {
757        Value::Object(o)
758    }
759
760    /// ECMAScript ToBoolean。
761    pub fn truthy(&self) -> bool {
762        match self {
763            Value::Undefined | Value::Null => false,
764            Value::Bool(b) => *b,
765            Value::Number(n) => *n != 0.0 && !n.is_nan(),
766            Value::Str(s) => !s.is_empty(),
767            Value::BigInt(b) => !b.is_zero(),
768            Value::Object(_) => true,
769        }
770    }
771
772    /// ECMAScript ToNumber(簡易)。
773    pub fn to_number(&self) -> f64 {
774        match self {
775            Value::Undefined => f64::NAN,
776            Value::Null => 0.0,
777            Value::Bool(b) => {
778                if *b {
779                    1.0
780                } else {
781                    0.0
782                }
783            }
784            Value::Number(n) => *n,
785            Value::Str(s) => {
786                let t = s.trim();
787                if t.is_empty() {
788                    0.0
789                } else if let Some(rest) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
790                    // `StringNumericLiteral` は `0x`/`0o`/`0b` 整数リテラルも受理する(符号無し)。
791                    // `Number("0x1F")`===31 のような変換が以前は常に `NaN` になっていた。
792                    u64::from_str_radix(rest, 16).map(|n| n as f64).unwrap_or(f64::NAN)
793                } else if let Some(rest) = t.strip_prefix("0o").or_else(|| t.strip_prefix("0O")) {
794                    u64::from_str_radix(rest, 8).map(|n| n as f64).unwrap_or(f64::NAN)
795                } else if let Some(rest) = t.strip_prefix("0b").or_else(|| t.strip_prefix("0B")) {
796                    u64::from_str_radix(rest, 2).map(|n| n as f64).unwrap_or(f64::NAN)
797                } else {
798                    t.parse::<f64>().unwrap_or(f64::NAN)
799                }
800            }
801            Value::BigInt(b) => b.to_f64(),
802            // `Date` は ToPrimitive(hint=number) で `valueOf()`(エポックミリ秒)相当になる
803            // (仕様上は他の型も `valueOf`/`Symbol.toPrimitive` を経由し得るが、この処理系は
804            // それらを汎用的にはサポートしないため `Date` だけ直接特殊扱いする)。
805            // これにより `date2 - date1` や `date1 < date2` の算術/比較が素で動くようになる。
806            // `Array` も ToPrimitive(hint=default/number) で `toString()`(`join(',')`)した
807            // 文字列を数値変換する(`[5]*2`===10、`[]+1`===1 のような定番イディオムが
808            // 以前は常に `NaN` になっていた)。単一要素なら中身がそのまま数値化され、空配列は
809            // `0`、複数要素は通常カンマ区切り文字列がパース不能で `NaN` になる(仕様どおり)。
810            Value::Object(o) => match &o.borrow().kind {
811                ObjKind::DateObj(ms) => *ms,
812                ObjKind::Array(_) => {
813                    let s = self.to_js_string();
814                    let t = s.trim();
815                    if t.is_empty() {
816                        0.0
817                    } else {
818                        t.parse::<f64>().unwrap_or(f64::NAN)
819                    }
820                }
821                // `Array.isArray`/`Object.keys`/`structuredClone`/`to_js_string` 等、
822                // 他の多くの操作は既に target へ委譲する透過性修正済みだったのに、
823                // 数値変換だけ見落とされていた(`+new Proxy([5],{})`===`5`、
824                // `new Proxy([5],{})*2`===`10` のような ToPrimitive(hint=number)
825                // 経由の算術が常に `NaN` になっていたバグ)。
826                ObjKind::Proxy { target, .. } => Value::Object(target.clone()).to_number(),
827                _ => f64::NAN,
828            },
829        }
830    }
831
832    /// 型を表す `typeof` 文字列。
833    pub fn type_of(&self) -> &'static str {
834        match self {
835            Value::Undefined => "undefined",
836            Value::Null => "object",
837            Value::Bool(_) => "boolean",
838            Value::Number(_) => "number",
839            Value::Str(_) => "string",
840            Value::BigInt(_) => "bigint",
841            Value::Object(o) => {
842                if o.borrow().is_callable() {
843                    "function"
844                } else {
845                    "object"
846                }
847            }
848        }
849    }
850
851    /// ECMAScript ToString(簡易・表示用)。
852    pub fn to_js_string(&self) -> String {
853        let mut seen = Vec::new();
854        self.to_js_string_seen(&mut seen)
855    }
856
857    /// `to_js_string` の内部実装。`Array` の要素文字列化は再帰的に自分自身の
858    /// `to_js_string` を呼ぶため、`var a=[1]; a.push(a); a.join()`(あるいは
859    /// `String(a)`/テンプレートリテラル補間等、内部的に同じ経路を通る操作)が
860    /// 循環参照でスタックオーバーフロー(この no_std 環境ではクラッシュ/ハングに
861    /// なり得る)していた。`structuredClone`/`JSON.stringify`/`console.log` で
862    /// 既に修正済みの「祖先を追跡して打ち切る」パターンをここにも適用し、
863    /// 既に祖先に現れた配列を再訪した時点で空文字列を返して打ち切る
864    /// (実ブラウザは無条件のスタックオーバーフローで `RangeError` を投げるが、
865    /// この処理系は例外化されない Rust ネイティブ再帰でのクラッシュを避ける
866    /// ことを優先し、値としては空文字列で打ち切る簡略挙動とする。
867    /// `a=[1]; a.push(a); a.join()` は `"1,1,"` になる——2番目の要素 `a` 自身の
868    /// `to_js_string()` が、その内部で自分自身を再訪した時点(3階層目)で
869    /// 空文字列に切り詰められるため)。
870    fn to_js_string_seen(&self, seen: &mut Vec<ObjRef>) -> String {
871        if seen.len() > 30 {
872            return String::new();
873        }
874        match self {
875            Value::Undefined => String::from("undefined"),
876            Value::Null => String::from("null"),
877            Value::Bool(b) => {
878                if *b {
879                    String::from("true")
880                } else {
881                    String::from("false")
882                }
883            }
884            Value::Number(n) => fmt_number(*n),
885            Value::Str(s) => (**s).clone(),
886            Value::BigInt(b) => b.to_decimal_string(),
887            Value::Object(o) => {
888                let b = o.borrow();
889                match &b.kind {
890                    ObjKind::Array(items) => {
891                        if seen.iter().any(|s| Rc::ptr_eq(s, o)) {
892                            return String::new();
893                        }
894                        seen.push(o.clone());
895                        let parts: Vec<String> = items
896                            .iter()
897                            .map(|v| match v {
898                                Value::Undefined | Value::Null => String::new(),
899                                _ => v.to_js_string_seen(seen),
900                            })
901                            .collect();
902                        seen.pop();
903                        parts.join(",")
904                    }
905                    ObjKind::Function(f) => format!("function {}() {{ [code] }}", f.name),
906                    ObjKind::Native { name, .. } => {
907                        format!("function {}() {{ [native code] }}", name)
908                    }
909                    // `fn.bind(...)` が返す束縛済み関数(`ObjKind::Bound`)を
910                    // 文字列化すると、専用ケースが無く汎用の `[object Object]` に
911                    // 落ちるバグだった(`.name`/`.length` の欠落と同種)。
912                    ObjKind::Bound { .. } => String::from("function () { [native code] }"),
913                    ObjKind::MapObj(_) => String::from("[object Map]"),
914                    ObjKind::SetObj(_) => String::from("[object Set]"),
915                    ObjKind::PromiseObj(_) => String::from("[object Promise]"),
916                    ObjKind::Resolver { .. } => String::from("function () { [native code] }"),
917                    ObjKind::Generator(_) => String::from("[object Generator]"),
918                    ObjKind::RegExpObj(r) => {
919                        let d = r.borrow();
920                        format!("/{}/{}", d.re.source, d.re.flags)
921                    }
922                    // `Proxy` を文字列化すると、`ObjKind::Proxy` 専用のケースが無く
923                    // 汎用の `[object Object]` に落ちるバグだった(`String(new
924                    // Proxy([1,2,3], {}))` が `"1,2,3"` になるべきところ壊れた結果に
925                    // なっていた)。`Array.isArray`/`Object.keys`/`structuredClone`
926                    // 等、他の多くの操作は既に target へ委譲する透過性修正済みだった
927                    // のに、この経路だけ見落とされていた。
928                    ObjKind::Proxy { target, .. } => {
929                        Value::Object(target.clone()).to_js_string_seen(seen)
930                    }
931                    // 文字列変換(テンプレートリテラル補間・`String(date)`・暗黙の
932                    // 文字列連結等)でも ISO 形式を返す。この簡易実装ではロケール依存の
933                    // `toDateString()` 的フォーマットは持たないため常に ISO 形式。
934                    ObjKind::DateObj(ms) => date_to_iso_string_repr(*ms),
935                    _ => String::from("[object Object]"),
936                }
937            }
938        }
939    }
940
941    /// `===` 厳密等価。
942    pub fn strict_eq(&self, other: &Value) -> bool {
943        match (self, other) {
944            (Value::Undefined, Value::Undefined) => true,
945            (Value::Null, Value::Null) => true,
946            (Value::Bool(a), Value::Bool(b)) => a == b,
947            (Value::Number(a), Value::Number(b)) => a == b,
948            (Value::Str(a), Value::Str(b)) => a == b,
949            (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b) == core::cmp::Ordering::Equal,
950            // DOM 要素は `Obj::dom(idx)` がアクセスの都度フレッシュな `Rc` を作るため
951            // (`document.getElementById`/`querySelector`/`.content` 等の戻り値がそう)、
952            // `Rc::ptr_eq` だけでは同じノードを指す2つのハンドルが常に不等になる
953            // バグだった(実ブラウザでは `getElementById('x') === getElementById('x')`
954            // は当然 `true`)。`ObjKind::DomElement` 同士は index で比較する。
955            (Value::Object(a), Value::Object(b)) => {
956                match (&a.borrow().kind, &b.borrow().kind) {
957                    (ObjKind::DomElement(ia), ObjKind::DomElement(ib)) => ia == ib,
958                    _ => Rc::ptr_eq(a, b),
959                }
960            }
961            _ => false,
962        }
963    }
964
965    /// 同一オブジェクト参照か(関数リスナの照合用)。プリミティブは strict_eq に委譲。
966    pub fn same_ref(&self, other: &Value) -> bool {
967        match (self, other) {
968            (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
969            _ => self.strict_eq(other),
970        }
971    }
972}
973
974/// JS の数値表示規則(整数は小数点を付けない、NaN/Infinity 対応)。
975pub fn fmt_number(n: f64) -> String {
976    if n.is_nan() {
977        return String::from("NaN");
978    }
979    if n.is_infinite() {
980        return String::from(if n > 0.0 { "Infinity" } else { "-Infinity" });
981    }
982    if n == 0.0 {
983        return String::from("0");
984    }
985    // 整数値は小数点なしで表示。
986    if libm::trunc(n) == n && libm::fabs(n) < 1e21 {
987        return format!("{}", n as i64);
988    }
989    // それ以外は既定の浮動小数表示。
990    let mut s = format!("{}", n);
991    // 末尾の不要な 0 を整理(Rust の {} は概ね妥当だがフォールバック)。
992    if s.contains('.') {
993        while s.ends_with('0') {
994            s.pop();
995        }
996        if s.ends_with('.') {
997            s.pop();
998        }
999    }
1000    s
1001}
1002
1003/// `Date` の文字列表現(ISO 8601、UTC)。`builtins.rs` の `format_iso`/`decompose_ms` と
1004/// 同じ Howard Hinnant 方式の変換ロジックだが、`value.rs` は `builtins.rs` に依存できない
1005/// (下位レイヤのため)ので、ごく小さい純粋関数として独立に複製する
1006/// (`kernel::timer` にもある3つ目の複製。詳細は spec/walkthrough.md 2026-07-07 参照)。
1007fn date_to_iso_string_repr(ms: f64) -> String {
1008    let total_ms = libm::floor(ms) as i64;
1009    let days = total_ms.div_euclid(86_400_000);
1010    let ms_of_day = total_ms.rem_euclid(86_400_000);
1011    let z = days + 719468;
1012    let era = (if z >= 0 { z } else { z - 146096 }) / 146097;
1013    let doe = z - era * 146097;
1014    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
1015    let y = yoe + era * 400;
1016    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
1017    let mp = (5 * doy + 2) / 153;
1018    let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
1019    let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u32;
1020    let y = if m <= 2 { y + 1 } else { y };
1021    let hh = (ms_of_day / 3_600_000) as u32;
1022    let mm = ((ms_of_day / 60_000) % 60) as u32;
1023    let ss = ((ms_of_day / 1000) % 60) as u32;
1024    let mms = (ms_of_day % 1000) as u32;
1025    format!(
1026        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
1027        y, m, d, hh, mm, ss, mms
1028    )
1029}