diff --git a/core/src/lexer.rs b/core/src/lexer.rs index 3e7f648..95f26f1 100644 --- a/core/src/lexer.rs +++ b/core/src/lexer.rs @@ -109,6 +109,18 @@ pub enum Token { Slash, #[strum(props(regex = r"^="))] Equal, + #[strum(props(regex = r"^\+="))] + PlusEqual, + #[strum(props(regex = r"^-\="))] + MinusEqual, + #[strum(props(regex = r"^\*="))] + StarEqual, + #[strum(props(regex = r"^/="))] + SlashEqual, + #[strum(props(regex = r"^%="))] + PercentEqual, + #[strum(props(regex = r"^\^="))] + CaretEqual, #[strum(props(regex = r"^=="))] EqualEqual, #[strum(props(regex = r"^!="))] @@ -133,6 +145,8 @@ pub enum Token { Not, #[strum(props(regex = r"^\*\*"))] StarStar, + #[strum(props(regex = r"^\^"))] + Caret, #[strum(props(regex = r"^%"))] Percent, #[strum(props(regex = r"^->"))] @@ -440,6 +454,23 @@ mod tests { ); } + #[test] + fn test_caret() { + let program = r#"m := a ^ b"#; + let mut lexer = Lexer::new(program); + let tokens = lexer.tokenize().unwrap(); + assert_eq!( + tokens, + vec![ + Token::Identifier("m".to_string()), + Token::ColonEqual, + Token::Identifier("a".to_string()), + Token::Caret, + Token::Identifier("b".to_string()), + ] + ); + } + #[test] fn test_arrow() { let program = r#"OBJ->FIELD"#; @@ -487,6 +518,96 @@ mod tests { ); } + #[test] + fn test_compound_plus() { + let program = "a += 1"; + let mut lexer = Lexer::new(program); + let tokens = lexer.tokenize().unwrap(); + assert_eq!( + tokens, + vec![ + Token::Identifier("a".to_string()), + Token::PlusEqual, + Token::Int(1), + ] + ); + } + + #[test] + fn test_compound_minus() { + let program = "a -= 1"; + let mut lexer = Lexer::new(program); + let tokens = lexer.tokenize().unwrap(); + assert_eq!( + tokens, + vec![ + Token::Identifier("a".to_string()), + Token::MinusEqual, + Token::Int(1), + ] + ); + } + + #[test] + fn test_compound_star() { + let program = "a *= 1"; + let mut lexer = Lexer::new(program); + let tokens = lexer.tokenize().unwrap(); + assert_eq!( + tokens, + vec![ + Token::Identifier("a".to_string()), + Token::StarEqual, + Token::Int(1), + ] + ); + } + + #[test] + fn test_compound_slash() { + let program = "a /= 1"; + let mut lexer = Lexer::new(program); + let tokens = lexer.tokenize().unwrap(); + assert_eq!( + tokens, + vec![ + Token::Identifier("a".to_string()), + Token::SlashEqual, + Token::Int(1), + ] + ); + } + + #[test] + fn test_compound_percent() { + let program = "a %= 1"; + let mut lexer = Lexer::new(program); + let tokens = lexer.tokenize().unwrap(); + assert_eq!( + tokens, + vec![ + Token::Identifier("a".to_string()), + Token::PercentEqual, + Token::Int(1), + ] + ); + } + + #[test] + fn test_compound_caret() { + let program = "a ^= 1"; + let mut lexer = Lexer::new(program); + let tokens = lexer.tokenize().unwrap(); + assert_eq!( + tokens, + vec![ + Token::Identifier("a".to_string()), + Token::CaretEqual, + Token::Int(1), + ] + ); + } + #[test] fn test_minus_minus() { let program = r#"--a"#; diff --git a/core/src/parser.rs b/core/src/parser.rs index 3795d41..ec1ee73 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -40,7 +40,7 @@ impl<'a> Parser<'a> { Some(Token::Minus) => Some(BinaryOperator::Subtract), Some(Token::Star) => Some(BinaryOperator::Multiply), Some(Token::Slash) => Some(BinaryOperator::Divide), - Some(Token::StarStar) => Some(BinaryOperator::Exponent), + Some(Token::StarStar) | Some(Token::Caret) => Some(BinaryOperator::Exponent), Some(Token::Percent) => Some(BinaryOperator::Modulo), Some(Token::And) => Some(BinaryOperator::And), Some(Token::Or) => Some(BinaryOperator::Or), @@ -65,7 +65,7 @@ impl<'a> Parser<'a> { let exp = match self.peek_token() { Some(Token::Identifier(name)) => { self.take_token()?; // consumes identifier - if self.peek_token() == Some(Token::ColonEqual) { + if self.is_assignment() { self.parse_assignment(name)? } else if self.peek_token() == Some(Token::OpenParens) { self.parse_fun_call(name)? @@ -89,12 +89,77 @@ impl<'a> Parser<'a> { Ok(exp) } + fn is_assignment(&self) -> bool { + self.peek_token() == Some(Token::ColonEqual) + || self.peek_token() == Some(Token::PlusEqual) + || self.peek_token() == Some(Token::MinusEqual) + || self.peek_token() == Some(Token::StarEqual) + || self.peek_token() == Some(Token::SlashEqual) + || self.peek_token() == Some(Token::PercentEqual) + || self.peek_token() == Some(Token::CaretEqual) + } + fn parse_assignment(&mut self, name: String) -> anyhow::Result { let var = Exp::Var(name); - self.expect(Token::ColonEqual)?; + let Some(op) = self.next_token()? else { + anyhow::bail!("Expected assignment operator, found end of file"); + }; let exp = self.parse_exp()?; - Ok(Exp::Assignment(Box::new(var), Box::new(exp))) + match op { + Token::ColonEqual => Ok(Exp::Assignment(Box::new(var), Box::new(exp))), + Token::PlusEqual => Ok(Exp::Assignment( + Box::new(var.clone()), + Box::new(Exp::Binary( + Box::new(var), + BinaryOperator::Add, + Box::new(exp), + )), + )), + Token::MinusEqual => Ok(Exp::Assignment( + Box::new(var.clone()), + Box::new(Exp::Binary( + Box::new(var), + BinaryOperator::Subtract, + Box::new(exp), + )), + )), + Token::StarEqual => Ok(Exp::Assignment( + Box::new(var.clone()), + Box::new(Exp::Binary( + Box::new(var), + BinaryOperator::Multiply, + Box::new(exp), + )), + )), + Token::SlashEqual => Ok(Exp::Assignment( + Box::new(var.clone()), + Box::new(Exp::Binary( + Box::new(var), + BinaryOperator::Divide, + Box::new(exp), + )), + )), + Token::PercentEqual => Ok(Exp::Assignment( + Box::new(var.clone()), + Box::new(Exp::Binary( + Box::new(var), + BinaryOperator::Modulo, + Box::new(exp), + )), + )), + Token::CaretEqual => Ok(Exp::Assignment( + Box::new(var.clone()), + Box::new(Exp::Binary( + Box::new(var), + BinaryOperator::Exponent, + Box::new(exp), + )), + )), + _ => { + anyhow::bail!("Expected assignment operator, found {op:?}"); + } + } } fn parse_fun_call(&mut self, name: String) -> anyhow::Result { diff --git a/samples/operators.prg b/samples/operators.prg index 0b90856..2c4fafe 100644 --- a/samples/operators.prg +++ b/samples/operators.prg @@ -2,3 +2,4 @@ nVar1 := 2 + 2 nVar2 := 10 - nVar1 nVar3 := -nVar1 nVar4 := ++nVar2 +nVar1 /= 2