Skip to main content

codehelion_frontend_c/
dialect.rs

1//! Lexical dialect descriptions for the C language family.
2//!
3//! C and C++ share almost all of their lexical structure; what differs is the
4//! keyword set, the operator inventory and a handful of literal forms. A
5//! [`Dialect`] captures exactly those differences, so one lexer and one
6//! unit-boundary detector serve both languages. The C dialect lives here; the
7//! C++ dialect is defined by the C++ frontend crate on top of the same
8//! machinery.
9
10/// The lexical parameters distinguishing one C-family language from another.
11#[derive(Debug, Clone, Copy)]
12pub struct Dialect {
13    /// Reserved words, lexed as [`TokenKind::Keyword`]. Contextual keywords
14    /// (`override`, `final`) are deliberately absent: they lex as identifiers,
15    /// matching how the language grammar treats them.
16    ///
17    /// [`TokenKind::Keyword`]: codehelion_core::frontend::TokenKind::Keyword
18    pub keywords: &'static [&'static str],
19    /// Multi-character operators, ordered longest first for greedy matching.
20    pub multi_punct: &'static [&'static str],
21    /// Whether `R"delim(...)delim"` raw string literals exist (C++ only).
22    pub raw_strings: bool,
23    /// Whether `'` may separate digits inside a number (C++14 and later).
24    pub digit_separators: bool,
25    /// Keywords that introduce a record body (`struct`, `union`, and for C++
26    /// also `class`), reported as [`UnitKind::Record`] units.
27    ///
28    /// [`UnitKind::Record`]: codehelion_core::frontend::UnitKind::Record
29    pub record_keywords: &'static [&'static str],
30    /// Whether `[capture](params) { ... }` lambdas exist (C++ only).
31    pub lambdas: bool,
32}
33
34/// C keywords: C11 plus the C23 spellings (`bool`, `true`, `nullptr`, ...).
35///
36/// Lexing a C11 file with C23 keywords is harmless — those spellings appear in
37/// practice via `<stdbool.h>` and friends, and treating them uniformly keeps
38/// token granularity stable across standard revisions.
39const C_KEYWORDS: &[&str] = &[
40    "_Alignas",
41    "_Alignof",
42    "_Atomic",
43    "_Bool",
44    "_Complex",
45    "_Generic",
46    "_Imaginary",
47    "_Noreturn",
48    "_Static_assert",
49    "_Thread_local",
50    "alignas",
51    "alignof",
52    "auto",
53    "bool",
54    "break",
55    "case",
56    "char",
57    "const",
58    "constexpr",
59    "continue",
60    "default",
61    "do",
62    "double",
63    "else",
64    "enum",
65    "extern",
66    "false",
67    "float",
68    "for",
69    "goto",
70    "if",
71    "inline",
72    "int",
73    "long",
74    "nullptr",
75    "register",
76    "restrict",
77    "return",
78    "short",
79    "signed",
80    "sizeof",
81    "static",
82    "static_assert",
83    "struct",
84    "switch",
85    "thread_local",
86    "true",
87    "typedef",
88    "typeof",
89    "typeof_unqual",
90    "union",
91    "unsigned",
92    "void",
93    "volatile",
94    "while",
95];
96
97/// C multi-character operators, longest first.
98const C_MULTI_PUNCT: &[&str] = &[
99    "<<=", ">>=", "...", "->", "++", "--", "<<", ">>", "<=", ">=", "==", "!=", "&&", "||", "+=",
100    "-=", "*=", "/=", "%=", "&=", "|=", "^=", "##",
101];
102
103/// The C dialect.
104pub const C: Dialect = Dialect {
105    keywords: C_KEYWORDS,
106    multi_punct: C_MULTI_PUNCT,
107    raw_strings: false,
108    digit_separators: false,
109    record_keywords: &["struct", "union"],
110    lambdas: false,
111};
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn c_multi_punct_is_ordered_longest_first() {
119        let lens: Vec<usize> = C.multi_punct.iter().map(|op| op.len()).collect();
120        let mut sorted = lens.clone();
121        sorted.sort_unstable_by(|a, b| b.cmp(a));
122        assert_eq!(lens, sorted, "greedy matching needs longest-first order");
123    }
124
125    #[test]
126    #[allow(clippy::assertions_on_constants)] // guards the dialect's shape
127    fn c_dialect_has_no_cpp_only_features() {
128        assert!(!C.raw_strings);
129        assert!(!C.digit_separators);
130        assert!(!C.lambdas);
131        assert!(!C.keywords.contains(&"class"));
132        assert!(!C.multi_punct.contains(&"::"));
133    }
134}