use std::cmp;
use std::fmt;
use std::fmt::Display;
use std::hash::Hash;
use std::hash::Hasher;
use std::ops::Add;
use std::ops::AddAssign;
use std::ops::Deref;
use std::ops::DerefMut;
use std::ptr;
use std::sync::Arc;
use allocative::Allocative;
use dupe::Dupe;
use once_cell::sync::Lazy;
#[derive(
Copy, Clone, Dupe, Hash, Eq, PartialEq, PartialOrd, Ord, Debug, Default, Allocative
)]
pub struct Pos(u32);
impl Pos {
pub const fn new(x: u32) -> Self {
Self(x)
}
pub const fn get(self) -> u32 {
self.0
}
}
impl Add<u32> for Pos {
type Output = Pos;
fn add(self, other: u32) -> Pos {
Pos(self.0 + other)
}
}
impl AddAssign<u32> for Pos {
fn add_assign(&mut self, other: u32) {
self.0 += other;
}
}
#[derive(Copy, Dupe, Clone, Hash, Eq, PartialEq, Debug, Default, Allocative)]
pub struct Span {
begin: Pos,
end: Pos,
}
impl Span {
pub fn new(begin: Pos, end: Pos) -> Self {
assert!(begin <= end);
Span { begin, end }
}
pub fn begin(self) -> Pos {
self.begin
}
pub fn end(self) -> Pos {
self.end
}
#[cfg(test)]
pub fn len(self) -> u32 {
self.end.0 - self.begin.0
}
pub fn merge(self, other: Span) -> Span {
Span {
begin: cmp::min(self.begin, other.begin),
end: cmp::max(self.end, other.end),
}
}
pub fn merge_all(spans: impl Iterator<Item = Span>) -> Span {
spans.reduce(Span::merge).unwrap_or_default()
}
pub fn end_span(self) -> Span {
Span {
begin: self.end,
end: self.end,
}
}
pub fn contains(self, pos: Pos) -> bool {
self.begin <= pos && pos <= self.end
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Copy)]
pub struct Spanned<T> {
pub node: T,
pub span: Span,
}
impl<T> Spanned<T> {
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
Spanned {
node: f(self.node),
span: self.span,
}
}
pub fn as_ref(&self) -> Spanned<&T> {
Spanned {
node: &self.node,
span: self.span,
}
}
}
impl<T> Deref for Spanned<T> {
type Target = T;
fn deref(&self) -> &T {
&self.node
}
}
impl<T> DerefMut for Spanned<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.node
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Dupe)]
pub struct CodeMapId(*const ());
impl CodeMapId {
pub const EMPTY: CodeMapId = CodeMapId(ptr::null());
}
#[derive(Clone, Dupe, Allocative)]
enum CodeMapImpl {
Real(Arc<CodeMapData>),
#[allocative(skip)]
Native(&'static NativeCodeMap),
}
#[derive(Clone, Dupe, Allocative)]
pub struct CodeMap(CodeMapImpl);
#[derive(Allocative)]
struct CodeMapData {
filename: String,
source: String,
lines: Vec<Pos>,
}
pub struct NativeCodeMap {
filename: &'static str,
start: ResolvedPos,
}
impl NativeCodeMap {
const SOURCE: &'static str = "<native>";
pub const FULL_SPAN: Span = Span {
begin: Pos::new(0),
end: Pos::new(Self::SOURCE.len() as u32),
};
pub const fn new(filename: &'static str, line: u32, column: u32) -> NativeCodeMap {
Self {
filename,
start: ResolvedPos {
line: line as usize,
column: column as usize,
},
}
}
pub const fn to_codemap(&'static self) -> CodeMap {
CodeMap(CodeMapImpl::Native(self))
}
}
impl Default for CodeMap {
fn default() -> Self {
Self::new("".to_owned(), "".to_owned())
}
}
impl fmt::Debug for CodeMap {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "CodeMap({:?})", self.filename())
}
}
impl PartialEq for CodeMap {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl Eq for CodeMap {}
impl Hash for CodeMap {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id().hash(state)
}
}
impl CodeMap {
pub fn new(filename: String, source: String) -> CodeMap {
let mut lines = vec![Pos(0)];
lines.extend(source.match_indices('\n').map(|(p, _)| Pos(p as u32 + 1)));
CodeMap(CodeMapImpl::Real(Arc::new(CodeMapData {
filename,
source,
lines,
})))
}
pub fn empty_static() -> &'static CodeMap {
static EMPTY_CODEMAP: Lazy<CodeMap> = Lazy::new(CodeMap::default);
&EMPTY_CODEMAP
}
pub fn id(&self) -> CodeMapId {
match &self.0 {
CodeMapImpl::Real(data) => CodeMapId(Arc::as_ptr(data) as *const ()),
CodeMapImpl::Native(data) => CodeMapId(*data as *const NativeCodeMap as *const ()),
}
}
pub fn full_span(&self) -> Span {
let source = self.source();
Span {
begin: Pos(0),
end: Pos(source.len() as u32),
}
}
pub fn file_span(&self, span: Span) -> FileSpan {
FileSpan {
file: self.dupe(),
span,
}
}
pub fn filename(&self) -> &str {
match &self.0 {
CodeMapImpl::Real(data) => &data.filename,
CodeMapImpl::Native(data) => data.filename,
}
}
pub fn byte_at(&self, pos: Pos) -> u8 {
self.source().as_bytes()[pos.0 as usize]
}
pub fn find_line(&self, pos: Pos) -> usize {
assert!(pos <= self.full_span().end());
match &self.0 {
CodeMapImpl::Real(data) => match data.lines.binary_search(&pos) {
Ok(i) => i,
Err(i) => i - 1,
},
CodeMapImpl::Native(data) => data.start.line,
}
}
fn find_line_col(&self, pos: Pos) -> ResolvedPos {
assert!(pos <= self.full_span().end());
match &self.0 {
CodeMapImpl::Real(_) => {
let line = self.find_line(pos);
let line_span = self.line_span(line);
let byte_col = pos.0 - line_span.begin.0;
let column = self.source_span(line_span)[..byte_col as usize]
.chars()
.count();
ResolvedPos { line, column }
}
CodeMapImpl::Native(data) => ResolvedPos {
line: data.start.line,
column: data.start.column + pos.0 as usize,
},
}
}
pub fn source(&self) -> &str {
match &self.0 {
CodeMapImpl::Real(data) => &data.source,
CodeMapImpl::Native(_) => NativeCodeMap::SOURCE,
}
}
pub fn source_span(&self, span: Span) -> &str {
&self.source()[(span.begin.0 as usize)..(span.end.0 as usize)]
}
pub fn line_span(&self, line: usize) -> Span {
self.line_span_opt(line)
.unwrap_or_else(|| panic!("Line {} is out of range for {:?}", line, self))
}
pub fn line_span_opt(&self, line: usize) -> Option<Span> {
match &self.0 {
CodeMapImpl::Real(data) if line < data.lines.len() => Some(Span {
begin: data.lines[line],
end: *data.lines.get(line + 1).unwrap_or(&self.full_span().end),
}),
CodeMapImpl::Native(data) if line == data.start.line => Some(Span {
begin: Pos(0),
end: Pos(NativeCodeMap::SOURCE.len() as u32),
}),
_ => None,
}
}
pub fn resolve_span(&self, span: Span) -> ResolvedSpan {
let begin = self.find_line_col(span.begin);
let end = self.find_line_col(span.end);
ResolvedSpan::from_span(begin, end)
}
pub fn source_line(&self, line: usize) -> &str {
self.source_span(self.line_span(line))
.trim_end_matches(&['\n', '\r'][..])
}
pub fn source_line_at_pos(&self, pos: Pos) -> &str {
self.source_line(self.find_line(pos))
}
}
#[derive(Copy, Clone, Dupe, Hash, Eq, PartialEq, Debug, Default)]
pub struct ResolvedPos {
pub line: usize,
pub column: usize,
}
impl Display for ResolvedPos {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line + 1, self.column + 1)
}
}
impl ResolvedPos {
fn _testing_parse(line_col: &str) -> ResolvedPos {
let (line, col) = line_col.split_once(':').unwrap();
ResolvedPos {
line: line.parse::<usize>().unwrap().checked_sub(1).unwrap(),
column: col.parse::<usize>().unwrap().checked_sub(1).unwrap(),
}
}
}
#[derive(Clone, Copy, Dupe, Eq, PartialEq, Debug)]
pub struct FileSpanRef<'a> {
pub file: &'a CodeMap,
pub span: Span,
}
#[derive(Clone, Dupe, Eq, PartialEq, Debug, Hash, Allocative)]
pub struct FileSpan {
pub file: CodeMap,
pub span: Span,
}
impl<'a> fmt::Display for FileSpanRef<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}:{}", self.file.filename(), self.resolve_span())
}
}
impl fmt::Display for FileSpan {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Display::fmt(&self.as_ref(), f)
}
}
impl<'a> FileSpanRef<'a> {
pub fn filename(&self) -> &str {
self.file.filename()
}
pub fn to_file_span(self) -> FileSpan {
FileSpan {
file: self.file.dupe(),
span: self.span,
}
}
pub fn resolve_span(&self) -> ResolvedSpan {
self.file.resolve_span(self.span)
}
pub fn source_span(self) -> &'a str {
self.file.source_span(self.span)
}
}
impl FileSpan {
pub fn new(filename: String, source: String) -> Self {
let file = CodeMap::new(filename, source);
let span = file.full_span();
Self { file, span }
}
pub fn filename(&self) -> &str {
self.file.filename()
}
pub fn source_span(&self) -> &str {
self.as_ref().source_span()
}
pub fn as_ref(&self) -> FileSpanRef {
FileSpanRef {
file: &self.file,
span: self.span,
}
}
pub fn resolve_span(&self) -> ResolvedSpan {
self.as_ref().resolve_span()
}
pub fn resolve(&self) -> ResolvedFileSpan {
ResolvedFileSpan {
file: self.file.filename().to_owned(),
span: self.file.resolve_span(self.span),
}
}
}
#[derive(Debug, Dupe, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ResolvedSpan {
pub begin: ResolvedPos,
pub end: ResolvedPos,
}
impl Display for ResolvedSpan {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let single_line = self.begin.line == self.end.line;
let is_empty = single_line && self.begin.column == self.end.column;
if is_empty {
write!(f, "{}:{}", self.begin.line + 1, self.begin.column + 1)
} else if single_line {
write!(f, "{}-{}", self.begin, self.end.column + 1)
} else {
write!(f, "{}-{}", self.begin, self.end,)
}
}
}
impl From<ResolvedSpan> for lsp_types::Range {
fn from(span: ResolvedSpan) -> Self {
lsp_types::Range::new(
lsp_types::Position::new(span.begin.line as u32, span.begin.column as u32),
lsp_types::Position::new(span.end.line as u32, span.end.column as u32),
)
}
}
impl ResolvedSpan {
pub fn contains(&self, pos: ResolvedPos) -> bool {
(self.begin.line < pos.line
|| (self.begin.line == pos.line && self.begin.column <= pos.column))
&& (self.end.line > pos.line
|| (self.end.line == pos.line && self.end.column >= pos.column))
}
fn from_span(begin: ResolvedPos, end: ResolvedPos) -> Self {
ResolvedSpan { begin, end }
}
fn _testing_parse(span: &str) -> ResolvedSpan {
match span.split_once('-') {
None => {
let line_col = ResolvedPos::_testing_parse(span);
ResolvedSpan::from_span(line_col, line_col)
}
Some((begin, end)) => {
let begin = ResolvedPos::_testing_parse(begin);
if end.contains(':') {
let end = ResolvedPos::_testing_parse(end);
ResolvedSpan::from_span(begin, end)
} else {
let end_col = end.parse::<usize>().unwrap().checked_sub(1).unwrap();
ResolvedSpan::from_span(
begin,
ResolvedPos {
line: begin.line,
column: end_col,
},
)
}
}
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display)]
#[display(fmt = "{}:{}", file, "line + 1")]
pub struct ResolvedFileLine {
pub file: String,
pub line: usize,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct ResolvedFileSpan {
pub file: String,
pub span: ResolvedSpan,
}
impl ResolvedFileSpan {
pub(crate) fn _testing_parse(span: &str) -> ResolvedFileSpan {
let (file, span) = span.split_once(':').unwrap();
ResolvedFileSpan {
file: file.to_owned(),
span: ResolvedSpan::_testing_parse(span),
}
}
pub fn begin_file_line(&self) -> ResolvedFileLine {
ResolvedFileLine {
file: self.file.clone(),
line: self.span.begin.line,
}
}
}
impl Display for ResolvedFileSpan {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.file, self.span)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_codemap() {
let source = "abcd\nefghij\nqwerty";
let codemap = CodeMap::new("test1.rs".to_owned(), source.to_owned());
let start = codemap.full_span().begin;
assert_eq!(codemap.filename(), "test1.rs");
assert_eq!(
codemap.find_line_col(start),
ResolvedPos { line: 0, column: 0 }
);
assert_eq!(
codemap.find_line_col(start + 4),
ResolvedPos { line: 0, column: 4 }
);
assert_eq!(
codemap.find_line_col(start + 5),
ResolvedPos { line: 1, column: 0 }
);
assert_eq!(
codemap.find_line_col(start + 16),
ResolvedPos { line: 2, column: 4 }
);
assert_eq!(codemap.source(), source);
assert!(
matches!(&codemap, CodeMap(CodeMapImpl::Real(codemap)) if codemap.lines.len() == 3)
);
for line in 0..3 {
let line_str = codemap.source_line(line);
let line_span = codemap.line_span(line);
assert_eq!(
line_str.len() + if line < 2 { 1 } else { 0 },
line_span.len() as usize
);
assert_eq!(line_str, source.lines().nth(line).unwrap());
assert_eq!(codemap.find_line(line_span.begin), line);
let end = Pos(line_span.end().0 - 1);
assert_eq!(codemap.find_line(end), line);
assert_eq!(
codemap.find_line_col(line_span.begin),
ResolvedPos { line, column: 0 }
);
assert_eq!(
codemap.find_line_col(end),
ResolvedPos {
line,
column: line_span.len() as usize - 1
}
);
}
assert_eq!(codemap.line_span_opt(4), None);
}
#[test]
fn test_multibyte() {
let content = "65°00′N 18°00′W 汉语\n🔬";
let codemap = CodeMap::new("<test>".to_owned(), content.to_owned());
assert_eq!(
codemap.find_line_col(codemap.full_span().begin + 21),
ResolvedPos {
line: 0,
column: 15
}
);
assert_eq!(
codemap.find_line_col(codemap.full_span().begin + 28),
ResolvedPos {
line: 0,
column: 18
}
);
assert_eq!(
codemap.find_line_col(codemap.full_span().begin + 33),
ResolvedPos { line: 1, column: 1 }
);
}
#[test]
fn test_line_col_span_display_point() {
let line_col = ResolvedPos { line: 0, column: 0 };
let span = ResolvedSpan::from_span(line_col, line_col);
assert_eq!(span.to_string(), "1:1");
}
#[test]
fn test_line_col_span_display_single_line_span() {
let begin = ResolvedPos { line: 0, column: 0 };
let end = ResolvedPos {
line: 0,
column: 32,
};
let span = ResolvedSpan::from_span(begin, end);
assert_eq!(span.to_string(), "1:1-33");
}
#[test]
fn test_line_col_span_display_multi_line_span() {
let begin = ResolvedPos { line: 0, column: 0 };
let end = ResolvedPos {
line: 2,
column: 32,
};
let span = ResolvedSpan::from_span(begin, end);
assert_eq!(span.to_string(), "1:1-3:33");
}
#[test]
fn test_native_code_map() {
static NATIVE_CODEMAP: NativeCodeMap = NativeCodeMap::new("test.rs", 100, 200);
static CODEMAP: CodeMap = NATIVE_CODEMAP.to_codemap();
assert_eq!(NativeCodeMap::SOURCE, CODEMAP.source());
assert_eq!(NativeCodeMap::SOURCE, CODEMAP.source_line(100));
assert_eq!(
ResolvedSpan {
begin: ResolvedPos {
line: 100,
column: 200,
},
end: ResolvedPos {
line: 100,
column: 200 + NativeCodeMap::SOURCE.len(),
}
},
CODEMAP.resolve_span(CODEMAP.full_span())
);
}
#[test]
fn test_resolved_span_contains() {
let span = ResolvedSpan {
begin: ResolvedPos { line: 2, column: 3 },
end: ResolvedPos { line: 4, column: 5 },
};
assert!(!span.contains(ResolvedPos { line: 0, column: 7 }));
assert!(!span.contains(ResolvedPos { line: 2, column: 2 }));
assert!(span.contains(ResolvedPos { line: 2, column: 3 }));
assert!(span.contains(ResolvedPos { line: 2, column: 9 }));
assert!(span.contains(ResolvedPos { line: 3, column: 1 }));
assert!(span.contains(ResolvedPos { line: 4, column: 4 }));
assert!(span.contains(ResolvedPos { line: 4, column: 5 }));
assert!(!span.contains(ResolvedPos { line: 4, column: 6 }));
assert!(!span.contains(ResolvedPos { line: 5, column: 0 }));
}
#[test]
fn test_resolved_file_span_to_begin_resolved_file_line() {
let span = ResolvedFileSpan {
file: "test.rs".to_owned(),
span: ResolvedSpan {
begin: ResolvedPos { line: 2, column: 3 },
end: ResolvedPos { line: 4, column: 5 },
},
};
assert_eq!("test.rs:3", span.begin_file_line().to_string());
}
}