use std::{
cmp,
fmt::{self, Display},
hash::{Hash, Hasher},
ops::{Add, Deref, DerefMut},
ptr,
sync::Arc,
};
use gazebo::prelude::*;
#[derive(
Copy, Clone, Dupe, Hash, Eq, PartialEq, Ord, PartialOrd, Debug, Default
)]
pub struct Pos(u32);
impl Pos {
pub const fn new(x: u32) -> Self {
Self(x)
}
}
impl Add<u32> for Pos {
type Output = Pos;
fn add(self, other: u32) -> Pos {
Pos(self.0 + other)
}
}
#[derive(Copy, Dupe, Clone, Hash, Eq, PartialEq, Debug, Default)]
pub(crate) 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
}
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),
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Copy)]
pub struct Spanned<T> {
pub node: T,
pub(crate) span: Span,
}
impl<T> Spanned<T> {
pub fn into_map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
Spanned {
node: f(self.node),
span: self.span,
}
}
pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Spanned<U> {
Spanned {
node: f(&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(crate) struct CodeMapId(*const ());
impl CodeMapId {
pub(crate) const EMPTY: CodeMapId = CodeMapId(ptr::null());
}
#[derive(Clone, Dupe)]
enum CodeMapImpl {
Real(Arc<CodeMapData>),
Native(&'static NativeCodeMap),
}
#[derive(Clone, Dupe)]
pub(crate) struct CodeMap(CodeMapImpl);
struct CodeMapData {
filename: String,
source: String,
lines: Vec<Pos>,
}
pub(crate) struct NativeCodeMap {
filename: &'static str,
start: LineCol,
}
impl NativeCodeMap {
const SOURCE: &'static str = "<native>";
pub(crate) const FULL_SPAN: Span = Span {
begin: Pos::new(0),
end: Pos::new(Self::SOURCE.len() as u32),
};
pub(crate) const fn new(filename: &'static str, line: u32, column: u32) -> NativeCodeMap {
Self {
filename,
start: LineCol {
line: line as usize,
column: column as usize,
},
}
}
pub(crate) 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(crate) 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(crate) 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(crate) fn full_span(&self) -> Span {
let source = self.source();
Span {
begin: Pos(0),
end: Pos(source.len() as u32),
}
}
pub(crate) 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(crate) 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) -> LineCol {
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();
LineCol { line, column }
}
CodeMapImpl::Native(data) => LineCol {
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(crate) fn source_span(&self, span: Span) -> &str {
&self.source()[(span.begin.0 as usize)..(span.end.0 as usize)]
}
pub(crate) fn line_span(&self, line: usize) -> Span {
match &self.0 {
CodeMapImpl::Real(data) => {
assert!(line < data.lines.len());
Span {
begin: data.lines[line],
end: *data.lines.get(line + 1).unwrap_or(&self.full_span().end),
}
}
CodeMapImpl::Native(data) => {
assert_eq!(line, data.start.line);
Span {
begin: Pos(0),
end: Pos(NativeCodeMap::SOURCE.len() as u32),
}
}
}
}
pub(crate) 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(crate) fn source_line_at_pos(&self, pos: Pos) -> &str {
self.source_line(self.find_line(pos))
}
}
#[derive(Copy, Clone, Dupe, Hash, Eq, PartialEq, Debug)]
struct LineCol {
pub line: usize,
pub column: usize,
}
#[derive(Clone, Copy, Dupe, Eq, PartialEq, Debug)]
pub struct FileSpanRef<'a> {
pub(crate) file: &'a CodeMap,
pub(crate) span: Span,
}
#[derive(Clone, Dupe, Eq, PartialEq, Debug, Hash)]
pub struct FileSpan {
pub(crate) file: CodeMap,
pub(crate) 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)
}
}
impl FileSpan {
pub fn filename(&self) -> &str {
self.file.filename()
}
pub fn source_span(&self) -> &str {
self.file.source_span(self.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, Default)]
pub struct ResolvedSpan {
pub begin_line: usize,
pub begin_column: usize,
pub end_line: usize,
pub end_column: usize,
}
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_line + 1,
self.begin_column + 1,
self.end_column + 1
)
} else {
write!(
f,
"{}:{}-{}:{}",
self.begin_line + 1,
self.begin_column + 1,
self.end_line + 1,
self.end_column + 1
)
}
}
}
impl ResolvedSpan {
fn from_span(begin: LineCol, end: LineCol) -> Self {
Self {
begin_line: begin.line,
begin_column: begin.column,
end_line: end.line,
end_column: end.column,
}
}
}
#[derive(Debug)]
pub struct ResolvedFileSpan {
pub file: String,
pub span: ResolvedSpan,
}
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), LineCol { line: 0, column: 0 });
assert_eq!(
codemap.find_line_col(start + 4),
LineCol { line: 0, column: 4 }
);
assert_eq!(
codemap.find_line_col(start + 5),
LineCol { line: 1, column: 0 }
);
assert_eq!(
codemap.find_line_col(start + 16),
LineCol { 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),
LineCol { line, column: 0 }
);
assert_eq!(
codemap.find_line_col(end),
LineCol {
line,
column: line_span.len() as usize - 1
}
);
}
}
#[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),
LineCol {
line: 0,
column: 15
}
);
assert_eq!(
codemap.find_line_col(codemap.full_span().begin + 28),
LineCol {
line: 0,
column: 18
}
);
assert_eq!(
codemap.find_line_col(codemap.full_span().begin + 33),
LineCol { line: 1, column: 1 }
);
}
#[test]
fn test_line_col_span_display_point() {
let line_col = LineCol { 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 = LineCol { line: 0, column: 0 };
let end = LineCol {
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 = LineCol { line: 0, column: 0 };
let end = LineCol {
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_line: 100,
begin_column: 200,
end_line: 100,
end_column: 200 + NativeCodeMap::SOURCE.len(),
},
CODEMAP.resolve_span(CODEMAP.full_span())
);
}
}