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.
145 lines
3.9 KiB
Rust
145 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 self.current == Some('\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];
|
|
|
|
self.col -= 1;
|
|
if self.col == 0 {
|
|
self.prev_line();
|
|
}
|
|
|
|
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 = 1;
|
|
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;
|
|
}
|
|
}
|