Skip to main content

codehelion_frontend_c/
lib.rs

1//! C frontends for codehelion.
2//!
3//! Fast mode implements [`codehelion_core::frontend::Frontend`]: an
4//! error-tolerant lexer paired with delimiter-matching unit-boundary
5//! detection. Nothing here preprocesses or executes the source; directives
6//! are dropped whole and macros pass through as ordinary tokens. Structural
7//! mode lives in [`ir`]: a real tree-sitter parse mapped onto the
8//! language-neutral Syntax IR.
9//!
10//! Both modes share their machinery with the C++ frontend crate: the lexer is
11//! parameterized by a [`dialect::Dialect`] carrying the keyword set, operator
12//! inventory and dialect-only literal forms, and the structural CST walker is
13//! parameterized by an [`ir::IrMapping`] carrying the node-mapping table.
14
15pub mod dialect;
16pub mod ir;
17pub mod lexer;
18pub mod units;
19
20use codehelion_core::discovery::Language;
21use codehelion_core::frontend::{Frontend, LexedFile};
22
23/// Version of the lexer and unit-boundary machinery shared by C and C++.
24///
25/// It is embedded in both Fast frontend fingerprint tags. Bump it whenever a
26/// change to the shared implementation changes tokens or unit boundaries.
27pub const C_FAMILY_LEXER_VERSION: &str = "c-family-lexer-v1";
28
29/// Version tag of this frontend, used as a fingerprint input. The C dialect
30/// revision and the shared C-family lexer revision are both part of it.
31pub const FRONTEND_VERSION: &str = "c-lexer-v1+c-family-lexer-v1";
32
33/// The C Fast-mode frontend.
34#[derive(Debug, Clone, Copy, Default)]
35pub struct CFrontend;
36
37impl Frontend for CFrontend {
38    fn language(&self) -> Language {
39        Language::C
40    }
41
42    fn frontend_version(&self) -> &'static str {
43        FRONTEND_VERSION
44    }
45
46    fn lex(&self, source: &str) -> LexedFile {
47        let (tokens, mut diagnostics) = lexer::lex(source, &dialect::C);
48        let (units, unit_diagnostics) = units::detect(&tokens, &dialect::C);
49        diagnostics.extend(unit_diagnostics);
50        LexedFile {
51            language: Language::C,
52            frontend_version: FRONTEND_VERSION,
53            tokens,
54            units,
55            diagnostics,
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use codehelion_core::ir::StructuralFrontend;
64    use proptest::prelude::*;
65    use std::time::{Duration, Instant};
66
67    #[test]
68    fn frontend_reports_language_and_version() {
69        let frontend = CFrontend;
70        assert_eq!(frontend.language(), Language::C);
71        assert_eq!(frontend.frontend_version(), FRONTEND_VERSION);
72        assert!(FRONTEND_VERSION.ends_with(C_FAMILY_LEXER_VERSION));
73    }
74
75    #[test]
76    fn lexing_a_file_yields_tokens_units_and_the_version() {
77        let lexed = CFrontend.lex("int main(void) { return 0; }");
78        assert_eq!(lexed.language, Language::C);
79        assert_eq!(lexed.frontend_version, FRONTEND_VERSION);
80        assert!(!lexed.tokens.is_empty());
81        assert_eq!(lexed.units.len(), 1);
82        assert!(lexed.diagnostics.is_empty());
83    }
84
85    proptest! {
86        #![proptest_config(ProptestConfig::with_cases(64))]
87
88        #[test]
89        fn arbitrary_text_never_panics(source in proptest::collection::vec(any::<char>(), 0..1024)
90            .prop_map(|characters| characters.into_iter().collect::<String>())) {
91            let started = Instant::now();
92            let _ = CFrontend.lex(&source);
93            let _ = ir::CStructuralFrontend.parse(&source);
94            prop_assert!(
95                started.elapsed() < Duration::from_secs(1),
96                "a bounded frontend input took too long"
97            );
98        }
99    }
100}