diff --git a/examples/control.mbas b/examples/control.mbas new file mode 100644 index 0000000..b56924e --- /dev/null +++ b/examples/control.mbas @@ -0,0 +1,5 @@ +REM Available to any processor, using the `control` instruction +reactor1.enabled = false + +REM Only available to world processors, using the `setprop` instruction +@unit.health = @unit.health / 2 diff --git a/src/config/builtin_function.rs b/src/config/builtin_function.rs index 7845df3..239f691 100644 --- a/src/config/builtin_function.rs +++ b/src/config/builtin_function.rs @@ -1,4 +1,9 @@ -use crate::{parse::ParseError, parse::ParseErrorKind, prelude::*, translate::{Namer, translate_expression}}; +use crate::{ + parse::ParseError, + parse::ParseErrorKind, + prelude::*, + translate::{translate_expression, Namer}, +}; pub trait BuiltinFunction { fn validate_args( @@ -27,13 +32,24 @@ macro_rules! expect_n_args { }; } -fn translate_arguments(arguments: &[BasicAstExpression], namer: &mut Namer, config: &Config) -> (Vec, MindustryProgram) { - let names = (0..arguments.len()).map(|_| namer.temporary()).collect::>(); +fn translate_arguments( + arguments: &[BasicAstExpression], + namer: &mut Namer, + config: &Config, +) -> (Vec, MindustryProgram) { + let names = (0..arguments.len()) + .map(|_| namer.temporary()) + .collect::>(); let mut res = MindustryProgram::new(); for (index, arg) in arguments.iter().enumerate() { - res.append(&mut translate_expression(arg, namer, names[index].clone(), config)); + res.append(&mut translate_expression( + arg, + namer, + names[index].clone(), + config, + )); } (names, res) @@ -87,12 +103,10 @@ impl BuiltinFunction for SensorOperator { match &args[1] { BasicAstExpression::Variable(_name) => Ok(()), - other => { - Err(ParseError::new( - ParseErrorKind::InvalidArgument(other.clone()), - call_span - )) - } + other => Err(ParseError::new( + ParseErrorKind::InvalidArgument(other.clone()), + call_span, + )), } } @@ -112,7 +126,7 @@ impl BuiltinFunction for SensorOperator { res.push(MindustryOperation::Sensor { out_name: target_name, object: Operand::Variable(object_name), - key: Operand::Variable(format!("@{}", key)) + key: Operand::Variable(format!("@{}", key)), }); res diff --git a/src/parse/ast.rs b/src/parse/ast.rs index 8cf76fb..81f6ec9 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -65,7 +65,7 @@ pub fn build_ast( break; }; - match &drop_position(tokens.peek(3))[..] { + match &drop_position(tokens.peek(4))[..] { [BasicToken::NewLine, BasicToken::Integer(label), ..] => { tokens.take(2); instructions.push(BasicAstInstruction::JumpLabel(label.to_string())); @@ -268,15 +268,33 @@ pub fn build_ast( tokens.take(1); instructions.push(BasicAstInstruction::Return); } - // == Misc == - [BasicToken::Name(variable_name), BasicToken::Assign, ..] => { - tokens.take(2); + // == Assign == + [BasicToken::Let, BasicToken::Name(variable_name), BasicToken::Assign, ..] + | [BasicToken::Name(variable_name), BasicToken::Assign, ..] => { + if let [(BasicToken::Let, _)] = &tokens.peek(1)[..] { + tokens.take(3); + } else { + tokens.take(2); + } + let expression = parse_expression(&mut tokens, config)?; instructions.push(BasicAstInstruction::Assign( variable_name.clone(), expression, )); } + [BasicToken::Name(variable_name), BasicToken::Operator(BasicOperator::Sensor), BasicToken::Name(key_name), BasicToken::Assign, ..] => + { + let object = BasicAstExpression::Variable(variable_name.clone()); + let key = SetPropOrControlKey::from(key_name.as_str()); + + tokens.take(4); + + let value = parse_expression(&mut tokens, config)?; + + instructions.push(BasicAstInstruction::SetPropOrControl(key, object, value)); + } + // == Misc == [BasicToken::Print, ..] => { tokens.take(1); @@ -596,23 +614,23 @@ pub(crate) fn parse_expression( } BasicOperator::Sensor => { if let Some(fn_config) = config.builtin_functions.get("__sensor_operator") { - let arguments = vec![ - ast, - rhs - ]; + let arguments = vec![ast, rhs]; fn_config.validate_args(&arguments, operator_pos)?; - BasicAstExpression::BuiltinFunction(String::from("__sensor_operator"), arguments) + BasicAstExpression::BuiltinFunction( + String::from("__sensor_operator"), + arguments, + ) } else { return Err(ParseError::new( - ParseErrorKind::UnexpectedToken(BasicToken::Operator(BasicOperator::Sensor)), - operator_pos + ParseErrorKind::UnexpectedToken(BasicToken::Operator( + BasicOperator::Sensor, + )), + operator_pos, )); } } }; - - } Ok(ast) diff --git a/src/parse/test.rs b/src/parse/test.rs index c872103..805ba97 100644 --- a/src/parse/test.rs +++ b/src/parse/test.rs @@ -21,6 +21,7 @@ fn test_tokenize_basic() { test_drop_position(tokenize("let thing = thing / 2")), vec![ BasicToken::NewLine, + BasicToken::Let, BasicToken::Name(String::from("thing")), BasicToken::Assign, BasicToken::Name(String::from("thing")), diff --git a/src/parse/tokenize.rs b/src/parse/tokenize.rs index 0611ede..b7147c3 100644 --- a/src/parse/tokenize.rs +++ b/src/parse/tokenize.rs @@ -32,6 +32,7 @@ pub enum BasicToken { Name(String), String(String), Operator(BasicOperator), + Let, } /// Transforms a raw string into a sequence of `BasicToken`s @@ -110,7 +111,7 @@ pub fn tokenize(raw: &str) -> Result, ParseError> { // Main match clause for tokens match_token!(line, res, line_index, ch; match_space => (), - match_let => (), + match_let => (BasicToken::Let), match_comment => (), match_jump => (BasicToken::Goto), match_word(word) => (match word.to_lowercase().as_str().trim() { diff --git a/src/repr/basic.rs b/src/repr/basic.rs index fed34e5..c78ecb9 100644 --- a/src/repr/basic.rs +++ b/src/repr/basic.rs @@ -6,15 +6,31 @@ pub enum BasicAstExpression { Float(f64), Variable(String), String(String), - Binary( - Operator, - Box, - Box, - ), + Binary(Operator, Box, Box), Unary(UnaryOperator, Box), BuiltinFunction(String, Vec), } +/// The set of keys for `control` that accept one operand, and otherwise a key for `setprop` +#[derive(Clone, Debug, PartialEq)] +pub enum SetPropOrControlKey { + ControlEnabled, + ControlConfig, + ControlColor, + SetProp(String), +} + +impl From<&str> for SetPropOrControlKey { + fn from(value: &str) -> Self { + match value { + "enabled" => Self::ControlEnabled, + "config" => Self::ControlConfig, + "color" => Self::ControlColor, + other => Self::SetProp(format!("@{}", other)), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub enum BasicAstInstruction { JumpLabel(String), @@ -39,6 +55,7 @@ pub enum BasicAstInstruction { }, While(BasicAstExpression, BasicAstBlock), DoWhile(BasicAstExpression, BasicAstBlock), + SetPropOrControl(SetPropOrControlKey, BasicAstExpression, BasicAstExpression), } #[derive(Clone, Debug, PartialEq, Default)] diff --git a/src/repr/mlog.rs b/src/repr/mlog.rs index 74f657d..8a224b8 100644 --- a/src/repr/mlog.rs +++ b/src/repr/mlog.rs @@ -72,6 +72,13 @@ pub enum MindustryOperation { key: Operand, }, + /// Sets a property of a given block, only available to world processors + WorldSetProp { + key: Operand, + object: Operand, + value: Operand, + }, + /// A generic operation, with the following invariants: /// - all of the operands are read-only /// - there is no external dependency to other variables @@ -144,6 +151,7 @@ impl_operands!( } => vec![cell, index], Self::Write { value, cell, index } => vec![value, cell, index], Self::Sensor { out_name: _, object, key } => vec![object, key], + Self::WorldSetProp { key, object, value } => vec![key, object, value], }, mut: { Self::Generic(_name, operands) => operands.iter_mut().collect::>(), @@ -170,7 +178,12 @@ impl MindustryOperation { cell: _, index: _, } - | Self::Control(_, _) => false, + | Self::Control(_, _) + | Self::WorldSetProp { + value: _, + object: _, + key: _, + } => false, Self::Operator(out_name, _, _, _) | Self::UnaryOperator(out_name, _, _) @@ -220,7 +233,12 @@ impl MindustryOperation { | Self::WorldPrintFlush(_) | Self::Generic(_, _) | Self::GenericMut(_, _, _) - | Self::Control(_, _) => false, + | Self::Control(_, _) + | Self::WorldSetProp { + value: _, + object: _, + key: _, + } => false, Self::Set(var_name, _) | Self::Operator(var_name, _, _, _) diff --git a/src/translate/display.rs b/src/translate/display.rs index 888c800..7baadf4 100644 --- a/src/translate/display.rs +++ b/src/translate/display.rs @@ -134,6 +134,9 @@ impl std::fmt::Display for MindustryProgram { } writeln!(f)?; } + MindustryOperation::WorldSetProp { key, object, value } => { + writeln!(f, "setprop {} {} {}", key, object, value)?; + } } } diff --git a/src/translate/mod.rs b/src/translate/mod.rs index cb5b81d..389f168 100644 --- a/src/translate/mod.rs +++ b/src/translate/mod.rs @@ -357,6 +357,50 @@ pub fn translate_ast( res.push(MindustryOperation::Print(Operand::Variable(tmp_name))); } } + Instr::SetPropOrControl(key, object, value) => { + let object_name = namer.temporary(); + res.append(&mut translate_expression( + object, + namer, + object_name.clone(), + config, + )); + let value_name = namer.temporary(); + res.append(&mut translate_expression( + value, + namer, + value_name.clone(), + config, + )); + + match key { + SetPropOrControlKey::ControlEnabled + | SetPropOrControlKey::ControlConfig + | SetPropOrControlKey::ControlColor => { + let key = match key { + SetPropOrControlKey::ControlEnabled => String::from("enabled"), + SetPropOrControlKey::ControlConfig => String::from("config"), + SetPropOrControlKey::ControlColor => String::from("color"), + _ => unreachable!(), + }; + + res.push(MindustryOperation::Control( + key, + vec![ + Operand::Variable(object_name), + Operand::Variable(value_name), + ], + )); + } + SetPropOrControlKey::SetProp(key) => { + res.push(MindustryOperation::WorldSetProp { + key: Operand::Variable(key.clone()), + object: Operand::Variable(object_name), + value: Operand::Variable(value_name), + }); + } + } + } Instr::CallBuiltin(name, arguments) if name == "read" => translate_call!( "read", arguments, diff --git a/tests/examples/control.0.mlog b/tests/examples/control.0.mlog new file mode 100644 index 0000000..92a738e --- /dev/null +++ b/tests/examples/control.0.mlog @@ -0,0 +1,9 @@ +set main__tmp_0 reactor1 +set main__tmp_1 false +control enabled main__tmp_0 main__tmp_1 +set main__tmp_2 @unit +set main__tmp_6 @unit +sensor main__tmp_4 main__tmp_6 @health +set main__tmp_5 2 +op div main__tmp_3 main__tmp_4 main__tmp_5 +setprop @health main__tmp_2 main__tmp_3 diff --git a/tests/examples/control.1.mlog b/tests/examples/control.1.mlog new file mode 100644 index 0000000..f159244 --- /dev/null +++ b/tests/examples/control.1.mlog @@ -0,0 +1,6 @@ +control enabled reactor1 false +set main__tmp_2 @unit +set main__tmp_6 @unit +sensor main__tmp_4 main__tmp_6 @health +op div main__tmp_3 main__tmp_4 2 +setprop @health main__tmp_2 main__tmp_3