#[macro_use]
extern crate lazy_static;
extern crate regex;
mod error;
pub use crate::error::Error;
use regex::Regex;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::marker::PhantomData;
#[derive(Debug, Default)]
pub struct Context<'a> {
included_files: BTreeMap<String, String>,
phantom: PhantomData<&'a String>,
}
impl<'a> Context<'a> {
pub fn new() -> Self {
Context {
..Default::default()
}
}
pub fn include<S>(&mut self, name: S, src: S) -> &mut Self
where
S: Into<String>,
{
self.included_files.insert(name.into(), src.into());
self
}
pub fn expand<S>(&self, src: S) -> Result<String, Error>
where
S: Into<String>,
{
let mut expanded_src = Vec::new();
self.expand_recursive(
&mut expanded_src,
&src.into(),
None,
&mut Vec::new(),
&mut BTreeSet::new(),
).map(move |_| expanded_src.join("\n"))
}
fn expand_recursive(
&'a self,
expanded_src: &mut Vec<String>,
src: &'a str,
in_file: Option<&'a str>,
include_stack: &mut Vec<&'a str>,
include_set: &mut BTreeSet<&'a str>,
) -> Result<(), Error> {
lazy_static! {
static ref INCLUDE_RE: Regex = Regex::new(
r#"^\s*#\s*(pragma\s*)?include\s+[<"](?P<file>.*)[>"]"#
).expect("failed to compile INCLUDE_RE regex");
}
let mut need_line_directive = false;
for (line_num, line) in src.lines().enumerate() {
if let Some(caps) = INCLUDE_RE.captures(line) {
let cap_match = caps
.name("file")
.expect("Could not find capture group with name \"file\"");
let included_file = cap_match.as_str();
if include_set.contains(&included_file) {
continue;
}
if include_stack.contains(&included_file) {
let in_file = in_file.map(|s| s.to_string());
let problem_include = included_file.to_string();
let include_stack = include_stack.into_iter().map(|s| s.to_string()).collect();
return Err(Error::RecursiveInclude {
in_file: in_file,
line_num: line_num,
problem_include: problem_include,
include_stack: include_stack,
});
}
if let Some(src) = self.included_files.get(included_file) {
include_stack.push(&included_file);
self.expand_recursive(
expanded_src,
&src,
Some(included_file),
include_stack,
include_set,
)?;
include_stack.pop();
need_line_directive = true;
} else {
let in_file = in_file.map(|s| s.to_string());
let problem_include = included_file.to_string();
return Err(Error::FileNotFound {
in_file: in_file,
line_num: line_num,
problem_include: problem_include,
});
}
} else {
if need_line_directive {
expanded_src.push(format!("#line {} 0", line_num + 1));
}
need_line_directive = false;
expanded_src.push(String::from(line));
}
}
if let Some(in_file) = in_file {
include_set.insert(in_file);
}
Ok(())
}
}