Compare commits
14 Commits
0.5.1
...
2070c2e4ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
2070c2e4ca
|
|||
|
4c704b8960
|
|||
|
3c7300d2e1
|
|||
|
647c3d29d5
|
|||
|
e3a38ec110
|
|||
|
1f98ab8d75
|
|||
|
76c5df5dc2
|
|||
|
0999ae8aed
|
|||
|
072a6b9ea6
|
|||
|
f5a28dfd6d
|
|||
|
9966009500
|
|||
|
bc7c77846f
|
|||
|
76add65235
|
|||
|
e56414c251
|
@@ -272,3 +272,108 @@ fn device_property_with_underscore_name() -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn device_index_read() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
check "
|
||||
device printer = \"d0\";
|
||||
let value = printer[255];
|
||||
"
|
||||
};
|
||||
|
||||
assert!(
|
||||
compiled.errors.is_empty(),
|
||||
"Expected no errors, got: {:?}",
|
||||
compiled.errors
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compiled.output,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
get r1 d0 255
|
||||
move r8 r1
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn device_index_write() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
check "
|
||||
device printer = \"d0\";
|
||||
printer[255] = 42;
|
||||
"
|
||||
};
|
||||
|
||||
assert!(
|
||||
compiled.errors.is_empty(),
|
||||
"Expected no errors, got: {:?}",
|
||||
compiled.errors
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compiled.output,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
put d0 255 42
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn device_index_db_not_allowed() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
check "
|
||||
device stack = \"db\";
|
||||
let x = stack[10];
|
||||
"
|
||||
};
|
||||
|
||||
assert!(
|
||||
!compiled.errors.is_empty(),
|
||||
"Expected error for db indexing"
|
||||
);
|
||||
assert!(
|
||||
compiled.errors[0]
|
||||
.to_string()
|
||||
.contains("Direct stack access on 'db' is not yet supported"),
|
||||
"Expected db restriction error"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn device_index_db_write_not_allowed() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
check "
|
||||
device stack = \"db\";
|
||||
stack[10] = 42;
|
||||
"
|
||||
};
|
||||
|
||||
assert!(
|
||||
!compiled.errors.is_empty(),
|
||||
"Expected error for db indexing"
|
||||
);
|
||||
assert!(
|
||||
compiled.errors[0]
|
||||
.to_string()
|
||||
.contains("Direct stack access on 'db' is not yet supported"),
|
||||
"Expected db restriction error"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -287,3 +287,68 @@ fn test_load_reagent() -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn test_clr() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
check
|
||||
"
|
||||
device stackDevice = \"d0\";
|
||||
clr(stackDevice);
|
||||
let deviceRef = 5;
|
||||
clr(deviceRef);
|
||||
"
|
||||
};
|
||||
|
||||
assert!(
|
||||
compiled.errors.is_empty(),
|
||||
"Expected no errors, got: {:?}",
|
||||
compiled.errors
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compiled.output,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
clr d0
|
||||
move r8 5
|
||||
clr r8
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn test_rmap() -> anyhow::Result<()> {
|
||||
let compiled = compile! {
|
||||
check
|
||||
"
|
||||
device printer = \"d0\";
|
||||
let reagentHash = 12345;
|
||||
let itemHash = rmap(printer, reagentHash);
|
||||
"
|
||||
};
|
||||
|
||||
assert!(
|
||||
compiled.errors.is_empty(),
|
||||
"Expected no errors, got: {:?}",
|
||||
compiled.errors
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compiled.output,
|
||||
indoc! {
|
||||
"
|
||||
j main
|
||||
main:
|
||||
move r8 12345
|
||||
rmap r15 d0 r8
|
||||
move r9 r15
|
||||
"
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ use parser::{
|
||||
tree_node::{
|
||||
AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression,
|
||||
DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression,
|
||||
InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression,
|
||||
LoopExpression, MemberAccessExpression, Spanned, TernaryExpression,
|
||||
IndexAccessExpression, InvocationExpression, Literal, LiteralOr, LiteralOrVariable,
|
||||
LogicalExpression, LoopExpression, MemberAccessExpression, Spanned, TernaryExpression,
|
||||
TupleAssignmentExpression, TupleDeclarationExpression, WhileExpression,
|
||||
},
|
||||
};
|
||||
@@ -67,6 +67,9 @@ pub enum Error<'a> {
|
||||
#[error("Expected a {0}-tuple, but you're trying to destructure into {1} variables")]
|
||||
TupleSizeMismatch(usize, usize, Span),
|
||||
|
||||
#[error("{0}")]
|
||||
OperationNotSupported(String, Span),
|
||||
|
||||
#[error("{0}")]
|
||||
Unknown(String, Option<Span>),
|
||||
}
|
||||
@@ -89,7 +92,8 @@ impl<'a> From<Error<'a>> for lsp_types::Diagnostic {
|
||||
| ConstAssignment(_, span)
|
||||
| DeviceAssignment(_, span)
|
||||
| AgrumentMismatch(_, span)
|
||||
| TupleSizeMismatch(_, _, span) => Diagnostic {
|
||||
| TupleSizeMismatch(_, _, span)
|
||||
| OperationNotSupported(_, span) => Diagnostic {
|
||||
range: span.into(),
|
||||
message: value.to_string(),
|
||||
severity: Some(DiagnosticSeverity::ERROR),
|
||||
@@ -387,6 +391,26 @@ impl<'a> Compiler<'a> {
|
||||
}
|
||||
Expression::Ternary(tern) => Ok(Some(self.expression_ternary(tern.node, scope)?)),
|
||||
Expression::Invocation(expr_invoke) => {
|
||||
// Special case: hash() with string literal can be evaluated at compile time
|
||||
if expr_invoke.node.name.node == "hash" && expr_invoke.node.arguments.len() == 1 {
|
||||
if let Expression::Literal(Spanned {
|
||||
node: Literal::String(str_to_hash),
|
||||
..
|
||||
}) = &expr_invoke.node.arguments[0].node
|
||||
{
|
||||
// Evaluate hash at compile time
|
||||
let hash_value = crc_hash_signed(str_to_hash);
|
||||
return Ok(Some(CompileLocation {
|
||||
location: VariableLocation::Constant(Literal::Number(Number::Integer(
|
||||
hash_value,
|
||||
Unit::None,
|
||||
))),
|
||||
temp_name: None,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Non-constant hash calls or other function calls
|
||||
self.expression_function_invocation(expr_invoke, scope)?;
|
||||
// Invocation returns result in r15 (RETURN_REGISTER).
|
||||
// If used as an expression, we must move it to a temp to avoid overwrite.
|
||||
@@ -487,6 +511,50 @@ impl<'a> Compiler<'a> {
|
||||
temp_name: Some(result_name),
|
||||
}))
|
||||
}
|
||||
Expression::IndexAccess(access) => {
|
||||
// "get" behavior (e.g. `let x = d0[255]`)
|
||||
let IndexAccessExpression { object, index } = access.node;
|
||||
|
||||
// 1. Resolve the object to a device string
|
||||
let (device, dev_cleanup) = self.resolve_device(*object, scope)?;
|
||||
|
||||
// Check if device is "db" (not allowed)
|
||||
if let Operand::Device(ref dev_str) = device {
|
||||
if dev_str.as_ref() == "db" {
|
||||
return Err(Error::OperationNotSupported(
|
||||
"Direct stack access on 'db' is not yet supported".to_string(),
|
||||
expr.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Compile the index expression to get the address
|
||||
let (addr, addr_cleanup) = self.compile_operand(*index, scope)?;
|
||||
|
||||
// 3. Allocate a temp register for the result
|
||||
let result_name = self.next_temp_name();
|
||||
let loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
|
||||
let reg = self.resolve_register(&loc)?;
|
||||
|
||||
// 4. Emit get instruction: get rX device address
|
||||
self.write_instruction(
|
||||
Instruction::Get(Operand::Register(reg), device, addr),
|
||||
Some(expr.span),
|
||||
)?;
|
||||
|
||||
// 5. Cleanup
|
||||
if let Some(c) = dev_cleanup {
|
||||
scope.free_temp(c, None)?;
|
||||
}
|
||||
if let Some(c) = addr_cleanup {
|
||||
scope.free_temp(c, None)?;
|
||||
}
|
||||
|
||||
Ok(Some(CompileLocation {
|
||||
location: loc,
|
||||
temp_name: Some(result_name),
|
||||
}))
|
||||
}
|
||||
Expression::MethodCall(call) => {
|
||||
// Methods are not yet fully supported (e.g. `d0.SomeFunc()`).
|
||||
// This would likely map to specialized syscalls or batch instructions.
|
||||
@@ -525,6 +593,28 @@ impl<'a> Compiler<'a> {
|
||||
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) => {
|
||||
self.expression_tuple_declaration(tuple_decl.node, scope)?;
|
||||
Ok(None)
|
||||
@@ -889,6 +979,58 @@ impl<'a> Compiler<'a> {
|
||||
}
|
||||
(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)
|
||||
}
|
||||
Expression::IndexAccess(_) => {
|
||||
// Compile the index access 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}` index access 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."),
|
||||
@@ -1025,6 +1167,37 @@ impl<'a> Compiler<'a> {
|
||||
scope.free_temp(c, None)?;
|
||||
}
|
||||
}
|
||||
Expression::IndexAccess(access) => {
|
||||
// Put instruction: put device address value
|
||||
let IndexAccessExpression { object, index } = access.node;
|
||||
|
||||
let (device, dev_cleanup) = self.resolve_device(*object, scope)?;
|
||||
|
||||
// Check if device is "db" (not allowed)
|
||||
if let Operand::Device(ref dev_str) = device {
|
||||
if dev_str.as_ref() == "db" {
|
||||
return Err(Error::OperationNotSupported(
|
||||
"Direct stack access on 'db' is not yet supported".to_string(),
|
||||
assignee.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let (addr, addr_cleanup) = self.compile_operand(*index, scope)?;
|
||||
let (val, val_cleanup) = self.compile_operand(*expression, scope)?;
|
||||
|
||||
self.write_instruction(Instruction::Put(device, addr, val), Some(assignee.span))?;
|
||||
|
||||
if let Some(c) = dev_cleanup {
|
||||
scope.free_temp(c, None)?;
|
||||
}
|
||||
if let Some(c) = addr_cleanup {
|
||||
scope.free_temp(c, None)?;
|
||||
}
|
||||
if let Some(c) = val_cleanup {
|
||||
scope.free_temp(c, None)?;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(Error::Unknown(
|
||||
@@ -2113,14 +2286,40 @@ impl<'a> Compiler<'a> {
|
||||
expr: Spanned<BinaryExpression<'a>>,
|
||||
scope: &mut VariableScope<'a, '_>,
|
||||
) -> Result<CompileLocation<'a>, Error<'a>> {
|
||||
fn fold_binary_expression<'a>(expr: &BinaryExpression<'a>) -> Option<Number> {
|
||||
fn fold_binary_expression<'a>(
|
||||
expr: &BinaryExpression<'a>,
|
||||
scope: &VariableScope<'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 {
|
||||
BinaryExpression::Add(l, r)
|
||||
| BinaryExpression::Subtract(l, r)
|
||||
| BinaryExpression::Multiply(l, r)
|
||||
| BinaryExpression::Divide(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, scope)?, fold_expression(r, scope)?)
|
||||
}
|
||||
};
|
||||
|
||||
match expr {
|
||||
@@ -2129,11 +2328,44 @@ impl<'a> Compiler<'a> {
|
||||
BinaryExpression::Multiply(..) => Some(lhs * rhs),
|
||||
BinaryExpression::Divide(..) => Some(lhs / rhs), // Watch out for div by zero panics!
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_expression<'a>(expr: &Expression<'a>) -> Option<Number> {
|
||||
fn fold_expression<'a>(
|
||||
expr: &Expression<'a>,
|
||||
scope: &VariableScope<'a, '_>,
|
||||
) -> Option<Number> {
|
||||
match expr {
|
||||
// 1. Base Case: It's already a number
|
||||
Expression::Literal(lit) => match lit.node {
|
||||
@@ -2142,23 +2374,60 @@ impl<'a> Compiler<'a> {
|
||||
},
|
||||
|
||||
// 2. Handle Parentheses: Just recurse deeper
|
||||
Expression::Priority(inner) => fold_expression(&inner.node),
|
||||
Expression::Priority(inner) => fold_expression(&inner.node, scope),
|
||||
|
||||
// 3. Handle Negation: Recurse, then negate
|
||||
Expression::Negation(inner) => {
|
||||
let val = fold_expression(&inner.node)?;
|
||||
let val = fold_expression(&inner.node, scope)?;
|
||||
Some(-val) // Requires impl Neg for Number
|
||||
}
|
||||
|
||||
// 4. Handle Binary Ops: Recurse BOTH sides, then combine
|
||||
Expression::Binary(bin) => fold_binary_expression(&bin.node),
|
||||
Expression::Binary(bin) => fold_binary_expression(&bin.node, scope),
|
||||
|
||||
// 5. Anything else (Variables, Function Calls) cannot be compile-time folded
|
||||
// 5. Handle Variable Reference: Check if it's a const
|
||||
Expression::Variable(var_id) => {
|
||||
if let Ok(var_loc) = scope.get_location_of(var_id, None) {
|
||||
if let VariableLocation::Constant(Literal::Number(num)) = var_loc {
|
||||
return Some(num);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// 6. Handle hash() syscall - evaluates to a constant at compile time
|
||||
Expression::Syscall(Spanned {
|
||||
node:
|
||||
SysCall::System(System::Hash(Spanned {
|
||||
node: Literal::String(str_to_hash),
|
||||
..
|
||||
})),
|
||||
..
|
||||
}) => {
|
||||
return Some(Number::Integer(crc_hash_signed(str_to_hash), Unit::None));
|
||||
}
|
||||
|
||||
// 7. Handle hash() macro as invocation - evaluates to a constant at compile time
|
||||
Expression::Invocation(inv) => {
|
||||
if inv.node.name.node == "hash" && inv.node.arguments.len() == 1 {
|
||||
if let Expression::Literal(Spanned {
|
||||
node: Literal::String(str_to_hash),
|
||||
..
|
||||
}) = &inv.node.arguments[0].node
|
||||
{
|
||||
// hash() takes a string literal and returns a signed integer
|
||||
return Some(Number::Integer(crc_hash_signed(str_to_hash), Unit::None));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// 8. Anything else cannot be compile-time folded
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(const_lit) = fold_binary_expression(&expr.node) {
|
||||
if let Some(const_lit) = fold_binary_expression(&expr.node, scope) {
|
||||
return Ok(CompileLocation {
|
||||
location: VariableLocation::Constant(Literal::Number(const_lit)),
|
||||
temp_name: None,
|
||||
@@ -2189,6 +2458,24 @@ impl<'a> Compiler<'a> {
|
||||
BinaryExpression::Modulo(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);
|
||||
@@ -2654,6 +2941,13 @@ impl<'a> Compiler<'a> {
|
||||
cleanup!(var_cleanup);
|
||||
Ok(None)
|
||||
}
|
||||
System::Clr(device) => {
|
||||
let (op, var_cleanup) = self.compile_operand(*device, scope)?;
|
||||
self.write_instruction(Instruction::Clr(op), Some(span))?;
|
||||
|
||||
cleanup!(var_cleanup);
|
||||
Ok(None)
|
||||
}
|
||||
System::Hash(hash_arg) => {
|
||||
let Spanned {
|
||||
node: Literal::String(str_lit),
|
||||
@@ -2973,6 +3267,42 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
cleanup!(reagent_cleanup, reagent_hash_cleanup, device_cleanup);
|
||||
|
||||
Ok(Some(CompileLocation {
|
||||
location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
|
||||
temp_name: None,
|
||||
}))
|
||||
}
|
||||
System::Rmap(device, reagent_hash) => {
|
||||
let Spanned {
|
||||
node: LiteralOrVariable::Variable(device_spanned),
|
||||
..
|
||||
} = device
|
||||
else {
|
||||
return Err(Error::AgrumentMismatch(
|
||||
"Arg1 expected to be a variable".into(),
|
||||
span,
|
||||
));
|
||||
};
|
||||
|
||||
let (device, device_cleanup) = self.compile_literal_or_variable(
|
||||
LiteralOrVariable::Variable(device_spanned),
|
||||
scope,
|
||||
)?;
|
||||
|
||||
let (reagent_hash, reagent_hash_cleanup) =
|
||||
self.compile_operand(*reagent_hash, scope)?;
|
||||
|
||||
self.write_instruction(
|
||||
Instruction::Rmap(
|
||||
Operand::Register(VariableScope::RETURN_REGISTER),
|
||||
device,
|
||||
reagent_hash,
|
||||
),
|
||||
Some(span),
|
||||
)?;
|
||||
|
||||
cleanup!(reagent_hash_cleanup, device_cleanup);
|
||||
|
||||
Ok(Some(CompileLocation {
|
||||
location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
|
||||
temp_name: None,
|
||||
|
||||
@@ -5,12 +5,14 @@ macro_rules! with_syscalls {
|
||||
// Big names
|
||||
"yield",
|
||||
"sleep",
|
||||
"clr",
|
||||
"hash",
|
||||
"load",
|
||||
"loadBatched",
|
||||
"loadBatchedNamed",
|
||||
"loadSlot",
|
||||
"loadReagent",
|
||||
"rmap",
|
||||
"set",
|
||||
"setBatched",
|
||||
"setBatchedNamed",
|
||||
|
||||
@@ -195,6 +195,9 @@ pub enum Instruction<'a> {
|
||||
/// `lr register device reagentMode int`
|
||||
LoadReagent(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>),
|
||||
|
||||
/// `rmap register device reagentHash` - Resolve Reagent to Item Hash
|
||||
Rmap(Operand<'a>, Operand<'a>, Operand<'a>),
|
||||
|
||||
/// `j label` - Unconditional Jump
|
||||
Jump(Operand<'a>),
|
||||
/// `jal label` - Jump and Link (Function Call)
|
||||
@@ -232,12 +235,22 @@ pub enum Instruction<'a> {
|
||||
/// `sle dst a b` - Set if Less or Equal
|
||||
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>),
|
||||
/// `or dst a b` - Logical OR
|
||||
/// `or dst a b` - Bitwise OR
|
||||
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>),
|
||||
/// `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(Operand<'a>),
|
||||
@@ -257,6 +270,8 @@ pub enum Instruction<'a> {
|
||||
Yield,
|
||||
/// `sleep val` - Sleep for seconds
|
||||
Sleep(Operand<'a>),
|
||||
/// `clr val` - Clear stack memory on device
|
||||
Clr(Operand<'a>),
|
||||
|
||||
/// `alias name target` - Define Alias (Usually handled by compiler, but good for IR)
|
||||
Alias(Cow<'a, str>, Operand<'a>),
|
||||
@@ -318,6 +333,9 @@ impl<'a> fmt::Display for Instruction<'a> {
|
||||
Instruction::LoadReagent(reg, device, reagent_mode, reagent_hash) => {
|
||||
write!(f, "lr {} {} {} {}", reg, device, reagent_mode, reagent_hash)
|
||||
}
|
||||
Instruction::Rmap(reg, device, reagent_hash) => {
|
||||
write!(f, "rmap {} {} {}", reg, device, reagent_hash)
|
||||
}
|
||||
Instruction::Jump(lbl) => write!(f, "j {}", lbl),
|
||||
Instruction::JumpAndLink(lbl) => write!(f, "jal {}", lbl),
|
||||
Instruction::JumpRelative(off) => write!(f, "jr {}", off),
|
||||
@@ -338,6 +356,11 @@ impl<'a> fmt::Display for Instruction<'a> {
|
||||
Instruction::And(dst, a, b) => write!(f, "and {} {} {}", 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::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::Pop(dst) => write!(f, "pop {}", dst),
|
||||
Instruction::Peek(dst) => write!(f, "peek {}", dst),
|
||||
@@ -348,6 +371,7 @@ impl<'a> fmt::Display for Instruction<'a> {
|
||||
}
|
||||
Instruction::Yield => write!(f, "yield"),
|
||||
Instruction::Sleep(val) => write!(f, "sleep {}", val),
|
||||
Instruction::Clr(val) => write!(f, "clr {}", val),
|
||||
Instruction::Alias(name, target) => write!(f, "alias {} {}", name, target),
|
||||
Instruction::Define(name, val) => write!(f, "define {} {}", name, val),
|
||||
Instruction::LabelDef(lbl) => write!(f, "{}:", lbl),
|
||||
|
||||
112
rust_compiler/libs/integration_tests/src/bitwise_tests.rs
Normal file
112
rust_compiler/libs/integration_tests/src/bitwise_tests.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sorter_bitwise_operations() {
|
||||
let source = indoc! {r#"
|
||||
device self = "db";
|
||||
device sorter = "d0";
|
||||
|
||||
loop {
|
||||
yield();
|
||||
// allow Hay with an op_code of `1`
|
||||
sorter[0] = (hash("ItemCropHay") << 8) | 1;
|
||||
}
|
||||
"#};
|
||||
let output = compile_with_and_without_optimization(source);
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bitwise_with_const() {
|
||||
let source = indoc! {r#"
|
||||
device sorterOutput = "d0";
|
||||
|
||||
const ingotSortClass = 19;
|
||||
const equals = 0;
|
||||
|
||||
loop {
|
||||
yield();
|
||||
clr(sorterOutput);
|
||||
sorterOutput[0] = (ingotSortClass << 16) | (equals << 8) | 3;
|
||||
}
|
||||
"#};
|
||||
|
||||
let output = compile_with_and_without_optimization(source);
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
}
|
||||
46
rust_compiler/libs/integration_tests/src/common.rs
Normal file
46
rust_compiler/libs/integration_tests/src/common.rs
Normal 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
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#[cfg(test)]
|
||||
mod device_indexing_tests {
|
||||
use crate::common::compile_with_and_without_optimization;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn test_device_indexing_with_hash_and_binary_literals() {
|
||||
let source = indoc! {"
|
||||
device printer = \"d0\";
|
||||
|
||||
let item_type = hash(\"ItemIronIngot\");
|
||||
let quality = 16;
|
||||
let quantity = 7;
|
||||
|
||||
// Pack into a single value using bitwise operations
|
||||
// Format: (itemHash << 16) | (quality << 8) | quantity
|
||||
let packed = (item_type << 16) | (quality << 8) | quantity;
|
||||
|
||||
// Write to device stack at address 255
|
||||
let addr = 255;
|
||||
printer[addr] = packed;
|
||||
|
||||
// Read it back
|
||||
let result = printer[addr];
|
||||
"};
|
||||
let output = compile_with_and_without_optimization(source);
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_indexing_with_computed_index() {
|
||||
let source = indoc! {"
|
||||
device storage = \"d1\";
|
||||
|
||||
let base_addr = 10;
|
||||
let offset = 5;
|
||||
let index = base_addr + offset;
|
||||
|
||||
let value = 42;
|
||||
storage[index] = value;
|
||||
|
||||
let retrieved = storage[index];
|
||||
"};
|
||||
let output = compile_with_and_without_optimization(source);
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_indexing_with_binary_literals() {
|
||||
let source = indoc! {"
|
||||
device mem = \"d0\";
|
||||
|
||||
// Binary literals for bitwise operations
|
||||
let flags = 0b1010_0101;
|
||||
let mask = 0b1111_0000;
|
||||
let masked = flags & mask;
|
||||
|
||||
// Write to device
|
||||
mem[0] = masked;
|
||||
|
||||
// Read back
|
||||
let read_value = mem[0];
|
||||
"};
|
||||
let output = compile_with_and_without_optimization(source);
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_indexing_complex_expression() {
|
||||
let source = indoc! {"
|
||||
device db = \"d0\";
|
||||
|
||||
let item = hash(\"ItemCopper\");
|
||||
let quality = 5;
|
||||
let quantity = 100;
|
||||
|
||||
// Complex bitwise expression
|
||||
let packed = (item << 16) | ((quality & 0xFF) << 8) | (quantity & 0xFF);
|
||||
|
||||
// Index with computed address
|
||||
let slot = 1;
|
||||
let address = slot * 256 + 100;
|
||||
db[address] = packed;
|
||||
|
||||
// Read back with different computation
|
||||
let read_addr = (slot + 0) * 256 + 100;
|
||||
let stored_value = db[read_addr];
|
||||
"};
|
||||
let output = compile_with_and_without_optimization(source);
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_indexing_optimization_folds_constants() {
|
||||
let source = indoc! {"
|
||||
device cache = \"d0\";
|
||||
|
||||
// This should optimize to a single constant
|
||||
let packed_constant = (hash(\"ItemSilver\") << 16) | (10 << 8) | 50;
|
||||
|
||||
cache[255] = packed_constant;
|
||||
let result = cache[255];
|
||||
"};
|
||||
let output = compile_with_and_without_optimization(source);
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
}
|
||||
48
rust_compiler/libs/integration_tests/src/function_tests.rs
Normal file
48
rust_compiler/libs/integration_tests/src/function_tests.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -4,180 +4,22 @@
|
||||
//! and optimization passes work correctly together using snapshot testing.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use compiler::Compiler;
|
||||
mod bitwise_tests;
|
||||
#[cfg(test)]
|
||||
mod common;
|
||||
#[cfg(test)]
|
||||
mod device_indexing_tests;
|
||||
#[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 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]
|
||||
fn test_tuples() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
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
|
||||
move r1 1
|
||||
move r10 1
|
||||
move r2 7
|
||||
move r11 7
|
||||
move r3 6
|
||||
move r12 6
|
||||
not r4 r8
|
||||
move r13 r4
|
||||
@@ -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
|
||||
move r1 32
|
||||
move r9 32
|
||||
move r2 4
|
||||
move r10 4
|
||||
move r3 4
|
||||
move r11 4
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: libs/integration_tests/src/bitwise_tests.rs
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
|
||||
j main
|
||||
main:
|
||||
__internal_L1:
|
||||
yield
|
||||
clr d0
|
||||
put d0 0 1245187
|
||||
j __internal_L1
|
||||
__internal_L2:
|
||||
|
||||
## Optimized Output
|
||||
|
||||
yield
|
||||
clr d0
|
||||
put d0 0 1245187
|
||||
j 0
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
source: libs/integration_tests/src/bitwise_tests.rs
|
||||
assertion_line: 91
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
|
||||
j main
|
||||
main:
|
||||
__internal_L1:
|
||||
yield
|
||||
put d0 0 55164456193
|
||||
j __internal_L1
|
||||
__internal_L2:
|
||||
|
||||
## Optimized Output
|
||||
|
||||
yield
|
||||
put d0 0 55164456193
|
||||
j 0
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
source: libs/integration_tests/src/device_indexing_tests.rs
|
||||
assertion_line: 90
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
|
||||
j main
|
||||
main:
|
||||
move r8 r15
|
||||
move r9 5
|
||||
move r10 100
|
||||
sll r1 r8 16
|
||||
and r2 r9 255
|
||||
sll r3 r2 8
|
||||
or r4 r1 r3
|
||||
and r5 r10 255
|
||||
or r6 r4 r5
|
||||
move r11 r6
|
||||
move r12 1
|
||||
mul r7 r12 256
|
||||
add r2 r7 100
|
||||
move r13 r2
|
||||
put d0 r13 r11
|
||||
add r1 r12 0
|
||||
mul r3 r1 256
|
||||
add r4 r3 100
|
||||
move r14 r4
|
||||
get r5 d0 r14
|
||||
push r5
|
||||
sub sp sp 1
|
||||
|
||||
## Optimized Output
|
||||
|
||||
move r8 r15
|
||||
move r9 5
|
||||
move r10 100
|
||||
sll r1 r8 16
|
||||
move r3 1280
|
||||
or r4 r1 r3
|
||||
move r5 100
|
||||
or r11 r4 r5
|
||||
move r12 1
|
||||
move r7 256
|
||||
move r2 356
|
||||
move r13 356
|
||||
put d0 r13 r11
|
||||
move r1 1
|
||||
move r3 256
|
||||
move r4 356
|
||||
move r14 356
|
||||
get r5 d0 r14
|
||||
push r5
|
||||
sub sp sp 1
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
source: libs/integration_tests/src/device_indexing_tests.rs
|
||||
assertion_line: 105
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
|
||||
j main
|
||||
main:
|
||||
move r8 -62440604628430
|
||||
put d0 255 r8
|
||||
get r1 d0 255
|
||||
move r9 r1
|
||||
|
||||
## Optimized Output
|
||||
|
||||
move r8 -62440604628430
|
||||
put d0 255 r8
|
||||
get r9 d0 255
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
source: libs/integration_tests/src/device_indexing_tests.rs
|
||||
assertion_line: 65
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
|
||||
j main
|
||||
main:
|
||||
move r8 165
|
||||
move r9 240
|
||||
and r1 r8 r9
|
||||
move r10 r1
|
||||
put d0 0 r10
|
||||
get r2 d0 0
|
||||
move r11 r2
|
||||
|
||||
## Optimized Output
|
||||
|
||||
move r8 165
|
||||
move r9 240
|
||||
move r1 160
|
||||
move r10 160
|
||||
put d0 0 r10
|
||||
get r11 d0 0
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
source: libs/integration_tests/src/device_indexing_tests.rs
|
||||
assertion_line: 45
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
|
||||
j main
|
||||
main:
|
||||
move r8 10
|
||||
move r9 5
|
||||
add r1 r8 r9
|
||||
move r10 r1
|
||||
move r11 42
|
||||
put d1 r10 r11
|
||||
get r2 d1 r10
|
||||
move r12 r2
|
||||
|
||||
## Optimized Output
|
||||
|
||||
move r8 10
|
||||
move r9 5
|
||||
move r1 15
|
||||
move r10 15
|
||||
move r11 42
|
||||
put d1 r10 r11
|
||||
get r12 d1 r10
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
source: libs/integration_tests/src/device_indexing_tests.rs
|
||||
assertion_line: 27
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
|
||||
j main
|
||||
main:
|
||||
move r8 r15
|
||||
move r9 16
|
||||
move r10 7
|
||||
sll r1 r8 16
|
||||
sll r2 r9 8
|
||||
or r3 r1 r2
|
||||
or r4 r3 r10
|
||||
move r11 r4
|
||||
move r12 255
|
||||
put d0 r12 r11
|
||||
get r5 d0 r12
|
||||
move r13 r5
|
||||
|
||||
## Optimized Output
|
||||
|
||||
move r8 r15
|
||||
move r9 16
|
||||
move r10 7
|
||||
sll r1 r8 16
|
||||
move r2 4096
|
||||
or r3 r1 r2
|
||||
or r11 r3 r10
|
||||
move r12 255
|
||||
put d0 r12 r11
|
||||
get r13 d0 r12
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
assertion_line: 70
|
||||
source: libs/integration_tests/src/function_tests.rs
|
||||
assertion_line: 20
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
assertion_line: 144
|
||||
source: libs/integration_tests/src/function_tests.rs
|
||||
assertion_line: 31
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
assertion_line: 173
|
||||
source: libs/integration_tests/src/function_tests.rs
|
||||
assertion_line: 46
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
assertion_line: 60
|
||||
source: libs/integration_tests/src/function_tests.rs
|
||||
assertion_line: 10
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
assertion_line: 242
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
source: libs/integration_tests/src/optimization_tests.rs
|
||||
assertion_line: 17
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
assertion_line: 158
|
||||
source: libs/integration_tests/src/optimization_tests.rs
|
||||
assertion_line: 80
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
source: libs/integration_tests/src/optimization_tests.rs
|
||||
assertion_line: 10
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
assertion_line: 103
|
||||
source: libs/integration_tests/src/optimization_tests.rs
|
||||
assertion_line: 36
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
assertion_line: 116
|
||||
source: libs/integration_tests/src/optimization_tests.rs
|
||||
assertion_line: 49
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
assertion_line: 133
|
||||
source: libs/integration_tests/src/optimization_tests.rs
|
||||
assertion_line: 66
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: libs/integration_tests/src/lib.rs
|
||||
assertion_line: 91
|
||||
source: libs/integration_tests/src/optimization_tests.rs
|
||||
assertion_line: 24
|
||||
expression: output
|
||||
---
|
||||
## Unoptimized Output
|
||||
@@ -1,212 +0,0 @@
|
||||
# Additional Optimization Opportunities for Slang IL Optimizer
|
||||
|
||||
## Currently Implemented ✓
|
||||
|
||||
1. Constant Propagation - Folds math operations with known values
|
||||
2. Register Forwarding - Eliminates intermediate moves
|
||||
3. Function Call Optimization - Removes unnecessary push/pop around calls
|
||||
4. Leaf Function Optimization - Removes RA save/restore for non-calling functions
|
||||
5. Redundant Move Elimination - Removes `move rx rx`
|
||||
6. Dead Code Elimination - Removes unreachable code after jumps
|
||||
|
||||
## Proposed Additional Optimizations
|
||||
|
||||
### 1. **Algebraic Simplification** 🔥 HIGH IMPACT
|
||||
|
||||
Simplify mathematical identities:
|
||||
|
||||
- `x + 0` → `x` (move)
|
||||
- `x - 0` → `x` (move)
|
||||
- `x * 1` → `x` (move)
|
||||
- `x * 0` → `0` (move to constant)
|
||||
- `x / 1` → `x` (move)
|
||||
- `x - x` → `0` (move to constant)
|
||||
- `x % 1` → `0` (move to constant)
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
add r1 r2 0 → move r1 r2
|
||||
mul r3 r4 1 → move r3 r4
|
||||
mul r5 r6 0 → move r5 0
|
||||
```
|
||||
|
||||
### 2. **Strength Reduction** 🔥 HIGH IMPACT
|
||||
|
||||
Replace expensive operations with cheaper ones:
|
||||
|
||||
- `x * 2` → `add x x x` (addition is cheaper than multiplication)
|
||||
- `x * power_of_2` → bit shifts (if IC10 supports)
|
||||
- `x / 2` → bit shifts (if IC10 supports)
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
mul r1 r2 2 → add r1 r2 r2
|
||||
```
|
||||
|
||||
### 3. **Peephole Optimization - Instruction Sequences** 🔥 MEDIUM-HIGH IMPACT
|
||||
|
||||
Recognize and optimize common instruction patterns:
|
||||
|
||||
#### Pattern: Conditional Branch Simplification
|
||||
|
||||
```
|
||||
seq r1 ra rb → beq ra rb label
|
||||
beqz r1 label (remove the seq entirely)
|
||||
|
||||
sne r1 ra rb → bne ra rb label
|
||||
beqz r1 label (remove the sne entirely)
|
||||
```
|
||||
|
||||
#### Pattern: Double Move Elimination
|
||||
|
||||
```
|
||||
move r1 r2 → move r1 r3
|
||||
move r1 r3 (remove first move if r1 not used between)
|
||||
```
|
||||
|
||||
#### Pattern: Redundant Load Elimination
|
||||
|
||||
If a register's value is already loaded and hasn't been clobbered:
|
||||
|
||||
```
|
||||
l r1 d0 Temperature
|
||||
... (no writes to r1)
|
||||
l r1 d0 Temperature → (remove second load)
|
||||
```
|
||||
|
||||
### 4. **Copy Propagation Enhancement** 🔥 MEDIUM IMPACT
|
||||
|
||||
Current register forwarding is good, but we can extend it:
|
||||
|
||||
- Track `move` chains: if `r1 = r2` and `r2 = 5`, propagate the `5` directly
|
||||
- Eliminate the intermediate register if possible
|
||||
|
||||
### 5. **Dead Store Elimination** 🔥 MEDIUM IMPACT
|
||||
|
||||
Remove writes to registers that are never read before being overwritten:
|
||||
|
||||
```
|
||||
move r1 5
|
||||
move r1 10 → move r1 10
|
||||
(first write is dead)
|
||||
```
|
||||
|
||||
### 6. **Common Subexpression Elimination (CSE)** 🔥 MEDIUM-HIGH IMPACT
|
||||
|
||||
Recognize when the same computation is done multiple times:
|
||||
|
||||
```
|
||||
add r1 r8 r9
|
||||
add r2 r8 r9 → add r1 r8 r9
|
||||
move r2 r1
|
||||
```
|
||||
|
||||
This is especially valuable for expensive operations like:
|
||||
|
||||
- Device loads (`l`)
|
||||
- Math functions (sqrt, sin, cos, etc.)
|
||||
|
||||
### 7. **Jump Threading** 🔥 LOW-MEDIUM IMPACT
|
||||
|
||||
Optimize jump-to-jump sequences:
|
||||
|
||||
```
|
||||
j label1
|
||||
...
|
||||
label1:
|
||||
j label2 → j label2 (rewrite first jump)
|
||||
```
|
||||
|
||||
### 8. **Branch Folding** 🔥 LOW-MEDIUM IMPACT
|
||||
|
||||
Merge consecutive branches to the same target:
|
||||
|
||||
```
|
||||
bgt r1 r2 label
|
||||
bgt r3 r4 label → Could potentially be optimized based on conditions
|
||||
```
|
||||
|
||||
### 9. **Loop Invariant Code Motion** 🔥 MEDIUM-HIGH IMPACT
|
||||
|
||||
Move calculations out of loops if they don't change:
|
||||
|
||||
```
|
||||
loop:
|
||||
mul r2 5 10 → mul r2 5 10 (hoisted before loop)
|
||||
add r1 r1 r2 loop:
|
||||
... add r1 r1 r2
|
||||
j loop ...
|
||||
j loop
|
||||
```
|
||||
|
||||
### 10. **Select Instruction Optimization** 🔥 LOW-MEDIUM IMPACT
|
||||
|
||||
The `select` instruction can sometimes replace branch patterns:
|
||||
|
||||
```
|
||||
beq r1 r2 else
|
||||
move r3 r4
|
||||
j end
|
||||
else:
|
||||
move r3 r5 → seq r6 r1 r2
|
||||
end: select r3 r6 r5 r4
|
||||
```
|
||||
|
||||
### 11. **Stack Access Pattern Optimization** 🔥 LOW IMPACT
|
||||
|
||||
If we see repeated `sub r0 sp N` + `get`, we might be able to optimize by:
|
||||
|
||||
- Caching the stack address in a register if used multiple times
|
||||
- Combining sequential gets from adjacent stack slots
|
||||
|
||||
### 12. **Inline Small Functions** 🔥 HIGH IMPACT (Complex to implement)
|
||||
|
||||
For very small leaf functions (1-2 instructions), inline them at the call site:
|
||||
|
||||
```
|
||||
calculateSum:
|
||||
add r15 r8 r9
|
||||
j ra
|
||||
|
||||
main:
|
||||
push 5 → main:
|
||||
push 10 add r15 5 10
|
||||
jal calculateSum
|
||||
```
|
||||
|
||||
### 13. **Branch Prediction Hints** 🔥 LOW IMPACT
|
||||
|
||||
Reorganize code to put likely branches inline (fall-through) and unlikely branches as jumps.
|
||||
|
||||
### 14. **Register Coalescing** 🔥 MEDIUM IMPACT
|
||||
|
||||
Reduce register pressure by reusing registers that have non-overlapping lifetimes.
|
||||
|
||||
## Priority Implementation Order
|
||||
|
||||
### Phase 1 (Quick Wins):
|
||||
|
||||
1. Algebraic Simplification (easy, high impact)
|
||||
2. Strength Reduction (easy, high impact)
|
||||
3. Dead Store Elimination (medium complexity, good impact)
|
||||
|
||||
### Phase 2 (Medium Effort):
|
||||
|
||||
4. Peephole Optimizations - seq/beq pattern (medium, high impact)
|
||||
5. Common Subexpression Elimination (medium, high impact)
|
||||
6. Copy Propagation Enhancement (medium, medium impact)
|
||||
|
||||
### Phase 3 (Advanced):
|
||||
|
||||
7. Loop Invariant Code Motion (complex, high impact for loop-heavy code)
|
||||
8. Function Inlining (complex, high impact)
|
||||
9. Register Coalescing (complex, medium impact)
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- Add test cases for each optimization
|
||||
- Ensure optimization preserves semantics (run existing tests after each)
|
||||
- Measure code size reduction
|
||||
- Consider adding benchmarks to measure game performance impact
|
||||
@@ -31,6 +31,26 @@ pub fn constant_propagation<'a>(
|
||||
Instruction::Mod(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| {
|
||||
if y.is_zero() { Decimal::ZERO } else { x % y }
|
||||
}),
|
||||
Instruction::And(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| x & y),
|
||||
Instruction::Or(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| x | y),
|
||||
Instruction::Xor(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| x ^ y),
|
||||
Instruction::Sll(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| {
|
||||
if y >= 64 { 0 } else { x << y as u32 }
|
||||
}),
|
||||
Instruction::Sra(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| {
|
||||
if y >= 64 {
|
||||
if x < 0 { -1 } else { 0 }
|
||||
} else {
|
||||
x >> y as u32
|
||||
}
|
||||
}),
|
||||
Instruction::Srl(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| {
|
||||
if y >= 64 {
|
||||
0
|
||||
} else {
|
||||
(x as u64 >> y as u32) as i64
|
||||
}
|
||||
}),
|
||||
Instruction::BranchEq(a, b, l) => {
|
||||
try_resolve_branch(a, b, l, ®isters, |x, y| x == y)
|
||||
}
|
||||
@@ -110,6 +130,43 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
fn decimal_to_i64(d: Decimal) -> i64 {
|
||||
// Convert decimal to i64, truncating if needed
|
||||
if let Ok(int_val) = d.try_into() {
|
||||
int_val
|
||||
} else {
|
||||
// For very large or very small values, use a default
|
||||
if d.is_sign_negative() {
|
||||
i64::MIN
|
||||
} else {
|
||||
i64::MAX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn i64_to_decimal(i: i64) -> Decimal {
|
||||
Decimal::from(i)
|
||||
}
|
||||
|
||||
fn try_fold_bitwise<'a, F>(
|
||||
dst: &Operand<'a>,
|
||||
a: &Operand<'a>,
|
||||
b: &Operand<'a>,
|
||||
regs: &[Option<Decimal>; 16],
|
||||
op: F,
|
||||
) -> Option<Instruction<'a>>
|
||||
where
|
||||
F: Fn(i64, i64) -> i64,
|
||||
{
|
||||
let val_a = resolve_value(a, regs)?;
|
||||
let val_b = resolve_value(b, regs)?;
|
||||
let result = op(decimal_to_i64(val_a), decimal_to_i64(val_b));
|
||||
Some(Instruction::Move(
|
||||
dst.clone(),
|
||||
Operand::Number(i64_to_decimal(result)),
|
||||
))
|
||||
}
|
||||
|
||||
fn try_resolve_branch<'a, F>(
|
||||
a: &Operand<'a>,
|
||||
b: &Operand<'a>,
|
||||
|
||||
@@ -43,6 +43,7 @@ pub fn get_destination_reg(instr: &Instruction) -> Option<u8> {
|
||||
| Instruction::Tan(Operand::Register(r), _)
|
||||
| Instruction::Trunc(Operand::Register(r), _)
|
||||
| Instruction::LoadReagent(Operand::Register(r), _, _, _)
|
||||
| Instruction::Rmap(Operand::Register(r), _, _)
|
||||
| Instruction::Pop(Operand::Register(r)) => Some(*r),
|
||||
_ => None,
|
||||
}
|
||||
@@ -107,6 +108,7 @@ pub fn set_destination_reg<'a>(instr: &Instruction<'a>, new_reg: u8) -> Option<I
|
||||
Instruction::Sqrt(_, a) => Some(Instruction::Sqrt(r, a.clone())),
|
||||
Instruction::Tan(_, a) => Some(Instruction::Tan(r, a.clone())),
|
||||
Instruction::Trunc(_, a) => Some(Instruction::Trunc(r, a.clone())),
|
||||
Instruction::Rmap(_, a, b) => Some(Instruction::Rmap(r, a.clone(), b.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -136,6 +138,7 @@ pub fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
|
||||
| Instruction::BranchLe(a, b, _) => check(a) || check(b),
|
||||
Instruction::BranchEqZero(a, _) | Instruction::BranchNeZero(a, _) => check(a),
|
||||
Instruction::LoadReagent(_, device, _, item_hash) => check(device) || check(item_hash),
|
||||
Instruction::Rmap(_, device, reagent_hash) => check(device) || check(reagent_hash),
|
||||
Instruction::LoadSlot(_, dev, slot, _) => check(dev) || check(slot),
|
||||
Instruction::LoadBatch(_, dev, _, mode) => check(dev) || check(mode),
|
||||
Instruction::LoadBatchNamed(_, d_hash, n_hash, _, mode) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -142,6 +142,12 @@ documented! {
|
||||
/// ## Slang
|
||||
/// `sleep(number|var);`
|
||||
Sleep(Box<Spanned<Expression<'a>>>),
|
||||
/// Clears stack memory on the provided device.
|
||||
/// ## IC10
|
||||
/// `clr d?`
|
||||
/// ## Slang
|
||||
/// `clr(device);`
|
||||
Clr(Box<Spanned<Expression<'a>>>),
|
||||
/// Gets the in-game hash for a specific prefab name. NOTE! This call is COMPLETELY
|
||||
/// optimized away unless you bind it to a `let` variable. If you use a `const` variable
|
||||
/// however, the hash is correctly computed at compile time and substitued automatically.
|
||||
@@ -249,6 +255,17 @@ documented! {
|
||||
Spanned<LiteralOrVariable<'a>>,
|
||||
Spanned<Literal<'a>>,
|
||||
Box<Spanned<Expression<'a>>>
|
||||
),
|
||||
/// Maps a reagent hash to the item hash that fulfills it on a device
|
||||
///
|
||||
/// ## IC10
|
||||
/// `rmap r? d? reagentHash(r?|num)`
|
||||
/// ## Slang
|
||||
/// `let itemHash = rmap(device, reagentHash);`
|
||||
/// `let itemHash = rmap(device, reagentHashValue);`
|
||||
Rmap(
|
||||
Spanned<LiteralOrVariable<'a>>,
|
||||
Box<Spanned<Expression<'a>>>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -258,6 +275,7 @@ impl<'a> std::fmt::Display for System<'a> {
|
||||
match self {
|
||||
System::Yield => write!(f, "yield()"),
|
||||
System::Sleep(a) => write!(f, "sleep({})", a),
|
||||
System::Clr(a) => write!(f, "clr({})", a),
|
||||
System::Hash(a) => write!(f, "hash({})", a),
|
||||
System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b),
|
||||
System::LoadBatch(a, b, c) => write!(f, "loadBatch({}, {}, {})", a, b, c),
|
||||
@@ -274,6 +292,7 @@ impl<'a> std::fmt::Display for System<'a> {
|
||||
System::LoadSlot(a, b, c) => write!(f, "loadSlot({}, {}, {})", a, b, c),
|
||||
System::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d),
|
||||
System::LoadReagent(a, b, c) => write!(f, "loadReagent({}, {}, {})", a, b, c),
|
||||
System::Rmap(a, b) => write!(f, "rmap({}, {})", a, b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,23 @@ fn test_const_hash_expression() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_const_hash() -> Result<()> {
|
||||
// This test explicitly validates the tokenizer rewind logic.
|
||||
// When parsing "const h = hash(...)", the parser:
|
||||
// 1. Consumes "const", identifier, "="
|
||||
// 2. Attempts to parse "hash(...)" as a literal - this fails
|
||||
// 3. Must rewind the tokenizer to before "hash"
|
||||
// 4. Then parse it as a syscall
|
||||
// If the rewind offset is wrong (e.g., positive instead of negative),
|
||||
// the tokenizer will be at the wrong position and parsing will fail.
|
||||
let expr = parser!(r#"const h = hash("ComponentComputer")"#)
|
||||
.parse()?
|
||||
.unwrap();
|
||||
assert_eq!(r#"(const h = hash("ComponentComputer"))"#, expr.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_literal_const() -> Result<()> {
|
||||
let expr = parser!(r#"const i = -123"#).parse()?.unwrap();
|
||||
@@ -287,3 +304,36 @@ fn test_tuple_declaration_all_complex_expressions() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn test_eof_error_has_span() -> Result<()> {
|
||||
// Test that UnexpectedEOF errors capture the span of the last token
|
||||
let mut parser = parser!("let x = 5");
|
||||
let result = parser.parse();
|
||||
|
||||
// Should have an error
|
||||
assert!(result.is_err());
|
||||
|
||||
let err = result.unwrap_err();
|
||||
|
||||
// Check that it's an UnexpectedEOF error
|
||||
match err {
|
||||
super::Error::UnexpectedEOF(Some(span)) => {
|
||||
// Verify the span points to somewhere in the code (not zero defaults)
|
||||
assert!(
|
||||
span.start_line > 0 || span.start_col > 0 || span.end_line > 0 || span.end_col > 0,
|
||||
"Span should not be all zeros: {:?}",
|
||||
span
|
||||
);
|
||||
}
|
||||
super::Error::UnexpectedEOF(None) => {
|
||||
eprintln!("ERROR: UnexpectedEOF captured None span instead of previous token span");
|
||||
eprintln!("This means unexpected_eof() is being called when current_token is None");
|
||||
panic!("UnexpectedEOF should have captured the previous token's span");
|
||||
}
|
||||
other => {
|
||||
panic!("Expected UnexpectedEOF error, got: {:?}", other);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -45,6 +45,12 @@ pub enum BinaryExpression<'a> {
|
||||
Subtract(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>>>),
|
||||
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> {
|
||||
@@ -56,6 +62,12 @@ impl<'a> std::fmt::Display for BinaryExpression<'a> {
|
||||
BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r),
|
||||
BinaryExpression::Exponent(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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,6 +209,18 @@ impl<'a> std::fmt::Display for MethodCallExpression<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct IndexAccessExpression<'a> {
|
||||
pub object: Box<Spanned<Expression<'a>>>,
|
||||
pub index: Box<Spanned<Expression<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for IndexAccessExpression<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}[{}]", self.object, self.index)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum LiteralOrVariable<'a> {
|
||||
Literal(Literal<'a>),
|
||||
@@ -367,6 +391,7 @@ pub enum Expression<'a> {
|
||||
Binary(Spanned<BinaryExpression<'a>>),
|
||||
Block(Spanned<BlockExpression<'a>>),
|
||||
Break(Span),
|
||||
BitwiseNot(Box<Spanned<Expression<'a>>>),
|
||||
ConstDeclaration(Spanned<ConstDeclarationExpression<'a>>),
|
||||
Continue(Span),
|
||||
Declaration(Spanned<Cow<'a, str>>, Box<Spanned<Expression<'a>>>),
|
||||
@@ -389,6 +414,7 @@ pub enum Expression<'a> {
|
||||
TupleDeclaration(Spanned<TupleDeclarationExpression<'a>>),
|
||||
Variable(Spanned<Cow<'a, str>>),
|
||||
While(Spanned<WhileExpression<'a>>),
|
||||
IndexAccess(Spanned<IndexAccessExpression<'a>>),
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for Expression<'a> {
|
||||
@@ -398,6 +424,7 @@ impl<'a> std::fmt::Display for Expression<'a> {
|
||||
Expression::Binary(e) => write!(f, "{}", e),
|
||||
Expression::Block(e) => write!(f, "{}", e),
|
||||
Expression::Break(_) => write!(f, "break"),
|
||||
Expression::BitwiseNot(e) => write!(f, "(~{})", e),
|
||||
Expression::ConstDeclaration(e) => write!(f, "{}", e),
|
||||
Expression::Continue(_) => write!(f, "continue"),
|
||||
Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e),
|
||||
@@ -436,7 +463,7 @@ impl<'a> std::fmt::Display for Expression<'a> {
|
||||
Expression::TupleDeclaration(e) => write!(f, "{}", e),
|
||||
Expression::Variable(id) => write!(f, "{}", id),
|
||||
Expression::While(e) => write!(f, "{}", e),
|
||||
Expression::IndexAccess(e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,8 +145,20 @@ impl<'a> TokenizerBuffer<'a> {
|
||||
use Ordering::*;
|
||||
match seek_to_int.cmp(&0) {
|
||||
Greater => {
|
||||
let mut tokens = Vec::with_capacity(seek_to_int as usize);
|
||||
for _ in 0..seek_to_int {
|
||||
let mut seek_remaining = seek_to_int as usize;
|
||||
|
||||
// First, consume tokens from the buffer (peeked but not yet consumed)
|
||||
while seek_remaining > 0 && !self.buffer.is_empty() {
|
||||
if let Some(token) = self.buffer.pop_front() {
|
||||
self.history.push_back(token);
|
||||
seek_remaining -= 1;
|
||||
self.index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Then get tokens from tokenizer if needed
|
||||
let mut tokens = Vec::with_capacity(seek_remaining);
|
||||
for _ in 0..seek_remaining {
|
||||
if let Some(token) = self.tokenizer.next_token()? {
|
||||
tokens.push(token);
|
||||
} else {
|
||||
@@ -157,6 +169,7 @@ impl<'a> TokenizerBuffer<'a> {
|
||||
}
|
||||
}
|
||||
self.history.extend(tokens);
|
||||
self.index += seek_remaining as i64;
|
||||
}
|
||||
Less => {
|
||||
let seek_to = seek_to_int.unsigned_abs() as usize;
|
||||
|
||||
@@ -135,6 +135,9 @@ pub enum TokenType<'a> {
|
||||
/// Represents a string token
|
||||
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)]
|
||||
/// Represents a number token
|
||||
Number(Number),
|
||||
@@ -172,6 +175,23 @@ pub enum TokenType<'a> {
|
||||
#[token(";", symbol!(Semicolon))]
|
||||
#[token(":", symbol!(Colon))]
|
||||
#[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!(Minus))]
|
||||
#[token("*", symbol!(Asterisk))]
|
||||
@@ -180,17 +200,9 @@ pub enum TokenType<'a> {
|
||||
#[token(">", symbol!(GreaterThan))]
|
||||
#[token("=", symbol!(Assign))]
|
||||
#[token("!", symbol!(LogicalNot))]
|
||||
#[token(".", symbol!(Dot))]
|
||||
#[token("^", symbol!(Caret))]
|
||||
#[token("%", symbol!(Percent))]
|
||||
#[token("?", symbol!(Question))]
|
||||
#[token("==", symbol!(Equal))]
|
||||
#[token("!=", symbol!(NotEqual))]
|
||||
#[token("&&", symbol!(LogicalAnd))]
|
||||
#[token("||", symbol!(LogicalOr))]
|
||||
#[token("<=", symbol!(LessThanOrEqual))]
|
||||
#[token(">=", symbol!(GreaterThanOrEqual))]
|
||||
#[token("**", symbol!(Exp))]
|
||||
#[token("&", symbol!(BitwiseAnd))]
|
||||
#[token("|", symbol!(BitwiseOr))]
|
||||
/// Represents a symbol token
|
||||
Symbol(Symbol),
|
||||
|
||||
@@ -221,44 +233,75 @@ pub enum Comment<'a> {
|
||||
|
||||
fn parse_number<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Result<Number, LexError> {
|
||||
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 mut span = lexer.span();
|
||||
span.end -= lexer.extras.line_start_index;
|
||||
span.start -= lexer.extras.line_start_index;
|
||||
|
||||
let unit = match suffix {
|
||||
Some('c') => Unit::Celsius,
|
||||
Some('f') => Unit::Fahrenheit,
|
||||
Some('k') => Unit::Kelvin,
|
||||
_ => Unit::None,
|
||||
};
|
||||
|
||||
if clean_str.contains('.') {
|
||||
Ok(Number::Decimal(
|
||||
clean_str
|
||||
.parse::<Decimal>()
|
||||
// Determine the base and parse accordingly
|
||||
if slice.starts_with("0x") || slice.starts_with("0X") {
|
||||
// Hexadecimal - no temperature suffix allowed
|
||||
let clean_str = slice[2..].replace('_', "");
|
||||
Ok(Number::Integer(
|
||||
i128::from_str_radix(&clean_str, 16)
|
||||
.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 {
|
||||
Ok(Number::Integer(
|
||||
clean_str
|
||||
.parse::<i128>()
|
||||
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
|
||||
unit,
|
||||
))
|
||||
// Decimal (with optional temperature suffix)
|
||||
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 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,
|
||||
/// Represents the `?` symbol
|
||||
Question,
|
||||
/// Represents the `&` symbol (bitwise AND)
|
||||
BitwiseAnd,
|
||||
/// Represents the `|` symbol (bitwise OR)
|
||||
BitwiseOr,
|
||||
/// Represents the `~` symbol (bitwise NOT)
|
||||
BitwiseNot,
|
||||
|
||||
// Double Character Symbols
|
||||
/// Represents the `==` symbol
|
||||
@@ -629,6 +678,12 @@ pub enum Symbol {
|
||||
LessThanOrEqual,
|
||||
/// Represents the `>=` symbol
|
||||
GreaterThanOrEqual,
|
||||
/// Represents the `<<` symbol (left shift)
|
||||
LeftShift,
|
||||
/// Represents the `>>` symbol (arithmetic right shift)
|
||||
RightShiftArithmetic,
|
||||
/// Represents the `>>>` symbol (logical right shift)
|
||||
RightShiftLogical,
|
||||
/// Represents the `**` symbol
|
||||
Exp,
|
||||
}
|
||||
@@ -643,6 +698,19 @@ impl Symbol {
|
||||
| Symbol::Slash
|
||||
| Symbol::Exp
|
||||
| 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::Dot => 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, "**"),
|
||||
}
|
||||
}
|
||||
@@ -715,7 +789,7 @@ documented! {
|
||||
/// }
|
||||
/// ```
|
||||
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
|
||||
/// 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.
|
||||
@@ -846,6 +920,7 @@ documented! {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TokenType;
|
||||
use super::{Number, Unit};
|
||||
use logos::Logos;
|
||||
|
||||
#[test]
|
||||
@@ -856,7 +931,141 @@ mod tests {
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ fn run_logic<'a>() -> Result<(), Error<'a>> {
|
||||
let input_string = match input_file {
|
||||
Some(input_path) => {
|
||||
let mut buf = String::new();
|
||||
let mut file = std::fs::File::open(input_path).unwrap();
|
||||
file.read_to_string(&mut buf).unwrap();
|
||||
let mut file = std::fs::File::open(input_path)?;
|
||||
file.read_to_string(&mut buf)?;
|
||||
buf
|
||||
}
|
||||
None => {
|
||||
|
||||
Reference in New Issue
Block a user