Skip to main content

vyre_libs/parsing/c/preprocess/
effects.rs

1//! Side-effect metadata for preprocessor directives.
2
3#[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/// Stable side-effect kind emitted for directives with frontend-visible state.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum CPreprocessorSideEffectKind {
19    /// `#include`.
20    Include,
21    /// GNU `#include_next`.
22    IncludeNext,
23    /// Unclassified pragma with implementation-defined payload.
24    Pragma,
25    /// `#pragma once`.
26    PragmaOnce,
27    /// `#pragma GCC diagnostic push` or `#pragma clang diagnostic push`.
28    PragmaDiagnosticPush,
29    /// `#pragma GCC diagnostic pop` or `#pragma clang diagnostic pop`.
30    PragmaDiagnosticPop,
31    /// `#pragma GCC diagnostic ignored`.
32    PragmaDiagnosticIgnored,
33    /// `#pragma GCC diagnostic warning`.
34    PragmaDiagnosticWarning,
35    /// `#pragma GCC diagnostic error`.
36    PragmaDiagnosticError,
37    /// `#error`.
38    ErrorDiagnostic,
39    /// GNU `#warning`.
40    WarningDiagnostic,
41    /// `#ident`.
42    Ident,
43    /// `#sccs`.
44    Sccs,
45    /// `#line`.
46    Line,
47}
48
49impl CPreprocessorSideEffectKind {
50    /// Return the stable side-effect metadata token ID.
51    #[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/// Source-positioned side effect decoded from one directive row.
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct CPreprocessorSideEffect {
75    /// Classified side-effect kind.
76    pub kind: CPreprocessorSideEffectKind,
77    /// Original source offset of the directive payload.
78    pub payload_start: usize,
79    /// Original byte length of the directive payload.
80    pub payload_len: usize,
81    /// Original source offset of the actionable detail payload.
82    pub detail_start: usize,
83    /// Original byte length of the actionable detail payload.
84    pub detail_len: usize,
85}
86
87/// Parallel metadata streams for preprocessor side effects.
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct CPreprocessorSideEffectMetadata {
90    /// Side-effect token ID per input token, or zero for no side effect.
91    pub kinds: Vec<u32>,
92    /// Original source offset of the directive payload per input token.
93    pub payload_starts: Vec<u32>,
94    /// Original byte length of the directive payload per input token.
95    pub payload_lens: Vec<u32>,
96    /// Original source offset of the detail payload per input token.
97    pub detail_starts: Vec<u32>,
98    /// Original byte length of the detail payload per input token.
99    pub detail_lens: Vec<u32>,
100}
101
102/// Decode pragma, include, and diagnostic side effects from one directive row.
103///
104/// # Errors
105///
106/// Returns a diagnostic when the row is not a directive row or uses a malformed
107/// recognized side-effect spelling.
108pub 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/// Build side-effect metadata for compact C tokens.
174///
175/// # Errors
176///
177/// Returns a diagnostic when token streams are inconsistent or a directive span
178/// is invalid.
179#[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}