🎨 Refine mlog IR

main
Shad Amethyst 9 months ago
parent 3867ef4991
commit c5299c719f

@ -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)

@ -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

@ -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<String, (String, bool, usize)>,
pub builtin_functions: HashMap<String, (Option<String>, bool, usize)>,
/// Used for functions like `print_flush_world`
pub special_functions: HashMap<
String,
Box<dyn Fn(Vec<BasicAstExpression>) -> Result<BasicAstInstruction, ParseError>>,
>,
}
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<dyn Fn(Vec<BasicAstExpression>) -> Result<BasicAstInstruction, _>>,
> = HashMap::new();
special_functions.insert(
String::from("print_flush_global"),
Box::new(|arguments: Vec<BasicAstExpression>| {
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,
}
}
}

@ -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),
}
}

@ -220,7 +220,10 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
}
let lowercase_fn_name = fn_name.to_lowercase();
if let Some((_, mutating, n_args)) =
if let Some(translation_fn) = config.special_functions.get(&lowercase_fn_name) {
instructions.push(translation_fn(arguments)?);
} else if let Some((_, mutating, n_args)) =
config.builtin_functions.get(&lowercase_fn_name)
{
if *mutating {
@ -242,7 +245,7 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result<BasicAstBlock
arguments,
));
} else {
unimplemented!("User procedure calls are not yet supported!");
unimplemented!("No function named {} found!", fn_name);
}
}
_ => {

@ -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),
}

@ -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::<Vec<_>>().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::<Vec<_>>(),
Self::GenericMut(_name, _out_name, operands) => operands.iter_mut().collect::<Vec<_>>(),
_ => 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,
}
}
}

@ -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)?,
};
}
}
}

@ -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:

@ -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:
Loading…
Cancel
Save