diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index c7ea1bf..bc86248 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -211,10 +211,11 @@ impl Instruction { instruction } - pub fn jump(jump_to: u8) -> Instruction { + pub fn jump(jump_offset: u8, is_positive: bool) -> Instruction { let mut instruction = Instruction(Operation::Jump as u32); - instruction.set_b(jump_to); + instruction.set_b(jump_offset); + instruction.set_c_to_boolean(is_positive); instruction } @@ -557,9 +558,14 @@ impl Instruction { format!("R{to_register} = !{argument}") } Operation::Jump => { - let jump_to = self.b(); + let jump_distance = self.b(); + let is_positive = self.c_as_boolean(); - format!("JUMP TO {jump_to}") + if is_positive { + format!("JUMP +{jump_distance}") + } else { + format!("JUMP -{jump_distance}") + } } Operation::Call => { let to_register = self.a(); @@ -839,10 +845,12 @@ mod tests { #[test] fn jump() { - let instruction = Instruction::jump(4); + let instruction = Instruction::jump(4, true); assert_eq!(instruction.operation(), Operation::Jump); + assert_eq!(instruction.b(), 4); + assert!(instruction.c_as_boolean()); } #[test] diff --git a/dust-lang/src/native_function.rs b/dust-lang/src/native_function.rs index 69d04b6..bdc946f 100644 --- a/dust-lang/src/native_function.rs +++ b/dust-lang/src/native_function.rs @@ -555,18 +555,37 @@ pub enum NativeFunctionError { impl AnnotatedError for NativeFunctionError { fn title() -> &'static str { - todo!() + "Native Function Error" } fn description(&self) -> &'static str { - todo!() + match self { + NativeFunctionError::ExpectedArgumentCount { .. } => { + "Expected a different number of arguments" + } + NativeFunctionError::Panic { .. } => "Explicit panic", + NativeFunctionError::Parse { .. } => "Failed to parse value", + NativeFunctionError::Io { .. } => "I/O error", + } } fn details(&self) -> Option { - todo!() + match self { + NativeFunctionError::ExpectedArgumentCount { + expected, found, .. + } => Some(format!("Expected {} arguments, found {}", expected, found)), + NativeFunctionError::Panic { message, .. } => message.clone(), + NativeFunctionError::Parse { error, .. } => Some(format!("{}", error)), + NativeFunctionError::Io { error, .. } => Some(format!("{}", error)), + } } fn position(&self) -> Span { - todo!() + match self { + NativeFunctionError::ExpectedArgumentCount { position, .. } => *position, + NativeFunctionError::Panic { position, .. } => *position, + NativeFunctionError::Parse { position, .. } => *position, + NativeFunctionError::Io { position, .. } => *position, + } } } diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 15e2a6b..044e251 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -228,6 +228,19 @@ impl<'src> Parser<'src> { }) } + fn get_last_jumpable_mut(&mut self) -> Option<&mut Instruction> { + self.chunk + .instructions_mut() + .iter_mut() + .find_map(|(instruction, _)| { + if let Operation::LoadBoolean | Operation::LoadConstant = instruction.operation() { + Some(instruction) + } else { + None + } + }) + } + fn emit_constant(&mut self, value: Value, position: Span) -> Result<(), ParseError> { let constant_index = self.chunk.push_constant(value, position)?; let register = self.next_register(); @@ -651,7 +664,6 @@ impl<'src> Parser<'src> { Token::LessEqual => Instruction::less_equal(true, left, right), Token::Greater => Instruction::less_equal(false, left, right), Token::GreaterEqual => Instruction::less(false, left, right), - _ => { return Err(ParseError::ExpectedTokenMultiple { expected: &[ @@ -688,9 +700,7 @@ impl<'src> Parser<'src> { self.emit_instruction(instruction, operator_position); - let jump_to = (self.chunk.len() + 2) as u8; - - self.emit_instruction(Instruction::jump(jump_to), operator_position); + self.emit_instruction(Instruction::jump(1, true), operator_position); self.emit_instruction( Instruction::load_boolean(register, true, true), operator_position, @@ -706,6 +716,7 @@ impl<'src> Parser<'src> { } fn parse_logical_binary(&mut self) -> Result<(), ParseError> { + let start_length = self.chunk.len(); let (left_instruction, left_position) = self.chunk .instructions_mut() @@ -719,7 +730,7 @@ impl<'src> Parser<'src> { let operator_position = self.current_position; let rule = ParseRule::from(&operator); - let instruction = match operator { + let test_instruction = match operator { Token::DoubleAmpersand => Instruction::test(left_instruction.a(), false), Token::DoublePipe => Instruction::test(left_instruction.a(), true), _ => { @@ -733,11 +744,11 @@ impl<'src> Parser<'src> { self.advance()?; self.emit_instruction(left_instruction, left_position); - self.emit_instruction(instruction, operator_position); + self.emit_instruction(test_instruction, operator_position); - let jump_to = (self.chunk.len() + 2) as u8; + let jump_distance = (self.chunk.len() - start_length) as u8; - self.emit_instruction(Instruction::jump(jump_to), operator_position); + self.emit_instruction(Instruction::jump(jump_distance, true), operator_position); self.parse_sub_expression(&rule.precedence)?; self.current_is_expression = true; @@ -956,7 +967,16 @@ impl<'src> Parser<'src> { }; if let Token::LeftCurlyBrace = self.current_token { + let block_start = self.chunk.len(); + self.parse_block(block_allowed)?; + + let block_end = self.chunk.len(); + let jump_distance = (block_end - block_start) as u8; + + if let Some(jump) = self.get_last_jump_mut() { + jump.set_b(jump_distance); + } } else { return Err(ParseError::ExpectedToken { expected: TokenKind::LeftCurlyBrace, @@ -965,14 +985,57 @@ impl<'src> Parser<'src> { }); } - let if_block_end = self.chunk.len(); - - if let Some(if_jump) = self.get_last_jump_mut() { - if_jump.set_b(if_block_end as u8); - } + let if_block_is_expression = self + .chunk + .instructions() + .iter() + .find_map(|(instruction, _)| { + if !matches!(instruction.operation(), Operation::Jump) { + Some(true) + } else { + None + } + }) + .unwrap_or(false); if let Token::Else = self.current_token { + let else_start = self.chunk.len(); + self.parse_else(allowed, block_allowed)?; + + let else_end = self.chunk.len(); + let jump_distance = (else_end - else_start) as u8; + self.current_is_expression = if_block_is_expression + && self + .chunk + .instructions() + .iter() + .find_map(|(instruction, _)| { + if !matches!(instruction.operation(), Operation::Jump) { + Some(true) + } else { + None + } + }) + .unwrap_or(false); + + if jump_distance == 1 { + if let Some(skippable) = self.get_last_jumpable_mut() { + skippable.set_c_to_boolean(true); + } else { + self.chunk.insert_instruction( + else_start, + Instruction::jump(jump_distance, true), + self.current_position, + )?; + } + } else { + self.chunk.insert_instruction( + else_start, + Instruction::jump(jump_distance, true), + self.current_position, + )?; + } } else { self.current_is_expression = false; } @@ -987,21 +1050,17 @@ impl<'src> Parser<'src> { if let Token::If = self.current_token { self.parse_if(allowed)?; - - let if_block_end = self.chunk.len() as u8; - - if let Some(if_jump) = self.get_last_jump_mut() { - if_jump.set_b(if_block_end + 1); - } } else if let Token::LeftCurlyBrace = self.current_token { self.parse_block(block_allowed)?; let else_end = self.chunk.len(); if else_end - if_block_end > 1 { + let jump_distance = (else_end - if_block_end) as u8; + self.chunk.insert_instruction( if_block_end, - Instruction::jump((else_end + 1) as u8), + Instruction::jump(jump_distance, true), self.current_position, )?; } @@ -1051,13 +1110,13 @@ impl<'src> Parser<'src> { implicit_return: false, })?; - let jump_end = self.chunk.len() as u8; - if let Some(jump) = self.get_last_jump_mut() { - jump.set_b(jump_end + 1); + jump.set_b(jump.b() + 1); } - let jump_back = Instruction::jump(jump_start); + let jump_end = self.chunk.len() as u8; + let jump_distance = jump_end - jump_start; + let jump_back = Instruction::jump(jump_distance, false); self.emit_instruction(jump_back, self.current_position); self.optimize_statement(); diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index dab2bbc..c8f1734 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -81,13 +81,13 @@ impl Vm { Operation::LoadBoolean => { let to_register = instruction.a(); let boolean = instruction.b_as_boolean(); - let skip = instruction.c_as_boolean(); + let jump = instruction.c_as_boolean(); let value = Value::boolean(boolean); self.set(to_register, value, position)?; - if skip { - self.ip += 1; + if jump { + self.jump_to_ip(self.ip + 1); } } Operation::LoadConstant => { @@ -98,7 +98,7 @@ impl Vm { self.set_constant(to_register, from_constant, position)?; if jump { - self.ip += 1; + self.jump_to_ip(self.ip + 1); } } Operation::LoadList => { @@ -247,7 +247,7 @@ impl Vm { self.ip - jump_distance as usize }; - self.ip = new_ip; + self.jump_to_ip(new_ip); } } Operation::Less => { @@ -283,7 +283,7 @@ impl Vm { self.ip - jump_distance as usize }; - self.ip = new_ip; + self.jump_to_ip(new_ip); } } Operation::LessEqual => { @@ -320,7 +320,7 @@ impl Vm { self.ip - jump_distance as usize }; - self.ip = new_ip; + self.jump_to_ip(new_ip); } } Operation::Negate => { @@ -348,9 +348,15 @@ impl Vm { self.set(instruction.a(), not, position)?; } Operation::Jump => { - let jump_to = instruction.b(); + let jump_distance = instruction.b(); + let is_positive = instruction.c_as_boolean(); + let new_ip = if is_positive { + self.ip + jump_distance as usize + } else { + self.ip - jump_distance as usize + }; - self.ip = jump_to as usize; + self.jump_to_ip(new_ip); } Operation::Call => { let to_register = instruction.a(); @@ -418,6 +424,24 @@ impl Vm { Ok(None) } + fn jump_to_ip(&mut self, new_ip: usize) { + let final_index = self.chunk.len() - 1; + + if new_ip > final_index { + let last_operation = self + .chunk + .instructions() + .last() + .map(|(instruction, _)| instruction.operation()); + + if let Some(Operation::Return) = last_operation { + self.ip = final_index; + } + } else { + self.ip = new_ip; + } + } + fn set(&mut self, to_register: u8, value: Value, position: Span) -> Result<(), VmError> { let length = self.stack.len(); self.last_assigned_register = Some(to_register); diff --git a/dust-lang/tests/control_flow.rs b/dust-lang/tests/control_flow.rs index dfac489..60aa85b 100644 --- a/dust-lang/tests/control_flow.rs +++ b/dust-lang/tests/control_flow.rs @@ -15,7 +15,7 @@ fn equality_assignment_long() { .set_c_is_constant(), Span(13, 15) ), - (Instruction::jump(3), Span(13, 15)), + (Instruction::jump(1, true), Span(13, 15)), (Instruction::load_boolean(0, true, true), Span(20, 24)), (Instruction::load_boolean(0, false, false), Span(34, 39)), (Instruction::define_local(0, 0, false), Span(4, 5)), @@ -45,7 +45,7 @@ fn equality_assignment_short() { .set_c_is_constant(), Span(10, 12) ), - (Instruction::jump(3), Span(10, 12)), + (Instruction::jump(1, true), Span(10, 12)), (Instruction::load_boolean(0, true, true), Span(10, 12)), (Instruction::load_boolean(0, false, false), Span(10, 12)), (Instruction::define_local(0, 0, false), Span(4, 5)), @@ -80,12 +80,13 @@ fn if_else_complex() { .set_c_is_constant(), Span(14, 16) ), - (Instruction::jump(7), Span(14, 16)), + (Instruction::jump(5, true), Span(14, 16)), (Instruction::load_constant(0, 2, false), Span(33, 34)), (Instruction::load_constant(1, 3, false), Span(36, 37)), (Instruction::load_constant(2, 4, false), Span(39, 40)), (Instruction::load_constant(3, 5, false), Span(42, 43)), - (Instruction::jump(11), Span(95, 95)), + (Instruction::jump(5, true), Span(95, 95)), + (Instruction::jump(4, true), Span(95, 95)), (Instruction::load_constant(4, 6, false), Span(74, 75)), (Instruction::load_constant(5, 7, false), Span(77, 78)), (Instruction::load_constant(6, 8, false), Span(80, 81)), @@ -137,27 +138,27 @@ fn if_else_complex() { // .set_c_is_constant(), // Span(14, 16) // ), -// (Instruction::jump(7), Span(14, 16)), +// (Instruction::jump(7, true), Span(14, 16)), // ( // *Instruction::equal(true, 0, 2) // .set_b_is_constant() // .set_c_is_constant(), // Span(38, 41) // ), -// (Instruction::jump(3), Span(38, 41)), +// (Instruction::jump(3, true), Span(38, 41)), // (Instruction::load_constant(0, 1, false), Span(61, 62)), -// (Instruction::jump(11), Span(95, 95)), +// (Instruction::jump(1, true1), Span(95, 95)), // ( // *Instruction::equal(true, 0, 3) // .set_b_is_constant() // .set_c_is_constant(), // Span(77, 79) // ), -// (Instruction::jump(3), Span(77, 79)), +// (Instruction::jump(3, true), Span(77, 79)), // (Instruction::load_constant(0, 2, false), Span(94, 95)), -// (Instruction::jump(11), Span(95, 95)), +// (Instruction::jump(1, true1), Span(95, 95)), // (Instruction::load_constant(0, 3, false), Span(114, 115)), -// (Instruction::jump(11), Span(95, 95)), +// (Instruction::jump(1, true1), Span(95, 95)), // (Instruction::load_constant(0, 4, false), Span(134, 135)), // (Instruction::r#return(true), Span(146, 146)), // ], @@ -194,12 +195,12 @@ fn if_else_false() { .set_c_is_constant(), Span(5, 7) ), - (Instruction::jump(3), Span(5, 7)), + (Instruction::jump(1, true), Span(5, 7)), ( Instruction::call_native(0, NativeFunction::Panic, 0), Span(12, 19) ), - (Instruction::load_constant(0, 2, false), Span(29, 31)), + (Instruction::load_constant(0, 2, true), Span(29, 31)), (Instruction::r#return(true), Span(33, 33)), ], vec![Value::integer(1), Value::integer(2), Value::integer(42)], @@ -225,19 +226,20 @@ fn if_else_true() { .set_c_is_constant(), Span(5, 7) ), - (Instruction::jump(3), Span(5, 7)), - (Instruction::load_constant(0, 2, false), Span(12, 14)), + (Instruction::jump(1, true), Span(5, 7)), + (Instruction::load_constant(0, 2, true), Span(12, 14)), ( Instruction::call_native(1, NativeFunction::Panic, 0), Span(24, 31) ), + (Instruction::r#return(true), Span(33, 33)) ], vec![Value::integer(1), Value::integer(1), Value::integer(42)], vec![] )), ); - assert_eq!(run(source), Ok(None)); + assert_eq!(run(source), Ok(Some(Value::integer(42)))); } #[test] @@ -255,7 +257,7 @@ fn if_expression_false() { .set_c_is_constant(), Span(5, 7) ), - (Instruction::jump(3), Span(5, 7)), + (Instruction::jump(1, true), Span(5, 7)), (Instruction::load_constant(0, 2, false), Span(12, 13)), ], vec![Value::integer(1), Value::integer(2), Value::integer(2)], @@ -281,7 +283,7 @@ fn if_expression_true() { .set_c_is_constant(), Span(5, 7) ), - (Instruction::jump(3), Span(5, 7)), + (Instruction::jump(1, true), Span(5, 7)), (Instruction::load_constant(0, 2, false), Span(12, 13)), ], vec![Value::integer(1), Value::integer(1), Value::integer(2)], diff --git a/dust-lang/tests/expressions.rs b/dust-lang/tests/expressions.rs index 3434f42..dc89588 100644 --- a/dust-lang/tests/expressions.rs +++ b/dust-lang/tests/expressions.rs @@ -59,7 +59,7 @@ fn and() { vec![ (Instruction::load_boolean(0, true, false), Span(0, 4)), (Instruction::test(0, false), Span(5, 7)), - (Instruction::jump(4), Span(5, 7)), + (Instruction::jump(4, true), Span(5, 7)), (Instruction::load_boolean(1, false, false), Span(8, 13)), (Instruction::r#return(true), Span(13, 13)), ], @@ -258,7 +258,7 @@ fn equal() { .set_c_is_constant(), Span(2, 4) ), - (Instruction::jump(3), Span(2, 4)), + (Instruction::jump(3, true), Span(2, 4)), (Instruction::load_boolean(0, true, true), Span(2, 4)), (Instruction::load_boolean(0, false, false), Span(2, 4)), (Instruction::r#return(true), Span(6, 6)), @@ -419,7 +419,7 @@ fn greater() { .set_c_is_constant(), Span(2, 3) ), - (Instruction::jump(3), Span(2, 3)), + (Instruction::jump(3, true), Span(2, 3)), (Instruction::load_boolean(0, true, true), Span(2, 3)), (Instruction::load_boolean(0, false, false), Span(2, 3)), (Instruction::r#return(true), Span(5, 5)), @@ -447,7 +447,7 @@ fn greater_than_or_equal() { .set_c_is_constant(), Span(2, 4) ), - (Instruction::jump(3), Span(2, 4)), + (Instruction::jump(3, true), Span(2, 4)), (Instruction::load_boolean(0, true, true), Span(2, 4)), (Instruction::load_boolean(0, false, false), Span(2, 4)), (Instruction::r#return(true), Span(6, 6)), @@ -475,7 +475,7 @@ fn less_than() { .set_c_is_constant(), Span(2, 3) ), - (Instruction::jump(3), Span(2, 3)), + (Instruction::jump(3, true), Span(2, 3)), (Instruction::load_boolean(0, true, true), Span(2, 3)), (Instruction::load_boolean(0, false, false), Span(2, 3)), (Instruction::r#return(true), Span(5, 5)), @@ -503,7 +503,7 @@ fn less_than_or_equal() { .set_c_is_constant(), Span(2, 4) ), - (Instruction::jump(3), Span(2, 4)), + (Instruction::jump(3, true), Span(2, 4)), (Instruction::load_boolean(0, true, true), Span(2, 4)), (Instruction::load_boolean(0, false, false), Span(2, 4)), (Instruction::r#return(true), Span(6, 6)), @@ -762,7 +762,7 @@ fn not_equal() { .set_c_is_constant(), Span(2, 4) ), - (Instruction::jump(3), Span(2, 4)), + (Instruction::jump(3, true), Span(2, 4)), (Instruction::load_boolean(0, true, true), Span(2, 4)), (Instruction::load_boolean(0, false, false), Span(2, 4)), (Instruction::r#return(true), Span(6, 6)), @@ -786,7 +786,7 @@ fn or() { vec![ (Instruction::load_boolean(0, true, false), Span(0, 4)), (Instruction::test(0, true), Span(5, 7)), - (Instruction::jump(4), Span(5, 7)), + (Instruction::jump(4, true), Span(5, 7)), (Instruction::load_boolean(1, false, false), Span(8, 13)), (Instruction::r#return(true), Span(13, 13)), ], @@ -917,7 +917,7 @@ fn variable_and() { (Instruction::define_local(1, 1, false), Span(18, 19)), (Instruction::get_local(2, 0), Span(29, 30)), (Instruction::test(2, false), Span(31, 33)), - (Instruction::jump(8), Span(31, 33)), + (Instruction::jump(8, true), Span(31, 33)), (Instruction::get_local(3, 1), Span(34, 35)), (Instruction::r#return(true), Span(35, 35)), ], @@ -947,9 +947,9 @@ fn r#while() { *Instruction::less(true, 0, 1).set_c_is_constant(), Span(23, 24) ), - (Instruction::jump(7), Span(23, 24)), + (Instruction::jump(7, true), Span(23, 24)), (*Instruction::add(0, 0, 2).set_c_is_constant(), Span(39, 40)), - (Instruction::jump(2), Span(41, 42)), + (Instruction::jump(2, true), Span(41, 42)), (Instruction::get_local(1, 0), Span(41, 42)), (Instruction::r#return(true), Span(42, 42)), ],