From 413cb70731694fac161f37f7d3330278539e4aa7 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 18 Sep 2024 16:43:34 -0400 Subject: [PATCH] Begin implementing control flow with if/else expressions --- dust-lang/src/instruction.rs | 185 ++++++++++++++++++++++++++--------- dust-lang/src/operation.rs | 42 ++++++-- dust-lang/src/parser/mod.rs | 42 +++++++- dust-lang/src/vm.rs | 56 ++++++++++- 4 files changed, 264 insertions(+), 61 deletions(-) diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index a79c417..d87e190 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -150,6 +150,16 @@ impl Instruction { instruction } + pub fn equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction { + let mut instruction = Instruction(Operation::Equal as u32); + + instruction.set_destination(if comparison_boolean { 1 } else { 0 }); + instruction.set_first_argument(left_index); + instruction.set_second_argument(right_index); + + instruction + } + pub fn negate(to_register: u8, from_index: u8) -> Instruction { let mut instruction = Instruction(Operation::Negate as u32); @@ -168,34 +178,35 @@ impl Instruction { instruction } + pub fn jump(offset: u8, is_positive: bool) -> Instruction { + let mut instruction = Instruction(Operation::Jump as u32); + + instruction.set_first_argument(offset); + instruction.set_second_argument(if is_positive { 1 } else { 0 }); + + instruction + } + pub fn r#return() -> Instruction { Instruction(Operation::Return as u32) } - pub fn set_first_argument_to_constant(&mut self) -> &mut Self { - self.0 |= 0b1000_0000; - - self + pub fn operation(&self) -> Operation { + Operation::from((self.0 & 0b0000_0000_0011_1111) as u8) } - pub fn first_argument_is_constant(&self) -> bool { - self.0 & 0b1000_0000 != 0 - } - - pub fn set_second_argument_to_constant(&mut self) -> &mut Self { - self.0 |= 0b0100_0000; - - self - } - - pub fn second_argument_is_constant(&self) -> bool { - self.0 & 0b0100_0000 != 0 + pub fn set_operation(&mut self, operation: Operation) { + self.0 |= u8::from(operation) as u32; } pub fn destination(&self) -> u8 { (self.0 >> 24) as u8 } + pub fn destination_as_boolean(&self) -> bool { + (self.0 >> 24) != 0 + } + pub fn set_destination(&mut self, destination: u8) { self.0 &= 0x00FFFFFF; self.0 |= (destination as u32) << 24; @@ -205,6 +216,20 @@ impl Instruction { (self.0 >> 16) as u8 } + pub fn first_argument_is_constant(&self) -> bool { + self.0 & 0b1000_0000 != 0 + } + + pub fn first_argument_as_boolean(&self) -> bool { + self.first_argument() != 0 + } + + pub fn set_first_argument_to_constant(&mut self) -> &mut Self { + self.0 |= 0b1000_0000; + + self + } + pub fn set_first_argument(&mut self, argument: u8) { self.0 |= (argument as u32) << 16; } @@ -213,18 +238,24 @@ impl Instruction { (self.0 >> 8) as u8 } + pub fn second_argument_is_constant(&self) -> bool { + self.0 & 0b0100_0000 != 0 + } + + pub fn second_argument_as_boolean(&self) -> bool { + self.second_argument() != 0 + } + + pub fn set_second_argument_to_constant(&mut self) -> &mut Self { + self.0 |= 0b0100_0000; + + self + } + pub fn set_second_argument(&mut self, argument: u8) { self.0 |= (argument as u32) << 8; } - pub fn operation(&self) -> Operation { - Operation::from((self.0 & 0b0000_0000_0011_1111) as u8) - } - - pub fn set_operation(&mut self, operation: Operation) { - self.0 |= u8::from(operation) as u32; - } - pub fn disassemble(&self, chunk: &Chunk) -> String { let mut disassembled = format!("{:16} ", self.operation().to_string()); @@ -263,14 +294,14 @@ impl Instruction { } Operation::LoadBoolean => { let to_register = self.destination(); - let boolean = if self.first_argument() == 0 { - "false" + let boolean = self.first_argument_as_boolean(); + let skip_display = if self.second_argument_as_boolean() { + "IP++" } else { - "true" + "" }; - let skip = self.second_argument() != 0; - format!("R({to_register}) = {boolean}; if {skip} ip++",) + format!("R({to_register}) = {boolean} {skip_display}",) } Operation::LoadConstant => { let constant_index = self.first_argument(); @@ -389,6 +420,37 @@ impl Instruction { format!("R({destination}) = {first_argument} || {second_argument}",) } + Operation::Equal => { + let comparison_symbol = if self.destination_as_boolean() { + "==" + } else { + "!=" + }; + + let (first_argument, second_argument) = format_arguments(); + + format!("if {first_argument} {comparison_symbol} {second_argument} IP++",) + } + Operation::Less => { + let comparison_symbol = if self.destination_as_boolean() { + "<" + } else { + ">=" + }; + let (first_argument, second_argument) = format_arguments(); + + format!("if {first_argument} {comparison_symbol} {second_argument} IP++",) + } + Operation::LessEqual => { + let comparison_symbol = if self.destination_as_boolean() { + "<=" + } else { + ">" + }; + let (first_argument, second_argument) = format_arguments(); + + format!("if {first_argument} {comparison_symbol} {second_argument} IP++",) + } Operation::Negate => { let destination = self.destination(); let argument = if self.first_argument_is_constant() { @@ -409,6 +471,16 @@ impl Instruction { format!("R({destination}) = !{argument}") } + Operation::Jump => { + let offset = self.first_argument(); + let positive = self.second_argument() != 0; + + if positive { + format!("IP += {}", offset) + } else { + format!("IP -= {}", offset) + } + } Operation::Return => return None, }; @@ -453,6 +525,16 @@ mod tests { assert_eq!(instruction.second_argument(), 2); } + #[test] + fn load_boolean() { + let instruction = Instruction::load_boolean(4, true, true); + + assert_eq!(instruction.operation(), Operation::LoadBoolean); + assert_eq!(instruction.destination(), 4); + assert!(instruction.first_argument_as_boolean()); + assert!(instruction.second_argument_as_boolean()); + } + #[test] fn load_constant() { let mut instruction = Instruction::load_constant(0, 1); @@ -484,8 +566,6 @@ mod tests { fn add() { let mut instruction = Instruction::add(1, 1, 0); - instruction.set_operation(Operation::Add); - instruction.set_first_argument_to_constant(); assert_eq!(instruction.operation(), Operation::Add); @@ -499,8 +579,6 @@ mod tests { fn subtract() { let mut instruction = Instruction::subtract(0, 1, 2); - instruction.set_operation(Operation::Subtract); - instruction.set_first_argument_to_constant(); instruction.set_second_argument_to_constant(); @@ -516,8 +594,6 @@ mod tests { fn multiply() { let mut instruction = Instruction::multiply(0, 1, 2); - instruction.set_operation(Operation::Multiply); - instruction.set_first_argument_to_constant(); instruction.set_second_argument_to_constant(); @@ -533,8 +609,6 @@ mod tests { fn divide() { let mut instruction = Instruction::divide(0, 1, 2); - instruction.set_operation(Operation::Divide); - instruction.set_first_argument_to_constant(); instruction.set_second_argument_to_constant(); @@ -550,8 +624,6 @@ mod tests { fn and() { let mut instruction = Instruction::and(0, 1, 2); - instruction.set_operation(Operation::And); - instruction.set_first_argument_to_constant(); instruction.set_second_argument_to_constant(); @@ -567,8 +639,6 @@ mod tests { fn or() { let mut instruction = Instruction::or(0, 1, 2); - instruction.set_operation(Operation::Or); - instruction.set_first_argument_to_constant(); instruction.set_second_argument_to_constant(); @@ -580,12 +650,25 @@ mod tests { assert!(instruction.second_argument_is_constant()); } + #[test] + fn equal() { + let mut instruction = Instruction::equal(true, 1, 2); + + instruction.set_first_argument_to_constant(); + instruction.set_second_argument_to_constant(); + + assert_eq!(instruction.operation(), Operation::Equal); + assert!(instruction.destination_as_boolean()); + assert_eq!(instruction.first_argument(), 1); + assert_eq!(instruction.second_argument(), 2); + assert!(instruction.first_argument_is_constant()); + assert!(instruction.second_argument_is_constant()); + } + #[test] fn negate() { let mut instruction = Instruction::negate(0, 1); - instruction.set_operation(Operation::Negate); - instruction.set_first_argument_to_constant(); instruction.set_second_argument_to_constant(); @@ -600,8 +683,6 @@ mod tests { fn not() { let mut instruction = Instruction::not(0, 1); - instruction.set_operation(Operation::Not); - instruction.set_first_argument_to_constant(); instruction.set_second_argument_to_constant(); @@ -612,12 +693,22 @@ mod tests { assert!(instruction.second_argument_is_constant()); } + #[test] + fn jump() { + let mut instruction = Instruction::jump(4, true); + + instruction.set_first_argument_to_constant(); + instruction.set_second_argument_to_constant(); + + assert_eq!(instruction.operation(), Operation::Jump); + assert_eq!(instruction.first_argument(), 4); + assert!(instruction.first_argument_as_boolean()); + } + #[test] fn r#return() { let mut instruction = Instruction::r#return(); - instruction.set_operation(Operation::Return); - instruction.set_first_argument_to_constant(); instruction.set_second_argument_to_constant(); diff --git a/dust-lang/src/operation.rs b/dust-lang/src/operation.rs index 61af3a5..b71629f 100644 --- a/dust-lang/src/operation.rs +++ b/dust-lang/src/operation.rs @@ -2,22 +2,32 @@ use std::fmt::{self, Display, Formatter}; const MOVE: u8 = 0b0000_0000; const CLOSE: u8 = 0b000_0001; + const LOAD_BOOLEAN: u8 = 0b0000_0010; const LOAD_CONSTANT: u8 = 0b0000_0011; const LOAD_LIST: u8 = 0b0000_0100; + const DECLARE_LOCAL: u8 = 0b0000_0101; const GET_LOCAL: u8 = 0b0000_0110; const SET_LOCAL: u8 = 0b0000_0111; + const ADD: u8 = 0b0000_1000; const SUBTRACT: u8 = 0b0000_1001; const MULTIPLY: u8 = 0b0000_1010; -const MODULO: u8 = 0b0000_1011; -const AND: u8 = 0b0000_1100; -const OR: u8 = 0b0000_1101; -const DIVIDE: u8 = 0b0000_1110; -const NEGATE: u8 = 0b0000_1111; -const NOT: u8 = 0b0001_0000; -const RETURN: u8 = 0b0001_0001; +const DIVIDE: u8 = 0b0000_1011; +const MODULO: u8 = 0b0000_1100; +const AND: u8 = 0b0000_1101; +const OR: u8 = 0b0000_1110; + +const EQUAL: u8 = 0b0000_1111; +const LESS: u8 = 0b0001_0000; +const LESS_EQUAL: u8 = 0b0001_0001; + +const NEGATE: u8 = 0b0001_0010; +const NOT: u8 = 0b0001_0011; + +const JUMP: u8 = 0b0001_0100; +const RETURN: u8 = 0b0001_0101; #[derive(Clone, Copy, Debug, PartialEq)] pub enum Operation { @@ -44,11 +54,17 @@ pub enum Operation { And = AND as isize, Or = OR as isize, + // Relational operations + Equal = EQUAL as isize, + Less = LESS as isize, + LessEqual = LESS_EQUAL as isize, + // Unary operations Negate = NEGATE as isize, Not = NOT as isize, // Control flow + Jump = JUMP as isize, Return = RETURN as isize, } @@ -85,8 +101,12 @@ impl From for Operation { MODULO => Operation::Modulo, AND => Operation::And, OR => Operation::Or, + EQUAL => Operation::Equal, + LESS => Operation::Less, + LESS_EQUAL => Operation::LessEqual, NEGATE => Operation::Negate, NOT => Operation::Not, + JUMP => Operation::Jump, RETURN => Operation::Return, _ => { if cfg!(test) { @@ -117,8 +137,12 @@ impl From for u8 { Operation::Modulo => MODULO, Operation::And => AND, Operation::Or => OR, + Operation::Equal => EQUAL, + Operation::Less => LESS, + Operation::LessEqual => LESS_EQUAL, Operation::Negate => NEGATE, Operation::Not => NOT, + Operation::Jump => JUMP, Operation::Return => RETURN, } } @@ -142,8 +166,12 @@ impl Display for Operation { Operation::Modulo => write!(f, "MODULO"), Operation::And => write!(f, "AND"), Operation::Or => write!(f, "OR"), + Operation::Equal => write!(f, "EQUAL"), + Operation::Less => write!(f, "LESS"), + Operation::LessEqual => write!(f, "LESS_EQUAL"), Operation::Negate => write!(f, "NEGATE"), Operation::Not => write!(f, "NOT"), + Operation::Jump => write!(f, "JUMP"), Operation::Return => write!(f, "RETURN"), } } diff --git a/dust-lang/src/parser/mod.rs b/dust-lang/src/parser/mod.rs index 28c4d0d..5581e1c 100644 --- a/dust-lang/src/parser/mod.rs +++ b/dust-lang/src/parser/mod.rs @@ -153,6 +153,7 @@ impl<'src> Parser<'src> { Instruction::load_boolean(self.current_register, boolean, false), self.previous_position, ); + self.increment_register()?; } Ok(()) @@ -382,6 +383,7 @@ impl<'src> Parser<'src> { TokenKind::Percent => Instruction::modulo(self.current_register, left, right), TokenKind::DoubleAmpersand => Instruction::and(self.current_register, left, right), TokenKind::DoublePipe => Instruction::or(self.current_register, left, right), + TokenKind::DoubleEqual => Instruction::equal(true, left, right), _ => { return Err(ParseError::ExpectedTokenMultiple { expected: &[ @@ -392,6 +394,7 @@ impl<'src> Parser<'src> { TokenKind::Percent, TokenKind::DoubleAmpersand, TokenKind::DoublePipe, + TokenKind::DoubleEqual, ], found: operator.to_owned(), position: operator_position, @@ -418,6 +421,10 @@ impl<'src> Parser<'src> { self.emit_instruction(instruction, operator_position); self.increment_register()?; + if let TokenKind::DoubleEqual = operator.kind() { + self.emit_instruction(Instruction::jump(2, true), operator_position); + } + Ok(()) } @@ -521,14 +528,17 @@ impl<'src> Parser<'src> { ends_with_semicolon = true; } + let end = self.current_position.1; + if ends_with_semicolon { - let end = self.current_position.1; let end_register = self.current_register; self.emit_instruction( Instruction::close(start_register, end_register), Span(start, end), ); + } else { + self.emit_instruction(Instruction::r#return(), Span(start, end)); } Ok(()) @@ -572,6 +582,18 @@ impl<'src> Parser<'src> { Ok(()) } + fn parse_if(&mut self, _allow_assignment: bool) -> Result<(), ParseError> { + self.advance()?; + self.parse_expression()?; + self.parse_block(false)?; + + if self.allow(TokenKind::Else)? { + self.parse_block(false)?; + } + + Ok(()) + } + fn parse_expression(&mut self) -> Result<(), ParseError> { self.parse(Precedence::None) } @@ -784,9 +806,17 @@ impl From<&TokenKind> for ParseRule<'_> { TokenKind::Async => todo!(), TokenKind::Bool => todo!(), TokenKind::Break => todo!(), - TokenKind::Else => todo!(), + TokenKind::Else => ParseRule { + prefix: None, + infix: None, + precedence: Precedence::None, + }, TokenKind::FloatKeyword => todo!(), - TokenKind::If => todo!(), + TokenKind::If => ParseRule { + prefix: Some(Parser::parse_if), + infix: None, + precedence: Precedence::None, + }, TokenKind::Int => todo!(), TokenKind::Let => ParseRule { prefix: Some(Parser::parse_let_statement), @@ -816,7 +846,11 @@ impl From<&TokenKind> for ParseRule<'_> { precedence: Precedence::LogicalAnd, }, TokenKind::DoubleDot => todo!(), - TokenKind::DoubleEqual => todo!(), + TokenKind::DoubleEqual => ParseRule { + prefix: None, + infix: Some(Parser::parse_binary), + precedence: Precedence::Equality, + }, TokenKind::DoublePipe => ParseRule { prefix: None, infix: Some(Parser::parse_binary), diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 953fa29..48cb072 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -77,13 +77,13 @@ impl Vm { } Operation::LoadBoolean => { let to_register = instruction.destination(); - let boolean = instruction.first_argument() != 0; - let skip = instruction.second_argument() != 0; + let boolean = instruction.first_argument_as_boolean(); + let skip = instruction.second_argument_as_boolean(); let value = Value::boolean(boolean); self.insert(value, to_register, position)?; - if skip { + if boolean && skip { self.ip += 1; } } @@ -201,6 +201,45 @@ impl Vm { self.insert(or, instruction.destination(), position)?; } + Operation::Equal => { + let (left, right) = take_constants_or_clone(self, instruction, position)?; + let equal = left + .equal(&right) + .map_err(|error| VmError::Value { error, position })?; + let compare_to = instruction.destination_as_boolean(); + + if let Some(boolean) = equal.as_boolean() { + if boolean == compare_to { + self.ip += 1; + } + } + } + Operation::Less => { + let (left, right) = take_constants_or_clone(self, instruction, position)?; + let less = left + .less_than(&right) + .map_err(|error| VmError::Value { error, position })?; + let compare_to = instruction.destination() != 0; + + if let Some(boolean) = less.as_boolean() { + if boolean != compare_to { + self.ip += 1; + } + } + } + Operation::LessEqual => { + let (left, right) = take_constants_or_clone(self, instruction, position)?; + let less_equal = left + .less_than_or_equal(&right) + .map_err(|error| VmError::Value { error, position })?; + let compare_to = instruction.destination() != 0; + + if let Some(boolean) = less_equal.as_boolean() { + if boolean != compare_to { + self.ip += 1; + } + } + } Operation::Negate => { let value = if instruction.first_argument_is_constant() { self.chunk @@ -227,6 +266,17 @@ impl Vm { self.insert(not, instruction.destination(), position)?; } + Operation::Jump => { + let offset = instruction.first_argument(); + let is_positive = instruction.second_argument_as_boolean(); + let new_ip = if is_positive { + self.ip + offset as usize + } else { + self.ip - offset as usize + }; + + self.ip = new_ip; + } Operation::Return => { let value = self.pop(position)?;