Skip to main content

sui_display/v2/
interpreter.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::borrow::Cow;
5use std::mem;
6use std::sync::Arc;
7
8use mysten_common::ZipDebugEqIteratorExt;
9
10use dashmap::DashMap;
11use futures::future::OptionFuture;
12use futures::future::join_all;
13use futures::join;
14use move_core_types::account_address::AccountAddress;
15use sui_types::dynamic_field::DynamicFieldType;
16use sui_types::dynamic_field::visitor as DFV;
17use sui_types::dynamic_field::visitor::FieldVisitor;
18
19use crate::v2::error::FormatError;
20use crate::v2::parser as P;
21use crate::v2::value as V;
22use crate::v2::visitor::extractor::Extractor;
23
24/// The interpreter is responsible for evaluating expressions inside format strings into values.
25pub struct Interpreter<S: V::Store> {
26    store: S,
27
28    /// Cache of the objects that have been fetched so far. This cache is never evicted -- it is
29    /// used to keep objects alive for the lifetime of the interpreter.
30    ///
31    /// The cache is keyed by the object ID and whether the object is being accessed as a child of
32    /// another object (bounded by its version -- `true`), or at its latest version (`false`).
33    cache: DashMap<(AccountAddress, bool), Option<Arc<V::OwnedSlice>>>,
34
35    root: V::OwnedSlice,
36}
37
38impl<S: V::Store> Interpreter<S> {
39    /// Create a new interpreter instance. `root` is the contents (bytes and layout) of an object
40    /// that acts as the root of all field accesses. `store` is used to fetch additional objects as
41    /// needed.
42    pub fn new(root: V::OwnedSlice, store: S) -> Self {
43        Self {
44            store,
45            cache: DashMap::new(),
46            root,
47        }
48    }
49
50    /// Entrypoint to evaluate a single format string, represented as a sequence of its strands.
51    /// Returns evaluated strands that can then be formatted.
52    pub(crate) async fn eval_strands<'s>(
53        &'s self,
54        strands: &'s [P::Strand<'s>],
55    ) -> Result<Option<Vec<V::Strand<'s>>>, FormatError> {
56        join_all(strands.iter().map(|strand| async move {
57            match strand {
58                P::Strand::Text(s) => Ok(Some(V::Strand::Text(s))),
59                P::Strand::Expr(P::Expr {
60                    offset,
61                    alternates,
62                    transform,
63                }) => Ok(self
64                    .eval_alts(alternates)
65                    .await?
66                    .map(move |value| V::Strand::Value {
67                        value,
68                        transform: *transform,
69                        offset: *offset,
70                    })),
71            }
72        }))
73        .await
74        .into_iter()
75        .collect()
76    }
77
78    /// Evaluate each `chain` in turn until one succeeds (produces a non-`None` value).
79    ///
80    /// Returns the result from the first chain that produces a value, or `Ok(None)` if none do.
81    /// Propagates any errors encountered during evaluation.
82    async fn eval_alts<'s>(
83        &'s self,
84        alts: &'s [P::Chain<'s>],
85    ) -> Result<Option<V::Value<'s>>, FormatError> {
86        for chain in alts {
87            if let Some(v) = self.eval_chain(chain).await? {
88                return Ok(Some(v));
89            }
90        }
91
92        Ok(None)
93    }
94
95    /// Evaluate a chain of field accesses against a root expression.
96    ///
97    /// If the chain does not have a root expression, the object being displayed is used as the
98    /// root. The root is evaluated first, and then each successive accessor is applied to it.
99    ///
100    /// This function returns `Ok(Some(value))` if all nested accesses succeed. An access succeeds
101    /// when the accessor evaluates to `Ok(Some(access))` and the part of the value it is
102    /// describing exists.
103    ///
104    /// Any errors encountered during evaluation are propagated.
105    pub(crate) async fn eval_chain<'s>(
106        &'s self,
107        chain: &'s P::Chain<'s>,
108    ) -> Result<Option<V::Value<'s>>, FormatError> {
109        use V::Accessor as A;
110        use V::Value as VV;
111
112        // Evaluate the root (if it is provided) and the accessors, concurrently.
113        let root: OptionFuture<_> = chain
114            .root
115            .as_ref()
116            .map(|literal| self.eval_literal(literal))
117            .into();
118
119        let accessors = join_all(chain.accessors.iter().map(|a| self.eval_accessor(a)));
120        let (root, accessors) = join!(root, accessors,);
121
122        let mut root = match root {
123            Some(Ok(Some(root))) => root,
124            Some(Ok(None)) => return Ok(None),
125            Some(Err(e)) => return Err(e),
126
127            // If a root was not provided, the object being displayed is the root.
128            None => VV::Slice(self.root.as_slice()),
129        };
130
131        let Some(mut accessors) = accessors
132            .into_iter()
133            .collect::<Result<Option<Vec<_>>, _>>()?
134        else {
135            return Ok(None);
136        };
137
138        accessors.reverse();
139        while let Some(accessor) = accessors.last() {
140            match (root, accessor) {
141                (VV::Address(a), A::DFIndex(i)) => {
142                    let df_id = i.derive_dynamic_field_id(a.bytes)?.into();
143                    let Some(slice) = self.fetch(df_id, a.scoped).await? else {
144                        return Ok(None);
145                    };
146
147                    let field = match FieldVisitor::deserialize(slice.bytes, slice.layout) {
148                        Ok(f) => f,
149                        Err(DFV::Error::Visitor(e)) => return Err(FormatError::Visitor(e)),
150                        Err(_) => return Ok(None),
151                    };
152
153                    if field.kind != DynamicFieldType::DynamicField {
154                        return Ok(None);
155                    }
156
157                    accessors.pop();
158                    root = VV::Slice(V::Slice {
159                        bytes: field.value_bytes,
160                        layout: field.value_layout,
161                        scoped: slice.scoped,
162                    });
163                }
164
165                (VV::Address(a), A::DOFIndex(i)) => {
166                    let df_id = i.derive_dynamic_object_field_id(a.bytes)?.into();
167                    let Some(slice) = self.fetch(df_id, a.scoped).await? else {
168                        return Ok(None);
169                    };
170
171                    let field = match FieldVisitor::deserialize(slice.bytes, slice.layout) {
172                        Ok(f) => f,
173                        Err(DFV::Error::Visitor(e)) => return Err(FormatError::Visitor(e)),
174                        Err(_) => return Ok(None),
175                    };
176
177                    if field.kind != DynamicFieldType::DynamicObject {
178                        return Ok(None);
179                    }
180
181                    let Ok(id) = AccountAddress::from_bytes(field.value_bytes) else {
182                        return Ok(None);
183                    };
184
185                    let Some(value_slice) = self.fetch(id, a.scoped).await? else {
186                        return Ok(None);
187                    };
188
189                    accessors.pop();
190                    root = VV::Slice(value_slice);
191                }
192
193                (VV::Address(a), A::Derived(i)) => {
194                    let id = i.derive_object_id(a.bytes)?.into();
195                    let Some(slice) = self.fetch(id, a.scoped).await? else {
196                        return Ok(None);
197                    };
198
199                    accessors.pop();
200                    root = VV::Slice(slice);
201                }
202
203                // Fetch a single byte from a byte array, as long as the accessor evaluates to a
204                // numeric index.
205                (VV::Bytes(bs), accessor) => {
206                    let Some(&b) = accessor.as_numeric_index().and_then(|i| bs.get(i as usize))
207                    else {
208                        return Ok(None);
209                    };
210
211                    accessors.pop();
212                    root = VV::U8(b);
213                }
214
215                // `V::String` corresponds to `std::string::String` in Move, which contains a
216                // single `bytes` field.
217                (VV::String(s), A::Field(f)) if *f == "bytes" => {
218                    accessors.pop();
219                    root = VV::Bytes(s)
220                }
221
222                // Fetch an element from a vector literal, as long as the accessor evaluates to a
223                // numeric index.
224                (VV::Vector(mut xs), accessor) => {
225                    let Some(i) = accessor.as_numeric_index() else {
226                        return Ok(None);
227                    };
228
229                    accessors.pop();
230                    root = if i as usize >= xs.elements.len() {
231                        return Ok(None);
232                    } else {
233                        xs.elements.swap_remove(i as usize)
234                    };
235                }
236
237                // Fetch a field from a struct or enum literal.
238                (VV::Struct(V::Struct { fields, .. }) | VV::Enum(V::Enum { fields, .. }), a) => {
239                    let Some(value) = fields.get(a) else {
240                        return Ok(None);
241                    };
242
243                    accessors.pop();
244                    root = value;
245                }
246
247                // Use the remaining accessors to extract a value from a slice of a serialized
248                // value. This can consume multiple accessors, but will pause if it encounters a
249                // dynamic (object) field access.
250                (VV::Slice(slice), _) => {
251                    let Some(mut value) = Extractor::deserialize_slice(slice, &mut accessors)?
252                    else {
253                        return Ok(None);
254                    };
255
256                    // The extractor does not track scoping -- attach that information on the side.
257                    value.set_scope(slice.scoped);
258                    root = value;
259                }
260
261                // Scalar values do not support accessors.
262                (
263                    VV::Address(_)
264                    | VV::Bool(_)
265                    | VV::String(_)
266                    | VV::U8(_)
267                    | VV::U16(_)
268                    | VV::U32(_)
269                    | VV::U64(_)
270                    | VV::U128(_)
271                    | VV::U256(_),
272                    _,
273                ) => return Ok(None),
274            }
275        }
276
277        Ok(Some(root))
278    }
279
280    /// Evaluates the contents of an accessor to a value.
281    ///
282    /// Returns `Ok(Some(value))` if the accessor evaluates to a value, otherwise it propagates
283    /// errors or `None` values.
284    async fn eval_accessor<'s>(
285        &'s self,
286        acc: &'s P::Accessor<'s>,
287    ) -> Result<Option<V::Accessor<'s>>, FormatError> {
288        use P::Accessor as PA;
289        use V::Accessor as VA;
290
291        Ok(match acc {
292            PA::Field(f) => Some(VA::Field(f.as_str())),
293            PA::Positional(i) => Some(VA::Positional(*i)),
294            PA::Index(chain) => Box::pin(self.eval_chain(chain)).await?.map(VA::Index),
295            PA::DFIndex(chain) => Box::pin(self.eval_chain(chain)).await?.map(VA::DFIndex),
296            PA::DOFIndex(chain) => Box::pin(self.eval_chain(chain)).await?.map(VA::DOFIndex),
297            PA::Derived(chain) => Box::pin(self.eval_chain(chain)).await?.map(VA::Derived),
298        })
299    }
300
301    /// Evaluate literals to values.
302    ///
303    /// Returns `Ok(Some(value))` if all parts of the literal evaluate to `Ok(Some(value))`,
304    /// otherwise it propagates errors or `None` values.
305    pub(crate) async fn eval_literal<'s>(
306        &'s self,
307        lit: &'s P::Literal<'s>,
308    ) -> Result<Option<V::Value<'s>>, FormatError> {
309        use P::Literal as L;
310        use V::Value as VV;
311
312        Ok(match lit {
313            L::Self_ => Some(VV::Slice(self.root.as_slice())),
314            L::Address(a) => Some(VV::Address(V::Address::latest(*a))),
315            L::Bool(b) => Some(VV::Bool(*b)),
316            L::U8(n) => Some(VV::U8(*n)),
317            L::U16(n) => Some(VV::U16(*n)),
318            L::U32(n) => Some(VV::U32(*n)),
319            L::U64(n) => Some(VV::U64(*n)),
320            L::U128(n) => Some(VV::U128(*n)),
321            L::U256(n) => Some(VV::U256(*n)),
322            L::ByteArray(bs) => Some(VV::Bytes(bs.into())),
323
324            L::String(s) => match s.clone() {
325                Cow::Borrowed(s) => Some(VV::String(Cow::Borrowed(s.as_bytes()))),
326                Cow::Owned(s) => Some(VV::String(Cow::Owned(s.into_bytes()))),
327            },
328
329            L::Vector(v) => self.eval_chains(&v.elements).await.and_then(|elements| {
330                let Some(elements) = elements else {
331                    return Ok(None);
332                };
333
334                // Evaluate the vector's element type and check that it is consistent across all
335                // elements.
336                let type_ = if let Some(explicit) = &v.type_ {
337                    Cow::Borrowed(explicit)
338                } else if let Some(first) = elements.first() {
339                    Cow::Owned(first.type_())
340                } else {
341                    return Err(FormatError::VectorNoType);
342                };
343
344                for e in &elements {
345                    let element_type = e.type_();
346                    if element_type != *type_ {
347                        return Err(FormatError::VectorTypeMismatch {
348                            offset: v.offset,
349                            this: type_.into_owned(),
350                            that: element_type,
351                        });
352                    }
353                }
354
355                Ok(Some(VV::Vector(V::Vector { type_, elements })))
356            })?,
357
358            L::Struct(s) => self.eval_fields(&s.fields).await?.map(|fields| {
359                VV::Struct(V::Struct {
360                    type_: &s.type_,
361                    fields,
362                })
363            }),
364
365            L::Enum(e) => self.eval_fields(&e.fields).await?.map(|fields| {
366                VV::Enum(V::Enum {
367                    type_: &e.type_,
368                    variant_name: e.variant_name,
369                    variant_index: e.variant_index,
370                    fields,
371                })
372            }),
373        })
374    }
375
376    /// Evaluate the fields of a struct or enum literal, concurrently.
377    ///
378    /// Returns `Ok(Some(fields))` if all the field values evaluate to `Ok(Some(value))`, otherwise
379    /// it propagates errors or `None` values.
380    async fn eval_fields<'s>(
381        &'s self,
382        field: &'s P::Fields<'s>,
383    ) -> Result<Option<V::Fields<'s>>, FormatError> {
384        Ok(match field {
385            P::Fields::Positional(fs) => self.eval_chains(fs).await?.map(V::Fields::Positional),
386            P::Fields::Named(fs) => self
387                .eval_chains(fs.iter().map(|(_, f)| f))
388                .await?
389                .map(|vs| V::Fields::Named(fs.iter().map(|(n, _)| *n).zip_debug_eq(vs).collect())),
390        })
391    }
392
393    /// Evaluate multiple chains concurrently.
394    ///
395    /// If all chains evaluate to `Ok(Some(value))`, returns `Some(vec![value, ...])`, otherwise it
396    /// propagates errors or `None` values.
397    async fn eval_chains<'s>(
398        &'s self,
399        chains: impl IntoIterator<Item = &'s P::Chain<'s>>,
400    ) -> Result<Option<Vec<V::Value<'s>>>, FormatError> {
401        let values = chains
402            .into_iter()
403            .map(|chain| Box::pin(self.eval_chain(chain)));
404
405        join_all(values).await.into_iter().collect()
406    }
407
408    /// Fetch an object from the store, caching the result.
409    ///
410    /// Accepts the object's ID and whether the fetch should be scoped by the store's parent object
411    /// (if the store supports this concept).
412    ///
413    /// Returns a `Slice<'s>` that borrows from the cached data. The returned slice is guaranteed
414    /// to remain valid for the lifetime 's because the cache (and the Arc it contains) lives for
415    /// the entire lifetime of the Interpreter.
416    async fn fetch<'s>(
417        &'s self,
418        id: AccountAddress,
419        scoped: bool,
420    ) -> Result<Option<V::Slice<'s>>, FormatError> {
421        let key = (id, scoped);
422        let owned = if let Some(cached) = self.cache.get(&key) {
423            cached.clone()
424        } else {
425            let loaded = if scoped {
426                self.store.scoped(id).await
427            } else {
428                self.store.latest(id).await
429            }
430            .map_err(|e| FormatError::Store(Arc::new(e)))?
431            .map(|(layout, bytes)| {
432                Arc::new(V::OwnedSlice {
433                    layout,
434                    bytes,
435                    scoped,
436                })
437            });
438
439            self.cache.entry(key).or_insert(loaded).clone()
440        };
441
442        let Some(owned) = owned.as_ref() else {
443            return Ok(None);
444        };
445
446        // SAFETY: Extending the lifetime of the slice from the reference (local to this
447        // scope), to the lifetime of the interpreter.  This is safe because the reference is
448        // pointing into an Arc that is owned by the interpreter.
449        let slice = owned.as_slice();
450        Ok(Some(unsafe {
451            mem::transmute::<V::Slice<'_>, V::Slice<'s>>(slice)
452        }))
453    }
454}