Skip to main content

rakata_formats/gff/
walk.rs

1//! Every value in a tree, with the address it sits at.
2//!
3//! # Why this is here rather than in each consumer
4//!
5//! Three callers want the same traversal: a panel drawing the raw tree, a
6//! search over it, and a cross-reference tool asking where a resource is
7//! named. Written three times it goes wrong in the same two places each time,
8//! nested lists and struct-typed fields, and the three drift apart quietly
9//! because each is only ever checked against whatever its author had open.
10//!
11//! # The addresses are the ones everything else is keyed by
12//!
13//! A yielded [`GffPath`] is the same address
14//! [`GffDocument::changes`](crate::GffDocument::changes) keys its map by and
15//! `rakata-lint` anchors a finding on. So a search hit, a change row and a
16//! diagnostic are one kind of key, and a consumer holding any of the three can
17//! navigate to the others without translating.
18//!
19//! That is worth more than it sounds. It is also the thing most likely to rot,
20//! which is why the test beside this resolves every path the walk produces
21//! back through [`GffStruct::get`] and compares the value.
22
23use super::{GffPath, GffPathSegment, GffStruct, GffValue};
24
25/// A pending struct: where it sits, what it is, and how far the walk has got.
26type Frame<'a> = (GffPath, &'a GffStruct, usize);
27
28/// Every value in a GFF tree, in the order the file carries them.
29///
30/// Built by [`GffStruct::walk`].
31#[derive(Debug, Clone)]
32pub struct GffWalk<'a> {
33    /// Innermost last. A struct is pushed when its own field is yielded, so
34    /// the next step descends into it before moving on to a sibling.
35    stack: Vec<Frame<'a>>,
36}
37
38impl<'a> Iterator for GffWalk<'a> {
39    type Item = (GffPath, &'a GffValue);
40
41    fn next(&mut self) -> Option<Self::Item> {
42        loop {
43            // The struct reference is copied out of the frame so what it lends
44            // outlives the borrow of the stack, which the push below needs.
45            let (structure, at, path) = {
46                let frame = self.stack.last_mut()?;
47                let Some(field) = frame.1.fields.get(frame.2) else {
48                    self.stack.pop();
49                    continue;
50                };
51                let path = frame.0.then(GffPathSegment::Field(field.label));
52                let at = frame.2;
53                frame.2 += 1;
54                (frame.1, at, path)
55            };
56
57            let value = &structure.fields[at].value;
58            match value {
59                GffValue::Struct(nested) => self.stack.push((path.clone(), nested, 0)),
60                // Reversed, because the stack is taken from the back and the
61                // elements have to come out in the order the list holds them.
62                GffValue::List(elements) => {
63                    for (index, element) in elements.iter().enumerate().rev() {
64                        self.stack
65                            .push((path.then(GffPathSegment::Index(index)), element, 0));
66                    }
67                }
68                // Every other value is a leaf. Named rather than swept up so
69                // that a field type added to the wire set has to be looked at
70                // here instead of silently becoming one.
71                GffValue::UInt8(_)
72                | GffValue::Int8(_)
73                | GffValue::UInt16(_)
74                | GffValue::Int16(_)
75                | GffValue::UInt32(_)
76                | GffValue::Int32(_)
77                | GffValue::UInt64(_)
78                | GffValue::Int64(_)
79                | GffValue::Single(_)
80                | GffValue::Double(_)
81                | GffValue::String(_)
82                | GffValue::ResRef(_)
83                | GffValue::LocalizedString(_)
84                | GffValue::Binary(_)
85                | GffValue::Vector3(_)
86                | GffValue::Vector4(_) => {}
87            }
88            return Some((path, value));
89        }
90    }
91}
92
93impl GffStruct {
94    /// Every value beneath this struct, with the address it sits at.
95    ///
96    /// Depth first in the order the file holds its fields, so a consumer
97    /// printing the sequence gets the tree as it is laid out rather than
98    /// sorted into something else. A struct-typed field is yielded before what
99    /// is inside it, and a list before its elements.
100    ///
101    /// # What the paths are good for
102    ///
103    /// Every one of them ends at a field, so every one is a path
104    /// [`get`](Self::get) accepts. That is not incidental: a path ending at a
105    /// list element names a struct rather than a value, which `get` refuses,
106    /// so the walk never produces one. The addresses are the same ones a
107    /// document's change map and a lint finding use.
108    ///
109    /// # Where a path does not resolve
110    ///
111    /// A struct may carry one label more than once, and vanilla content does:
112    /// each `EntryList` and `ReplyList` node of some dialogues carries
113    /// `SoundExists` six times. Every copy is yielded, since dropping one
114    /// would make the walk lie about what the file holds, and their paths are
115    /// identical and name no single field. [`get`](Self::get) refuses such an
116    /// address, deliberately, so those paths come back
117    /// [`AmbiguousLabel`](super::GffPathError::AmbiguousLabel) rather than
118    /// resolving to a guess.
119    ///
120    /// The alternative was an occurrence number inside the address itself.
121    /// That was not taken because the address type is shared with the linter,
122    /// the change map and the diff, and this is not a shared problem: across
123    /// every GFF in an install it happens for one label, in two lists, in one
124    /// resource type. Across every save reachable from here, including the
125    /// per-module resources inside their nested archives, it does not happen
126    /// at all. A consumer that reads dialogue needs to expect it; a save
127    /// editor never meets it.
128    pub fn walk(&self) -> GffWalk<'_> {
129        GffWalk {
130            stack: vec![(GffPath::new(Vec::new()), self, 0)],
131        }
132    }
133}