5 Commits

43 changed files with 1483 additions and 306 deletions

View File

@@ -1,13 +1,5 @@
# Changelog # Changelog
[0.5.1]
- Fixed optimizer bug where `StoreBatch` and `StoreBatchNamed` instructions
were not recognized as reading operands, causing incorrect elimination of
necessary device property loads
- Added comprehensive register read tracking for `StoreSlot`, `JumpRelative`,
and `Alias` instructions in the optimizer
[0.5.0] [0.5.0]
- Added full tuple support: declarations, assignments, and returns - Added full tuple support: declarations, assignments, and returns

View File

@@ -2,7 +2,7 @@
<ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Slang</Name> <Name>Slang</Name>
<Author>JoeDiertay</Author> <Author>JoeDiertay</Author>
<Version>0.5.1</Version> <Version>0.5.0</Version>
<Description> <Description>
[h1]Slang: High-Level Programming for Stationeers[/h1] [h1]Slang: High-Level Programming for Stationeers[/h1]

View File

@@ -39,7 +39,7 @@ namespace Slang
{ {
public const string PluginGuid = "com.biddydev.slang"; public const string PluginGuid = "com.biddydev.slang";
public const string PluginName = "Slang"; public const string PluginName = "Slang";
public const string PluginVersion = "0.5.1"; public const string PluginVersion = "0.5.0";
private static Harmony? _harmony; private static Harmony? _harmony;

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AssemblyName>StationeersSlang</AssemblyName> <AssemblyName>StationeersSlang</AssemblyName>
<Description>Slang Compiler Bridge</Description> <Description>Slang Compiler Bridge</Description>
<Version>0.5.1</Version> <Version>0.5.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>

View File

@@ -1039,7 +1039,7 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]] [[package]]
name = "slang" name = "slang"
version = "0.5.1" version = "0.5.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "slang" name = "slang"
version = "0.5.1" version = "0.5.0"
edition = "2021" edition = "2021"
[workspace] [workspace]

View File

@@ -525,6 +525,28 @@ impl<'a> Compiler<'a> {
temp_name: Some(result_name), temp_name: Some(result_name),
})) }))
} }
Expression::BitwiseNot(inner_expr) => {
// Compile bitwise NOT using the NOT instruction
let (inner_str, cleanup) = self.compile_operand(*inner_expr, 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_instruction(
Instruction::Not(Operand::Register(result_reg), inner_str),
Some(expr.span),
)?;
if let Some(name) = cleanup {
scope.free_temp(name, None)?;
}
Ok(Some(CompileLocation {
location: result_loc,
temp_name: Some(result_name),
}))
}
Expression::TupleDeclaration(tuple_decl) => { Expression::TupleDeclaration(tuple_decl) => {
self.expression_tuple_declaration(tuple_decl.node, scope)?; self.expression_tuple_declaration(tuple_decl.node, scope)?;
Ok(None) Ok(None)
@@ -889,6 +911,32 @@ impl<'a> Compiler<'a> {
} }
(var_loc, None) (var_loc, None)
} }
Expression::BitwiseNot(_) => {
// Compile the bitwise NOT 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}` bitwise NOT expression did not produce a value"),
Some(name_span),
));
}
(var_loc, None)
}
_ => { _ => {
return Err(Error::Unknown( return Err(Error::Unknown(
format!("`{name_str}` declaration of this type is not supported/implemented."), format!("`{name_str}` declaration of this type is not supported/implemented."),
@@ -2114,13 +2162,36 @@ impl<'a> Compiler<'a> {
scope: &mut VariableScope<'a, '_>, scope: &mut VariableScope<'a, '_>,
) -> Result<CompileLocation<'a>, Error<'a>> { ) -> Result<CompileLocation<'a>, Error<'a>> {
fn fold_binary_expression<'a>(expr: &BinaryExpression<'a>) -> Option<Number> { fn fold_binary_expression<'a>(expr: &BinaryExpression<'a>) -> Option<Number> {
fn number_to_i64(n: Number) -> Option<i64> {
match n {
Number::Integer(i, _) => i64::try_from(i).ok(),
Number::Decimal(d, _) => {
// Convert decimal to i64 by truncating
let int_part = d.trunc();
i64::try_from(int_part.mantissa() / 10_i128.pow(int_part.scale())).ok()
}
}
}
fn i64_to_number(i: i64) -> Number {
Number::Integer(i as i128, Unit::None)
}
let (lhs, rhs) = match &expr { let (lhs, rhs) = match &expr {
BinaryExpression::Add(l, r) BinaryExpression::Add(l, r)
| BinaryExpression::Subtract(l, r) | BinaryExpression::Subtract(l, r)
| BinaryExpression::Multiply(l, r) | BinaryExpression::Multiply(l, r)
| BinaryExpression::Divide(l, r) | BinaryExpression::Divide(l, r)
| BinaryExpression::Exponent(l, r) | BinaryExpression::Exponent(l, r)
| BinaryExpression::Modulo(l, r) => (fold_expression(l)?, fold_expression(r)?), | BinaryExpression::Modulo(l, r)
| BinaryExpression::BitwiseAnd(l, r)
| BinaryExpression::BitwiseOr(l, r)
| BinaryExpression::BitwiseXor(l, r)
| BinaryExpression::LeftShift(l, r)
| BinaryExpression::RightShiftArithmetic(l, r)
| BinaryExpression::RightShiftLogical(l, r) => {
(fold_expression(l)?, fold_expression(r)?)
}
}; };
match expr { match expr {
@@ -2129,7 +2200,37 @@ impl<'a> Compiler<'a> {
BinaryExpression::Multiply(..) => Some(lhs * rhs), BinaryExpression::Multiply(..) => Some(lhs * rhs),
BinaryExpression::Divide(..) => Some(lhs / rhs), // Watch out for div by zero panics! BinaryExpression::Divide(..) => Some(lhs / rhs), // Watch out for div by zero panics!
BinaryExpression::Modulo(..) => Some(lhs % rhs), BinaryExpression::Modulo(..) => Some(lhs % rhs),
_ => None, // Handle Exponent separately or implement pow BinaryExpression::BitwiseAnd(..) => {
let lhs_int = number_to_i64(lhs)?;
let rhs_int = number_to_i64(rhs)?;
Some(i64_to_number(lhs_int & rhs_int))
}
BinaryExpression::BitwiseOr(..) => {
let lhs_int = number_to_i64(lhs)?;
let rhs_int = number_to_i64(rhs)?;
Some(i64_to_number(lhs_int | rhs_int))
}
BinaryExpression::BitwiseXor(..) => {
let lhs_int = number_to_i64(lhs)?;
let rhs_int = number_to_i64(rhs)?;
Some(i64_to_number(lhs_int ^ rhs_int))
}
BinaryExpression::LeftShift(..) => {
let lhs_int = number_to_i64(lhs)?;
let rhs_int = number_to_i64(rhs)?;
Some(i64_to_number(lhs_int << rhs_int))
}
BinaryExpression::RightShiftArithmetic(..) => {
let lhs_int = number_to_i64(lhs)?;
let rhs_int = number_to_i64(rhs)?;
Some(i64_to_number(lhs_int >> rhs_int))
}
BinaryExpression::RightShiftLogical(..) => {
let lhs_int = number_to_i64(lhs)?;
let rhs_int = number_to_i64(rhs)?;
Some(i64_to_number(lhs_int >> rhs_int))
}
_ => None, // Exponent not handled in compile-time folding
} }
} }
@@ -2189,6 +2290,24 @@ impl<'a> Compiler<'a> {
BinaryExpression::Modulo(l, r) => { BinaryExpression::Modulo(l, r) => {
(|into, lhs, rhs| Instruction::Mod(into, lhs, rhs), l, r) (|into, lhs, rhs| Instruction::Mod(into, lhs, rhs), l, r)
} }
BinaryExpression::BitwiseAnd(l, r) => {
(|into, lhs, rhs| Instruction::And(into, lhs, rhs), l, r)
}
BinaryExpression::BitwiseOr(l, r) => {
(|into, lhs, rhs| Instruction::Or(into, lhs, rhs), l, r)
}
BinaryExpression::BitwiseXor(l, r) => {
(|into, lhs, rhs| Instruction::Xor(into, lhs, rhs), l, r)
}
BinaryExpression::LeftShift(l, r) => {
(|into, lhs, rhs| Instruction::Sll(into, lhs, rhs), l, r)
}
BinaryExpression::RightShiftArithmetic(l, r) => {
(|into, lhs, rhs| Instruction::Sra(into, lhs, rhs), l, r)
}
BinaryExpression::RightShiftLogical(l, r) => {
(|into, lhs, rhs| Instruction::Srl(into, lhs, rhs), l, r)
}
}; };
let span = Self::merge_spans(left_expr.span, right_expr.span); let span = Self::merge_spans(left_expr.span, right_expr.span);

View File

@@ -232,12 +232,22 @@ pub enum Instruction<'a> {
/// `sle dst a b` - Set if Less or Equal /// `sle dst a b` - Set if Less or Equal
SetLe(Operand<'a>, Operand<'a>, Operand<'a>), SetLe(Operand<'a>, Operand<'a>, Operand<'a>),
/// `and dst a b` - Logical AND /// `and dst a b` - Bitwise AND
And(Operand<'a>, Operand<'a>, Operand<'a>), And(Operand<'a>, Operand<'a>, Operand<'a>),
/// `or dst a b` - Logical OR /// `or dst a b` - Bitwise OR
Or(Operand<'a>, Operand<'a>, Operand<'a>), Or(Operand<'a>, Operand<'a>, Operand<'a>),
/// `xor dst a b` - Logical XOR /// `xor dst a b` - Bitwise XOR
Xor(Operand<'a>, Operand<'a>, Operand<'a>), Xor(Operand<'a>, Operand<'a>, Operand<'a>),
/// `nor dst a b` - Bitwise NOR
Nor(Operand<'a>, Operand<'a>, Operand<'a>),
/// `not dst a` - Bitwise NOT
Not(Operand<'a>, Operand<'a>),
/// `sll dst a b` - Logical Left Shift
Sll(Operand<'a>, Operand<'a>, Operand<'a>),
/// `sra dst a b` - Arithmetic Right Shift
Sra(Operand<'a>, Operand<'a>, Operand<'a>),
/// `srl dst a b` - Logical Right Shift
Srl(Operand<'a>, Operand<'a>, Operand<'a>),
/// `push val` - Push to Stack /// `push val` - Push to Stack
Push(Operand<'a>), Push(Operand<'a>),
@@ -338,6 +348,11 @@ impl<'a> fmt::Display for Instruction<'a> {
Instruction::And(dst, a, b) => write!(f, "and {} {} {}", dst, a, b), Instruction::And(dst, a, b) => write!(f, "and {} {} {}", dst, a, b),
Instruction::Or(dst, a, b) => write!(f, "or {} {} {}", dst, a, b), Instruction::Or(dst, a, b) => write!(f, "or {} {} {}", dst, a, b),
Instruction::Xor(dst, a, b) => write!(f, "xor {} {} {}", dst, a, b), Instruction::Xor(dst, a, b) => write!(f, "xor {} {} {}", dst, a, b),
Instruction::Nor(dst, a, b) => write!(f, "nor {} {} {}", dst, a, b),
Instruction::Not(dst, a) => write!(f, "not {} {}", dst, a),
Instruction::Sll(dst, a, b) => write!(f, "sll {} {} {}", dst, a, b),
Instruction::Sra(dst, a, b) => write!(f, "sra {} {} {}", dst, a, b),
Instruction::Srl(dst, a, b) => write!(f, "srl {} {} {}", dst, a, b),
Instruction::Push(val) => write!(f, "push {}", val), Instruction::Push(val) => write!(f, "push {}", val),
Instruction::Pop(dst) => write!(f, "pop {}", dst), Instruction::Pop(dst) => write!(f, "pop {}", dst),
Instruction::Peek(dst) => write!(f, "peek {}", dst), Instruction::Peek(dst) => write!(f, "peek {}", dst),

View File

@@ -0,0 +1,77 @@
#[cfg(test)]
mod bitwise_tests {
use crate::common::compile_with_and_without_optimization;
use indoc::indoc;
#[test]
fn test_bitwise_operations() {
let source = indoc! {"
let a = 5;
let b = 3;
let and_result = a & b;
let or_result = a | b;
let xor_result = a ^ b;
let not_result = ~a;
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_bitwise_shifts() {
let source = indoc! {"
let x = 8;
let left_shift = x << 2;
let arithmetic_shift = x >> 1;
let logical_shift = x >>> 1;
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_bitwise_constant_folding() {
let source = indoc! {"
let packed = (1 << 16) & (255 << 8) & 2;
let mask = 0xFF & 0x0F;
let combined = (15 | 4096);
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_bitwise_with_variables() {
let source = indoc! {"
fn pack_bits(high, low) {
let packed = (high << 8) | low;
return packed;
}
fn extract_bits(value) {
let high = (value >> 8) & 0xFF;
let low = value & 0xFF;
return (high, low);
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_complex_bit_manipulation() {
let source = indoc! {"
fn encode_flags(enabled, mode, priority) {
let flag_byte = (enabled << 7) | (mode << 4) | priority;
return flag_byte;
}
fn decode_flags(byte) {
let enabled = (byte >> 7) & 1;
let mode = (byte >> 4) & 0x7;
let priority = byte & 0xF;
return (enabled, mode, priority);
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
}

View File

@@ -0,0 +1,46 @@
use compiler::Compiler;
use parser::Parser;
use tokenizer::Tokenizer;
/// Compile Slang source code and return both unoptimized and optimized output
pub fn compile_with_and_without_optimization(source: &str) -> String {
// Compile for unoptimized output
let tokenizer = Tokenizer::from(source);
let parser = Parser::new(tokenizer);
let compiler = Compiler::new(parser, None);
let result = compiler.compile();
// Get unoptimized output
let mut unoptimized_writer = std::io::BufWriter::new(Vec::new());
result
.instructions
.write(&mut unoptimized_writer)
.expect("Failed to write unoptimized output");
let unoptimized_bytes = unoptimized_writer
.into_inner()
.expect("Failed to get bytes");
let unoptimized = String::from_utf8(unoptimized_bytes).expect("Invalid UTF-8");
// Compile again for optimized output
let tokenizer2 = Tokenizer::from(source);
let parser2 = Parser::new(tokenizer2);
let compiler2 = Compiler::new(parser2, None);
let result2 = compiler2.compile();
// Apply optimizations
let optimized_instructions = optimizer::optimize(result2.instructions);
// Get optimized output
let mut optimized_writer = std::io::BufWriter::new(Vec::new());
optimized_instructions
.write(&mut optimized_writer)
.expect("Failed to write optimized output");
let optimized_bytes = optimized_writer.into_inner().expect("Failed to get bytes");
let optimized = String::from_utf8(optimized_bytes).expect("Invalid UTF-8");
// Combine both outputs with clear separators
format!(
"## Unoptimized Output\n\n{}\n## Optimized Output\n\n{}",
unoptimized, optimized
)
}

View File

@@ -0,0 +1,48 @@
#[cfg(test)]
mod function_tests {
use crate::common::compile_with_and_without_optimization;
use indoc::indoc;
#[test]
fn test_simple_leaf_function() {
let source = "fn test() { let x = 10; }";
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_function_with_call() {
let source = indoc! {"
fn add(a, b) { return a + b; }
fn main() { let x = add(5, 10); }
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_leaf_function_no_stack_frame() {
let source = indoc! {"
fn increment(x) {
x = x + 1;
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_nested_function_calls() {
let source = indoc! {"
fn add(a, b) { return a + b; }
fn multiply(x, y) { return x * 2; }
fn complex(a, b) {
let sum = add(a, b);
let doubled = multiply(sum, 2);
return doubled;
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
}

View File

@@ -4,180 +4,20 @@
//! and optimization passes work correctly together using snapshot testing. //! and optimization passes work correctly together using snapshot testing.
#[cfg(test)] #[cfg(test)]
mod tests { mod bitwise_tests;
use compiler::Compiler; #[cfg(test)]
mod common;
#[cfg(test)]
mod function_tests;
#[cfg(test)]
mod number_literal_tests;
#[cfg(test)]
mod optimization_tests;
#[cfg(test)]
mod integration_tests {
use crate::common::compile_with_and_without_optimization;
use indoc::indoc; use indoc::indoc;
use parser::Parser;
use tokenizer::Tokenizer;
/// Compile Slang source code and return both unoptimized and optimized output
fn compile_with_and_without_optimization(source: &str) -> String {
// Compile for unoptimized output
let tokenizer = Tokenizer::from(source);
let parser = Parser::new(tokenizer);
let compiler = Compiler::new(parser, None);
let result = compiler.compile();
assert!(
result.errors.is_empty(),
"Compilation errors: {:?}",
result.errors
);
// Get unoptimized output
let mut unoptimized_writer = std::io::BufWriter::new(Vec::new());
result
.instructions
.write(&mut unoptimized_writer)
.expect("Failed to write unoptimized output");
let unoptimized_bytes = unoptimized_writer
.into_inner()
.expect("Failed to get bytes");
let unoptimized = String::from_utf8(unoptimized_bytes).expect("Invalid UTF-8");
// Compile again for optimized output
let tokenizer2 = Tokenizer::from(source);
let parser2 = Parser::new(tokenizer2);
let compiler2 = Compiler::new(parser2, None);
let result2 = compiler2.compile();
// Apply optimizations
let optimized_instructions = optimizer::optimize(result2.instructions);
// Get optimized output
let mut optimized_writer = std::io::BufWriter::new(Vec::new());
optimized_instructions
.write(&mut optimized_writer)
.expect("Failed to write optimized output");
let optimized_bytes = optimized_writer.into_inner().expect("Failed to get bytes");
let optimized = String::from_utf8(optimized_bytes).expect("Invalid UTF-8");
// Combine both outputs with clear separators
format!(
"## Unoptimized Output\n\n{}\n## Optimized Output\n\n{}",
unoptimized, optimized
)
}
#[test]
fn test_simple_leaf_function() {
let source = "fn test() { let x = 10; }";
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_function_with_call() {
let source = indoc! {"
fn add(a, b) { return a + b; }
fn main() { let x = add(5, 10); }
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_constant_folding() {
let source = "let x = 5 + 10;";
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_algebraic_simplification() {
let source = "let x = 5; let y = x * 1;";
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_strength_reduction() {
let source = "fn double(x) { return x * 2; }";
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_dead_code_elimination() {
let source = indoc! {"
fn compute(x) {
let unused = 20;
return x + 1;
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_peephole_comparison_fusion() {
let source = indoc! {"
fn compare(x, y) {
if (x > y) {
let z = 1;
}
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_select_optimization() {
let source = indoc! {"
fn ternary(cond) {
let result = 0;
if (cond) {
result = 10;
} else {
result = 20;
}
return result;
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_leaf_function_no_stack_frame() {
let source = indoc! {"
fn increment(x) {
x = x + 1;
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_complex_arithmetic() {
let source = indoc! {"
fn compute(a, b, c) {
let x = a * 2;
let y = b + 0;
let z = c * 1;
return x + y + z;
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_nested_function_calls() {
let source = indoc! {"
fn add(a, b) { return a + b; }
fn multiply(x, y) { return x * 2; }
fn complex(a, b) {
let sum = add(a, b);
let doubled = multiply(sum, 2);
return doubled;
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test] #[test]
fn test_tuples() { fn test_tuples() {
@@ -225,20 +65,4 @@ mod tests {
let output = compile_with_and_without_optimization(source); let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output); insta::assert_snapshot!(output);
} }
#[test]
fn test_setbatched_with_member_access() {
let source = indoc! {r#"
const SENSOR = 20088;
const PANELS = hash("StructureSolarPanelDual");
loop {
setBatched(PANELS, "Horizontal", SENSOR.Horizontal);
setBatched(PANELS, "Vertical", SENSOR.Vertical + 90);
yield();
}
"#};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
} }

View File

@@ -0,0 +1,29 @@
#[cfg(test)]
mod number_literal_tests {
use crate::common::compile_with_and_without_optimization;
use indoc::indoc;
#[test]
fn test_binary_literals() {
let source = indoc! {"
let binary = 0b1010_1100;
let octal = 0o755;
let hex_upper = 0xDEAD_BEEF;
let hex_lower = 0xcafe;
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_number_literal_optimization() {
let source = indoc! {"
let decimal = 42_000;
let negative_hex = -0xFF;
let negative_binary = -0b1111_0000;
let temp_c = 100c;
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
}

View File

@@ -0,0 +1,82 @@
#[cfg(test)]
mod optimization_tests {
use crate::common::compile_with_and_without_optimization;
use indoc::indoc;
#[test]
fn test_constant_folding() {
let source = "let x = 5 + 10;";
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_algebraic_simplification() {
let source = "let x = 5; let y = x * 1;";
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_strength_reduction() {
let source = "fn double(x) { return x * 2; }";
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_dead_code_elimination() {
let source = indoc! {"
fn compute(x) {
let unused = 20;
return x + 1;
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_peephole_comparison_fusion() {
let source = indoc! {"
fn compare(x, y) {
if (x > y) {
let z = 1;
}
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_select_optimization() {
let source = indoc! {"
fn ternary(cond) {
let result = 0;
if (cond) {
result = 10;
} else {
result = 20;
}
return result;
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_complex_arithmetic() {
let source = indoc! {"
fn compute(a, b, c) {
let x = a * 2;
let y = b + 0;
let z = c * 1;
return x + y + z;
}
"};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
}

View File

@@ -0,0 +1,18 @@
---
source: libs/integration_tests/src/bitwise_tests.rs
assertion_line: 40
expression: output
---
## Unoptimized Output
j main
main:
move r8 0
move r9 15
move r10 4111
## Optimized Output
move r8 0
move r9 15
move r10 4111

View File

@@ -0,0 +1,29 @@
---
source: libs/integration_tests/src/bitwise_tests.rs
assertion_line: 17
expression: output
---
## Unoptimized Output
j main
main:
move r8 5
move r9 3
and r1 r8 r9
move r10 r1
or r2 r8 r9
move r11 r2
xor r3 r8 r9
move r12 r3
not r4 r8
move r13 r4
## Optimized Output
move r8 5
move r9 3
and r10 r8 r9
or r11 r8 r9
xor r12 r8 r9
not r4 r8
move r13 r4

View File

@@ -0,0 +1,26 @@
---
source: libs/integration_tests/src/bitwise_tests.rs
assertion_line: 29
expression: output
---
## Unoptimized Output
j main
main:
move r8 8
sll r1 r8 2
move r9 r1
sra r2 r8 1
move r10 r2
srl r3 r8 1
move r11 r3
## Optimized Output
move r8 8
sll r1 r8 2
move r9 r1
sra r2 r8 1
move r10 r2
srl r3 r8 1
move r11 r3

View File

@@ -0,0 +1,67 @@
---
source: libs/integration_tests/src/bitwise_tests.rs
assertion_line: 57
expression: output
---
## Unoptimized Output
j main
pack_bits:
pop r8
pop r9
push sp
push ra
sll r1 r9 8
or r2 r1 r8
move r10 r2
move r15 r10
j __internal_L1
__internal_L1:
pop ra
pop sp
j ra
extract_bits:
pop r8
push sp
push ra
sra r1 r8 8
and r2 r1 255
move r9 r2
and r3 r8 255
move r10 r3
push r9
push r10
sub r0 sp 4
get r0 db r0
move r15 r0
j __internal_L2
__internal_L2:
sub r0 sp 3
get ra db r0
j ra
## Optimized Output
j main
pop r8
pop r9
push sp
push ra
sll r1 r9 8
or r15 r1 r8
pop ra
pop sp
j ra
pop r8
push sp
push ra
sra r1 r8 8
and r9 r1 255
and r10 r8 255
push r9
push r10
sub r0 sp 4
get r15 db r0
sub r0 sp 3
get ra db r0
j ra

View File

@@ -0,0 +1,80 @@
---
source: libs/integration_tests/src/bitwise_tests.rs
assertion_line: 75
expression: output
---
## Unoptimized Output
j main
encode_flags:
pop r8
pop r9
pop r10
push sp
push ra
sll r1 r10 7
sll r2 r9 4
or r3 r1 r2
or r4 r3 r8
move r11 r4
move r15 r11
j __internal_L1
__internal_L1:
pop ra
pop sp
j ra
decode_flags:
pop r8
push sp
push ra
sra r1 r8 7
and r2 r1 1
move r9 r2
sra r3 r8 4
and r4 r3 7
move r10 r4
and r5 r8 15
move r11 r5
push r9
push r10
push r11
sub r0 sp 5
get r0 db r0
move r15 r0
j __internal_L2
__internal_L2:
sub r0 sp 4
get ra db r0
j ra
## Optimized Output
j main
pop r8
pop r9
pop r10
push sp
push ra
sll r1 r10 7
sll r2 r9 4
or r3 r1 r2
or r15 r3 r8
pop ra
pop sp
j ra
pop r8
push sp
push ra
sra r1 r8 7
and r9 r1 1
sra r3 r8 4
and r10 r3 7
and r11 r8 15
push r9
push r10
push r11
sub r0 sp 5
get r15 db r0
sub r0 sp 4
get ra db r0
j ra

View File

@@ -1,6 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/function_tests.rs
assertion_line: 70 assertion_line: 20
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -1,6 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/function_tests.rs
assertion_line: 144 assertion_line: 31
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -1,6 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/function_tests.rs
assertion_line: 173 assertion_line: 46
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -1,6 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/function_tests.rs
assertion_line: 60 assertion_line: 10
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -0,0 +1,224 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 54
expression: output
---
## Unoptimized Output
j main
waitForIdle:
push sp
push ra
yield
__internal_L2:
l r1 d0 Idle
seq r2 r1 0
beqz r2 __internal_L3
yield
j __internal_L2
__internal_L3:
__internal_L1:
pop ra
pop sp
j ra
deposit:
push sp
push ra
s d0 Setting 1
jal waitForIdle
move r1 r15
s d0 Activate 1
jal waitForIdle
move r2 r15
s d1 Open 0
__internal_L4:
pop ra
pop sp
j ra
checkAndHarvest:
pop r8
push sp
push ra
sle r1 r8 1
ls r15 d0 255 Seeding
slt r2 r15 1
or r3 r1 r2
beqz r3 __internal_L6
j __internal_L5
__internal_L6:
__internal_L7:
ls r15 d0 255 Mature
beqz r15 __internal_L8
yield
s d0 Activate 1
j __internal_L7
__internal_L8:
ls r15 d0 255 Occupied
move r9 r15
s d0 Setting 1
push r8
push r9
jal waitForIdle
pop r9
pop r8
move r4 r15
push r8
push r9
jal deposit
pop r9
pop r8
move r5 r15
beqz r9 __internal_L9
push r8
push r9
jal deposit
pop r9
pop r8
move r6 r15
__internal_L9:
s d0 Setting r8
push r8
push r9
jal waitForIdle
pop r9
pop r8
move r6 r15
ls r15 d0 0 Occupied
beqz r15 __internal_L10
s d0 Activate 1
__internal_L10:
push r8
push r9
jal waitForIdle
pop r9
pop r8
move r7 r15
__internal_L5:
pop ra
pop sp
j ra
main:
move r8 0
__internal_L11:
yield
l r1 d0 Idle
seq r2 r1 0
beqz r2 __internal_L13
j __internal_L11
__internal_L13:
add r3 r8 1
sgt r4 r3 19
add r5 r8 1
select r6 r4 2 r5
move r9 r6
push r8
push r9
push r8
jal checkAndHarvest
pop r9
pop r8
move r7 r15
s d0 Setting r9
move r8 r9
j __internal_L11
__internal_L12:
## Optimized Output
j 77
push sp
push ra
yield
l r1 d0 Idle
bne r1 0 8
yield
j 4
pop ra
pop sp
j ra
push sp
push ra
s d0 Setting 1
jal 1
move r1 r15
s d0 Activate 1
jal 1
move r2 r15
s d1 Open 0
pop ra
pop sp
j ra
pop r8
push sp
push ra
sle r1 r8 1
ls r15 d0 255 Seeding
slt r2 r15 1
or r3 r1 r2
beqz r3 32
j 74
ls r15 d0 255 Mature
beqz r15 37
yield
s d0 Activate 1
j 32
ls r9 d0 255 Occupied
s d0 Setting 1
push r8
push r9
jal 1
pop r9
pop r8
move r4 r15
push r8
push r9
jal 11
pop r9
pop r8
move r5 r15
beqz r9 58
push r8
push r9
jal 11
pop r9
pop r8
move r6 r15
s d0 Setting r8
push r8
push r9
jal 1
pop r9
pop r8
move r6 r15
ls r15 d0 0 Occupied
beqz r15 68
s d0 Activate 1
push r8
push r9
jal 1
pop r9
pop r8
move r7 r15
pop ra
pop sp
j ra
move r8 0
yield
l r1 d0 Idle
bne r1 0 82
j 78
add r3 r8 1
sgt r4 r3 19
add r5 r8 1
select r6 r4 2 r5
move r9 r6
push r8
push r9
push r8
jal 23
pop r9
pop r8
move r7 r15
s d0 Setting r9
move r8 r9
j 78

View File

@@ -0,0 +1,112 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 61
expression: output
---
## Unoptimized Output
j main
main:
s d2 Mode 1
s d2 On 0
move r8 0
move r9 0
__internal_L1:
yield
l r1 d0 Reagents
move r10 r1
sge r2 r10 100
sge r3 r9 2
or r4 r2 r3
beqz r4 __internal_L3
move r8 1
__internal_L3:
slt r5 r10 100
ls r15 d0 0 Occupied
seq r6 r15 0
and r7 r5 r6
add r1 r9 1
select r2 r7 r1 0
move r9 r2
l r3 d0 Rpm
slt r4 r3 1
and r5 r8 r4
beqz r5 __internal_L4
s d0 Open 1
seq r6 r10 0
ls r15 d0 0 Occupied
and r7 r6 r15
seq r1 r7 0
move r8 r1
__internal_L4:
seq r6 r8 0
s d0 On r6
ls r15 d1 0 Quantity
move r11 r15
l r7 d3 Pressure
sgt r1 r7 200
beqz r1 __internal_L5
j __internal_L1
__internal_L5:
sgt r2 r11 0
s d1 On r2
sgt r3 r11 0
s d1 Activate r3
l r4 d3 Pressure
sgt r5 r4 0
l r6 d1 Activate
or r7 r5 r6
s d2 On r7
l r1 d1 Activate
s db Setting r1
j __internal_L1
__internal_L2:
## Optimized Output
s d2 Mode 1
s d2 On 0
move r8 0
move r9 0
yield
l r10 d0 Reagents
sge r2 r10 100
sge r3 r9 2
or r4 r2 r3
beqz r4 11
move r8 1
slt r5 r10 100
ls r15 d0 0 Occupied
seq r6 r15 0
and r7 r5 r6
add r1 r9 1
select r2 r7 r1 0
move r9 r2
l r3 d0 Rpm
slt r4 r3 1
and r5 r8 r4
beqz r5 27
s d0 Open 1
seq r6 r10 0
ls r15 d0 0 Occupied
and r7 r6 r15
seq r8 r7 0
seq r6 r8 0
s d0 On r6
ls r15 d1 0 Quantity
move r11 r15
l r7 d3 Pressure
ble r7 200 34
j 4
sgt r2 r11 0
s d1 On r2
sgt r3 r11 0
s d1 Activate r3
l r4 d3 Pressure
sgt r5 r4 0
l r6 d1 Activate
or r7 r5 r6
s d2 On r7
l r1 d1 Activate
s db Setting r1
j 4

View File

@@ -0,0 +1,93 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 47
expression: output
---
## Unoptimized Output
j main
getSomethingElse:
pop r8
push sp
push ra
add r1 r8 1
move r15 r1
j __internal_L1
__internal_L1:
pop ra
pop sp
j ra
getSensorData:
push sp
push ra
l r1 d0 Vertical
push r1
l r2 d0 Horizontal
push r2
push 3
jal getSomethingElse
move r3 r15
push r3
sub r0 sp 5
get r0 db r0
move r15 r0
j __internal_L2
__internal_L2:
sub r0 sp 4
get ra db r0
j ra
main:
__internal_L3:
yield
jal getSensorData
pop r0
pop r9
pop r8
move sp r15
jal getSensorData
pop r0
pop r0
pop r9
move sp r15
s db Setting r9
j __internal_L3
__internal_L4:
## Optimized Output
j 23
pop r8
push sp
push ra
add r15 r8 1
pop ra
pop sp
j ra
push sp
push ra
l r1 d0 Vertical
push r1
l r2 d0 Horizontal
push r2
push 3
jal 1
move r3 r15
push r3
sub r0 sp 5
get r15 db r0
sub r0 sp 4
get ra db r0
j ra
yield
jal 8
pop r0
pop r9
pop r8
move sp r15
jal 8
pop r0
pop r0
pop r9
move sp r15
s db Setting r9
j 23

View File

@@ -0,0 +1,20 @@
---
source: libs/integration_tests/src/number_literal_tests.rs
assertion_line: 15
expression: output
---
## Unoptimized Output
j main
main:
move r8 172
move r9 493
move r10 3735928559
move r11 51966
## Optimized Output
move r8 172
move r9 493
move r10 3735928559
move r11 51966

View File

@@ -0,0 +1,20 @@
---
source: libs/integration_tests/src/number_literal_tests.rs
assertion_line: 27
expression: output
---
## Unoptimized Output
j main
main:
move r8 42000
move r9 -255
move r10 -240
move r11 373.15
## Optimized Output
move r8 42000
move r9 -255
move r10 -240
move r11 373.15

View File

@@ -1,5 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/optimization_tests.rs
assertion_line: 17
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -1,6 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/optimization_tests.rs
assertion_line: 158 assertion_line: 80
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -1,5 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/optimization_tests.rs
assertion_line: 10
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -1,6 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/optimization_tests.rs
assertion_line: 103 assertion_line: 36
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -1,6 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/optimization_tests.rs
assertion_line: 116 assertion_line: 49
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -1,6 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/optimization_tests.rs
assertion_line: 133 assertion_line: 66
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -1,6 +1,6 @@
--- ---
source: libs/integration_tests/src/lib.rs source: libs/integration_tests/src/optimization_tests.rs
assertion_line: 91 assertion_line: 24
expression: output expression: output
--- ---
## Unoptimized Output ## Unoptimized Output

View File

@@ -1,28 +0,0 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 242
expression: output
---
## Unoptimized Output
j main
main:
__internal_L1:
l r1 20088 Horizontal
sb -539224550 Horizontal r1
l r2 20088 Vertical
add r3 r2 90
sb -539224550 Vertical r3
yield
j __internal_L1
__internal_L2:
## Optimized Output
l r1 20088 Horizontal
sb -539224550 Horizontal r1
l r2 20088 Vertical
add r3 r2 90
sb -539224550 Vertical r3
yield
j 0

View File

@@ -125,9 +125,6 @@ pub fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
| Instruction::Pow(_, a, b) => check(a) || check(b), | Instruction::Pow(_, a, b) => check(a) || check(b),
Instruction::Load(_, a, _) => check(a), Instruction::Load(_, a, _) => check(a),
Instruction::Store(a, _, b) => check(a) || check(b), Instruction::Store(a, _, b) => check(a) || check(b),
Instruction::StoreBatch(a, _, b) => check(a) || check(b),
Instruction::StoreBatchNamed(a, b, _, c) => check(a) || check(b) || check(c),
Instruction::StoreSlot(a, b, _, c) => check(a) || check(b) || check(c),
Instruction::BranchEq(a, b, _) Instruction::BranchEq(a, b, _)
| Instruction::BranchNe(a, b, _) | Instruction::BranchNe(a, b, _)
| Instruction::BranchGt(a, b, _) | Instruction::BranchGt(a, b, _)
@@ -170,8 +167,6 @@ pub fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
Instruction::Atan2(_, a, b) | Instruction::Max(_, a, b) | Instruction::Min(_, a, b) => { Instruction::Atan2(_, a, b) | Instruction::Max(_, a, b) | Instruction::Min(_, a, b) => {
check(a) || check(b) check(a) || check(b)
} }
Instruction::JumpRelative(a) => check(a),
Instruction::Alias(_, a) => check(a),
_ => false, _ => false,
} }
} }

View File

@@ -294,12 +294,12 @@ impl<'a> Parser<'a> {
// Handle Infix operators (Binary, Logical, Assignment) // Handle Infix operators (Binary, Logical, Assignment)
if self_matches_peek!( if self_matches_peek!(
self, self,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign | Symbol::Question) TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || s.is_bitwise() || matches!(s, Symbol::Assign | Symbol::Question)
) { ) {
return Ok(Some(self.infix(lhs)?)); return Ok(Some(self.infix(lhs)?));
} 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() || matches!(s, Symbol::Assign | Symbol::Question) TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || s.is_bitwise() || matches!(s, Symbol::Assign | Symbol::Question)
) { ) {
self.tokenizer.seek(SeekFrom::Current(-1))?; self.tokenizer.seek(SeekFrom::Current(-1))?;
return Ok(Some(self.infix(lhs)?)); return Ok(Some(self.infix(lhs)?));
@@ -608,6 +608,23 @@ impl<'a> Parser<'a> {
}) })
} }
TokenType::Symbol(Symbol::BitwiseNot) => {
let start_span = self.current_span();
self.assign_next()?;
let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?;
let inner_with_postfix = self.parse_postfix(inner_expr)?;
let combined_span = Span {
start_line: start_span.start_line,
start_col: start_span.start_col,
end_line: inner_with_postfix.span.end_line,
end_col: inner_with_postfix.span.end_col,
};
Some(Spanned {
span: combined_span,
node: Expression::BitwiseNot(boxed!(inner_with_postfix)),
})
}
_ => { _ => {
return Err(Error::UnexpectedToken( return Err(Error::UnexpectedToken(
self.current_span(), self.current_span(),
@@ -699,6 +716,20 @@ impl<'a> Parser<'a> {
}), }),
} }
} }
TokenType::Symbol(Symbol::BitwiseNot) => {
self.assign_next()?;
let inner = self.get_infix_child_node()?;
let span = Span {
start_line: start_span.start_line,
start_col: start_span.start_col,
end_line: inner.span.end_line,
end_col: inner.span.end_col,
};
Spanned {
span,
node: Expression::BitwiseNot(boxed!(inner)),
}
}
_ => { _ => {
return Err(Error::UnexpectedToken( return Err(Error::UnexpectedToken(
self.current_span(), self.current_span(),
@@ -777,6 +808,7 @@ impl<'a> Parser<'a> {
| Expression::Variable(_) | Expression::Variable(_)
| Expression::Ternary(_) | Expression::Ternary(_)
| Expression::Negation(_) | Expression::Negation(_)
| Expression::BitwiseNot(_)
| Expression::MemberAccess(_) | Expression::MemberAccess(_)
| Expression::MethodCall(_) | Expression::MethodCall(_)
| Expression::Tuple(_) => {} | Expression::Tuple(_) => {}
@@ -796,7 +828,7 @@ impl<'a> Parser<'a> {
// Include Assign in the operator loop // Include Assign in the operator loop
while token_matches!( while token_matches!(
temp_token, temp_token,
TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign | Symbol::Question | Symbol::Colon) TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || s.is_bitwise() || matches!(s, Symbol::Assign | Symbol::Question | Symbol::Colon)
) { ) {
let operator = match temp_token.token_type { let operator = match temp_token.token_type {
TokenType::Symbol(s) => s, TokenType::Symbol(s) => s,
@@ -869,6 +901,24 @@ impl<'a> Parser<'a> {
Symbol::Minus => { Symbol::Minus => {
BinaryExpression::Subtract(boxed!(left), boxed!(right)) BinaryExpression::Subtract(boxed!(left), boxed!(right))
} }
Symbol::LeftShift => {
BinaryExpression::LeftShift(boxed!(left), boxed!(right))
}
Symbol::RightShiftArithmetic => {
BinaryExpression::RightShiftArithmetic(boxed!(left), boxed!(right))
}
Symbol::RightShiftLogical => {
BinaryExpression::RightShiftLogical(boxed!(left), boxed!(right))
}
Symbol::BitwiseAnd => {
BinaryExpression::BitwiseAnd(boxed!(left), boxed!(right))
}
Symbol::BitwiseOr => {
BinaryExpression::BitwiseOr(boxed!(left), boxed!(right))
}
Symbol::Caret => {
BinaryExpression::BitwiseXor(boxed!(left), boxed!(right))
}
_ => unreachable!(), _ => unreachable!(),
}; };
@@ -895,7 +945,22 @@ impl<'a> Parser<'a> {
// --- PRECEDENCE LEVEL 3: Additive (+, -) --- // --- PRECEDENCE LEVEL 3: Additive (+, -) ---
process_binary_ops!(Symbol::Plus | Symbol::Minus, BinaryExpression); process_binary_ops!(Symbol::Plus | Symbol::Minus, BinaryExpression);
// --- PRECEDENCE LEVEL 4: Comparison (<, >, <=, >=) --- // --- PRECEDENCE LEVEL 4: Shift Operations (<<, >>, >>>) ---
process_binary_ops!(
Symbol::LeftShift | Symbol::RightShiftArithmetic | Symbol::RightShiftLogical,
BinaryExpression
);
// --- PRECEDENCE LEVEL 5: Bitwise AND (&) ---
process_binary_ops!(Symbol::BitwiseAnd, BinaryExpression);
// --- PRECEDENCE LEVEL 6: Bitwise XOR (^) ---
process_binary_ops!(Symbol::Caret, BinaryExpression);
// --- PRECEDENCE LEVEL 7: Bitwise OR (|) ---
process_binary_ops!(Symbol::BitwiseOr, BinaryExpression);
// --- PRECEDENCE LEVEL 8: Comparison (<, >, <=, >=) ---
let mut current_iteration = 0; let mut current_iteration = 0;
for (i, operator) in operators.iter().enumerate() { for (i, operator) in operators.iter().enumerate() {
if operator.is_comparison() && !matches!(operator, Symbol::Equal | Symbol::NotEqual) { if operator.is_comparison() && !matches!(operator, Symbol::Equal | Symbol::NotEqual) {

View File

@@ -45,6 +45,12 @@ pub enum BinaryExpression<'a> {
Subtract(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>), Subtract(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Exponent(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>), Exponent(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
Modulo(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>), Modulo(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
BitwiseAnd(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
BitwiseOr(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
BitwiseXor(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
LeftShift(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
RightShiftArithmetic(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
RightShiftLogical(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
} }
impl<'a> std::fmt::Display for BinaryExpression<'a> { impl<'a> std::fmt::Display for BinaryExpression<'a> {
@@ -56,6 +62,12 @@ impl<'a> std::fmt::Display for BinaryExpression<'a> {
BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r), BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r),
BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r), BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r),
BinaryExpression::Modulo(l, r) => write!(f, "({} % {})", l, r), BinaryExpression::Modulo(l, r) => write!(f, "({} % {})", l, r),
BinaryExpression::BitwiseAnd(l, r) => write!(f, "({} & {})", l, r),
BinaryExpression::BitwiseOr(l, r) => write!(f, "({} | {})", l, r),
BinaryExpression::BitwiseXor(l, r) => write!(f, "({} ^ {})", l, r),
BinaryExpression::LeftShift(l, r) => write!(f, "({} << {})", l, r),
BinaryExpression::RightShiftArithmetic(l, r) => write!(f, "({} >> {})", l, r),
BinaryExpression::RightShiftLogical(l, r) => write!(f, "({} >>> {})", l, r),
} }
} }
} }
@@ -367,6 +379,7 @@ pub enum Expression<'a> {
Binary(Spanned<BinaryExpression<'a>>), Binary(Spanned<BinaryExpression<'a>>),
Block(Spanned<BlockExpression<'a>>), Block(Spanned<BlockExpression<'a>>),
Break(Span), Break(Span),
BitwiseNot(Box<Spanned<Expression<'a>>>),
ConstDeclaration(Spanned<ConstDeclarationExpression<'a>>), ConstDeclaration(Spanned<ConstDeclarationExpression<'a>>),
Continue(Span), Continue(Span),
Declaration(Spanned<Cow<'a, str>>, Box<Spanned<Expression<'a>>>), Declaration(Spanned<Cow<'a, str>>, Box<Spanned<Expression<'a>>>),
@@ -398,6 +411,7 @@ impl<'a> std::fmt::Display for Expression<'a> {
Expression::Binary(e) => write!(f, "{}", e), Expression::Binary(e) => write!(f, "{}", e),
Expression::Block(e) => write!(f, "{}", e), Expression::Block(e) => write!(f, "{}", e),
Expression::Break(_) => write!(f, "break"), Expression::Break(_) => write!(f, "break"),
Expression::BitwiseNot(e) => write!(f, "(~{})", e),
Expression::ConstDeclaration(e) => write!(f, "{}", e), Expression::ConstDeclaration(e) => write!(f, "{}", e),
Expression::Continue(_) => write!(f, "continue"), Expression::Continue(_) => write!(f, "continue"),
Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e), Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e),
@@ -439,4 +453,3 @@ impl<'a> std::fmt::Display for Expression<'a> {
} }
} }
} }

View File

@@ -135,6 +135,9 @@ pub enum TokenType<'a> {
/// Represents a string token /// Represents a string token
String(Cow<'a, str>), String(Cow<'a, str>),
#[regex(r"0[xX][0-9a-fA-F][0-9a-fA-F_]*", parse_number)]
#[regex(r"0[oO][0-7][0-7_]*", parse_number)]
#[regex(r"0[bB][01][01_]*", parse_number)]
#[regex(r"[0-9][0-9_]*(\.[0-9][0-9_]*)?([cfk])?", parse_number)] #[regex(r"[0-9][0-9_]*(\.[0-9][0-9_]*)?([cfk])?", parse_number)]
/// Represents a number token /// Represents a number token
Number(Number), Number(Number),
@@ -172,6 +175,23 @@ pub enum TokenType<'a> {
#[token(";", symbol!(Semicolon))] #[token(";", symbol!(Semicolon))]
#[token(":", symbol!(Colon))] #[token(":", symbol!(Colon))]
#[token(",", symbol!(Comma))] #[token(",", symbol!(Comma))]
#[token("?", symbol!(Question))]
#[token(".", symbol!(Dot))]
#[token("%", symbol!(Percent))]
#[token("~", symbol!(BitwiseNot))]
// Multi-character tokens must be defined before their single-character prefixes
// For tokens like >> and >>>, define >>> before >> to ensure correct matching
#[token(">>>", symbol!(RightShiftLogical))]
#[token(">>", symbol!(RightShiftArithmetic))]
#[token("<<", symbol!(LeftShift))]
#[token("==", symbol!(Equal))]
#[token("!=", symbol!(NotEqual))]
#[token("&&", symbol!(LogicalAnd))]
#[token("||", symbol!(LogicalOr))]
#[token("<=", symbol!(LessThanOrEqual))]
#[token(">=", symbol!(GreaterThanOrEqual))]
#[token("**", symbol!(Exp))]
// Single-character tokens
#[token("+", symbol!(Plus))] #[token("+", symbol!(Plus))]
#[token("-", symbol!(Minus))] #[token("-", symbol!(Minus))]
#[token("*", symbol!(Asterisk))] #[token("*", symbol!(Asterisk))]
@@ -180,17 +200,9 @@ pub enum TokenType<'a> {
#[token(">", symbol!(GreaterThan))] #[token(">", symbol!(GreaterThan))]
#[token("=", symbol!(Assign))] #[token("=", symbol!(Assign))]
#[token("!", symbol!(LogicalNot))] #[token("!", symbol!(LogicalNot))]
#[token(".", symbol!(Dot))]
#[token("^", symbol!(Caret))] #[token("^", symbol!(Caret))]
#[token("%", symbol!(Percent))] #[token("&", symbol!(BitwiseAnd))]
#[token("?", symbol!(Question))] #[token("|", symbol!(BitwiseOr))]
#[token("==", symbol!(Equal))]
#[token("!=", symbol!(NotEqual))]
#[token("&&", symbol!(LogicalAnd))]
#[token("||", symbol!(LogicalOr))]
#[token("<=", symbol!(LessThanOrEqual))]
#[token(">=", symbol!(GreaterThanOrEqual))]
#[token("**", symbol!(Exp))]
/// Represents a symbol token /// Represents a symbol token
Symbol(Symbol), Symbol(Symbol),
@@ -221,44 +233,75 @@ pub enum Comment<'a> {
fn parse_number<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Result<Number, LexError> { fn parse_number<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Result<Number, LexError> {
let slice = lexer.slice(); let slice = lexer.slice();
let last_char = slice.chars().last().unwrap_or_default();
let (num_str, suffix) = match last_char {
'c' | 'k' | 'f' => (&slice[..slice.len() - 1], Some(last_char)),
_ => (slice, None),
};
let clean_str = if num_str.contains('_') {
num_str.replace('_', "")
} else {
num_str.to_string()
};
let line = lexer.extras.line_count; let line = lexer.extras.line_count;
let mut span = lexer.span(); let mut span = lexer.span();
span.end -= lexer.extras.line_start_index; span.end -= lexer.extras.line_start_index;
span.start -= lexer.extras.line_start_index; span.start -= lexer.extras.line_start_index;
let unit = match suffix { // Determine the base and parse accordingly
Some('c') => Unit::Celsius, if slice.starts_with("0x") || slice.starts_with("0X") {
Some('f') => Unit::Fahrenheit, // Hexadecimal - no temperature suffix allowed
Some('k') => Unit::Kelvin, let clean_str = slice[2..].replace('_', "");
_ => Unit::None, Ok(Number::Integer(
}; i128::from_str_radix(&clean_str, 16)
if clean_str.contains('.') {
Ok(Number::Decimal(
clean_str
.parse::<Decimal>()
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?, .map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
unit, Unit::None,
))
} else if slice.starts_with("0o") || slice.starts_with("0O") {
// Octal - no temperature suffix allowed
let clean_str = slice[2..].replace('_', "");
Ok(Number::Integer(
i128::from_str_radix(&clean_str, 8)
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
Unit::None,
))
} else if slice.starts_with("0b") || slice.starts_with("0B") {
// Binary - no temperature suffix allowed
let clean_str = slice[2..].replace('_', "");
Ok(Number::Integer(
i128::from_str_radix(&clean_str, 2)
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
Unit::None,
)) ))
} else { } else {
Ok(Number::Integer( // Decimal (with optional temperature suffix)
clean_str let last_char = slice.chars().last().unwrap_or_default();
.parse::<i128>() let (num_str, suffix) = match last_char {
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?, 'c' | 'k' | 'f' => (&slice[..slice.len() - 1], Some(last_char)),
unit, _ => (slice, None),
)) };
let clean_str = if num_str.contains('_') {
num_str.replace('_', "")
} else {
num_str.to_string()
};
let unit = match suffix {
Some('c') => Unit::Celsius,
Some('f') => Unit::Fahrenheit,
Some('k') => Unit::Kelvin,
_ => Unit::None,
};
if clean_str.contains('.') {
// Decimal floating point
Ok(Number::Decimal(
clean_str
.parse::<Decimal>()
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
unit,
))
} else {
// Decimal integer
Ok(Number::Integer(
clean_str
.parse::<i128>()
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
unit,
))
}
} }
} }
@@ -615,6 +658,12 @@ pub enum Symbol {
Percent, Percent,
/// Represents the `?` symbol /// Represents the `?` symbol
Question, Question,
/// Represents the `&` symbol (bitwise AND)
BitwiseAnd,
/// Represents the `|` symbol (bitwise OR)
BitwiseOr,
/// Represents the `~` symbol (bitwise NOT)
BitwiseNot,
// Double Character Symbols // Double Character Symbols
/// Represents the `==` symbol /// Represents the `==` symbol
@@ -629,6 +678,12 @@ pub enum Symbol {
LessThanOrEqual, LessThanOrEqual,
/// Represents the `>=` symbol /// Represents the `>=` symbol
GreaterThanOrEqual, GreaterThanOrEqual,
/// Represents the `<<` symbol (left shift)
LeftShift,
/// Represents the `>>` symbol (arithmetic right shift)
RightShiftArithmetic,
/// Represents the `>>>` symbol (logical right shift)
RightShiftLogical,
/// Represents the `**` symbol /// Represents the `**` symbol
Exp, Exp,
} }
@@ -643,6 +698,19 @@ impl Symbol {
| Symbol::Slash | Symbol::Slash
| Symbol::Exp | Symbol::Exp
| Symbol::Percent | Symbol::Percent
| Symbol::Caret
)
}
pub fn is_bitwise(&self) -> bool {
matches!(
self,
Symbol::BitwiseAnd
| Symbol::BitwiseOr
| Symbol::BitwiseNot
| Symbol::LeftShift
| Symbol::RightShiftArithmetic
| Symbol::RightShiftLogical
) )
} }
@@ -693,6 +761,12 @@ impl std::fmt::Display for Symbol {
Self::NotEqual => write!(f, "!="), Self::NotEqual => write!(f, "!="),
Self::Dot => write!(f, "."), Self::Dot => write!(f, "."),
Self::Caret => write!(f, "^"), Self::Caret => write!(f, "^"),
Self::BitwiseAnd => write!(f, "&"),
Self::BitwiseOr => write!(f, "|"),
Self::BitwiseNot => write!(f, "~"),
Self::LeftShift => write!(f, "<<"),
Self::RightShiftArithmetic => write!(f, ">>"),
Self::RightShiftLogical => write!(f, ">>>"),
Self::Exp => write!(f, "**"), Self::Exp => write!(f, "**"),
} }
} }
@@ -715,7 +789,7 @@ documented! {
/// } /// }
/// ``` /// ```
Continue, Continue,
/// Prepresents the `const` keyword. This allows you to define a variable that will never /// Represents the `const` keyword. This allows you to define a variable that will never
/// change throughout the lifetime of the program, similar to `define` in IC10. If you are /// change throughout the lifetime of the program, similar to `define` in IC10. If you are
/// not planning on mutating the variable (changing it), it is recommend you store it as a /// not planning on mutating the variable (changing it), it is recommend you store it as a
/// const, as the compiler will not assign it to a register or stack variable. /// const, as the compiler will not assign it to a register or stack variable.
@@ -846,6 +920,7 @@ documented! {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::TokenType; use super::TokenType;
use super::{Number, Unit};
use logos::Logos; use logos::Logos;
#[test] #[test]
@@ -856,7 +931,141 @@ mod tests {
let tokens = lexer.collect::<Vec<_>>(); let tokens = lexer.collect::<Vec<_>>();
assert!(!tokens.iter().any(|res| res.is_err())); assert!(
!tokens.iter().any(|res| res.is_err()),
"Expected no lexing errors for CRLF endings"
);
Ok(())
}
#[test]
fn test_binary_literals() -> anyhow::Result<()> {
let src = "0b1010 0b0 0b1111_0000";
let lexer = TokenType::lexer(src);
let tokens: Vec<_> = lexer.collect::<Result<Vec<_>, _>>()?;
assert_eq!(tokens.len(), 3);
assert!(
matches!(
&tokens[0],
TokenType::Number(Number::Integer(10, Unit::None))
),
"Expected binary 0b1010 = 10"
);
assert!(
matches!(
&tokens[1],
TokenType::Number(Number::Integer(0, Unit::None))
),
"Expected binary 0b0 = 0"
);
assert!(
matches!(
&tokens[2],
TokenType::Number(Number::Integer(240, Unit::None))
),
"Expected binary 0b1111_0000 = 240"
);
Ok(())
}
#[test]
fn test_octal_literals() -> anyhow::Result<()> {
let src = "0o77 0o0 0o7_777";
let lexer = TokenType::lexer(src);
let tokens: Vec<_> = lexer.collect::<Result<Vec<_>, _>>()?;
assert_eq!(tokens.len(), 3);
assert!(
matches!(
&tokens[0],
TokenType::Number(Number::Integer(63, Unit::None))
),
"Expected octal 0o77 = 63"
);
assert!(
matches!(
&tokens[1],
TokenType::Number(Number::Integer(0, Unit::None))
),
"Expected octal 0o0 = 0"
);
assert!(
matches!(
&tokens[2],
TokenType::Number(Number::Integer(4095, Unit::None))
),
"Expected octal 0o7_777 = 4095"
);
Ok(())
}
#[test]
fn test_hex_literals() -> anyhow::Result<()> {
let src = "0xFF 0x0 0xFF_FF 0xFF_FF_FF";
let lexer = TokenType::lexer(src);
let tokens: Vec<_> = lexer.collect::<Result<Vec<_>, _>>()?;
assert_eq!(tokens.len(), 4);
assert!(
matches!(
&tokens[0],
TokenType::Number(Number::Integer(255, Unit::None))
),
"Expected hex 0xFF = 255"
);
assert!(
matches!(
&tokens[1],
TokenType::Number(Number::Integer(0, Unit::None))
),
"Expected hex 0x0 = 0"
);
assert!(
matches!(
&tokens[2],
TokenType::Number(Number::Integer(65535, Unit::None))
),
"Expected hex 0xFF_FF = 65535"
);
assert!(
matches!(
&tokens[3],
TokenType::Number(Number::Integer(16777215, Unit::None))
),
"Expected hex 0xFF_FF_FF = 16777215"
);
Ok(())
}
#[test]
fn test_hex_literals_lowercase() -> anyhow::Result<()> {
let src = "0xff 0xab 0xcd_ef";
let lexer = TokenType::lexer(src);
let tokens: Vec<_> = lexer.collect::<Result<Vec<_>, _>>()?;
assert_eq!(tokens.len(), 3);
assert!(
matches!(
&tokens[0],
TokenType::Number(Number::Integer(255, Unit::None))
),
"Expected hex 0xff = 255"
);
assert!(
matches!(
&tokens[1],
TokenType::Number(Number::Integer(171, Unit::None))
),
"Expected hex 0xab = 171"
);
assert!(
matches!(
&tokens[2],
TokenType::Number(Number::Integer(52719, Unit::None))
),
"Expected hex 0xcd_ef = 52719"
);
Ok(()) Ok(())
} }
} }