Finish up scanner, add string-base error reporting
This commit is contained in:
+29
-14
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user