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}