diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-12-10 22:44:35 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-12-10 22:45:45 +0100 |
| commit | 1cbd5f3051ba90b3f673bc2f6319192d05381719 (patch) | |
| tree | 182134e9f355062a00a145fab3a988847c4ed13b /src/parse | |
| parent | fdc1b378a3eb3cf325592b801c43e2ec2478ddff (diff) | |
Refine test infrastructure ✅
- Tests diagnostics
- More and better separated image tests
Diffstat (limited to 'src/parse')
| -rw-r--r-- | src/parse/lines.rs | 71 | ||||
| -rw-r--r-- | src/parse/tests.rs | 8 |
2 files changed, 60 insertions, 19 deletions
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>>, |
