Skip to main content

sim_lib_lang_javascript/
collections.rs

1//! ECMAScript collection policy composed over shared sequence semantics.
2
3use crate::JavascriptValue;
4use std::collections::BTreeMap;
5
6/// A unique ECMAScript Symbol identity.
7#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub struct JavascriptSymbol {
9    id: u64,
10    description: Option<String>,
11}
12impl JavascriptSymbol {
13    /// Stable identity allocated by a registry.
14    pub fn id(&self) -> u64 {
15        self.id
16    }
17    /// Optional descriptive text; it never participates in identity.
18    pub fn description(&self) -> Option<&str> {
19        self.description.as_deref()
20    }
21}
22
23/// Realm-local allocator and global-symbol registry.
24#[derive(Clone, Debug, Default)]
25pub struct JavascriptSymbolRegistry {
26    next: u64,
27    globals: BTreeMap<String, JavascriptSymbol>,
28}
29impl JavascriptSymbolRegistry {
30    /// Allocate a fresh symbol.
31    pub fn symbol(&mut self, description: Option<String>) -> JavascriptSymbol {
32        let symbol = JavascriptSymbol {
33            id: self.next,
34            description,
35        };
36        self.next += 1;
37        symbol
38    }
39    /// Return the stable `Symbol.for` identity for `key`.
40    pub fn symbol_for(&mut self, key: impl Into<String>) -> JavascriptSymbol {
41        let key = key.into();
42        if let Some(symbol) = self.globals.get(&key) {
43            return symbol.clone();
44        }
45        let symbol = self.symbol(Some(key.clone()));
46        self.globals.insert(key, symbol.clone());
47        symbol
48    }
49    /// Recover the `Symbol.for` key, if any.
50    pub fn key_for(&self, symbol: &JavascriptSymbol) -> Option<&str> {
51        self.globals
52            .iter()
53            .find_map(|(key, value)| (value == symbol).then_some(key.as_str()))
54    }
55}
56
57/// Failure from a bounded collection method.
58#[derive(Clone, Debug, Eq, PartialEq)]
59pub enum JavascriptCollectionError {
60    /// A sparse or explicit index is outside the collection.
61    Index,
62    /// The caller's explicit work bound was exhausted.
63    Limit,
64}
65
66/// ECMAScript array with explicit holes distinct from `undefined`.
67#[derive(Clone, Debug, Default, PartialEq)]
68pub struct JavascriptArray {
69    elements: Vec<Option<JavascriptValue>>,
70}
71impl JavascriptArray {
72    /// Construct a dense array.
73    pub fn dense(values: Vec<JavascriptValue>) -> Self {
74        Self {
75            elements: values.into_iter().map(Some).collect(),
76        }
77    }
78    /// Construct with an explicit length and holes.
79    pub fn sparse(length: usize) -> Self {
80        Self {
81            elements: vec![None; length],
82        }
83    }
84    /// ECMAScript length.
85    pub fn len(&self) -> usize {
86        self.elements.len()
87    }
88    /// Whether length is zero.
89    pub fn is_empty(&self) -> bool {
90        self.elements.is_empty()
91    }
92    /// Read an own indexed element; holes remain distinguishable.
93    pub fn get(&self, index: usize) -> Option<&JavascriptValue> {
94        self.elements.get(index).and_then(Option::as_ref)
95    }
96    /// Set an index, growing through holes as JavaScript arrays do.
97    pub fn set(&mut self, index: usize, value: JavascriptValue) {
98        if index >= self.len() {
99            self.elements.resize(index + 1, None);
100        }
101        self.elements[index] = Some(value);
102    }
103    /// Append and return the new length.
104    pub fn push(&mut self, value: JavascriptValue) -> usize {
105        self.elements.push(Some(value));
106        self.len()
107    }
108    /// Remove and return the last element (`undefined` and a hole both return `None` at this policy seam).
109    pub fn pop(&mut self) -> Option<JavascriptValue> {
110        self.elements.pop().flatten()
111    }
112    /// JavaScript array iterator: holes are observed as `undefined`.
113    pub fn values(&self) -> JavascriptIterator {
114        JavascriptIterator::new(
115            self.elements
116                .iter()
117                .map(|v| v.clone().unwrap_or(JavascriptValue::Undefined))
118                .collect(),
119        )
120    }
121    /// Bounded `map`; callbacks skip holes and holes are retained.
122    pub fn map(
123        &self,
124        max_visits: usize,
125        mut f: impl FnMut(&JavascriptValue, usize) -> JavascriptValue,
126    ) -> Result<Self, JavascriptCollectionError> {
127        let visits = self.elements.iter().filter(|v| v.is_some()).count();
128        if visits > max_visits {
129            return Err(JavascriptCollectionError::Limit);
130        }
131        Ok(Self {
132            elements: self
133                .elements
134                .iter()
135                .enumerate()
136                .map(|(i, v)| v.as_ref().map(|v| f(v, i)))
137                .collect(),
138        })
139    }
140    /// Bounded `filter`; callbacks skip holes and the result is dense.
141    pub fn filter(
142        &self,
143        max_visits: usize,
144        mut f: impl FnMut(&JavascriptValue, usize) -> bool,
145    ) -> Result<Self, JavascriptCollectionError> {
146        let mut out = Vec::new();
147        let mut visits = 0;
148        for (i, value) in self.elements.iter().enumerate() {
149            if let Some(value) = value {
150                visits += 1;
151                if visits > max_visits {
152                    return Err(JavascriptCollectionError::Limit);
153                }
154                if f(value, i) {
155                    out.push(Some(value.clone()));
156                }
157            }
158        }
159        Ok(Self { elements: out })
160    }
161}
162
163/// Insertion-ordered ECMAScript Map using SameValueZero-style scalar keys.
164#[derive(Clone, Debug, Default, PartialEq)]
165pub struct JavascriptMap {
166    entries: Vec<(JavascriptValue, JavascriptValue)>,
167}
168impl JavascriptMap {
169    /// Insert or replace without changing insertion position.
170    pub fn set(&mut self, key: JavascriptValue, value: JavascriptValue) {
171        if let Some(e) = self
172            .entries
173            .iter_mut()
174            .find(|(k, _)| same_value_zero(k, &key))
175        {
176            e.1 = value;
177        } else {
178            self.entries.push((key, value));
179        }
180    }
181    /// Lookup a value.
182    pub fn get(&self, key: &JavascriptValue) -> Option<&JavascriptValue> {
183        self.entries
184            .iter()
185            .find(|(k, _)| same_value_zero(k, key))
186            .map(|e| &e.1)
187    }
188    /// Delete a key.
189    pub fn delete(&mut self, key: &JavascriptValue) -> bool {
190        if let Some(i) = self
191            .entries
192            .iter()
193            .position(|(k, _)| same_value_zero(k, key))
194        {
195            self.entries.remove(i);
196            true
197        } else {
198            false
199        }
200    }
201    /// Entry count.
202    pub fn len(&self) -> usize {
203        self.entries.len()
204    }
205    /// Whether empty.
206    pub fn is_empty(&self) -> bool {
207        self.entries.is_empty()
208    }
209    /// Insertion-ordered entries.
210    pub fn entries(&self) -> impl Iterator<Item = (&JavascriptValue, &JavascriptValue)> {
211        self.entries.iter().map(|(k, v)| (k, v))
212    }
213}
214
215/// Insertion-ordered ECMAScript Set.
216#[derive(Clone, Debug, Default, PartialEq)]
217pub struct JavascriptSet {
218    values: Vec<JavascriptValue>,
219}
220impl JavascriptSet {
221    /// Add a value with SameValueZero uniqueness.
222    pub fn add(&mut self, value: JavascriptValue) {
223        if !self.has(&value) {
224            self.values.push(value);
225        }
226    }
227    /// Membership query.
228    pub fn has(&self, value: &JavascriptValue) -> bool {
229        self.values.iter().any(|v| same_value_zero(v, value))
230    }
231    /// Delete a value.
232    pub fn delete(&mut self, value: &JavascriptValue) -> bool {
233        if let Some(i) = self.values.iter().position(|v| same_value_zero(v, value)) {
234            self.values.remove(i);
235            true
236        } else {
237            false
238        }
239    }
240    /// Value count.
241    pub fn len(&self) -> usize {
242        self.values.len()
243    }
244    /// Whether empty.
245    pub fn is_empty(&self) -> bool {
246        self.values.is_empty()
247    }
248    /// Insertion-ordered values.
249    pub fn values(&self) -> JavascriptIterator {
250        JavascriptIterator::new(self.values.clone())
251    }
252}
253
254/// One iterator result cell.
255#[derive(Clone, Debug, PartialEq)]
256pub struct JavascriptIteratorResult {
257    /// Produced value, absent after completion.
258    pub value: Option<JavascriptValue>,
259    /// ECMAScript `done` flag.
260    pub done: bool,
261}
262/// Bounded, stateful ECMAScript iterator cell.
263#[derive(Clone, Debug)]
264pub struct JavascriptIterator {
265    values: Vec<JavascriptValue>,
266    at: usize,
267}
268impl JavascriptIterator {
269    /// Build an iterator over an owned snapshot.
270    pub fn new(values: Vec<JavascriptValue>) -> Self {
271        Self { values, at: 0 }
272    }
273    /// Execute the iterator protocol's `next` method.
274    pub fn next_result(&mut self) -> JavascriptIteratorResult {
275        if let Some(value) = self.values.get(self.at).cloned() {
276            self.at += 1;
277            JavascriptIteratorResult {
278                value: Some(value),
279                done: false,
280            }
281        } else {
282            JavascriptIteratorResult {
283                value: None,
284                done: true,
285            }
286        }
287    }
288}
289fn same_value_zero(a: &JavascriptValue, b: &JavascriptValue) -> bool {
290    match (a, b) {
291        (JavascriptValue::Number(a), JavascriptValue::Number(b)) => {
292            a == b || (a.is_nan() && b.is_nan())
293        }
294        _ => a == b,
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    #[test]
302    fn arrays_preserve_holes_and_iterators_materialize_undefined() {
303        let mut a = JavascriptArray::sparse(2);
304        a.set(1, JavascriptValue::Number(2.));
305        assert_eq!(a.map(1, |v, _| v.clone()).unwrap().get(0), None);
306        let mut it = a.values();
307        assert_eq!(it.next_result().value, Some(JavascriptValue::Undefined));
308        assert!(!it.next_result().done);
309        assert!(it.next_result().done);
310    }
311    #[test]
312    fn map_set_use_same_value_zero_and_insertion_order() {
313        let mut m = JavascriptMap::default();
314        m.set(
315            JavascriptValue::Number(f64::NAN),
316            JavascriptValue::Number(1.),
317        );
318        m.set(
319            JavascriptValue::Number(f64::NAN),
320            JavascriptValue::Number(2.),
321        );
322        assert_eq!(m.len(), 1);
323        let mut s = JavascriptSet::default();
324        s.add(JavascriptValue::Number(-0.));
325        s.add(JavascriptValue::Number(0.));
326        assert_eq!(s.len(), 1);
327    }
328    #[test]
329    fn symbols_have_identity_and_registry_keys() {
330        let mut r = JavascriptSymbolRegistry::default();
331        assert_ne!(r.symbol(Some("x".into())), r.symbol(Some("x".into())));
332        let s = r.symbol_for("x");
333        assert_eq!(s, r.symbol_for("x"));
334        assert_eq!(r.key_for(&s), Some("x"));
335    }
336}