use std::str::{self, FromStr};
use std::path::{PathBuf, Path};
use std::fs::File;
use std::io::{self, Read};
use std::cmp::Ordering;
use nom::{IResult, ErrorKind, Needed, FindSubstring, digit, space, multispace, line_ending};
use parser::escape_c_string;
use ::{byte_offset_to_line_col, line_to_byte_offset};
#[derive(Debug)]
pub enum BoundsError {
NotWithinBounds,
IOError(io::Error, Option<PathBuf>),
ParseError(::ParseError)
}
impl From<io::Error> for BoundsError {
fn from(err: io::Error) -> Self {
BoundsError::IOError(err, None)
}
}
impl From<::ParseError> for BoundsError {
fn from(err: ::ParseError) -> Self {
BoundsError::ParseError(err)
}
}
#[derive(Debug)]
pub enum IncludeError {
NoBoundReturned(PathBuf),
LinemarkerInDtsi(PathBuf),
IOError(io::Error, Option<PathBuf>),
ParseError(::ParseError)
}
impl From<io::Error> for IncludeError {
fn from(err: io::Error) -> Self {
IncludeError::IOError(err, None)
}
}
impl From<::ParseError> for IncludeError {
fn from(err: ::ParseError) -> Self {
IncludeError::ParseError(err)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncludeBounds {
path: PathBuf,
global_start: usize,
child_start: usize,
len: usize,
method: IncludeMethod,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IncludeMethod {
DTS,
CPP,
}
impl PartialOrd for IncludeBounds {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for IncludeBounds {
fn cmp(&self, other: &Self) -> Ordering {
use std::cmp::Ordering::*;
match self.start().cmp(&other.start()) {
Equal => self.end().cmp(&other.end()),
o => o,
}
}
}
impl IncludeBounds {
pub fn child_path(&self) -> &Path {
&self.path
}
pub fn start(&self) -> usize {
self.global_start
}
pub fn end(&self) -> usize {
self.global_start + self.len
}
pub fn len(&self) -> usize {
self.len
}
pub fn child_start(&self) -> usize {
self.child_start
}
pub fn include_method(&self) -> &IncludeMethod {
&self.method
}
fn split_bounds(bounds: &mut Vec<IncludeBounds>, start: usize, end: usize, offset: usize) {
let mut remainders: Vec<IncludeBounds> = Vec::new();
for b in bounds.iter_mut() {
if b.start() <= start && b.end() >= start {
let remainder = b.end() - start - offset;
remainders.push(IncludeBounds {
path: b.path.clone(),
global_start: end,
child_start: start - b.start() + offset,
len: remainder,
method: b.include_method().clone(),
});
b.len = start - b.start();
}
}
bounds.extend_from_slice(&remainders);
bounds.sort();
}
pub fn file_line_from_global(&self,
global_buffer: &[u8],
offset: usize)
-> Result<(usize, usize), BoundsError> {
if offset >= self.global_start && offset < self.end() {
match self.method {
IncludeMethod::DTS => {
let b = match File::open(&self.path) {
Ok(f) => f,
Err(e) => return Err(BoundsError::IOError(e, Some(self.path.to_owned()))),
}
.bytes().filter_map(|e| e.ok());
byte_offset_to_line_col(b, offset - self.global_start + self.child_start)
.map_err(|e| e.into())
}
IncludeMethod::CPP => {
let (g_line, g_col) = byte_offset_to_line_col(global_buffer.iter(), offset)?;
let (s_line, s_col) = byte_offset_to_line_col(global_buffer.iter(),
self.global_start)?;
let b = match File::open(&self.path) {
Ok(f) => f,
Err(e) => return Err(BoundsError::IOError(e, Some(self.path.to_owned()))),
}
.bytes().filter_map(|e| e.ok());
let (c_line, c_col) = byte_offset_to_line_col(b, self.child_start)?;
let line = g_line - s_line + c_line;
let col = if g_line == s_line {
g_col - s_col - c_col + 2
} else {
g_col - c_col + 1
};
Ok((line, col))
}
}
} else {
Err(BoundsError::NotWithinBounds)
}
}
}
pub fn get_bounds_containing_offset(bounds: &[IncludeBounds],
offset: usize)
-> Result<&IncludeBounds, BoundsError> {
match bounds.binary_search_by(|b| {
use std::cmp::Ordering::*;
match (b.start().cmp(&offset), b.end().cmp(&offset)) {
(Less, Greater) | (Equal, Greater) => Equal,
(Greater, Greater) => Greater,
(Equal, Less) | (Less, Less) | (Less, Equal) | (Equal, Equal) => Less,
_ => unreachable!(),
}
}) {
Ok(off) => Ok(&bounds[off]),
Err(_) => Err(BoundsError::NotWithinBounds),
}
}
#[derive(Debug, PartialEq)]
struct Linemarker {
child_line: usize,
path: PathBuf,
flag: Option<LinemarkerFlag>,
}
#[derive(Debug, PartialEq)]
enum LinemarkerFlag {
Start,
Return,
System,
Extern,
}
named!(parse_linemarker<Linemarker>,
complete!(do_parse!(
tag!("#") >>
opt!(tag!("line")) >>
space >>
line: map_res!(map_res!(digit, str::from_utf8), usize::from_str) >>
space >>
path: delimited!(
char!('"'),
map!(escape_c_string, PathBuf::from),
char!('"')
) >>
flag: opt!(preceded!(space, map_res!(map_res!(digit, str::from_utf8), u64::from_str))) >>
line_ending >>
(Linemarker {
child_line: line,
path: path,
flag: flag.map(|f| match f {
1 => LinemarkerFlag::Start,
2 => LinemarkerFlag::Return,
3 => LinemarkerFlag::System,
4 => LinemarkerFlag::Extern,
_ => unreachable!(),
}),
})
))
);
fn find_linemarker_start(input: &[u8]) -> IResult<&[u8], &[u8]> {
if "# ".len() > input.len() {
IResult::Incomplete(Needed::Size("# ".len()))
} else {
match input.find_substring("# ").iter().chain(input.find_substring("#line ").iter()).min() {
None => {
IResult::Error(error_position!(ErrorKind::TakeUntil, input))
},
Some(index) => {
IResult::Done(&input[*index..], &input[0..*index])
},
}
}
}
named!(find_linemarker<(&[u8], Linemarker)>, do_parse!(
pre: find_linemarker_start >>
marker: parse_linemarker >>
(pre, marker)
));
fn parse_linemarkers(buf: &[u8],
bounds: &mut Vec<IncludeBounds>,
global_offset: usize,
post_len: usize)
-> Result<(), IncludeError> {
let end_offset = global_offset + buf.len();
let mut buf = buf;
while let IResult::Done(rem, (pre, marker)) = find_linemarker(buf) {
match bounds.last_mut() {
Some(ref mut bound) if bound.method == IncludeMethod::CPP => { bound.len = pre.len() }
Some(&mut IncludeBounds{ ref path, .. }) =>
return Err(IncludeError::LinemarkerInDtsi(path.to_owned())),
None => unreachable!(),
}
let new_bound = IncludeBounds {
path: marker.path.clone(),
global_start: end_offset - rem.len(),
child_start: match File::open(&marker.path) {
Ok(f) => line_to_byte_offset(f.bytes().filter_map(|e| e.ok()), marker.child_line)?,
Err(_) => 0,
},
len: rem.len() + post_len,
method: IncludeMethod::CPP,
};
bounds.push(new_bound);
buf = rem;
}
Ok(())
}
named!(parse_include<String>, complete!(preceded!(
tag!("/include/"),
preceded!( multispace,
delimited!(
char!('"'),
escape_c_string,
char!('"')
))
)));
fn find_include(buf: &[u8]) -> Option<(&[u8], PathBuf, &[u8])> {
for (index, win) in buf.windows("/include/".len()).enumerate() {
if win == b"/include/" {
match parse_include(&buf[index..]) {
IResult::Done(rem, file) => {
return Some((&buf[..index], PathBuf::from(file), rem))
}
IResult::Error(_) => {}
IResult::Incomplete(_) => unreachable!(),
}
}
}
None
}
pub fn include_files<P: AsRef<Path>, I: AsRef<Path>>(file: P, include_dirs: &[I]) -> Result<(Vec<u8>, Vec<IncludeBounds>), IncludeError> {
fn _include_files(path: &Path,
include_dirs: &[&Path],
main_offset: usize)
-> Result<(Vec<u8>, Vec<IncludeBounds>), IncludeError> {
fn find_file(path: &Path, include_dirs: &[&Path]) -> Result<PathBuf, IncludeError> {
for dir in include_dirs.iter() {
let p = dir.join(path);
if p.is_file() {
return Ok(p);
}
}
Err(IncludeError::IOError(io::Error::from(io::ErrorKind::NotFound), Some(path.to_owned())))
}
let path = find_file(path, include_dirs)?;
let mut file = match File::open(&path) {
Ok(f) => f,
Err(e) => return Err(IncludeError::IOError(e, Some(path.to_owned()))),
};
let mut buffer: Vec<u8> = Vec::new();
let mut bounds: Vec<IncludeBounds> = Vec::new();
let mut string_buffer = String::new();
file.read_to_string(&mut string_buffer)?;
let mut buf = string_buffer.as_bytes();
named!(first_linemarker<(&[u8], Linemarker)>,
do_parse!(
marker: peek!(parse_linemarker) >>
line: recognize!(parse_linemarker) >>
(line, marker)
)
);
let start_bound = if let IResult::Done(rem, (line, marker)) = first_linemarker(buf) {
let bound = IncludeBounds {
path: marker.path.clone(),
global_start: buf.len() - rem.len(),
child_start: {
let b = match File::open(&marker.path) {
Ok(f) => f,
Err(e) => return Err(IncludeError::IOError(e, Some(marker.path.to_owned()))),
}
.bytes().filter_map(|e| e.ok());
line_to_byte_offset(b, marker.child_line)?
},
len: match File::open(&marker.path) {
Ok(f) => f,
Err(e) => return Err(IncludeError::IOError(e, Some(marker.path.to_owned()))),
}
.bytes().count(),
method: IncludeMethod::CPP,
};
buffer.extend_from_slice(line);
buf = rem;
bound
} else {
IncludeBounds {
path: path.to_owned(),
global_start: main_offset,
child_start: 0,
len: match File::open(&path) {
Ok(f) => f,
Err(e) => return Err(IncludeError::IOError(e, Some(path.to_owned()))),
}
.bytes().count(),
method: IncludeMethod::DTS,
}
};
bounds.push(start_bound);
while let Some((pre, included_path, rem)) = find_include(&buf[..]) {
parse_linemarkers(pre, &mut bounds, buffer.len(), buf.len() - pre.len())?;
buffer.extend_from_slice(pre);
let offset = pre.len();
let total_len = buffer.len() + main_offset;
let (sub_buf, sub_bounds) = _include_files(&included_path, include_dirs, total_len)?;
buffer.extend(sub_buf);
let inc_start = sub_bounds.first()
.map(|b| b.global_start)
.ok_or_else(||
IncludeError::NoBoundReturned(included_path.clone()))?;
let inc_end = sub_bounds.last()
.map(|b| b.end())
.ok_or_else(||
IncludeError::NoBoundReturned(included_path.clone()))?;
let eaten_len = (buf.len() - offset) - rem.len();
IncludeBounds::split_bounds(&mut bounds, inc_start, inc_end, eaten_len);
bounds.extend_from_slice(&sub_bounds);
bounds.sort();
buf = rem;
}
parse_linemarkers(buf, &mut bounds, buffer.len(), 0)?;
buffer.extend(buf);
Ok((buffer, bounds))
}
_include_files(file.as_ref(),
&include_dirs.iter().map(|p| p.as_ref()).collect::<Vec<_>>(),
0)
}
#[cfg(test)]
mod tests {
use super::*;
use nom::IResult;
#[test]
fn linemarker_no_flag() {
let input = b"# 1 \"<built-in>\"\n";
assert_eq!(
parse_linemarker(input),
IResult::Done(
&b""[..],
Linemarker {
child_line: 1,
path: PathBuf::from("<built-in>"),
flag: None,
}
)
);
}
#[test]
fn linemarker_flag() {
let input = b"# 12 \"am33xx.dtsi\" 2\n";
assert_eq!(
parse_linemarker(input),
IResult::Done(
&b""[..],
Linemarker {
child_line: 12,
path: PathBuf::from("am33xx.dtsi"),
flag: Some(LinemarkerFlag::Return),
}
)
);
}
}