1#[cfg(any(test, feature = "cpu-parity"))]
4use crate::parsing::c::preprocess::directive_scan::for_each_directive_row;
5use crate::parsing::c::lex::tokens::{
6 TOK_PP_EFFECT_ERROR_DIAGNOSTIC, TOK_PP_EFFECT_IDENT, TOK_PP_EFFECT_INCLUDE,
7 TOK_PP_EFFECT_INCLUDE_NEXT, TOK_PP_EFFECT_LINE, TOK_PP_EFFECT_PRAGMA,
8 TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_ERROR, TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_IGNORED,
9 TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_POP, TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_PUSH,
10 TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_WARNING, TOK_PP_EFFECT_PRAGMA_ONCE, TOK_PP_EFFECT_SCCS,
11 TOK_PP_EFFECT_WARNING_DIAGNOSTIC,
12};
13use crate::parsing::c::preprocess::directive_scan::{skip_horizontal_ws, ScannedDirective};
14use crate::parsing::c::preprocess::{CPreprocessorDirectiveKind, CPreprocessorError};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum CPreprocessorSideEffectKind {
19 Include,
21 IncludeNext,
23 Pragma,
25 PragmaOnce,
27 PragmaDiagnosticPush,
29 PragmaDiagnosticPop,
31 PragmaDiagnosticIgnored,
33 PragmaDiagnosticWarning,
35 PragmaDiagnosticError,
37 ErrorDiagnostic,
39 WarningDiagnostic,
41 Ident,
43 Sccs,
45 Line,
47}
48
49impl CPreprocessorSideEffectKind {
50 #[must_use]
52 pub const fn token_id(self) -> u32 {
53 match self {
54 Self::Include => TOK_PP_EFFECT_INCLUDE,
55 Self::IncludeNext => TOK_PP_EFFECT_INCLUDE_NEXT,
56 Self::Pragma => TOK_PP_EFFECT_PRAGMA,
57 Self::PragmaOnce => TOK_PP_EFFECT_PRAGMA_ONCE,
58 Self::PragmaDiagnosticPush => TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_PUSH,
59 Self::PragmaDiagnosticPop => TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_POP,
60 Self::PragmaDiagnosticIgnored => TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_IGNORED,
61 Self::PragmaDiagnosticWarning => TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_WARNING,
62 Self::PragmaDiagnosticError => TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_ERROR,
63 Self::ErrorDiagnostic => TOK_PP_EFFECT_ERROR_DIAGNOSTIC,
64 Self::WarningDiagnostic => TOK_PP_EFFECT_WARNING_DIAGNOSTIC,
65 Self::Ident => TOK_PP_EFFECT_IDENT,
66 Self::Sccs => TOK_PP_EFFECT_SCCS,
67 Self::Line => TOK_PP_EFFECT_LINE,
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct CPreprocessorSideEffect {
75 pub kind: CPreprocessorSideEffectKind,
77 pub payload_start: usize,
79 pub payload_len: usize,
81 pub detail_start: usize,
83 pub detail_len: usize,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct CPreprocessorSideEffectMetadata {
90 pub kinds: Vec<u32>,
92 pub payload_starts: Vec<u32>,
94 pub payload_lens: Vec<u32>,
96 pub detail_starts: Vec<u32>,
98 pub detail_lens: Vec<u32>,
100}
101
102pub fn classify_c_preprocessor_side_effect(
109 row: &[u8],
110 directive_offset: usize,
111) -> Result<Option<CPreprocessorSideEffect>, CPreprocessorError> {
112 let scan = ScannedDirective::classify(row, directive_offset)?;
113 let directive = scan.directive;
114 let payload = scan.payload()?;
115 let payload_start = scan.source_offset(directive.payload_start);
116 let payload_end = scan.source_offset(directive.logical_end);
117 let payload_len = payload_end.saturating_sub(payload_start);
118
119 let (kind, detail_rel, detail_len) = match directive.kind {
120 CPreprocessorDirectiveKind::Include => (
121 CPreprocessorSideEffectKind::Include,
122 first_payload_byte(payload),
123 payload_trimmed_len(payload),
124 ),
125 CPreprocessorDirectiveKind::IncludeNext => (
126 CPreprocessorSideEffectKind::IncludeNext,
127 first_payload_byte(payload),
128 payload_trimmed_len(payload),
129 ),
130 CPreprocessorDirectiveKind::Pragma => {
131 classify_pragma_payload(payload).map_err(|mut err| {
132 err.offset += directive.payload_start;
133 scan.remap(err)
134 })?
135 }
136 CPreprocessorDirectiveKind::Error => (
137 CPreprocessorSideEffectKind::ErrorDiagnostic,
138 first_payload_byte(payload),
139 payload_trimmed_len(payload),
140 ),
141 CPreprocessorDirectiveKind::Warning => (
142 CPreprocessorSideEffectKind::WarningDiagnostic,
143 first_payload_byte(payload),
144 payload_trimmed_len(payload),
145 ),
146 CPreprocessorDirectiveKind::Ident => (
147 CPreprocessorSideEffectKind::Ident,
148 first_payload_byte(payload),
149 payload_trimmed_len(payload),
150 ),
151 CPreprocessorDirectiveKind::Sccs => (
152 CPreprocessorSideEffectKind::Sccs,
153 first_payload_byte(payload),
154 payload_trimmed_len(payload),
155 ),
156 CPreprocessorDirectiveKind::Line => (
157 CPreprocessorSideEffectKind::Line,
158 first_payload_byte(payload),
159 payload_trimmed_len(payload),
160 ),
161 _ => return Ok(None),
162 };
163
164 Ok(Some(CPreprocessorSideEffect {
165 kind,
166 payload_start,
167 payload_len,
168 detail_start: scan.source_offset(directive.payload_start + detail_rel),
169 detail_len,
170 }))
171}
172
173#[cfg(any(test, feature = "cpu-parity"))]
180pub fn reference_c_preprocessor_side_effect_metadata(
181 tok_types: &[u32],
182 tok_starts: &[u32],
183 tok_lens: &[u32],
184 source: &[u8],
185) -> Result<CPreprocessorSideEffectMetadata, CPreprocessorError> {
186 let mut metadata = CPreprocessorSideEffectMetadata {
187 kinds: vec![0; tok_types.len()],
188 payload_starts: vec![0; tok_types.len()],
189 payload_lens: vec![0; tok_types.len()],
190 detail_starts: vec![0; tok_types.len()],
191 detail_lens: vec![0; tok_types.len()],
192 };
193 for_each_directive_row(tok_types, tok_starts, tok_lens, source, |row| {
194 let Some(effect) = classify_c_preprocessor_side_effect(row.bytes, row.start)? else {
195 return Ok(());
196 };
197 let idx = row.index;
198 metadata.kinds[idx] = effect.kind.token_id();
199 metadata.payload_starts[idx] = checked_u32(
200 effect.payload_start,
201 "Fix: payload offset exceeds u32 metadata",
202 )?;
203 metadata.payload_lens[idx] = checked_u32(
204 effect.payload_len,
205 "Fix: payload length exceeds u32 metadata",
206 )?;
207 metadata.detail_starts[idx] = checked_u32(
208 effect.detail_start,
209 "Fix: detail offset exceeds u32 metadata",
210 )?;
211 metadata.detail_lens[idx] =
212 checked_u32(effect.detail_len, "Fix: detail length exceeds u32 metadata")?;
213 Ok(())
214 })?;
215 Ok(metadata)
216}
217
218fn classify_pragma_payload(
219 payload: &[u8],
220) -> Result<(CPreprocessorSideEffectKind, usize, usize), CPreprocessorError> {
221 let Some((first, first_start, first_end)) = next_ident(payload, 0) else {
222 return Err(CPreprocessorError {
223 offset: 0,
224 message: "Fix: #pragma needs a pragma namespace or command",
225 });
226 };
227 if first == b"once" {
228 return Ok((
229 CPreprocessorSideEffectKind::PragmaOnce,
230 first_start,
231 first_end - first_start,
232 ));
233 }
234 if first == b"GCC" || first == b"clang" {
235 let Some((second, _, second_end)) = next_ident(payload, first_end) else {
236 return Ok((
237 CPreprocessorSideEffectKind::Pragma,
238 first_start,
239 payload_trimmed_len(payload),
240 ));
241 };
242 if second == b"diagnostic" {
243 let Some((action, action_start, action_end)) = next_ident(payload, second_end) else {
244 return Err(CPreprocessorError {
245 offset: second_end,
246 message: "Fix: #pragma diagnostic needs push, pop, ignored, warning, or error",
247 });
248 };
249 let kind = match action {
250 b"push" => CPreprocessorSideEffectKind::PragmaDiagnosticPush,
251 b"pop" => CPreprocessorSideEffectKind::PragmaDiagnosticPop,
252 b"ignored" => CPreprocessorSideEffectKind::PragmaDiagnosticIgnored,
253 b"warning" => CPreprocessorSideEffectKind::PragmaDiagnosticWarning,
254 b"error" => CPreprocessorSideEffectKind::PragmaDiagnosticError,
255 _ => {
256 return Err(CPreprocessorError {
257 offset: action_start,
258 message:
259 "Fix: #pragma diagnostic action must be push, pop, ignored, warning, or error",
260 });
261 }
262 };
263 let detail_start = if matches!(
264 kind,
265 CPreprocessorSideEffectKind::PragmaDiagnosticIgnored
266 | CPreprocessorSideEffectKind::PragmaDiagnosticWarning
267 | CPreprocessorSideEffectKind::PragmaDiagnosticError
268 ) {
269 skip_horizontal_ws(payload, action_end)
270 } else {
271 action_start
272 };
273 return Ok((
274 kind,
275 detail_start,
276 payload_trimmed_len(&payload[detail_start..]),
277 ));
278 }
279 }
280 Ok((
281 CPreprocessorSideEffectKind::Pragma,
282 first_start,
283 payload_trimmed_len(payload),
284 ))
285}
286
287fn next_ident(payload: &[u8], index: usize) -> Option<(&[u8], usize, usize)> {
288 let mut start = skip_horizontal_ws(payload, index);
289 if !matches!(payload.get(start), Some(b'_' | b'a'..=b'z' | b'A'..=b'Z')) {
290 return None;
291 }
292 let ident_start = start;
293 start += 1;
294 while matches!(
295 payload.get(start),
296 Some(b'_' | b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9')
297 ) {
298 start += 1;
299 }
300 Some((&payload[ident_start..start], ident_start, start))
301}
302
303fn first_payload_byte(payload: &[u8]) -> usize {
304 skip_horizontal_ws(payload, 0)
305}
306
307fn payload_trimmed_len(payload: &[u8]) -> usize {
308 let start = skip_horizontal_ws(payload, 0);
309 let mut end = payload.len();
310 while end > start && matches!(payload[end - 1], b' ' | b'\t' | b'\x0b' | b'\x0c') {
311 end -= 1;
312 }
313 end.saturating_sub(start)
314}
315
316fn checked_u32(value: usize, message: &'static str) -> Result<u32, CPreprocessorError> {
317 u32::try_from(value).map_err(|_| CPreprocessorError {
318 offset: value,
319 message,
320 })
321}