diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/geom/point.rs | 5 | ||||
| -rw-r--r-- | src/geom/size.rs | 5 | ||||
| -rw-r--r-- | src/main.rs | 6 | ||||
| -rw-r--r-- | src/parse/lines.rs | 71 | ||||
| -rw-r--r-- | src/parse/tests.rs | 8 |
5 files changed, 73 insertions, 22 deletions
diff --git a/src/geom/point.rs b/src/geom/point.rs index 10ab2d3a..4523a861 100644 --- a/src/geom/point.rs +++ b/src/geom/point.rs @@ -17,6 +17,11 @@ impl Point { pub fn new(x: Length, y: Length) -> Self { Self { x, y } } + + /// Create an instance with two equal components. + pub fn uniform(value: Length) -> Self { + Self { x: value, y: value } + } } impl Get<SpecAxis> for Point { diff --git a/src/geom/size.rs b/src/geom/size.rs index 0ad0e0f8..28984659 100644 --- a/src/geom/size.rs +++ b/src/geom/size.rs @@ -21,6 +21,11 @@ impl Size { Self { width, height } } + /// Create an instance with two equal components. + pub fn uniform(value: Length) -> Self { + Self { width: value, height: value } + } + /// Whether the other size fits into this one (smaller width and height). pub fn fits(self, other: Self) -> bool { self.width >= other.width && self.height >= other.height diff --git a/src/main.rs b/src/main.rs index 3f12655b..acd2d0cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,10 +59,10 @@ fn main() -> anyhow::Result<()> { let map = LineMap::new(&src); for diag in diags { let span = diag.span; - let start = map.location(span.start); - let end = map.location(span.end); + let start = map.location(span.start).unwrap(); + let end = map.location(span.end).unwrap(); println!( - " {}: {}:{}-{}: {}", + "{}: {}:{}-{}: {}", diag.v.level, src_path.display(), start, diff --git a/src/parse/lines.rs b/src/parse/lines.rs index ce5a1fe5..be120d8a 100644 --- a/src/parse/lines.rs +++ b/src/parse/lines.rs @@ -1,7 +1,7 @@ //! Conversion of byte positions to line/column locations. use super::Scanner; -use crate::syntax::{Location, Pos}; +use crate::syntax::{Location, Offset, Pos}; /// Enables conversion of byte position to locations. pub struct LineMap<'s> { @@ -25,23 +25,48 @@ impl<'s> LineMap<'s> { } /// Convert a byte position to a location. - /// - /// # Panics - /// This panics if the position is out of bounds. - pub fn location(&self, pos: Pos) -> Location { + pub fn location(&self, pos: Pos) -> Option<Location> { + // Find the line which contains the position. let line_index = match self.line_starts.binary_search(&pos) { Ok(i) => i, Err(i) => i - 1, }; - let line_start = self.line_starts[line_index]; - let head = &self.src[line_start.to_usize() .. pos.to_usize()]; + let start = self.line_starts.get(line_index)?; + let head = self.src.get(start.to_usize() .. pos.to_usize())?; let column_index = head.chars().count(); - Location { + Some(Location { line: 1 + line_index as u32, column: 1 + column_index as u32, - } + }) + } + + /// Convert a location to a byte position. + pub fn pos(&self, location: Location) -> Option<Pos> { + // Determine the boundaries of the line. + let line_idx = location.line.checked_sub(1)? as usize; + let line_start = self.line_starts.get(line_idx)?; + let line_end = self + .line_starts + .get(location.line as usize) + .map_or(self.src.len(), |pos| pos.to_usize()); + + let line = self.src.get(line_start.to_usize() .. line_end)?; + + // Find the index in the line. For the first column, the index is always zero. For + // other columns, we have to look at which byte the char directly before the + // column in question ends. We can't do `nth(column_idx)` directly since the + // column may be behind the last char. + let column_idx = location.column.checked_sub(1)? as usize; + let line_offset = if let Some(prev_idx) = column_idx.checked_sub(1) { + let (idx, prev) = line.char_indices().nth(prev_idx)?; + idx + prev.len_utf8() + } else { + 0 + }; + + Some(line_start.offset(Pos(line_offset as u32))) } } @@ -71,18 +96,26 @@ mod tests { #[test] fn test_line_map_location() { let map = LineMap::new(TEST); - assert_eq!(map.location(Pos(0)), Location::new(1, 1)); - assert_eq!(map.location(Pos(2)), Location::new(1, 2)); - assert_eq!(map.location(Pos(6)), Location::new(1, 6)); - assert_eq!(map.location(Pos(7)), Location::new(2, 1)); - assert_eq!(map.location(Pos(8)), Location::new(2, 2)); - assert_eq!(map.location(Pos(12)), Location::new(2, 3)); - assert_eq!(map.location(Pos(21)), Location::new(4, 4)); + assert_eq!(map.location(Pos(0)), Some(Location::new(1, 1))); + assert_eq!(map.location(Pos(2)), Some(Location::new(1, 2))); + assert_eq!(map.location(Pos(6)), Some(Location::new(1, 6))); + assert_eq!(map.location(Pos(7)), Some(Location::new(2, 1))); + assert_eq!(map.location(Pos(8)), Some(Location::new(2, 2))); + assert_eq!(map.location(Pos(12)), Some(Location::new(2, 3))); + assert_eq!(map.location(Pos(21)), Some(Location::new(4, 4))); + assert_eq!(map.location(Pos(22)), None); } #[test] - #[should_panic] - fn test_line_map_panics_out_of_bounds() { - LineMap::new(TEST).location(Pos(22)); + fn test_line_map_pos() { + fn assert_round_trip(map: &LineMap, pos: Pos) { + assert_eq!(map.location(pos).and_then(|loc| map.pos(loc)), Some(pos)); + } + + let map = LineMap::new(TEST); + assert_round_trip(&map, Pos(0)); + assert_round_trip(&map, Pos(7)); + assert_round_trip(&map, Pos(12)); + assert_round_trip(&map, Pos(21)); } } diff --git a/src/parse/tests.rs b/src/parse/tests.rs index 054b2cd9..172b1d15 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -56,24 +56,31 @@ use Unit::*; fn Id(ident: &str) -> Expr { Expr::Lit(Lit::Ident(Ident(ident.to_string()))) } + fn Bool(b: bool) -> Expr { Expr::Lit(Lit::Bool(b)) } + fn Int(int: i64) -> Expr { Expr::Lit(Lit::Int(int)) } + fn Float(float: f64) -> Expr { Expr::Lit(Lit::Float(float)) } + fn Percent(percent: f64) -> Expr { Expr::Lit(Lit::Percent(percent)) } + fn Length(val: f64, unit: Unit) -> Expr { Expr::Lit(Lit::Length(val, unit)) } + fn Color(color: RgbaColor) -> Expr { Expr::Lit(Lit::Color(color)) } + fn Str(string: &str) -> Expr { Expr::Lit(Lit::Str(string.to_string())) } @@ -98,6 +105,7 @@ fn Unary(op: impl Into<Spanned<UnOp>>, expr: impl Into<Spanned<Expr>>) -> Expr { expr: expr.into().map(Box::new), }) } + fn Binary( op: impl Into<Spanned<BinOp>>, lhs: impl Into<Spanned<Expr>>, |
