extern crate cpp_syn as syn;
extern crate memchr;
use std::fmt::{self, Write};
use std::error;
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::{self, Error, ErrorKind};
use std::path::{Path, PathBuf};
use syn::{Attribute, Crate, Ident, Item, ItemKind, Lit, LitKind, MetaItem, Span};
use syn::fold::{self, Folder};
use std::mem;
const FILE_PADDING_BYTES: usize = 1;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct LocInfo<'a> {
pub path: &'a Path,
pub line: usize,
pub col: usize,
}
impl<'a> fmt::Display for LocInfo<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}:{}", self.path.display(), self.line, self.col)
}
}
#[derive(Debug)]
struct FileInfo {
span: Span,
path: PathBuf,
src: String,
lines: Vec<usize>,
}
fn offset_line_col(lines: &Vec<usize>, off: usize) -> (usize, usize) {
match lines.binary_search(&off) {
Ok(found) => (found + 1, 0),
Err(idx) => (idx, off - lines[idx - 1]),
}
}
fn lines_offsets(s: &[u8]) -> Vec<usize> {
let mut lines = vec![0];
let mut prev = 0;
while let Some(len) = memchr::memchr(b'\n', &s[prev..]) {
prev += len + 1;
lines.push(prev);
}
lines
}
#[derive(Debug)]
pub struct SourceMap {
files: Vec<FileInfo>,
offset: usize,
}
impl SourceMap {
pub fn new() -> SourceMap {
SourceMap {
files: Vec::new(),
offset: 0,
}
}
pub fn add_crate_root<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Crate> {
self.parse_canonical_file(fs::canonicalize(path)?)
}
fn parse_canonical_file(&mut self, path: PathBuf) -> io::Result<Crate> {
let mut source = String::new();
File::open(&path)?.read_to_string(&mut source)?;
let krate = syn::parse_crate(&source).map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
let parent = path.parent()
.ok_or(Error::new(
ErrorKind::InvalidInput,
"cannot parse file without parent directory",
))?
.to_path_buf();
let offset = self.offset;
self.offset += source.len() + FILE_PADDING_BYTES;
self.files.push(FileInfo {
span: Span {
lo: offset,
hi: offset + source.len(),
},
path: path,
lines: lines_offsets(source.as_bytes()),
src: source,
});
let idx = self.files.len() - 1;
let mut walker = Walker {
idx: idx,
error: None,
sm: self,
parent: parent,
};
let krate = walker.fold_crate(krate);
if let Some(err) = walker.error {
return Err(err);
}
Ok(krate)
}
fn local_fileinfo(&self, mut span: Span) -> io::Result<(&FileInfo, Span)> {
if span.lo > span.hi {
return Err(Error::new(
ErrorKind::InvalidInput,
"Invalid span object with negative length",
));
}
for fi in &self.files {
if span.lo >= fi.span.lo && span.lo <= fi.span.hi && span.hi >= fi.span.lo
&& span.hi <= fi.span.hi
{
span.lo -= fi.span.lo;
span.hi -= fi.span.lo;
return Ok((fi, span));
}
}
Err(Error::new(
ErrorKind::InvalidInput,
"Span is not part of any input file",
))
}
pub fn filename(&self, span: Span) -> io::Result<&Path> {
Ok(&self.local_fileinfo(span)?.0.path)
}
pub fn source_text(&self, span: Span) -> io::Result<&str> {
let (fi, span) = self.local_fileinfo(span)?;
Ok(&fi.src[span.lo..span.hi])
}
pub fn locinfo(&self, span: Span) -> io::Result<LocInfo> {
let (fi, span) = self.local_fileinfo(span)?;
let (line, col) = offset_line_col(&fi.lines, span.lo);
Ok(LocInfo {
path: &fi.path,
line: line,
col: col,
})
}
}
struct Walker<'a> {
idx: usize,
error: Option<Error>,
sm: &'a mut SourceMap,
parent: PathBuf,
}
impl<'a> Walker<'a> {
fn read_submodule(&mut self, path: PathBuf) -> io::Result<Crate> {
let faux_crate = self.sm.parse_canonical_file(path)?;
if faux_crate.shebang.is_some() {
return Err(Error::new(
ErrorKind::InvalidData,
"Only the root file of a crate may contain shebangs",
));
}
Ok(faux_crate)
}
fn get_attrs_items(&mut self, attrs: &[Attribute], ident: &Ident) -> io::Result<Crate> {
for attr in attrs {
match attr.value {
MetaItem::NameValue(
ref id,
Lit {
node: LitKind::Str(ref s, _),
..
},
) => if id.as_ref() == "path" {
let explicit = self.parent.join(&s[..]);
return self.read_submodule(explicit);
},
_ => {}
}
}
let mut subdir = self.parent.join(ident.as_ref());
subdir.push("mod.rs");
if subdir.is_file() {
return self.read_submodule(subdir);
}
let adjacent = self.parent.join(&format!("{}.rs", ident));
if adjacent.is_file() {
return self.read_submodule(adjacent);
}
Err(Error::new(
ErrorKind::NotFound,
format!("No file with module definition for `mod {}`", ident),
))
}
}
impl<'a> Folder for Walker<'a> {
fn fold_item(&mut self, mut item: Item) -> Item {
if self.error.is_some() {
return item; }
match item.node {
ItemKind::Mod(None) => {
let (attrs, items) = match self.get_attrs_items(&item.attrs, &item.ident) {
Ok(Crate { attrs, items, .. }) => (attrs, items),
Err(e) => {
let span = self.fold_span(item.span);
let loc = match self.sm.locinfo(span) {
Ok(li) => li.to_string(),
Err(_) => "unknown location".to_owned(),
};
let e = Error::new(
ErrorKind::Other,
ModParseErr {
err: e,
msg: format!(
"Error while parsing `mod {}` \
statement at {}",
item.ident,
loc
),
},
);
self.error = Some(e);
return item;
}
};
item.attrs.extend_from_slice(&attrs);
item.node = ItemKind::Mod(Some(items));
item
}
ItemKind::Mod(Some(items)) => {
let mut parent = self.parent.join(item.ident.as_ref());
mem::swap(&mut self.parent, &mut parent);
let items = items.into_iter().map(|item| self.fold_item(item)).collect();
mem::swap(&mut self.parent, &mut parent);
item.node = ItemKind::Mod(Some(items));
item
}
_ => fold::noop_fold_item(self, item),
}
}
fn fold_span(&mut self, span: Span) -> Span {
let offset = self.sm.files[self.idx].span.lo;
Span {
lo: span.lo + offset,
hi: span.hi + offset,
}
}
}
#[derive(Debug)]
struct ModParseErr {
err: Error,
msg: String,
}
impl error::Error for ModParseErr {
fn description(&self) -> &str {
&self.msg
}
fn cause(&self) -> Option<&error::Error> {
Some(&self.err)
}
}
impl fmt::Display for ModParseErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.msg, f)?;
f.write_char('\n')?;
fmt::Display::fmt(&self.err, f)
}
}