diff --git a/Cargo.lock b/Cargo.lock index 78c84e4..628bb83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,7 +272,7 @@ dependencies = [ [[package]] name = "qcalc" -version = "0.7.0" +version = "0.8.0" dependencies = [ "crossterm", "dirs-next", diff --git a/Cargo.toml b/Cargo.toml index 598e777..20830d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qcalc" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = ["ochir "] description = """ diff --git a/src/app.rs b/src/app.rs index 82138e1..cc76e96 100644 --- a/src/app.rs +++ b/src/app.rs @@ -328,4 +328,12 @@ mod tests { input_and_evaluate(&mut app, "bar(3)(2)"); assert_output(&app, Value::Int(9)); } + + #[test] + fn test_if_else() { + let mut app = new_app(); + + input_and_evaluate(&mut app, "if 0 then 3 % 5 else \"this is the answer\""); + assert_output(&app, Value::String("this is the answer".to_string())); + } } diff --git a/src/interpreter.rs b/src/interpreter.rs index d656660..0c71e64 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -169,7 +169,15 @@ impl Interpreter { match expr { Expr::Float(float) => Ok(Value::Float(*float)), Expr::Int(int) => Ok(Value::Int(*int)), + Expr::String(string) => Ok(Value::String(string.clone())), Expr::Bool(bool) => Ok(Value::Bool(*bool)), + Expr::If(cond, then, else_expr) => { + if self.interpret_expr(cond)?.truthy() { + self.interpret_expr(then) + } else { + self.interpret_expr(else_expr) + } + } Expr::Binary(left, operator, right) => { let left = self.interpret_expr(left)?; let right = self.interpret_expr(right)?; diff --git a/src/parse.rs b/src/parse.rs index 8903894..04941e0 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -124,13 +124,19 @@ pub enum Expr { Fun(Vec, Box), Var(String), Bool(bool), + If(Box, Box, Box), + String(String), } impl Expr { pub fn format(&self) -> String { match self { Self::Float(float) => float.to_string(), + Self::If(cond, then, else_expr) => { + format!("if {} then {} else {}", cond, then, else_expr) + } Self::Int(int) => int.to_string(), + Self::String(string) => format!("\"{}\"", string.clone()), Self::Unary(expr, operator) => format!("{}{}", operator, expr.format()), Self::Grouping(expr) => format!("({})", expr.format()), Self::Var(var) => var.to_string(), @@ -264,7 +270,7 @@ impl<'a> Parser<'a> { fn expression(&mut self) -> Result { match self.peek() { Token::Pipe => self.callable(), - _ => self.or(), + _ => self.if_expr(), } } @@ -296,6 +302,24 @@ impl<'a> Parser<'a> { Ok(Expr::Fun(parameters, expr)) } + fn if_expr(&mut self) -> Result { + if *self.peek() == Token::If { + self.advance(); + let cond = Box::new(self.expression()?); + eprintln!("Cond: {:?}", cond); + self.consume(Token::Then, "Expected then after condition")?; + let then = Box::new(self.expression()?); + eprintln!("Then: {:?}", then); + println!("Next: {:?}", self.tokenizer.peek()); + self.consume(Token::Else, "Expected else after then body")?; + let else_expr = Box::new(self.expression()?); + eprintln!("Else: {:?}", else_expr); + Ok(Expr::If(cond, then, else_expr)) + } else { + self.or() + } + } + fn or(&mut self) -> Result { let mut expr = self.and()?; while *self.peek() == Token::Or { @@ -436,6 +460,11 @@ impl<'a> Parser<'a> { self.advance(); res } + Token::String(string) => { + let res = Ok(Expr::String(string.clone())); + self.advance(); + res + } Token::True => { self.advance(); Ok(Expr::Bool(true)) @@ -715,4 +744,22 @@ mod tests { let current = tokenizer.next().unwrap(); assert_eq!(Parser::new(tokenizer, current).parse(), Ok(expected)); } + + #[test] + fn test_if() { + let expected = Stmt::Expr(Expr::If( + Box::new(Expr::Bool(true)), + Box::new(Expr::Int(1)), + Box::new(Expr::String("this is the answer".to_string())), + )); + + let mut tokenizer = Tokenizer::new( + "if true then 1 else \"this is the answer\"" + .chars() + .peekable(), + ) + .peekable(); + let current = tokenizer.next().unwrap(); + assert_eq!(Parser::new(tokenizer, current).parse(), Ok(expected)); + } } diff --git a/src/token.rs b/src/token.rs index 6bd689b..132d85f 100644 --- a/src/token.rs +++ b/src/token.rs @@ -7,6 +7,9 @@ const LET: &str = "let"; const UNDEF: &str = "undef"; const TRUE: &str = "true"; const FALSE: &str = "false"; +const IF: &str = "if"; +const THEN: &str = "then"; +const ELSE: &str = "else"; #[derive(PartialEq, Debug, Clone)] pub enum Token { @@ -41,6 +44,10 @@ pub enum Token { Lte, Or, And, + If, + Then, + Else, + String(String), } impl std::fmt::Display for Token { @@ -48,6 +55,7 @@ impl std::fmt::Display for Token { match self { Token::Float(float) => inner_write(float, f), Token::Int(int) => inner_write(int, f), + Token::String(string) => inner_write(format!("\"{}\"", string), f), Token::Undef => inner_write(UNDEF, f), Token::Comma => inner_write(',', f), Token::Ident(ident) => inner_write(ident, f), @@ -67,8 +75,8 @@ impl std::fmt::Display for Token { Token::RParen => inner_write(')', f), Token::Shr => inner_write(">>", f), Token::Shl => inner_write("<<", f), - Token::True => inner_write("true", f), - Token::False => inner_write("false", f), + Token::True => inner_write(TRUE, f), + Token::False => inner_write(FALSE, f), Token::Eq => inner_write("==", f), Token::Ne => inner_write("!=", f), Token::Gt => inner_write(">", f), @@ -77,6 +85,9 @@ impl std::fmt::Display for Token { Token::Lte => inner_write("<=", f), Token::Or => inner_write("||", f), Token::And => inner_write("&&", f), + Token::If => inner_write(IF, f), + Token::Then => inner_write(THEN, f), + Token::Else => inner_write(ELSE, f), } } } @@ -217,21 +228,45 @@ impl<'a> Iterator for Tokenizer<'a> { } } } + '"' => { + let mut string = String::new(); + while self.input.peek().is_some_and(|c| *c != '"') { + let next = self.input.next().unwrap(); + if next == '\\' { + if let Some(char) = self.input.next() { + string.push('\\'); + string.push(char); + } else { + return None; + } + } else { + string.push(next); + } + } + if let Some('"') = self.input.next() { + Token::String(string) + } else { + return None; + } + } 'A'..='Z' | 'a'..='z' => { - let mut func = next.to_string(); + let mut ident = next.to_string(); while self .input .peek() .is_some_and(|c| c.is_ascii_alphanumeric() || *c == '_') { - func.push(self.input.next().unwrap()); + ident.push(self.input.next().unwrap()); } - match func.as_str() { + match ident.as_str() { LET => Token::Let, UNDEF => Token::Undef, TRUE => Token::True, FALSE => Token::False, - _ => Token::Ident(func), + IF => Token::If, + THEN => Token::Then, + ELSE => Token::Else, + _ => Token::Ident(ident), } } _ => return None, // Unknown chars just end the parsing. Not sure if good or @@ -320,4 +355,26 @@ mod tests { Some(Token::Ident("foo_bar1337".to_string())) ); } + + #[test] + fn test_if_else() { + let str = "if 1 == 3 then 3 % 3 else \"lol\"".chars().peekable(); + println!("str: {:?}", str); + let tokens = Tokenizer::new(str).collect::>(); + assert_eq!( + tokens, + vec![ + Token::If, + Token::Int(1), + Token::Eq, + Token::Int(3), + Token::Then, + Token::Int(3), + Token::Mod, + Token::Int(3), + Token::Else, + Token::String("lol".to_string()), + ] + ); + } }