1#[cfg(any(test, feature = "cpu-parity"))]
4use crate::parsing::c::lex::tokens::TOK_PREPROC;
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};
13#[cfg(any(test, feature = "cpu-parity"))]
14use crate::parsing::c::preprocess::c_logical_directive_len;
15use crate::parsing::c::preprocess::{
16 c_directive_payload, c_translation_phase_line_splice, try_classify_preprocessor_directive,
17 CPreprocessorDirectiveKind, CPreprocessorError,
18};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum CPreprocessorSideEffectKind {
23 Include,
25 IncludeNext,
27 Pragma,
29 PragmaOnce,
31 PragmaDiagnosticPush,
33 PragmaDiagnosticPop,
35 PragmaDiagnosticIgnored,
37 PragmaDiagnosticWarning,
39 PragmaDiagnosticError,
41 ErrorDiagnostic,
43 WarningDiagnostic,
45 Ident,
47 Sccs,
49 Line,
51}
52
53impl CPreprocessorSideEffectKind {
54 #[must_use]
56 pub const fn token_id(self) -> u32 {
57 match self {
58 Self::Include => TOK_PP_EFFECT_INCLUDE,
59 Self::IncludeNext => TOK_PP_EFFECT_INCLUDE_NEXT,
60 Self::Pragma => TOK_PP_EFFECT_PRAGMA,
61 Self::PragmaOnce => TOK_PP_EFFECT_PRAGMA_ONCE,
62 Self::PragmaDiagnosticPush => TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_PUSH,
63 Self::PragmaDiagnosticPop => TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_POP,
64 Self::PragmaDiagnosticIgnored => TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_IGNORED,
65 Self::PragmaDiagnosticWarning => TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_WARNING,
66 Self::PragmaDiagnosticError => TOK_PP_EFFECT_PRAGMA_DIAGNOSTIC_ERROR,
67 Self::ErrorDiagnostic => TOK_PP_EFFECT_ERROR_DIAGNOSTIC,
68 Self::WarningDiagnostic => TOK_PP_EFFECT_WARNING_DIAGNOSTIC,
69 Self::Ident => TOK_PP_EFFECT_IDENT,
70 Self::Sccs => TOK_PP_EFFECT_SCCS,
71 Self::Line => TOK_PP_EFFECT_LINE,
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct CPreprocessorSideEffect {
79 pub kind: CPreprocessorSideEffectKind,
81 pub payload_start: usize,
83 pub payload_len: usize,
85 pub detail_start: usize,
87 pub detail_len: usize,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct CPreprocessorSideEffectMetadata {
94 pub kinds: Vec<u32>,
96 pub payload_starts: Vec<u32>,
98 pub payload_lens: Vec<u32>,
100 pub detail_starts: Vec<u32>,
102 pub detail_lens: Vec<u32>,
104}
105
106pub fn classify_c_preprocessor_side_effect(
113 row: &[u8],
114 directive_offset: usize,
115) -> Result<Option<CPreprocessorSideEffect>, CPreprocessorError> {
116 let spliced = c_translation_phase_line_splice(row);
117 let directive = try_classify_preprocessor_directive(&spliced.bytes).map_err(|mut err| {
118 err.offset = directive_offset + spliced.original_offset(err.offset);
119 err
120 })?;
121 let payload = c_directive_payload(&spliced.bytes, directive).map_err(|mut err| {
122 err.offset = directive_offset + spliced.original_offset(err.offset);
123 err
124 })?;
125 let payload_start = directive_offset + spliced.original_offset(directive.payload_start);
126 let payload_end = directive_offset + spliced.original_offset(directive.logical_end);
127 let payload_len = payload_end.saturating_sub(payload_start);
128
129 let (kind, detail_rel, detail_len) = match directive.kind {
130 CPreprocessorDirectiveKind::Include => (
131 CPreprocessorSideEffectKind::Include,
132 first_payload_byte(payload),
133 payload_trimmed_len(payload),
134 ),
135 CPreprocessorDirectiveKind::IncludeNext => (
136 CPreprocessorSideEffectKind::IncludeNext,
137 first_payload_byte(payload),
138 payload_trimmed_len(payload),
139 ),
140 CPreprocessorDirectiveKind::Pragma => {
141 classify_pragma_payload(payload).map_err(|mut err| {
142 err.offset = directive_offset
143 + spliced.original_offset(directive.payload_start + err.offset);
144 err
145 })?
146 }
147 CPreprocessorDirectiveKind::Error => (
148 CPreprocessorSideEffectKind::ErrorDiagnostic,
149 first_payload_byte(payload),
150 payload_trimmed_len(payload),
151 ),
152 CPreprocessorDirectiveKind::Warning => (
153 CPreprocessorSideEffectKind::WarningDiagnostic,
154 first_payload_byte(payload),
155 payload_trimmed_len(payload),
156 ),
157 CPreprocessorDirectiveKind::Ident => (
158 CPreprocessorSideEffectKind::Ident,
159 first_payload_byte(payload),
160 payload_trimmed_len(payload),
161 ),
162 CPreprocessorDirectiveKind::Sccs => (
163 CPreprocessorSideEffectKind::Sccs,
164 first_payload_byte(payload),
165 payload_trimmed_len(payload),
166 ),
167 CPreprocessorDirectiveKind::Line => (
168 CPreprocessorSideEffectKind::Line,
169 first_payload_byte(payload),
170 payload_trimmed_len(payload),
171 ),
172 _ => return Ok(None),
173 };
174
175 Ok(Some(CPreprocessorSideEffect {
176 kind,
177 payload_start,
178 payload_len,
179 detail_start: directive_offset
180 + spliced.original_offset(directive.payload_start + detail_rel),
181 detail_len,
182 }))
183}
184
185#[deprecated(
192 note = "CPU reference oracle only; production side-effect metadata must use GPU preprocessor classification"
193)]
194#[cfg(any(test, feature = "cpu-parity"))]
195pub fn reference_c_preprocessor_side_effect_metadata(
196 tok_types: &[u32],
197 tok_starts: &[u32],
198 tok_lens: &[u32],
199 source: &[u8],
200) -> Result<CPreprocessorSideEffectMetadata, CPreprocessorError> {
201 if tok_types.len() != tok_starts.len() || tok_types.len() != tok_lens.len() {
202 return Err(CPreprocessorError {
203 offset: tok_types.len().min(tok_starts.len()).min(tok_lens.len()),
204 message: "Fix: token type/start/length streams must have identical lengths",
205 });
206 }
207
208 let mut metadata = CPreprocessorSideEffectMetadata {
209 kinds: vec![0; tok_types.len()],
210 payload_starts: vec![0; tok_types.len()],
211 payload_lens: vec![0; tok_types.len()],
212 detail_starts: vec![0; tok_types.len()],
213 detail_lens: vec![0; tok_types.len()],
214 };
215 for (idx, ((tok_type, start), len)) in
216 tok_types.iter().zip(tok_starts).zip(tok_lens).enumerate()
217 {
218 if *tok_type != TOK_PREPROC {
219 continue;
220 }
221 let start = usize::try_from(*start).map_err(|_| CPreprocessorError {
222 offset: idx,
223 message: "Fix: token start does not fit host usize",
224 })?;
225 let len = usize::try_from(*len).map_err(|_| CPreprocessorError {
226 offset: idx,
227 message: "Fix: token length does not fit host usize",
228 })?;
229 let token_end = start.checked_add(len).ok_or(CPreprocessorError {
230 offset: start,
231 message: "Fix: token span overflows source address space",
232 })?;
233 let logical_len = c_logical_directive_len(source, start);
234 if logical_len > len {
235 return Err(CPreprocessorError {
236 offset: start + len,
237 message:
238 "Fix: TOK_PREPROC span must include the full phase-2 spliced directive row",
239 });
240 }
241 if token_end > source.len() {
242 return Err(CPreprocessorError {
243 offset: start,
244 message: "Fix: preprocessor token span must be inside the source buffer",
245 });
246 }
247 let logical_end = start.checked_add(logical_len).ok_or(CPreprocessorError {
248 offset: start,
249 message: "Fix: directive logical span overflows source address space",
250 })?;
251 let row = source.get(start..logical_end).ok_or(CPreprocessorError {
252 offset: start,
253 message: "Fix: preprocessor token span must be inside the source buffer",
254 })?;
255 if let Some(effect) = classify_c_preprocessor_side_effect(row, start)? {
256 metadata.kinds[idx] = effect.kind.token_id();
257 metadata.payload_starts[idx] = checked_u32(
258 effect.payload_start,
259 "Fix: payload offset exceeds u32 metadata",
260 )?;
261 metadata.payload_lens[idx] = checked_u32(
262 effect.payload_len,
263 "Fix: payload length exceeds u32 metadata",
264 )?;
265 metadata.detail_starts[idx] = checked_u32(
266 effect.detail_start,
267 "Fix: detail offset exceeds u32 metadata",
268 )?;
269 metadata.detail_lens[idx] =
270 checked_u32(effect.detail_len, "Fix: detail length exceeds u32 metadata")?;
271 }
272 }
273 Ok(metadata)
274}
275
276fn classify_pragma_payload(
277 payload: &[u8],
278) -> Result<(CPreprocessorSideEffectKind, usize, usize), CPreprocessorError> {
279 let Some((first, first_start, first_end)) = next_ident(payload, 0) else {
280 return Err(CPreprocessorError {
281 offset: 0,
282 message: "Fix: #pragma needs a pragma namespace or command",
283 });
284 };
285 if first == b"once" {
286 return Ok((
287 CPreprocessorSideEffectKind::PragmaOnce,
288 first_start,
289 first_end - first_start,
290 ));
291 }
292 if first == b"GCC" || first == b"clang" {
293 let Some((second, _, second_end)) = next_ident(payload, first_end) else {
294 return Ok((
295 CPreprocessorSideEffectKind::Pragma,
296 first_start,
297 payload_trimmed_len(payload),
298 ));
299 };
300 if second == b"diagnostic" {
301 let Some((action, action_start, action_end)) = next_ident(payload, second_end) else {
302 return Err(CPreprocessorError {
303 offset: second_end,
304 message: "Fix: #pragma diagnostic needs push, pop, ignored, warning, or error",
305 });
306 };
307 let kind = match action {
308 b"push" => CPreprocessorSideEffectKind::PragmaDiagnosticPush,
309 b"pop" => CPreprocessorSideEffectKind::PragmaDiagnosticPop,
310 b"ignored" => CPreprocessorSideEffectKind::PragmaDiagnosticIgnored,
311 b"warning" => CPreprocessorSideEffectKind::PragmaDiagnosticWarning,
312 b"error" => CPreprocessorSideEffectKind::PragmaDiagnosticError,
313 _ => {
314 return Err(CPreprocessorError {
315 offset: action_start,
316 message:
317 "Fix: #pragma diagnostic action must be push, pop, ignored, warning, or error",
318 });
319 }
320 };
321 let detail_start = if matches!(
322 kind,
323 CPreprocessorSideEffectKind::PragmaDiagnosticIgnored
324 | CPreprocessorSideEffectKind::PragmaDiagnosticWarning
325 | CPreprocessorSideEffectKind::PragmaDiagnosticError
326 ) {
327 skip_horizontal_ws(payload, action_end)
328 } else {
329 action_start
330 };
331 return Ok((
332 kind,
333 detail_start,
334 payload_trimmed_len(&payload[detail_start..]),
335 ));
336 }
337 }
338 Ok((
339 CPreprocessorSideEffectKind::Pragma,
340 first_start,
341 payload_trimmed_len(payload),
342 ))
343}
344
345fn next_ident(payload: &[u8], index: usize) -> Option<(&[u8], usize, usize)> {
346 let mut start = skip_horizontal_ws(payload, index);
347 if !matches!(payload.get(start), Some(b'_' | b'a'..=b'z' | b'A'..=b'Z')) {
348 return None;
349 }
350 let ident_start = start;
351 start += 1;
352 while matches!(
353 payload.get(start),
354 Some(b'_' | b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9')
355 ) {
356 start += 1;
357 }
358 Some((&payload[ident_start..start], ident_start, start))
359}
360
361fn first_payload_byte(payload: &[u8]) -> usize {
362 skip_horizontal_ws(payload, 0)
363}
364
365fn payload_trimmed_len(payload: &[u8]) -> usize {
366 let start = skip_horizontal_ws(payload, 0);
367 let mut end = payload.len();
368 while end > start && matches!(payload[end - 1], b' ' | b'\t' | b'\x0b' | b'\x0c') {
369 end -= 1;
370 }
371 end.saturating_sub(start)
372}
373
374fn skip_horizontal_ws(bytes: &[u8], mut index: usize) -> usize {
375 while matches!(bytes.get(index), Some(b' ' | b'\t' | b'\x0b' | b'\x0c')) {
376 index += 1;
377 }
378 index
379}
380
381fn checked_u32(value: usize, message: &'static str) -> Result<u32, CPreprocessorError> {
382 u32::try_from(value).map_err(|_| CPreprocessorError {
383 offset: value,
384 message,
385 })
386}