Skip to main content

adze_parser_backend_core/
lib.rs

1//! Core parser backend selection primitives.
2
3#![forbid(unsafe_op_in_unsafe_fn)]
4#![deny(missing_docs)]
5#![cfg_attr(feature = "strict_api", deny(unreachable_pub))]
6#![cfg_attr(not(feature = "strict_api"), warn(unreachable_pub))]
7#![cfg_attr(feature = "strict_docs", deny(missing_docs))]
8#![cfg_attr(not(feature = "strict_docs"), allow(missing_docs))]
9
10use core::fmt::{self, Display, Formatter};
11
12/// Contract outcome for backend selection under a given conflict condition.
13///
14/// This keeps behavior assertions in one place across parser and governance
15/// contracts without forcing callers to duplicate panic-string checks.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ParserBackendSelection {
18    /// A concrete backend can be selected for the current feature set.
19    Backend(ParserBackend),
20    /// Conflict grammars require the `glr` feature to be enabled.
21    ConflictsRequireGlr,
22}
23
24/// Message emitted when conflict handling requires GLR support.
25pub const CONFLICTS_REQUIRE_GLR_MESSAGE: &str = "Grammar has conflicts but GLR feature is not enabled. Enable the 'glr' feature in Cargo.toml or use the tree-sitter C runtime.";
26
27impl ParserBackend {
28    /// Resolve the backend-selection contract for a conflict condition.
29    ///
30    /// This mirrors `select` but returns an explicit outcome rather than panicking.
31    pub const fn select_contract(has_conflicts: bool) -> ParserBackendSelection {
32        match (cfg!(feature = "glr"), cfg!(feature = "pure-rust")) {
33            (true, _) => ParserBackendSelection::Backend(Self::GLR),
34            (false, true) => {
35                if has_conflicts {
36                    ParserBackendSelection::ConflictsRequireGlr
37                } else {
38                    ParserBackendSelection::Backend(Self::PureRust)
39                }
40            }
41            _ => ParserBackendSelection::Backend(Self::TreeSitter),
42        }
43    }
44}
45
46/// Parser backend supported by the runtime feature matrix.
47///
48/// # Examples
49///
50/// ```
51/// use adze_parser_backend_core::ParserBackend;
52///
53/// let backend = ParserBackend::GLR;
54/// assert!(backend.is_glr());
55/// assert!(backend.is_pure_rust());
56/// assert_eq!(backend.name(), "pure-Rust GLR parser");
57/// ```
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum ParserBackend {
60    /// Tree-sitter C runtime (default when pure-Rust is disabled).
61    TreeSitter,
62    /// Pure Rust LR parser (simple grammars without conflicts).
63    PureRust,
64    /// Pure Rust GLR parser (conflict-capable).
65    GLR,
66}
67
68impl ParserBackend {
69    /// Select parser backend based on feature flags and grammar metadata.
70    ///
71    /// # Arguments
72    /// * `has_conflicts` - Whether the grammar contains shift/reduce or reduce/reduce conflicts.
73    ///
74    /// # Panics
75    /// Panics if `has_conflicts` is true and the `pure-rust` feature is enabled without the `glr` feature.
76    pub const fn select(_has_conflicts: bool) -> Self {
77        match Self::select_contract(_has_conflicts) {
78            ParserBackendSelection::Backend(backend) => backend,
79            ParserBackendSelection::ConflictsRequireGlr => {
80                panic!("{}", CONFLICTS_REQUIRE_GLR_MESSAGE)
81            }
82        }
83    }
84
85    /// Whether this backend is the GLR parser.
86    pub const fn is_glr(self) -> bool {
87        matches!(self, Self::GLR)
88    }
89
90    /// Whether this backend is a pure-Rust parser (LR or GLR).
91    pub const fn is_pure_rust(self) -> bool {
92        matches!(self, Self::PureRust | Self::GLR)
93    }
94
95    /// Human-readable backend name.
96    pub const fn name(self) -> &'static str {
97        match self {
98            Self::TreeSitter => "tree-sitter C runtime",
99            Self::PureRust => "pure-Rust LR parser",
100            Self::GLR => "pure-Rust GLR parser",
101        }
102    }
103}
104
105impl Display for ParserBackend {
106    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
107        write!(f, "{}", self.name())
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn name_returns_human_readable_string() {
117        assert_eq!(ParserBackend::TreeSitter.name(), "tree-sitter C runtime");
118        assert_eq!(ParserBackend::PureRust.name(), "pure-Rust LR parser");
119        assert_eq!(ParserBackend::GLR.name(), "pure-Rust GLR parser");
120    }
121
122    #[test]
123    fn display_matches_name() {
124        for backend in [
125            ParserBackend::TreeSitter,
126            ParserBackend::PureRust,
127            ParserBackend::GLR,
128        ] {
129            assert_eq!(format!("{backend}"), backend.name());
130        }
131    }
132
133    #[test]
134    fn is_glr_only_for_glr() {
135        assert!(ParserBackend::GLR.is_glr());
136        assert!(!ParserBackend::PureRust.is_glr());
137        assert!(!ParserBackend::TreeSitter.is_glr());
138    }
139
140    #[test]
141    fn is_pure_rust_for_lr_and_glr() {
142        assert!(ParserBackend::PureRust.is_pure_rust());
143        assert!(ParserBackend::GLR.is_pure_rust());
144        assert!(!ParserBackend::TreeSitter.is_pure_rust());
145    }
146
147    #[test]
148    fn clone_and_eq() {
149        let a = ParserBackend::GLR;
150        let b = a;
151        assert_eq!(a, b);
152    }
153
154    #[test]
155    fn debug_format() {
156        let dbg = format!("{:?}", ParserBackend::TreeSitter);
157        assert_eq!(dbg, "TreeSitter");
158    }
159}