use super::spec::collect_defs;
use super::{Definition, Pragma, Specification, split_version_selector};
use crate::error::{ParseError, ParserResult};
use crate::parser::{IncludeResolver, parser_text_with_resolver};
use crate::versioning::VersioningRecord;
pub(crate) struct VersionedIncludes<'a> {
resolver: &'a mut dyn IncludeResolver,
current: Option<String>,
stack: Vec<String>,
}
impl<'a> VersionedIncludes<'a> {
pub(crate) fn new(resolver: &'a mut dyn IncludeResolver, current: Option<String>) -> Self {
Self {
resolver,
current,
stack: Vec::new(),
}
}
pub(crate) fn expand(&mut self, raw_path: &str, out: &mut Vec<Definition>) -> ParserResult<()> {
let (target, version) = split_version_selector(raw_path);
let Some(version) = version else {
return Ok(());
};
let (file, content) = self.resolver.resolve(self.current.as_deref(), target)?;
if self.stack.contains(&file) {
return Err(ParseError::Message(format!(
"cyclic include detected: {} -> {file}",
self.stack.join(" -> ")
)));
}
self.stack.push(file.clone());
let projected = self.project(&file, &content, version);
self.stack.pop();
out.extend(projected?);
Ok(())
}
fn project(
&mut self,
file: &str,
content: &str,
version: &str,
) -> ParserResult<Vec<Definition>> {
let typed = parser_text_with_resolver(content, Some(file), &mut *self.resolver)?;
let current = self.current.replace(file.to_string());
let mut definitions = Vec::new();
let collected = collect_defs(typed.0, &mut definitions, self);
self.current = current;
collected?;
let spec = Specification(definitions);
let Some(record) = VersioningRecord::analyze(&spec)? else {
return Err(ParseError::Message(format!(
"xidl include: {file} is included at version `{version}` and declares no \
version timeline"
)));
};
let timeline = record.timeline();
let id = timeline
.iter()
.find(|(_, definition)| definition.ident == version || definition.value == version)
.map(|(id, _)| id);
let Some(id) = id else {
return Err(ParseError::Message(format!(
"xidl include: {file} declares no version `{version}` (declared versions: {})",
timeline.declared()
)));
};
let projected = record.project(&spec, id)?;
Ok(projected
.0
.into_iter()
.filter(|definition| {
!matches!(
definition,
Definition::Pragma(Pragma::XidlcOpenApiVersion(_))
)
})
.collect())
}
}