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    /// Renders source text for `language` from a parsed or constructed network.
46    ///
47    /// Parsed networks are rendered from their document root. Hand-built
48    /// networks without document links are rendered from top-level syntax or
49    /// token links whose metadata language matches `language`.
50    #[must_use]
51    pub fn render_source(&self, language: &str) -> String {
52        if let Some(source) = self.render_source_from_document(language) {
53            return source;
54        }
55
56        self.render_source_roots(language)
57    }
58
59    /// Renders source text from a specific syntax, document, region, or token link.
60    #[must_use]
61    pub fn render_source_from(&self, root: LinkId, language: &str) -> String {
62        let mut visiting = BTreeSet::new();
63        self.render_link(root, language, &mut visiting)
64    }
65
66    /// Renders source text from document links matching `language`.
67    ///
68    /// Returns `None` when the network has no matching document link, which is
69    /// common for programmatically constructed syntax fragments.
70    #[must_use]
71    pub fn render_source_from_document(&self, language: &str) -> Option<String> {
72        let mut documents = self
73            .links()
74            .filter(|link| {
75                link.metadata().link_type() == Some(LinkType::Document)
76                    && language_matches(link.metadata().language(), language)
77            })
78            .map(Link::id)
79            .peekable();
80
81        documents.peek()?;
82
83        let mut source = String::new();
84        for document in documents {
85            source.push_str(&self.render_source_from(document, language));
86        }
87        Some(source)
88    }
89
90    fn render_source_roots(&self, language: &str) -> String {
91        let child_ids = self.renderable_child_ids(language);
92        let mut roots = self
93            .links()
94            .filter(|link| renderable_root(link, language))
95            .filter(|link| !child_ids.contains(&link.id()))
96            .map(Link::id)
97            .collect::<Vec<_>>();
98        roots.sort_unstable_by_key(|id| id.as_u64());
99
100        let mut source = String::new();
101        for root in roots {
102            source.push_str(&self.render_source_from(root, language));
103        }
104        source
105    }
106
107    fn renderable_child_ids(&self, language: &str) -> BTreeSet<LinkId> {
108        let mut child_ids = BTreeSet::new();
109        for link in self
110            .links()
111            .filter(|link| renderable_container(link, language))
112        {
113            for child in self.render_children(link, language) {
114                child_ids.insert(child);
115            }
116        }
117        child_ids
118    }
119
120    fn render_link(&self, id: LinkId, language: &str, visiting: &mut BTreeSet<LinkId>) -> String {
121        let Some(link) = self.link(id) else {
122            return String::new();
123        };
124        if !renderable_link(link, language) || link.metadata().flags().is_missing() {
125            return String::new();
126        }
127        if link.metadata().link_type() == Some(LinkType::Token) {
128            return link.metadata().term().unwrap_or_default().to_string();
129        }
130        if !visiting.insert(id) {
131            return String::new();
132        }
133
134        let mut source = String::new();
135        for child in self.render_children(link, language) {
136            source.push_str(&self.render_link(child, language, visiting));
137        }
138        visiting.remove(&id);
139        source
140    }
141
142    fn render_children(&self, link: &Link, language: &str) -> Vec<LinkId> {
143        if uses_owned_child_links(link) {
144            let owned_children = self.owned_render_children(link.id(), language);
145            if !owned_children.is_empty() {
146                return owned_children;
147            }
148        }
149
150        if link.metadata().span().is_none() {
151            let direct_children = self.direct_render_children(link, language);
152            if !direct_children.is_empty() {
153                return direct_children;
154            }
155        }
156
157        self.field_render_children(link.id(), language)
158    }
159
160    fn owned_render_children(&self, parent: LinkId, language: &str) -> Vec<LinkId> {
161        let mut children = self
162            .links()
163            .filter(|link| link.id() != parent)
164            .filter(|link| {
165                link.references()
166                    .first()
167                    .is_some_and(|reference| *reference == parent)
168            })
169            .filter(|link| renderable_child(link, language))
170            .map(Link::id)
171            .collect::<Vec<_>>();
172        self.sort_children_by_source_order(&mut children);
173        children
174    }
175
176    fn direct_render_children(&self, link: &Link, language: &str) -> Vec<LinkId> {
177        let mut children = Vec::new();
178        let mut seen = BTreeSet::new();
179        for child in link.references().iter().copied() {
180            if child == link.id() || !seen.insert(child) {
181                continue;
182            }
183            let Some(child_link) = self.link(child) else {
184                continue;
185            };
186            if renderable_child(child_link, language) {
187                children.push(child);
188            }
189        }
190        children
191    }
192
193    fn field_render_children(&self, parent: LinkId, language: &str) -> Vec<LinkId> {
194        let mut fields = self
195            .links()
196            .filter(|link| link.metadata().link_type() == Some(LinkType::Field))
197            .filter(|link| {
198                link.references()
199                    .first()
200                    .is_some_and(|reference| *reference == parent)
201            })
202            .filter_map(|field| {
203                field
204                    .references()
205                    .get(2)
206                    .copied()
207                    .map(|child| (field.id(), child))
208            })
209            .filter(|(_field, child)| {
210                self.link(*child)
211                    .is_some_and(|link| renderable_child(link, language))
212            })
213            .collect::<Vec<_>>();
214        fields.sort_unstable_by_key(|(field, _child)| field.as_u64());
215
216        let mut children = Vec::new();
217        let mut seen = BTreeSet::new();
218        for (_field, child) in fields {
219            if seen.insert(child) {
220                children.push(child);
221            }
222        }
223        children
224    }
225
226    fn sort_children_by_source_order(&self, children: &mut [LinkId]) {
227        children.sort_unstable_by_key(|id| {
228            let span = self.link(*id).and_then(|link| link.metadata().span());
229            (
230                span.is_none(),
231                span.map_or(usize::MAX, |span| span.byte_range().start()),
232                id.as_u64(),
233            )
234        });
235    }
236}
237
238fn renderable_root(link: &Link, language: &str) -> bool {
239    matches!(
240        link.metadata().link_type(),
241        Some(LinkType::Syntax | LinkType::Token)
242    ) && language_matches(link.metadata().language(), language)
243        && !link.metadata().flags().is_missing()
244}
245
246fn renderable_container(link: &Link, language: &str) -> bool {
247    matches!(
248        link.metadata().link_type(),
249        Some(LinkType::Document | LinkType::Region | LinkType::Syntax)
250    ) && language_matches(link.metadata().language(), language)
251        && !link.metadata().flags().is_missing()
252}
253
254fn renderable_child(link: &Link, language: &str) -> bool {
255    matches!(
256        link.metadata().link_type(),
257        Some(LinkType::Syntax | LinkType::Token)
258    ) && language_matches(link.metadata().language(), language)
259        && !link.metadata().flags().is_missing()
260}
261
262fn renderable_link(link: &Link, language: &str) -> bool {
263    matches!(
264        link.metadata().link_type(),
265        Some(LinkType::Document | LinkType::Region | LinkType::Syntax | LinkType::Token)
266    ) && language_matches(link.metadata().language(), language)
267}
268
269const fn uses_owned_child_links(link: &Link) -> bool {
270    link.metadata().span().is_some()
271        || matches!(
272            link.metadata().link_type(),
273            Some(LinkType::Document | LinkType::Region)
274        )
275}
276
277fn language_matches(source_language: Option<&str>, target_language: &str) -> bool {
278    source_language.map_or(true, |source_language| {
279        source_language.eq_ignore_ascii_case(target_language)
280    })
281}