Skip to main content

codehelion_frontend_rust/
lib.rs

1//! Rust frontends for codehelion.
2//!
3//! Implements [`codehelion_core::frontend::Frontend`] for Rust: an
4//! error-tolerant lexer paired with brace-matching unit-boundary detection.
5//! The [`ir`] module adds the Structural-mode frontend, which parses the file
6//! with a real Rust parser and maps the tree onto the Syntax IR. Nothing here
7//! executes or expands the source; macros and generics pass through as tokens.
8
9pub mod ir;
10mod lexer;
11mod units;
12
13use codehelion_core::discovery::Language;
14use codehelion_core::frontend::{Frontend, LexedFile};
15
16/// Version tag of this frontend, used as a fingerprint input. Bump it whenever
17/// a change alters the token stream or unit boundaries for unchanged input.
18///
19/// Bumped with the parser it is built on. A newer parser can classify a token
20/// differently — a word that was an identifier becoming a keyword is the usual
21/// way — and fingerprints carry this version so that streams produced under
22/// rules that may disagree are never merged on the strength of an equal hash.
23pub const FRONTEND_VERSION: &str = "rust-lexer-v1";
24
25/// The Rust Fast-mode frontend.
26#[derive(Debug, Clone, Copy, Default)]
27pub struct RustFrontend;
28
29impl Frontend for RustFrontend {
30    fn language(&self) -> Language {
31        Language::Rust
32    }
33
34    fn frontend_version(&self) -> &'static str {
35        FRONTEND_VERSION
36    }
37
38    fn lex(&self, source: &str) -> LexedFile {
39        let (tokens, diagnostics) = lexer::lex(source);
40        let units = units::detect(&tokens);
41        LexedFile {
42            language: Language::Rust,
43            frontend_version: FRONTEND_VERSION,
44            tokens,
45            units,
46            diagnostics,
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use codehelion_core::ir::StructuralFrontend;
55    use proptest::prelude::*;
56    use std::time::{Duration, Instant};
57
58    #[test]
59    fn frontend_reports_language_and_version() {
60        let frontend = RustFrontend;
61        assert_eq!(frontend.language(), Language::Rust);
62        assert_eq!(frontend.frontend_version(), FRONTEND_VERSION);
63    }
64
65    #[test]
66    fn lexing_a_file_yields_tokens_units_and_the_version() {
67        let lexed = RustFrontend.lex("fn main() { let x = 1; }");
68        assert_eq!(lexed.language, Language::Rust);
69        assert_eq!(lexed.frontend_version, FRONTEND_VERSION);
70        assert!(!lexed.tokens.is_empty());
71        assert_eq!(lexed.units.len(), 1);
72        assert!(lexed.diagnostics.is_empty());
73    }
74
75    proptest! {
76        #![proptest_config(ProptestConfig::with_cases(64))]
77
78        #[test]
79        fn arbitrary_text_never_panics(source in proptest::collection::vec(any::<char>(), 0..1024)
80            .prop_map(|characters| characters.into_iter().collect::<String>())) {
81            let started = Instant::now();
82            let _ = RustFrontend.lex(&source);
83            let _ = ir::RustStructuralFrontend.parse(&source);
84            prop_assert!(
85                started.elapsed() < Duration::from_secs(1),
86                "a bounded frontend input took too long"
87            );
88        }
89    }
90}