You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
3.9 KiB
Rust

/// Convenience helper for iterating over the characters in a string.
/// Supports backwards seeking and tracks line/column numbers.
pub struct Cursor {
raw: Vec<char>, // array of all characters
pos: usize, // index (in `raw`) of the *next* character to be read
line_lengths: Vec<usize>, // previous line lengths (for seeking back)
line: usize, // current line (counting from 1)
col: usize, // current column (counting from 1)
chop: usize, // value of `pos` when `chop()` was called the last time
current: Option<char>, // current character
}
impl Iterator for Cursor {
type Item = char;
fn next(&mut self) -> Option<char> {
if self.pos < self.raw.len() {
let c = self.raw[self.pos];
self.pos += 1;
if c == '\n' {
self.new_line();
} else {
self.col += 1;
}
self.current = Some(c);
Some(c)
} else {
None
}
}
}
impl Cursor {
pub fn new(raw: String) -> Cursor {
Cursor {
raw: Vec::from_iter(raw.chars()),
line_lengths: Vec::new(),
line: 1,
col: 0, // increments in first call to next()
pos: 0,
chop: 0,
current: None,
}
}
/// Reverse the cursor by a single character.
pub fn prev(&mut self) -> Option<char> {
if self.pos > 0 {
self.pos -= 1;
let c = self.raw[self.pos];
if self.col == 0 {
self.prev_line();
} else {
self.col -= 1;
}
self.current = Some(c);
Some(c)
} else {
None
}
}
/// Seek backward and return all characters that were encountered.
pub fn seek_back(&mut self, n: usize) -> Vec<char> {
// TODO: implement this properly as well
let mut v = Vec::new();
for _ in 0..n {
match self.prev() {
Some(c) => v.push(c),
None => break,
}
}
v.reverse(); // TODO: there is probably a more elegant way for this
v
}
/// Seek forward until the `test` callback returns false.
pub fn seek_while(&mut self, test: fn(c: char) -> bool) -> Vec<char> {
let mut v = Vec::new();
while let Some(c) = self.peek() {
if test(c) {
v.push(c);
self.next();
} else {
break;
}
}
v
}
/// Return the next character without actually advancing the cursor.
pub fn peek(&mut self) -> Option<char> {
let c = self.next()?;
self.prev();
Some(c)
}
pub fn skip_whitespace(&mut self) {
self.seek_while(|c| c.is_ascii_whitespace());
}
/// Return a string of every character since
/// the last time this method was called.
pub fn chop(&mut self) -> String {
assert!(self.pos >= self.chop);
let s = String::from_iter(self.raw[self.chop..self.pos].into_iter());
self.chop = self.pos;
s
}
/// Return the line number (starting from 1) of the last
/// character that was read. Returns 1 if no characters
/// have been read yet.
pub fn line(&self) -> usize {
self.line
}
/// Return the column number (starting from 1) of the last
/// character that was read. Returns 0 if no characters
/// have been read yet.
pub fn col(&self) -> usize {
self.col
}
pub fn current(&self) -> Option<char> {
self.current
}
fn new_line(&mut self) {
self.line_lengths.push(self.col);
self.col = 0;
self.line += 1;
}
fn prev_line(&mut self) {
assert!(self.line > 0);
assert_eq!(self.col, 0);
self.col = self.line_lengths.pop().unwrap();
self.line -= 1;
}
}