diff --git a/libs/compiler/src/test/declaration_literal.rs b/libs/compiler/src/test/declaration_literal.rs index bcfec16..9316e54 100644 --- a/libs/compiler/src/test/declaration_literal.rs +++ b/libs/compiler/src/test/declaration_literal.rs @@ -85,3 +85,63 @@ fn variable_declaration_negative() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn test_boolean_declaration() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let t = true; + let f = false; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 1 #t + move r9 0 #f + " + } + ); + + Ok(()) +} + +#[test] +fn test_boolean_return() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + fn getTrue() { + return true; + }; + + let val = getTrue(); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + getTrue: + push ra + move r15 1 #returnValue + sub r0 sp 1 + get ra db r0 + sub sp sp 1 + j ra + main: + jal getTrue + move r8 r15 #val + " + } + ); + + Ok(()) +} diff --git a/libs/compiler/src/test/logic_expression.rs b/libs/compiler/src/test/logic_expression.rs index e4a2ead..55bdda6 100644 --- a/libs/compiler/src/test/logic_expression.rs +++ b/libs/compiler/src/test/logic_expression.rs @@ -110,7 +110,65 @@ fn test_math_with_logic() -> anyhow::Result<()> { compiled, indoc! { " - + j main + main: + add r1 1 2 + sgt r2 r1 1 + move r8 r2 #logic + " + } + ); + + Ok(()) +} + +#[test] +fn test_boolean_in_logic() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let res = true && false; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + and r1 1 0 + move r8 r1 #res + " + } + ); + + Ok(()) +} + +#[test] +fn test_invert_a_boolean() -> anyhow::Result<()> { + let compiled = compile! { + debug + " + let i = true; + let y = !i; + + let result = y == false; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 1 #i + seq r1 r8 0 + move r9 r1 #y + seq r2 r9 0 + move r10 r2 #result " } ); diff --git a/libs/compiler/src/v1.rs b/libs/compiler/src/v1.rs index b7fc5d1..a8b5201 100644 --- a/libs/compiler/src/v1.rs +++ b/libs/compiler/src/v1.rs @@ -178,6 +178,16 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { temp_name: Some(temp_name), })) } + Expression::Literal(Literal::Boolean(b)) => { + let val = if b { "1" } else { "0" }; + let temp_name = self.next_temp_name(); + let loc = scope.add_variable(&temp_name, LocationRequest::Temp)?; + self.emit_variable_assignment(&temp_name, &loc, val)?; + Ok(Some(CompilationResult { + location: loc, + temp_name: Some(temp_name), + })) + } Expression::Variable(name) => { let loc = scope.get_location_of(&name)?; Ok(Some(CompilationResult { @@ -258,6 +268,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.emit_variable_assignment(&var_name, &var_location, num)?; var_location } + Expression::Literal(Literal::Boolean(b)) => { + let val = if b { "1" } else { "0" }; + let var_location = + scope.add_variable(var_name.clone(), LocationRequest::Persist)?; + + self.emit_variable_assignment(&var_name, &var_location, val)?; + var_location + } Expression::Invocation(invoke_expr) => { self.expression_function_invocation(invoke_expr, scope)?; @@ -364,6 +382,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { let num_str = num.to_string(); self.write_output(format!("push {num_str}"))?; } + Expression::Literal(Literal::Boolean(b)) => { + let val = if b { "1" } else { "0" }; + self.write_output(format!("push {val}"))?; + } Expression::Variable(var_name) => match stack.get_location_of(var_name)? { VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => { self.write_output(format!("push r{reg}"))?; @@ -471,6 +493,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { return Ok((n.to_string(), None)); } + // Optimization for boolean literals + if let Expression::Literal(Literal::Boolean(b)) = expr { + return Ok((if b { "1".to_string() } else { "0".to_string() }, None)); + } + // Optimization for negated literals used as operands. // E.g., `1 + -2` -> return "-2" string, no register used. if let Expression::Negation(inner) = &expr @@ -705,6 +732,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { num, )?; } + Expression::Literal(Literal::Boolean(b)) => { + let val = if b { "1" } else { "0" }; + self.emit_variable_assignment( + "returnValue", + &VariableLocation::Persistant(VariableScope::RETURN_REGISTER), + val, + )?; + } Expression::Binary(bin_expr) => { let result = self.expression_binary(bin_expr, scope)?; let result_reg = self.resolve_register(&result.location)?; diff --git a/libs/parser/src/lib.rs b/libs/parser/src/lib.rs index e504361..b01c195 100644 --- a/libs/parser/src/lib.rs +++ b/libs/parser/src/lib.rs @@ -171,9 +171,8 @@ impl Parser { ) { return Ok(Some(self.infix(lhs)?)); } - // This is an edge case. We need to move back one token if the current token is an - // operator, comparison, or logical symbol so the binary expression can pick up - // the operator + // This is an edge case. We need to move back one token if the current token is an operator + // so the binary expression can pick up the operator else if self_matches_current!( self, TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() @@ -245,7 +244,9 @@ impl Parser { TokenType::Symbol(Symbol::LBrace) => Expression::Block(self.block()?), // match literal expressions with a semi-colon afterwards - TokenType::Number(_) | TokenType::String(_) => Expression::Literal(self.literal()?), + TokenType::Number(_) | TokenType::String(_) | TokenType::Boolean(_) => { + Expression::Literal(self.literal()?) + } // match priority expressions with a left parenthesis TokenType::Symbol(Symbol::LParen) => Expression::Priority(self.priority()?), @@ -280,8 +281,8 @@ impl Parser { let current_token = token_from_option!(self.current_token); match current_token.token_type { - // A literal number - TokenType::Number(_) => self.literal().map(Expression::Literal), + // A literal number or boolean + TokenType::Number(_) | TokenType::Boolean(_) => self.literal().map(Expression::Literal), // A plain variable TokenType::Identifier(ident) if !self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) => @@ -378,7 +379,7 @@ impl Parser { | Expression::Logical(_) | Expression::Invocation(_) | Expression::Priority(_) - | Expression::Literal(Literal::Number(_)) + | Expression::Literal(_) | Expression::Variable(_) | Expression::Negation(_) => {} _ => { @@ -755,6 +756,7 @@ impl Parser { let literal = match current_token.token_type { TokenType::Number(num) => Literal::Number(num), TokenType::String(string) => Literal::String(string), + TokenType::Boolean(boolean) => Literal::Boolean(boolean), _ => return Err(Error::UnexpectedToken(current_token.clone())), }; @@ -1050,3 +1052,4 @@ impl Parser { } } } + diff --git a/libs/tokenizer/src/lib.rs b/libs/tokenizer/src/lib.rs index ad767f6..4afb2e2 100644 --- a/libs/tokenizer/src/lib.rs +++ b/libs/tokenizer/src/lib.rs @@ -409,6 +409,7 @@ impl Tokenizer { "device" if next_ws!() => keyword!(Device), "loop" if next_ws!() => keyword!(Loop), "break" if next_ws!() => keyword!(Break), + "while" if next_ws!() => keyword!(While), // boolean literals "true" if next_ws!() => { @@ -886,4 +887,39 @@ mod tests { Ok(()) } + + #[test] + fn test_compact_syntax() -> Result<()> { + let mut tokenizer = Tokenizer::from(String::from("if(true) while(false)")); + + // if(true) + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Keyword(Keyword::If) + ); + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Symbol(Symbol::LParen) + ); + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Boolean(true) + ); + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Symbol(Symbol::RParen) + ); + + // while(false) + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Keyword(Keyword::While) + ); + assert_eq!( + tokenizer.next_token()?.unwrap().token_type, + TokenType::Symbol(Symbol::LParen) + ); + + Ok(()) + } } diff --git a/libs/tokenizer/src/token.rs b/libs/tokenizer/src/token.rs index 93a090c..5e7d597 100644 --- a/libs/tokenizer/src/token.rs +++ b/libs/tokenizer/src/token.rs @@ -228,4 +228,6 @@ pub enum Keyword { Loop, /// Represents the `break` keyword Break, + /// Represents the `while` keyword + While, }