Skip to main content

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}