Attempt to fold constants when folding expressions
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -2286,7 +2286,10 @@ 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(),
|
||||
@@ -2315,7 +2318,7 @@ impl<'a> Compiler<'a> {
|
||||
| BinaryExpression::LeftShift(l, r)
|
||||
| BinaryExpression::RightShiftArithmetic(l, r)
|
||||
| BinaryExpression::RightShiftLogical(l, r) => {
|
||||
(fold_expression(l)?, fold_expression(r)?)
|
||||
(fold_expression(l, scope)?, fold_expression(r, scope)?)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2359,7 +2362,10 @@ impl<'a> Compiler<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -2368,18 +2374,28 @@ 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. Handle hash() syscall - evaluates to a constant at compile time
|
||||
// 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 {
|
||||
@@ -2391,7 +2407,7 @@ impl<'a> Compiler<'a> {
|
||||
return Some(Number::Integer(crc_hash_signed(str_to_hash), Unit::None));
|
||||
}
|
||||
|
||||
// 6. Handle hash() macro as invocation - evaluates to a constant at compile time
|
||||
// 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 {
|
||||
@@ -2406,12 +2422,12 @@ impl<'a> Compiler<'a> {
|
||||
None
|
||||
}
|
||||
|
||||
// 7. Anything else cannot be compile-time folded
|
||||
// 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,
|
||||
@@ -2925,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),
|
||||
@@ -3244,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)
|
||||
@@ -267,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>),
|
||||
@@ -328,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),
|
||||
@@ -363,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),
|
||||
|
||||
@@ -90,4 +90,23 @@ mod bitwise_tests {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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) => {
|
||||
|
||||
@@ -1960,6 +1960,11 @@ impl<'a> Parser<'a> {
|
||||
let expr = args.next().ok_or_else(|| self.unexpected_eof())?;
|
||||
Ok(SysCall::System(System::Sleep(boxed!(expr))))
|
||||
}
|
||||
"clr" => {
|
||||
let mut args = args!(1);
|
||||
let expr = args.next().ok_or_else(|| self.unexpected_eof())?;
|
||||
Ok(SysCall::System(System::Clr(boxed!(expr))))
|
||||
}
|
||||
"hash" => {
|
||||
let mut args = args!(1);
|
||||
let lit_str = literal_or_variable!(args.next());
|
||||
@@ -2191,6 +2196,17 @@ impl<'a> Parser<'a> {
|
||||
Box::new(reagent_hash),
|
||||
)))
|
||||
}
|
||||
"rmap" => {
|
||||
let mut args = args!(2);
|
||||
let next = args.next();
|
||||
let device = literal_or_variable!(next);
|
||||
let reagent_hash = args.next().ok_or_else(|| self.unexpected_eof())?;
|
||||
|
||||
Ok(SysCall::System(System::Rmap(
|
||||
device,
|
||||
Box::new(reagent_hash),
|
||||
)))
|
||||
}
|
||||
|
||||
// Math SysCalls
|
||||
"acos" => {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user