From c2ad0ad08c90d8ae05d745280d4fe3b1ed6fbeb4 Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Tue, 3 Oct 2023 17:34:21 +0200 Subject: [PATCH] :sparkles: End keyword --- src/optimize/constant.rs | 13 +--- src/optimize/dead.rs | 8 +-- src/optimize/jump.rs | 9 +-- src/optimize/mod.rs | 11 +++ src/parse/ast.rs | 6 +- src/parse/test.rs | 16 +++++ src/parse/tokenize.rs | 3 + src/repr/basic.rs | 3 +- src/repr/mlog.rs | 61 +++++++++++++---- src/translate/display.rs | 142 ++++++++++++++++++++++++++++++++++++++ src/translate/mod.rs | 143 ++------------------------------------- 11 files changed, 237 insertions(+), 178 deletions(-) create mode 100644 src/translate/display.rs diff --git a/src/optimize/constant.rs b/src/optimize/constant.rs index a674fab..61b6609 100644 --- a/src/optimize/constant.rs +++ b/src/optimize/constant.rs @@ -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 diff --git a/src/optimize/dead.rs b/src/optimize/dead.rs index ab760df..ca8e742 100644 --- a/src/optimize/dead.rs +++ b/src/optimize/dead.rs @@ -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; } diff --git a/src/optimize/jump.rs b/src/optimize/jump.rs index 42eb7f9..10dc70e 100644 --- a/src/optimize/jump.rs +++ b/src/optimize/jump.rs @@ -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, }, ) diff --git a/src/optimize/mod.rs b/src/optimize/mod.rs index 8641c7b..628131f 100644 --- a/src/optimize/mod.rs +++ b/src/optimize/mod.rs @@ -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") +} diff --git a/src/parse/ast.rs b/src/parse/ast.rs index 9fa2798..eff39d2 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -233,7 +233,7 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result { tokens.take(2); instructions.push(BasicAstInstruction::Jump(label.to_string())); @@ -242,6 +242,10 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result { + tokens.take(1); + instructions.push(BasicAstInstruction::End); + } // == Misc == [BasicToken::Name(variable_name), BasicToken::Assign, ..] => { tokens.take(2); diff --git a/src/parse/test.rs b/src/parse/test.rs index 95e4f91..142b020 100644 --- a/src/parse/test.rs +++ b/src/parse/test.rs @@ -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] diff --git a/src/parse/tokenize.rs b/src/parse/tokenize.rs index 13f7c18..57e03a4 100644 --- a/src/parse/tokenize.rs +++ b/src/parse/tokenize.rs @@ -17,6 +17,7 @@ pub enum BasicToken { Next, While, Wend, + End, Do, Loop, LabelEnd, @@ -80,6 +81,7 @@ pub fn tokenize(raw: &str) -> Result, 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, 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())), diff --git a/src/repr/basic.rs b/src/repr/basic.rs index 099c1cd..766623f 100644 --- a/src/repr/basic.rs +++ b/src/repr/basic.rs @@ -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), diff --git a/src/repr/mlog.rs b/src/repr/mlog.rs index 6eca0de..4ce71ea 100644 --- a/src/repr/mlog.rs +++ b/src/repr/mlog.rs @@ -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", } } } diff --git a/src/translate/display.rs b/src/translate/display.rs new file mode 100644 index 0000000..dde38c2 --- /dev/null +++ b/src/translate/display.rs @@ -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::>(); + + 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); + } + } +} diff --git a/src/translate/mod.rs b/src/translate/mod.rs index fda9f69..d409f9b 100644 --- a/src/translate/mod.rs +++ b/src/translate/mod.rs @@ -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::>(); - - 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); - } - } -}