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}