End keyword

main
Shad Amethyst 9 months ago
parent 55d78ab5ce
commit c2ad0ad08c

@ -1,12 +1,8 @@
use regex::Regex;
use super::*;
use crate::prelude::*;
/// 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) {
@ -21,7 +17,7 @@ pub fn optimize_constant(program: MindustryProgram) -> MindustryProgram {
.operands()
.iter()
.filter_map(|operand| match operand {
Operand::Variable(name) if tmp_regex.is_match(name) => Some(name),
Operand::Variable(name) if is_temporary_variable(name) => Some(name),
_ => None,
})
.filter_map(|name| {
@ -36,7 +32,7 @@ pub fn optimize_constant(program: MindustryProgram) -> MindustryProgram {
{
Lookaround::Abort
}
MindustryOperation::JumpLabel(_label) => {
other => {
// Note: jump labels mark boundaries for constants. For instance:
// ```
// set __tmp_1 "this is not a constant"
@ -54,10 +50,7 @@ pub fn optimize_constant(program: MindustryProgram) -> MindustryProgram {
//
// A more complex algorithm could be used to check the flow of the program,
// but this usecase isn't needed yet.
Lookaround::Abort
}
other => {
if other.mutates(name) {
if other.mutates(name) || other.breaks_flow() {
Lookaround::Abort
} else {
Lookaround::Continue

@ -1,13 +1,9 @@
use regex::Regex;
use std::collections::HashSet;
use super::*;
use crate::prelude::*;
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| {
@ -36,7 +32,7 @@ pub(crate) fn optimize_dead_code(program: MindustryProgram) -> MindustryProgram
program,
|_instructions, instruction, _index| match instruction {
MindustryOperation::Set(name, _) | MindustryOperation::Operator(name, _, _, _) => {
if !tmp_regex.is_match(name) {
if !is_temporary_variable(name) {
return None;
}
@ -47,7 +43,7 @@ pub(crate) fn optimize_dead_code(program: MindustryProgram) -> MindustryProgram
Some(vec![])
}
MindustryOperation::JumpLabel(label) => {
if !label_regex.is_match(label) {
if !is_automatic_label(label) {
return None;
}

@ -1,12 +1,8 @@
use regex::Regex;
use super::*;
use crate::prelude::*;
/// 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;
@ -36,7 +32,7 @@ pub fn optimize_jump_op(program: MindustryProgram) -> MindustryProgram {
}
};
if !tmp_regex.is_match(&var_name) {
if !is_temporary_variable(&var_name) {
res.push(instruction.clone());
continue;
}
@ -52,8 +48,7 @@ pub fn optimize_jump_op(program: MindustryProgram) -> MindustryProgram {
{
Lookaround::Stop((*operator, lhs.clone(), rhs.clone()))
}
MindustryOperation::JumpLabel(_) => Lookaround::Abort,
x if x.mutates(&var_name) => Lookaround::Abort,
x if x.mutates(&var_name) || x.breaks_flow() => Lookaround::Abort,
_ => Lookaround::Continue,
},
)

@ -18,3 +18,14 @@ pub(crate) use replace::*;
// - shorten temporary variable names
// - jump normalization
// - variable normalization
/// Returns true if the label was automatically generated, and can be removed by dead code elimination.
pub(crate) fn is_automatic_label(name: &str) -> bool {
name.contains("__label") && !name.contains("__phantom")
}
/// Returns true if `name` refers to a temporary variable.
/// A temporary variable is a variable whose compuation results aren't needed outside of the program.
pub(crate) fn is_temporary_variable(name: &str) -> bool {
name.contains("__tmp")
}

@ -233,7 +233,7 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
});
}
// == Goto ==
// == Goto and End ==
[BasicToken::Goto, BasicToken::Integer(label), ..] => {
tokens.take(2);
instructions.push(BasicAstInstruction::Jump(label.to_string()));
@ -242,6 +242,10 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
tokens.take(2);
instructions.push(BasicAstInstruction::Jump(label.clone()));
}
[BasicToken::End, ..] => {
tokens.take(1);
instructions.push(BasicAstInstruction::End);
}
// == Misc ==
[BasicToken::Name(variable_name), BasicToken::Assign, ..] => {
tokens.take(2);

@ -63,6 +63,22 @@ fn test_tokenize_basic() {
BasicToken::EndIf,
],
);
assert_eq!(
tokenize("if x > 0 then\nend\nend if").unwrap(),
vec![
BasicToken::NewLine,
BasicToken::If,
BasicToken::Name(String::from("x")),
BasicToken::Operator(Operator::Gt),
BasicToken::Integer(0),
BasicToken::Then,
BasicToken::NewLine,
BasicToken::End,
BasicToken::NewLine,
BasicToken::EndIf,
]
);
}
#[test]

@ -17,6 +17,7 @@ pub enum BasicToken {
Next,
While,
Wend,
End,
Do,
Loop,
LabelEnd,
@ -80,6 +81,7 @@ pub fn tokenize(raw: &str) -> Result<Vec<BasicToken>, ParseError> {
let match_jump = Regex::new(r"(?i)^go\s*to").unwrap();
let match_word =
Regex::new(r"(?i)^(?:if|then|else|end\s?(?:if|while)|print|for|to|step|next|while|do|wend|loop)(?:\s|$)").unwrap();
let match_end = Regex::new(r"(?i)^end(?:\s|$)").unwrap();
let match_space = Regex::new(r"^\s+").unwrap();
let match_variable = Regex::new(r"^@?[a-zA-Z_][a-zA-Z_0-9]*").unwrap();
let match_float = Regex::new(r"^[0-9]*\.[0-9]+").unwrap();
@ -122,6 +124,7 @@ pub fn tokenize(raw: &str) -> Result<Vec<BasicToken>, ParseError> {
"loop" => BasicToken::Loop,
_ => unreachable!("{}", word),
}),
match_end => (BasicToken::End),
match_variable(name) => (BasicToken::Name(name.to_string())),
match_float(float) => (BasicToken::Float(float.parse().unwrap())),
match_integer(int) => (BasicToken::Integer(int.parse().unwrap())),

@ -13,8 +13,9 @@ pub enum BasicAstExpression {
#[derive(Clone, Debug, PartialEq)]
pub enum BasicAstInstruction {
JumpLabel(String),
Assign(String, BasicAstExpression),
Jump(String),
End,
Assign(String, BasicAstExpression),
IfThenElse(BasicAstExpression, BasicAstBlock, BasicAstBlock),
Print(Vec<(BasicAstExpression, bool)>),
CallBuiltin(String, Vec<BasicAstExpression>),

@ -37,6 +37,7 @@ pub enum MindustryOperation {
JumpLabel(String),
Jump(String),
JumpIf(String, Operator, Operand, Operand),
End,
Operator(String, Operator, Operand, Operand),
UnaryOperator(String, UnaryOperator, Operand),
@ -76,7 +77,7 @@ pub enum MindustryOperation {
impl MindustryOperation {
pub(crate) fn operands(&self) -> Box<[&Operand]> {
match self {
Self::Jump(_) | Self::JumpLabel(_) => Box::new([]),
Self::Jump(_) | Self::JumpLabel(_) | Self::End => Box::new([]),
Self::JumpIf(_label, _operator, lhs, rhs) => Box::new([lhs, rhs]),
Self::Operator(_target, _operator, lhs, rhs) => Box::new([lhs, rhs]),
Self::UnaryOperator(_target, _operator, value) => Box::new([value]),
@ -106,7 +107,7 @@ impl MindustryOperation {
pub(crate) fn operands_mut(&mut self) -> Vec<&mut Operand> {
match self {
Self::Jump(_) | Self::JumpLabel(_) => vec![],
Self::Jump(_) | Self::JumpLabel(_) | Self::End => vec![],
Self::JumpIf(_label, _operator, lhs, rhs) => vec![lhs, rhs],
Self::Operator(_target, _operator, lhs, rhs) => vec![lhs, rhs],
Self::UnaryOperator(_target, _operator, value) => vec![value],
@ -132,29 +133,59 @@ impl MindustryOperation {
pub(crate) fn mutates(&self, var_name: &str) -> bool {
match self {
MindustryOperation::JumpLabel(_)
| MindustryOperation::Jump(_)
| MindustryOperation::JumpIf(_, _, _, _)
| MindustryOperation::Generic(_, _)
| MindustryOperation::Write {
Self::JumpLabel(_)
| Self::Jump(_)
| Self::JumpIf(_, _, _, _)
| Self::Generic(_, _)
| Self::End
| Self::Write {
value: _,
cell: _,
index: _,
} => false,
MindustryOperation::Operator(out_name, _, _, _)
| MindustryOperation::UnaryOperator(out_name, _, _)
| MindustryOperation::Set(out_name, _)
| MindustryOperation::GenericMut(_, out_name, _)
| MindustryOperation::Read {
Self::Operator(out_name, _, _, _)
| Self::UnaryOperator(out_name, _, _)
| Self::Set(out_name, _)
| Self::GenericMut(_, out_name, _)
| Self::Read {
out_name,
cell: _,
index: _,
} => out_name == var_name,
MindustryOperation::Print(_)
| MindustryOperation::PrintFlush(_)
| MindustryOperation::WorldPrintFlush(_) => var_name == STRING_BUFFER,
Self::Print(_) | Self::PrintFlush(_) | Self::WorldPrintFlush(_) => {
var_name == STRING_BUFFER
}
}
}
/// Returns true if the instruction could cause the next instruction to be executed to not be `@counter + 1`,
/// or the previous instruction to be executed to not be `@counter - 1`.
/// In this case, a jump label should be present at the site of jump, or else the optimizations could break things.
pub fn breaks_flow(&self) -> bool {
match self {
Self::JumpLabel(_) | Self::Jump(_) | Self::JumpIf(_, _, _, _) | Self::End => true,
Self::Read {
out_name: _,
cell: _,
index: _,
}
| Self::Write {
value: _,
cell: _,
index: _,
}
| Self::Print(_)
| Self::PrintFlush(_)
| Self::WorldPrintFlush(_)
| Self::Generic(_, _)
| Self::GenericMut(_, _, _) => false,
Self::Set(var_name, _)
| Self::Operator(var_name, _, _, _)
| Self::UnaryOperator(var_name, _, _) => var_name == "@counter",
}
}
}

@ -0,0 +1,142 @@
use super::*;
impl std::fmt::Display for MindustryProgram {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let numeric = Regex::new(r"^[0-9]+$").unwrap();
let numeric_jumps =
self.0
.iter()
.filter_map(|op| match op {
MindustryOperation::Jump(target)
| MindustryOperation::JumpIf(target, _, _, _) => Some(target),
_ => None,
})
.filter(|target| numeric.is_match(target))
.cloned()
.collect::<Vec<_>>();
for instruction in self.0.iter() {
match instruction {
MindustryOperation::JumpLabel(label) => {
if numeric.is_match(label) {
if numeric_jumps.contains(label) {
writeln!(f, "line__{}:", label)?;
}
} else {
writeln!(f, "{}:", label)?;
}
}
MindustryOperation::Jump(label) => {
if numeric.is_match(label) {
writeln!(f, "jump line__{} always 0 0", label)?;
} else {
writeln!(f, "jump {} always 0 0", label)?;
}
}
MindustryOperation::JumpIf(label, operator, lhs, rhs) => {
if numeric.is_match(label) {
writeln!(
f,
"jump line__{} {} {} {}",
label,
format_condition(*operator),
lhs,
rhs
)?;
} else {
writeln!(
f,
"jump {} {} {} {}",
label,
format_condition(*operator),
lhs,
rhs
)?;
}
}
MindustryOperation::End => {
writeln!(f, "end")?;
}
MindustryOperation::Operator(name, operator, lhs, rhs) => {
writeln!(
f,
"op {} {} {} {}",
format_operator(*operator),
name,
lhs,
rhs
)?;
}
MindustryOperation::UnaryOperator(name, operator, lhs) => {
writeln!(
f,
"op {} {} {} 0",
format_unary_operator(*operator),
name,
lhs
)?;
}
MindustryOperation::Set(name, value) => {
writeln!(f, "set {} {}", name, value)?;
}
MindustryOperation::Print(value) => {
writeln!(f, "print {}", value)?;
}
MindustryOperation::Read {
out_name,
cell,
index,
} => {
writeln!(f, "read {} {} {}", out_name, cell, index)?;
}
MindustryOperation::Write { value, cell, index } => {
writeln!(f, "write {} {} {}", value, cell, index)?;
}
MindustryOperation::PrintFlush(cell) => {
writeln!(f, "printflush {}", cell)?;
}
MindustryOperation::Generic(name, operands) => {
write!(f, "{}", name)?;
for operand in operands {
write!(f, " {}", operand)?;
}
writeln!(f)?;
}
MindustryOperation::GenericMut(name, out_name, operands) => {
write!(f, "{}", name)?;
write!(f, " {}", out_name)?;
for operand in operands {
write!(f, " {}", operand)?;
}
writeln!(f)?;
}
MindustryOperation::WorldPrintFlush(config) => {
match config {
WorldPrintFlush::Notify => writeln!(f, "message notify")?,
WorldPrintFlush::Mission => writeln!(f, "message mission")?,
WorldPrintFlush::Announce(time) => {
writeln!(f, "message announce {}", time)?
}
WorldPrintFlush::Toast(time) => writeln!(f, "message toast {}", time)?,
};
}
}
}
Ok(())
}
}
fn format_condition(operator: Operator) -> &'static str {
match operator {
Operator::Eq => "equal",
Operator::Neq => "notEqual",
Operator::Lt => "lessThan",
Operator::Lte => "lessThanEqual",
Operator::Gt => "greaterThan",
Operator::Gte => "greaterThanEqual",
x => {
panic!("Operator {:?} is not a condition!", x);
}
}
}

@ -2,6 +2,8 @@ use regex::Regex;
use crate::prelude::*;
mod display;
pub struct Namer {
var_index: usize,
label_index: usize,
@ -132,6 +134,9 @@ pub fn translate_ast(
Instr::Jump(to) => {
res.push(MindustryOperation::Jump(to.clone()));
}
Instr::End => {
res.push(MindustryOperation::End);
}
Instr::Assign(name, expression) => {
let mut instructions = translate_expression(expression, namer, name.clone());
res.append(&mut instructions);
@ -368,141 +373,3 @@ pub fn translate_ast(
res
}
impl std::fmt::Display for MindustryProgram {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let numeric = Regex::new(r"^[0-9]+$").unwrap();
let numeric_jumps =
self.0
.iter()
.filter_map(|op| match op {
MindustryOperation::Jump(target)
| MindustryOperation::JumpIf(target, _, _, _) => Some(target),
_ => None,
})
.filter(|target| numeric.is_match(target))
.cloned()
.collect::<Vec<_>>();
for instruction in self.0.iter() {
match instruction {
MindustryOperation::JumpLabel(label) => {
if numeric.is_match(label) {
if numeric_jumps.contains(label) {
writeln!(f, "line__{}:", label)?;
}
} else {
writeln!(f, "{}:", label)?;
}
}
MindustryOperation::Jump(label) => {
if numeric.is_match(label) {
writeln!(f, "jump line__{} always 0 0", label)?;
} else {
writeln!(f, "jump {} always 0 0", label)?;
}
}
MindustryOperation::JumpIf(label, operator, lhs, rhs) => {
if numeric.is_match(label) {
writeln!(
f,
"jump line__{} {} {} {}",
label,
format_condition(*operator),
lhs,
rhs
)?;
} else {
writeln!(
f,
"jump {} {} {} {}",
label,
format_condition(*operator),
lhs,
rhs
)?;
}
}
MindustryOperation::Operator(name, operator, lhs, rhs) => {
writeln!(
f,
"op {} {} {} {}",
format_operator(*operator),
name,
lhs,
rhs
)?;
}
MindustryOperation::UnaryOperator(name, operator, lhs) => {
writeln!(
f,
"op {} {} {} 0",
format_unary_operator(*operator),
name,
lhs
)?;
}
MindustryOperation::Set(name, value) => {
writeln!(f, "set {} {}", name, value)?;
}
MindustryOperation::Print(value) => {
writeln!(f, "print {}", value)?;
}
MindustryOperation::Read {
out_name,
cell,
index,
} => {
writeln!(f, "read {} {} {}", out_name, cell, index)?;
}
MindustryOperation::Write { value, cell, index } => {
writeln!(f, "write {} {} {}", value, cell, index)?;
}
MindustryOperation::PrintFlush(cell) => {
writeln!(f, "printflush {}", cell)?;
}
MindustryOperation::Generic(name, operands) => {
write!(f, "{}", name)?;
for operand in operands {
write!(f, " {}", operand)?;
}
writeln!(f)?;
}
MindustryOperation::GenericMut(name, out_name, operands) => {
write!(f, "{}", name)?;
write!(f, " {}", out_name)?;
for operand in operands {
write!(f, " {}", operand)?;
}
writeln!(f)?;
}
MindustryOperation::WorldPrintFlush(config) => {
match config {
WorldPrintFlush::Notify => writeln!(f, "message notify")?,
WorldPrintFlush::Mission => writeln!(f, "message mission")?,
WorldPrintFlush::Announce(time) => {
writeln!(f, "message announce {}", time)?
}
WorldPrintFlush::Toast(time) => writeln!(f, "message toast {}", time)?,
};
}
}
}
Ok(())
}
}
fn format_condition(operator: Operator) -> &'static str {
match operator {
Operator::Eq => "equal",
Operator::Neq => "notEqual",
Operator::Lt => "lessThan",
Operator::Lte => "lessThanEqual",
Operator::Gt => "greaterThan",
Operator::Gte => "greaterThanEqual",
x => {
panic!("Operator {:?} is not a condition!", x);
}
}
}

Loading…
Cancel
Save