use serde::{
Serialize,
ser::{SerializeSeq, Serializer},
};
#[derive(Debug, Clone, Default, PartialEq)]
pub(crate) struct LeveloffsetRange {
pub(crate) start_offset: usize,
pub(crate) end_offset: usize,
pub(crate) value: isize,
}
impl LeveloffsetRange {
#[must_use]
pub(crate) fn new(start_offset: usize, end_offset: usize, value: isize) -> Self {
Self {
start_offset,
end_offset,
value,
}
}
#[must_use]
pub(crate) fn contains(&self, byte_offset: usize) -> bool {
byte_offset >= self.start_offset && byte_offset < self.end_offset
}
}
#[must_use]
pub(crate) fn calculate_leveloffset_at(ranges: &[LeveloffsetRange], byte_offset: usize) -> isize {
ranges
.iter()
.filter_map(|r| {
if r.contains(byte_offset) {
Some(r.value)
} else {
None
}
})
.sum()
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SourceRange {
pub(crate) start_offset: usize,
pub(crate) end_offset: usize,
pub(crate) file: std::path::PathBuf,
pub(crate) start_line: usize,
}
impl SourceRange {
#[must_use]
pub(crate) fn contains(&self, byte_offset: usize) -> bool {
byte_offset >= self.start_offset && byte_offset < self.end_offset
}
}
pub(crate) trait Locateable {
fn location(&self) -> &Location;
}
#[derive(Debug, Default, Clone, Hash, Eq, PartialEq)]
#[non_exhaustive]
pub struct Location {
pub absolute_start: usize,
pub absolute_end: usize,
pub start: Position,
pub end: Position,
}
impl Location {
pub fn validate(&self, input: &str) -> Result<(), String> {
if self.absolute_start > self.absolute_end {
return Err(format!(
"Invalid range: start {} > end {}",
self.absolute_start, self.absolute_end
));
}
if self.absolute_end > input.len() {
return Err(format!(
"End offset {} exceeds input length {}",
self.absolute_end,
input.len()
));
}
if !input.is_char_boundary(self.absolute_start) {
return Err(format!(
"Start offset {} not on UTF-8 boundary",
self.absolute_start
));
}
if !input.is_char_boundary(self.absolute_end) {
return Err(format!(
"End offset {} not on UTF-8 boundary",
self.absolute_end
));
}
Ok(())
}
pub fn shift(&mut self, parent: Option<&Location>) {
if let Some(parent) = parent {
if parent.start.line == 0 {
return;
}
self.absolute_start += parent.absolute_start;
self.absolute_end += parent.absolute_start;
self.start.line += parent.start.line;
self.end.line += parent.start.line;
}
}
pub fn shift_inline(&mut self, parent: Option<&Location>) {
if let Some(parent) = parent {
if parent.start.line != 0 || parent.start.column != 0 {
self.absolute_start += parent.absolute_start;
self.absolute_end += parent.absolute_start;
}
if parent.start.line != 0 {
self.start.line += parent.start.line - 1;
self.end.line += parent.start.line - 1;
}
if parent.start.column != 0 {
self.start.column += parent.start.column - 1;
self.end.column += parent.start.column - 1;
}
}
}
pub fn shift_line_column(&mut self, line: usize, column: usize) {
self.start.line += line - 1;
self.end.line += line - 1;
self.start.column += column - 1;
self.end.column += column - 1;
}
}
impl Serialize for Location {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_seq(Some(4))?;
state.serialize_element(&self.start)?;
state.serialize_element(&self.end)?;
state.end()
}
}
impl std::fmt::Display for Location {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"location.start({}), location.end({})",
self.start, self.end
)
}
}
#[derive(Debug, Default, Clone, Hash, Eq, PartialEq, Serialize)]
#[non_exhaustive]
pub struct Position {
pub line: usize,
#[serde(rename = "col")]
pub column: usize,
}
impl std::fmt::Display for Position {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "line: {}, column: {}", self.line, self.column)
}
}