4 Commits

13 changed files with 2040 additions and 93 deletions

View File

@@ -83,7 +83,7 @@ cargo test --package compiler --lib -- test::tuple_literals::test::test_tuple_li
```bash
cd rust_compiler
# Compile Slang code to IC10 using current compiler changes
echo 'let x = 5;' | cargo run --bin slang -
echo 'let x = 5;' | cargo run --bin slang --
# Or from file
cargo run --bin slang -- input.slang -o output.ic10
# Optimize the output with -z flag
@@ -99,8 +99,13 @@ Tests follow a macro pattern in [libs/compiler/src/test/mod.rs](rust_compiler/li
```rust
#[test]
fn test_name() -> Result<()> {
let output = compile!("slang code here");
assert_eq!(expected_ic10, output);
let output = compile!(debug "slang code here");
assert_eq!(
output,
indoc! {
"Expected IC10 output here"
}
);
Ok(())
}
```

View File

@@ -4,15 +4,21 @@ use pretty_assertions::assert_eq;
#[test]
fn simple_binary_expression() -> Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let i = 1 + 2;
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -27,8 +33,8 @@ fn simple_binary_expression() -> Result<()> {
#[test]
fn nested_binary_expressions() -> Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
fn calculateArgs(arg1, arg2, arg3) {
return (arg1 + arg2) * arg3;
@@ -38,8 +44,14 @@ fn nested_binary_expressions() -> Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -72,15 +84,21 @@ fn nested_binary_expressions() -> Result<()> {
#[test]
fn stress_test_constant_folding() -> Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let negationHell = (-1 + -2) * (-3 + (-4 * (-5 + -6)));
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -95,16 +113,22 @@ fn stress_test_constant_folding() -> Result<()> {
#[test]
fn test_constant_folding_with_variables_mixed_in() -> Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
r#"
device self = "db";
let i = 1 - 3 * (1 + 123.4) * self.Setting + 245c;
"#
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -123,15 +147,21 @@ fn test_constant_folding_with_variables_mixed_in() -> Result<()> {
#[test]
fn test_ternary_expression() -> Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
r#"
let i = 1 > 2 ? 15 : 20;
"#
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -148,16 +178,22 @@ fn test_ternary_expression() -> Result<()> {
#[test]
fn test_ternary_expression_assignment() -> Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
r#"
let i = 0;
i = 1 > 2 ? 15 : 20;
"#
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -175,15 +211,21 @@ fn test_ternary_expression_assignment() -> Result<()> {
#[test]
fn test_negative_literals() -> Result<()> {
let compiled = compile!(
debug
let result = compile!(
check
r#"
let item = -10c - 20c;
"#
);
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -198,16 +240,22 @@ fn test_negative_literals() -> Result<()> {
#[test]
fn test_mismatched_temperature_literals() -> Result<()> {
let compiled = compile!(
debug
let result = compile!(
check
r#"
let item = -10c - 100k;
let item2 = item + 500c;
"#
);
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main

View File

@@ -3,8 +3,8 @@ use pretty_assertions::assert_eq;
#[test]
fn test_if_statement() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let a = 10;
if (a > 5) {
@@ -13,8 +13,14 @@ fn test_if_statement() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -33,8 +39,8 @@ fn test_if_statement() -> anyhow::Result<()> {
#[test]
fn test_if_else_statement() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let a = 0;
if (10 > 5) {
@@ -45,8 +51,14 @@ fn test_if_else_statement() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -68,8 +80,8 @@ fn test_if_else_statement() -> anyhow::Result<()> {
#[test]
fn test_if_else_if_statement() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let a = 0;
if (a == 1) {
@@ -82,8 +94,14 @@ fn test_if_else_if_statement() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -111,8 +129,8 @@ fn test_if_else_if_statement() -> anyhow::Result<()> {
#[test]
fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let a = 1;
let b = 2;
@@ -129,8 +147,14 @@ fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main

View File

@@ -3,14 +3,20 @@ use pretty_assertions::assert_eq;
#[test]
fn no_arguments() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
fn doSomething() {};
let i = doSomething();
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
let to_test = indoc! {
"
j main
@@ -25,15 +31,15 @@ fn no_arguments() -> anyhow::Result<()> {
"
};
assert_eq!(compiled, to_test);
assert_eq!(result.output, to_test);
Ok(())
}
#[test]
fn let_var_args() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
fn mul2(arg1) {
return arg1 * 2;
@@ -46,8 +52,14 @@ fn let_var_args() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -99,8 +111,8 @@ fn incorrect_args_count() -> anyhow::Result<()> {
#[test]
fn inline_literal_args() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
fn doSomething(arg1, arg2) {
return 5;
@@ -110,8 +122,14 @@ fn inline_literal_args() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -141,8 +159,8 @@ fn inline_literal_args() -> anyhow::Result<()> {
#[test]
fn mixed_args() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let arg1 = 123;
let returnValue = doSomething(arg1, 456);
@@ -150,8 +168,14 @@ fn mixed_args() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -179,8 +203,8 @@ fn mixed_args() -> anyhow::Result<()> {
#[test]
fn with_return_statement() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
fn doSomething(arg1) {
return 456;
@@ -190,8 +214,14 @@ fn with_return_statement() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -216,8 +246,8 @@ fn with_return_statement() -> anyhow::Result<()> {
#[test]
fn with_negative_return_literal() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
fn doSomething() {
return -1;
@@ -226,8 +256,14 @@ fn with_negative_return_literal() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main

View File

@@ -0,0 +1,224 @@
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn device_declaration() -> anyhow::Result<()> {
let compiled = compile! {
debug "
device d0 = \"d0\";
"
};
// Declaration only emits the jump label header
assert_eq!(compiled, "j main\n");
Ok(())
}
#[test]
fn device_property_read() -> anyhow::Result<()> {
let compiled = compile! {
debug "
device ac = \"d0\";
let temp = ac.Temperature;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
l r1 d0 Temperature
move r8 r1
"
}
);
Ok(())
}
#[test]
fn device_property_write() -> anyhow::Result<()> {
let compiled = compile! {
debug "
device ac = \"d0\";
ac.On = 1;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
s d0 On 1
"
}
);
Ok(())
}
#[test]
fn multiple_device_declarations() -> anyhow::Result<()> {
let compiled = compile! {
debug "
device d0 = \"d0\";
device d1 = \"d1\";
device d2 = \"d2\";
"
};
// Declarations only emit the header when unused
assert_eq!(compiled, "j main\n");
Ok(())
}
#[test]
fn device_with_variable_interaction() -> anyhow::Result<()> {
let compiled = compile! {
debug "
device sensor = \"d0\";
let reading = sensor.Temperature;
let threshold = 373.15;
let alert = reading > threshold;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
l r1 d0 Temperature
move r8 r1
move r9 373.15
sgt r2 r8 r9
move r10 r2
"
}
);
Ok(())
}
#[test]
fn device_property_in_arithmetic() -> anyhow::Result<()> {
let compiled = compile! {
debug "
device d0 = \"d0\";
let result = d0.Temperature + 100;
"
};
// Verify that we load property, add 100, and move to result
assert_eq!(
compiled,
indoc! {
"
j main
main:
l r1 d0 Temperature
add r2 r1 100
move r8 r2
"
}
);
Ok(())
}
#[test]
fn device_used_in_function() -> anyhow::Result<()> {
let compiled = compile! {
debug "
device d0 = \"d0\";
fn check_power() {
return d0.On;
};
let powered = check_power();
"
};
assert_eq!(
compiled,
indoc! {
"
j main
check_power:
push ra
l r1 d0 On
move r15 r1
j __internal_L1
__internal_L1:
pop ra
j ra
main:
jal check_power
move r8 r15
"
}
);
Ok(())
}
#[test]
fn device_in_conditional() -> anyhow::Result<()> {
let compiled = compile! {
debug "
device d0 = \"d0\";
if (d0.On) {
let x = 1;
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
l r1 d0 On
beqz r1 __internal_L1
move r8 1
__internal_L1:
"
}
);
Ok(())
}
#[test]
fn device_property_with_underscore_name() -> anyhow::Result<()> {
let compiled = compile! {
debug "
device cool_device = \"d0\";
let value = cool_device.SomeProperty;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
l r1 d0 SomeProperty
move r8 r1
"
}
);
Ok(())
}

View File

@@ -0,0 +1,532 @@
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn zero_value_handling() -> anyhow::Result<()> {
let result = compile! {
check "
let x = 0;
let y = x + 0;
let z = x * 100;
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
result.output,
indoc! {
"
j main
main:
move r8 0
add r1 r8 0
move r9 r1
mul r2 r8 100
move r10 r2
"
}
);
Ok(())
}
#[test]
fn negative_number_handling() -> anyhow::Result<()> {
let result = compile! {
check "
let x = -100;
let y = -x;
let z = -(-50);
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
result.output,
indoc! {
"
j main
main:
move r8 -100
sub r1 0 r8
move r9 r1
move r10 50
"
}
);
Ok(())
}
#[test]
fn large_number_constants() -> anyhow::Result<()> {
let result = compile! {
check "
let x = 999999999;
let y = x + 1;
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
result.output,
indoc! {
"
j main
main:
move r8 999999999
add r1 r8 1
move r9 r1
"
}
);
Ok(())
}
#[test]
fn floating_point_precision() -> anyhow::Result<()> {
let result = compile! {
check "
let pi = 3.14159265;
let e = 2.71828182;
let sum = pi + e;
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
result.output,
indoc! {
"
j main
main:
move r8 3.14159265
move r9 2.71828182
add r1 r8 r9
move r10 r1
"
}
);
Ok(())
}
#[test]
fn temperature_unit_conversion() -> anyhow::Result<()> {
let result = compile! {
check "
let celsius = 20c;
let fahrenheit = 68f;
let kelvin = 293.15k;
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
result.output,
indoc! {
"
j main
main:
move r8 293.15
move r9 293.15
move r10 293.15
"
}
);
Ok(())
}
#[test]
fn mixed_temperature_units() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let c = 0c;
let f = 32f;
let k = 273.15k;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 273.15
move r9 273.15
move r10 273.15
"
}
);
Ok(())
}
#[test]
fn boolean_constant_folding() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = true;
let y = false;
let z = true && true;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 1
move r9 0
and r1 1 1
move r10 r1
"
}
);
Ok(())
}
#[test]
fn empty_block() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 5;
{
}
let y = x;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 5
move r9 r8
"
}
);
Ok(())
}
#[test]
fn multiple_statements_same_line() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 1; let y = 2; let z = 3;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 1
move r9 2
move r10 3
"
}
);
Ok(())
}
#[test]
fn function_with_no_return() -> anyhow::Result<()> {
let compiled = compile! {
debug "
fn no_return() {
let x = 5;
};
no_return();
"
};
assert_eq!(
compiled,
indoc! {
"
j main
no_return:
push ra
move r8 5
__internal_L1:
pop ra
j ra
main:
jal no_return
move r1 r15
"
}
);
Ok(())
}
#[test]
fn deeply_nested_expressions() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = ((((((((1 + 2) + 3) + 4) + 5) + 6) + 7) + 8) + 9);
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 45
"
}
);
Ok(())
}
#[test]
fn constant_folding_with_operations() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 10 * 5 + 3 - 2;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 51
"
}
);
Ok(())
}
#[test]
fn constant_folding_with_division() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 100 / 2 / 5;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 10
"
}
);
Ok(())
}
#[test]
fn modulo_operation() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 17 % 5;
let y = 10 % 3;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 2
move r9 1
"
}
);
Ok(())
}
#[test]
fn exponentiation() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 2 ^ 8;
let y = 3 ^ 3;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r0 3
move r1 3
"
}
);
Ok(())
}
#[test]
fn comparison_with_zero() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 0 == 0;
let y = 0 < 1;
let z = 0 > -1;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
seq r1 0 0
move r8 r1
slt r2 0 1
move r9 r2
sgt r3 0 -1
move r10 r3
"
}
);
Ok(())
}
#[test]
fn boolean_negation_edge_cases() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = !0;
let y = !1;
let z = !100;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
seq r1 0 0
move r8 r1
seq r2 1 0
move r9 r2
seq r3 100 0
move r10 r3
"
}
);
Ok(())
}
#[test]
fn function_with_many_parameters() -> anyhow::Result<()> {
let compiled = compile! {
debug "
fn many_params(a, b, c, d, e, f, g, h) {
return a + b + c + d + e + f + g + h;
};
let result = many_params(1, 2, 3, 4, 5, 6, 7, 8);
"
};
assert_eq!(
compiled,
indoc! {
"
j main
many_params:
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
push ra
sub r0 sp 2
get r1 db r0
add r2 r1 r14
add r3 r2 r13
add r4 r3 r12
add r5 r4 r11
add r6 r5 r10
add r7 r6 r9
add r1 r7 r8
move r15 r1
j __internal_L1
__internal_L1:
pop ra
sub sp sp 1
j ra
main:
push 1
push 2
push 3
push 4
push 5
push 6
push 7
push 8
jal many_params
move r8 r15
"
}
);
Ok(())
}

View File

@@ -0,0 +1,197 @@
use crate::Error;
use crate::variable_manager::Error as ScopeError;
#[test]
fn unknown_identifier_error() {
let errors = compile! {
result "let x = unknown_var;"
};
assert_eq!(errors.len(), 1);
match &errors[0] {
Error::UnknownIdentifier(name, _) => {
assert_eq!(name.as_ref(), "unknown_var");
}
_ => panic!("Expected UnknownIdentifier error, got {:?}", errors[0]),
}
}
#[test]
fn duplicate_identifier_error() {
let errors = compile! {
result "
let x = 5;
let x = 10;
"
};
assert_eq!(errors.len(), 1);
match &errors[0] {
Error::Scope(ScopeError::DuplicateVariable(name, _)) => {
assert_eq!(name.as_ref(), "x");
}
_ => panic!("Expected DuplicateIdentifier error, got {:?}", errors[0]),
}
}
#[test]
fn const_reassignment_error() {
let errors = compile! {
result "
const PI = 3.14;
PI = 2.71;
"
};
assert_eq!(errors.len(), 1);
match &errors[0] {
Error::ConstAssignment(name, _) => {
assert_eq!(name.as_ref(), "PI");
}
_ => panic!("Expected ConstAssignment error, got {:?}", errors[0]),
}
}
#[test]
fn unknown_function_call_error() {
let errors = compile! {
result "
let result = unknown_function();
"
};
assert_eq!(errors.len(), 1);
match &errors[0] {
Error::UnknownIdentifier(name, _) => {
assert_eq!(name.as_ref(), "unknown_function");
}
_ => panic!("Expected UnknownIdentifier error, got {:?}", errors[0]),
}
}
#[test]
fn argument_mismatch_error() {
let errors = compile! {
result "
fn add(a, b) {
return a + b;
};
let result = add(1);
"
};
// The error should be an AgrumentMismatch
assert!(
errors
.iter()
.any(|e| matches!(e, Error::AgrumentMismatch(_, _)))
);
}
#[test]
fn tuple_size_mismatch_error() {
let errors = compile! {
result "
fn pair() {
return (1, 2);
};
let (x, y, z) = pair();
"
};
assert!(
errors
.iter()
.any(|e| matches!(e, Error::TupleSizeMismatch(2, 3, _)))
);
}
#[test]
fn multiple_errors_reported() {
let errors = compile! {
result "
let x = unknown1;
let x = 5;
let y = unknown2;
"
};
// Should have at least 3 errors
assert!(
errors.len() >= 2,
"Expected at least 2 errors, got {}",
errors.len()
);
}
#[test]
fn return_outside_function_error() {
let errors = compile! {
result "
let x = 5;
return x;
"
};
// Should have an error about return outside function
assert!(
!errors.is_empty(),
"Expected error for return outside function"
);
}
#[test]
fn break_outside_loop_error() {
let errors = compile! {
result "
break;
"
};
assert!(!errors.is_empty(), "Expected error for break outside loop");
}
#[test]
fn continue_outside_loop_error() {
let errors = compile! {
result "
continue;
"
};
assert!(
!errors.is_empty(),
"Expected error for continue outside loop"
);
}
#[test]
fn device_reassignment_error() {
let errors = compile! {
result "
device d0 = \"d0\";
device d0 = \"d1\";
"
};
assert!(
errors
.iter()
.any(|e| matches!(e, Error::DuplicateIdentifier(_, _)))
);
}
#[test]
fn invalid_device_error() {
let errors = compile! {
result "
device d0 = \"d0\";
d0 = \"d1\";
"
};
// Device reassignment should fail
assert!(!errors.is_empty(), "Expected error for device reassignment");
}

View File

@@ -3,8 +3,8 @@ use pretty_assertions::assert_eq;
#[test]
fn test_comparison_expressions() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let isGreater = 10 > 5;
let isLess = 5 < 10;
@@ -15,8 +15,14 @@ fn test_comparison_expressions() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -42,8 +48,8 @@ fn test_comparison_expressions() -> anyhow::Result<()> {
#[test]
fn test_logical_and_or_not() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let logic1 = 1 && 1;
let logic2 = 1 || 0;
@@ -51,8 +57,14 @@ fn test_logical_and_or_not() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -72,15 +84,21 @@ fn test_logical_and_or_not() -> anyhow::Result<()> {
#[test]
fn test_complex_logic() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let logic = (10 > 5) && (5 < 10);
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -98,15 +116,21 @@ fn test_complex_logic() -> anyhow::Result<()> {
#[test]
fn test_math_with_logic() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let logic = (1 + 2) > 1;
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -122,15 +146,21 @@ fn test_math_with_logic() -> anyhow::Result<()> {
#[test]
fn test_boolean_in_logic() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let res = true && false;
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -146,8 +176,8 @@ fn test_boolean_in_logic() -> anyhow::Result<()> {
#[test]
fn test_invert_a_boolean() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let i = true;
let y = !i;
@@ -156,8 +186,14 @@ fn test_invert_a_boolean() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
assert_eq!(
compiled,
result.output,
indoc! {
"
j main

View File

@@ -3,8 +3,8 @@ use pretty_assertions::assert_eq;
#[test]
fn test_infinite_loop() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let a = 0;
loop {
@@ -13,9 +13,15 @@ fn test_infinite_loop() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
// __internal_Labels: L1 (start), L2 (end)
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -35,8 +41,8 @@ fn test_infinite_loop() -> anyhow::Result<()> {
#[test]
fn test_loop_break() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let a = 0;
loop {
@@ -48,9 +54,15 @@ fn test_loop_break() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
// __internal_Labels: L1 (start), L2 (end), L3 (if end - implicit else label)
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -74,8 +86,8 @@ fn test_loop_break() -> anyhow::Result<()> {
#[test]
fn test_while_loop() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
"
let a = 0;
while (a < 10) {
@@ -84,9 +96,15 @@ fn test_while_loop() -> anyhow::Result<()> {
"
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
// __internal_Labels: L1 (start), L2 (end)
assert_eq!(
compiled,
result.output,
indoc! {
"
j main
@@ -108,8 +126,8 @@ fn test_while_loop() -> anyhow::Result<()> {
#[test]
fn test_loop_continue() -> anyhow::Result<()> {
let compiled = compile! {
debug
let result = compile! {
check
r#"
let a = 0;
loop {
@@ -122,9 +140,15 @@ fn test_loop_continue() -> anyhow::Result<()> {
"#
};
assert!(
result.errors.is_empty(),
"Expected no errors, got: {:?}",
result.errors
);
// __internal_Labels: L1 (start), L2 (end), L3 (if end)
assert_eq!(
compiled,
result.output,
indoc! {
"
j main

View File

@@ -6,6 +6,12 @@ macro_rules! output {
};
}
/// Represents both compilation errors and compiled output
pub struct CompilationCheckResult {
pub errors: Vec<crate::Error<'static>>,
pub output: String,
}
#[cfg_attr(test, macro_export)]
macro_rules! compile {
($source:expr) => {{
@@ -37,14 +43,34 @@ macro_rules! compile {
res.instructions.write(&mut writer)?;
output!(writer)
}};
(check $source:expr) => {{
let mut writer = std::io::BufWriter::new(Vec::new());
let compiler = crate::Compiler::new(
parser::Parser::new(tokenizer::Tokenizer::from($source)),
Some(crate::CompilerConfig { debug: true }),
);
let res = compiler.compile();
res.instructions.write(&mut writer)?;
let output = output!(writer);
crate::test::CompilationCheckResult {
errors: res.errors,
output,
}
}};
}
mod binary_expression;
mod branching;
mod declaration_function_invocation;
mod declaration_literal;
mod device_access;
mod edge_cases;
mod error_handling;
mod function_declaration;
mod logic_expression;
mod loops;
mod math_syscall;
mod negation_priority;
mod scoping;
mod syscall;
mod tuple_literals;

View File

@@ -0,0 +1,310 @@
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn simple_negation() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = -5;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 -5
"
}
);
Ok(())
}
#[test]
fn negation_of_variable() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 10;
let y = -x;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 10
sub r1 0 r8
move r9 r1
"
}
);
Ok(())
}
#[test]
fn double_negation() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = -(-5);
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 5
"
}
);
Ok(())
}
#[test]
fn negation_in_expression() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 10 + (-5);
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 5
"
}
);
Ok(())
}
#[test]
fn negation_with_multiplication() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = -3 * 4;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 -12
"
}
);
Ok(())
}
#[test]
fn parentheses_priority() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = (2 + 3) * 4;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 20
"
}
);
Ok(())
}
#[test]
fn nested_parentheses() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = ((2 + 3) * (4 - 1));
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 15
"
}
);
Ok(())
}
#[test]
fn parentheses_with_variables() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let a = 5;
let b = 10;
let c = (a + b) * 2;
"
};
// Should calculate (5 + 10) * 2 = 30
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 5
move r9 10
add r1 r8 r9
mul r2 r1 2
move r10 r2
"
}
);
Ok(())
}
#[test]
fn priority_affects_result() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let with_priority = (2 + 3) * 4;
let without_priority = 2 + 3 * 4;
"
};
// with_priority should be 20, without_priority should be 14
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 20
move r9 14
"
}
);
Ok(())
}
#[test]
fn negation_of_expression() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = -(2 + 3);
"
};
// Should be -5 (constant folded)
assert_eq!(
compiled,
indoc! {
"
j main
main:
sub r1 0 5
move r8 r1
"
}
);
Ok(())
}
#[test]
fn complex_negation_and_priority() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = -((10 - 5) * 2);
"
};
// Should be -(5 * 2) = -10 (folded to constant)
assert_eq!(
compiled,
indoc! {
"
j main
main:
sub r1 0 10
move r8 r1
"
}
);
Ok(())
}
#[test]
fn negation_in_logical_expression() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = !(-5);
"
};
// -5 is truthy, so !(-5) should be 0
assert_eq!(
compiled,
indoc! {
"
j main
main:
sub r1 0 5
seq r2 r1 0
move r8 r2
"
}
);
Ok(())
}
#[test]
fn parentheses_in_comparison() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = (10 + 5) > (3 * 4);
"
};
// (10 + 5) = 15 > (3 * 4) = 12, so true (1)
assert_eq!(
compiled,
indoc! {
"
j main
main:
sgt r1 15 12
move r8 r1
"
}
);
Ok(())
}

View File

@@ -0,0 +1,428 @@
use indoc::indoc;
use pretty_assertions::assert_eq;
#[test]
fn block_scope() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 10;
{
let y = 20;
let z = x + y;
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 10
move r9 20
add r1 r8 r9
move r10 r1
"
}
);
Ok(())
}
#[test]
fn variable_scope_isolation() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 10;
{
let x = 20;
let y = x;
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 10
move r9 20
move r10 r9
"
}
);
Ok(())
}
#[test]
fn function_parameter_scope() -> anyhow::Result<()> {
let compiled = compile! {
debug "
fn double(x) {
return x * 2;
};
let result = double(5);
"
};
assert_eq!(
compiled,
indoc! {
"
j main
double:
pop r8
push ra
mul r1 r8 2
move r15 r1
j __internal_L1
__internal_L1:
pop ra
j ra
main:
push 5
jal double
move r8 r15
"
}
);
Ok(())
}
#[test]
fn function_local_variables() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let global = 100;
fn test() {
let local = 50;
return local + global;
};
let result = test();
"
};
assert_eq!(
compiled,
indoc! {
"
j main
test:
push ra
move r8 50
add r1 r8 r0
move r15 r1
j __internal_L1
__internal_L1:
pop ra
j ra
main:
move r8 100
push r8
jal test
pop r8
move r9 r15
"
}
);
Ok(())
}
#[test]
fn nested_block_scopes() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 1;
{
let x = 2;
{
let x = 3;
let y = x;
}
let z = x;
}
let w = x;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 1
move r9 2
move r10 3
move r11 r10
move r10 r9
move r9 r8
"
}
);
Ok(())
}
#[test]
fn variable_shadowing_in_conditional() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 10;
if (true) {
let x = 20;
}
let y = x;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 10
beqz 1 __internal_L1
move r9 20
__internal_L1:
move r9 r8
"
}
);
Ok(())
}
#[test]
fn variable_shadowing_in_loop() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 0;
loop {
let x = x + 1;
if (x > 5) {
break;
}
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 0
__internal_L1:
add r1 r8 1
move r9 r1
sgt r2 r9 5
beqz r2 __internal_L3
j __internal_L2
__internal_L3:
j __internal_L1
__internal_L2:
"
}
);
Ok(())
}
#[test]
fn const_scope() -> anyhow::Result<()> {
let compiled = compile! {
debug "
const PI = 3.14;
{
const PI = 2.71;
let x = PI;
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 2.71
"
}
);
Ok(())
}
#[test]
fn device_in_scope() -> anyhow::Result<()> {
let compiled = compile! {
debug "
device d0 = \"d0\";
{
let value = d0.Temperature;
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
l r1 d0 Temperature
move r8 r1
"
}
);
Ok(())
}
#[test]
fn function_scope_isolation() -> anyhow::Result<()> {
let compiled = compile! {
debug "
fn func1() {
let x = 10;
return x;
};
fn func2() {
let x = 20;
return x;
};
let a = func1();
let b = func2();
"
};
assert_eq!(
compiled,
indoc! {
"
j main
func1:
push ra
move r8 10
move r15 r8
j __internal_L1
__internal_L1:
pop ra
j ra
func2:
push ra
move r8 20
move r15 r8
j __internal_L2
__internal_L2:
pop ra
j ra
main:
jal func1
move r8 r15
push r8
jal func2
pop r8
move r9 r15
"
}
);
Ok(())
}
#[test]
fn tuple_unpacking_scope() -> anyhow::Result<()> {
let compiled = compile! {
debug "
fn pair() {
return (1, 2);
};
{
let (x, y) = pair();
let z = x + y;
}
"
};
assert_eq!(
compiled,
indoc! {
"
j main
pair:
move r15 sp
push ra
push 1
push 2
move r15 1
sub r0 sp 3
get ra db r0
j ra
main:
jal pair
pop r9
pop r8
move sp r15
add r1 r8 r9
move r10 r1
"
}
);
Ok(())
}
#[test]
fn shadowing_doesnt_affect_outer() -> anyhow::Result<()> {
let compiled = compile! {
debug "
let x = 5;
let y = x;
{
let x = 10;
let z = x;
}
let w = x + y;
"
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
move r8 5
move r9 r8
move r10 10
move r11 r10
add r1 r8 r9
move r10 r1
"
}
);
Ok(())
}

View File

@@ -603,15 +603,13 @@ impl<'a> Compiler<'a> {
let name_str = var_name.node;
let name_span = var_name.span;
// optimization. Check for a negated numeric literal
if let Expression::Negation(box_expr) = &expr.node
&& let Expression::Literal(spanned_lit) = &box_expr.node
&& let Literal::Number(neg_num) = &spanned_lit.node
{
// optimization. Check for a negated numeric literal (including nested negations)
// e.g., -5, -(-5), -(-(5)), etc.
if let Some(num) = self.try_fold_negation(&expr.node) {
let loc =
scope.add_variable(name_str.clone(), LocationRequest::Persist, Some(name_span))?;
self.emit_variable_assignment(&loc, Operand::Number((-*neg_num).into()))?;
self.emit_variable_assignment(&loc, Operand::Number(num.into()))?;
return Ok(Some(CompileLocation {
location: loc,
temp_name: None,
@@ -848,6 +846,46 @@ impl<'a> Compiler<'a> {
}
(var_loc, None)
}
Expression::Negation(_) => {
// Use try_fold_negation to see if this is a constant folded negation
if let Some(num) = self.try_fold_negation(&expr.node) {
let loc = scope.add_variable(
name_str.clone(),
LocationRequest::Persist,
Some(name_span),
)?;
self.emit_variable_assignment(&loc, Operand::Number(num.into()))?;
return Ok(Some(CompileLocation {
location: loc,
temp_name: None,
}));
}
// Otherwise, compile the negation expression
let result = self.expression(expr, scope)?;
let var_loc = scope.add_variable(
name_str.clone(),
LocationRequest::Persist,
Some(name_span),
)?;
if let Some(res) = result {
// Move result from temp to new persistent variable
let result_reg = self.resolve_register(&res.location)?;
self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?;
// Free the temp result
if let Some(name) = res.temp_name {
scope.free_temp(name, None)?;
}
} else {
return Err(Error::Unknown(
format!("`{name_str}` negation expression did not produce a value"),
Some(name_span),
));
}
(var_loc, None)
}
_ => {
return Err(Error::Unknown(
format!("`{name_str}` declaration of this type is not supported/implemented."),
@@ -2034,6 +2072,25 @@ impl<'a> Compiler<'a> {
)
}
/// Recursively fold negations of numeric literals, e.g., -5 => 5, -(-5) => 5
fn try_fold_negation(&self, expr: &Expression) -> Option<Number> {
match expr {
// Base case: plain number literal
Expression::Literal(lit) => {
if let Literal::Number(n) = lit.node {
Some(n)
} else {
None
}
}
// Recursive case: negation of something foldable
Expression::Negation(inner) => self.try_fold_negation(&inner.node).map(|n| -n),
// Parentheses just pass through
Expression::Priority(inner) => self.try_fold_negation(&inner.node),
_ => None,
}
}
fn expression_binary(
&mut self,
expr: Spanned<BinaryExpression<'a>>,