1
0

Add better parser error

This commit is contained in:
Jeff 2024-09-24 00:24:09 -04:00
parent c31991cc24
commit 5441938725
3 changed files with 74 additions and 19 deletions

View File

@ -745,31 +745,40 @@ impl<'src> Parser<'src> {
} }
fn parse_if(&mut self, allow_assignment: bool, allow_return: bool) -> Result<(), ParseError> { fn parse_if(&mut self, allow_assignment: bool, allow_return: bool) -> Result<(), ParseError> {
let position = self.current_position;
self.advance()?; self.advance()?;
self.parse_expression()?; self.parse_expression()?;
let (second_load_boolean, second_position) = let (mut condition, condition_position) =
self.chunk.pop_instruction(self.current_position)?; self.chunk.pop_instruction(self.current_position)?;
let (first_load_boolean, first_position) =
self.chunk.pop_instruction(self.current_position)?;
let length_after_expression = self.chunk.len();
if let Operation::LoadBoolean = condition.operation() {
condition.set_second_argument_to_boolean(true);
}
self.emit_instruction(condition, condition_position);
self.emit_instruction(Instruction::jump(1, true), condition_position);
self.parse_block(allow_assignment, allow_return)?; self.parse_block(allow_assignment, allow_return)?;
let jump_position = self.current_position;
let jump_start = self.current_register; let jump_start = self.current_register;
let jump_index = self.chunk.len(); let jump_index = self.chunk.len();
if self.allow(TokenKind::Else)? { if self.allow(TokenKind::Else)? {
if self.allow(TokenKind::If)? { if self.allow(TokenKind::If)? {
self.parse_if(allow_assignment, allow_return)?; self.parse_if(allow_assignment, allow_return)?;
} else { }
if self.allow(TokenKind::LeftCurlyBrace)? {
self.parse_block(allow_assignment, allow_return)?; self.parse_block(allow_assignment, allow_return)?;
} }
}
if self.chunk.len() == length_after_expression { return Err(ParseError::ExpectedTokenMultiple {
self.emit_instruction(first_load_boolean, first_position); expected: &[TokenKind::If, TokenKind::LeftCurlyBrace],
self.emit_instruction(second_load_boolean, second_position); found: self.current_token.to_owned(),
position: self.current_position,
});
} }
if let [Some(Operation::LoadBoolean), Some(Operation::LoadBoolean)] = if let [Some(Operation::LoadBoolean), Some(Operation::LoadBoolean)] =
@ -785,15 +794,20 @@ impl<'src> Parser<'src> {
second_load_boolean.set_destination(first_load_boolean.destination()); second_load_boolean.set_destination(first_load_boolean.destination());
self.emit_instruction(second_load_boolean, second_position); self.emit_instruction(second_load_boolean, second_position);
} else if let Some(Operation::LoadBoolean) = self.chunk.get_last_operation() { } else if let Some(Operation::LoadBoolean) = self.chunk.get_last_operation() {
// Skip the jump if the last instruction was a LoadBoolean operation. A LoadBoolean can let (mut load_boolean, position) = self.chunk.pop_instruction(self.current_position)?;
// skip the following instruction, so a jump is unnecessary.
load_boolean.set_second_argument_to_boolean(true);
self.emit_instruction(load_boolean, position);
} else { } else {
let jump_end = self.current_register; let jump_end = self.current_register;
let jump_distance = (jump_end - jump_start).max(1); let jump_distance = (jump_end - jump_start).max(1);
let jump = Instruction::jump(jump_distance, true); let jump = Instruction::jump(jump_distance, true);
self.chunk if jump_distance > 1 {
.insert_instruction(jump_index, jump, self.current_position); self.chunk
.insert_instruction(jump_index, jump, jump_position);
}
} }
Ok(()) Ok(())
@ -1287,12 +1301,23 @@ impl AnnotatedError for ParseError {
Self::ExpectedTokenMultiple { Self::ExpectedTokenMultiple {
expected, found, .. expected, found, ..
} => { } => {
let expected = expected let mut details = String::from("Expected");
.iter()
.map(|kind| kind.to_string() + ", ")
.collect::<String>();
Some(format!("Expected one of {expected}, found \"{found}\"")) for (index, token) in expected.iter().enumerate() {
details.push_str(&format!(" \"{token}\""));
if index < expected.len() - 2 {
details.push_str(", ");
}
if index == expected.len() - 2 {
details.push_str(" or");
}
}
details.push_str(&format!(" found \"{found}\""));
Some(details)
} }
Self::InvalidAssignmentTarget { found, .. } => { Self::InvalidAssignmentTarget { found, .. } => {
Some(format!("Invalid assignment target, found \"{found}\"")) Some(format!("Invalid assignment target, found \"{found}\""))

View File

@ -230,6 +230,30 @@ fn equality_assignment_short() {
); );
} }
#[test]
fn if_expression() {
let source = "if 1 == 1 { 2 }";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
vec![
(
*Instruction::equal(true, 0, 1)
.set_first_argument_to_constant()
.set_second_argument_to_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_constant(0, 2), Span(12, 13)),
(Instruction::jump(1, true), Span(5, 7)),
],
vec![Value::integer(1), Value::integer(1), Value::integer(2)],
vec![]
)),
);
}
#[test] #[test]
fn if_else_expression() { fn if_else_expression() {
let source = "if 1 == 1 { 2 } else { 3 }"; let source = "if 1 == 1 { 2 } else { 3 }";
@ -246,7 +270,7 @@ fn if_else_expression() {
), ),
(Instruction::jump(1, true), Span(5, 7)), (Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_constant(0, 2), Span(12, 13)), (Instruction::load_constant(0, 2), Span(12, 13)),
(Instruction::jump(1, true), Span(26, 26)), (Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_constant(0, 3), Span(23, 24)), (Instruction::load_constant(0, 3), Span(23, 24)),
], ],
vec![ vec![

View File

@ -1,5 +1,11 @@
use dust_lang::*; use dust_lang::*;
#[test]
fn if_expression() {
assert_eq!(run("if true { 1 }"), Ok(Some(Value::integer(1))));
assert_eq!(run("if false { 1 }"), Ok(None));
}
#[test] #[test]
fn less_than() { fn less_than() {
assert_eq!(run("1 < 2"), Ok(Some(Value::boolean(true)))); assert_eq!(run("1 < 2"), Ok(Some(Value::boolean(true))));