#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct Position {
pub line: usize,
pub column: usize,
pub offset: usize,
}
impl Position {
pub fn new() -> Self {
Self {
line: 1,
column: 1,
offset: 0,
}
}
pub fn advance(&mut self, ch: char) {
self.offset += ch.len_utf8();
if ch == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
}
}
impl std::fmt::Display for Position {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct Span {
pub start: Position,
pub end: Position,
}
impl Span {
pub fn new() -> Self {
Self::default()
}
pub fn from_positions(start: Position, end: Position) -> Self {
Self { start, end }
}
pub fn at(pos: Position) -> Self {
Self {
start: pos,
end: pos,
}
}
pub fn merge(self, other: Span) -> Self {
let start = if self.start.offset <= other.start.offset {
self.start
} else {
other.start
};
let end = if self.end.offset >= other.end.offset {
self.end
} else {
other.end
};
Self { start, end }
}
pub fn line(&self) -> usize {
self.start.line
}
}
impl std::fmt::Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.start.line == self.end.line {
write!(f, "line {}", self.start.line)
} else {
write!(f, "lines {}-{}", self.start.line, self.end.line)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_advance() {
let mut pos = Position::new();
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 1);
assert_eq!(pos.offset, 0);
pos.advance('a');
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 2);
assert_eq!(pos.offset, 1);
pos.advance('\n');
assert_eq!(pos.line, 2);
assert_eq!(pos.column, 1);
assert_eq!(pos.offset, 2);
pos.advance('b');
assert_eq!(pos.line, 2);
assert_eq!(pos.column, 2);
assert_eq!(pos.offset, 3);
}
#[test]
fn test_position_display() {
let pos = Position {
line: 5,
column: 10,
offset: 50,
};
assert_eq!(format!("{}", pos), "5:10");
}
#[test]
fn test_span_merge() {
let span1 = Span {
start: Position {
line: 1,
column: 1,
offset: 0,
},
end: Position {
line: 1,
column: 5,
offset: 4,
},
};
let span2 = Span {
start: Position {
line: 1,
column: 10,
offset: 9,
},
end: Position {
line: 2,
column: 3,
offset: 15,
},
};
let merged = span1.merge(span2);
assert_eq!(merged.start.offset, 0);
assert_eq!(merged.end.offset, 15);
}
#[test]
fn test_span_display() {
let single_line = Span {
start: Position {
line: 3,
column: 1,
offset: 0,
},
end: Position {
line: 3,
column: 10,
offset: 9,
},
};
assert_eq!(format!("{}", single_line), "line 3");
let multi_line = Span {
start: Position {
line: 1,
column: 1,
offset: 0,
},
end: Position {
line: 5,
column: 1,
offset: 50,
},
};
assert_eq!(format!("{}", multi_line), "lines 1-5");
}
}