Add better parser error
This commit is contained in:
parent
c31991cc24
commit
5441938725
@ -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 {
|
|
||||||
self.parse_block(allow_assignment, allow_return)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.chunk.len() == length_after_expression {
|
if self.allow(TokenKind::LeftCurlyBrace)? {
|
||||||
self.emit_instruction(first_load_boolean, first_position);
|
self.parse_block(allow_assignment, allow_return)?;
|
||||||
self.emit_instruction(second_load_boolean, second_position);
|
}
|
||||||
|
|
||||||
|
return Err(ParseError::ExpectedTokenMultiple {
|
||||||
|
expected: &[TokenKind::If, TokenKind::LeftCurlyBrace],
|
||||||
|
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);
|
||||||
|
|
||||||
|
if jump_distance > 1 {
|
||||||
self.chunk
|
self.chunk
|
||||||
.insert_instruction(jump_index, jump, self.current_position);
|
.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}\""))
|
||||||
|
@ -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![
|
||||||
|
@ -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))));
|
||||||
|
Loading…
Reference in New Issue
Block a user