boa-cat 0.3.1

Tree-walking ECMAScript interpreter built on ecma-syntax-cat. v0.3.0 added accessor-property support at the engine level (`Object::with_accessor`, getter/setter dispatch on read/write). v0.3.1 picks up ecma-parse-cat 0.2 so the same dispatch path now also fires for accessor / shorthand-method object-literal syntax (`{ get x() {} }` / `{ set x(v) {} }` / `{ foo() {} }`) in user-provided JS source -- the engine already handled those AST variants in 0.3.0, only the parser was holding it back.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
//! Runtime values and identifiers.

use std::collections::BTreeMap;

use ecma_syntax_cat::function::ArrowBody;
use ecma_syntax_cat::identifier::Identifier;
use ecma_syntax_cat::pattern::Pattern;
use ecma_syntax_cat::statement::Statement;

use crate::env::Env;

/// A heap-allocated object's identifier.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ObjectId(u64);

impl ObjectId {
    /// Build an `ObjectId` from a raw integer.  Internal use; the heap is
    /// the only legitimate source of `ObjectId`s.
    #[must_use]
    pub(crate) fn new(id: u64) -> Self {
        Self(id)
    }

    /// The raw underlying id.
    #[must_use]
    pub fn raw(&self) -> u64 {
        self.0
    }
}

impl std::fmt::Display for ObjectId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "obj#{}", self.0)
    }
}

/// A heap-allocated function's identifier.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FunctionId(u64);

impl FunctionId {
    /// Build a `FunctionId` from a raw integer.
    #[must_use]
    pub(crate) fn new(id: u64) -> Self {
        Self(id)
    }

    /// The raw underlying id.
    #[must_use]
    pub fn raw(&self) -> u64 {
        self.0
    }
}

impl std::fmt::Display for FunctionId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "fn#{}", self.0)
    }
}

/// A heap-allocated variable cell's identifier.  Variables live in cells
/// so that assignment can update the value without rebuilding the
/// surrounding environment.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CellId(u64);

impl CellId {
    /// Build a `CellId` from a raw integer.
    #[must_use]
    pub(crate) fn new(id: u64) -> Self {
        Self(id)
    }

    /// The raw underlying id.
    #[must_use]
    pub fn raw(&self) -> u64 {
        self.0
    }
}

impl std::fmt::Display for CellId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "cell#{}", self.0)
    }
}

/// A variable cell: the current value plus a mutability flag.  `let`/`var`
/// declarations allocate mutable cells; `const` allocates immutable ones,
/// and the engine rejects assignment to immutable cells.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cell {
    value: Value,
    mutable: bool,
}

impl Cell {
    /// Build a cell with the given value and mutability.
    #[must_use]
    pub fn new(value: Value, mutable: bool) -> Self {
        Self { value, mutable }
    }

    /// The current value.
    #[must_use]
    pub fn value(&self) -> &Value {
        &self.value
    }

    /// Whether the cell accepts assignment.
    #[must_use]
    pub fn is_mutable(&self) -> bool {
        self.mutable
    }

    /// Replace the value, preserving mutability.
    #[must_use]
    pub fn with_value(self, value: Value) -> Self {
        Self {
            value,
            mutable: self.mutable,
        }
    }
}

/// A runtime value.
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    /// The `undefined` singleton.
    Undefined,
    /// The `null` singleton.
    Null,
    /// A boolean.
    Boolean(bool),
    /// An IEEE-754 double.  Equality follows IEEE-754 (NaN != NaN) which
    /// matches `===` semantics.
    Number(f64),
    /// A string.
    String(String),
    /// A handle to a heap-allocated object.
    Object(ObjectId),
    /// A handle to a heap-allocated function.
    Function(FunctionId),
    /// A native (Rust-implemented) callable.  Native callables receive
    /// their arguments, a `this` binding, the current heap, and the fuel
    /// budget, and return the standard expression-evaluation result.
    Native(NativeFn),
}

/// Signature of a native (Rust-implemented) callable.
///
/// `args` are the call's positional arguments after evaluation; `this`
/// is the binding the call site selected (the method receiver for
/// `obj.method(...)`, or `Undefined` for plain calls); `heap` and `fuel`
/// thread the persistent state.
pub type NativeFn = fn(
    args: Vec<Value>,
    this: Value,
    heap: crate::heap::Heap,
    fuel: crate::fuel::Fuel,
) -> crate::outcome::EvalResult;

impl Eq for Value {}

impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Undefined => f.write_str("undefined"),
            Self::Null => f.write_str("null"),
            Self::Boolean(b) => write!(f, "{b}"),
            Self::Number(n) => write!(f, "{n}"),
            Self::String(s) => write!(f, "{s:?}"),
            Self::Object(id) => write!(f, "{id}"),
            Self::Function(id) => write!(f, "{id}"),
            Self::Native(_) => f.write_str("function [native code]"),
        }
    }
}

/// A heap-allocated object: a string-keyed map of property values.  Arrays
/// are objects with numeric string keys plus a `length` property.
///
/// v0.3 adds a parallel accessor map: any key present in `accessors`
/// is a getter/setter property (the engine invokes the getter on
/// `obj.key` reads and the setter on `obj.key = value` writes).
/// Data and accessor maps are mutually exclusive by key -- writing a
/// data value via [`Self::with`] evicts any existing accessor on the
/// same key, and installing an accessor via [`Self::with_accessor`]
/// evicts any existing data value.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Object {
    properties: BTreeMap<String, Value>,
    accessors: BTreeMap<String, AccessorPair>,
}

/// The getter / setter pair backing an accessor property.  Either
/// half may be absent: a getter-only accessor returns `undefined`
/// on assignment in non-strict mode; a setter-only accessor returns
/// `undefined` on read.  Each function value is whatever
/// [`Value::Function`] or [`Value::Native`] the call site supplied.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AccessorPair {
    get: Option<Value>,
    set: Option<Value>,
}

impl AccessorPair {
    /// Build an accessor with the given getter / setter halves.
    #[must_use]
    pub fn new(get: Option<Value>, set: Option<Value>) -> Self {
        Self { get, set }
    }

    /// The getter, if any.
    #[must_use]
    pub fn get_fn(&self) -> Option<&Value> {
        self.get.as_ref()
    }

    /// The setter, if any.
    #[must_use]
    pub fn set_fn(&self) -> Option<&Value> {
        self.set.as_ref()
    }

    /// Return a new pair with the getter replaced.
    #[must_use]
    pub fn with_get(&self, get: Value) -> Self {
        Self {
            get: Some(get),
            set: self.set.clone(),
        }
    }

    /// Return a new pair with the setter replaced.
    #[must_use]
    pub fn with_set(&self, set: Value) -> Self {
        Self {
            get: self.get.clone(),
            set: Some(set),
        }
    }
}

impl Object {
    /// An empty object.
    #[must_use]
    pub fn empty() -> Self {
        Self::default()
    }

    /// Build an object from the given data-property map.  Accessor
    /// properties are not installed here; use
    /// [`Self::with_accessor`] for those.
    #[must_use]
    pub fn from_properties(properties: BTreeMap<String, Value>) -> Self {
        Self {
            properties,
            accessors: BTreeMap::new(),
        }
    }

    /// Look up a data property by name.  Returns `None` for absent
    /// keys and for accessor properties (use [`Self::accessor`] to
    /// reach those).
    #[must_use]
    pub fn get(&self, key: &str) -> Option<&Value> {
        self.properties.get(key)
    }

    /// All data properties in name order.  Accessor properties are
    /// not surfaced here; this preserves the v0.2 caller contract
    /// (e.g. `JSON.stringify` enumerates data properties only).
    #[must_use]
    pub fn properties(&self) -> &BTreeMap<String, Value> {
        &self.properties
    }

    /// Look up an accessor property by name.
    #[must_use]
    pub fn accessor(&self, key: &str) -> Option<&AccessorPair> {
        self.accessors.get(key)
    }

    /// All accessor properties in name order.
    #[must_use]
    pub fn accessors(&self) -> &BTreeMap<String, AccessorPair> {
        &self.accessors
    }

    /// Return a copy of the object with `key` set to a data property
    /// holding `value`.  Any existing accessor on `key` is evicted.
    #[must_use]
    pub fn with(&self, key: String, value: Value) -> Self {
        let mut next_props = self.properties.clone();
        let mut next_accs = self.accessors.clone();
        let _ = next_accs.remove(&key);
        let _ = next_props.insert(key, value);
        Self {
            properties: next_props,
            accessors: next_accs,
        }
    }

    /// Return a copy of the object with `key` set to the given
    /// accessor pair.  Any existing data value on `key` is evicted.
    #[must_use]
    pub fn with_accessor(&self, key: String, pair: AccessorPair) -> Self {
        let mut next_props = self.properties.clone();
        let mut next_accs = self.accessors.clone();
        let _ = next_props.remove(&key);
        let _ = next_accs.insert(key, pair);
        Self {
            properties: next_props,
            accessors: next_accs,
        }
    }
}

/// A function's static definition.  Captures the parameters, body, the
/// environment at the definition site (for closure semantics), and whether
/// the function is an arrow function (no own `this`).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FunctionDef {
    name: Option<Identifier>,
    params: Vec<Pattern>,
    body: FunctionBody,
    captured_env: Env,
    is_arrow: bool,
}

impl FunctionDef {
    /// Build a function definition.
    #[must_use]
    pub fn new(
        name: Option<Identifier>,
        params: Vec<Pattern>,
        body: FunctionBody,
        captured_env: Env,
        is_arrow: bool,
    ) -> Self {
        Self {
            name,
            params,
            body,
            captured_env,
            is_arrow,
        }
    }

    /// The function's name, if any.
    #[must_use]
    pub fn name(&self) -> Option<&Identifier> {
        self.name.as_ref()
    }

    /// The formal parameters.
    #[must_use]
    pub fn params(&self) -> &[Pattern] {
        &self.params
    }

    /// The body.
    #[must_use]
    pub fn body(&self) -> &FunctionBody {
        &self.body
    }

    /// The lexical environment at the definition site.
    #[must_use]
    pub fn captured_env(&self) -> &Env {
        &self.captured_env
    }

    /// Whether the function is an arrow function.
    #[must_use]
    pub fn is_arrow(&self) -> bool {
        self.is_arrow
    }
}

/// A function body: statements (for `function ...`) or an expression
/// (for concise arrow bodies, kept as a wrapping `Statement::Return` in
/// the unified `Statements` variant).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FunctionBody {
    /// A statement-list body.
    Statements(Vec<Statement>),
    /// An arrow body, retained so the evaluator can distinguish expression
    /// vs block forms.  Currently both lower to statements at evaluation
    /// time; the variant is kept for source fidelity.
    Arrow(Box<ArrowBody>),
}

impl FunctionBody {
    /// View the body as statements; an expression body is wrapped in a
    /// synthetic `Return` statement at evaluation.
    #[must_use]
    pub fn as_statements(&self) -> ArrowOrStatements<'_> {
        match self {
            Self::Statements(stmts) => ArrowOrStatements::Statements(stmts),
            Self::Arrow(body) => ArrowOrStatements::Arrow(body),
        }
    }
}

/// View result of [`FunctionBody::as_statements`].
pub enum ArrowOrStatements<'a> {
    /// Statement list (function declarations / non-concise arrow bodies).
    Statements(&'a [Statement]),
    /// Arrow body (concise expression or block).
    Arrow(&'a ArrowBody),
}