lang_forge/language.rs
1//! [`Language`]: a language forged from a schematic.
2
3use alloc::{boxed::Box, format, vec::Vec};
4use core::str::FromStr;
5
6use diag_lang::{Diagnostic, Label, Severity};
7use pass_lang::{Outcome, Pass, PassError, PassManager};
8use syntax_lang::{Node, Span, Token};
9
10use crate::{
11 Error, Parse, codes,
12 error::Report,
13 grammar::{self, Grammar, Lex},
14 kind::Kind,
15 noml, parser, schematic,
16};
17
18/// A capability pass, boxed for [`Language::pipeline`].
19///
20/// A capability is a [`pass_lang::Pass`] over a [`Parse`], named by its
21/// [`Pass::name`]. Implement the pass for every lifetime —
22/// `impl<'a> Pass<Parse<'a>> for MyPass` — and box it as a `Capability`; the
23/// language then runs it whenever its schematic includes that name.
24///
25/// # Examples
26///
27/// ```
28/// use lang_forge::pass_lang::{Outcome, Pass, PassError};
29/// use lang_forge::{Capability, Parse};
30///
31/// struct CountNodes(usize);
32///
33/// impl<'a> Pass<Parse<'a>> for CountNodes {
34/// fn name(&self) -> &'static str {
35/// "count-nodes"
36/// }
37///
38/// fn run(&mut self, parse: &mut Parse<'a>) -> Result<Outcome, PassError> {
39/// self.0 = parse.tree().descendants().count();
40/// Ok(Outcome::Unchanged)
41/// }
42/// }
43///
44/// let capability: Capability = Box::new(CountNodes(0));
45/// assert_eq!(capability.name(), "count-nodes");
46/// ```
47pub type Capability = Box<dyn for<'a> Pass<Parse<'a>>>;
48
49/// A language forged from a `.lsf` schematic: a lexer, a parser, and the kinds
50/// of its syntax tree.
51///
52/// Forge one with [`Language::from_lsf`] (or `str::parse`), then call
53/// [`parse`](Self::parse) as often as needed. Forging does all the analysis
54/// up front — every rule resolved, every set computed, every conflict
55/// refused — so parsing is a walk over precomputed tables that never fails and
56/// never panics: malformed input yields a complete tree plus diagnostics.
57///
58/// A `Language` is immutable once forged. It is `Send` and `Sync`, so one
59/// language can parse on many threads at once, and `Clone` when a copy is
60/// needed.
61///
62/// # The sketch
63///
64/// A sketch (also called a schematic) is a NOML document. A format-1 sketch —
65/// the format of lang-forge 1.x, read exactly as 1.x read it — has up to four
66/// tables: `[language]` (the name, and optionally the version, file
67/// extensions, and start rule), `[lexer]` (identifier style, significant
68/// newlines, comments, strings), `[rules]` (the grammar), and
69/// `[capabilities]` (passes the language includes). A sketch that begins
70/// with `[sketch] format = 2` is read as LSF2, which adds token classes,
71/// lexer modes, string classes with interpolation and counted delimiters,
72/// keyword policies, layout, field labels, predicates, `[ast]`, and
73/// `[injections]`. The full reference is in `docs/API.md`; a sketch split
74/// over several files is forged from a [`Sketch`](crate::Sketch).
75///
76/// # Examples
77///
78/// ```
79/// use lang_forge::Language;
80///
81/// let calc = Language::from_lsf(
82/// r##"
83/// [language]
84/// name = "calc"
85/// version = "1.0.0"
86/// extensions = ["calc"]
87///
88/// [lexer]
89/// line_comments = ["#"]
90///
91/// [rules]
92/// program = "stmt*"
93/// stmt = "'let' IDENT '=' expr ';' | expr ';'"
94///
95/// [rules.expr]
96/// operand = "NUMBER | IDENT | '(' expr ')'"
97/// levels = [
98/// { left = ["+", "-"] },
99/// { left = ["*", "/"] },
100/// { prefix = ["-"] },
101/// ]
102/// "##,
103/// )?;
104///
105/// let parse = calc.parse("let x = 2 * (3 + 4); # seven, doubled\n-x;");
106/// assert!(!parse.has_errors());
107///
108/// let stmt = calc.kind("stmt").expect("a rule");
109/// assert_eq!(parse.tree().child_nodes().filter(|n| *n.kind() == stmt).count(), 2);
110/// # Ok::<(), lang_forge::Error>(())
111/// ```
112#[derive(Clone, Debug)]
113pub struct Language {
114 grammar: Grammar,
115}
116
117impl Language {
118 /// Forges a language from the text of a `.lsf` schematic.
119 ///
120 /// The schematic is read, checked against the schematic layout, and its
121 /// grammar compiled and analysed. Everything wrong with it is reported at
122 /// once.
123 ///
124 /// # Errors
125 ///
126 /// Returns an [`Error`] carrying one diagnostic per problem, with spans
127 /// into `schematic`: NOML syntax errors; unknown, missing, or mistyped
128 /// settings; malformed rules; undefined rules (with a suggestion);
129 /// literals the lexer cannot produce; delimiters used twice; left
130 /// recursion; repetitions of something that can match nothing;
131 /// alternatives that can never match; and the use of NOML's dynamic
132 /// features, which would make the language depend on where it was forged.
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// use lang_forge::Language;
138 ///
139 /// let json = Language::from_lsf(
140 /// r#"
141 /// [language]
142 /// name = "json"
143 ///
144 /// [lexer]
145 /// strings = ['"']
146 ///
147 /// [rules]
148 /// document = "value"
149 /// value = "object | array | STRING | NUMBER | 'true' | 'false' | 'null'"
150 /// object = "'{' (member (',' member)*)? '}'"
151 /// member = "STRING ':' value"
152 /// array = "'[' (value (',' value)*)? ']'"
153 /// "#,
154 /// )?;
155 /// assert!(!json.parse(r#"{"a": [1, true, {"b": null}]}"#).has_errors());
156 /// assert!(json.parse(r#"{"a": }"#).has_errors());
157 /// # Ok::<(), lang_forge::Error>(())
158 /// ```
159 ///
160 /// A left-recursive rule is refused, with the fix:
161 ///
162 /// ```
163 /// use lang_forge::Language;
164 ///
165 /// let err = Language::from_lsf(
166 /// "[language]\nname = \"bad\"\n[rules]\nsum = \"sum '+' NUMBER | NUMBER\"\n",
167 /// )
168 /// .unwrap_err();
169 /// assert_eq!(err.to_string(), "4:1: rule `sum` is left-recursive: sum → sum");
170 /// ```
171 pub fn from_lsf(schematic: &str) -> Result<Self, Error> {
172 let mut report = Report::default();
173 let root = match noml::read(schematic) {
174 Ok(root) => root,
175 Err(diagnostic) => {
176 report.diagnostic(*diagnostic);
177 return Err(report.into_error(schematic));
178 }
179 };
180 let modules = crate::spec2::modules(&root);
181 if let Some((_, span)) = modules.first() {
182 report.error_help(
183 codes::MODULE_NOT_FOUND,
184 *span,
185 "this sketch lists `modules`, which `Language::from_lsf` cannot load",
186 "forge it from a `Sketch` holding the entry and every module",
187 );
188 return Err(report.into_error_v2(schematic));
189 }
190 let spec = schematic::interpret(root, &mut report);
191 let format = spec.as_ref().map_or(1, |s| s.format);
192 if let Some(role) = spec.as_ref().and_then(|s| s.v2.as_ref()).map(|v| v.role) {
193 if role != crate::spec2::Role::Language {
194 report.error(
195 codes::FILE_ROLE,
196 Span::empty(0),
197 "a part or mixin cannot be forged on its own; forge the entry that lists it",
198 );
199 }
200 }
201 let grammar = spec.and_then(|spec| {
202 if spec.format == 2 {
203 let locate =
204 |span: Span| crate::error::line_col(schematic, span.start().to_usize());
205 crate::grammar2::compile(&spec, &locate, &mut report)
206 } else {
207 grammar::compile(&spec, schematic, &mut report)
208 }
209 });
210 Self::finish(grammar, report, format, |report| {
211 if format == 2 {
212 (report.into_error_v2(schematic), Vec::new())
213 } else {
214 (report.into_error(schematic), Vec::new())
215 }
216 })
217 }
218
219 /// Wraps a compiled grammar, or turns the report into the error.
220 pub(crate) fn finish(
221 grammar: Option<Grammar>,
222 mut report: Report,
223 format: u8,
224 fail: impl FnOnce(Report) -> (Error, Vec<Diagnostic>),
225 ) -> Result<Self, Error> {
226 match grammar {
227 Some(mut grammar) if report.is_clean() => {
228 if let Some(extra) = grammar.extra.as_mut() {
229 let warnings = if format == 2 {
230 report.into_warnings_v2()
231 } else {
232 report.into_warnings()
233 };
234 extra.warnings = warnings.into();
235 }
236 Ok(Self { grammar })
237 }
238 _ => {
239 if report.is_clean() {
240 report.error(
241 codes::MISSING,
242 Span::empty(0),
243 "the schematic could not be forged",
244 );
245 }
246 Err(fail(report).0)
247 }
248 }
249 }
250
251 /// Wraps compiled tables (the language image).
252 pub(crate) fn from_grammar(grammar: Grammar) -> Self {
253 Self { grammar }
254 }
255
256 /// The compiled tables, for the image writer.
257 pub(crate) fn tables(&self) -> &Grammar {
258 &self.grammar
259 }
260
261 /// The compiled tables, for the crate's own tests.
262 #[cfg(test)]
263 pub(crate) fn grammar(&self) -> &Grammar {
264 &self.grammar
265 }
266
267 /// The sketch format the language was forged from: `1` or `2`.
268 ///
269 /// # Examples
270 ///
271 /// ```
272 /// use lang_forge::Language;
273 ///
274 /// let v1 = Language::from_lsf("[language]\nname = \"x\"\n[rules]\nx = \"IDENT\"\n")?;
275 /// assert_eq!(v1.format(), 1);
276 /// let v2 = Language::from_lsf(
277 /// "[sketch]\nformat = 2\n[language]\nname = \"x\"\nversion = \"1.0.0\"\n[rules]\nx = \"IDENT\"\n",
278 /// )?;
279 /// assert_eq!(v2.format(), 2);
280 /// # Ok::<(), lang_forge::Error>(())
281 /// ```
282 #[must_use]
283 pub fn format(&self) -> u8 {
284 if self.grammar.extra.is_some() { 2 } else { 1 }
285 }
286
287 /// The language's name, from `[language] name`.
288 #[inline]
289 #[must_use]
290 pub fn name(&self) -> &str {
291 &self.grammar.name
292 }
293
294 /// The language's version, from `[language] version`, if given.
295 ///
296 /// The text is kept as written; lang-forge does not interpret it.
297 #[inline]
298 #[must_use]
299 pub fn version(&self) -> Option<&str> {
300 self.grammar.version.as_deref()
301 }
302
303 /// The file extensions of the language's source files, without the dot,
304 /// from `[language] extensions`.
305 ///
306 /// # Examples
307 ///
308 /// ```
309 /// use lang_forge::Language;
310 ///
311 /// let lang = Language::from_lsf(
312 /// "[language]\nname = \"mox\"\nextensions = [\"mox\", \"mx\"]\n[rules]\nfile = \"IDENT*\"\n",
313 /// )?;
314 /// assert_eq!(lang.extensions().collect::<Vec<_>>(), ["mox", "mx"]);
315 /// assert!(lang.extensions().any(|e| e == "mx"));
316 /// # Ok::<(), lang_forge::Error>(())
317 /// ```
318 pub fn extensions(&self) -> impl ExactSizeIterator<Item = &str> {
319 self.grammar.extensions.iter().map(|e| &**e)
320 }
321
322 /// The capabilities the schematic includes, in the order their passes
323 /// run, from `[capabilities] include`.
324 ///
325 /// # Examples
326 ///
327 /// ```
328 /// use lang_forge::Language;
329 ///
330 /// let lang = Language::from_lsf(
331 /// "[language]\nname = \"iron\"\n[rules]\nfile = \"IDENT*\"\n\
332 /// [capabilities]\ninclude = [\"borrow-check\", \"thermal\"]\n",
333 /// )?;
334 /// assert_eq!(lang.capabilities().collect::<Vec<_>>(), ["borrow-check", "thermal"]);
335 /// # Ok::<(), lang_forge::Error>(())
336 /// ```
337 pub fn capabilities(&self) -> impl ExactSizeIterator<Item = &str> {
338 self.grammar.capabilities.iter().map(|c| &*c.name)
339 }
340
341 /// The kind called `name`, or `None` if the language has no such kind.
342 ///
343 /// Rule names name the nodes rules build (hidden `_` rules build none);
344 /// a keyword or symbol is named by its text; Pratt levels add their node
345 /// names (`binary`, `prefix`, `postfix` unless renamed); and every
346 /// language has `IDENT`, `NUMBER`, `STRING`, `NEWLINE`, `WHITESPACE`,
347 /// `COMMENT`, `UNKNOWN`, and `ERROR`. See [`Kind`] for the full table.
348 ///
349 /// The lookup is a binary search; look kinds up once and keep them.
350 ///
351 /// # Examples
352 ///
353 /// ```
354 /// use lang_forge::Language;
355 ///
356 /// let lang = Language::from_lsf("[language]\nname = \"x\"\n[rules]\nitem = \"'go' NUMBER\"\n")?;
357 /// assert!(lang.kind("item").is_some());
358 /// assert!(lang.kind("go").is_some());
359 /// assert!(lang.kind("ERROR").is_some());
360 /// assert!(lang.kind("missing").is_none());
361 /// # Ok::<(), lang_forge::Error>(())
362 /// ```
363 #[must_use]
364 pub fn kind(&self, name: &str) -> Option<Kind> {
365 let kinds = &self.grammar.kinds;
366 if self.grammar.extra.is_some() {
367 // Format 2 reference syntax (LSF2 §6.3): `kind:x` is the node
368 // `x`, `'x'` the literal `x`; a plain name shared by a keyword
369 // and an operator node names the keyword.
370 if let Some(node) = name.strip_prefix("kind:") {
371 return kinds
372 .all_named(node)
373 .find(|k| kinds.cats[k.slot()] == grammar::CAT_NODE);
374 }
375 if let Some(literal) = name
376 .strip_prefix('\'')
377 .and_then(|n| n.strip_suffix('\''))
378 .filter(|n| !n.is_empty())
379 {
380 return kinds
381 .all_named(literal)
382 .find(|k| kinds.cats[k.slot()] == grammar::CAT_LITERAL);
383 }
384 }
385 kinds.get(name)
386 }
387
388 /// The name of `kind`: the inverse of [`kind`](Self::kind).
389 ///
390 /// A kind is only meaningful to the language that made it. Given a kind
391 /// from another language, `kind_name` cannot tell: it returns whatever
392 /// name this language has at that kind's position in its kind table —
393 /// usually a wrong one — or `"<unknown>"` when this language has fewer
394 /// kinds than that.
395 ///
396 /// # Examples
397 ///
398 /// ```
399 /// use lang_forge::Language;
400 ///
401 /// let lang = Language::from_lsf("[language]\nname = \"x\"\n[rules]\nitem = \"'go' NUMBER\"\n")?;
402 /// let parse = lang.parse("go 7");
403 /// let names: Vec<&str> = parse.tree().tokens().map(|t| lang.kind_name(*t.kind())).collect();
404 /// assert_eq!(names, ["go", "WHITESPACE", "NUMBER"]);
405 ///
406 /// // Another language's kinds get a wrong name, or none.
407 /// let other = Language::from_lsf(
408 /// "[language]\nname = \"y\"\n[rules]\nlist = \"'[' (pair (',' pair)*)? ']'\"\npair = \"IDENT ':' NUMBER\"\n",
409 /// )?;
410 /// assert_eq!(lang.kind_name(other.kind("[").expect("a symbol")), "go");
411 /// assert_eq!(lang.kind_name(other.kind("pair").expect("a rule")), "<unknown>");
412 /// # Ok::<(), lang_forge::Error>(())
413 /// ```
414 #[must_use]
415 pub fn kind_name(&self, kind: Kind) -> &str {
416 self.grammar.kinds.name(kind)
417 }
418
419 /// How many kinds the language has: valid [`Kind::index`] values are
420 /// `0..kind_count()`.
421 ///
422 /// # Examples
423 ///
424 /// ```
425 /// use lang_forge::Language;
426 ///
427 /// let lang = Language::from_lsf("[language]\nname = \"x\"\n[rules]\nitem = \"'go' NUMBER\"\n")?;
428 /// let all: Vec<&str> = (0..lang.kind_count() as u16)
429 /// .filter_map(|i| lang.kind_at(i))
430 /// .map(|k| lang.kind_name(k))
431 /// .collect();
432 /// assert!(all.contains(&"go") && all.contains(&"item") && all.contains(&"ERROR"));
433 /// # Ok::<(), lang_forge::Error>(())
434 /// ```
435 #[must_use]
436 pub fn kind_count(&self) -> usize {
437 self.grammar.kinds.len()
438 }
439
440 /// The kind with index `index` (see [`Kind::index`]), or `None` if the
441 /// language has no such kind (ISSUES M04).
442 ///
443 /// The kind comes back with its trivia flag set as this language sets it,
444 /// so it compares equal to the kinds in this language's trees. The index
445 /// of the internal end-of-input marker has no kind.
446 ///
447 /// # Examples
448 ///
449 /// ```
450 /// use lang_forge::Language;
451 ///
452 /// let lang = Language::from_lsf("[language]\nname = \"x\"\n[rules]\nitem = \"NUMBER\"\n")?;
453 /// let space = lang.kind("WHITESPACE").expect("built in");
454 /// assert_eq!(lang.kind_at(space.index()), Some(space));
455 /// assert_eq!(lang.kind_at(u16::MAX), None);
456 /// # Ok::<(), lang_forge::Error>(())
457 /// ```
458 #[must_use]
459 pub fn kind_at(&self, index: u16) -> Option<Kind> {
460 let kinds = &self.grammar.kinds;
461 let i = usize::from(index);
462 (i < kinds.len() && kinds.cats[i] != grammar::CAT_EOF).then(|| kinds.at(i))
463 }
464
465 /// The kind of every tree's root: the start rule's node.
466 ///
467 /// # Examples
468 ///
469 /// ```
470 /// use lang_forge::Language;
471 ///
472 /// let lang = Language::from_lsf("[language]\nname = \"x\"\n[rules]\nfile = \"NUMBER*\"\n")?;
473 /// assert_eq!(lang.root_kind(), lang.kind("file").expect("a rule"));
474 /// assert_eq!(*lang.parse("1 2").tree().kind(), lang.root_kind());
475 /// # Ok::<(), lang_forge::Error>(())
476 /// ```
477 #[must_use]
478 pub fn root_kind(&self) -> Kind {
479 let program = &self.grammar.program;
480 program.rules[program.start as usize]
481 .node
482 .unwrap_or(program.error)
483 }
484
485 /// The name of field label `label` (format 2), or `None` if the
486 /// language has no such label.
487 ///
488 /// # Examples
489 ///
490 /// ```
491 /// use lang_forge::Language;
492 ///
493 /// let lang = Language::from_lsf(
494 /// "[sketch]\nformat = 2\n[language]\nname = \"x\"\nversion = \"1.0.0\"\n\
495 /// [rules]\nlet_stmt = \"'let' name:IDENT '=' value:NUMBER\"\n",
496 /// )?;
497 /// let name = lang.label_id("name").expect("a label");
498 /// assert_eq!(lang.label_name(name), Some("name"));
499 /// assert_eq!(lang.label_id("missing"), None);
500 /// # Ok::<(), lang_forge::Error>(())
501 /// ```
502 #[must_use]
503 pub fn label_name(&self, label: u16) -> Option<&str> {
504 let extra = self.grammar.extra.as_ref()?;
505 extra.labels.get(usize::from(label)).map(|l| &**l)
506 }
507
508 /// The id of the field label called `name`, the number lower-lang's
509 /// `Pick::Label` takes. Labels are numbered by first occurrence over the
510 /// rules in order (LSF2 §5.4).
511 ///
512 /// # Examples
513 ///
514 /// ```
515 /// use lang_forge::Language;
516 ///
517 /// let lang = Language::from_lsf(
518 /// "[sketch]\nformat = 2\n[language]\nname = \"x\"\nversion = \"1.0.0\"\n\
519 /// [rules]\nassign = \"target:IDENT '=' value:NUMBER\"\n",
520 /// )?;
521 /// assert_eq!(lang.label_id("target"), Some(0));
522 /// assert_eq!(lang.label_id("value"), Some(1));
523 /// assert_eq!(lang.label_id("missing"), None);
524 /// # Ok::<(), lang_forge::Error>(())
525 /// ```
526 #[must_use]
527 pub fn label_id(&self, name: &str) -> Option<u16> {
528 let extra = self.grammar.extra.as_ref()?;
529 extra
530 .labels
531 .iter()
532 .position(|l| &**l == name)
533 .map(|i| i as u16)
534 }
535
536 /// The field label of `parent`'s child at `index` (counting every child,
537 /// trivia included, as [`Node::children`] yields them), or `None` for an
538 /// unlabelled child or an index out of range.
539 ///
540 /// This has the shape of lower-lang's `Labeler::label`, so the adapter
541 /// that hands a format-2 tree's labels to `Lowerer::with_labeler` is one
542 /// line: `fn label(&self, p: &Node<Kind>, i: usize) -> Option<u16> {
543 /// self.0.field_label(p, i) }`. Labels live on the tree itself (each
544 /// child's kind carries the label of its edge), so this works on any tree
545 /// the language built, cloned or not.
546 ///
547 /// # Examples
548 ///
549 /// ```
550 /// use lang_forge::Language;
551 ///
552 /// let lang = Language::from_lsf(
553 /// "[sketch]\nformat = 2\n[language]\nname = \"w\"\nversion = \"1.0.0\"\n\
554 /// [rules]\nwhile_stmt = \"'while' cond:IDENT body:block\"\nblock = \"'{' '}'\"\n",
555 /// )?;
556 /// let parse = lang.parse("while ready { }");
557 /// let root = parse.tree();
558 /// let names: Vec<Option<&str>> = (0..root.len())
559 /// .map(|i| lang.field_label(root, i).and_then(|l| lang.label_name(l)))
560 /// .collect();
561 /// assert_eq!(names, [None, None, Some("cond"), None, Some("body")]);
562 /// # Ok::<(), lang_forge::Error>(())
563 /// ```
564 #[must_use]
565 pub fn field_label(&self, parent: &Node<Kind>, index: usize) -> Option<u16> {
566 parent.children().nth(index).and_then(|c| c.kind().label())
567 }
568
569 /// The fields of node kind `kind` (format 2): every label its children
570 /// can carry, with the kinds the field can hold and its cardinality,
571 /// derived from the grammar (LSF2 §11.3). Empty for a kind with no
572 /// labelled children, and for every kind of a format-1 language.
573 ///
574 /// # Examples
575 ///
576 /// ```
577 /// use lang_forge::{Cardinality, Language};
578 ///
579 /// let lang = Language::from_lsf(
580 /// "[sketch]\nformat = 2\n[language]\nname = \"c\"\nversion = \"1.0.0\"\n\
581 /// [rules]\ncall = \"callee:IDENT '(' (args:NUMBER (',' args:NUMBER)*)? ')' tail:';'?\"\n",
582 /// )?;
583 /// let call = lang.kind("call").expect("a rule");
584 /// let fields: Vec<(&str, Cardinality)> =
585 /// lang.fields(call).map(|f| (f.name(), f.cardinality())).collect();
586 /// assert_eq!(
587 /// fields,
588 /// [("callee", Cardinality::One), ("args", Cardinality::Many), ("tail", Cardinality::Optional)]
589 /// );
590 /// # Ok::<(), lang_forge::Error>(())
591 /// ```
592 pub fn fields(&self, kind: Kind) -> impl Iterator<Item = crate::Field<'_>> {
593 let defs: &[grammar::FieldDef] = self
594 .grammar
595 .extra
596 .as_ref()
597 .and_then(|e| {
598 e.fields
599 .binary_search_by_key(&kind.index(), |(k, _)| *k)
600 .ok()
601 .map(|at| &*e.fields[at].1)
602 })
603 .unwrap_or(&[]);
604 defs.iter().map(move |d| crate::Field::new(self, d))
605 }
606
607 /// The members of `[ast]` supertype `name` (format 2), with supertypes
608 /// of supertypes expanded, or `None` if there is no such supertype.
609 ///
610 /// # Examples
611 ///
612 /// ```
613 /// use lang_forge::Language;
614 ///
615 /// let lang = Language::from_lsf(
616 /// "[sketch]\nformat = 2\n[language]\nname = \"s\"\nversion = \"1.0.0\"\n\
617 /// [rules]\nfile = \"(num | word)*\"\nnum = \"NUMBER\"\nword = \"IDENT\"\n\
618 /// [ast]\nAtom = [\"num\", \"word\"]\n",
619 /// )?;
620 /// let atoms: Vec<&str> = lang.supertype("Atom").expect("declared").map(|k| lang.kind_name(k)).collect();
621 /// assert_eq!(atoms, ["num", "word"]);
622 /// assert_eq!(lang.supertypes().collect::<Vec<_>>(), ["Atom"]);
623 /// # Ok::<(), lang_forge::Error>(())
624 /// ```
625 pub fn supertype(&self, name: &str) -> Option<impl ExactSizeIterator<Item = Kind> + '_> {
626 let extra = self.grammar.extra.as_ref()?;
627 let (_, members) = extra.supertypes.iter().find(|(n, _)| &**n == name)?;
628 Some(
629 members
630 .iter()
631 .map(|&i| self.grammar.kinds.at(usize::from(i))),
632 )
633 }
634
635 /// The names of the `[ast]` supertypes, in sketch order.
636 ///
637 /// # Examples
638 ///
639 /// ```
640 /// use lang_forge::Language;
641 ///
642 /// let lang = Language::from_lsf(
643 /// "[sketch]\nformat = 2\n[language]\nname = \"s\"\nversion = \"1.0.0\"\n\
644 /// [rules]\nfile = \"(num | word)*\"\nnum = \"NUMBER\"\nword = \"IDENT\"\n\
645 /// [ast]\nLiteral = [\"num\"]\nAtom = [\"Literal\", \"word\"]\n",
646 /// )?;
647 /// assert_eq!(lang.supertypes().collect::<Vec<_>>(), ["Literal", "Atom"]);
648 /// # Ok::<(), lang_forge::Error>(())
649 /// ```
650 pub fn supertypes(&self) -> impl Iterator<Item = &str> {
651 self.grammar
652 .extra
653 .iter()
654 .flat_map(|e| e.supertypes.iter().map(|(n, _)| &**n))
655 }
656
657 /// Warnings found while forging (format 2): checks set to `warn`, such
658 /// as unused rules (`LSF4302`) and unused token classes (`LSF3401`), and
659 /// keys LSF2 specifies that this release does not check. Spans are into
660 /// the sketch, as for [`Error`].
661 ///
662 /// # Examples
663 ///
664 /// ```
665 /// use lang_forge::Language;
666 ///
667 /// let lang = Language::from_lsf(
668 /// "[sketch]\nformat = 2\n[language]\nname = \"w\"\nversion = \"1.0.0\"\n\
669 /// [rules]\nfile = \"IDENT*\"\nforgotten = \"NUMBER\"\n",
670 /// )?;
671 /// let warning = &lang.warnings()[0];
672 /// assert_eq!(warning.code().map(|c| c.to_string()).as_deref(), Some("LSF4302"));
673 /// assert_eq!(warning.message(), "rule `forgotten` is never used");
674 /// # Ok::<(), lang_forge::Error>(())
675 /// ```
676 #[must_use]
677 pub fn warnings(&self) -> &[Diagnostic] {
678 self.grammar.extra.as_ref().map_or(&[], |e| &e.warnings)
679 }
680
681 /// The language's display name, from `[language] display_name`
682 /// (format 2), or its [`name`](Self::name).
683 ///
684 /// # Examples
685 ///
686 /// ```
687 /// use lang_forge::Language;
688 ///
689 /// let lang = Language::from_lsf(
690 /// "[sketch]\nformat = 2\n[language]\nname = \"mox\"\nversion = \"0.1.0\"\n\
691 /// display_name = \"Mox\"\ndescription = \"A modern PHP.\"\nedition = \"2026\"\n\
692 /// [rules]\nfile = \"IDENT*\"\n",
693 /// )?;
694 /// assert_eq!(lang.display_name(), "Mox");
695 /// assert_eq!(lang.description(), Some("A modern PHP."));
696 /// assert_eq!(lang.edition(), Some("2026"));
697 ///
698 /// let plain = Language::from_lsf("[language]\nname = \"p\"\n[rules]\nfile = \"IDENT*\"\n")?;
699 /// assert_eq!((plain.display_name(), plain.description(), plain.edition()), ("p", None, None));
700 /// # Ok::<(), lang_forge::Error>(())
701 /// ```
702 #[must_use]
703 pub fn display_name(&self) -> &str {
704 self.grammar
705 .extra
706 .as_ref()
707 .and_then(|e| e.display_name.as_deref())
708 .unwrap_or(&self.grammar.name)
709 }
710
711 /// `[language] description` (format 2), if given (see
712 /// [`display_name`](Self::display_name) for an example).
713 #[must_use]
714 pub fn description(&self) -> Option<&str> {
715 self.grammar.extra.as_ref()?.description.as_deref()
716 }
717
718 /// `[language] edition` (format 2), if given (see
719 /// [`display_name`](Self::display_name) for an example).
720 #[must_use]
721 pub fn edition(&self) -> Option<&str> {
722 self.grammar.extra.as_ref()?.edition.as_deref()
723 }
724
725 /// `[language] shebang_names` (format 2): the interpreter names that
726 /// identify the language in a `#!` line, for editors.
727 ///
728 /// # Examples
729 ///
730 /// ```
731 /// use lang_forge::Language;
732 ///
733 /// let lang = Language::from_lsf(
734 /// "[sketch]\nformat = 2\n[language]\nname = \"m\"\nversion = \"0.1.0\"\n\
735 /// shebang_names = [\"m\", \"mscript\"]\n[rules]\nfile = \"IDENT*\"\n",
736 /// )?;
737 /// assert_eq!(lang.shebang_names().collect::<Vec<_>>(), ["m", "mscript"]);
738 /// # Ok::<(), lang_forge::Error>(())
739 /// ```
740 pub fn shebang_names(&self) -> impl Iterator<Item = &str> {
741 self.grammar
742 .extra
743 .iter()
744 .flat_map(|e| e.shebang_names.iter().map(|s| &**s))
745 }
746
747 /// Splits `source` into tokens, trivia included.
748 ///
749 /// The tokens are contiguous and cover the whole source, so this is the
750 /// stream a syntax highlighter wants. Characters that begin no token come
751 /// back as `UNKNOWN` tokens; [`parse`](Self::parse) reports them, `lex`
752 /// does not. A source of 4 GiB or more, which spans cannot address,
753 /// yields no tokens.
754 ///
755 /// # Examples
756 ///
757 /// ```
758 /// use lang_forge::Language;
759 /// use lang_forge::syntax_lang::TokenKind;
760 ///
761 /// let lang = Language::from_lsf(
762 /// "[language]\nname = \"x\"\n[lexer]\nline_comments = [\"--\"]\n[rules]\nfile = \"IDENT*\"\n",
763 /// )?;
764 /// let tokens = lang.lex("alpha -- note\nbeta");
765 /// let significant: Vec<&str> = tokens
766 /// .iter()
767 /// .filter(|t| !t.is_trivia())
768 /// .map(|t| lang.kind_name(*t.kind()))
769 /// .collect();
770 /// assert_eq!(significant, ["IDENT", "IDENT"]);
771 /// assert_eq!(tokens.len(), 5); // IDENT, WHITESPACE, COMMENT, WHITESPACE, IDENT
772 /// # Ok::<(), lang_forge::Error>(())
773 /// ```
774 #[must_use]
775 pub fn lex(&self, source: &str) -> Vec<Token<Kind>> {
776 let mut tokens = Vec::new();
777 if u32::try_from(source.len()).is_ok() {
778 let mut diagnostics = Vec::new();
779 self.grammar
780 .lexer
781 .run(source, &mut tokens, &mut diagnostics);
782 }
783 tokens
784 }
785
786 /// Parses `source` into a lossless syntax tree.
787 ///
788 /// Never fails: problems become diagnostics on the returned [`Parse`] and
789 /// the tree is complete regardless, with unexpected tokens wrapped in
790 /// `ERROR` nodes. Input nested too deeply to parse is reported rather
791 /// than followed: the parser recurses at most 768 grammar levels, which
792 /// needs at most about 256 KiB of stack in a release build (768 KiB in a
793 /// debug build) and allows well over a hundred levels of nesting in
794 /// typical grammars.
795 ///
796 /// # Examples
797 ///
798 /// ```
799 /// use lang_forge::Language;
800 ///
801 /// let lang = Language::from_lsf(
802 /// "[language]\nname = \"block\"\n[rules]\nblock = \"'{' stmt* '}'\"\nstmt = \"IDENT ';'\"\n",
803 /// )?;
804 ///
805 /// let good = lang.parse("{ a; b; }");
806 /// assert!(!good.has_errors());
807 ///
808 /// // A stray `;` is skipped, the rest still parses.
809 /// let bad = lang.parse("{ a; ; b; }");
810 /// assert_eq!(bad.diagnostics().len(), 1);
811 /// assert_eq!(bad.diagnostics()[0].message(), "expected stmt, found `;`");
812 /// let error = lang.kind("ERROR").expect("built in");
813 /// assert_eq!(bad.tree().descendants().filter(|n| *n.kind() == error).count(), 1);
814 /// # Ok::<(), lang_forge::Error>(())
815 /// ```
816 #[must_use]
817 pub fn parse<'a>(&'a self, source: &'a str) -> Parse<'a> {
818 let (tree, diagnostics) = parser::parse(&self.grammar, source);
819 crate::inject::finish(self, source, tree, diagnostics)
820 }
821
822 /// Parses `source` as a file with `extension`: with the lexer mode and
823 /// start rule `[language] files` gives that extension (format 2), or as
824 /// [`parse`](Self::parse) does when it gives none.
825 ///
826 /// # Examples
827 ///
828 /// ```
829 /// use lang_forge::Language;
830 ///
831 /// let lang = Language::from_lsf(
832 /// "[sketch]\nformat = 2\n[language]\nname = \"x\"\nversion = \"1.0.0\"\n\
833 /// extensions = [\"x\", \"xs\"]\nfiles = { xs = { start = \"item\" } }\n\
834 /// [rules]\nfile = \"item*\"\nitem = \"NUMBER\"\n",
835 /// )?;
836 /// assert_eq!(lang.parse_file("xs", "7").tree().kind(), &lang.kind("item").expect("a rule"));
837 /// assert_eq!(lang.parse_file("x", "7 8").tree().kind(), &lang.kind("file").expect("a rule"));
838 /// # Ok::<(), lang_forge::Error>(())
839 /// ```
840 pub fn parse_file<'a>(&'a self, extension: &str, source: &'a str) -> Parse<'a> {
841 let entry = self.grammar.extra.as_ref().and_then(|e| {
842 e.files
843 .iter()
844 .find(|(ext, _, _)| &**ext == extension)
845 .map(|(_, mode, start)| (*mode, *start))
846 });
847 let (Some((mode, start)), Lex::V2(scanner)) = (entry, &self.grammar.lexer) else {
848 return self.parse(source);
849 };
850 if u32::try_from(source.len()).is_err() {
851 return self.parse(source);
852 }
853 let mut tokens = Vec::new();
854 let mut diagnostics = Vec::new();
855 scanner.run_range(source, 0, source.len(), mode, &mut tokens, &mut diagnostics);
856 let start = if start == u32::MAX {
857 self.grammar.program.start
858 } else {
859 start
860 };
861 let (tree, diagnostics) =
862 parser::parse_tokens(&self.grammar, source, &tokens, start, diagnostics);
863 crate::inject::finish(self, source, tree, diagnostics)
864 }
865
866 /// Assembles the language's capability pipeline from a registry of passes.
867 ///
868 /// `passes` may hold passes for many languages; the pipeline takes the
869 /// ones whose [`Pass::name`] the schematic's `[capabilities] include`
870 /// lists, in that order, and ignores the rest. Run it over each
871 /// [`Parse`] with [`PassManager::run`].
872 ///
873 /// # Errors
874 ///
875 /// Returns an [`Error`] with a diagnostic, pointing into the schematic,
876 /// for every included capability that has no pass in `passes` or more
877 /// than one.
878 ///
879 /// # Examples
880 ///
881 /// ```
882 /// use lang_forge::diag_lang::{Diagnostic, Label, Severity};
883 /// use lang_forge::pass_lang::{Outcome, Pass, PassError};
884 /// use lang_forge::{Capability, Language, Parse};
885 ///
886 /// /// Warns about every identifier written in capitals.
887 /// struct Shouting;
888 ///
889 /// impl<'a> Pass<Parse<'a>> for Shouting {
890 /// fn name(&self) -> &'static str {
891 /// "no-shouting"
892 /// }
893 ///
894 /// fn run(&mut self, parse: &mut Parse<'a>) -> Result<Outcome, PassError> {
895 /// let ident = parse.language().kind("IDENT").ok_or_else(|| PassError::new("no IDENT"))?;
896 /// let loud: Vec<_> = parse
897 /// .tree()
898 /// .tokens()
899 /// .filter(|t| *t.kind() == ident)
900 /// .filter(|t| {
901 /// let text = &parse.source()[t.span().start().to_usize()..t.span().end().to_usize()];
902 /// text.len() > 1 && text.chars().all(|c| c.is_ascii_uppercase())
903 /// })
904 /// .map(|t| t.span())
905 /// .collect();
906 /// for span in loud {
907 /// parse.report(Diagnostic::new(Severity::Warning, "no need to shout", Label::unlabelled(span)));
908 /// }
909 /// Ok(Outcome::Unchanged)
910 /// }
911 /// }
912 ///
913 /// let lang = Language::from_lsf(
914 /// "[language]\nname = \"words\"\n[rules]\nfile = \"IDENT*\"\n\
915 /// [capabilities]\ninclude = [\"no-shouting\"]\n",
916 /// )?;
917 /// let registry: Vec<Capability> = vec![Box::new(Shouting)];
918 /// let mut pipeline = lang.pipeline(registry)?;
919 ///
920 /// let mut parse = lang.parse("quiet LOUD calm");
921 /// pipeline.run(&mut parse).expect("the pass succeeds");
922 /// assert_eq!(parse.diagnostics().len(), 1);
923 /// assert_eq!(parse.diagnostics()[0].message(), "no need to shout");
924 /// # Ok::<(), lang_forge::Error>(())
925 /// ```
926 pub fn pipeline<'a>(
927 &self,
928 passes: impl IntoIterator<Item = Capability>,
929 ) -> Result<PassManager<Parse<'a>>, Error> {
930 let mut available: Vec<Option<Capability>> = passes.into_iter().map(Some).collect();
931 let mut manager = PassManager::new();
932 let mut problems = Vec::new();
933 let mut location = None;
934 for capability in self.grammar.capabilities.iter() {
935 let mut matching = available
936 .iter()
937 .enumerate()
938 .filter(|(_, p)| p.as_ref().is_some_and(|p| p.name() == &*capability.name))
939 .map(|(i, _)| i);
940 let (first, second) = (matching.next(), matching.next());
941 let problem = match (first, second) {
942 (Some(i), None) => {
943 if let Some(pass) = available[i].take() {
944 let _ = manager.add(Plugged(pass));
945 }
946 continue;
947 }
948 (None, _) => Diagnostic::new(
949 Severity::Error,
950 format!("capability `{}` has no pass", capability.name),
951 Label::unlabelled(capability.span),
952 )
953 .with_help(format!(
954 "pass a capability whose `name()` is \"{}\"",
955 capability.name
956 ))
957 .with_code(codes::CAPABILITY_MISSING),
958 (Some(_), Some(_)) => Diagnostic::new(
959 Severity::Error,
960 format!("capability `{}` has more than one pass", capability.name),
961 Label::unlabelled(capability.span),
962 )
963 .with_code(codes::CAPABILITY_AMBIGUOUS),
964 };
965 if location.is_none() {
966 location = Some((capability.line, capability.column));
967 }
968 problems.push(problem);
969 }
970 match location {
971 None => Ok(manager),
972 Some((line, column)) => Err(Error::located(problems, line, column)),
973 }
974 }
975}
976
977impl FromStr for Language {
978 type Err = Error;
979
980 /// Forges a language; the same as [`Language::from_lsf`].
981 ///
982 /// ```
983 /// use lang_forge::Language;
984 ///
985 /// let lang: Language = "[language]\nname = \"n\"\n[rules]\nn = \"NUMBER\"\n".parse()?;
986 /// assert_eq!(lang.name(), "n");
987 /// # Ok::<(), lang_forge::Error>(())
988 /// ```
989 fn from_str(schematic: &str) -> Result<Self, Error> {
990 Self::from_lsf(schematic)
991 }
992}
993
994/// A boxed capability, as the pass manager stores passes.
995struct Plugged(Capability);
996
997impl<'a> Pass<Parse<'a>> for Plugged {
998 fn name(&self) -> &'static str {
999 self.0.name()
1000 }
1001
1002 fn run(&mut self, unit: &mut Parse<'a>) -> Result<Outcome, PassError> {
1003 self.0.run(unit)
1004 }
1005}