badness_parser/lib.rs
1//! badness-parser — the lossless CST parser, semantic model, and
2//! command-signature database behind [badness](https://badness.dev/), for
3//! LaTeX (`.tex`, `.sty`/`.cls`, `.dtx`, `.ins`) and BibTeX (`.bib`).
4//!
5//! The parser treats input as generic TeX surface syntax and always produces a
6//! lossless rowan tree: `reconstruct(text) == text`, byte for byte. Semantics
7//! (arity, verbatim-ness, sectioning) are layered on top in [`semantic`],
8//! never inside the grammar.
9
10#![deny(clippy::debug_assert_with_mut_call)]
11
12macro_rules! impl_rowan_lang {
13 ($language:ty, $kind:ty, $name:literal) => {
14 const _: () = assert!(
15 <$kind>::ROOT as u16 + 1 == <$kind>::__LAST as u16,
16 "ROOT must be the final syntax kind"
17 );
18
19 impl From<$kind> for rowan::SyntaxKind {
20 fn from(kind: $kind) -> Self {
21 Self(kind as u16)
22 }
23 }
24
25 impl rowan::Language for $language {
26 type Kind = $kind;
27
28 fn kind_from_raw(raw: rowan::SyntaxKind) -> $kind {
29 assert!(
30 raw.0 <= <$kind>::ROOT as u16,
31 "invalid {} SyntaxKind discriminant: {}",
32 $name,
33 raw.0
34 );
35 // SAFETY: the kind is a contiguous `#[repr(u16)]` enum from zero
36 // through `ROOT`, and the assertion bounds the raw value to it.
37 unsafe { std::mem::transmute::<u16, $kind>(raw.0) }
38 }
39
40 fn kind_to_raw(kind: $kind) -> rowan::SyntaxKind {
41 kind.into()
42 }
43 }
44 };
45}
46
47pub mod ast;
48pub mod bib;
49pub mod declarations;
50pub mod directives;
51mod error;
52pub mod parser;
53pub mod semantic;
54pub mod syntax;
55
56pub use error::SyntaxError;
57
58// Re-export rowan so embedders can name the exact tree types this crate is
59// built against without pinning a matching rowan version themselves.
60pub use rowan;