Skip to main content

sim_lib_lang_javascript/objects/
function.rs

1/// JavaScript property keys. Private names are deliberately class-scoped and
2/// cannot be manufactured from strings.
3#[derive(Clone, Debug, Eq, Hash, PartialEq)]
4pub enum JavascriptPropertyKey {
5    /// String property.
6    String(String),
7    /// Symbol identity (ordered after strings).
8    Symbol(u64),
9    /// Declared private name, paired with its declaring class identity.
10    Private {
11        /// Declaring class brand.
12        class: ManagedId,
13        /// Source-level declared name.
14        name: String,
15    },
16}
17/// The ordinary function forms admitted by this profile.
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum JavascriptFunctionKind {
20    /// Ordinary dynamically-received function.
21    Function,
22    /// Lexically-received arrow closure.
23    Arrow,
24    /// Class constructor, callable only through construction.
25    ClassConstructor,
26}
27
28/// JavaScript policy retained beside a language-neutral function plan.
29#[derive(Clone, Debug)]
30pub struct JavascriptFunctionPolicy {
31    /// Function form and its receiver policy.
32    pub kind: JavascriptFunctionKind,
33    /// Whether `new` is legal.
34    pub constructable: bool,
35    /// Declaration-time default values, keyed by frozen parameter name.
36    pub defaults: BTreeMap<Symbol, JavascriptValue>,
37    /// Whether invocation creates an async continuation.
38    pub asynchronous: bool,
39    /// Whether invocation creates a generator frame.
40    pub generator: bool,
41    /// Stable realm identity used by JavaScript intrinsic lookup.
42    pub realm: Symbol,
43    /// Stable source origin used for JavaScript errors.
44    pub error_origin: String,
45}
46
47/// A JavaScript call-binding failure with its guest-owned source origin.
48#[derive(Clone, Debug, Eq, PartialEq)]
49pub struct JavascriptCallError {
50    /// Stable source origin.
51    pub origin: String,
52    /// Human-readable ECMAScript binding failure.
53    pub message: String,
54}
55
56/// Inspectable callable metadata. Shared plans and capture cells own neutral
57/// mechanics; this record retains only JavaScript and object-system policy.
58#[derive(Clone, Debug)]
59pub struct JavascriptFunction {
60    plan: FunctionPlan,
61    captures: Vec<CapturedBinding>,
62    policy: JavascriptFunctionPolicy,
63    /// Declared private names for a class constructor.
64    pub private_names: Vec<String>,
65}
66
67impl JavascriptFunction {
68    /// Freezes a JavaScript function over an already validated neutral plan.
69    pub fn new(
70        plan: FunctionPlan,
71        captures: Vec<CapturedBinding>,
72        policy: JavascriptFunctionPolicy,
73        private_names: Vec<String>,
74    ) -> Result<Self, InstanceError> {
75        validate_capture_bindings(&plan, &captures)?;
76        Ok(Self {
77            plan,
78            captures,
79            policy,
80            private_names,
81        })
82    }
83
84    /// Borrows the shared immutable declaration plan.
85    pub const fn plan(&self) -> &FunctionPlan {
86        &self.plan
87    }
88
89    /// Borrows exact capture cells in frozen plan order.
90    pub fn captures(&self) -> &[CapturedBinding] {
91        &self.captures
92    }
93
94    /// Borrows JavaScript-only callable policy.
95    pub const fn policy(&self) -> &JavascriptFunctionPolicy {
96        &self.policy
97    }
98
99    /// Applies ECMAScript positional, default, and rest rules to raw values.
100    pub fn bind_arguments(
101        &self,
102        arguments: &[JavascriptValue],
103    ) -> Result<BTreeMap<Symbol, Vec<JavascriptValue>>, JavascriptCallError> {
104        let mut bound = BTreeMap::new();
105        let mut at = 0;
106        for parameter in self.plan.parameters() {
107            let values = match parameter.kind() {
108                ParameterKind::Remainder => {
109                    let values = arguments[at..].to_vec();
110                    at = arguments.len();
111                    values
112                }
113                ParameterKind::Required => {
114                    let Some(value) = arguments.get(at) else {
115                        return self
116                            .bind_error(format!("missing required argument {}", parameter.name()));
117                    };
118                    at += 1;
119                    vec![value.clone()]
120                }
121                ParameterKind::Optional => {
122                    let supplied = arguments.get(at);
123                    let value = match supplied {
124                        Some(JavascriptValue::Undefined) | None => self
125                            .policy
126                            .defaults
127                            .get(parameter.name())
128                            .cloned()
129                            .or_else(|| supplied.cloned()),
130                        Some(value) => Some(value.clone()),
131                    };
132                    if supplied.is_some() {
133                        at += 1;
134                    }
135                    vec![value.unwrap_or(JavascriptValue::Undefined)]
136                }
137            };
138            bound.insert(parameter.name().clone(), values);
139        }
140        if at != arguments.len() {
141            return self.bind_error("too many arguments".into());
142        }
143        Ok(bound)
144    }
145
146    fn bind_error<T>(&self, message: String) -> Result<T, JavascriptCallError> {
147        Err(JavascriptCallError {
148            origin: self.policy.error_origin.clone(),
149            message,
150        })
151    }
152}
153
154/// Receiver selected for a call.
155#[derive(Clone, Debug, PartialEq)]
156pub enum JavascriptThis {
157    /// Arrow functions retain their lexical receiver.
158    Lexical(JavascriptValue),
159    /// Ordinary calls receive the call-site receiver.
160    Dynamic(JavascriptValue),
161}
162
163/// Explicit object-model gaps. These are queryable rather than silent partial
164/// emulation of invariants that ordinary objects cannot satisfy.
165#[derive(Clone, Copy, Debug, Eq, PartialEq)]
166pub enum JavascriptObjectGap {
167    /// Proxy trap invariants are not emulated by ordinary descriptors.
168    ProxyInvariants,
169    /// Host and specification exotic internal methods are unsupported.
170    ExoticObjectInvariants,
171}
172
173/// Error from JavaScript-owned prototype or descriptor policy.
174#[derive(Clone, Debug, Eq, PartialEq)]
175pub enum JavascriptObjectError {
176    /// Shared managed arena rejected an operation.
177    Arena(ArenaError),
178    /// Shared managed node rejected a checked edge mutation.
179    ManagedGraph(JavascriptManagedMutationError),
180    /// Descriptor invariant was violated.
181    Descriptor(DefineError),
182    /// Prototype traversal or interception exceeded its explicit bound.
183    Access,
184    /// A prototype cycle was requested.
185    PrototypeCycle,
186    /// `new` was applied to a non-constructor.
187    NotConstructor,
188    /// A private name was used outside its declaring class.
189    PrivateBrand,
190}
191impl From<ArenaError> for JavascriptObjectError {
192    fn from(v: ArenaError) -> Self {
193        Self::Arena(v)
194    }
195}
196
197impl From<JavascriptManagedMutationError> for JavascriptObjectError {
198    fn from(value: JavascriptManagedMutationError) -> Self {
199        Self::ManagedGraph(value)
200    }
201}
202impl From<DefineError> for JavascriptObjectError {
203    fn from(v: DefineError) -> Self {
204        Self::Descriptor(v)
205    }
206}
207
208#[derive(Clone, Debug, PartialEq)]
209struct Accessor {
210    get: Option<JavascriptValue>,
211    set: bool,
212}
213#[derive(Default)]
214struct Hooks {
215    writes: Vec<(ManagedId, JavascriptPropertyKey, JavascriptValue)>,
216}
217impl PropertyHook<ManagedId, JavascriptPropertyKey, JavascriptValue, Accessor> for Hooks {
218    type Error = ();
219    fn get(
220        &mut self,
221        _: &mut AccessContext<ManagedId, JavascriptPropertyKey>,
222        hook: &Accessor,
223        _: &ManagedId,
224        _: &JavascriptPropertyKey,
225    ) -> Result<JavascriptValue, AccessError<()>> {
226        hook.get.clone().ok_or(AccessError::Hook(()))
227    }
228    fn set(
229        &mut self,
230        _: &mut AccessContext<ManagedId, JavascriptPropertyKey>,
231        hook: &Accessor,
232        receiver: &ManagedId,
233        key: &JavascriptPropertyKey,
234        value: JavascriptValue,
235    ) -> Result<(), AccessError<()>> {
236        if !hook.set {
237            return Err(AccessError::Hook(()));
238        }
239        self.writes.push((*receiver, key.clone(), value));
240        Ok(())
241    }
242}