Add support for the 'while' keyword
This commit is contained in:
@@ -85,3 +85,63 @@ fn variable_declaration_negative() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -110,7 +110,65 @@ fn test_math_with_logic() -> anyhow::Result<()> {
|
|||||||
compiled,
|
compiled,
|
||||||
indoc! {
|
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
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -178,6 +178,16 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
temp_name: Some(temp_name),
|
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) => {
|
Expression::Variable(name) => {
|
||||||
let loc = scope.get_location_of(&name)?;
|
let loc = scope.get_location_of(&name)?;
|
||||||
Ok(Some(CompilationResult {
|
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)?;
|
self.emit_variable_assignment(&var_name, &var_location, num)?;
|
||||||
var_location
|
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) => {
|
Expression::Invocation(invoke_expr) => {
|
||||||
self.expression_function_invocation(invoke_expr, scope)?;
|
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();
|
let num_str = num.to_string();
|
||||||
self.write_output(format!("push {num_str}"))?;
|
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)? {
|
Expression::Variable(var_name) => match stack.get_location_of(var_name)? {
|
||||||
VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => {
|
VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => {
|
||||||
self.write_output(format!("push r{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));
|
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.
|
// Optimization for negated literals used as operands.
|
||||||
// E.g., `1 + -2` -> return "-2" string, no register used.
|
// E.g., `1 + -2` -> return "-2" string, no register used.
|
||||||
if let Expression::Negation(inner) = &expr
|
if let Expression::Negation(inner) = &expr
|
||||||
@@ -705,6 +732,14 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
|
|||||||
num,
|
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) => {
|
Expression::Binary(bin_expr) => {
|
||||||
let result = self.expression_binary(bin_expr, scope)?;
|
let result = self.expression_binary(bin_expr, scope)?;
|
||||||
let result_reg = self.resolve_register(&result.location)?;
|
let result_reg = self.resolve_register(&result.location)?;
|
||||||
|
|||||||
@@ -171,9 +171,8 @@ impl Parser {
|
|||||||
) {
|
) {
|
||||||
return Ok(Some(self.infix(lhs)?));
|
return Ok(Some(self.infix(lhs)?));
|
||||||
}
|
}
|
||||||
// This is an edge case. We need to move back one token if the current token is an
|
// This is an edge case. We need to move back one token if the current token is an operator
|
||||||
// operator, comparison, or logical symbol so the binary expression can pick up
|
// so the binary expression can pick up the operator
|
||||||
// the operator
|
|
||||||
else if self_matches_current!(
|
else if self_matches_current!(
|
||||||
self,
|
self,
|
||||||
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical()
|
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()?),
|
TokenType::Symbol(Symbol::LBrace) => Expression::Block(self.block()?),
|
||||||
|
|
||||||
// match literal expressions with a semi-colon afterwards
|
// 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
|
// match priority expressions with a left parenthesis
|
||||||
TokenType::Symbol(Symbol::LParen) => Expression::Priority(self.priority()?),
|
TokenType::Symbol(Symbol::LParen) => Expression::Priority(self.priority()?),
|
||||||
@@ -280,8 +281,8 @@ impl Parser {
|
|||||||
let current_token = token_from_option!(self.current_token);
|
let current_token = token_from_option!(self.current_token);
|
||||||
|
|
||||||
match current_token.token_type {
|
match current_token.token_type {
|
||||||
// A literal number
|
// A literal number or boolean
|
||||||
TokenType::Number(_) => self.literal().map(Expression::Literal),
|
TokenType::Number(_) | TokenType::Boolean(_) => self.literal().map(Expression::Literal),
|
||||||
// A plain variable
|
// A plain variable
|
||||||
TokenType::Identifier(ident)
|
TokenType::Identifier(ident)
|
||||||
if !self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) =>
|
if !self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) =>
|
||||||
@@ -378,7 +379,7 @@ impl Parser {
|
|||||||
| Expression::Logical(_)
|
| Expression::Logical(_)
|
||||||
| Expression::Invocation(_)
|
| Expression::Invocation(_)
|
||||||
| Expression::Priority(_)
|
| Expression::Priority(_)
|
||||||
| Expression::Literal(Literal::Number(_))
|
| Expression::Literal(_)
|
||||||
| Expression::Variable(_)
|
| Expression::Variable(_)
|
||||||
| Expression::Negation(_) => {}
|
| Expression::Negation(_) => {}
|
||||||
_ => {
|
_ => {
|
||||||
@@ -755,6 +756,7 @@ impl Parser {
|
|||||||
let literal = match current_token.token_type {
|
let literal = match current_token.token_type {
|
||||||
TokenType::Number(num) => Literal::Number(num),
|
TokenType::Number(num) => Literal::Number(num),
|
||||||
TokenType::String(string) => Literal::String(string),
|
TokenType::String(string) => Literal::String(string),
|
||||||
|
TokenType::Boolean(boolean) => Literal::Boolean(boolean),
|
||||||
_ => return Err(Error::UnexpectedToken(current_token.clone())),
|
_ => return Err(Error::UnexpectedToken(current_token.clone())),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1050,3 +1052,4 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -409,6 +409,7 @@ impl Tokenizer {
|
|||||||
"device" if next_ws!() => keyword!(Device),
|
"device" if next_ws!() => keyword!(Device),
|
||||||
"loop" if next_ws!() => keyword!(Loop),
|
"loop" if next_ws!() => keyword!(Loop),
|
||||||
"break" if next_ws!() => keyword!(Break),
|
"break" if next_ws!() => keyword!(Break),
|
||||||
|
"while" if next_ws!() => keyword!(While),
|
||||||
|
|
||||||
// boolean literals
|
// boolean literals
|
||||||
"true" if next_ws!() => {
|
"true" if next_ws!() => {
|
||||||
@@ -886,4 +887,39 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,4 +228,6 @@ pub enum Keyword {
|
|||||||
Loop,
|
Loop,
|
||||||
/// Represents the `break` keyword
|
/// Represents the `break` keyword
|
||||||
Break,
|
Break,
|
||||||
|
/// Represents the `while` keyword
|
||||||
|
While,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user