reproto_core/
utils.rs

1use Span;
2use errors::Result;
3use std::io::Read;
4
5const NL: u8 = '\n' as u8;
6const CR: u8 = '\r' as u8;
7
8/// A position withing a source.
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub struct Position {
11    pub line: usize,
12    pub col: usize,
13}
14
15/// Find the line corresponding to the given position.
16pub fn find_line<'a, R: AsMut<Read + 'a>>(
17    mut reader: R,
18    span: (usize, usize),
19) -> Result<(String, usize, (usize, usize))> {
20    let r = reader.as_mut();
21
22    let mut line = 0usize;
23    let mut current = 0usize;
24    let mut buffer: Vec<u8> = Vec::new();
25
26    let start = span.0;
27    let end = span.1;
28
29    let mut it = r.bytes().peekable();
30    let mut read = 0usize;
31
32    while let Some(b) = it.next() {
33        let b = b?;
34        read += 1;
35
36        match b {
37            NL => {}
38            _ => {
39                buffer.push(b);
40                continue;
41            }
42        }
43
44        let start_of_line = current;
45        current += read;
46
47        if current > start {
48            let buffer = String::from_utf8(buffer)?;
49            let end = ::std::cmp::min(end, current);
50            let range = (start - start_of_line, end - start_of_line);
51            return Ok((buffer, line, range));
52        }
53
54        read = 0usize;
55        line += 1;
56        buffer.clear();
57    }
58
59    Err("bad file position".into())
60}
61
62/// Find the range corresponding to the given position.
63pub fn find_range<'a, R: AsMut<Read + 'a>, S: Into<Span>>(
64    mut reader: R,
65    span: S,
66    encoding: Encoding,
67) -> Result<(Position, Position)> {
68    let span = span.into();
69
70    let r = reader.as_mut();
71
72    let mut start = Position::default();
73    let mut end = Position::default();
74
75    let mut line = 0usize;
76    let mut col = 0usize;
77
78    // keep the current line in buffer.
79    let mut buffer = Vec::new();
80    let mut it = r.bytes().enumerate().peekable();
81
82    while let Some((c, b)) = it.next() {
83        let b = b?;
84
85        let nl = match b {
86            // macos
87            CR => {
88                // windows
89                if let Some(&(_, Ok(NL))) = it.peek() {
90                    it.next();
91                }
92
93                true
94            }
95            NL => true,
96            _ => false,
97        };
98
99        if nl {
100            line += 1;
101            col = 0;
102            buffer.clear();
103        } else {
104            buffer.push(b);
105        }
106
107        if c == span.start {
108            start.line = line;
109            start.col = encoding.column(&buffer, col)?;
110        }
111
112        if c == span.end {
113            end.line = line;
114            end.col = encoding.column(&buffer, col)?;
115            break;
116        }
117
118        if !nl {
119            col += 1;
120        }
121    }
122
123    Ok((start, end))
124}
125
126/// Encoding for which to check the range.
127#[derive(Debug, Clone, Copy)]
128pub enum Encoding {
129    /// Emit the raw byte offset for the column.
130    Bytes,
131    /// Emit the UTF-8 offset for the column.
132    Utf8,
133    /// Emit the UTF-16 offset for the column.
134    Utf16,
135}
136
137impl Encoding {
138    /// Calculate the column, which depends on the encoding.
139    pub fn column(&self, buffer: &[u8], col: usize) -> Result<usize> {
140        use self::Encoding::*;
141
142        match *self {
143            Bytes => Ok(col),
144            Utf8 => Ok(::std::str::from_utf8(&buffer[..col])?.chars().count()),
145            Utf16 => Ok(::std::str::from_utf8(&buffer[..col])?
146                .encode_utf16()
147                .count()),
148        }
149    }
150}