Finish up scanner, add string-base error reporting

This commit is contained in:
2024-05-05 20:46:19 -07:00
parent 669bcbfcb7
commit fe21c1a792
+29 -14
View File
@@ -15,7 +15,7 @@
use std::{ use std::{
iter::{Fuse, Peekable}, iter::{Fuse, Peekable},
str::Chars, str::CharIndices,
}; };
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
@@ -27,22 +27,24 @@ pub enum Token {
String(String), String(String),
} }
pub fn scan(input: &str) -> Vec<Token> { pub fn scan(input: &str) -> (Vec<Token>, Vec<String>) {
let mut scanner = Scanner::new(input); let mut scanner = Scanner::new(input);
scanner.scan(); scanner.scan();
scanner.tokens (scanner.tokens, scanner.errors)
} }
struct Scanner<'a> { struct Scanner<'a> {
text: Peekable<Fuse<Chars<'a>>>, text: Peekable<Fuse<CharIndices<'a>>>,
tokens: Vec<Token>, tokens: Vec<Token>,
errors: Vec<String>,
} }
impl<'a> Scanner<'a> { impl<'a> Scanner<'a> {
fn new(input: &'a str) -> Self { fn new(input: &'a str) -> Self {
Scanner { Scanner {
text: input.chars().fuse().peekable(), text: input.char_indices().fuse().peekable(),
tokens: vec![], tokens: vec![],
errors: vec![],
} }
} }
@@ -77,7 +79,11 @@ impl<'a> Scanner<'a> {
self.identifier(); self.identifier();
} }
_ => { _ => {
todo!("unused chars") if let Some((i, c)) = self.text.peek() {
self.errors
.push(format!("Unexpected character {} at offset {}", c, i));
}
self.advance();
} }
} }
} }
@@ -103,7 +109,7 @@ impl<'a> Scanner<'a> {
} }
} }
if !self.consume('"') { if !self.consume('"') {
todo!("Handle premature EOF") self.errors.push("string not terminated".into());
} }
self.tokens.push(Token::String(result)); self.tokens.push(Token::String(result));
} }
@@ -152,11 +158,11 @@ impl<'a> Scanner<'a> {
} }
fn peek(&mut self) -> Option<char> { fn peek(&mut self) -> Option<char> {
self.text.peek().copied() self.text.peek().map(|(_, c)| *c)
} }
fn advance(&mut self) -> Option<char> { fn advance(&mut self) -> Option<char> {
self.text.next() self.text.next().map(|(_, c)| c)
} }
fn consume(&mut self, c: char) -> bool { fn consume(&mut self, c: char) -> bool {
@@ -187,6 +193,10 @@ mod tests {
token_helper!(raw_text, RawText); token_helper!(raw_text, RawText);
token_helper!(string, String); token_helper!(string, String);
fn scan(input: &str) -> Vec<Token> {
super::scan(input).0
}
#[test] #[test]
fn scan_empty() { fn scan_empty() {
assert_eq!(vec![] as Vec<Token>, scan("")); assert_eq!(vec![] as Vec<Token>, scan(""));
@@ -269,12 +279,17 @@ mod tests {
); );
} }
#[ignore = "not implemented yet"]
#[test] #[test]
fn scan_premature_end_of_string() { fn scan_premature_end_of_string() {
assert_eq!( let (tokens, errors) = super::scan("%foo \"bar");
vec![Percent, identifier("foo"), string("bar")], assert_eq!(vec![Percent, identifier("foo"), string("bar")], tokens);
scan("%foo \"bar") assert!(errors.len() == 1);
); }
#[test]
fn scan_unexpected_chars() {
let (tokens, errors) = super::scan("%foo @bar");
assert_eq!(vec![Percent, identifier("foo"), identifier("bar")], tokens);
assert!(errors.len() == 1);
} }
} }