Skip to main content

meta_language/
source_generation.rs

1use std::collections::BTreeSet;
2
3use crate::link_network::{Link, LinkId, LinkMetadata, LinkNetwork, LinkType};
4
5impl LinkNetwork {
6    /// Inserts a source token that can be rendered without an original source span.
7    ///
8    /// This is the construction-side counterpart to parser-created token
9    /// links: the token text is stored in the link term, but no byte range is
10    /// required.
11    #[must_use]
12    pub fn insert_source_token(&mut self, language: &str, text: &str) -> LinkId {
13        self.insert_link(
14            [],
15            LinkMetadata::new()
16                .with_link_type(LinkType::Token)
17                .with_named(!text.trim().is_empty())
18                .with_term(text)
19                .with_language(language),
20        )
21    }
22
23    /// Inserts a syntax node whose ordered references are renderable children.
24    ///
25    /// The `kind` should match the grammar node vocabulary used by the
26    /// corresponding parser where possible. Rendering only emits descendant
27    /// token text; the kind is metadata for queries and round-trip validation.
28    #[must_use]
29    pub fn insert_syntax_node<const N: usize>(
30        &mut self,
31        language: &str,
32        kind: &str,
33        children: [LinkId; N],
34    ) -> LinkId {
35        self.insert_link(
36            children,
37            LinkMetadata::new()
38                .with_link_type(LinkType::Syntax)
39                .with_named(true)
40                .with_term(kind)
41                .with_language(language),
42        )
43    }
44
45    /// Inserts a syntax node whose child count is known only at run time.
46    #[must_use]
47    pub fn insert_dynamic_syntax_node(
48        &mut self,
49        language: &str,
50        kind: &str,
51        children: &[LinkId],
52    ) -> LinkId {
53        self.insert_dynamic_link(
54            children,
55            LinkMetadata::new()
56                .with_link_type(LinkType::Syntax)
57                .with_named(true)
58                .with_term(kind)
59                .with_language(language),
60        )
61    }
62
63    /// Renders source text for `language` from a parsed or constructed network.
64    ///
65    /// Parsed networks are rendered from their document root. Hand-built
66    /// networks without document links are rendered from top-level syntax or
67    /// token links whose metadata language matches `language`.
68    #[must_use]
69    pub fn render_source(&self, language: &str) -> String {
70        if let Some(source) = self.render_source_from_document(language) {
71            return source;
72        }
73
74        self.render_source_roots(language)
75    }
76
77    /// Renders source text from a specific syntax, document, region, or token link.
78    #[must_use]
79    pub fn render_source_from(&self, root: LinkId, language: &str) -> String {
80        let mut visiting = BTreeSet::new();
81        self.render_link(root, language, &mut visiting)
82    }
83
84    /// Renders source text from document links matching `language`.
85    ///
86    /// Returns `None` when the network has no matching document link, which is
87    /// common for programmatically constructed syntax fragments.
88    #[must_use]
89    pub fn render_source_from_document(&self, language: &str) -> Option<String> {
90        let mut documents = self
91            .links()
92            .filter(|link| {
93                link.metadata().link_type() == Some(LinkType::Document)
94                    && language_matches(link.metadata().language(), language)
95            })
96            .map(Link::id)
97            .peekable();
98
99        documents.peek()?;
100
101        let mut source = String::new();
102        for document in documents {
103            source.push_str(&self.render_source_from(document, language));
104        }
105        Some(source)
106    }
107
108    fn render_source_roots(&self, language: &str) -> String {
109        let child_ids = self.renderable_child_ids(language);
110        let mut roots = self
111            .links()
112            .filter(|link| renderable_root(link, language))
113            .filter(|link| !child_ids.contains(&link.id()))
114            .map(Link::id)
115            .collect::<Vec<_>>();
116        roots.sort_unstable_by_key(|id| id.as_u64());
117
118        let mut source = String::new();
119        for root in roots {
120            source.push_str(&self.render_source_from(root, language));
121        }
122        source
123    }
124
125    fn renderable_child_ids(&self, language: &str) -> BTreeSet<LinkId> {
126        let mut child_ids = BTreeSet::new();
127        for link in self
128            .links()
129            .filter(|link| renderable_container(link, language))
130        {
131            for child in self.render_children(link, language) {
132                child_ids.insert(child);
133            }
134        }
135        child_ids
136    }
137
138    fn render_link(&self, id: LinkId, language: &str, visiting: &mut BTreeSet<LinkId>) -> String {
139        let Some(link) = self.link(id) else {
140            return String::new();
141        };
142        if !renderable_link(link, language) || link.metadata().flags().is_missing() {
143            return String::new();
144        }
145        if link.metadata().link_type() == Some(LinkType::Token) {
146            return link.metadata().term().unwrap_or_default().to_string();
147        }
148        if !visiting.insert(id) {
149            return String::new();
150        }
151
152        let mut source = String::new();
153        for child in self.render_children(link, language) {
154            source.push_str(&self.render_link(child, language, visiting));
155        }
156        visiting.remove(&id);
157        source
158    }
159
160    fn render_children(&self, link: &Link, language: &str) -> Vec<LinkId> {
161        if uses_owned_child_links(link) {
162            let owned_children = self.owned_render_children(link.id(), language);
163            if !owned_children.is_empty() {
164                return owned_children;
165            }
166        }
167
168        if link.metadata().span().is_none() {
169            let direct_children = self.direct_render_children(link, language);
170            if !direct_children.is_empty() {
171                return direct_children;
172            }
173        }
174
175        self.field_render_children(link.id(), language)
176    }
177
178    fn owned_render_children(&self, parent: LinkId, language: &str) -> Vec<LinkId> {
179        let mut children = self
180            .links()
181            .filter(|link| link.id() != parent)
182            .filter(|link| {
183                link.references()
184                    .first()
185                    .is_some_and(|reference| *reference == parent)
186            })
187            .filter(|link| renderable_child(link, language))
188            .map(Link::id)
189            .collect::<Vec<_>>();
190        self.sort_children_by_source_order(&mut children);
191        children
192    }
193
194    fn direct_render_children(&self, link: &Link, language: &str) -> Vec<LinkId> {
195        let mut children = Vec::new();
196        let mut seen = BTreeSet::new();
197        for child in link.references().iter().copied() {
198            if child == link.id() || !seen.insert(child) {
199                continue;
200            }
201            let Some(child_link) = self.link(child) else {
202                continue;
203            };
204            if renderable_child(child_link, language) {
205                children.push(child);
206            }
207        }
208        children
209    }
210
211    fn field_render_children(&self, parent: LinkId, language: &str) -> Vec<LinkId> {
212        let mut fields = self
213            .links()
214            .filter(|link| link.metadata().link_type() == Some(LinkType::Field))
215            .filter(|link| {
216                link.references()
217                    .first()
218                    .is_some_and(|reference| *reference == parent)
219            })
220            .filter_map(|field| {
221                field
222                    .references()
223                    .get(2)
224                    .copied()
225                    .map(|child| (field.id(), child))
226            })
227            .filter(|(_field, child)| {
228                self.link(*child)
229                    .is_some_and(|link| renderable_child(link, language))
230            })
231            .collect::<Vec<_>>();
232        fields.sort_unstable_by_key(|(field, _child)| field.as_u64());
233
234        let mut children = Vec::new();
235        let mut seen = BTreeSet::new();
236        for (_field, child) in fields {
237            if seen.insert(child) {
238                children.push(child);
239            }
240        }
241        children
242    }
243
244    fn sort_children_by_source_order(&self, children: &mut [LinkId]) {
245        children.sort_unstable_by_key(|id| {
246            let span = self.link(*id).and_then(|link| link.metadata().span());
247            (
248                span.is_none(),
249                span.map_or(usize::MAX, |span| span.byte_range().start()),
250                id.as_u64(),
251            )
252        });
253    }
254}
255
256fn renderable_root(link: &Link, language: &str) -> bool {
257    matches!(
258        link.metadata().link_type(),
259        Some(LinkType::Syntax | LinkType::Token)
260    ) && language_matches(link.metadata().language(), language)
261        && !link.metadata().flags().is_missing()
262}
263
264fn renderable_container(link: &Link, language: &str) -> bool {
265    matches!(
266        link.metadata().link_type(),
267        Some(LinkType::Document | LinkType::Region | LinkType::Syntax)
268    ) && language_matches(link.metadata().language(), language)
269        && !link.metadata().flags().is_missing()
270}
271
272fn renderable_child(link: &Link, language: &str) -> bool {
273    matches!(
274        link.metadata().link_type(),
275        Some(LinkType::Syntax | LinkType::Token)
276    ) && language_matches(link.metadata().language(), language)
277        && !link.metadata().flags().is_missing()
278}
279
280fn renderable_link(link: &Link, language: &str) -> bool {
281    matches!(
282        link.metadata().link_type(),
283        Some(LinkType::Document | LinkType::Region | LinkType::Syntax | LinkType::Token)
284    ) && language_matches(link.metadata().language(), language)
285}
286
287const fn uses_owned_child_links(link: &Link) -> bool {
288    link.metadata().span().is_some()
289        || matches!(
290            link.metadata().link_type(),
291            Some(LinkType::Document | LinkType::Region)
292        )
293}
294
295fn language_matches(source_language: Option<&str>, target_language: &str) -> bool {
296    source_language.map_or(true, |source_language| {
297        source_language.eq_ignore_ascii_case(target_language)
298    })
299}