kingfisher/src/location.rs
2025-06-24 17:17:16 -07:00

129 lines
3.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use core::ops::Range;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// A point defined by a byte offset.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Copy, Clone)]
pub struct OffsetPoint(pub usize);
impl OffsetPoint {
#[inline]
pub fn new(idx: usize) -> Self {
OffsetPoint(idx)
}
}
/// A nonempty span defined by two byte offsets (halfopen interval `[start, end)`).
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
pub struct OffsetSpan {
pub start: usize,
pub end: usize,
}
impl std::fmt::Display for OffsetSpan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{}", self.start, self.end)
}
}
impl OffsetSpan {
#[inline]
pub fn from_offsets(start: OffsetPoint, end: OffsetPoint) -> Self {
OffsetSpan { start: start.0, end: end.0 }
}
#[inline]
pub fn from_range(range: Range<usize>) -> Self {
OffsetSpan { start: range.start, end: range.end }
}
/// Length in bytes.
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.end.saturating_sub(self.start)
}
/// True if empty or inverted.
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.start >= self.end
}
/// True if `other` lies entirely within `self`.
#[inline]
#[must_use]
pub fn fully_contains(&self, other: &Self) -> bool {
self.start <= other.start && other.end <= self.end
}
}
/// A point in the source file (line 1indexed, column 0indexed).
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SourcePoint {
pub line: usize,
pub column: usize,
}
impl std::fmt::Display for SourcePoint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
/// A closed interval between two source points.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SourceSpan {
pub start: SourcePoint,
pub end: SourcePoint,
}
impl std::fmt::Display for SourceSpan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{}", self.start, self.end)
}
}
/// Records newline byteoffsets to map offsets -- (line, column).
pub struct LocationMapping {
newline_offsets: Vec<usize>,
}
impl LocationMapping {
/// Scan once for all `\n` positions.
pub fn new(input: &[u8]) -> Self {
let newline_offsets =
input.iter().enumerate().filter_map(|(i, &b)| (b == b'\n').then_some(i)).collect();
LocationMapping { newline_offsets }
}
/// Map a byte offset to a `SourcePoint`.
pub fn get_source_point(&self, offset: usize) -> SourcePoint {
let line = match self.newline_offsets.binary_search(&offset) {
Ok(idx) => idx + 2, // exact newline -- next line
Err(idx) => idx + 1,
};
let column = if let Some(&last) = self.newline_offsets.get(line.saturating_sub(2)) {
offset.saturating_sub(last + 1)
} else {
offset
};
SourcePoint { line, column }
}
/// Map an `OffsetSpan` -- `SourceSpan` (closed interval).
pub fn get_source_span(&self, span: &OffsetSpan) -> SourceSpan {
let start = self.get_source_point(span.start);
let end = self.get_source_point(span.end.saturating_sub(1));
SourceSpan { start, end }
}
}
/// Combined byte and sourcespan.
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
pub struct Location {
pub offset_span: OffsetSpan,
pub source_span: SourceSpan,
}