#[cfg(any(test, feature = "cpu-parity"))]
use crate::parsing::c::preprocess::directive_scan::for_each_directive_row;
use crate::parsing::c::preprocess::directive_scan::{skip_horizontal_ws, ScannedDirective};
use crate::parsing::c::preprocess::{CPreprocessorDirectiveKind, CPreprocessorError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CIncludeStyle {
Quote,
Angle,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CIncludeRequest {
pub directive: CPreprocessorDirectiveKind,
pub style: CIncludeStyle,
pub spelling: Vec<u8>,
pub directive_offset: usize,
pub payload_offset: usize,
}
impl CIncludeRequest {
#[must_use]
pub const fn is_include_next(&self) -> bool {
matches!(self.directive, CPreprocessorDirectiveKind::IncludeNext)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CSourceFile {
pub source_id: u32,
pub display_name: String,
pub bytes: Vec<u8>,
}
pub trait CPreprocessorSourceManager {
fn load_include(&self, request: &CIncludeRequest) -> Result<CSourceFile, CPreprocessorError>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CResolvedInclude {
pub token_index: usize,
pub request: CIncludeRequest,
pub source: CSourceFile,
}
pub fn parse_c_include_request(
row: &[u8],
directive_offset: usize,
) -> Result<Option<CIncludeRequest>, CPreprocessorError> {
let scan = ScannedDirective::classify(row, directive_offset)?;
let directive = scan.directive;
if !matches!(
directive.kind,
CPreprocessorDirectiveKind::Include | CPreprocessorDirectiveKind::IncludeNext
) {
return Ok(None);
}
let payload = scan.payload()?;
let (style, spelling, payload_rel) =
parse_header_name_payload(payload).map_err(|mut err| {
err.offset += directive.payload_start;
scan.remap(err)
})?;
Ok(Some(CIncludeRequest {
directive: directive.kind,
style,
spelling,
directive_offset,
payload_offset: scan.source_offset(directive.payload_start + payload_rel),
}))
}
#[cfg(any(test, feature = "cpu-parity"))]
pub fn reference_c_preprocessor_load_includes<M: CPreprocessorSourceManager>(
tok_types: &[u32],
tok_starts: &[u32],
tok_lens: &[u32],
source: &[u8],
manager: &M,
) -> Result<Vec<CResolvedInclude>, CPreprocessorError> {
let mut resolved = Vec::new();
for_each_directive_row(tok_types, tok_starts, tok_lens, source, |row| {
if let Some(request) = parse_c_include_request(row.bytes, row.start)? {
let loaded = manager.load_include(&request)?;
resolved.push(CResolvedInclude {
token_index: row.index,
request,
source: loaded,
});
}
Ok(())
})?;
Ok(resolved)
}
fn parse_header_name_payload(
payload: &[u8],
) -> Result<(CIncludeStyle, Vec<u8>, usize), CPreprocessorError> {
let start = skip_horizontal_ws(payload, 0);
let Some(open) = payload.get(start).copied() else {
return Err(CPreprocessorError {
offset: start,
message: "Fix: #include needs a header name payload",
});
};
match open {
b'"' => parse_delimited_header(payload, start, b'"', CIncludeStyle::Quote),
b'<' => parse_delimited_header(payload, start, b'>', CIncludeStyle::Angle),
_ => Err(CPreprocessorError {
offset: start,
message:
"Fix: #include payload must be a quoted or angle-bracket header name after macro expansion",
}),
}
}
fn parse_delimited_header(
payload: &[u8],
start: usize,
close: u8,
style: CIncludeStyle,
) -> Result<(CIncludeStyle, Vec<u8>, usize), CPreprocessorError> {
let mut index = start + 1;
while let Some(byte) = payload.get(index).copied() {
if matches!(byte, b'\n' | b'\r') {
return Err(CPreprocessorError {
offset: index,
message: "Fix: #include header name must close before newline",
});
}
if byte == close {
let trailing = skip_horizontal_ws(payload, index + 1);
if trailing != payload.len() {
return Err(CPreprocessorError {
offset: trailing,
message: "Fix: unexpected tokens after #include header name",
});
}
return Ok((style, payload[start + 1..index].to_vec(), start + 1));
}
index += 1;
}
Err(CPreprocessorError {
offset: start,
message: "Fix: terminate #include header name",
})
}