From 6ca96bc1dc64b6db900fadf59d2b8e7389f319c1 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 17 Sep 2024 17:51:39 -0400 Subject: [PATCH] Add &&, || and ! to the language; Add tests --- dust-lang/src/instruction.rs | 181 +++++++++++++++++++++++++++++++++- dust-lang/src/parser/mod.rs | 28 ++++-- dust-lang/src/parser/tests.rs | 70 ++++++++++++- dust-lang/src/token.rs | 2 +- dust-lang/src/vm.rs | 63 ++++++++++++ dust-lang/tests/operations.rs | 5 + 6 files changed, 329 insertions(+), 20 deletions(-) diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index 7fdb063..f5924fe 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -99,6 +99,36 @@ impl Instruction { instruction } + pub fn modulo(to_register: u8, left_index: u8, right_index: u8) -> Instruction { + let mut instruction = Instruction(Operation::Modulo as u32); + + instruction.set_destination(to_register); + instruction.set_first_argument(left_index); + instruction.set_second_argument(right_index); + + instruction + } + + pub fn and(to_register: u8, left_index: u8, right_index: u8) -> Instruction { + let mut instruction = Instruction(Operation::And as u32); + + instruction.set_destination(to_register); + instruction.set_first_argument(left_index); + instruction.set_second_argument(right_index); + + instruction + } + + pub fn or(to_register: u8, left_index: u8, right_index: u8) -> Instruction { + let mut instruction = Instruction(Operation::Or as u32); + + instruction.set_destination(to_register); + 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); @@ -108,6 +138,15 @@ impl Instruction { instruction } + pub fn not(to_register: u8, from_index: u8) -> Instruction { + let mut instruction = Instruction(Operation::Not as u32); + + instruction.set_destination(to_register); + instruction.set_first_argument(from_index); + + instruction + } + pub fn r#return() -> Instruction { Instruction(Operation::Return as u32) } @@ -306,6 +345,51 @@ impl Instruction { format!("R({destination}) = {first_argument} / {second_argument}",) } + Operation::Modulo => { + let destination = self.destination(); + let first_argument = if self.first_argument_is_constant() { + format!("C({})", self.first_argument()) + } else { + format!("R({})", self.first_argument()) + }; + let second_argument = if self.second_argument_is_constant() { + format!("C({})", self.second_argument()) + } else { + format!("R({})", self.second_argument()) + }; + + format!("R({destination}) = {first_argument} % {second_argument}",) + } + Operation::And => { + let destination = self.destination(); + let first_argument = if self.first_argument_is_constant() { + format!("C({})", self.first_argument()) + } else { + format!("R({})", self.first_argument()) + }; + let second_argument = if self.second_argument_is_constant() { + format!("C({})", self.second_argument()) + } else { + format!("R({})", self.second_argument()) + }; + + format!("R({destination}) = {first_argument} && {second_argument}",) + } + Operation::Or => { + let destination = self.destination(); + let first_argument = if self.first_argument_is_constant() { + format!("C({})", self.first_argument()) + } else { + format!("R({})", self.first_argument()) + }; + let second_argument = if self.second_argument_is_constant() { + format!("C({})", self.second_argument()) + } else { + format!("R({})", self.second_argument()) + }; + + format!("R({destination}) = {first_argument} || {second_argument}",) + } Operation::Negate => { let destination = self.destination(); let argument = if self.first_argument_is_constant() { @@ -316,6 +400,16 @@ impl Instruction { format!("R({destination}) = -{argument}") } + Operation::Not => { + let destination = self.destination(); + let argument = if self.first_argument_is_constant() { + format!("C({})", self.first_argument()) + } else { + format!("R({})", self.first_argument()) + }; + + format!("R({destination}) = !{argument}") + } Operation::Return => return None, }; @@ -342,9 +436,13 @@ const SET_LOCAL: u8 = 0b0000_0101; const ADD: u8 = 0b0000_0110; const SUBTRACT: u8 = 0b0000_0111; const MULTIPLY: u8 = 0b0000_1000; -const DIVIDE: u8 = 0b0000_1001; -const NEGATE: u8 = 0b0000_1010; -const RETURN: u8 = 0b0000_1011; +const MODULO: u8 = 0b0000_1001; +const AND: u8 = 0b0000_1010; +const OR: u8 = 0b0000_1011; +const DIVIDE: u8 = 0b0000_1100; +const NEGATE: u8 = 0b0000_1101; +const NOT: u8 = 0b0000_1110; +const RETURN: u8 = 0b0000_1111; #[derive(Clone, Copy, Debug, PartialEq)] pub enum Operation { @@ -365,9 +463,13 @@ pub enum Operation { Subtract = SUBTRACT as isize, Multiply = MULTIPLY as isize, Divide = DIVIDE as isize, + Modulo = MODULO as isize, + And = AND as isize, + Or = OR as isize, // Unary operations Negate = NEGATE as isize, + Not = NOT as isize, // Control flow Return = RETURN as isize, @@ -377,7 +479,13 @@ impl Operation { pub fn is_binary(&self) -> bool { matches!( self, - Operation::Add | Operation::Subtract | Operation::Multiply | Operation::Divide + Operation::Add + | Operation::Subtract + | Operation::Multiply + | Operation::Divide + | Operation::Modulo + | Operation::And + | Operation::Or ) } } @@ -395,8 +503,13 @@ impl From for Operation { SUBTRACT => Operation::Subtract, MULTIPLY => Operation::Multiply, DIVIDE => Operation::Divide, + MODULO => Operation::Modulo, + AND => Operation::And, + OR => Operation::Or, NEGATE => Operation::Negate, - _ => Operation::Return, + NOT => Operation::Not, + RETURN => Operation::Return, + _ => panic!("Invalid operation byte: {}", byte), } } } @@ -414,7 +527,11 @@ impl From for u8 { Operation::Subtract => SUBTRACT, Operation::Multiply => MULTIPLY, Operation::Divide => DIVIDE, + Operation::Modulo => MODULO, + Operation::And => AND, + Operation::Or => OR, Operation::Negate => NEGATE, + Operation::Not => NOT, Operation::Return => RETURN, } } @@ -433,7 +550,11 @@ impl Display for Operation { Operation::Subtract => write!(f, "SUBTRACT"), Operation::Multiply => write!(f, "MULTIPLY"), Operation::Divide => write!(f, "DIVIDE"), + Operation::Modulo => write!(f, "MODULO"), + Operation::And => write!(f, "AND"), + Operation::Or => write!(f, "OR"), Operation::Negate => write!(f, "NEGATE"), + Operation::Not => write!(f, "NOT"), Operation::Return => write!(f, "RETURN"), } } @@ -564,6 +685,40 @@ mod tests { assert!(instruction.second_argument_is_constant()); } + #[test] + 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(); + + assert_eq!(instruction.operation(), Operation::And); + assert_eq!(instruction.destination(), 0); + 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 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(); + + assert_eq!(instruction.operation(), Operation::Or); + assert_eq!(instruction.destination(), 0); + 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); @@ -580,6 +735,22 @@ mod tests { assert!(instruction.second_argument_is_constant()); } + #[test] + 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(); + + assert_eq!(instruction.operation(), Operation::Not); + assert_eq!(instruction.destination(), 0); + assert_eq!(instruction.first_argument(), 1); + assert!(instruction.first_argument_is_constant()); + assert!(instruction.second_argument_is_constant()); + } + #[test] fn r#return() { let mut instruction = Instruction::r#return(); diff --git a/dust-lang/src/parser/mod.rs b/dust-lang/src/parser/mod.rs index 7ab352a..b6d0e81 100644 --- a/dust-lang/src/parser/mod.rs +++ b/dust-lang/src/parser/mod.rs @@ -279,8 +279,8 @@ impl<'src> Parser<'src> { fn parse_binary(&mut self) -> Result<(), ParseError> { let operator_position = self.current_position; - let operator = self.current_token.kind(); - let rule = ParseRule::from(&operator); + let operator = self.current_token; + let rule = ParseRule::from(&operator.kind()); self.advance()?; @@ -330,11 +330,14 @@ impl<'src> Parser<'src> { } }; - let mut instruction = match operator { + let mut instruction = match operator.kind() { TokenKind::Plus => Instruction::add(self.current_register, left, right), TokenKind::Minus => Instruction::subtract(self.current_register, left, right), TokenKind::Star => Instruction::multiply(self.current_register, left, right), TokenKind::Slash => Instruction::divide(self.current_register, left, right), + 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), _ => { return Err(ParseError::ExpectedTokenMultiple { expected: vec![ @@ -343,7 +346,7 @@ impl<'src> Parser<'src> { TokenKind::Star, TokenKind::Slash, ], - found: self.previous_token.to_owned(), + found: operator.to_owned(), position: operator_position, }) } @@ -448,6 +451,7 @@ impl<'src> Parser<'src> { } pub fn parse_block(&mut self, _allow_assignment: bool) -> Result<(), ParseError> { + self.advance()?; self.chunk.begin_scope(); while !self.allow(TokenKind::RightCurlyBrace)? && !self.is_eof() { @@ -467,13 +471,11 @@ impl<'src> Parser<'src> { let start = self.current_position.0; let (is_expression_statement, contains_block) = match self.current_token { Token::Let => { - self.advance()?; self.parse_let_statement(true)?; (false, false) } Token::LeftCurlyBrace => { - self.advance()?; self.parse_block(true)?; (true, true) @@ -495,7 +497,7 @@ impl<'src> Parser<'src> { } fn parse_let_statement(&mut self, _allow_assignment: bool) -> Result<(), ParseError> { - self.allow(TokenKind::Let)?; + self.advance()?; let position = self.current_position; let identifier = if let Token::Identifier(text) = self.current_token { @@ -692,7 +694,11 @@ impl From<&TokenKind> for ParseRule<'_> { TokenKind::Str => todo!(), TokenKind::While => todo!(), TokenKind::BangEqual => todo!(), - TokenKind::Bang => todo!(), + TokenKind::Bang => ParseRule { + prefix: Some(Parser::parse_unary), + infix: None, + precedence: Precedence::Unary, + }, TokenKind::Colon => todo!(), TokenKind::Comma => todo!(), TokenKind::Dot => todo!(), @@ -703,7 +709,11 @@ impl From<&TokenKind> for ParseRule<'_> { }, TokenKind::DoubleDot => todo!(), TokenKind::DoubleEqual => todo!(), - TokenKind::DoublePipe => todo!(), + TokenKind::DoublePipe => ParseRule { + prefix: None, + infix: Some(Parser::parse_binary), + precedence: Precedence::LogicalOr, + }, TokenKind::Equal => ParseRule { prefix: None, infix: None, diff --git a/dust-lang/src/parser/tests.rs b/dust-lang/src/parser/tests.rs index 6159b7b..eccf2f6 100644 --- a/dust-lang/src/parser/tests.rs +++ b/dust-lang/src/parser/tests.rs @@ -148,15 +148,60 @@ fn declare_local() { } #[test] -fn constant() { +fn and() { assert_eq!( - parse("42"), + parse("1 && 2"), Ok(Chunk::with_data( vec![ - (Instruction::load_constant(0, 0), Span(0, 2)), - (Instruction::r#return(), Span(0, 2)), + ( + *Instruction::and(0, 0, 1) + .set_first_argument_to_constant() + .set_second_argument_to_constant(), + Span(2, 4) + ), + (Instruction::r#return(), Span(0, 6)), ], - vec![Value::integer(42)], + vec![Value::integer(1), Value::integer(2)], + vec![] + )) + ); +} + +#[test] +fn divide() { + assert_eq!( + parse("1 / 2"), + Ok(Chunk::with_data( + vec![ + ( + *Instruction::divide(0, 0, 1) + .set_first_argument_to_constant() + .set_second_argument_to_constant(), + Span(2, 3) + ), + (Instruction::r#return(), Span(0, 5)), + ], + vec![Value::integer(1), Value::integer(2)], + vec![] + )) + ); +} + +#[test] +fn multiply() { + assert_eq!( + parse("1 * 2"), + Ok(Chunk::with_data( + vec![ + ( + *Instruction::multiply(0, 0, 1) + .set_first_argument_to_constant() + .set_second_argument_to_constant(), + Span(2, 3) + ), + (Instruction::r#return(), Span(0, 5)), + ], + vec![Value::integer(1), Value::integer(2)], vec![] )) ); @@ -201,3 +246,18 @@ fn subtract() { )) ); } + +#[test] +fn constant() { + assert_eq!( + parse("42"), + Ok(Chunk::with_data( + vec![ + (Instruction::load_constant(0, 0), Span(0, 2)), + (Instruction::r#return(), Span(0, 2)), + ], + vec![Value::integer(42)], + vec![] + )) + ); +} diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 531a1dc..abff7fb 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter}; /// Source code token. -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum Token<'src> { // End of file Eof, diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index b9c0c9c..23d8db8 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -149,6 +149,57 @@ impl Vm { self.insert(quotient, instruction.destination(), position)?; } + Operation::Modulo => { + let left = self.take_constant_or_clone_register( + instruction.first_argument(), + instruction.first_argument_is_constant(), + position, + )?; + let right = self.take_constant_or_clone_register( + instruction.second_argument(), + instruction.second_argument_is_constant(), + position, + )?; + let remainder = left + .modulo(&right) + .map_err(|error| VmError::Value { error, position })?; + + self.insert(remainder, instruction.destination(), position)?; + } + Operation::And => { + let left = self.take_constant_or_clone_register( + instruction.first_argument(), + instruction.first_argument_is_constant(), + position, + )?; + let right = self.take_constant_or_clone_register( + instruction.second_argument(), + instruction.second_argument_is_constant(), + position, + )?; + let result = left + .and(&right) + .map_err(|error| VmError::Value { error, position })?; + + self.insert(result, instruction.destination(), position)?; + } + Operation::Or => { + let left = self.take_constant_or_clone_register( + instruction.first_argument(), + instruction.first_argument_is_constant(), + position, + )?; + let right = self.take_constant_or_clone_register( + instruction.second_argument(), + instruction.second_argument_is_constant(), + position, + )?; + let result = left + .or(&right) + .map_err(|error| VmError::Value { error, position })?; + + self.insert(result, instruction.destination(), position)?; + } Operation::Negate => { let value = self.take_constant_or_clone_register( instruction.first_argument(), @@ -161,6 +212,18 @@ impl Vm { self.insert(negated, instruction.destination(), position)?; } + Operation::Not => { + let value = self.take_constant_or_clone_register( + instruction.first_argument(), + instruction.first_argument_is_constant(), + position, + )?; + let result = value + .not() + .map_err(|error| VmError::Value { error, position })?; + + self.insert(result, instruction.destination(), position)?; + } Operation::Return => { let value = self.pop(position)?; diff --git a/dust-lang/tests/operations.rs b/dust-lang/tests/operations.rs index 84838f3..4fb7417 100644 --- a/dust-lang/tests/operations.rs +++ b/dust-lang/tests/operations.rs @@ -1,5 +1,10 @@ use dust_lang::*; +#[test] +fn long_math() { + assert_eq!(run("1 + 2 * 3 - 4 / 2"), Ok(Some(Value::integer(5)))); +} + #[test] fn add() { assert_eq!(run("1 + 2"), Ok(Some(Value::integer(3))));