summaryrefslogtreecommitdiff
path: root/src/parse/lines.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/parse/lines.rs')
-rw-r--r--src/parse/lines.rs145
1 files changed, 0 insertions, 145 deletions
diff --git a/src/parse/lines.rs b/src/parse/lines.rs
deleted file mode 100644
index 2d97a25c..00000000
--- a/src/parse/lines.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-// FIXME:
-// Both `LineMap::location` and `search_column` can lead to quadratic compile
-// times for very long lines. We probably need some smart acceleration structure
-// to determine columns.
-
-use super::Scanner;
-use crate::syntax::{Location, Pos};
-
-/// Enables conversion of byte position to locations.
-pub struct LineMap<'s> {
- src: &'s str,
- line_starts: Vec<Pos>,
-}
-
-impl<'s> LineMap<'s> {
- /// Create a new line map for a source string.
- pub fn new(src: &'s str) -> Self {
- let mut line_starts = vec![Pos::ZERO];
- let mut s = Scanner::new(src);
-
- while let Some(c) = s.eat_merging_crlf() {
- if is_newline(c) {
- line_starts.push(s.index().into());
- }
- }
-
- Self { src, line_starts }
- }
-
- /// Convert a byte position to a 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 start = self.line_starts.get(line_index)?;
- let head = self.src.get(start.to_usize() .. pos.to_usize())?;
-
- // TODO: What about tabs?
- let column_index = head.chars().count();
-
- 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) {
- // TODO: What about tabs?
- let (idx, prev) = line.char_indices().nth(prev_idx)?;
- idx + prev.len_utf8()
- } else {
- 0
- };
-
- Some(line_start + line_offset)
- }
-}
-
-/// Count how many column the string would fill.
-pub fn count_columns(src: &str) -> usize {
- let mut column = 0;
- for c in src.chars().rev() {
- if is_newline(c) {
- break;
- } else if c == '\t' {
- // TODO: How many columns per tab?
- column += 2;
- } else {
- column += 1;
- }
- }
- column
-}
-
-/// Whether this character denotes a newline.
-#[inline]
-pub fn is_newline(character: char) -> bool {
- matches!(
- character,
- // Line Feed, Vertical Tab, Form Feed, Carriage Return.
- '\n' | '\x0B' | '\x0C' | '\r' |
- // Next Line, Line Separator, Paragraph Separator.
- '\u{0085}' | '\u{2028}' | '\u{2029}'
- )
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- const TEST: &str = "äbcde\nf💛g\r\nhi\rjkl";
-
- #[test]
- fn test_line_map_new() {
- let map = LineMap::new(TEST);
- assert_eq!(map.line_starts, vec![Pos(0), Pos(7), Pos(15), Pos(18)]);
- }
-
- #[test]
- fn test_line_map_location() {
- let map = LineMap::new(TEST);
- 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]
- 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));
- }
-}