lang_forge/parse.rs
1//! [`Parse`]: the result of parsing source text with a forged language.
2
3use alloc::{string::String, vec::Vec};
4use core::fmt::Write as _;
5
6use diag_lang::{Diagnostic, Severity};
7use syntax_lang::{Element, Node};
8
9use crate::{Language, kind::Kind};
10
11/// The result of [`Language::parse`]: a lossless syntax tree and the problems
12/// found while building it.
13///
14/// Parsing never fails. Malformed input still yields a complete tree — missing
15/// pieces are left out, unexpected tokens are wrapped in `ERROR` nodes — and a
16/// [`Diagnostic`] for each problem, so an editor can highlight and navigate a
17/// half-typed file and a compiler can report every syntax error in one pass.
18/// The tree is lossless either way: it covers every byte of the source,
19/// whitespace and comments included.
20///
21/// A `Parse` borrows the language and the source it was made from, so names,
22/// text, and diagnostics can be resolved from it alone. It is also the unit
23/// that capability passes run over; see [`Language::pipeline`].
24///
25/// # Examples
26///
27/// ```
28/// use lang_forge::Language;
29///
30/// let lang = Language::from_lsf(
31/// r#"
32/// [language]
33/// name = "pairs"
34///
35/// [rules]
36/// file = "pair*"
37/// pair = "IDENT '=' NUMBER ';'"
38/// "#,
39/// )?;
40///
41/// let parse = lang.parse("a = 1;\nb = ;\n");
42/// assert!(parse.has_errors());
43/// assert_eq!(parse.diagnostics()[0].message(), "expected a number, found `;`");
44///
45/// // Still a full tree, covering the whole source.
46/// assert_eq!(parse.tree().text(parse.source()), Some("a = 1;\nb = ;\n"));
47/// assert_eq!(parse.tree().child_nodes().count(), 2);
48/// # Ok::<(), lang_forge::Error>(())
49/// ```
50#[derive(Clone, Debug)]
51pub struct Parse<'a> {
52 language: &'a Language,
53 source: &'a str,
54 tree: Node<Kind>,
55 diagnostics: Vec<Diagnostic>,
56}
57
58impl<'a> Parse<'a> {
59 pub(crate) fn new(
60 language: &'a Language,
61 source: &'a str,
62 tree: Node<Kind>,
63 diagnostics: Vec<Diagnostic>,
64 ) -> Self {
65 Self {
66 language,
67 source,
68 tree,
69 diagnostics,
70 }
71 }
72
73 /// The root of the syntax tree: a node of the start rule's kind that
74 /// covers the whole source.
75 ///
76 /// Walk it with the [`syntax_lang::Node`] API — [`children`](Node::children),
77 /// [`descendants`](Node::descendants), [`tokens`](Node::tokens) — and slice
78 /// the source with [`Node::text`]. Every walk is iterative, so even very
79 /// deep trees are safe to traverse.
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// use lang_forge::Language;
85 ///
86 /// let lang = Language::from_lsf(
87 /// "[language]\nname = \"list\"\n[rules]\nlist = \"'[' (NUMBER (',' NUMBER)*)? ']'\"\n",
88 /// )?;
89 /// let parse = lang.parse("[1, 2, 3]");
90 /// let number = lang.kind("NUMBER").expect("built in");
91 /// let numbers: Vec<&str> = parse
92 /// .tree()
93 /// .tokens()
94 /// .filter(|t| *t.kind() == number)
95 /// .map(|t| &parse.source()[t.span().start().to_usize()..t.span().end().to_usize()])
96 /// .collect();
97 /// assert_eq!(numbers, ["1", "2", "3"]);
98 /// # Ok::<(), lang_forge::Error>(())
99 /// ```
100 #[inline]
101 #[must_use]
102 pub fn tree(&self) -> &Node<Kind> {
103 &self.tree
104 }
105
106 /// The source text that was parsed. Spans in the tree and the
107 /// diagnostics are byte offsets into it.
108 #[inline]
109 #[must_use]
110 pub fn source(&self) -> &'a str {
111 self.source
112 }
113
114 /// The language that parsed the source, for looking kinds up and naming
115 /// them.
116 #[inline]
117 #[must_use]
118 pub fn language(&self) -> &'a Language {
119 self.language
120 }
121
122 /// The problems found, in source order: lexical errors (unexpected
123 /// characters, unterminated strings and comments, malformed numbers),
124 /// syntax errors, and anything capability passes have [`report`]ed.
125 ///
126 /// Spans are byte offsets into [`source`](Self::source). Add the source to
127 /// a fresh [`SourceMap`](diag_lang::SourceMap) and
128 /// [`Renderer`](diag_lang::Renderer) draws each one under the line at
129 /// fault.
130 ///
131 /// [`report`]: Self::report
132 ///
133 /// # Examples
134 ///
135 /// ```
136 /// use lang_forge::Language;
137 /// use lang_forge::diag_lang::{Renderer, SourceMap};
138 ///
139 /// let lang = Language::from_lsf(
140 /// "[language]\nname = \"calls\"\n[rules]\nfile = \"call*\"\ncall = \"IDENT '(' ')' ';'\"\n",
141 /// )?;
142 /// let source = "start();\nstop(;\n";
143 /// let parse = lang.parse(source);
144 ///
145 /// let mut map = SourceMap::new();
146 /// map.add("main.calls", source).expect("fits");
147 /// let report = Renderer::new().render(&parse.diagnostics()[0], &map);
148 /// assert!(report.contains("expected `)`, found `;`"));
149 /// assert!(report.contains("main.calls:2:6"));
150 /// # Ok::<(), lang_forge::Error>(())
151 /// ```
152 #[inline]
153 #[must_use]
154 pub fn diagnostics(&self) -> &[Diagnostic] {
155 &self.diagnostics
156 }
157
158 /// Whether any diagnostic is an error (rather than a warning or a note a
159 /// capability pass added).
160 #[must_use]
161 pub fn has_errors(&self) -> bool {
162 self.diagnostics
163 .iter()
164 .any(|d| d.severity() == Severity::Error)
165 }
166
167 /// Adds a diagnostic — the way a capability pass reports what it finds.
168 ///
169 /// # Examples
170 ///
171 /// ```
172 /// use lang_forge::Language;
173 /// use lang_forge::diag_lang::{Diagnostic, Label, Severity};
174 ///
175 /// let lang = Language::from_lsf("[language]\nname = \"n\"\n[rules]\nn = \"NUMBER\"\n")?;
176 /// let mut parse = lang.parse("42");
177 /// let span = parse.tree().span();
178 /// parse.report(Diagnostic::new(Severity::Warning, "the answer", Label::unlabelled(span)));
179 ///
180 /// assert_eq!(parse.diagnostics().len(), 1);
181 /// assert!(!parse.has_errors());
182 /// # Ok::<(), lang_forge::Error>(())
183 /// ```
184 pub fn report(&mut self, diagnostic: Diagnostic) {
185 self.diagnostics.push(diagnostic);
186 }
187
188 /// Takes the tree, dropping the diagnostics and the borrows.
189 #[inline]
190 #[must_use]
191 pub fn into_tree(self) -> Node<Kind> {
192 self.tree
193 }
194
195 /// The tree as indented text, one node or token per line: each line is a
196 /// kind name and a byte range, and each token also shows its text.
197 ///
198 /// Meant for tests, snapshots, and debugging a grammar. The format is
199 /// stable within a major version.
200 ///
201 /// # Examples
202 ///
203 /// ```
204 /// use lang_forge::Language;
205 ///
206 /// let lang = Language::from_lsf(
207 /// "[language]\nname = \"let\"\n[rules]\nstmt = \"'let' IDENT '=' NUMBER\"\n",
208 /// )?;
209 /// assert_eq!(
210 /// lang.parse("let x = 1").dump(),
211 /// "stmt@0..9\n \
212 /// let@0..3 \"let\"\n \
213 /// WHITESPACE@3..4 \" \"\n \
214 /// IDENT@4..5 \"x\"\n \
215 /// WHITESPACE@5..6 \" \"\n \
216 /// =@6..7 \"=\"\n \
217 /// WHITESPACE@7..8 \" \"\n \
218 /// NUMBER@8..9 \"1\"\n",
219 /// );
220 /// # Ok::<(), lang_forge::Error>(())
221 /// ```
222 #[must_use]
223 pub fn dump(&self) -> String {
224 let mut out = String::new();
225 let mut stack: Vec<(&Element<Kind>, usize)> = Vec::new();
226 let line = |out: &mut String, depth: usize, kind: Kind, start: u32, end: u32| {
227 for _ in 0..depth {
228 out.push_str(" ");
229 }
230 // Writing to a `String` cannot fail.
231 let _ = write!(out, "{}@{start}..{end}", self.language.kind_name(kind));
232 };
233 let span = self.tree.span();
234 line(
235 &mut out,
236 0,
237 *self.tree.kind(),
238 span.start().to_u32(),
239 span.end().to_u32(),
240 );
241 out.push('\n');
242 stack.extend(self.tree.children().map(|c| (c, 1)));
243 stack.reverse();
244 while let Some((element, depth)) = stack.pop() {
245 let span = element.span();
246 line(
247 &mut out,
248 depth,
249 *element.kind(),
250 span.start().to_u32(),
251 span.end().to_u32(),
252 );
253 match element {
254 Element::Node(node) => {
255 let from = stack.len();
256 stack.extend(node.children().map(|c| (c, depth + 1)));
257 stack[from..].reverse();
258 }
259 Element::Token(_) => {
260 let text = &self.source[span.start().to_usize()..span.end().to_usize()];
261 // Writing to a `String` cannot fail.
262 let _ = write!(out, " {text:?}");
263 }
264 }
265 out.push('\n');
266 }
267 out
268 }
269}