diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index ea0cbe0..21320d8 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -159,9 +159,15 @@ impl<'src> Lexer<'src> { } } '*' => { - self.position += 1; + if let Some('=') = self.peek_second_char() { + self.position += 2; - (Token::Star, Span(self.position - 1, self.position)) + (Token::StarEqual, Span(self.position - 2, self.position)) + } else { + self.position += 1; + + (Token::Star, Span(self.position - 1, self.position)) + } } '(' => { self.position += 1; @@ -261,9 +267,15 @@ impl<'src> Lexer<'src> { ) } '/' => { - self.position += 1; + if let Some('=') = self.peek_second_char() { + self.position += 2; - (Token::Slash, Span(self.position - 1, self.position)) + (Token::SlashEqual, Span(self.position - 2, self.position)) + } else { + self.position += 1; + + (Token::Slash, Span(self.position - 1, self.position)) + } } '%' => { self.position += 1; diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index b6db338..1b42ca7 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -348,12 +348,12 @@ impl<'src> Parser<'src> { ) -> Result<(bool, bool, bool, u8), ParseError> { let mut push_back = false; let mut is_constant = false; - let mut is_local = false; + let mut is_mutable_local = false; let argument = match instruction.operation() { Operation::GetLocal => { - is_local = true; let local_index = instruction.b(); let local = self.chunk.get_local(local_index, self.current_position)?; + is_mutable_local = local.is_mutable; if let Some(index) = local.register_index { index @@ -380,24 +380,44 @@ impl<'src> Parser<'src> { } }; - Ok((push_back, is_constant, is_local, argument)) + Ok((push_back, is_constant, is_mutable_local, argument)) } fn parse_math_binary(&mut self) -> Result<(), ParseError> { let (left_instruction, left_position) = self.chunk.pop_instruction(self.current_position)?; - let (push_back_left, left_is_constant, left_is_local, left) = + let (push_back_left, left_is_constant, left_is_mutable_local, left) = self.handle_binary_argument(&left_instruction)?; let operator = self.current_token; let operator_position = self.current_position; let rule = ParseRule::from(&operator.kind()); + if let TokenKind::PlusEqual + | TokenKind::MinusEqual + | TokenKind::StarEqual + | TokenKind::SlashEqual = operator.kind() + { + if !left_is_mutable_local { + return Err(ParseError::ExpectedMutableVariable { + found: self.previous_token.to_owned(), + position: left_position, + }); + } + } + self.advance()?; self.parse(rule.precedence.increment())?; - let register = if left_is_local { + let (right_instruction, right_position) = + self.chunk.pop_instruction(self.current_position)?; + let (push_back_right, right_is_constant, right_is_mutable_local, right) = + self.handle_binary_argument(&right_instruction)?; + + let register = if left_is_mutable_local { left + } else if right_is_mutable_local { + right } else { let current = self.current_register; @@ -406,18 +426,26 @@ impl<'src> Parser<'src> { current }; let mut new_instruction = match operator.kind() { - TokenKind::Plus => Instruction::add(register, left, 0), - TokenKind::Minus => Instruction::subtract(register, left, 0), - TokenKind::Star => Instruction::multiply(register, left, 0), - TokenKind::Slash => Instruction::divide(register, left, 0), - TokenKind::Percent => Instruction::modulo(register, left, 0), + TokenKind::Plus => Instruction::add(register, left, right), + TokenKind::PlusEqual => Instruction::add(register, left, right), + TokenKind::Minus => Instruction::subtract(register, left, right), + TokenKind::MinusEqual => Instruction::subtract(register, left, right), + TokenKind::Star => Instruction::multiply(register, left, right), + TokenKind::StarEqual => Instruction::multiply(register, left, right), + TokenKind::Slash => Instruction::divide(register, left, right), + TokenKind::SlashEqual => Instruction::divide(register, left, right), + TokenKind::Percent => Instruction::modulo(register, left, right), _ => { return Err(ParseError::ExpectedTokenMultiple { expected: &[ TokenKind::Plus, + TokenKind::PlusEqual, TokenKind::Minus, + TokenKind::MinusEqual, TokenKind::Star, + TokenKind::StarEqual, TokenKind::Slash, + TokenKind::SlashEqual, TokenKind::Percent, ], found: operator.to_owned(), @@ -426,13 +454,6 @@ impl<'src> Parser<'src> { } }; - let (right_instruction, right_position) = - self.chunk.pop_instruction(self.current_position)?; - let (push_back_right, right_is_constant, right_is_local, right) = - self.handle_binary_argument(&right_instruction)?; - - new_instruction.set_c(right); - if left_is_constant { new_instruction.set_b_is_constant(); } @@ -1122,7 +1143,11 @@ impl From<&TokenKind> for ParseRule<'_> { infix: Some(Parser::parse_math_binary), precedence: Precedence::Term, }, - TokenKind::MinusEqual => todo!(), + TokenKind::MinusEqual => ParseRule { + prefix: None, + infix: Some(Parser::parse_math_binary), + precedence: Precedence::Assignment, + }, TokenKind::Mut => ParseRule { prefix: None, infix: None, @@ -1138,7 +1163,11 @@ impl From<&TokenKind> for ParseRule<'_> { infix: Some(Parser::parse_math_binary), precedence: Precedence::Term, }, - TokenKind::PlusEqual => todo!(), + TokenKind::PlusEqual => ParseRule { + prefix: None, + infix: Some(Parser::parse_math_binary), + precedence: Precedence::Assignment, + }, TokenKind::RightCurlyBrace => ParseRule { prefix: None, infix: None, @@ -1164,11 +1193,21 @@ impl From<&TokenKind> for ParseRule<'_> { infix: Some(Parser::parse_math_binary), precedence: Precedence::Factor, }, + TokenKind::SlashEqual => ParseRule { + prefix: None, + infix: Some(Parser::parse_math_binary), + precedence: Precedence::Assignment, + }, TokenKind::Star => ParseRule { prefix: None, infix: Some(Parser::parse_math_binary), precedence: Precedence::Factor, }, + TokenKind::StarEqual => ParseRule { + prefix: None, + infix: Some(Parser::parse_math_binary), + precedence: Precedence::Assignment, + }, TokenKind::Str => todo!(), TokenKind::String => ParseRule { prefix: Some(Parser::parse_string), @@ -1205,6 +1244,10 @@ pub enum ParseError { found: TokenOwned, position: Span, }, + ExpectedMutableVariable { + found: TokenOwned, + position: Span, + }, InvalidAssignmentTarget { found: TokenOwned, position: Span, @@ -1250,6 +1293,7 @@ impl AnnotatedError for ParseError { Self::ExpectedExpression { .. } => "Expected an expression", Self::ExpectedToken { .. } => "Expected a specific token", Self::ExpectedTokenMultiple { .. } => "Expected one of multiple tokens", + Self::ExpectedMutableVariable { .. } => "Expected a mutable variable", Self::InvalidAssignmentTarget { .. } => "Invalid assignment target", Self::UndefinedVariable { .. } => "Undefined variable", Self::RegisterOverflow { .. } => "Register overflow", @@ -1291,6 +1335,9 @@ impl AnnotatedError for ParseError { Some(details) } + Self::ExpectedMutableVariable { found, .. } => { + Some(format!("Expected mutable variable, found \"{found}\"")) + } Self::InvalidAssignmentTarget { found, .. } => { Some(format!("Invalid assignment target, found \"{found}\"")) } @@ -1312,6 +1359,7 @@ impl AnnotatedError for ParseError { Self::ExpectedExpression { position, .. } => *position, Self::ExpectedToken { position, .. } => *position, Self::ExpectedTokenMultiple { position, .. } => *position, + Self::ExpectedMutableVariable { position, .. } => *position, Self::InvalidAssignmentTarget { position, .. } => *position, Self::UndefinedVariable { position, .. } => *position, Self::RegisterOverflow { position } => *position, diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 0159382..9a31fff 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -60,7 +60,9 @@ pub enum Token<'src> { RightSquareBrace, Semicolon, Slash, + SlashEqual, Star, + StarEqual, } impl<'src> Token<'src> { @@ -116,7 +118,9 @@ impl<'src> Token<'src> { Token::RightSquareBrace => 1, Token::Semicolon => 1, Token::Slash => 1, + Token::SlashEqual => 2, Token::Star => 1, + Token::StarEqual => 2, } } @@ -167,7 +171,9 @@ impl<'src> Token<'src> { Token::RightSquareBrace => TokenOwned::RightSquareBrace, Token::Semicolon => TokenOwned::Semicolon, Token::Star => TokenOwned::Star, + Token::StarEqual => TokenOwned::StarEqual, Token::Slash => TokenOwned::Slash, + Token::SlashEqual => TokenOwned::SlashEqual, Token::String(text) => TokenOwned::String(text.to_string()), Token::Str => TokenOwned::Str, Token::Struct => TokenOwned::Struct, @@ -222,7 +228,9 @@ impl<'src> Token<'src> { Token::RightSquareBrace => TokenKind::RightSquareBrace, Token::Semicolon => TokenKind::Semicolon, Token::Star => TokenKind::Star, + Token::StarEqual => TokenKind::StarEqual, Token::Slash => TokenKind::Slash, + Token::SlashEqual => TokenKind::SlashEqual, Token::Str => TokenKind::Str, Token::String(_) => TokenKind::String, Token::Struct => TokenKind::Struct, @@ -279,7 +287,9 @@ impl<'src> Display for Token<'src> { Token::RightSquareBrace => write!(f, "]"), Token::Semicolon => write!(f, ";"), Token::Slash => write!(f, "/"), + Token::SlashEqual => write!(f, "/="), Token::Star => write!(f, "*"), + Token::StarEqual => write!(f, "*="), Token::Str => write!(f, "str"), Token::String(value) => write!(f, "{value}"), Token::Struct => write!(f, "struct"), @@ -348,8 +358,10 @@ pub enum TokenOwned { RightSquareBrace, Semicolon, Star, + StarEqual, Struct, Slash, + SlashEqual, } impl Display for TokenOwned { @@ -400,7 +412,9 @@ impl Display for TokenOwned { TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f), TokenOwned::Semicolon => Token::Semicolon.fmt(f), TokenOwned::Star => Token::Star.fmt(f), + TokenOwned::StarEqual => Token::StarEqual.fmt(f), TokenOwned::Slash => Token::Slash.fmt(f), + TokenOwned::SlashEqual => Token::SlashEqual.fmt(f), TokenOwned::Str => Token::Str.fmt(f), TokenOwned::String(string) => Token::String(string).fmt(f), TokenOwned::Struct => Token::Struct.fmt(f), @@ -467,8 +481,10 @@ pub enum TokenKind { RightSquareBrace, Semicolon, Star, + StarEqual, Struct, Slash, + SlashEqual, } impl Display for TokenKind { @@ -519,8 +535,10 @@ impl Display for TokenKind { TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f), TokenKind::Semicolon => Token::Semicolon.fmt(f), TokenKind::Star => Token::Star.fmt(f), + TokenKind::StarEqual => Token::StarEqual.fmt(f), TokenKind::Str => Token::Str.fmt(f), TokenKind::Slash => Token::Slash.fmt(f), + TokenKind::SlashEqual => Token::SlashEqual.fmt(f), TokenKind::String => write!(f, "string value"), TokenKind::Struct => Token::Struct.fmt(f), TokenKind::While => Token::While.fmt(f), diff --git a/dust-lang/tests/expressions.rs b/dust-lang/tests/expressions.rs index b738329..f792d73 100644 --- a/dust-lang/tests/expressions.rs +++ b/dust-lang/tests/expressions.rs @@ -1,4 +1,5 @@ use dust_lang::*; + #[test] fn add() { let source = "1 + 2"; @@ -23,6 +24,28 @@ fn add() { assert_eq!(run(source), Ok(Some(Value::integer(3)))); } +#[test] +fn add_assign() { + let source = "let mut a = 1; a += 2; a"; + + assert_eq!( + parse(source), + Ok(Chunk::with_data( + vec![ + (Instruction::load_constant(0, 0, false), Span(12, 13)), + (Instruction::define_local(0, 0, true), Span(8, 9)), + (*Instruction::add(0, 0, 1).set_c_is_constant(), Span(17, 19)), + (Instruction::get_local(1, 0), Span(23, 24)), + (Instruction::r#return(), Span(24, 24)) + ], + vec![Value::integer(1), Value::integer(2)], + vec![Local::new(Identifier::new("a"), true, 0, Some(0))] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::integer(3)))); +} + #[test] fn and() { let source = "true && false"; @@ -97,6 +120,7 @@ fn block_scope() { #[test] fn constant() { let source = "42"; + assert_eq!( parse(source), Ok(Chunk::with_data( @@ -115,6 +139,7 @@ fn constant() { #[test] fn define_local() { let source = "let x = 42;"; + assert_eq!( parse(source), Ok(Chunk::with_data( @@ -133,6 +158,7 @@ fn define_local() { #[test] fn divide() { let source = "2 / 2"; + assert_eq!( parse(source), Ok(Chunk::with_data( @@ -153,9 +179,35 @@ fn divide() { assert_eq!(run(source), Ok(Some(Value::integer(1)))); } +#[test] +fn divide_assign() { + let source = "let mut a = 2; a /= 2; a"; + + assert_eq!( + parse(source), + Ok(Chunk::with_data( + vec![ + (Instruction::load_constant(0, 0, false), Span(12, 13)), + (Instruction::define_local(0, 0, true), Span(8, 9)), + ( + *Instruction::divide(0, 0, 1).set_c_is_constant(), + Span(17, 19) + ), + (Instruction::get_local(1, 0), Span(23, 24)), + (Instruction::r#return(), Span(24, 24)) + ], + vec![Value::integer(2), Value::integer(2)], + vec![Local::new(Identifier::new("a"), true, 0, Some(0))] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::integer(1)))); +} + #[test] fn empty() { let source = ""; + assert_eq!(parse(source), Ok(Chunk::with_data(vec![], vec![], vec![])),); assert_eq!(run(source), Ok(None)); } @@ -554,6 +606,7 @@ fn list_with_simple_expression() { #[test] fn math_operator_precedence() { let source = "1 + 2 - 3 * 4 / 5"; + assert_eq!( parse(source), Ok(Chunk::with_data( @@ -594,6 +647,7 @@ fn math_operator_precedence() { #[test] fn multiply() { let source = "1 * 2"; + assert_eq!( parse(source), Ok(Chunk::with_data( @@ -614,6 +668,31 @@ fn multiply() { assert_eq!(run(source), Ok(Some(Value::integer(2)))); } +#[test] +fn multiply_assign() { + let source = "let mut a = 2; a *= 3 a"; + + assert_eq!( + parse(source), + Ok(Chunk::with_data( + vec![ + (Instruction::load_constant(0, 0, false), Span(12, 13)), + (Instruction::define_local(0, 0, true), Span(8, 9)), + ( + *Instruction::multiply(0, 0, 1).set_c_is_constant(), + Span(17, 19) + ), + (Instruction::get_local(1, 0), Span(22, 23)), + (Instruction::r#return(), Span(23, 23)) + ], + vec![Value::integer(2), Value::integer(3)], + vec![Local::new(Identifier::new("a"), true, 0, Some(0)),] + )) + ); + + assert_eq!(run(source), Ok(Some(Value::integer(6)))); +} + #[test] fn negate() { let source = "-(42)"; @@ -683,6 +762,7 @@ fn not_equal() { #[test] fn or() { let source = "true || false"; + assert_eq!( parse(source), Ok(Chunk::with_data( @@ -704,6 +784,7 @@ fn or() { #[test] fn parentheses_precedence() { let source = "(1 + 2) * 3"; + assert_eq!( parse(source), Ok(Chunk::with_data( @@ -731,6 +812,7 @@ fn parentheses_precedence() { #[test] fn set_local() { let source = "let mut x = 41; x = 42; x"; + assert_eq!( parse(source), Ok(Chunk::with_data( @@ -753,6 +835,7 @@ fn set_local() { #[test] fn subtract() { let source = "1 - 2"; + assert_eq!( parse(source), Ok(Chunk::with_data( @@ -773,9 +856,35 @@ fn subtract() { assert_eq!(run(source), Ok(Some(Value::integer(-1)))); } +#[test] +fn subtract_assign() { + let source = "let mut x = 42; x -= 2; x"; + + assert_eq!( + parse(source), + Ok(Chunk::with_data( + vec![ + (Instruction::load_constant(0, 0, false), Span(12, 14)), + (Instruction::define_local(0, 0, true), Span(8, 9)), + ( + *Instruction::subtract(0, 0, 1).set_c_is_constant(), + Span(18, 20) + ), + (Instruction::get_local(1, 0), Span(24, 25)), + (Instruction::r#return(), Span(25, 25)), + ], + vec![Value::integer(42), Value::integer(2)], + vec![Local::new(Identifier::new("x"), true, 0, Some(0)),] + )), + ); + + assert_eq!(run(source), Ok(Some(Value::integer(40)))); +} + #[test] fn variable_and() { let source = "let a = true; let b = false; a && b"; + assert_eq!( parse(source), Ok(Chunk::with_data( diff --git a/dust-lang/tests/parse_errors.rs b/dust-lang/tests/parse_errors.rs new file mode 100644 index 0000000..7b6c5ae --- /dev/null +++ b/dust-lang/tests/parse_errors.rs @@ -0,0 +1,65 @@ +use dust_lang::*; + +#[test] +fn add_assign_expects_variable() { + let source = "1 += 2"; + + assert_eq!( + parse(source), + Err(DustError::Parse { + error: ParseError::ExpectedMutableVariable { + found: Token::Integer("1").to_owned(), + position: Span(0, 1) + }, + source + }) + ); +} + +#[test] +fn divide_assign_expects_variable() { + let source = "1 -= 2"; + + assert_eq!( + parse(source), + Err(DustError::Parse { + error: ParseError::ExpectedMutableVariable { + found: Token::Integer("1").to_owned(), + position: Span(0, 1) + }, + source + }) + ); +} + +#[test] +fn multiply_assign_expects_variable() { + let source = "1 *= 2"; + + assert_eq!( + parse(source), + Err(DustError::Parse { + error: ParseError::ExpectedMutableVariable { + found: Token::Integer("1").to_owned(), + position: Span(0, 1) + }, + source + }) + ); +} + +#[test] +fn subtract_assign_expects_variable() { + let source = "1 -= 2"; + + assert_eq!( + parse(source), + Err(DustError::Parse { + error: ParseError::ExpectedMutableVariable { + found: Token::Integer("1").to_owned(), + position: Span(0, 1) + }, + source + }) + ); +}