Clean optimizations up

main
Shad Amethyst 1 year ago
parent 8d33422f82
commit 3efaf5b7e2

23
Cargo.lock generated

@ -15,15 +15,32 @@ dependencies = [
name = "basic-to-mindustry"
version = "0.1.0"
dependencies = [
"pretty_assertions",
"regex",
]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "memchr"
version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]]
name = "pretty_assertions"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
dependencies = [
"diff",
"yansi",
]
[[package]]
name = "regex"
version = "1.9.5"
@ -52,3 +69,9 @@ name = "regex-syntax"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"

@ -7,3 +7,6 @@ edition = "2021"
[dependencies]
regex = "1.9.5"
[dev-dependencies]
pretty_assertions = "1.4.0"

@ -101,26 +101,33 @@ pub(crate) fn format_unary_operator(operator: UnaryOperator) -> &'static str {
#[derive(Clone, Debug)]
pub struct Config {
pub builtin_functions: HashMap<String, (String, usize)>,
pub builtin_functions: HashMap<String, (String, bool, usize)>,
}
impl Default for Config {
fn default() -> Self {
macro_rules! builtin_function {
( $name:expr, $target_name:expr, $n_args:expr ) => {
(String::from($name), (String::from($target_name), $n_args))
( $name:expr, $target_name:expr, $mutating:expr, $n_args:expr ) => {
(
String::from($name),
(String::from($target_name), $mutating, $n_args),
)
};
}
Self {
builtin_functions: HashMap::from([
builtin_function!("print_flush", "printflush", 1),
builtin_function!("print_message_mission", "message mission", 0),
builtin_function!("read", "read", 3),
builtin_function!("write", "write", 3),
builtin_function!("wait", "wait", 1),
builtin_function!("set_flag", "setflag", 2),
builtin_function!("get_flag", "getflag", 2),
builtin_function!("spawn", "spawn", 6),
builtin_function!("print_flush", "printflush", false, 1),
// TODO: write a special case for message
builtin_function!("print_message_mission", "message mission", false, 0),
builtin_function!("read", "read", true, 3),
// TODO: don't use a generic operation here
builtin_function!("write", "write", false, 3),
builtin_function!("wait", "wait", false, 1),
// TODO: don't use a generic operation here either
builtin_function!("set_flag", "setflag", false, 2),
builtin_function!("get_flag", "getflag", true, 2),
// TODO: same thing
builtin_function!("spawn", "spawn", false, 6),
]),
}
}

@ -35,8 +35,18 @@ pub enum MindustryOperation {
JumpIf(String, Operator, Operand, Operand),
Operator(String, Operator, Operand, Operand),
UnaryOperator(String, UnaryOperator, Operand),
// TODO: add RandOperator
Set(String, Operand),
/// A generic operation, with the following invariants:
/// - all of the operands are read-only
/// - there is no external dependency to other variables
/// - no external variable is modified
Generic(String, Vec<Operand>),
/// A generic, mutating operation `(name, out_name, operands)`, with the following invariants:
/// - all of the operands are read-only
/// - there is no external dependency to other variables, except `out_name`
/// - only `out_name` is modified
GenericMut(String, String, Vec<Operand>),
}
impl MindustryOperation {
@ -48,6 +58,9 @@ impl MindustryOperation {
Self::Generic(_name, operands) => {
operands.iter().collect::<Vec<_>>().into_boxed_slice()
}
Self::GenericMut(_name, _out_name, operands) => {
operands.iter().collect::<Vec<_>>().into_boxed_slice()
}
_ => Box::new([]),
}
}
@ -58,9 +71,24 @@ impl MindustryOperation {
Self::Operator(_target, _operator, lhs, rhs) => vec![lhs, rhs],
Self::Set(_target, value) => vec![value],
Self::Generic(_name, operands) => operands.iter_mut().collect::<Vec<_>>(),
Self::GenericMut(_name, _out_name, operands) => operands.iter_mut().collect::<Vec<_>>(),
_ => vec![],
}
}
fn mutates(&self, var_name: &str) -> bool {
match self {
MindustryOperation::JumpLabel(_)
| MindustryOperation::Jump(_)
| MindustryOperation::JumpIf(_, _, _, _)
| MindustryOperation::Generic(_, _) => false,
MindustryOperation::Operator(out_name, _, _, _)
| MindustryOperation::UnaryOperator(out_name, _, _)
| MindustryOperation::Set(out_name, _)
| MindustryOperation::GenericMut(_, out_name, _) => out_name == var_name,
}
}
}
#[derive(Debug, Clone)]
@ -244,10 +272,17 @@ pub fn translate_ast(
}
}
Instr::CallBuiltin(name, arguments) => {
let argument_names = (0..arguments.len())
let Some((target_name, mutating, _)) = config.builtin_functions.get(name) else {
unreachable!("CallBuilting constructed with unknown function name");
};
let mutating = *mutating;
let first_index = mutating as usize;
let argument_names = (first_index..arguments.len())
.map(|_| namer.temporary())
.collect::<Vec<_>>();
for (i, argument) in arguments.iter().enumerate() {
for (i, argument) in arguments.iter().skip(first_index).enumerate() {
res.append(&mut translate_expression(
argument,
namer,
@ -255,7 +290,23 @@ pub fn translate_ast(
));
}
if let Some((target_name, _)) = config.builtin_functions.get(name) {
if mutating {
let BasicAstExpression::Variable(out_name) = arguments[0].clone() else {
unreachable!(
"First argument to {} isn't a variable, got {:?}",
name, arguments[0]
);
};
res.push(MindustryOperation::GenericMut(
target_name.clone(),
out_name,
argument_names
.into_iter()
.map(|name| Operand::Variable(name))
.collect(),
));
} else {
res.push(MindustryOperation::Generic(
target_name.clone(),
argument_names
@ -354,6 +405,14 @@ impl std::fmt::Display for MindustryProgram {
}
write!(f, "\n")?;
}
MindustryOperation::GenericMut(name, out_name, operands) => {
write!(f, "{}", name)?;
write!(f, " {}", out_name)?;
for operand in operands {
write!(f, " {}", operand)?;
}
write!(f, "\n")?;
}
}
}

@ -1,407 +0,0 @@
use std::collections::HashSet;
use super::*;
/// Optimizes away unnecessary `sets`
pub fn optimize_set_use(program: MindustryProgram) -> MindustryProgram {
let mut res = MindustryProgram::new();
let instructions = program.0;
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
// Find and replace references to constants
// TODO: multiple rounds?
for (use_index, instruction) in instructions.iter().enumerate() {
let optimizable_operands = instruction
.operands()
.iter()
.filter_map(|operand| match operand {
Operand::Variable(name) => {
if tmp_regex.is_match(name) {
Some(name)
} else {
None
}
}
_ => None,
})
// PERF: check when it would be better to deduplicate operands
// .collect::<HashSet<_>>()
// .into_iter()
.filter_map(|name| {
for (index, instr) in instructions[0..use_index].iter().enumerate().rev() {
match instr {
MindustryOperation::Set(set_name, value) if set_name == name => {
return Some((name.clone(), value.clone(), index))
}
MindustryOperation::Operator(op_name, _op, _lhs, _rhs)
if op_name == name =>
{
// Not optimizable
break;
}
MindustryOperation::JumpLabel(_label) => {
// Note: jump labels mark boundaries for constants. For instance:
// ```
// set __tmp_1 "this is not a constant"
// jump_label:
// set __tmp_2 "this is a constant"
// op add result __tmp_1 __tmp_2
// ```
//
// gets optimized to:
// ```
// set __tmp_1 "this is not a constant"
// jump_label:
// op add result __tmp_1 "this is a constant"
// ```
//
// A more complex algorithm could be used to check the flow of the program,
// but this usecase isn't needed yet.
break;
}
_ => {}
}
}
None
})
.filter(|(_name, value, set_index)| {
// Don't optimize operands that refer to a mutating variable (either mutable @-variables or instructions that get updated in-between)
if let Operand::Variable(assigned_var) = value {
if matches!(
assigned_var.as_str(),
"@this" | "@thisx" | "@thisy" | "@links"
) || is_unit_constant(assigned_var.as_str())
{
return true;
}
if assigned_var.starts_with('@') {
return false;
}
for instr_between in &instructions[*set_index..use_index] {
match instr_between {
MindustryOperation::Set(var_name, _)
| MindustryOperation::Operator(var_name, _, _, _) => {
if var_name == assigned_var {
return false;
}
}
_ => {}
}
}
true
} else {
true
}
})
.collect::<Vec<_>>();
if optimizable_operands.len() > 0 {
let mut instruction = instruction.clone();
for operand in instruction.operands_mut() {
if let Operand::Variable(use_name) = operand {
if let Some((_, optimized_into, _)) = optimizable_operands
.iter()
.find(|(set_name, _, _)| set_name == use_name)
{
*operand = optimized_into.clone();
}
}
}
res.push(instruction);
} else {
res.push(instruction.clone());
}
}
optimize_dead_code(res)
}
fn is_unit_constant(name: &str) -> bool {
matches!(
name,
"@stell"
| "@locus"
| "@precept"
| "@vanquish"
| "@conquer"
| "@merui"
| "@cleroi"
| "@anthicus"
| "@tecta"
| "@collaris"
| "@elude"
| "@avert"
| "@obviate"
| "@quell"
| "@disrupt"
)
}
// TODO:
// - optimize jump(1)-label(2)-...instr-label(1) into ...instr-jump(2)
// - shorten temporary variable names
/// Tries to merge the condition in an `op` into the `jump` itself
pub fn optimize_jump_op(program: MindustryProgram) -> MindustryProgram {
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
let mut res = MindustryProgram::new();
let instructions = program.0;
for (index, instruction) in instructions.iter().enumerate() {
match instruction {
MindustryOperation::JumpIf(label, operator, lhs, rhs) => {
let (truthiness, var_name) = match (
operator,
replace_constants(lhs.clone()),
replace_constants(rhs.clone()),
) {
(Operator::Neq, Operand::Variable(var_name), Operand::Integer(0))
| (Operator::Eq, Operand::Variable(var_name), Operand::Integer(1))
| (Operator::Neq, Operand::Integer(0), Operand::Variable(var_name))
| (Operator::Eq, Operand::Integer(1), Operand::Variable(var_name)) => {
(true, var_name)
}
(Operator::Eq, Operand::Variable(var_name), Operand::Integer(0))
| (Operator::Neq, Operand::Variable(var_name), Operand::Integer(1))
| (Operator::Eq, Operand::Integer(0), Operand::Variable(var_name))
| (Operator::Neq, Operand::Integer(1), Operand::Variable(var_name)) => {
(false, var_name)
}
_ => {
res.push(instruction.clone());
continue;
}
};
if !tmp_regex.is_match(&var_name) {
res.push(instruction.clone());
continue;
}
let mut last_op = None;
for prev_instruction in instructions[0..=index].iter().rev().skip(1) {
match prev_instruction {
MindustryOperation::Operator(name, operator, lhs, rhs)
if *name == var_name && is_condition_op(*operator) =>
{
last_op = Some((*operator, lhs.clone(), rhs.clone()));
}
MindustryOperation::JumpLabel(_) => break,
_ => {}
}
}
let Some(last_op) = last_op else {
res.push(instruction.clone());
continue
};
let (operator, lhs, rhs) = if truthiness {
last_op
} else {
(
match last_op.0 {
Operator::Gt => Operator::Lte,
Operator::Lt => Operator::Gte,
Operator::Gte => Operator::Lt,
Operator::Lte => Operator::Gt,
Operator::Eq => Operator::Neq,
Operator::Neq => Operator::Eq,
_ => unreachable!(),
},
last_op.1,
last_op.2,
)
};
res.push(MindustryOperation::JumpIf(
label.clone(),
operator,
lhs,
rhs,
));
}
_ => {
res.push(instruction.clone());
}
}
}
return optimize_dead_code(res);
fn replace_constants(value: Operand) -> Operand {
if let Operand::Variable(var) = &value {
match var.as_str() {
"true" => Operand::Integer(1),
"false" | "null" => Operand::Integer(0),
_ => value,
}
} else {
value
}
}
fn is_condition_op(op: Operator) -> bool {
matches!(
op,
Operator::Neq
| Operator::Eq
| Operator::Lt
| Operator::Lte
| Operator::Gt
| Operator::Gte
)
}
}
/// Tries to remove unnecessary `jump always` instructions
pub fn optimize_jump_always(mut program: MindustryProgram) -> MindustryProgram {
let instructions = &mut program.0;
let mut substitutions = Vec::new();
// Detect `label`-`jump always` pairs
for (index, instruction) in instructions.iter().enumerate() {
let MindustryOperation::JumpLabel(label_from) = instruction else {
continue
};
for future_instruction in instructions[index..].iter() {
match future_instruction {
MindustryOperation::JumpLabel(_) => {}
MindustryOperation::Jump(label_to) => {
substitutions.push((label_from.clone(), label_to.clone()));
break;
}
_ => break,
}
}
}
// Apply transitivity to the pairs
let substitutions = substitutions
.iter()
.map(|(from, to)| {
let mut new_to = to;
let mut history = vec![to];
loop {
let mut found = false;
for (other_from, other_to) in substitutions.iter() {
if other_from == new_to {
// Leave cycles untouched
if history.contains(&other_to) {
return (from.clone(), to.clone());
}
new_to = other_to;
history.push(other_to);
found = true;
break;
}
}
if !found {
break;
}
}
(from.clone(), to.clone())
})
.collect::<Vec<_>>();
for instruction in instructions.iter_mut() {
match instruction {
MindustryOperation::Jump(label) => {
if let Some((_, new_label)) = substitutions.iter().find(|(from, _)| from == label) {
*label = new_label.clone();
}
}
MindustryOperation::JumpIf(label, _, _, _) => {
if let Some((_, new_label)) = substitutions.iter().find(|(from, _)| from == label) {
*label = new_label.clone();
}
}
_ => {}
}
}
optimize_dead_code(program)
}
fn optimize_dead_code(program: MindustryProgram) -> MindustryProgram {
let instructions = program.0;
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
let label_regex = Regex::new(r"__label_[0-9]+").unwrap();
let mut res = MindustryProgram::new();
let mut needed_vars = HashSet::new();
let mut needed_labels = HashSet::new();
let mut push_var = |operand: &Operand| match operand {
Operand::Variable(name) => {
needed_vars.insert(name.clone());
}
_ => {}
};
for instruction in instructions.iter() {
match instruction {
MindustryOperation::JumpLabel(_) => {}
MindustryOperation::Jump(label) => {
needed_labels.insert(label.clone());
}
MindustryOperation::JumpIf(label, _, lhs, rhs) => {
needed_labels.insert(label.clone());
push_var(lhs);
push_var(rhs);
}
MindustryOperation::Operator(_, _, lhs, rhs) => {
push_var(lhs);
push_var(rhs);
}
MindustryOperation::UnaryOperator(_, _, value) => {
push_var(value);
}
MindustryOperation::Set(_, value) => {
push_var(value);
}
MindustryOperation::Generic(_, values) => {
values.iter().for_each(&mut push_var);
}
}
}
// Remove unneeded `set`s and `op`s
for instruction in instructions.iter() {
match instruction {
MindustryOperation::Set(name, _) | MindustryOperation::Operator(name, _, _, _) => {
if tmp_regex.is_match(name) {
if needed_vars.contains(name) {
res.push(instruction.clone());
}
// else don't push
} else {
res.push(instruction.clone());
}
}
MindustryOperation::JumpLabel(label) => {
if label_regex.is_match(label) {
if needed_labels.contains(label) {
res.push(instruction.clone());
}
// else don't push
} else {
res.push(instruction.clone());
}
}
_ => {
res.push(instruction.clone());
continue;
}
};
}
res
}

@ -0,0 +1,129 @@
use super::*;
/// Optimizes away unnecessary `sets`
pub fn optimize_constant(program: MindustryProgram) -> MindustryProgram {
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
// Returns true if the variable is safe to substitute into an operand
let is_safe_variable = |name: &str| -> bool {
if matches!(name, "@this" | "@thisx" | "@thisy" | "@links") || is_unit_constant(name) {
return true;
}
if name.starts_with('@') {
false
} else {
true
}
};
let res = replace_if(program, |instructions, instruction, use_index| {
let optimizable_operands = instruction
.operands()
.iter()
.filter_map(|operand| match operand {
Operand::Variable(name) if tmp_regex.is_match(name) => Some(name),
_ => None,
})
// PERF: check when it would be better to deduplicate operands
// .collect::<HashSet<_>>()
// .into_iter()
.filter_map(|name| {
lookbehind(instructions, use_index, |instr| {
match instr {
MindustryOperation::Set(set_name, value) if set_name == name => {
Lookaround::Stop((name.clone(), value.clone()))
}
MindustryOperation::Operator(op_name, _op, _lhs, _rhs)
if op_name == name =>
{
Lookaround::Abort
}
MindustryOperation::JumpLabel(_label) => {
// Note: jump labels mark boundaries for constants. For instance:
// ```
// set __tmp_1 "this is not a constant"
// jump_label:
// set __tmp_2 "this is a constant"
// op add result __tmp_1 __tmp_2
// ```
//
// gets optimized to:
// ```
// set __tmp_1 "this is not a constant"
// jump_label:
// op add result __tmp_1 "this is a constant"
// ```
//
// A more complex algorithm could be used to check the flow of the program,
// but this usecase isn't needed yet.
Lookaround::Abort
}
_ => Lookaround::Continue,
}
})
.map(|(index, (name, value))| (name, value, index))
})
.filter(|(_name, value, set_index)| {
// Don't optimize operands that refer to a mutating variable (either mutable @-variables or instructions that get updated in-between)
if let Operand::Variable(assigned_var) = value {
if !is_safe_variable(&assigned_var) {
return false;
}
for instr_between in &instructions[*set_index..use_index] {
if instr_between.mutates(&assigned_var) {
return false;
}
}
true
} else {
true
}
})
.map(|(name, value, _index)| (name, value))
.collect::<Vec<_>>();
if optimizable_operands.len() == 0 {
return None
}
let mut instruction = instruction.clone();
for operand in instruction.operands_mut() {
if let Operand::Variable(use_name) = operand {
if let Some((_, optimized_into)) = optimizable_operands
.iter()
.find(|(set_name, _)| set_name == use_name)
{
*operand = optimized_into.clone();
}
}
}
Some(vec![instruction])
});
optimize_dead_code(res)
}
// TODO: add serpulo units
fn is_unit_constant(name: &str) -> bool {
matches!(
name,
"@stell"
| "@locus"
| "@precept"
| "@vanquish"
| "@conquer"
| "@merui"
| "@cleroi"
| "@anthicus"
| "@tecta"
| "@collaris"
| "@elude"
| "@avert"
| "@obviate"
| "@quell"
| "@disrupt"
)
}

@ -0,0 +1,74 @@
use super::*;
pub(crate) fn optimize_dead_code(program: MindustryProgram) -> MindustryProgram {
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
let label_regex = Regex::new(r"__label_[0-9]+").unwrap();
let mut needed_vars = HashSet::new();
let mut needed_labels = HashSet::new();
let mut push_var = |operand: &Operand| match operand {
Operand::Variable(name) => {
needed_vars.insert(name.clone());
}
_ => {}
};
for instruction in program.0.iter() {
match instruction {
MindustryOperation::JumpLabel(_) => {}
MindustryOperation::Jump(label) => {
needed_labels.insert(label.clone());
}
MindustryOperation::JumpIf(label, _, lhs, rhs) => {
needed_labels.insert(label.clone());
push_var(lhs);
push_var(rhs);
}
MindustryOperation::Operator(_, _, lhs, rhs) => {
push_var(lhs);
push_var(rhs);
}
MindustryOperation::UnaryOperator(_, _, value) => {
push_var(value);
}
MindustryOperation::Set(_, value) => {
push_var(value);
}
MindustryOperation::Generic(_, values) => {
values.iter().for_each(&mut push_var);
}
MindustryOperation::GenericMut(_, _, values) => {
values.iter().for_each(&mut push_var);
}
}
}
// Remove unneeded `set`s and `op`s
replace_if(program, |_instructions, instruction, _index| {
match instruction {
MindustryOperation::Set(name, _) | MindustryOperation::Operator(name, _, _, _) => {
if !tmp_regex.is_match(name) {
return None
}
if needed_vars.contains(name) {
return None
}
return Some(vec![])
}
MindustryOperation::JumpLabel(label) => {
if !label_regex.is_match(label) {
return None
}
if needed_labels.contains(label) {
return None
}
return Some(vec![])
}
_ => None
}
})
}

@ -0,0 +1,196 @@
use super::*;
/// Tries to merge the condition in an `op` into the `jump` itself
pub fn optimize_jump_op(program: MindustryProgram) -> MindustryProgram {
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
let mut res = MindustryProgram::new();
let instructions = program.0;
for (index, instruction) in instructions.iter().enumerate() {
match instruction {
MindustryOperation::JumpIf(label, operator, lhs, rhs) => {
let (truthiness, var_name) = match (
operator,
replace_constants(lhs.clone()),
replace_constants(rhs.clone()),
) {
(Operator::Neq, Operand::Variable(var_name), Operand::Integer(0))
| (Operator::Eq, Operand::Variable(var_name), Operand::Integer(1))
| (Operator::Neq, Operand::Integer(0), Operand::Variable(var_name))
| (Operator::Eq, Operand::Integer(1), Operand::Variable(var_name)) => {
(true, var_name)
}
(Operator::Eq, Operand::Variable(var_name), Operand::Integer(0))
| (Operator::Neq, Operand::Variable(var_name), Operand::Integer(1))
| (Operator::Eq, Operand::Integer(0), Operand::Variable(var_name))
| (Operator::Neq, Operand::Integer(1), Operand::Variable(var_name)) => {
(false, var_name)
}
_ => {
res.push(instruction.clone());
continue;
}
};
if !tmp_regex.is_match(&var_name) {
res.push(instruction.clone());
continue;
}
// Find the last operation defining `var_name`
let Some((_, last_op)) =
lookbehind(
&*instructions,
index,
|prev_instruction| match prev_instruction {
MindustryOperation::Operator(name, operator, lhs, rhs)
if *name == var_name && is_condition_op(*operator) =>
{
Lookaround::Stop((*operator, lhs.clone(), rhs.clone()))
}
MindustryOperation::JumpLabel(_) => Lookaround::Abort,
x if x.mutates(&var_name) => Lookaround::Abort,
_ => Lookaround::Continue,
},
)
else {
res.push(instruction.clone());
continue;
};
let (operator, lhs, rhs) = if truthiness {
last_op
} else {
(
match last_op.0 {
Operator::Gt => Operator::Lte,
Operator::Lt => Operator::Gte,
Operator::Gte => Operator::Lt,
Operator::Lte => Operator::Gt,
Operator::Eq => Operator::Neq,
Operator::Neq => Operator::Eq,
_ => unreachable!(),
},
last_op.1,
last_op.2,
)
};
res.push(MindustryOperation::JumpIf(
label.clone(),
operator,
lhs,
rhs,
));
}
_ => {
res.push(instruction.clone());
}
}
}
return optimize_dead_code(res);
fn replace_constants(value: Operand) -> Operand {
if let Operand::Variable(var) = &value {
match var.as_str() {
"true" => Operand::Integer(1),
"false" | "null" => Operand::Integer(0),
_ => value,
}
} else {
value
}
}
fn is_condition_op(op: Operator) -> bool {
matches!(
op,
Operator::Neq
| Operator::Eq
| Operator::Lt
| Operator::Lte
| Operator::Gt
| Operator::Gte
)
}
}
/// Tries to remove unnecessary `jump always` instructions
pub fn optimize_jump_always(mut program: MindustryProgram) -> MindustryProgram {
let instructions = &mut program.0;
let mut substitutions = Vec::new();
// Detect `label`-`jump always` pairs
for (index, instruction) in instructions.iter().enumerate() {
let MindustryOperation::JumpLabel(label_from) = instruction else {
continue;
};
if let Some((_, label_to)) =
lookahead(
&*instructions,
index,
|future_instruction| match future_instruction {
MindustryOperation::JumpLabel(_) => Lookaround::Continue,
MindustryOperation::Jump(label_to) => Lookaround::Stop(label_to),
_ => Lookaround::Abort,
},
)
{
substitutions.push((label_from.clone(), label_to.clone()));
}
}
// Apply transitivity to the pairs
let substitutions = substitutions
.iter()
.map(|(from, to)| {
let mut new_to = to;
let mut history = vec![to];
loop {
let mut found = false;
for (other_from, other_to) in substitutions.iter() {
if other_from == new_to {
// Leave cycles untouched
if history.contains(&other_to) {
return (from.clone(), to.clone());
}
new_to = other_to;
history.push(other_to);
found = true;
break;
}
}
if !found {
break;
}
}
(from.clone(), to.clone())
})
.collect::<Vec<_>>();
for instruction in instructions.iter_mut() {
match instruction {
MindustryOperation::Jump(label) => {
if let Some((_, new_label)) = substitutions.iter().find(|(from, _)| from == label) {
*label = new_label.clone();
}
}
MindustryOperation::JumpIf(label, _, _, _) => {
if let Some((_, new_label)) = substitutions.iter().find(|(from, _)| from == label) {
*label = new_label.clone();
}
}
_ => {}
}
}
optimize_dead_code(program)
}

@ -0,0 +1,54 @@
//! Lookaround helpers for quickly implementing optimizations
use super::*;
pub(crate) enum Lookaround<T> {
Stop(T),
Abort,
Continue,
}
pub(crate) fn lookahead<'a, I, F, T>(
instructions: I,
from: usize,
mut callback: F,
) -> Option<(usize, T)>
where
I: IntoIterator<Item = &'a MindustryOperation>,
F: FnMut(&'a MindustryOperation) -> Lookaround<T>,
{
for (index, instruction) in instructions.into_iter().enumerate().skip(from) {
match callback(instruction) {
Lookaround::Stop(value) => {
return Some((index, value));
}
Lookaround::Abort => return None,
Lookaround::Continue => {}
}
}
None
}
pub(crate) fn lookbehind<'a, I, F, T>(
instructions: I,
from: usize,
mut callback: F,
) -> Option<(usize, T)>
where
I: IntoIterator<Item = &'a MindustryOperation>,
<I as IntoIterator>::IntoIter: ExactSizeIterator + DoubleEndedIterator,
F: FnMut(&'a MindustryOperation) -> Lookaround<T>,
{
for (index, instruction) in instructions.into_iter().enumerate().take(from).rev() {
match callback(instruction) {
Lookaround::Stop(value) => {
return Some((index, value));
}
Lookaround::Abort => return None,
Lookaround::Continue => {}
}
}
None
}

@ -0,0 +1,24 @@
use std::collections::HashSet;
use super::*;
mod constant;
pub use constant::*;
mod jump;
pub use jump::*;
mod dead;
pub(crate) use dead::optimize_dead_code;
mod lookaround;
pub(crate) use lookaround::*;
mod replace;
pub(crate) use replace::*;
// TODO:
// - optimize jump(1)-label(2)-...instr-label(1) into ...instr-jump(2)
// - shorten temporary variable names
// - jump normalization
// - variable normalization

@ -0,0 +1,22 @@
use super::*;
pub(crate) fn replace_if<F>(program: MindustryProgram, callback: F) -> MindustryProgram
where
F: for<'c> Fn(&'c Vec<MindustryOperation>, &'c MindustryOperation, usize) -> Option<Vec<MindustryOperation>>
{
let mut res = MindustryProgram::new();
for (index, instruction) in program.0.iter().enumerate() {
match callback(&program.0, instruction, index) {
Some(mut to_replace) => {
res.0.append(&mut to_replace);
}
None => {
res.push(instruction.clone());
}
}
}
res
}

@ -1,6 +1,6 @@
use basic_to_mindustry::{
common::Config,
compile::{optimize_jump_always, optimize_jump_op, optimize_set_use, translate_ast, Namer},
compile::{optimize_constant, optimize_jump_always, optimize_jump_op, translate_ast, Namer},
parse::{build_ast, tokenize},
};
@ -13,9 +13,9 @@ fn main() {
let parsed = build_ast(&tokens, &config).unwrap();
let transformed = translate_ast(&parsed, &mut Namer::default(), &config);
// println!("{}", transformed);
println!("{}", transformed);
let optimized = optimize_set_use(transformed);
let optimized = optimize_constant(transformed);
let optimized = optimize_jump_op(optimized);
let optimized = optimize_jump_always(optimized);

@ -189,7 +189,15 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
}
let lowercase_fn_name = fn_name.to_lowercase();
if let Some((_, n_args)) = config.builtin_functions.get(&lowercase_fn_name) {
if let Some((_, mutating, n_args)) =
config.builtin_functions.get(&lowercase_fn_name)
{
if *mutating {
let BasicAstExpression::Variable(_) = &arguments[0] else {
return Err(ParseError::ExpectedVariable);
};
}
if arguments.len() != *n_args {
return Err(ParseError::InvalidArgumentCount(
lowercase_fn_name,

@ -6,3 +6,13 @@ pub use ast::*;
#[cfg(test)]
mod test;
#[derive(PartialEq, Clone, Debug)]
pub enum ParseError {
InvalidToken(String),
UnexpectedToken(BasicToken),
MissingToken(BasicToken),
InvalidArgumentCount(String, usize, usize),
ExpectedVariable,
ExpectedOperand,
}

@ -1,3 +1,4 @@
use super::ParseError;
use crate::common::*;
use regex::Regex;
@ -22,15 +23,6 @@ pub enum BasicToken {
Operator(Operator),
}
#[derive(PartialEq, Clone, Debug)]
pub enum ParseError {
InvalidToken(String),
UnexpectedToken(BasicToken),
MissingToken(BasicToken),
InvalidArgumentCount(String, usize, usize),
ExpectedOperand,
}
/// Transforms a raw string into a sequence of `BasicToken`s
pub fn tokenize(raw: &str) -> Result<Vec<BasicToken>, ParseError> {
macro_rules! match_token {

@ -1,7 +1,7 @@
use std::path::{Path, PathBuf};
use basic_to_mindustry::common::Config;
use basic_to_mindustry::compile::{optimize_set_use, translate_ast, optimize_jump_op, optimize_jump_always};
use basic_to_mindustry::compile::{optimize_constant, translate_ast, optimize_jump_op, optimize_jump_always};
use basic_to_mindustry::parse::{build_ast, tokenize};
fn read_basic_examples() -> impl Iterator<Item=(String, String)> {
@ -39,7 +39,7 @@ fn test_examples() {
panic!("Error parsing {:?}: {:?}", file_name, e);
});
let translated = translate_ast(&parsed, &mut Default::default(), &config);
let optimized = optimize_set_use(translated);
let optimized = optimize_constant(translated);
let _ = optimized;
}
@ -67,14 +67,14 @@ fn test_examples_opt() {
panic!("Couldn't open tests/examples/{}.0.mlog: {:?}", program_name, e);
});
assert_eq!(opt_0.trim(), format!("{}", translated).trim());
pretty_assertions::assert_eq!(opt_0.trim(), format!("{}", translated).trim());
let optimized = optimize_jump_always(optimize_jump_op(optimize_set_use(translated)));
let optimized = optimize_jump_always(optimize_jump_op(optimize_constant(translated)));
let opt_1 = std::fs::read_to_string(format!("tests/examples/{}.1.mlog", program_name)).unwrap_or_else(|e| {
panic!("Couldn't open tests/examples/{}.1.mlog: {:?}", program_name, e);
});
assert_eq!(opt_1.trim(), format!("{}", optimized).trim());
pretty_assertions::assert_eq!(opt_1.trim(), format!("{}", optimized).trim());
}
}

@ -85,140 +85,139 @@ set main__tmp_43 wave
set main__tmp_44 cell1
set main__tmp_45 1
write main__tmp_43 main__tmp_44 main__tmp_45
set main__tmp_46 timeout
set main__tmp_47 cell1
set main__tmp_48 0
read main__tmp_46 main__tmp_47 main__tmp_48
set main__tmp_49 @time
set main__tmp_51 timeout
set main__tmp_52 1000
op mul main__tmp_50 main__tmp_51 main__tmp_52
op add timeout main__tmp_49 main__tmp_50
set main__tmp_46 cell1
set main__tmp_47 0
read timeout main__tmp_46 main__tmp_47
set main__tmp_48 @time
set main__tmp_50 timeout
set main__tmp_51 1000
op mul main__tmp_49 main__tmp_50 main__tmp_51
op add timeout main__tmp_48 main__tmp_49
main_wait:
set main__tmp_53 timeout
set main__tmp_54 @time
op sub remaining main__tmp_53 main__tmp_54
set main__tmp_56 remaining
set main__tmp_57 0
op lessThanEq main__tmp_55 main__tmp_56 main__tmp_57
jump main__label_1_endif notEqual main__tmp_55 true
set main__tmp_52 timeout
set main__tmp_53 @time
op sub remaining main__tmp_52 main__tmp_53
set main__tmp_55 remaining
set main__tmp_56 0
op lessThanEq main__tmp_54 main__tmp_55 main__tmp_56
jump main__label_1_endif notEqual main__tmp_54 true
jump main always 0 0
main__label_1_endif:
set main__tmp_58 "[yellow]Wave "
set main__tmp_57 "[yellow]Wave "
print main__tmp_57
set main__tmp_58 wave
print main__tmp_58
set main__tmp_59 wave
set main__tmp_59 "[white] - "
print main__tmp_59
set main__tmp_60 "[white] - "
set main__tmp_60 "Next wave: "
print main__tmp_60
set main__tmp_61 "Next wave: "
set main__tmp_62 remaining
set main__tmp_63 1000
op div main__tmp_61 main__tmp_62 main__tmp_63
op ceil main__tmp_61 main__tmp_61 0
print main__tmp_61
set main__tmp_63 remaining
set main__tmp_64 1000
op div main__tmp_62 main__tmp_63 main__tmp_64
op ceil main__tmp_62 main__tmp_62 0
print main__tmp_62
set main__tmp_65 " s"
print main__tmp_65
set main__tmp_64 " s"
print main__tmp_64
message mission
set main__tmp_66 0.5
wait main__tmp_66
set main__tmp_65 0.5
wait main__tmp_65
jump main_wait always 0 0
spawn_tank:
set spawned 0
spawn_tank_loop:
set roll progression
op rand roll roll 0
set main__tmp_68 roll
set main__tmp_69 3
op greaterThanEq main__tmp_67 main__tmp_68 main__tmp_69
jump main__label_2_else notEqual main__tmp_67 true
set main__tmp_71 roll
set main__tmp_72 4
op greaterThanEq main__tmp_70 main__tmp_71 main__tmp_72
jump main__label_4_else notEqual main__tmp_70 true
set main__tmp_73 @conquer
set main__tmp_74 spawnx
set main__tmp_75 spawny
set main__tmp_76 0
set main__tmp_77 @crux
set main__tmp_78 _
spawn main__tmp_73 main__tmp_74 main__tmp_75 main__tmp_76 main__tmp_77 main__tmp_78
set main__tmp_79 spawnx
set main__tmp_80 5.75
op sub spawnx main__tmp_79 main__tmp_80
set main__tmp_67 roll
set main__tmp_68 3
op greaterThanEq main__tmp_66 main__tmp_67 main__tmp_68
jump main__label_2_else notEqual main__tmp_66 true
set main__tmp_70 roll
set main__tmp_71 4
op greaterThanEq main__tmp_69 main__tmp_70 main__tmp_71
jump main__label_4_else notEqual main__tmp_69 true
set main__tmp_72 @conquer
set main__tmp_73 spawnx
set main__tmp_74 spawny
set main__tmp_75 0
set main__tmp_76 @crux
set main__tmp_77 _
spawn main__tmp_72 main__tmp_73 main__tmp_74 main__tmp_75 main__tmp_76 main__tmp_77
set main__tmp_78 spawnx
set main__tmp_79 5.75
op sub spawnx main__tmp_78 main__tmp_79
jump main__label_5_endif always 0 0
main__label_4_else:
set main__tmp_81 @vanquish
set main__tmp_82 spawnx
set main__tmp_83 spawny
set main__tmp_84 0
set main__tmp_85 @crux
set main__tmp_86 _
spawn main__tmp_81 main__tmp_82 main__tmp_83 main__tmp_84 main__tmp_85 main__tmp_86
set main__tmp_87 spawnx
set main__tmp_88 3.5
op sub spawnx main__tmp_87 main__tmp_88
set main__tmp_80 @vanquish
set main__tmp_81 spawnx
set main__tmp_82 spawny
set main__tmp_83 0
set main__tmp_84 @crux
set main__tmp_85 _
spawn main__tmp_80 main__tmp_81 main__tmp_82 main__tmp_83 main__tmp_84 main__tmp_85
set main__tmp_86 spawnx
set main__tmp_87 3.5
op sub spawnx main__tmp_86 main__tmp_87
main__label_5_endif:
jump main__label_3_endif always 0 0
main__label_2_else:
set main__tmp_90 roll
set main__tmp_91 2
op greaterThanEq main__tmp_89 main__tmp_90 main__tmp_91
jump main__label_6_else notEqual main__tmp_89 true
set main__tmp_92 @precept
set main__tmp_93 spawnx
set main__tmp_94 spawny
set main__tmp_95 0
set main__tmp_96 @crux
set main__tmp_97 _
spawn main__tmp_92 main__tmp_93 main__tmp_94 main__tmp_95 main__tmp_96 main__tmp_97
set main__tmp_98 spawnx
set main__tmp_99 3.25
op sub spawnx main__tmp_98 main__tmp_99
set main__tmp_89 roll
set main__tmp_90 2
op greaterThanEq main__tmp_88 main__tmp_89 main__tmp_90
jump main__label_6_else notEqual main__tmp_88 true
set main__tmp_91 @precept
set main__tmp_92 spawnx
set main__tmp_93 spawny
set main__tmp_94 0
set main__tmp_95 @crux
set main__tmp_96 _
spawn main__tmp_91 main__tmp_92 main__tmp_93 main__tmp_94 main__tmp_95 main__tmp_96
set main__tmp_97 spawnx
set main__tmp_98 3.25
op sub spawnx main__tmp_97 main__tmp_98
jump main__label_7_endif always 0 0
main__label_6_else:
set main__tmp_101 roll
set main__tmp_102 1
op greaterThanEq main__tmp_100 main__tmp_101 main__tmp_102
jump main__label_8_else notEqual main__tmp_100 true
set main__tmp_103 @locus
set main__tmp_104 spawnx
set main__tmp_105 spawny
set main__tmp_106 0
set main__tmp_107 @crux
set main__tmp_108 _
spawn main__tmp_103 main__tmp_104 main__tmp_105 main__tmp_106 main__tmp_107 main__tmp_108
set main__tmp_109 spawnx
set main__tmp_110 1
op sub spawnx main__tmp_109 main__tmp_110
set main__tmp_100 roll
set main__tmp_101 1
op greaterThanEq main__tmp_99 main__tmp_100 main__tmp_101
jump main__label_8_else notEqual main__tmp_99 true
set main__tmp_102 @locus
set main__tmp_103 spawnx
set main__tmp_104 spawny
set main__tmp_105 0
set main__tmp_106 @crux
set main__tmp_107 _
spawn main__tmp_102 main__tmp_103 main__tmp_104 main__tmp_105 main__tmp_106 main__tmp_107
set main__tmp_108 spawnx
set main__tmp_109 1
op sub spawnx main__tmp_108 main__tmp_109
jump main__label_9_endif always 0 0
main__label_8_else:
set main__tmp_111 @stell
set main__tmp_112 spawnx
set main__tmp_113 spawny
set main__tmp_114 0
set main__tmp_115 @crux
set main__tmp_116 _
spawn main__tmp_111 main__tmp_112 main__tmp_113 main__tmp_114 main__tmp_115 main__tmp_116
set main__tmp_117 spawnx
set main__tmp_118 1
op sub spawnx main__tmp_117 main__tmp_118
set main__tmp_110 @stell
set main__tmp_111 spawnx
set main__tmp_112 spawny
set main__tmp_113 0
set main__tmp_114 @crux
set main__tmp_115 _
spawn main__tmp_110 main__tmp_111 main__tmp_112 main__tmp_113 main__tmp_114 main__tmp_115
set main__tmp_116 spawnx
set main__tmp_117 1
op sub spawnx main__tmp_116 main__tmp_117
main__label_9_endif:
main__label_7_endif:
main__label_3_endif:
set main__tmp_120 spawnx
set main__tmp_121 10
op lessThan main__tmp_119 main__tmp_120 main__tmp_121
jump main__label_10_endif notEqual main__tmp_119 true
set main__tmp_119 spawnx
set main__tmp_120 10
op lessThan main__tmp_118 main__tmp_119 main__tmp_120
jump main__label_10_endif notEqual main__tmp_118 true
set spawnx 10
main__label_10_endif:
set main__tmp_122 spawned
set main__tmp_123 1
op add spawned main__tmp_122 main__tmp_123
set main__tmp_125 spawned
set main__tmp_126 tank_units
op lessThan main__tmp_124 main__tmp_125 main__tmp_126
jump main__label_11_endif notEqual main__tmp_124 true
set main__tmp_121 spawned
set main__tmp_122 1
op add spawned main__tmp_121 main__tmp_122
set main__tmp_124 spawned
set main__tmp_125 tank_units
op lessThan main__tmp_123 main__tmp_124 main__tmp_125
jump main__label_11_endif notEqual main__tmp_123 true
jump spawn_tank_loop always 0 0
main__label_11_endif:
jump spawn_tank_end always 0 0
@ -227,97 +226,97 @@ set spawned 0
spawn_mech_loop:
set roll progression
op rand roll roll 0
set main__tmp_128 roll
set main__tmp_129 3
op greaterThanEq main__tmp_127 main__tmp_128 main__tmp_129
jump main__label_12_else notEqual main__tmp_127 true
set main__tmp_131 roll
set main__tmp_132 4
op greaterThanEq main__tmp_130 main__tmp_131 main__tmp_132
jump main__label_14_else notEqual main__tmp_130 true
set main__tmp_133 @collaris
set main__tmp_134 spawnx
set main__tmp_135 spawny
set main__tmp_136 0
set main__tmp_137 @crux
set main__tmp_138 _
spawn main__tmp_133 main__tmp_134 main__tmp_135 main__tmp_136 main__tmp_137 main__tmp_138
set main__tmp_139 spawnx
set main__tmp_140 5.5
op sub spawnx main__tmp_139 main__tmp_140
set main__tmp_127 roll
set main__tmp_128 3
op greaterThanEq main__tmp_126 main__tmp_127 main__tmp_128
jump main__label_12_else notEqual main__tmp_126 true
set main__tmp_130 roll
set main__tmp_131 4
op greaterThanEq main__tmp_129 main__tmp_130 main__tmp_131
jump main__label_14_else notEqual main__tmp_129 true
set main__tmp_132 @collaris
set main__tmp_133 spawnx
set main__tmp_134 spawny
set main__tmp_135 0
set main__tmp_136 @crux
set main__tmp_137 _
spawn main__tmp_132 main__tmp_133 main__tmp_134 main__tmp_135 main__tmp_136 main__tmp_137
set main__tmp_138 spawnx
set main__tmp_139 5.5
op sub spawnx main__tmp_138 main__tmp_139
jump main__label_15_endif always 0 0
main__label_14_else:
set main__tmp_141 @tecta
set main__tmp_142 spawnx
set main__tmp_143 spawny
set main__tmp_144 0
set main__tmp_145 @crux
set main__tmp_146 _
spawn main__tmp_141 main__tmp_142 main__tmp_143 main__tmp_144 main__tmp_145 main__tmp_146
set main__tmp_147 spawnx
set main__tmp_148 2.87
op sub spawnx main__tmp_147 main__tmp_148
set main__tmp_140 @tecta
set main__tmp_141 spawnx
set main__tmp_142 spawny
set main__tmp_143 0
set main__tmp_144 @crux
set main__tmp_145 _
spawn main__tmp_140 main__tmp_141 main__tmp_142 main__tmp_143 main__tmp_144 main__tmp_145
set main__tmp_146 spawnx
set main__tmp_147 2.87
op sub spawnx main__tmp_146 main__tmp_147
main__label_15_endif:
jump main__label_13_endif always 0 0
main__label_12_else:
set main__tmp_150 roll
set main__tmp_151 2
op greaterThanEq main__tmp_149 main__tmp_150 main__tmp_151
jump main__label_16_else notEqual main__tmp_149 true
set main__tmp_152 @anthicus
set main__tmp_153 spawnx
set main__tmp_154 spawny
set main__tmp_155 0
set main__tmp_156 @crux
set main__tmp_157 _
spawn main__tmp_152 main__tmp_153 main__tmp_154 main__tmp_155 main__tmp_156 main__tmp_157
set main__tmp_158 spawnx
set main__tmp_159 2.62
op sub spawnx main__tmp_158 main__tmp_159
set main__tmp_149 roll
set main__tmp_150 2
op greaterThanEq main__tmp_148 main__tmp_149 main__tmp_150
jump main__label_16_else notEqual main__tmp_148 true
set main__tmp_151 @anthicus
set main__tmp_152 spawnx
set main__tmp_153 spawny
set main__tmp_154 0
set main__tmp_155 @crux
set main__tmp_156 _
spawn main__tmp_151 main__tmp_152 main__tmp_153 main__tmp_154 main__tmp_155 main__tmp_156
set main__tmp_157 spawnx
set main__tmp_158 2.62
op sub spawnx main__tmp_157 main__tmp_158
jump main__label_17_endif always 0 0
main__label_16_else:
set main__tmp_161 roll
set main__tmp_162 1
op greaterThanEq main__tmp_160 main__tmp_161 main__tmp_162
jump main__label_18_else notEqual main__tmp_160 true
set main__tmp_163 @cleroi
set main__tmp_164 spawnx
set main__tmp_165 spawny
set main__tmp_166 0
set main__tmp_167 @crux
set main__tmp_168 _
spawn main__tmp_163 main__tmp_164 main__tmp_165 main__tmp_166 main__tmp_167 main__tmp_168
set main__tmp_169 spawnx
set main__tmp_170 1
op sub spawnx main__tmp_169 main__tmp_170
set main__tmp_160 roll
set main__tmp_161 1
op greaterThanEq main__tmp_159 main__tmp_160 main__tmp_161
jump main__label_18_else notEqual main__tmp_159 true
set main__tmp_162 @cleroi
set main__tmp_163 spawnx
set main__tmp_164 spawny
set main__tmp_165 0
set main__tmp_166 @crux
set main__tmp_167 _
spawn main__tmp_162 main__tmp_163 main__tmp_164 main__tmp_165 main__tmp_166 main__tmp_167
set main__tmp_168 spawnx
set main__tmp_169 1
op sub spawnx main__tmp_168 main__tmp_169
jump main__label_19_endif always 0 0
main__label_18_else:
set main__tmp_171 @merui
set main__tmp_172 spawnx
set main__tmp_173 spawny
set main__tmp_174 0
set main__tmp_175 @crux
set main__tmp_176 _
spawn main__tmp_171 main__tmp_172 main__tmp_173 main__tmp_174 main__tmp_175 main__tmp_176
set main__tmp_177 spawnx
set main__tmp_178 1
op sub spawnx main__tmp_177 main__tmp_178
set main__tmp_170 @merui
set main__tmp_171 spawnx
set main__tmp_172 spawny
set main__tmp_173 0
set main__tmp_174 @crux
set main__tmp_175 _
spawn main__tmp_170 main__tmp_171 main__tmp_172 main__tmp_173 main__tmp_174 main__tmp_175
set main__tmp_176 spawnx
set main__tmp_177 1
op sub spawnx main__tmp_176 main__tmp_177
main__label_19_endif:
main__label_17_endif:
main__label_13_endif:
set main__tmp_180 spawnx
set main__tmp_181 10
op lessThan main__tmp_179 main__tmp_180 main__tmp_181
jump main__label_20_endif notEqual main__tmp_179 true
set main__tmp_179 spawnx
set main__tmp_180 10
op lessThan main__tmp_178 main__tmp_179 main__tmp_180
jump main__label_20_endif notEqual main__tmp_178 true
set spawnx 10
main__label_20_endif:
set main__tmp_182 spawned
set main__tmp_183 1
op add spawned main__tmp_182 main__tmp_183
set main__tmp_185 spawned
set main__tmp_186 mech_units
op lessThan main__tmp_184 main__tmp_185 main__tmp_186
jump main__label_21_endif notEqual main__tmp_184 true
set main__tmp_181 spawned
set main__tmp_182 1
op add spawned main__tmp_181 main__tmp_182
set main__tmp_184 spawned
set main__tmp_185 mech_units
op lessThan main__tmp_183 main__tmp_184 main__tmp_185
jump main__label_21_endif notEqual main__tmp_183 true
jump spawn_mech_loop always 0 0
main__label_21_endif:
jump spawn_mech_end always 0 0
@ -326,97 +325,97 @@ set spawned 0
spawn_air_loop:
set roll progression
op rand roll roll 0
set main__tmp_188 roll
set main__tmp_189 3
op greaterThanEq main__tmp_187 main__tmp_188 main__tmp_189
jump main__label_22_else notEqual main__tmp_187 true
set main__tmp_191 roll
set main__tmp_192 4
op greaterThanEq main__tmp_190 main__tmp_191 main__tmp_192
jump main__label_24_else notEqual main__tmp_190 true
set main__tmp_193 @disrupt
set main__tmp_194 spawnx
set main__tmp_195 spawny
set main__tmp_196 0
set main__tmp_197 @crux
set main__tmp_198 _
spawn main__tmp_193 main__tmp_194 main__tmp_195 main__tmp_196 main__tmp_197 main__tmp_198
set main__tmp_199 spawnx
set main__tmp_200 5.75
op sub spawnx main__tmp_199 main__tmp_200
set main__tmp_187 roll
set main__tmp_188 3
op greaterThanEq main__tmp_186 main__tmp_187 main__tmp_188
jump main__label_22_else notEqual main__tmp_186 true
set main__tmp_190 roll
set main__tmp_191 4
op greaterThanEq main__tmp_189 main__tmp_190 main__tmp_191
jump main__label_24_else notEqual main__tmp_189 true
set main__tmp_192 @disrupt
set main__tmp_193 spawnx
set main__tmp_194 spawny
set main__tmp_195 0
set main__tmp_196 @crux
set main__tmp_197 _
spawn main__tmp_192 main__tmp_193 main__tmp_194 main__tmp_195 main__tmp_196 main__tmp_197
set main__tmp_198 spawnx
set main__tmp_199 5.75
op sub spawnx main__tmp_198 main__tmp_199
jump main__label_25_endif always 0 0
main__label_24_else:
set main__tmp_201 @quell
set main__tmp_202 spawnx
set main__tmp_203 spawny
set main__tmp_204 0
set main__tmp_205 @crux
set main__tmp_206 _
spawn main__tmp_201 main__tmp_202 main__tmp_203 main__tmp_204 main__tmp_205 main__tmp_206
set main__tmp_207 spawnx
set main__tmp_208 4.5
op sub spawnx main__tmp_207 main__tmp_208
set main__tmp_200 @quell
set main__tmp_201 spawnx
set main__tmp_202 spawny
set main__tmp_203 0
set main__tmp_204 @crux
set main__tmp_205 _
spawn main__tmp_200 main__tmp_201 main__tmp_202 main__tmp_203 main__tmp_204 main__tmp_205
set main__tmp_206 spawnx
set main__tmp_207 4.5
op sub spawnx main__tmp_206 main__tmp_207
main__label_25_endif:
jump main__label_23_endif always 0 0
main__label_22_else:
set main__tmp_210 roll
set main__tmp_211 2
op greaterThanEq main__tmp_209 main__tmp_210 main__tmp_211
jump main__label_26_else notEqual main__tmp_209 true
set main__tmp_212 @obviate
set main__tmp_213 spawnx
set main__tmp_214 spawny
set main__tmp_215 0
set main__tmp_216 @crux
set main__tmp_217 _
spawn main__tmp_212 main__tmp_213 main__tmp_214 main__tmp_215 main__tmp_216 main__tmp_217
set main__tmp_218 spawnx
set main__tmp_219 3.12
op sub spawnx main__tmp_218 main__tmp_219
set main__tmp_209 roll
set main__tmp_210 2
op greaterThanEq main__tmp_208 main__tmp_209 main__tmp_210
jump main__label_26_else notEqual main__tmp_208 true
set main__tmp_211 @obviate
set main__tmp_212 spawnx
set main__tmp_213 spawny
set main__tmp_214 0
set main__tmp_215 @crux
set main__tmp_216 _
spawn main__tmp_211 main__tmp_212 main__tmp_213 main__tmp_214 main__tmp_215 main__tmp_216
set main__tmp_217 spawnx
set main__tmp_218 3.12
op sub spawnx main__tmp_217 main__tmp_218
jump main__label_27_endif always 0 0
main__label_26_else:
set main__tmp_221 roll
set main__tmp_222 1
op greaterThanEq main__tmp_220 main__tmp_221 main__tmp_222
jump main__label_28_else notEqual main__tmp_220 true
set main__tmp_223 @avert
set main__tmp_224 spawnx
set main__tmp_225 spawny
set main__tmp_226 0
set main__tmp_227 @crux
set main__tmp_228 _
spawn main__tmp_223 main__tmp_224 main__tmp_225 main__tmp_226 main__tmp_227 main__tmp_228
set main__tmp_229 spawnx
set main__tmp_230 1
op sub spawnx main__tmp_229 main__tmp_230
set main__tmp_220 roll
set main__tmp_221 1
op greaterThanEq main__tmp_219 main__tmp_220 main__tmp_221
jump main__label_28_else notEqual main__tmp_219 true
set main__tmp_222 @avert
set main__tmp_223 spawnx
set main__tmp_224 spawny
set main__tmp_225 0
set main__tmp_226 @crux
set main__tmp_227 _
spawn main__tmp_222 main__tmp_223 main__tmp_224 main__tmp_225 main__tmp_226 main__tmp_227
set main__tmp_228 spawnx
set main__tmp_229 1
op sub spawnx main__tmp_228 main__tmp_229
jump main__label_29_endif always 0 0
main__label_28_else:
set main__tmp_231 @elude
set main__tmp_232 spawnx
set main__tmp_233 spawny
set main__tmp_234 0
set main__tmp_235 @crux
set main__tmp_236 _
spawn main__tmp_231 main__tmp_232 main__tmp_233 main__tmp_234 main__tmp_235 main__tmp_236
set main__tmp_237 spawnx
set main__tmp_238 1
op sub spawnx main__tmp_237 main__tmp_238
set main__tmp_230 @elude
set main__tmp_231 spawnx
set main__tmp_232 spawny
set main__tmp_233 0
set main__tmp_234 @crux
set main__tmp_235 _
spawn main__tmp_230 main__tmp_231 main__tmp_232 main__tmp_233 main__tmp_234 main__tmp_235
set main__tmp_236 spawnx
set main__tmp_237 1
op sub spawnx main__tmp_236 main__tmp_237
main__label_29_endif:
main__label_27_endif:
main__label_23_endif:
set main__tmp_240 spawnx
set main__tmp_241 10
op lessThan main__tmp_239 main__tmp_240 main__tmp_241
jump main__label_30_endif notEqual main__tmp_239 true
set main__tmp_239 spawnx
set main__tmp_240 10
op lessThan main__tmp_238 main__tmp_239 main__tmp_240
jump main__label_30_endif notEqual main__tmp_238 true
set spawnx 10
main__label_30_endif:
set main__tmp_242 spawned
set main__tmp_243 1
op add spawned main__tmp_242 main__tmp_243
set main__tmp_245 spawned
set main__tmp_246 air_units
op lessThan main__tmp_244 main__tmp_245 main__tmp_246
jump main__label_31_endif notEqual main__tmp_244 true
set main__tmp_241 spawned
set main__tmp_242 1
op add spawned main__tmp_241 main__tmp_242
set main__tmp_244 spawned
set main__tmp_245 air_units
op lessThan main__tmp_243 main__tmp_244 main__tmp_245
jump main__label_31_endif notEqual main__tmp_243 true
jump spawn_air_loop always 0 0
main__label_31_endif:
jump spawn_air_end always 0 0

@ -52,12 +52,12 @@ jump spawn_air always 0 0
spawn_air_end:
write wave cell1 1
read timeout cell1 0
set main__tmp_49 @time
op mul main__tmp_50 timeout 1000
op add timeout main__tmp_49 main__tmp_50
set main__tmp_48 @time
op mul main__tmp_49 timeout 1000
op add timeout main__tmp_48 main__tmp_49
main_wait:
set main__tmp_54 @time
op sub remaining timeout main__tmp_54
set main__tmp_53 @time
op sub remaining timeout main__tmp_53
jump main__label_1_endif greaterThan remaining 0
jump main always 0 0
main__label_1_endif:
@ -65,9 +65,9 @@ print "[yellow]Wave "
print wave
print "[white] - "
print "Next wave: "
op div main__tmp_62 remaining 1000
op ceil main__tmp_62 main__tmp_62 0
print main__tmp_62
op div main__tmp_61 remaining 1000
op ceil main__tmp_61 main__tmp_61 0
print main__tmp_61
print " s"
message mission
wait 0.5
@ -79,30 +79,30 @@ set roll progression
op rand roll roll 0
jump main__label_2_else lessThan roll 3
jump main__label_4_else lessThan roll 4
set main__tmp_77 @crux
spawn @conquer spawnx spawny 0 main__tmp_77 _
set main__tmp_76 @crux
spawn @conquer spawnx spawny 0 main__tmp_76 _
op sub spawnx spawnx 5.75
jump main__label_3_endif always 0 0
main__label_4_else:
set main__tmp_85 @crux
spawn @vanquish spawnx spawny 0 main__tmp_85 _
set main__tmp_84 @crux
spawn @vanquish spawnx spawny 0 main__tmp_84 _
op sub spawnx spawnx 3.5
jump main__label_3_endif always 0 0
main__label_2_else:
jump main__label_6_else lessThan roll 2
set main__tmp_96 @crux
spawn @precept spawnx spawny 0 main__tmp_96 _
set main__tmp_95 @crux
spawn @precept spawnx spawny 0 main__tmp_95 _
op sub spawnx spawnx 3.25
jump main__label_7_endif always 0 0
main__label_6_else:
jump main__label_8_else lessThan roll 1
set main__tmp_107 @crux
spawn @locus spawnx spawny 0 main__tmp_107 _
set main__tmp_106 @crux
spawn @locus spawnx spawny 0 main__tmp_106 _
op sub spawnx spawnx 1
jump main__label_9_endif always 0 0
main__label_8_else:
set main__tmp_115 @crux
spawn @stell spawnx spawny 0 main__tmp_115 _
set main__tmp_114 @crux
spawn @stell spawnx spawny 0 main__tmp_114 _
op sub spawnx spawnx 1
main__label_9_endif:
main__label_7_endif:
@ -121,30 +121,30 @@ set roll progression
op rand roll roll 0
jump main__label_12_else lessThan roll 3
jump main__label_14_else lessThan roll 4
set main__tmp_137 @crux
spawn @collaris spawnx spawny 0 main__tmp_137 _
set main__tmp_136 @crux
spawn @collaris spawnx spawny 0 main__tmp_136 _
op sub spawnx spawnx 5.5
jump main__label_13_endif always 0 0
main__label_14_else:
set main__tmp_145 @crux
spawn @tecta spawnx spawny 0 main__tmp_145 _
set main__tmp_144 @crux
spawn @tecta spawnx spawny 0 main__tmp_144 _
op sub spawnx spawnx 2.87
jump main__label_13_endif always 0 0
main__label_12_else:
jump main__label_16_else lessThan roll 2
set main__tmp_156 @crux
spawn @anthicus spawnx spawny 0 main__tmp_156 _
set main__tmp_155 @crux
spawn @anthicus spawnx spawny 0 main__tmp_155 _
op sub spawnx spawnx 2.62
jump main__label_17_endif always 0 0
main__label_16_else:
jump main__label_18_else lessThan roll 1
set main__tmp_167 @crux
spawn @cleroi spawnx spawny 0 main__tmp_167 _
set main__tmp_166 @crux
spawn @cleroi spawnx spawny 0 main__tmp_166 _
op sub spawnx spawnx 1
jump main__label_19_endif always 0 0
main__label_18_else:
set main__tmp_175 @crux
spawn @merui spawnx spawny 0 main__tmp_175 _
set main__tmp_174 @crux
spawn @merui spawnx spawny 0 main__tmp_174 _
op sub spawnx spawnx 1
main__label_19_endif:
main__label_17_endif:
@ -163,30 +163,30 @@ set roll progression
op rand roll roll 0
jump main__label_22_else lessThan roll 3
jump main__label_24_else lessThan roll 4
set main__tmp_197 @crux
spawn @disrupt spawnx spawny 0 main__tmp_197 _
set main__tmp_196 @crux
spawn @disrupt spawnx spawny 0 main__tmp_196 _
op sub spawnx spawnx 5.75
jump main__label_23_endif always 0 0
main__label_24_else:
set main__tmp_205 @crux
spawn @quell spawnx spawny 0 main__tmp_205 _
set main__tmp_204 @crux
spawn @quell spawnx spawny 0 main__tmp_204 _
op sub spawnx spawnx 4.5
jump main__label_23_endif always 0 0
main__label_22_else:
jump main__label_26_else lessThan roll 2
set main__tmp_216 @crux
spawn @obviate spawnx spawny 0 main__tmp_216 _
set main__tmp_215 @crux
spawn @obviate spawnx spawny 0 main__tmp_215 _
op sub spawnx spawnx 3.12
jump main__label_27_endif always 0 0
main__label_26_else:
jump main__label_28_else lessThan roll 1
set main__tmp_227 @crux
spawn @avert spawnx spawny 0 main__tmp_227 _
set main__tmp_226 @crux
spawn @avert spawnx spawny 0 main__tmp_226 _
op sub spawnx spawnx 1
jump main__label_29_endif always 0 0
main__label_28_else:
set main__tmp_235 @crux
spawn @elude spawnx spawny 0 main__tmp_235 _
set main__tmp_234 @crux
spawn @elude spawnx spawny 0 main__tmp_234 _
op sub spawnx spawnx 1
main__label_29_endif:
main__label_27_endif:

Loading…
Cancel
Save