Skip to main content

adze_parser_feature_profile_core/
lib.rs

1//! Core parser feature-profile representation and backend resolution policy.
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/// Re-exported parser backend enum for feature-profile–based backend resolution.
13pub use adze_parser_backend_core::ParserBackend;
14pub use adze_parser_backend_core::ParserBackendSelection;
15
16/// Snapshot of parser-related feature flags for this build.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct ParserFeatureProfile {
19    /// `pure-rust` feature is enabled.
20    pub pure_rust: bool,
21    /// `tree-sitter-standard` feature is enabled.
22    pub tree_sitter_standard: bool,
23    /// `tree-sitter-c2rust` feature is enabled.
24    pub tree_sitter_c2rust: bool,
25    /// `glr` feature is enabled.
26    pub glr: bool,
27}
28
29impl ParserFeatureProfile {
30    /// Snapshot of active feature flags for the current crate compilation.
31    #[must_use]
32    pub const fn current() -> Self {
33        Self {
34            pure_rust: cfg!(feature = "pure-rust"),
35            tree_sitter_standard: cfg!(feature = "tree-sitter-standard"),
36            tree_sitter_c2rust: cfg!(feature = "tree-sitter-c2rust"),
37            glr: cfg!(feature = "glr"),
38        }
39    }
40
41    /// Resolve the effective backend from this profile.
42    #[must_use]
43    pub const fn resolve_backend(self, has_conflicts: bool) -> ParserBackend {
44        match Self::backend_selection_contract(self, has_conflicts) {
45            ParserBackendSelection::Backend(backend) => backend,
46            ParserBackendSelection::ConflictsRequireGlr => panic!(
47                "Grammar has conflicts but GLR feature is not enabled. Enable the 'glr' feature in Cargo.toml or use the tree-sitter C runtime."
48            ),
49        }
50    }
51
52    /// Resolution contract for this profile and conflict condition.
53    ///
54    /// Exposed for test-surface and migration checks that need to compare panic-vs.
55    /// backend-returning behavior without matching panic strings.
56    #[must_use]
57    pub const fn backend_selection_contract(self, has_conflicts: bool) -> ParserBackendSelection {
58        if self.glr {
59            ParserBackendSelection::Backend(ParserBackend::GLR)
60        } else if self.pure_rust {
61            if has_conflicts {
62                ParserBackendSelection::ConflictsRequireGlr
63            } else {
64                ParserBackendSelection::Backend(ParserBackend::PureRust)
65            }
66        } else {
67            ParserBackendSelection::Backend(ParserBackend::TreeSitter)
68        }
69    }
70
71    /// Whether feature flags indicate the pure-Rust runtime is compiled in.
72    #[must_use]
73    pub const fn has_pure_rust(self) -> bool {
74        self.pure_rust
75    }
76
77    /// Whether feature flags indicate GLR is compiled in.
78    #[must_use]
79    pub const fn has_glr(self) -> bool {
80        self.glr
81    }
82
83    /// Whether feature flags indicate any tree-sitter backend is compiled in.
84    #[must_use]
85    pub const fn has_tree_sitter(self) -> bool {
86        self.tree_sitter_standard || self.tree_sitter_c2rust
87    }
88}
89
90impl Display for ParserFeatureProfile {
91    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
92        let mut active = 0usize;
93
94        if self.pure_rust {
95            write!(f, "pure-rust")?;
96            active += 1;
97        }
98        if self.tree_sitter_standard {
99            if active > 0 {
100                write!(f, ", ")?;
101            }
102            write!(f, "tree-sitter-standard")?;
103            active += 1;
104        }
105        if self.tree_sitter_c2rust {
106            if active > 0 {
107                write!(f, ", ")?;
108            }
109            write!(f, "tree-sitter-c2rust")?;
110            active += 1;
111        }
112        if self.glr {
113            if active > 0 {
114                write!(f, ", ")?;
115            }
116            write!(f, "glr")?;
117            active += 1;
118        }
119
120        if active == 0 {
121            write!(f, "none")
122        } else {
123            Ok(())
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn profile_matches_cfg() {
134        let profile = ParserFeatureProfile::current();
135        assert_eq!(profile.pure_rust, cfg!(feature = "pure-rust"));
136        assert_eq!(
137            profile.tree_sitter_standard,
138            cfg!(feature = "tree-sitter-standard")
139        );
140        assert_eq!(
141            profile.tree_sitter_c2rust,
142            cfg!(feature = "tree-sitter-c2rust")
143        );
144        assert_eq!(profile.glr, cfg!(feature = "glr"));
145    }
146
147    #[test]
148    fn resolve_backend_glr_takes_priority() {
149        let profile = ParserFeatureProfile {
150            pure_rust: true,
151            tree_sitter_standard: true,
152            tree_sitter_c2rust: true,
153            glr: true,
154        };
155        assert_eq!(profile.resolve_backend(false), ParserBackend::GLR);
156        assert_eq!(profile.resolve_backend(true), ParserBackend::GLR);
157    }
158
159    #[test]
160    fn resolve_backend_pure_rust_without_conflicts() {
161        let profile = ParserFeatureProfile {
162            pure_rust: true,
163            tree_sitter_standard: false,
164            tree_sitter_c2rust: false,
165            glr: false,
166        };
167        assert_eq!(profile.resolve_backend(false), ParserBackend::PureRust);
168    }
169
170    #[test]
171    #[should_panic(expected = "GLR feature is not enabled")]
172    fn resolve_backend_pure_rust_with_conflicts_panics() {
173        let profile = ParserFeatureProfile {
174            pure_rust: true,
175            tree_sitter_standard: false,
176            tree_sitter_c2rust: false,
177            glr: false,
178        };
179        let _ = profile.resolve_backend(true);
180    }
181
182    #[test]
183    fn display_none_when_empty() {
184        let profile = ParserFeatureProfile {
185            pure_rust: false,
186            tree_sitter_standard: false,
187            tree_sitter_c2rust: false,
188            glr: false,
189        };
190        assert_eq!(profile.to_string(), "none");
191    }
192}