Working logic and if / else / else if statements

This commit is contained in:
2025-11-25 01:22:54 -07:00
parent 31ca798e55
commit b5bf9a94a6
5 changed files with 362 additions and 12 deletions

View File

@@ -0,0 +1,158 @@
use crate::compile;
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn test_if_statement() -> anyhow::Result<()> {
let compiled = compile! {
debug
"
let a = 10;
if (a > 5) {
a = 20;
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 10 #a
sgt r1 r8 5
beq r1 0 L1
move r8 20 #a
L1:
"
}
);
Ok(())
}
#[test]
fn test_if_else_statement() -> anyhow::Result<()> {
let compiled = compile! {
debug
"
let a = 0;
if (10 > 5) {
a = 1;
} else {
a = 2;
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 0 #a
sgt r1 10 5
beq r1 0 L2
move r8 1 #a
j L1
L2:
move r8 2 #a
L1:
"
}
);
Ok(())
}
#[test]
fn test_if_else_if_statement() -> anyhow::Result<()> {
let compiled = compile! {
debug
"
let a = 0;
if (a == 1) {
a = 10;
} else if (a == 2) {
a = 20;
} else {
a = 30;
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 0 #a
seq r1 r8 1
beq r1 0 L2
move r8 10 #a
j L1
L2:
seq r2 r8 2
beq r2 0 L4
move r8 20 #a
j L3
L4:
move r8 30 #a
L3:
L1:
"
}
);
Ok(())
}
#[test]
fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> {
let compiled = compile! {
debug
"
let a = 1;
let b = 2;
let c = 3;
let d = 4;
let e = 5;
let f = 6;
let g = 7;
let h = 8; // Spilled to stack (offset 0)
if (a == 1) {
h = 99;
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 1 #a
move r9 2 #b
move r10 3 #c
move r11 4 #d
move r12 5 #e
move r13 6 #f
move r14 7 #g
push 8 #h
seq r1 r8 1
beq r1 0 L1
sub r0 sp 1
put db r0 99 #h
L1:
"
}
);
Ok(())
}

View File

@@ -41,6 +41,7 @@ macro_rules! compile {
}}; }};
} }
mod binary_expression; mod binary_expression;
mod branching;
mod declaration_function_invocation; mod declaration_function_invocation;
mod declaration_literal; mod declaration_literal;
mod function_declaration; mod function_declaration;

View File

@@ -2,8 +2,9 @@ use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableS
use parser::{ use parser::{
Parser as ASTParser, Parser as ASTParser,
tree_node::{ tree_node::{
BinaryExpression, BlockExpression, DeviceDeclarationExpression, Expression, AssignmentExpression, BinaryExpression, BlockExpression, DeviceDeclarationExpression,
FunctionExpression, InvocationExpression, Literal, LogicalExpression, Expression, FunctionExpression, IfExpression, InvocationExpression, Literal,
LogicalExpression,
}, },
}; };
use quick_error::quick_error; use quick_error::quick_error;
@@ -75,6 +76,7 @@ pub struct Compiler<'a, W: std::io::Write> {
declared_main: bool, declared_main: bool,
config: CompilerConfig, config: CompilerConfig,
temp_counter: usize, temp_counter: usize,
label_counter: usize,
} }
impl<'a, W: std::io::Write> Compiler<'a, W> { impl<'a, W: std::io::Write> Compiler<'a, W> {
@@ -93,6 +95,7 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
declared_main: false, declared_main: false,
config: config.unwrap_or_default(), config: config.unwrap_or_default(),
temp_counter: 0, temp_counter: 0,
label_counter: 0,
} }
} }
@@ -120,6 +123,11 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
format!("__binary_temp_{}", self.temp_counter) format!("__binary_temp_{}", self.temp_counter)
} }
fn next_label_name(&mut self) -> String {
self.label_counter += 1;
format!("L{}", self.label_counter)
}
fn expression<'v>( fn expression<'v>(
&mut self, &mut self,
expr: Expression, expr: Expression,
@@ -134,6 +142,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
self.expression_block(expr_block, scope)?; self.expression_block(expr_block, scope)?;
Ok(None) Ok(None)
} }
Expression::If(expr_if) => {
self.expression_if(expr_if, scope)?;
Ok(None)
}
Expression::DeviceDeclaration(expr_dev) => { Expression::DeviceDeclaration(expr_dev) => {
self.expression_device(expr_dev)?; self.expression_device(expr_dev)?;
Ok(None) Ok(None)
@@ -145,6 +157,10 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
temp_name: None, temp_name: None,
})) }))
} }
Expression::Assignment(assign_expr) => {
self.expression_assignment(assign_expr, scope)?;
Ok(None)
}
Expression::Invocation(expr_invoke) => { Expression::Invocation(expr_invoke) => {
self.expression_function_invocation(expr_invoke, scope)?; self.expression_function_invocation(expr_invoke, scope)?;
// Invocation returns result in r15 (RETURN_REGISTER). // Invocation returns result in r15 (RETURN_REGISTER).
@@ -353,6 +369,50 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(Some(loc)) Ok(Some(loc))
} }
fn expression_assignment<'v>(
&mut self,
expr: AssignmentExpression,
scope: &mut VariableScope<'v>,
) -> Result<(), Error> {
let AssignmentExpression {
identifier,
expression,
} = expr;
let location = scope.get_location_of(&identifier)?;
let (val_str, cleanup) = self.compile_operand(*expression, scope)?;
let debug_tag = if self.config.debug {
format!(" #{}", identifier)
} else {
String::new()
};
match location {
VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => {
self.write_output(format!("move r{reg} {val_str}{debug_tag}"))?;
}
VariableLocation::Stack(offset) => {
// Calculate address: sp - offset
self.write_output(format!(
"sub r{0} sp {offset}",
VariableScope::TEMP_STACK_REGISTER
))?;
// Store value to stack/db at address
self.write_output(format!(
"put db r{0} {val_str}{debug_tag}",
VariableScope::TEMP_STACK_REGISTER
))?;
}
}
if let Some(name) = cleanup {
scope.free_temp(name)?;
}
Ok(())
}
fn expression_function_invocation( fn expression_function_invocation(
&mut self, &mut self,
invoke_expr: InvocationExpression, invoke_expr: InvocationExpression,
@@ -467,6 +527,49 @@ impl<'a, W: std::io::Write> Compiler<'a, W> {
Ok(()) Ok(())
} }
fn expression_if<'v>(
&mut self,
expr: IfExpression,
scope: &mut VariableScope<'v>,
) -> Result<(), Error> {
let end_label = self.next_label_name();
let else_label = if expr.else_branch.is_some() {
self.next_label_name()
} else {
end_label.clone()
};
// Compile Condition
let (cond_str, cleanup) = self.compile_operand(*expr.condition, scope)?;
// If condition is FALSE (0), jump to else_label
self.write_output(format!("beq {cond_str} 0 {else_label}"))?;
if let Some(name) = cleanup {
scope.free_temp(name)?;
}
// Compile Body
// Scope variables in body are ephemeral to the block, handled by expression_block
self.expression_block(expr.body, scope)?;
// If we have an else branch, we need to jump over it after the 'if' body
if expr.else_branch.is_some() {
self.write_output(format!("j {end_label}"))?;
self.write_output(format!("{else_label}:"))?;
match *expr.else_branch.unwrap() {
Expression::Block(block) => self.expression_block(block, scope)?,
Expression::If(if_expr) => self.expression_if(if_expr, scope)?,
_ => unreachable!("Parser ensures else branch is Block or If"),
}
}
self.write_output(format!("{end_label}:"))?;
Ok(())
}
/// Helper to resolve a location to a register string (e.g., "r0"). /// Helper to resolve a location to a register string (e.g., "r0").
/// Note: This does not handle Stack locations automatically, as they require /// Note: This does not handle Stack locations automatically, as they require
/// instruction emission to load. Use `compile_operand` for general handling. /// instruction emission to load. Use `compile_operand` for general handling.

View File

@@ -204,9 +204,7 @@ impl Parser {
let expr = match current_token.token_type { let expr = match current_token.token_type {
// match unsupported keywords // match unsupported keywords
TokenType::Keyword(e) TokenType::Keyword(e) if matches_keyword!(e, Keyword::Enum, Keyword::While) => {
if matches_keyword!(e, Keyword::Enum, Keyword::If, Keyword::Else) =>
{
return Err(Error::UnsupportedKeyword(current_token.clone())); return Err(Error::UnsupportedKeyword(current_token.clone()));
} }
@@ -218,6 +216,9 @@ impl Parser {
// match functions with a `fn` keyword // match functions with a `fn` keyword
TokenType::Keyword(Keyword::Fn) => Expression::Function(self.function()?), TokenType::Keyword(Keyword::Fn) => Expression::Function(self.function()?),
// match if statements
TokenType::Keyword(Keyword::If) => Expression::If(self.if_expression()?),
// match syscalls with a `syscall` keyword // match syscalls with a `syscall` keyword
TokenType::Identifier(ref id) if SysCall::is_syscall(id) => { TokenType::Identifier(ref id) if SysCall::is_syscall(id) => {
Expression::Syscall(self.syscall()?) Expression::Syscall(self.syscall()?)
@@ -711,10 +712,19 @@ impl Parser {
let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?;
let return_expr = Expression::Return(boxed!(expression)); let return_expr = Expression::Return(boxed!(expression));
expressions.push(return_expr); expressions.push(return_expr);
self.assign_next()?;
}
self.assign_next()?; // check for semicolon
let next = token_from_option!(self.get_next()?);
if !token_matches!(next, TokenType::Symbol(Symbol::Semicolon)) {
return Err(Error::UnexpectedToken(next.clone()));
}
// check for right brace
let next = token_from_option!(self.get_next()?);
if !token_matches!(next, TokenType::Symbol(Symbol::RBrace)) {
return Err(Error::UnexpectedToken(next.clone()));
}
}
Ok(BlockExpression(expressions)) Ok(BlockExpression(expressions))
} }
@@ -763,6 +773,65 @@ impl Parser {
Ok(literal) Ok(literal)
} }
fn if_expression(&mut self) -> Result<IfExpression, Error> {
let current_token = token_from_option!(self.current_token);
if !self_matches_current!(self, TokenType::Keyword(Keyword::If)) {
return Err(Error::UnexpectedToken(current_token.clone()));
}
// consume 'if'
let next = token_from_option!(self.get_next()?);
if !token_matches!(next, TokenType::Symbol(Symbol::LParen)) {
return Err(Error::UnexpectedToken(next.clone()));
}
self.assign_next()?;
// parse condition
let condition = self.expression()?.ok_or(Error::UnexpectedEOF)?;
// check for ')'
let next = token_from_option!(self.get_next()?);
if !token_matches!(next, TokenType::Symbol(Symbol::RParen)) {
return Err(Error::UnexpectedToken(next.clone()));
}
// check for '{'
let next = token_from_option!(self.get_next()?);
if !token_matches!(next, TokenType::Symbol(Symbol::LBrace)) {
return Err(Error::UnexpectedToken(next.clone()));
}
// parse body
let body = self.block()?;
// check for 'else'
let else_branch = if self_matches_peek!(self, TokenType::Keyword(Keyword::Else)) {
self.assign_next()?; // consume 'else'
if self_matches_peek!(self, TokenType::Keyword(Keyword::If)) {
// else if ...
self.assign_next()?;
Some(boxed!(Expression::If(self.if_expression()?)))
} else if self_matches_peek!(self, TokenType::Symbol(Symbol::LBrace)) {
// else { ... }
self.assign_next()?;
Some(boxed!(Expression::Block(self.block()?)))
} else {
return Err(Error::UnexpectedToken(
token_from_option!(self.get_next()?).clone(),
));
}
} else {
None
};
Ok(IfExpression {
condition: boxed!(condition),
body,
else_branch,
})
}
fn function(&mut self) -> Result<FunctionExpression, Error> { fn function(&mut self) -> Result<FunctionExpression, Error> {
let current_token = token_from_option!(self.current_token); let current_token = token_from_option!(self.current_token);
// Sanify check that the current token is a `fn` keyword // Sanify check that the current token is a `fn` keyword

View File

@@ -168,35 +168,54 @@ impl std::fmt::Display for DeviceDeclarationExpression {
} }
} }
#[derive(Debug, PartialEq, Eq)]
pub struct IfExpression {
pub condition: Box<Expression>,
pub body: BlockExpression,
pub else_branch: Option<Box<Expression>>,
}
impl std::fmt::Display for IfExpression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(if ({}) {}", self.condition, self.body)?;
if let Some(else_branch) = &self.else_branch {
write!(f, " else {}", else_branch)?;
}
write!(f, ")")
}
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Expression { pub enum Expression {
Assignment(AssignmentExpression), Assignment(AssignmentExpression),
Binary(BinaryExpression), Binary(BinaryExpression),
Block(BlockExpression), Block(BlockExpression),
Declaration(String, Box<Expression>), Declaration(String, Box<Expression>),
DeviceDeclaration(DeviceDeclarationExpression),
Function(FunctionExpression), Function(FunctionExpression),
If(IfExpression),
Invocation(InvocationExpression), Invocation(InvocationExpression),
Literal(Literal), Literal(Literal),
Logical(LogicalExpression), Logical(LogicalExpression),
Negation(Box<Expression>), Negation(Box<Expression>),
Priority(Box<Expression>), Priority(Box<Expression>),
Return(Box<Expression>), Return(Box<Expression>),
Variable(String),
DeviceDeclaration(DeviceDeclarationExpression),
Syscall(SysCall), Syscall(SysCall),
Variable(String),
} }
impl std::fmt::Display for Expression { impl std::fmt::Display for Expression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Expression::Assignment(e) => write!(f, "{}", e),
Expression::Binary(e) => write!(f, "{}", e),
Expression::Literal(l) => write!(f, "{}", l), Expression::Literal(l) => write!(f, "{}", l),
Expression::Negation(e) => write!(f, "(-{})", e), Expression::Negation(e) => write!(f, "(-{})", e),
Expression::Binary(e) => write!(f, "{}", e),
Expression::Logical(e) => write!(f, "{}", e), Expression::Logical(e) => write!(f, "{}", e),
Expression::Assignment(e) => write!(f, "{}", e),
Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e), Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e),
Expression::Function(e) => write!(f, "{}", e), Expression::Function(e) => write!(f, "{}", e),
Expression::Block(e) => write!(f, "{}", e), Expression::Block(e) => write!(f, "{}", e),
Expression::If(e) => write!(f, "{}", e),
Expression::Invocation(e) => write!(f, "{}", e), Expression::Invocation(e) => write!(f, "{}", e),
Expression::Variable(id) => write!(f, "{}", id), Expression::Variable(id) => write!(f, "{}", id),
Expression::Priority(e) => write!(f, "({})", e), Expression::Priority(e) => write!(f, "({})", e),