use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct SourceMap {
source: Vec<char>,
line_indices: Vec<usize>,
path: PathBuf,
}
impl SourceMap {
pub fn new(source: String, path: PathBuf) -> Self {
let (chars, line_indices) = Self::load_source(source);
Self {
path,
source: chars,
line_indices,
}
}
pub fn from_source(source: String) -> Self {
let (chars, line_indices) = Self::load_source(source);
Self {
path: PathBuf::new(),
source: chars,
line_indices,
}
}
pub fn from_path(path: &Path) -> std::io::Result<Self> {
let source = std::fs::read_to_string(path)?;
let (chars, line_indices) = Self::load_source(source);
Ok(Self {
path: path.to_path_buf(),
line_indices,
source: chars,
})
}
fn load_source(source: String) -> (Vec<char>, Vec<usize>) {
let chars: Vec<char> = source.chars().collect();
let mut line_indices = Vec::new();
let mut in_string = false;
let mut i = 0;
while i < chars.len() {
match chars[i] {
'"' if !in_string => {
in_string = true;
}
'"' if in_string => {
in_string = false;
}
'\\' if in_string => {
i += 1; if i < chars.len() {
i += 1; continue;
}
}
'\n' if !in_string => {
line_indices.push(i);
}
_ => {}
}
i += 1;
}
(chars, line_indices)
}
pub fn get_path(&self) -> &Path {
self.path.as_path()
}
pub fn get_source(&self) -> &Vec<char> {
&self.source
}
pub fn get_line(&self, line_number: u32) -> &[char] {
if line_number == 0 {
return &[];
}
let line_index = (line_number - 1) as usize;
let start = if line_number == 1 {
0
} else {
if let Some(&prev_newline_pos) = self.line_indices.get(line_index - 1) {
prev_newline_pos + 1
} else {
return &[];
}
};
let end = if let Some(&newline_pos) = self.line_indices.get(line_index) {
newline_pos } else if line_index == self.line_indices.len() {
self.source.len()
} else {
return &[];
};
if start <= end && end <= self.source.len() {
&self.source[start..end]
} else {
&[]
}
}
pub fn get_line_number(&self, position: usize) -> u32 {
if position >= self.source.len() {
return (self.line_indices.len() + 1) as u32;
}
match self.line_indices.binary_search(&position) {
Ok(index) => (index + 1) as u32, Err(index) => (index + 1) as u32, }
}
pub fn get_column_number(&self, position: usize) -> u32 {
if position >= self.source.len() {
return 1;
}
let line_number = self.get_line_number(position);
let line_start = if line_number == 1 {
0
} else {
let line_index = (line_number - 2) as usize;
if let Some(&newline_pos) = self.line_indices.get(line_index) {
newline_pos + 1
} else {
0
}
};
(position - line_start + 1) as u32
}
}