From c5299c719ffddb921fb64bb16c53aee5a928def1 Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Mon, 2 Oct 2023 19:16:24 +0200 Subject: [PATCH] :art: Refine mlog IR --- examples/procedural-waves.mbas | 4 +- examples/world-print.mbas | 15 ++++ src/config.rs | 74 +++++++++++++++---- src/optimize/dead.rs | 17 +---- src/parse/ast.rs | 7 +- src/parse/mod.rs | 3 + src/repr/mlog.rs | 80 +++++++++++++++++++-- src/translate/mod.rs | 114 ++++++++++++++++++++++++++++-- tests/examples/world-print.0.mlog | 29 ++++++++ tests/examples/world-print.1.mlog | 16 +++++ 10 files changed, 316 insertions(+), 43 deletions(-) create mode 100644 examples/world-print.mbas create mode 100644 tests/examples/world-print.0.mlog create mode 100644 tests/examples/world-print.1.mlog diff --git a/examples/procedural-waves.mbas b/examples/procedural-waves.mbas index 661f660..f7a4251 100644 --- a/examples/procedural-waves.mbas +++ b/examples/procedural-waves.mbas @@ -12,7 +12,7 @@ initial_wait: PRINT "[red]Enemies[white] approaching: " PRINT ceil(remaining / 1000), " s" - PRINT_MESSAGE_MISSION() + PRINT_FLUSH_GLOBAL(mission) wait(0.5) @@ -54,7 +54,7 @@ main: PRINT "[yellow]Wave ", wave, "[white] - " PRINT "Next wave: ", ceil(remaining / 1000), " s" - PRINT_MESSAGE_MISSION() + PRINT_FLUSH_GLOBAL(mission) wait(0.5) diff --git a/examples/world-print.mbas b/examples/world-print.mbas new file mode 100644 index 0000000..2ea58db --- /dev/null +++ b/examples/world-print.mbas @@ -0,0 +1,15 @@ +PRINT "Hello, world" + +IF method < 2 THEN + IF method == 0 THEN + PRINT_FLUSH_GLOBAL(mission) + ELSE + PRINT_FLUSH_GLOBAL(notify) + END IF +ELSE + IF method == 2 THEN + PRINT_FLUSH_GLOBAL(toast, 1) + ELSE + PRINT_FLUSH_GLOBAL(announce, 1) + END IF +END IF diff --git a/src/config.rs b/src/config.rs index 8fbc55d..aea5194 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,8 +1,18 @@ use std::collections::HashMap; -#[derive(Clone, Debug)] +use crate::{ + parse::ParseError, + repr::basic::{BasicAstExpression, BasicAstInstruction}, +}; + pub struct Config { - pub builtin_functions: HashMap, + pub builtin_functions: HashMap, bool, usize)>, + + /// Used for functions like `print_flush_world` + pub special_functions: HashMap< + String, + Box) -> Result>, + >, } impl Default for Config { @@ -11,25 +21,63 @@ impl Default for Config { ( $name:expr, $target_name:expr, $mutating:expr, $n_args:expr ) => { ( String::from($name), - (String::from($target_name), $mutating, $n_args), + ( + ($target_name as Option<&'static str>).map(String::from), + $mutating, + $n_args, + ), ) }; } + + let mut special_functions: HashMap< + String, + Box) -> Result>, + > = HashMap::new(); + + special_functions.insert( + String::from("print_flush_global"), + Box::new(|arguments: Vec| { + let BasicAstExpression::Variable(buffer) = &arguments[0] else { + return Err(ParseError::InvalidArgument(arguments[0].clone())); + }; + + let expected_length = match buffer.as_str() { + "notify" => 1, + "mission" => 1, + "announce" => 2, + "toast" => 2, + _ => return Err(ParseError::InvalidArgument(arguments[0].clone())), + }; + + if arguments.len() != expected_length { + return Err(ParseError::InvalidArgumentCount( + String::from("print_flush_global"), + expected_length, + arguments.len(), + )); + } + + Ok(BasicAstInstruction::CallBuiltin( + String::from("print_flush_global"), + arguments, + )) + }), + ); + Self { builtin_functions: HashMap::from([ - 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), + builtin_function!("print_flush", None, false, 1), + builtin_function!("read", None, true, 3), + builtin_function!("write", None, false, 3), + builtin_function!("wait", Some("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), + builtin_function!("set_flag", Some("setflag"), false, 2), + builtin_function!("get_flag", Some("getflag"), true, 2), // TODO: same thing - builtin_function!("spawn", "spawn", false, 6), + builtin_function!("spawn", Some("spawn"), false, 6), ]), + special_functions, } } } diff --git a/src/optimize/dead.rs b/src/optimize/dead.rs index 944dd90..ab760df 100644 --- a/src/optimize/dead.rs +++ b/src/optimize/dead.rs @@ -27,22 +27,7 @@ pub(crate) fn optimize_dead_code(program: MindustryProgram) -> MindustryProgram 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); - } + other => other.operands().iter().copied().for_each(&mut push_var), } } diff --git a/src/parse/ast.rs b/src/parse/ast.rs index df279aa..a4fce20 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -220,7 +220,10 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result Result { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index fe165a3..5702580 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -4,6 +4,8 @@ pub use tokenize::*; mod ast; pub use ast::*; +use crate::repr::basic::BasicAstExpression; + #[cfg(test)] mod test; @@ -16,4 +18,5 @@ pub enum ParseError { ExpectedVariable, ExpectedOperand, WrongForVariable(String, String), + InvalidArgument(BasicAstExpression), } diff --git a/src/repr/mlog.rs b/src/repr/mlog.rs index 4320866..6eca0de 100644 --- a/src/repr/mlog.rs +++ b/src/repr/mlog.rs @@ -1,5 +1,7 @@ use super::operator::*; +const STRING_BUFFER: &'static str = "@__mlog__string"; + #[derive(Debug, Clone)] pub enum Operand { Variable(String), @@ -22,6 +24,14 @@ impl std::fmt::Display for Operand { } } +#[derive(Debug, Clone)] +pub enum WorldPrintFlush { + Notify, + Mission, + Announce(Operand), + Toast(Operand), +} + #[derive(Debug, Clone)] pub enum MindustryOperation { JumpLabel(String), @@ -29,6 +39,26 @@ pub enum MindustryOperation { JumpIf(String, Operator, Operand, Operand), Operator(String, Operator, Operand, Operand), UnaryOperator(String, UnaryOperator, Operand), + + /// Reads the value at the `index`-th place in `cell` + Read { + out_name: String, + cell: Operand, + index: Operand, + }, + /// Writes `value` to the `index`-th place in `cell` + Write { + value: Operand, + cell: Operand, + index: Operand, + }, + /// Appends the given value to the print buffer + Print(Operand), + /// Flushes the print buffer to a message cell + PrintFlush(Operand), + /// Available to world processors only - flushes the print buffer to a global buffer + WorldPrintFlush(WorldPrintFlush), + // TODO: add RandOperator Set(String, Operand), /// A generic operation, with the following invariants: @@ -46,6 +76,7 @@ pub enum MindustryOperation { impl MindustryOperation { pub(crate) fn operands(&self) -> Box<[&Operand]> { match self { + Self::Jump(_) | Self::JumpLabel(_) => 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]), @@ -56,19 +87,46 @@ impl MindustryOperation { Self::GenericMut(_name, _out_name, operands) => { operands.iter().collect::>().into_boxed_slice() } - _ => Box::new([]), + Self::PrintFlush(cell) => Box::new([cell]), + Self::WorldPrintFlush(wpf) => match wpf { + WorldPrintFlush::Notify => Box::new([]), + WorldPrintFlush::Mission => Box::new([]), + WorldPrintFlush::Announce(time) => Box::new([time]), + WorldPrintFlush::Toast(time) => Box::new([time]), + }, + Self::Print(operand) => Box::new([operand]), + Self::Read { + out_name: _, + cell, + index, + } => Box::new([cell, index]), + Self::Write { value, cell, index } => Box::new([value, cell, index]), } } pub(crate) fn operands_mut(&mut self) -> Vec<&mut Operand> { match self { + Self::Jump(_) | Self::JumpLabel(_) => 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], Self::Set(_target, value) => vec![value], Self::Generic(_name, operands) => operands.iter_mut().collect::>(), Self::GenericMut(_name, _out_name, operands) => operands.iter_mut().collect::>(), - _ => vec![], + Self::PrintFlush(cell) => vec![cell], + Self::WorldPrintFlush(wpf) => match wpf { + WorldPrintFlush::Notify => vec![], + WorldPrintFlush::Mission => vec![], + WorldPrintFlush::Announce(time) => vec![time], + WorldPrintFlush::Toast(time) => vec![time], + }, + Self::Print(operand) => vec![operand], + Self::Read { + out_name: _, + cell, + index, + } => vec![cell, index], + Self::Write { value, cell, index } => vec![value, cell, index], } } @@ -77,12 +135,26 @@ impl MindustryOperation { MindustryOperation::JumpLabel(_) | MindustryOperation::Jump(_) | MindustryOperation::JumpIf(_, _, _, _) - | MindustryOperation::Generic(_, _) => false, + | MindustryOperation::Generic(_, _) + | MindustryOperation::Write { + value: _, + cell: _, + index: _, + } => false, MindustryOperation::Operator(out_name, _, _, _) | MindustryOperation::UnaryOperator(out_name, _, _) | MindustryOperation::Set(out_name, _) - | MindustryOperation::GenericMut(_, out_name, _) => out_name == var_name, + | MindustryOperation::GenericMut(_, out_name, _) + | MindustryOperation::Read { + out_name, + cell: _, + index: _, + } => out_name == var_name, + + MindustryOperation::Print(_) + | MindustryOperation::PrintFlush(_) + | MindustryOperation::WorldPrintFlush(_) => var_name == STRING_BUFFER, } } } diff --git a/src/translate/mod.rs b/src/translate/mod.rs index aacb94f..fa3fe00 100644 --- a/src/translate/mod.rs +++ b/src/translate/mod.rs @@ -86,6 +86,36 @@ fn translate_expression( } } +macro_rules! translate_call { + ( + $name:expr, + $arguments:expr, + $res:expr, + [ $( $matcher:pat ),* $(,)? ], + $instruction:expr $(,)? + ) => {{ + let [ $( $matcher ),* ] = &$arguments[..] else { + let name_uppercase = String::from($name).to_uppercase(); + panic!("{}(...) called with invalid arguments", name_uppercase); + }; + + let instruction = $instruction; + $res.push(instruction); + }} +} + +macro_rules! translate_operand { + ( + $operand:expr, + $res:expr, + $namer:expr $(,)? + ) => {{ + let name = $namer.temporary(); + $res.append(&mut translate_expression($operand, $namer, name.clone())); + Operand::Variable(name) + }}; +} + pub fn translate_ast( basic_ast: &BasicAstBlock, namer: &mut Namer, @@ -188,15 +218,61 @@ pub fn translate_ast( namer, tmp_name.clone(), )); - res.push(MindustryOperation::Generic( - String::from("print"), - vec![Operand::Variable(tmp_name)], - )); + res.push(MindustryOperation::Print(Operand::Variable(tmp_name))); } } + Instr::CallBuiltin(name, arguments) if name == "read" => translate_call!( + "read", + arguments, + res, + [BasicAstExpression::Variable(out_name), cell, index], + MindustryOperation::Read { + out_name: out_name.clone(), + cell: translate_operand!(cell, res, namer), + index: translate_operand!(index, res, namer), + } + ), + Instr::CallBuiltin(name, arguments) if name == "write" => translate_call!( + "write", + arguments, + res, + [value, cell, index], + MindustryOperation::Write { + value: translate_operand!(value, res, namer), + cell: translate_operand!(cell, res, namer), + index: translate_operand!(index, res, namer), + } + ), + Instr::CallBuiltin(name, arguments) if name == "print_flush" => translate_call!( + "print_flush", + arguments, + res, + [cell], + MindustryOperation::PrintFlush(translate_operand!(cell, res, namer)) + ), + Instr::CallBuiltin(name, arguments) if name == "print_flush_global" => { + let BasicAstExpression::Variable(buffer) = &arguments[0] else { + unreachable!("print_flush_global constructed with invalid arguments"); + }; + + let instruction = MindustryOperation::WorldPrintFlush(match buffer.as_str() { + "mission" => WorldPrintFlush::Mission, + "notify" => WorldPrintFlush::Notify, + "announce" => { + WorldPrintFlush::Announce(translate_operand!(&arguments[1], res, namer)) + } + "toast" => { + WorldPrintFlush::Toast(translate_operand!(&arguments[1], res, namer)) + } + _ => unreachable!("print_flush_global constructed with invalid arguments"), + }); + + res.push(instruction); + } Instr::CallBuiltin(name, arguments) => { - let Some((target_name, mutating, _)) = config.builtin_functions.get(name) else { - unreachable!("CallBuilting constructed with unknown function name"); + let Some((Some(target_name), mutating, _)) = config.builtin_functions.get(name) + else { + unreachable!("CallBuiltin constructed with unknown function name"); }; let mutating = *mutating; @@ -315,6 +391,22 @@ impl std::fmt::Display for MindustryProgram { 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 { @@ -330,6 +422,16 @@ impl std::fmt::Display for MindustryProgram { } 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)?, + }; + } } } diff --git a/tests/examples/world-print.0.mlog b/tests/examples/world-print.0.mlog new file mode 100644 index 0000000..8d8502f --- /dev/null +++ b/tests/examples/world-print.0.mlog @@ -0,0 +1,29 @@ +set main__tmp_0 "Hello, world" +print main__tmp_0 +set main__tmp_2 method +set main__tmp_3 2 +op lessThan main__tmp_1 main__tmp_2 main__tmp_3 +jump main__label_0_else notEqual main__tmp_1 true +set main__tmp_5 method +set main__tmp_6 0 +op equal main__tmp_4 main__tmp_5 main__tmp_6 +jump main__label_2_else notEqual main__tmp_4 true +message mission +jump main__label_3_endif always 0 0 +main__label_2_else: +message notify +main__label_3_endif: +jump main__label_1_endif always 0 0 +main__label_0_else: +set main__tmp_8 method +set main__tmp_9 2 +op equal main__tmp_7 main__tmp_8 main__tmp_9 +jump main__label_4_else notEqual main__tmp_7 true +set main__tmp_10 1 +message toast main__tmp_10 +jump main__label_5_endif always 0 0 +main__label_4_else: +set main__tmp_11 1 +message announce main__tmp_11 +main__label_5_endif: +main__label_1_endif: diff --git a/tests/examples/world-print.1.mlog b/tests/examples/world-print.1.mlog new file mode 100644 index 0000000..3f13b90 --- /dev/null +++ b/tests/examples/world-print.1.mlog @@ -0,0 +1,16 @@ +print "Hello, world" +jump main__label_0_else greaterThanEqual method 2 +jump main__label_2_else notEqual method 0 +message mission +jump main__label_1_endif always 0 0 +main__label_2_else: +message notify +jump main__label_1_endif always 0 0 +main__label_0_else: +jump main__label_4_else notEqual method 2 +message toast 1 +jump main__label_5_endif always 0 0 +main__label_4_else: +message announce 1 +main__label_5_endif: +main__label_1_endif: