End keyword

main
Shad Amethyst 9 months ago
parent 55d78ab5ce
commit c2ad0ad08c

@ -1,12 +1,8 @@
use regex::Regex;
use super::*; use super::*;
use crate::prelude::*; use crate::prelude::*;
/// Optimizes away unnecessary `sets` /// Optimizes away unnecessary `sets`
pub fn optimize_constant(program: MindustryProgram) -> MindustryProgram { 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 // Returns true if the variable is safe to substitute into an operand
let is_safe_variable = |name: &str| -> bool { let is_safe_variable = |name: &str| -> bool {
if matches!(name, "@this" | "@thisx" | "@thisy" | "@links") || is_unit_constant(name) { if matches!(name, "@this" | "@thisx" | "@thisy" | "@links") || is_unit_constant(name) {
@ -21,7 +17,7 @@ pub fn optimize_constant(program: MindustryProgram) -> MindustryProgram {
.operands() .operands()
.iter() .iter()
.filter_map(|operand| match operand { .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, _ => None,
}) })
.filter_map(|name| { .filter_map(|name| {
@ -36,7 +32,7 @@ pub fn optimize_constant(program: MindustryProgram) -> MindustryProgram {
{ {
Lookaround::Abort Lookaround::Abort
} }
MindustryOperation::JumpLabel(_label) => { other => {
// Note: jump labels mark boundaries for constants. For instance: // Note: jump labels mark boundaries for constants. For instance:
// ``` // ```
// set __tmp_1 "this is not a constant" // 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, // A more complex algorithm could be used to check the flow of the program,
// but this usecase isn't needed yet. // but this usecase isn't needed yet.
Lookaround::Abort if other.mutates(name) || other.breaks_flow() {
}
other => {
if other.mutates(name) {
Lookaround::Abort Lookaround::Abort
} else { } else {
Lookaround::Continue Lookaround::Continue

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

@ -1,12 +1,8 @@
use regex::Regex;
use super::*; use super::*;
use crate::prelude::*; use crate::prelude::*;
/// Tries to merge the condition in an `op` into the `jump` itself /// Tries to merge the condition in an `op` into the `jump` itself
pub fn optimize_jump_op(program: MindustryProgram) -> MindustryProgram { pub fn optimize_jump_op(program: MindustryProgram) -> MindustryProgram {
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
let mut res = MindustryProgram::new(); let mut res = MindustryProgram::new();
let instructions = program.0; 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()); res.push(instruction.clone());
continue; continue;
} }
@ -52,8 +48,7 @@ pub fn optimize_jump_op(program: MindustryProgram) -> MindustryProgram {
{ {
Lookaround::Stop((*operator, lhs.clone(), rhs.clone())) Lookaround::Stop((*operator, lhs.clone(), rhs.clone()))
} }
MindustryOperation::JumpLabel(_) => Lookaround::Abort, x if x.mutates(&var_name) || x.breaks_flow() => Lookaround::Abort,
x if x.mutates(&var_name) => Lookaround::Abort,
_ => Lookaround::Continue, _ => Lookaround::Continue,
}, },
) )

@ -18,3 +18,14 @@ pub(crate) use replace::*;
// - shorten temporary variable names // - shorten temporary variable names
// - jump normalization // - jump normalization
// - variable 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), ..] => { [BasicToken::Goto, BasicToken::Integer(label), ..] => {
tokens.take(2); tokens.take(2);
instructions.push(BasicAstInstruction::Jump(label.to_string())); instructions.push(BasicAstInstruction::Jump(label.to_string()));
@ -242,6 +242,10 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
tokens.take(2); tokens.take(2);
instructions.push(BasicAstInstruction::Jump(label.clone())); instructions.push(BasicAstInstruction::Jump(label.clone()));
} }
[BasicToken::End, ..] => {
tokens.take(1);
instructions.push(BasicAstInstruction::End);
}
// == Misc == // == Misc ==
[BasicToken::Name(variable_name), BasicToken::Assign, ..] => { [BasicToken::Name(variable_name), BasicToken::Assign, ..] => {
tokens.take(2); tokens.take(2);

@ -63,6 +63,22 @@ fn test_tokenize_basic() {
BasicToken::EndIf, 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] #[test]

@ -17,6 +17,7 @@ pub enum BasicToken {
Next, Next,
While, While,
Wend, Wend,
End,
Do, Do,
Loop, Loop,
LabelEnd, 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_jump = Regex::new(r"(?i)^go\s*to").unwrap();
let match_word = 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(); 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_space = Regex::new(r"^\s+").unwrap();
let match_variable = Regex::new(r"^@?[a-zA-Z_][a-zA-Z_0-9]*").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(); 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, "loop" => BasicToken::Loop,
_ => unreachable!("{}", word), _ => unreachable!("{}", word),
}), }),
match_end => (BasicToken::End),
match_variable(name) => (BasicToken::Name(name.to_string())), match_variable(name) => (BasicToken::Name(name.to_string())),
match_float(float) => (BasicToken::Float(float.parse().unwrap())), match_float(float) => (BasicToken::Float(float.parse().unwrap())),
match_integer(int) => (BasicToken::Integer(int.parse().unwrap())), match_integer(int) => (BasicToken::Integer(int.parse().unwrap())),

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

@ -37,6 +37,7 @@ pub enum MindustryOperation {
JumpLabel(String), JumpLabel(String),
Jump(String), Jump(String),
JumpIf(String, Operator, Operand, Operand), JumpIf(String, Operator, Operand, Operand),
End,
Operator(String, Operator, Operand, Operand), Operator(String, Operator, Operand, Operand),
UnaryOperator(String, UnaryOperator, Operand), UnaryOperator(String, UnaryOperator, Operand),
@ -76,7 +77,7 @@ pub enum MindustryOperation {
impl MindustryOperation { impl MindustryOperation {
pub(crate) fn operands(&self) -> Box<[&Operand]> { pub(crate) fn operands(&self) -> Box<[&Operand]> {
match self { 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::JumpIf(_label, _operator, lhs, rhs) => Box::new([lhs, rhs]),
Self::Operator(_target, _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]), Self::UnaryOperator(_target, _operator, value) => Box::new([value]),
@ -106,7 +107,7 @@ impl MindustryOperation {
pub(crate) fn operands_mut(&mut self) -> Vec<&mut Operand> { pub(crate) fn operands_mut(&mut self) -> Vec<&mut Operand> {
match self { match self {
Self::Jump(_) | Self::JumpLabel(_) => vec![], Self::Jump(_) | Self::JumpLabel(_) | Self::End => vec![],
Self::JumpIf(_label, _operator, lhs, rhs) => vec![lhs, rhs], Self::JumpIf(_label, _operator, lhs, rhs) => vec![lhs, rhs],
Self::Operator(_target, _operator, lhs, rhs) => vec![lhs, rhs], Self::Operator(_target, _operator, lhs, rhs) => vec![lhs, rhs],
Self::UnaryOperator(_target, _operator, value) => vec![value], Self::UnaryOperator(_target, _operator, value) => vec![value],
@ -132,29 +133,59 @@ impl MindustryOperation {
pub(crate) fn mutates(&self, var_name: &str) -> bool { pub(crate) fn mutates(&self, var_name: &str) -> bool {
match self { match self {
MindustryOperation::JumpLabel(_) Self::JumpLabel(_)
| MindustryOperation::Jump(_) | Self::Jump(_)
| MindustryOperation::JumpIf(_, _, _, _) | Self::JumpIf(_, _, _, _)
| MindustryOperation::Generic(_, _) | Self::Generic(_, _)
| MindustryOperation::Write { | Self::End
| Self::Write {
value: _, value: _,
cell: _, cell: _,
index: _, index: _,
} => false, } => false,
MindustryOperation::Operator(out_name, _, _, _) Self::Operator(out_name, _, _, _)
| MindustryOperation::UnaryOperator(out_name, _, _) | Self::UnaryOperator(out_name, _, _)
| MindustryOperation::Set(out_name, _) | Self::Set(out_name, _)
| MindustryOperation::GenericMut(_, out_name, _) | Self::GenericMut(_, out_name, _)
| MindustryOperation::Read { | Self::Read {
out_name, out_name,
cell: _, cell: _,
index: _, index: _,
} => out_name == var_name, } => out_name == var_name,
MindustryOperation::Print(_) Self::Print(_) | Self::PrintFlush(_) | Self::WorldPrintFlush(_) => {
| MindustryOperation::PrintFlush(_) var_name == STRING_BUFFER
| MindustryOperation::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::*; use crate::prelude::*;
mod display;
pub struct Namer { pub struct Namer {
var_index: usize, var_index: usize,
label_index: usize, label_index: usize,
@ -132,6 +134,9 @@ pub fn translate_ast(
Instr::Jump(to) => { Instr::Jump(to) => {
res.push(MindustryOperation::Jump(to.clone())); res.push(MindustryOperation::Jump(to.clone()));
} }
Instr::End => {
res.push(MindustryOperation::End);
}
Instr::Assign(name, expression) => { Instr::Assign(name, expression) => {
let mut instructions = translate_expression(expression, namer, name.clone()); let mut instructions = translate_expression(expression, namer, name.clone());
res.append(&mut instructions); res.append(&mut instructions);
@ -368,141 +373,3 @@ pub fn translate_ast(
res 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