Fixed documentation rendering and added ternary expressions

This commit is contained in:
2025-12-10 18:00:20 -07:00
parent c2208fbb15
commit 0ac010ef8f
8 changed files with 236 additions and 10 deletions

View File

@@ -1,9 +1,10 @@
use crate::compile;
use anyhow::Result;
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn simple_binary_expression() -> anyhow::Result<()> {
fn simple_binary_expression() -> Result<()> {
let compiled = compile! {
debug
"
@@ -26,7 +27,7 @@ fn simple_binary_expression() -> anyhow::Result<()> {
}
#[test]
fn nested_binary_expressions() -> anyhow::Result<()> {
fn nested_binary_expressions() -> Result<()> {
let compiled = compile! {
debug
"
@@ -71,7 +72,7 @@ fn nested_binary_expressions() -> anyhow::Result<()> {
}
#[test]
fn stress_test_constant_folding() -> anyhow::Result<()> {
fn stress_test_constant_folding() -> Result<()> {
let compiled = compile! {
debug
"
@@ -94,7 +95,7 @@ fn stress_test_constant_folding() -> anyhow::Result<()> {
}
#[test]
fn test_constant_folding_with_variables_mixed_in() -> anyhow::Result<()> {
fn test_constant_folding_with_variables_mixed_in() -> Result<()> {
let compiled = compile! {
debug
r#"
@@ -120,3 +121,55 @@ fn test_constant_folding_with_variables_mixed_in() -> anyhow::Result<()> {
Ok(())
}
#[test]
fn test_ternary_expression() -> Result<()> {
let compiled = compile! {
debug
r#"
let i = 1 > 2 ? 15 : 20;
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
sgt r1 1 2
select r2 r1 15 20
move r8 r2 #i
"
}
);
Ok(())
}
#[test]
fn test_ternary_expression_assignment() -> Result<()> {
let compiled = compile! {
debug
r#"
let i = 0;
i = 1 > 2 ? 15 : 20;
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 0 #i
sgt r1 1 2
select r2 r1 15 20
move r8 r2 #i
"
}
);
Ok(())
}

View File

@@ -8,7 +8,7 @@ use parser::{
AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression,
DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression,
InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression,
LoopExpression, MemberAccessExpression, Span, Spanned, WhileExpression,
LoopExpression, MemberAccessExpression, Span, Spanned, TernaryExpression, WhileExpression,
},
};
use std::{
@@ -145,6 +145,7 @@ pub struct CompilerConfig {
pub debug: bool,
}
#[derive(Debug)]
struct CompilationResult<'a> {
location: VariableLocation<'a>,
/// If Some, this is the name of the temporary variable that holds the result.
@@ -313,6 +314,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
self.expression_assignment(assign_expr.node, scope)?;
Ok(None)
}
Expression::Ternary(tern) => Ok(Some(self.expression_ternary(tern.node, scope)?)),
Expression::Invocation(expr_invoke) => {
self.expression_function_invocation(expr_invoke, scope)?;
// Invocation returns result in r15 (RETURN_REGISTER).
@@ -740,6 +742,23 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
(var_loc, None)
}
Expression::Ternary(ternary) => {
let res = self.expression_ternary(ternary.node, scope)?;
println!("{res:?}");
let var_loc = scope.add_variable(
name_str.clone(),
LocationRequest::Persist,
Some(name_span),
)?;
let res_register = self.resolve_register(&res.location)?;
self.emit_variable_assignment(name_str, &var_loc, res_register)?;
if let Some(name) = res.temp_name {
scope.free_temp(name, None)?;
}
(var_loc, None)
}
_ => {
return Err(Error::Unknown(
format!("`{name_str}` declaration of this type is not supported/implemented."),
@@ -1208,6 +1227,45 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> {
}
}
fn expression_ternary(
&mut self,
expr: TernaryExpression<'a>,
scope: &mut VariableScope<'a, '_>,
) -> Result<CompilationResult<'a>, Error<'a>> {
let TernaryExpression {
condition,
true_value,
false_value,
} = expr;
let (cond, cond_clean) = self.compile_operand(*condition, scope)?;
let (true_val, true_clean) = self.compile_operand(*true_value, scope)?;
let (false_val, false_clean) = self.compile_operand(*false_value, scope)?;
let result_name = self.next_temp_name();
let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
let result_reg = self.resolve_register(&result_loc)?;
self.write_output(format!(
"select {} {} {} {}",
result_reg, cond, true_val, false_val
))?;
if let Some(clean) = cond_clean {
scope.free_temp(clean, None)?;
}
if let Some(clean) = true_clean {
scope.free_temp(clean, None)?;
}
if let Some(clean) = false_clean {
scope.free_temp(clean, None)?;
}
Ok(CompilationResult {
location: result_loc,
temp_name: Some(result_name),
})
}
/// Helper to resolve a location to a register string (e.g., "r0").
/// Note: This does not handle Stack locations automatically, as they require
/// instruction emission to load. Use `compile_operand` for general handling.

View File

@@ -293,12 +293,12 @@ impl<'a> Parser<'a> {
// Handle Infix operators (Binary, Logical, Assignment)
if self_matches_peek!(
self,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign)
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign | Symbol::Question)
) {
return Ok(Some(self.infix(lhs)?));
} else if self_matches_current!(
self,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign)
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign | Symbol::Question)
) {
self.tokenizer.seek(SeekFrom::Current(-1))?;
return Ok(Some(self.infix(lhs)?));
@@ -769,6 +769,7 @@ impl<'a> Parser<'a> {
| Expression::Priority(_)
| Expression::Literal(_)
| Expression::Variable(_)
| Expression::Ternary(_)
| Expression::Negation(_)
| Expression::MemberAccess(_)
| Expression::MethodCall(_) => {}
@@ -788,7 +789,7 @@ impl<'a> Parser<'a> {
// Include Assign in the operator loop
while token_matches!(
temp_token,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign)
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign | Symbol::Question | Symbol::Colon)
) {
let operator = match temp_token.token_type {
TokenType::Symbol(s) => s,
@@ -1019,7 +1020,52 @@ impl<'a> Parser<'a> {
}
operators.retain(|symbol| !matches!(symbol, Symbol::LogicalOr));
// --- PRECEDENCE LEVEL 8: Assignment (=) ---
// -- PRECEDENCE LEVEL 8: Ternary (x ? 1 : 2)
for i in (0..operators.len()).rev() {
if matches!(operators[i], Symbol::Question) {
// Ensure next operator is a colon
if i + 1 >= operators.len() || !matches!(operators[i + 1], Symbol::Colon) {
return Err(Error::InvalidSyntax(
self.current_span(),
"Ternary operator '?' missing matching ':'".to_string(),
));
}
let false_branch = expressions.remove(i + 2);
let true_branch = expressions.remove(i + 1);
let condition = expressions.remove(i);
let span = Span {
start_line: condition.span.start_line,
end_line: false_branch.span.end_line,
start_col: condition.span.start_col,
end_col: false_branch.span.end_col,
};
let ternary_node = Spanned {
span,
node: TernaryExpression {
condition: Box::new(condition),
true_value: Box::new(true_branch),
false_value: Box::new(false_branch),
},
};
expressions.insert(
i,
Spanned {
node: Expression::Ternary(ternary_node),
span,
},
);
// Remove the `?` and the `:` from the operators list
operators.remove(i);
operators.remove(i);
}
}
// --- PRECEDENCE LEVEL 9: Assignment (=) ---
// Assignment is Right Associative: a = b = c => a = (b = c)
// We iterate Right to Left
for (i, operator) in operators.iter().enumerate().rev() {

View File

@@ -160,3 +160,37 @@ fn test_negative_literal_const() -> Result<()> {
Ok(())
}
#[test]
fn test_ternary_expression() -> Result<()> {
let expr = parser!(r#"let i = x ? 1 : 2;"#).parse()?.unwrap();
assert_eq!("(let i = (x ? 1 : 2))", expr.to_string());
Ok(())
}
#[test]
fn test_complex_binary_with_ternary() -> Result<()> {
let expr = parser!("let i = (x ? 1 : 3) * 2;").parse()?.unwrap();
assert_eq!("(let i = ((x ? 1 : 3) * 2))", expr.to_string());
Ok(())
}
#[test]
fn test_operator_prescedence_with_ternary() -> Result<()> {
let expr = parser!("let x = x ? 1 : 3 * 2;").parse()?.unwrap();
assert_eq!("(let x = (x ? 1 : (3 * 2)))", expr.to_string());
Ok(())
}
#[test]
fn test_nested_ternary_right_associativity() -> Result<()> {
let expr = parser!("let i = a ? b : c ? d : e;").parse()?.unwrap();
assert_eq!("(let i = (a ? b : (c ? d : e)))", expr.to_string());
Ok(())
}

View File

@@ -277,6 +277,23 @@ pub struct WhileExpression<'a> {
pub body: BlockExpression<'a>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct TernaryExpression<'a> {
pub condition: Box<Spanned<Expression<'a>>>,
pub true_value: Box<Spanned<Expression<'a>>>,
pub false_value: Box<Spanned<Expression<'a>>>,
}
impl<'a> std::fmt::Display for TernaryExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"({} ? {} : {})",
self.condition, self.true_value, self.false_value
)
}
}
impl<'a> std::fmt::Display for WhileExpression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(while {} {})", self.condition, self.body)
@@ -366,6 +383,7 @@ pub enum Expression<'a> {
Priority(Box<Spanned<Expression<'a>>>),
Return(Box<Spanned<Expression<'a>>>),
Syscall(Spanned<SysCall<'a>>),
Ternary(Spanned<TernaryExpression<'a>>),
Variable(Spanned<Cow<'a, str>>),
While(Spanned<WhileExpression<'a>>),
}
@@ -393,6 +411,7 @@ impl<'a> std::fmt::Display for Expression<'a> {
Expression::Priority(e) => write!(f, "({})", e),
Expression::Return(e) => write!(f, "(return {})", e),
Expression::Syscall(e) => write!(f, "{}", e),
Expression::Ternary(e) => write!(f, "{}", e),
Expression::Variable(id) => write!(f, "{}", id),
Expression::While(e) => write!(f, "{}", e),
}

View File

@@ -225,6 +225,7 @@ pub enum TokenType<'a> {
#[token(".", symbol!(Dot))]
#[token("^", symbol!(Caret))]
#[token("%", symbol!(Percent))]
#[token("?", symbol!(Question))]
#[token("==", symbol!(Equal))]
#[token("!=", symbol!(NotEqual))]
#[token("&&", symbol!(LogicalAnd))]
@@ -535,6 +536,8 @@ pub enum Symbol {
Caret,
/// Represents the `%` symbol
Percent,
/// Represents the `?` symbol
Question,
// Double Character Symbols
/// Represents the `==` symbol
@@ -601,6 +604,7 @@ impl std::fmt::Display for Symbol {
Self::Asterisk => write!(f, "*"),
Self::Slash => write!(f, "/"),
Self::LessThan => write!(f, "<"),
Self::Question => write!(f, "?"),
Self::LessThanOrEqual => write!(f, "<="),
Self::GreaterThan => write!(f, ">"),
Self::GreaterThanOrEqual => write!(f, ">="),