vyre_libs/parsing/c/preprocess/
source.rs1#[cfg(any(test, feature = "cpu-parity"))]
4use crate::parsing::c::lex::tokens::TOK_PREPROC;
5#[cfg(any(test, feature = "cpu-parity"))]
6use crate::parsing::c::preprocess::c_logical_directive_len;
7use crate::parsing::c::preprocess::{
8 c_directive_payload, c_translation_phase_line_splice, try_classify_preprocessor_directive,
9 CPreprocessorDirectiveKind, CPreprocessorError,
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum CIncludeStyle {
15 Quote,
17 Angle,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct CIncludeRequest {
24 pub directive: CPreprocessorDirectiveKind,
26 pub style: CIncludeStyle,
28 pub spelling: Vec<u8>,
30 pub directive_offset: usize,
32 pub payload_offset: usize,
34}
35
36impl CIncludeRequest {
37 #[must_use]
39 pub const fn is_include_next(&self) -> bool {
40 matches!(self.directive, CPreprocessorDirectiveKind::IncludeNext)
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct CSourceFile {
47 pub source_id: u32,
49 pub display_name: String,
51 pub bytes: Vec<u8>,
53}
54
55pub trait CPreprocessorSourceManager {
61 fn load_include(&self, request: &CIncludeRequest) -> Result<CSourceFile, CPreprocessorError>;
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct CResolvedInclude {
73 pub token_index: usize,
75 pub request: CIncludeRequest,
77 pub source: CSourceFile,
79}
80
81pub fn parse_c_include_request(
91 row: &[u8],
92 directive_offset: usize,
93) -> Result<Option<CIncludeRequest>, CPreprocessorError> {
94 let spliced = c_translation_phase_line_splice(row);
95 let directive = try_classify_preprocessor_directive(&spliced.bytes).map_err(|mut err| {
96 err.offset = directive_offset + spliced.original_offset(err.offset);
97 err
98 })?;
99 if !matches!(
100 directive.kind,
101 CPreprocessorDirectiveKind::Include | CPreprocessorDirectiveKind::IncludeNext
102 ) {
103 return Ok(None);
104 }
105
106 let payload = c_directive_payload(&spliced.bytes, directive).map_err(|mut err| {
107 err.offset = directive_offset + spliced.original_offset(err.offset);
108 err
109 })?;
110 let (style, spelling, payload_rel) =
111 parse_header_name_payload(payload).map_err(|mut err| {
112 err.offset =
113 directive_offset + spliced.original_offset(directive.payload_start + err.offset);
114 err
115 })?;
116 Ok(Some(CIncludeRequest {
117 directive: directive.kind,
118 style,
119 spelling,
120 directive_offset,
121 payload_offset: directive_offset
122 + spliced.original_offset(directive.payload_start + payload_rel),
123 }))
124}
125
126#[deprecated(
134 note = "CPU reference oracle only; production include loading must use the GPU preprocessor pipeline source-manager path"
135)]
136#[cfg(any(test, feature = "cpu-parity"))]
137pub fn reference_c_preprocessor_load_includes<M: CPreprocessorSourceManager>(
138 tok_types: &[u32],
139 tok_starts: &[u32],
140 tok_lens: &[u32],
141 source: &[u8],
142 manager: &M,
143) -> Result<Vec<CResolvedInclude>, CPreprocessorError> {
144 if tok_types.len() != tok_starts.len() || tok_types.len() != tok_lens.len() {
145 return Err(CPreprocessorError {
146 offset: tok_types.len().min(tok_starts.len()).min(tok_lens.len()),
147 message: "Fix: token type/start/length streams must have identical lengths",
148 });
149 }
150
151 let mut resolved = Vec::new();
152 for (idx, ((tok_type, start), len)) in
153 tok_types.iter().zip(tok_starts).zip(tok_lens).enumerate()
154 {
155 if *tok_type != TOK_PREPROC {
156 continue;
157 }
158 let start = usize::try_from(*start).map_err(|_| CPreprocessorError {
159 offset: idx,
160 message: "Fix: token start does not fit host usize",
161 })?;
162 let len = usize::try_from(*len).map_err(|_| CPreprocessorError {
163 offset: idx,
164 message: "Fix: token length does not fit host usize",
165 })?;
166 let token_end = start.checked_add(len).ok_or(CPreprocessorError {
167 offset: start,
168 message: "Fix: token span overflows source address space",
169 })?;
170 let logical_len = c_logical_directive_len(source, start);
171 if logical_len > len {
172 return Err(CPreprocessorError {
173 offset: start + len,
174 message:
175 "Fix: TOK_PREPROC span must include the full phase-2 spliced directive row",
176 });
177 }
178 if token_end > source.len() {
179 return Err(CPreprocessorError {
180 offset: start,
181 message: "Fix: preprocessor token span must be inside the source buffer",
182 });
183 }
184 let logical_end = start.checked_add(logical_len).ok_or(CPreprocessorError {
185 offset: start,
186 message: "Fix: directive logical span overflows source address space",
187 })?;
188 let row = source.get(start..logical_end).ok_or(CPreprocessorError {
189 offset: start,
190 message: "Fix: preprocessor token span must be inside the source buffer",
191 })?;
192 if let Some(request) = parse_c_include_request(row, start)? {
193 let source = manager.load_include(&request)?;
194 resolved.push(CResolvedInclude {
195 token_index: idx,
196 request,
197 source,
198 });
199 }
200 }
201 Ok(resolved)
202}
203
204fn parse_header_name_payload(
205 payload: &[u8],
206) -> Result<(CIncludeStyle, Vec<u8>, usize), CPreprocessorError> {
207 let start = skip_horizontal_ws(payload, 0);
208 let Some(open) = payload.get(start).copied() else {
209 return Err(CPreprocessorError {
210 offset: start,
211 message: "Fix: #include needs a header name payload",
212 });
213 };
214 match open {
215 b'"' => parse_delimited_header(payload, start, b'"', CIncludeStyle::Quote),
216 b'<' => parse_delimited_header(payload, start, b'>', CIncludeStyle::Angle),
217 _ => Err(CPreprocessorError {
218 offset: start,
219 message:
220 "Fix: #include payload must be a quoted or angle-bracket header name after macro expansion",
221 }),
222 }
223}
224
225fn parse_delimited_header(
226 payload: &[u8],
227 start: usize,
228 close: u8,
229 style: CIncludeStyle,
230) -> Result<(CIncludeStyle, Vec<u8>, usize), CPreprocessorError> {
231 let mut index = start + 1;
232 while let Some(byte) = payload.get(index).copied() {
233 if matches!(byte, b'\n' | b'\r') {
234 return Err(CPreprocessorError {
235 offset: index,
236 message: "Fix: #include header name must close before newline",
237 });
238 }
239 if byte == close {
240 let trailing = skip_horizontal_ws(payload, index + 1);
241 if trailing != payload.len() {
242 return Err(CPreprocessorError {
243 offset: trailing,
244 message: "Fix: unexpected tokens after #include header name",
245 });
246 }
247 return Ok((style, payload[start + 1..index].to_vec(), start + 1));
248 }
249 index += 1;
250 }
251 Err(CPreprocessorError {
252 offset: start,
253 message: "Fix: terminate #include header name",
254 })
255}
256
257fn skip_horizontal_ws(bytes: &[u8], mut index: usize) -> usize {
258 while matches!(bytes.get(index), Some(b' ' | b'\t' | b'\x0b' | b'\x0c')) {
259 index += 1;
260 }
261 index
262}