Skip to main content

sim_lib_dispatch/
property.rs

1//! Language-neutral own-property storage and bounded descriptor execution.
2
3use std::{collections::HashSet, hash::Hash};
4
5/// A stored data-property descriptor.
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub struct DataDescriptor<V> {
8    /// Stored value.
9    pub value: V,
10    /// Whether assignment may replace the stored value.
11    pub writable: bool,
12    /// Whether enumeration policy may expose the property.
13    pub enumerable: bool,
14    /// Whether the property may be deleted or incompatibly redefined.
15    pub configurable: bool,
16}
17
18/// A stored accessor-property descriptor.
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct AccessorDescriptor<H> {
21    /// Caller-defined getter hook token.
22    pub get: Option<H>,
23    /// Caller-defined setter hook token.
24    pub set: Option<H>,
25    /// Whether enumeration policy may expose the property.
26    pub enumerable: bool,
27    /// Whether the property may be deleted or incompatibly redefined.
28    pub configurable: bool,
29}
30
31/// A data or accessor descriptor. The enum prevents mixed invalid records.
32#[derive(Clone, Debug, Eq, PartialEq)]
33pub enum Descriptor<V, H> {
34    /// Directly stored data.
35    Data(DataDescriptor<V>),
36    /// Caller-interpreted access hooks.
37    Accessor(AccessorDescriptor<H>),
38}
39
40impl<V, H> Descriptor<V, H> {
41    fn configurable(&self) -> bool {
42        match self {
43            Self::Data(value) => value.configurable,
44            Self::Accessor(value) => value.configurable,
45        }
46    }
47
48    fn enumerable(&self) -> bool {
49        match self {
50            Self::Data(value) => value.enumerable,
51            Self::Accessor(value) => value.enumerable,
52        }
53    }
54}
55
56/// Failure to define or delete an own property.
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub enum DefineError {
59    /// A non-configurable property cannot accept the requested replacement.
60    InvariantViolation,
61}
62
63/// Kind of guarded accessor invocation.
64#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
65pub enum AccessKind {
66    /// Getter invocation.
67    Get,
68    /// Setter invocation.
69    Set,
70}
71
72/// Failure during bounded traversal or accessor interception.
73#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74pub enum AccessError<E> {
75    /// The explicit work budget was exhausted.
76    BudgetExhausted,
77    /// The same hook was entered recursively for the same receiver and key.
78    RecursiveReentry,
79    /// A caller-supplied hook failed.
80    Hook(E),
81}
82
83/// Budget and reentry state shared by one property operation.
84pub struct AccessContext<O, K> {
85    remaining: usize,
86    active: HashSet<(AccessKind, O, K)>,
87}
88
89impl<O, K> AccessContext<O, K>
90where
91    O: Clone + Eq + Hash,
92    K: Clone + Eq + Hash,
93{
94    /// Creates an operation context with an exact work allowance.
95    pub fn new(work_budget: usize) -> Self {
96        Self {
97            remaining: work_budget,
98            active: HashSet::new(),
99        }
100    }
101
102    /// Returns the unspent work allowance.
103    pub fn remaining(&self) -> usize {
104        self.remaining
105    }
106
107    fn charge<E>(&mut self) -> Result<(), AccessError<E>> {
108        self.remaining = self
109            .remaining
110            .checked_sub(1)
111            .ok_or(AccessError::BudgetExhausted)?;
112        Ok(())
113    }
114
115    /// Runs one guarded interception. Nested hooks may use this method to
116    /// preserve the same budget and reentry invariant.
117    pub fn intercept<T, E>(
118        &mut self,
119        kind: AccessKind,
120        receiver: &O,
121        key: &K,
122        call: impl FnOnce(&mut Self) -> Result<T, AccessError<E>>,
123    ) -> Result<T, AccessError<E>> {
124        self.charge()?;
125        let signature = (kind, receiver.clone(), key.clone());
126        if !self.active.insert(signature.clone()) {
127            return Err(AccessError::RecursiveReentry);
128        }
129        let result = call(self);
130        self.active.remove(&signature);
131        result
132    }
133}
134
135/// Caller-owned interpretation of accessor hook tokens.
136pub trait PropertyHook<O, K, V, H> {
137    /// Hook-specific error.
138    type Error;
139
140    /// Invokes a getter with the original receiver, not merely the owner where
141    /// the descriptor was found.
142    fn get(
143        &mut self,
144        context: &mut AccessContext<O, K>,
145        hook: &H,
146        receiver: &O,
147        key: &K,
148    ) -> Result<V, AccessError<Self::Error>>;
149
150    /// Invokes a setter with the original receiver.
151    fn set(
152        &mut self,
153        context: &mut AccessContext<O, K>,
154        hook: &H,
155        receiver: &O,
156        key: &K,
157        value: V,
158    ) -> Result<(), AccessError<Self::Error>>;
159}
160
161#[derive(Clone, Debug)]
162struct OwnProperty<K, V, H> {
163    key: K,
164    descriptor: Descriptor<V, H>,
165}
166
167type PropertyObject<O, K, V, H> = (O, Vec<OwnProperty<K, V, H>>);
168
169/// Ordered own-property records keyed by caller-owned object identities.
170///
171/// This store deliberately has no parent pointer and no built-in traversal or
172/// precedence rule. Callers supply an already-policy-ordered owner slice for
173/// every inherited operation.
174#[derive(Clone, Debug, Default)]
175pub struct PropertyStore<O, K, V, H> {
176    objects: Vec<PropertyObject<O, K, V, H>>,
177}
178
179impl<O, K, V, H> PropertyStore<O, K, V, H> {
180    /// Creates an empty property store without imposing `Default` on its
181    /// caller-owned identity, key, value, or hook types.
182    pub const fn new() -> Self {
183        Self {
184            objects: Vec::new(),
185        }
186    }
187}
188
189impl<O, K, V, H> PropertyStore<O, K, V, H>
190where
191    O: Clone + Eq + Hash,
192    K: Clone + Eq + Hash,
193    V: Clone + PartialEq,
194    H: Clone + PartialEq,
195{
196    fn properties(&self, owner: &O) -> Option<&[OwnProperty<K, V, H>]> {
197        self.objects
198            .iter()
199            .find(|(candidate, _)| candidate == owner)
200            .map(|(_, properties)| properties.as_slice())
201    }
202
203    fn properties_mut(&mut self, owner: &O) -> &mut Vec<OwnProperty<K, V, H>> {
204        if let Some(index) = self
205            .objects
206            .iter()
207            .position(|(candidate, _)| candidate == owner)
208        {
209            return &mut self.objects[index].1;
210        }
211        self.objects.push((owner.clone(), Vec::new()));
212        &mut self.objects.last_mut().expect("object was inserted").1
213    }
214
215    /// Returns an own descriptor without invoking it.
216    pub fn own(&self, owner: &O, key: &K) -> Option<&Descriptor<V, H>> {
217        self.properties(owner)?
218            .iter()
219            .find(|property| &property.key == key)
220            .map(|property| &property.descriptor)
221    }
222
223    /// Defines an own property, retaining its original key position on update.
224    pub fn define(
225        &mut self,
226        owner: &O,
227        key: K,
228        descriptor: Descriptor<V, H>,
229    ) -> Result<(), DefineError> {
230        let properties = self.properties_mut(owner);
231        if let Some(property) = properties.iter_mut().find(|property| property.key == key) {
232            if !compatible_redefinition(&property.descriptor, &descriptor) {
233                return Err(DefineError::InvariantViolation);
234            }
235            property.descriptor = descriptor;
236        } else {
237            properties.push(OwnProperty { key, descriptor });
238        }
239        Ok(())
240    }
241
242    /// Deletes an own property. Missing properties succeed.
243    pub fn delete(&mut self, owner: &O, key: &K) -> Result<bool, DefineError> {
244        let Some((_, properties)) = self
245            .objects
246            .iter_mut()
247            .find(|(candidate, _)| candidate == owner)
248        else {
249            return Ok(false);
250        };
251        let Some(index) = properties.iter().position(|property| &property.key == key) else {
252            return Ok(false);
253        };
254        if !properties[index].descriptor.configurable() {
255            return Err(DefineError::InvariantViolation);
256        }
257        properties.remove(index);
258        Ok(true)
259    }
260
261    /// Returns own keys in stable definition order, optionally filtering out
262    /// non-enumerable records. Delete followed by define appends a fresh key.
263    pub fn own_keys(&self, owner: &O, enumerable_only: bool) -> Vec<K> {
264        self.properties(owner)
265            .unwrap_or_default()
266            .iter()
267            .filter(|property| !enumerable_only || property.descriptor.enumerable())
268            .map(|property| property.key.clone())
269            .collect()
270    }
271
272    /// Reads along an explicitly supplied owner order. Duplicate owners are
273    /// skipped, making cyclic caller-produced orders safe.
274    pub fn get<E>(
275        &self,
276        owners: &[O],
277        receiver: &O,
278        key: &K,
279        context: &mut AccessContext<O, K>,
280        hooks: &mut impl PropertyHook<O, K, V, H, Error = E>,
281    ) -> Result<Option<V>, AccessError<E>> {
282        let mut visited = HashSet::new();
283        for owner in owners {
284            context.charge()?;
285            if !visited.insert(owner.clone()) {
286                continue;
287            }
288            let Some(descriptor) = self.own(owner, key) else {
289                continue;
290            };
291            return match descriptor {
292                Descriptor::Data(data) => Ok(Some(data.value.clone())),
293                Descriptor::Accessor(accessor) => match &accessor.get {
294                    Some(hook) => context
295                        .intercept(AccessKind::Get, receiver, key, |context| {
296                            hooks.get(context, hook, receiver, key)
297                        })
298                        .map(Some),
299                    None => Ok(None),
300                },
301            };
302        }
303        Ok(None)
304    }
305
306    /// Assigns through the first descriptor in an explicit owner order.
307    /// Writable data is updated on its owner; accessor setters receive the
308    /// original receiver. Missing and read-only properties return `Ok(false)`.
309    pub fn set<E>(
310        &mut self,
311        owners: &[O],
312        receiver: &O,
313        key: &K,
314        value: V,
315        context: &mut AccessContext<O, K>,
316        hooks: &mut impl PropertyHook<O, K, V, H, Error = E>,
317    ) -> Result<bool, AccessError<E>> {
318        let mut visited = HashSet::new();
319        for owner in owners {
320            context.charge()?;
321            if !visited.insert(owner.clone()) {
322                continue;
323            }
324            let Some(descriptor) = self.own(owner, key).cloned() else {
325                continue;
326            };
327            return match descriptor {
328                Descriptor::Data(data) if data.writable => {
329                    let property = self
330                        .properties_mut(owner)
331                        .iter_mut()
332                        .find(|property| &property.key == key)
333                        .expect("descriptor was found");
334                    let Descriptor::Data(data) = &mut property.descriptor else {
335                        unreachable!("cloned descriptor kind remains stable")
336                    };
337                    data.value = value;
338                    Ok(true)
339                }
340                Descriptor::Data(_) => Ok(false),
341                Descriptor::Accessor(accessor) => match accessor.set {
342                    Some(hook) => context
343                        .intercept(AccessKind::Set, receiver, key, |context| {
344                            hooks.set(context, &hook, receiver, key, value)
345                        })
346                        .map(|()| true),
347                    None => Ok(false),
348                },
349            };
350        }
351        Ok(false)
352    }
353}
354
355fn compatible_redefinition<V: PartialEq, H: PartialEq>(
356    current: &Descriptor<V, H>,
357    replacement: &Descriptor<V, H>,
358) -> bool {
359    if current.configurable() {
360        return true;
361    }
362    match (current, replacement) {
363        (Descriptor::Data(old), Descriptor::Data(new)) => {
364            !new.configurable
365                && old.enumerable == new.enumerable
366                && (old.writable || !new.writable)
367                && (old.writable || old.value == new.value)
368        }
369        (Descriptor::Accessor(old), Descriptor::Accessor(new)) => {
370            !new.configurable
371                && old.enumerable == new.enumerable
372                && old.get == new.get
373                && old.set == new.set
374        }
375        _ => false,
376    }
377}