#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "https://doc.rust-lang.org/favicon.ico",
html_root_url = "https://doc.rust-lang.org/nightly/")]
#![deny(warnings)]
use std::cell::{Cell, RefCell};
use std::ops::{Add, Sub};
use std::rc::Rc;
use std::cmp;
use std::fmt;
#[macro_use]
extern crate serde_derive;
extern crate serde;
use serde::ser::{Serializer, SerializeSeq};
use serde::de::{self, Deserializer, Visitor, SeqAccess, Unexpected};
pub mod hygiene;
pub use hygiene::{SyntaxContext, ExpnInfo, ExpnFormat, NameAndSpan};
pub mod symbol;
pub type FileName = String;
#[derive(Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Span {
pub lo: BytePos,
pub hi: BytePos,
#[serde(skip)]
pub ctxt: SyntaxContext,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct MultiSpan {
primary_spans: Vec<Span>,
span_labels: Vec<(Span, String)>,
}
impl Span {
pub fn end_point(self) -> Span {
let lo = cmp::max(self.hi.0 - 1, self.lo.0);
Span { lo: BytePos(lo), ..self }
}
pub fn next_point(self) -> Span {
let lo = cmp::max(self.hi.0, self.lo.0 + 1);
Span { lo: BytePos(lo), hi: BytePos(lo), ..self }
}
pub fn substitute_dummy(self, other: Span) -> Span {
if self.source_equal(&DUMMY_SP) { other } else { self }
}
pub fn contains(self, other: Span) -> bool {
self.lo <= other.lo && other.hi <= self.hi
}
pub fn source_equal(&self, other: &Span) -> bool {
self.lo == other.lo && self.hi == other.hi
}
pub fn trim_start(self, other: Span) -> Option<Span> {
if self.hi > other.hi {
Some(Span { lo: cmp::max(self.lo, other.hi), .. self })
} else {
None
}
}
pub fn source_callsite(self) -> Span {
self.ctxt.outer().expn_info().map(|info| info.call_site.source_callsite()).unwrap_or(self)
}
pub fn source_callee(self) -> Option<NameAndSpan> {
fn source_callee(info: ExpnInfo) -> NameAndSpan {
match info.call_site.ctxt.outer().expn_info() {
Some(info) => source_callee(info),
None => info.callee,
}
}
self.ctxt.outer().expn_info().map(source_callee)
}
pub fn allows_unstable(&self) -> bool {
match self.ctxt.outer().expn_info() {
Some(info) => info.callee.allow_internal_unstable,
None => false,
}
}
pub fn macro_backtrace(mut self) -> Vec<MacroBacktrace> {
let mut prev_span = DUMMY_SP;
let mut result = vec![];
loop {
let info = match self.ctxt.outer().expn_info() {
Some(info) => info,
None => break,
};
let (pre, post) = match info.callee.format {
ExpnFormat::MacroAttribute(..) => ("#[", "]"),
ExpnFormat::MacroBang(..) => ("", "!"),
ExpnFormat::CompilerDesugaring(..) => ("desugaring of `", "`"),
};
let macro_decl_name = format!("{}{}{}", pre, info.callee.name(), post);
let def_site_span = info.callee.span;
if !info.call_site.source_equal(&prev_span) {
result.push(MacroBacktrace {
call_site: info.call_site,
macro_decl_name: macro_decl_name,
def_site_span: def_site_span,
});
}
prev_span = self;
self = info.call_site;
}
result
}
pub fn to(self, end: Span) -> Span {
if end.ctxt == SyntaxContext::empty() {
Span { lo: self.lo, ..end }
} else {
Span { hi: end.hi, ..self }
}
}
pub fn between(self, end: Span) -> Span {
Span {
lo: self.hi,
hi: end.lo,
ctxt: if end.ctxt == SyntaxContext::empty() {
end.ctxt
} else {
self.ctxt
}
}
}
pub fn until(self, end: Span) -> Span {
Span {
lo: self.lo,
hi: end.lo,
ctxt: if end.ctxt == SyntaxContext::empty() {
end.ctxt
} else {
self.ctxt
}
}
}
}
#[derive(Clone, Debug)]
pub struct SpanLabel {
pub span: Span,
pub is_primary: bool,
pub label: Option<String>,
}
fn default_span_debug(span: Span, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Span {{ lo: {:?}, hi: {:?}, ctxt: {:?} }}",
span.lo, span.hi, span.ctxt)
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
SPAN_DEBUG.with(|span_debug| span_debug.get()(*self, f))
}
}
pub const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0), ctxt: NO_EXPANSION };
impl MultiSpan {
pub fn new() -> MultiSpan {
MultiSpan {
primary_spans: vec![],
span_labels: vec![]
}
}
pub fn from_span(primary_span: Span) -> MultiSpan {
MultiSpan {
primary_spans: vec![primary_span],
span_labels: vec![]
}
}
pub fn from_spans(vec: Vec<Span>) -> MultiSpan {
MultiSpan {
primary_spans: vec,
span_labels: vec![]
}
}
pub fn push_span_label(&mut self, span: Span, label: String) {
self.span_labels.push((span, label));
}
pub fn primary_span(&self) -> Option<Span> {
self.primary_spans.first().cloned()
}
pub fn primary_spans(&self) -> &[Span] {
&self.primary_spans
}
pub fn replace(&mut self, before: Span, after: Span) -> bool {
let mut replacements_occurred = false;
for primary_span in &mut self.primary_spans {
if *primary_span == before {
*primary_span = after;
replacements_occurred = true;
}
}
for span_label in &mut self.span_labels {
if span_label.0 == before {
span_label.0 = after;
replacements_occurred = true;
}
}
replacements_occurred
}
pub fn span_labels(&self) -> Vec<SpanLabel> {
let is_primary = |span| self.primary_spans.contains(&span);
let mut span_labels = vec![];
for &(span, ref label) in &self.span_labels {
span_labels.push(SpanLabel {
span: span,
is_primary: is_primary(span),
label: Some(label.clone())
});
}
for &span in &self.primary_spans {
if !span_labels.iter().any(|sl| sl.span == span) {
span_labels.push(SpanLabel {
span: span,
is_primary: true,
label: None
});
}
}
span_labels
}
}
impl From<Span> for MultiSpan {
fn from(span: Span) -> MultiSpan {
MultiSpan::from_span(span)
}
}
pub const NO_EXPANSION: SyntaxContext = ::hygiene::NO_EXPANSION;
#[derive(Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct MultiByteChar {
pub pos: BytePos,
pub bytes: usize,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct FileMap {
pub name: FileName,
pub name_was_remapped: bool,
#[serde(skip, default = "invalid_crate")]
pub crate_of_origin: u32,
#[serde(skip)]
pub src: Option<Rc<String>>,
pub start_pos: BytePos,
pub end_pos: BytePos,
#[serde(serialize_with = "serialize_lines", deserialize_with = "deserialize_lines")]
pub lines: RefCell<Vec<BytePos>>,
pub multibyte_chars: RefCell<Vec<MultiByteChar>>,
}
fn invalid_crate() -> u32 {
::std::u32::MAX - 1
}
fn serialize_lines<S>(lines: &RefCell<Vec<BytePos>>, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
let lines = lines.borrow();
if lines.is_empty() {
serializer.serialize_seq(Some(0))?.end()
} else {
let mut seq = serializer.serialize_seq(Some(lines.len() + 1))?;
let max_line_length = if lines.len() == 1 {
0
} else {
lines.windows(2)
.map(|w| w[1] - w[0])
.map(|bp| bp.to_usize())
.max()
.unwrap()
};
let bytes_per_diff: u8 = match max_line_length {
0 ... 0xFF => 1,
0x100 ... 0xFFFF => 2,
_ => 4
};
seq.serialize_element(&bytes_per_diff)?;
seq.serialize_element(&lines[0])?;
let diff_iter = (&lines[..]).windows(2)
.map(|w| (w[1] - w[0]));
match bytes_per_diff {
1 => for diff in diff_iter { seq.serialize_element(&(diff.0 as u8))? },
2 => for diff in diff_iter { seq.serialize_element(&(diff.0 as u16))? },
4 => for diff in diff_iter { seq.serialize_element(&diff.0)? },
_ => unreachable!()
}
seq.end()
}
}
fn deserialize_lines<'de, D>(deserializer: D) -> Result<RefCell<Vec<BytePos>>, D::Error>
where D: Deserializer<'de>
{
struct LinesVisitor;
impl<'de> Visitor<'de> for LinesVisitor {
type Value = RefCell<Vec<BytePos>>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("compressed locations of lines beginnings in the source code")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where A: SeqAccess<'de>
{
let mut lines = Vec::with_capacity(seq.size_hint().unwrap_or(0));
if let Some(bytes_per_diff) = seq.next_element::<u8>()? {
let mut line_start: BytePos = seq.next_element()?
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
lines.push(line_start);
while let Some(diff) = match bytes_per_diff {
1 => seq.next_element::<u8>()?.map(|u| u as u32),
2 => seq.next_element::<u16>()?.map(|u| u as u32),
4 => seq.next_element::<u32>()?,
_ => {
return Err(de::Error::invalid_value(
Unexpected::Unsigned(bytes_per_diff as u64),
&"bytes per diff"));
}
} {
line_start = line_start + BytePos(diff);
lines.push(line_start);
}
}
Ok(RefCell::new(lines))
}
}
deserializer.deserialize_seq(LinesVisitor)
}
impl fmt::Debug for FileMap {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "FileMap({})", self.name)
}
}
impl FileMap {
pub fn next_line(&self, pos: BytePos) {
let mut lines = self.lines.borrow_mut();
let line_len = lines.len();
assert!(line_len == 0 || ((*lines)[line_len - 1] < pos));
lines.push(pos);
}
pub fn get_line(&self, line_number: usize) -> Option<&str> {
match self.src {
Some(ref src) => {
let lines = self.lines.borrow();
lines.get(line_number).map(|&line| {
let begin: BytePos = line - self.start_pos;
let begin = begin.to_usize();
let slice = &src[begin..];
match slice.find('\n') {
Some(e) => &slice[..e],
None => slice
}
})
}
None => None
}
}
pub fn record_multibyte_char(&self, pos: BytePos, bytes: usize) {
assert!(bytes >=2 && bytes <= 4);
let mbc = MultiByteChar {
pos: pos,
bytes: bytes,
};
self.multibyte_chars.borrow_mut().push(mbc);
}
pub fn is_real_file(&self) -> bool {
!(self.name.starts_with("<") &&
self.name.ends_with(">"))
}
pub fn is_imported(&self) -> bool {
self.src.is_none()
}
pub fn byte_length(&self) -> u32 {
self.end_pos.0 - self.start_pos.0
}
pub fn count_lines(&self) -> usize {
self.lines.borrow().len()
}
pub fn lookup_line(&self, pos: BytePos) -> Option<usize> {
let lines = self.lines.borrow();
if lines.len() == 0 {
return None;
}
let line_index = lookup_line(&lines[..], pos);
assert!(line_index < lines.len() as isize);
if line_index >= 0 {
Some(line_index as usize)
} else {
None
}
}
pub fn line_bounds(&self, line_index: usize) -> (BytePos, BytePos) {
if self.start_pos == self.end_pos {
return (self.start_pos, self.end_pos);
}
let lines = self.lines.borrow();
assert!(line_index < lines.len());
if line_index == (lines.len() - 1) {
(lines[line_index], self.end_pos)
} else {
(lines[line_index], lines[line_index + 1])
}
}
}
pub trait Pos {
fn from_usize(n: usize) -> Self;
fn to_usize(&self) -> usize;
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)]
pub struct BytePos(pub u32);
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct CharPos(pub usize);
impl Pos for BytePos {
fn from_usize(n: usize) -> BytePos { BytePos(n as u32) }
fn to_usize(&self) -> usize { let BytePos(n) = *self; n as usize }
}
impl Add for BytePos {
type Output = BytePos;
fn add(self, rhs: BytePos) -> BytePos {
BytePos((self.to_usize() + rhs.to_usize()) as u32)
}
}
impl Sub for BytePos {
type Output = BytePos;
fn sub(self, rhs: BytePos) -> BytePos {
BytePos((self.to_usize() - rhs.to_usize()) as u32)
}
}
impl Pos for CharPos {
fn from_usize(n: usize) -> CharPos { CharPos(n) }
fn to_usize(&self) -> usize { let CharPos(n) = *self; n }
}
impl Add for CharPos {
type Output = CharPos;
fn add(self, rhs: CharPos) -> CharPos {
CharPos(self.to_usize() + rhs.to_usize())
}
}
impl Sub for CharPos {
type Output = CharPos;
fn sub(self, rhs: CharPos) -> CharPos {
CharPos(self.to_usize() - rhs.to_usize())
}
}
#[derive(Debug, Clone)]
pub struct Loc {
pub file: Rc<FileMap>,
pub line: usize,
pub col: CharPos
}
#[derive(Debug)]
pub struct LocWithOpt {
pub filename: FileName,
pub line: usize,
pub col: CharPos,
pub file: Option<Rc<FileMap>>,
}
#[derive(Debug)]
pub struct FileMapAndLine { pub fm: Rc<FileMap>, pub line: usize }
#[derive(Debug)]
pub struct FileMapAndBytePos { pub fm: Rc<FileMap>, pub pos: BytePos }
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct LineInfo {
pub line_index: usize,
pub start_col: CharPos,
pub end_col: CharPos,
}
pub struct FileLines {
pub file: Rc<FileMap>,
pub lines: Vec<LineInfo>
}
thread_local!(pub static SPAN_DEBUG: Cell<fn(Span, &mut fmt::Formatter) -> fmt::Result> =
Cell::new(default_span_debug));
pub struct MacroBacktrace {
pub call_site: Span,
pub macro_decl_name: String,
pub def_site_span: Option<Span>,
}
pub type FileLinesResult = Result<FileLines, SpanLinesError>;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum SpanLinesError {
IllFormedSpan(Span),
DistinctSources(DistinctSources),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum SpanSnippetError {
IllFormedSpan(Span),
DistinctSources(DistinctSources),
MalformedForCodemap(MalformedCodemapPositions),
SourceNotAvailable { filename: String }
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct DistinctSources {
pub begin: (String, BytePos),
pub end: (String, BytePos)
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct MalformedCodemapPositions {
pub name: String,
pub source_len: usize,
pub begin_pos: BytePos,
pub end_pos: BytePos
}
fn lookup_line(lines: &[BytePos], pos: BytePos) -> isize {
match lines.binary_search(&pos) {
Ok(line) => line as isize,
Err(line) => line as isize - 1
}
}
#[cfg(test)]
mod tests {
use super::{lookup_line, BytePos};
#[test]
fn test_lookup_line() {
let lines = &[BytePos(3), BytePos(17), BytePos(28)];
assert_eq!(lookup_line(lines, BytePos(0)), -1);
assert_eq!(lookup_line(lines, BytePos(3)), 0);
assert_eq!(lookup_line(lines, BytePos(4)), 0);
assert_eq!(lookup_line(lines, BytePos(16)), 0);
assert_eq!(lookup_line(lines, BytePos(17)), 1);
assert_eq!(lookup_line(lines, BytePos(18)), 1);
assert_eq!(lookup_line(lines, BytePos(28)), 2);
assert_eq!(lookup_line(lines, BytePos(29)), 2);
}
}