[rlox] Add parse errors, pop expr statements

This commit is contained in:
ctsk
2024-09-01 23:44:38 +02:00
parent 80b0c20ccd
commit 297ec48bdb
3 changed files with 171 additions and 66 deletions

View File

@@ -22,6 +22,7 @@ pub enum Op {
Less, Less,
Print, Print,
Pop
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]

View File

@@ -1,4 +1,5 @@
use std::iter::Peekable; use std::iter::Peekable;
use std::ops::Range;
use std::str::CharIndices; use std::str::CharIndices;
use std::{collections::HashMap, convert::identity}; use std::{collections::HashMap, convert::identity};
@@ -52,7 +53,7 @@ enum TokenType {
Error, Error,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct Token<'src> { pub struct Token<'src> {
ttype: TokenType, ttype: TokenType,
span: &'src str, span: &'src str,
@@ -72,21 +73,16 @@ impl LineMap {
fn new(source: &str) -> LineMap { fn new(source: &str) -> LineMap {
LineMap { LineMap {
line_breaks: source line_breaks: source
.char_indices() .bytes()
.filter_map(|(pos, c)| if c == '\n' { Some(pos) } else { None }) .enumerate()
.filter_map(|(pos, c)| if c == b'\n' { Some(pos) } else { None })
.collect(), .collect(),
} }
} }
fn get_line(&self, pos: usize) -> usize { fn pos_to_line(&self, pos: usize) -> usize {
self.line_breaks let (Ok(index) | Err(index)) = self.line_breaks.binary_search(&pos);
.binary_search(&pos) index + 1
.unwrap_or_else(identity)
}
fn get_lines(&self, start: usize, slice: &str) -> (usize, usize) {
let end = start + slice.len();
(self.get_line(start), self.get_line(end))
} }
} }
@@ -106,6 +102,17 @@ impl<'src> Scanner<'src> {
} }
} }
pub fn get_range(&self, token: Token) -> Option<Range<usize>> {
let [source_addr, span_addr]: [usize; 2] =
[self.source, token.span].map(|s| s.as_ptr() as usize);
if span_addr < source_addr || span_addr + token.span.len() > source_addr + self.source.len()
{
return None; // out of bounds
}
let start_index = span_addr - source_addr;
Some(start_index..start_index + token.span.len())
}
fn consume_if<P>(&mut self, p: P) -> Option<usize> fn consume_if<P>(&mut self, p: P) -> Option<usize>
where where
P: Fn(char) -> bool, P: Fn(char) -> bool,
@@ -153,6 +160,8 @@ impl<'src> Scanner<'src> {
.consume_while(|c| c.is_ascii_alphanumeric()) .consume_while(|c| c.is_ascii_alphanumeric())
.unwrap_or(end); .unwrap_or(end);
// todo: make robust to non-ascii chars
if let Some(pos) = self.consume_if_eq('.') { if let Some(pos) = self.consume_if_eq('.') {
end = pos; end = pos;
@@ -267,6 +276,7 @@ impl<'src> Iterator for Scanner<'src> {
struct Parser<'src> { struct Parser<'src> {
scanner: Peekable<Scanner<'src>>, scanner: Peekable<Scanner<'src>>,
errors: Vec<ParseError<'src>>,
intern_table: HashMap<&'src str, u8>, intern_table: HashMap<&'src str, u8>,
} }
@@ -292,16 +302,18 @@ enum Precedence {
} }
enum ParseError<'src> { enum ParseError<'src> {
InvalidNumber(Token<'src>) InvalidNumber(Token<'src>),
UnexpectedToken(Vec<TokenType>, Token<'src>),
UnexpectedEOF,
} }
type Result<'src, T> = std::result::Result<T, ParseError<'src>>; type Result<'src> = std::result::Result<(), ParseError<'src>>;
impl<'src> Parser<'src> { impl<'src> Parser<'src> {
fn new(sc: Scanner<'src>) -> Self { fn new(sc: Scanner<'src>) -> Self {
Parser { Parser {
scanner: sc.into_iter().peekable(), scanner: sc.into_iter().peekable(),
errors: Vec::new(),
intern_table: HashMap::new(), intern_table: HashMap::new(),
} }
} }
@@ -328,12 +340,12 @@ impl<'src> Parser<'src> {
} }
} }
fn _expression(&mut self, chunk: &mut Chunk, min_prec: Precedence) { fn _expression(&mut self, chunk: &mut Chunk, min_prec: Precedence) -> Result<'src> {
match self.scanner.next() { match self.scanner.next() {
None => panic!("Expected further tokens"), None => return Err(ParseError::UnexpectedEOF),
Some(token) => match token.ttype { Some(token) => match token.ttype {
TokenType::Minus | TokenType::Bang => { TokenType::Minus | TokenType::Bang => {
self._expression(chunk, Precedence::Unary); self._expression(chunk, Precedence::Unary)?;
let op = match token.ttype { let op = match token.ttype {
TokenType::Minus => Op::Negate, TokenType::Minus => Op::Negate,
TokenType::Bang => Op::Not, TokenType::Bang => Op::Not,
@@ -343,9 +355,9 @@ impl<'src> Parser<'src> {
} }
TokenType::Number => { TokenType::Number => {
match token.span.parse::<f64>() { match token.span.parse::<f64>() {
Ok(c) => chunk.add_constant(c.into(), 0), Ok(c) => Ok(chunk.add_constant(c.into(), 0)),
_ => panic!("Could not parse number"), _ => Err(ParseError::InvalidNumber(token)),
}; }?;
} }
TokenType::String => { TokenType::String => {
let without_quotes = &token.span[1..(token.span.len() - 1)]; let without_quotes = &token.span[1..(token.span.len() - 1)];
@@ -362,20 +374,26 @@ impl<'src> Parser<'src> {
} }
}; };
} }
TokenType::Nil | TokenType::True | TokenType::False => {
let op = match token.ttype {
TokenType::Nil => Op::Nil,
TokenType::True => Op::True,
TokenType::False => Op::False,
_ => unreachable!(),
};
chunk.add_op(op, 0);
}
TokenType::LeftParen => { TokenType::LeftParen => {
self._expression(chunk, Precedence::None); self._expression(chunk, Precedence::None)?;
assert_eq!(self.scanner.next().unwrap().ttype, TokenType::RightParen) assert_eq!(self.scanner.next().unwrap().ttype, TokenType::RightParen)
} }
_ => panic!("Expected '-' or number"), TokenType::Nil => {
chunk.add_op(Op::Nil, 0);
}
TokenType::True => {
chunk.add_op(Op::True, 0);
}
TokenType::False => {
chunk.add_op(Op::False, 0);
}
_ => {
use TokenType::*;
return Err(ParseError::UnexpectedToken(
vec![Minus, Bang, Number, String, Nil, True, False, LeftParen],
token,
));
}
}, },
}; };
@@ -398,7 +416,7 @@ impl<'src> Parser<'src> {
} }
}) { }) {
// Generates code for rhs // Generates code for rhs
self._expression(chunk, Self::precedence(op.ttype)); self._expression(chunk, Self::precedence(op.ttype))?;
match op.ttype { match op.ttype {
TokenType::Plus => chunk.add_op(Op::Add, 0), TokenType::Plus => chunk.add_op(Op::Add, 0),
@@ -411,50 +429,89 @@ impl<'src> Parser<'src> {
TokenType::BangEqual => chunk.add_op(Op::Equal, 0).add_op(Op::Not, 0), TokenType::BangEqual => chunk.add_op(Op::Equal, 0).add_op(Op::Not, 0),
TokenType::GreaterEqual => chunk.add_op(Op::Less, 0).add_op(Op::Not, 0), TokenType::GreaterEqual => chunk.add_op(Op::Less, 0).add_op(Op::Not, 0),
TokenType::LessEqual => chunk.add_op(Op::Greater, 0).add_op(Op::Not, 0), TokenType::LessEqual => chunk.add_op(Op::Greater, 0).add_op(Op::Not, 0),
_ => todo!(), _ => {
use TokenType::*;
return Err(ParseError::UnexpectedToken(
vec![
Plus,
Minus,
Star,
Slash,
EqualEqual,
Greater,
Less,
BangEqual,
GreaterEqual,
LessEqual,
],
op,
));
}
}; };
} }
Ok(())
} }
pub fn expression(&mut self, chunk: &mut Chunk) { pub fn expression(&mut self, chunk: &mut Chunk) -> Result<'src> {
self._expression(chunk, Precedence::None) self._expression(chunk, Precedence::None)
} }
pub fn must_consume(&mut self, ttype: TokenType) -> Option<Token> { pub fn must_consume(&mut self, ttype: TokenType) -> Result<'src> {
self.scanner.next_if( match self.scanner.peek() {
|tok| tok.ttype == ttype Some(token) => {
).or_else( if token.ttype == ttype {
|| panic!() self.scanner.next();
) Ok(())
} else {
Err(ParseError::UnexpectedToken(vec![ttype], token.clone()))
}
},
None => Err(ParseError::UnexpectedEOF),
}
} }
pub fn print_statement(&mut self, chunk: &mut Chunk) { pub fn print_statement(&mut self, chunk: &mut Chunk) -> Result<'src> {
self.must_consume(TokenType::Print).unwrap(); self.must_consume(TokenType::Print)?;
self.expression(chunk); self.expression(chunk)?;
chunk.add_op(Op::Print, 0); chunk.add_op(Op::Print, 0);
self.must_consume(TokenType::Semicolon).unwrap(); self.must_consume(TokenType::Semicolon)
} }
pub fn expr_statement(&mut self, chunk: &mut Chunk) { pub fn expr_statement(&mut self, chunk: &mut Chunk) -> Result<'src> {
self.expression(chunk); self.expression(chunk)?;
self.must_consume(TokenType::Semicolon).unwrap(); chunk.add_op(Op::Pop, 0);
self.must_consume(TokenType::Semicolon)
} }
pub fn statement(&mut self, chunk: &mut Chunk) { pub fn statement(&mut self, chunk: &mut Chunk) -> Result<'src> {
match self.scanner.peek().unwrap().ttype { match self.scanner.peek().unwrap().ttype {
TokenType::Print => self.print_statement(chunk), TokenType::Print => self.print_statement(chunk),
_ => self.expr_statement(chunk), _ => self.expr_statement(chunk),
} }
} }
pub fn synchronize(&mut self) {
use TokenType::*;
while let Some(token) = self.scanner.next_if(
|tok| ![Semicolon, Class, Fun, Var, For, If, While, Print, Return].contains(&tok.ttype)
) {}
}
pub fn declaration(&mut self, chunk: &mut Chunk) { pub fn declaration(&mut self, chunk: &mut Chunk) {
self.statement(chunk); self.statement(chunk).unwrap_or_else(
|err| {
self.errors.push(err);
self.synchronize();
}
)
} }
pub fn compile(&mut self, chunk: &mut Chunk) { pub fn compile(&mut self, chunk: &mut Chunk) {
while let Some(_) = self.scanner.peek() { while let Some(_) = self.scanner.peek() {
self.declaration(chunk); self.declaration(chunk)
} }
} }
} }
@@ -492,44 +549,55 @@ mod tests {
vec![ vec![
Token { Token {
ttype: TokenType::Print, ttype: TokenType::Print,
span: &source[0..=4] span: "print"
}, },
Token { Token {
ttype: TokenType::LeftParen, ttype: TokenType::LeftParen,
span: &source[5..=5] span: "("
}, },
Token { Token {
ttype: TokenType::Number, ttype: TokenType::Number,
span: &source[6..=6] span: "1"
}, },
Token { Token {
ttype: TokenType::Plus, ttype: TokenType::Plus,
span: &source[7..=7] span: "+"
}, },
Token { Token {
ttype: TokenType::Number, ttype: TokenType::Number,
span: &source[8..=8] span: "2"
}, },
Token { Token {
ttype: TokenType::Star, ttype: TokenType::Star,
span: &source[9..=9] span: "*"
}, },
Token { Token {
ttype: TokenType::Number, ttype: TokenType::Number,
span: &source[10..=10] span: "3"
}, },
Token { Token {
ttype: TokenType::RightParen, ttype: TokenType::RightParen,
span: &source[11..=11] span: ")"
}, },
Token { Token {
ttype: TokenType::Semicolon, ttype: TokenType::Semicolon,
span: &source[12..=12] span: ";"
} }
] ]
); );
} }
// #[test]
// fn number_scan() {
// let source = "1a";
// let scanner = Scanner::new(source);
// let tokens: Vec<Token> = scanner.collect();
// assert_eq!(
// tokens,
// vec![Token{ttype: TokenType::Number, span: "1a"}]
// );
// }
#[test] #[test]
fn comment_scan() { fn comment_scan() {
let source = "1\n2//comment\n3"; let source = "1\n2//comment\n3";
@@ -541,15 +609,15 @@ mod tests {
vec![ vec![
Token { Token {
ttype: TokenType::Number, ttype: TokenType::Number,
span: &source[0..=0] span: "1"
}, },
Token { Token {
ttype: TokenType::Number, ttype: TokenType::Number,
span: &source[2..=2] span: "2"
}, },
Token { Token {
ttype: TokenType::Number, ttype: TokenType::Number,
span: &source[13..=13] span: "3"
} }
] ]
); );
@@ -565,7 +633,7 @@ mod tests {
tokens, tokens,
vec![Token { vec![Token {
ttype: TokenType::String, ttype: TokenType::String,
span: &source[0..=12] span: "\"hello world\""
}] }]
); );
@@ -580,11 +648,12 @@ mod tests {
assert!(chunk.instr_eq(expected)); assert!(chunk.instr_eq(expected));
} }
fn test_parse_program(source: &str, expected: &Chunk) { fn test_parse_program<'src>(source: &'src str, expected: &Chunk) {
let scanner = Scanner::new(source); let scanner = Scanner::new(source);
let mut parser = Parser::new(scanner); let mut parser = Parser::new(scanner);
let mut chunk = Chunk::new(); let mut chunk = Chunk::new();
parser.compile(&mut chunk); parser.compile(&mut chunk);
assert!(parser.errors.is_empty());
assert!(chunk.instr_eq(expected)); assert!(chunk.instr_eq(expected));
} }
@@ -699,7 +768,7 @@ mod tests {
let source = "1 / 1;"; let source = "1 / 1;";
use crate::bc::Op::*; use crate::bc::Op::*;
let expected = Chunk::new_with( let expected = Chunk::new_with(
vec![Constant { offset: 0 }, Constant { offset: 1 }, Divide], vec![Constant { offset: 0 }, Constant { offset: 1 }, Divide, Pop],
vec![], vec![],
vec![Value::from(1.0), Value::from(1.0)], vec![Value::from(1.0), Value::from(1.0)],
LinkedList::new(), LinkedList::new(),
@@ -707,4 +776,36 @@ mod tests {
test_parse_program(source, &expected); test_parse_program(source, &expected);
} }
#[test]
fn no_line_breaks() {
let line_map = LineMap::new("0123456789");
for i in 0..=9 {
assert_eq!(line_map.pos_to_line(i), 1);
}
}
#[test]
fn some_line_breaks() {
let line_map = LineMap::new("012\n456\n89\n");
for i in 0..=2 {
assert_eq!(line_map.pos_to_line(i), 1);
}
assert_eq!(line_map.pos_to_line(3), 1);
for i in 4..=6 {
assert_eq!(line_map.pos_to_line(i), 2);
}
assert_eq!(line_map.pos_to_line(7), 2);
for i in 8..=9 {
assert_eq!(line_map.pos_to_line(i), 3);
}
assert_eq!(line_map.pos_to_line(10), 3);
}
} }

View File

@@ -173,6 +173,9 @@ impl VM {
let value = self.pop()?; let value = self.pop()?;
writeln!(output, "{}", value) writeln!(output, "{}", value)
.map_err(|_| VMError::Runtime("Failed to print".into(), self.pc))? .map_err(|_| VMError::Runtime("Failed to print".into(), self.pc))?
},
Op::Pop => {
self.pop()?;
} }
} }
} }