From 0f6d9b301f1a8b28685e1cda1509a4943fed080a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Tue, 16 Sep 2025 23:31:13 +0200 Subject: [PATCH] check that assignments to comptime variables are only done in comptime conditionals --- src/semantic/mod.rs | 5 +- src/syntax/syntax_tree/expression.rs | 73 +++- src/transpile/checks.rs | 81 ++++ src/transpile/lua.rs | 12 +- src/transpile/mod.rs | 3 + src/transpile/transpiler.rs | 595 ++++++++++++++++----------- 6 files changed, 524 insertions(+), 245 deletions(-) create mode 100644 src/transpile/checks.rs diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index fd12db9..5107caf 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -42,8 +42,6 @@ mod scope; use error::{IncompatibleFunctionAnnotation, InvalidNamespaceName, UnexpectedExpression}; pub use scope::{SemanticScope, VariableType}; -use super::syntax::syntax_tree::ConnectedList; - impl ProgramFile { /// Analyzes the semantics of the program. pub fn analyze_semantics( @@ -172,8 +170,7 @@ impl Function { if let Some(parameters) = self .parameters() .as_ref() - .map(ConnectedList::elements) - .map(Iterator::collect::>) + .map(|l| l.elements().collect::>()) { if let Some(incompatible) = self.annotations().iter().find(|a| { ["tick", "load", "uninstall"].contains(&a.assignment().identifier.span.str()) diff --git a/src/syntax/syntax_tree/expression.rs b/src/syntax/syntax_tree/expression.rs index be0d1ee..9751f7d 100644 --- a/src/syntax/syntax_tree/expression.rs +++ b/src/syntax/syntax_tree/expression.rs @@ -473,7 +473,7 @@ impl SourceElement for TemplateStringLiteralPart { /// 'lua' '(' (Expression (',' Expression)*)? ')' '{' (.*?)* '}' /// ``` #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +#[derive(Debug, Clone, Getters)] pub struct LuaCode { /// The `lua` keyword. #[get = "pub"] @@ -496,6 +496,12 @@ pub struct LuaCode { /// The right brace of the lua code. #[get = "pub"] right_brace: Punctuation, + /// Result of the evaluation + #[cfg(all(feature = "lua", feature = "shulkerbox"))] + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) eval_result: std::sync::OnceLock< + Result, + >, } impl SourceElement for LuaCode { @@ -520,6 +526,69 @@ impl LuaCode { } } +// PartialEq, Eq, PartialOrd, Ord, Hash + +impl PartialEq for LuaCode { + fn eq(&self, other: &Self) -> bool { + self.lua_keyword == other.lua_keyword + && self.left_parenthesis == other.left_parenthesis + && self.inputs == other.inputs + && self.right_parenthesis == other.right_parenthesis + && self.left_brace == other.left_brace + && self.code == other.code + && self.right_brace == other.right_brace + } +} +impl Eq for LuaCode {} + +impl Ord for LuaCode { + fn cmp(&self, other: &Self) -> Ordering { + let cmp_lua = self.lua_keyword.cmp(&other.lua_keyword); + if cmp_lua != Ordering::Equal { + return cmp_lua; + } + let cmp_left_paren = self.left_parenthesis.cmp(&other.left_parenthesis); + if cmp_left_paren != Ordering::Equal { + return cmp_left_paren; + } + let cmp_inputs = self.inputs.cmp(&other.inputs); + if cmp_inputs != Ordering::Equal { + return cmp_inputs; + } + let cmp_right_paren = self.right_parenthesis.cmp(&other.right_parenthesis); + if cmp_right_paren != Ordering::Equal { + return cmp_right_paren; + } + let cmp_left_brace = self.left_brace.cmp(&other.left_brace); + if cmp_left_brace != Ordering::Equal { + return cmp_left_brace; + } + let cmp_code = self.code.cmp(&other.code); + if cmp_code != Ordering::Equal { + return cmp_code; + } + self.right_brace.cmp(&other.right_brace) + } +} + +impl PartialOrd for LuaCode { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl std::hash::Hash for LuaCode { + fn hash(&self, state: &mut H) { + self.lua_keyword.hash(state); + self.left_parenthesis.hash(state); + self.inputs.hash(state); + self.right_parenthesis.hash(state); + self.left_brace.hash(state); + self.code.hash(state); + self.right_brace.hash(state); + } +} + /// Represents a member access in the syntax tree. /// /// Syntax Synopsis: @@ -802,6 +871,8 @@ impl Parser<'_> { left_brace: tree.open, code: tree.tree?, right_brace: tree.close, + #[cfg(all(feature = "lua", feature = "shulkerbox"))] + eval_result: std::sync::OnceLock::new(), }))) } diff --git a/src/transpile/checks.rs b/src/transpile/checks.rs new file mode 100644 index 0000000..48478b5 --- /dev/null +++ b/src/transpile/checks.rs @@ -0,0 +1,81 @@ +use std::sync::Arc; + +use crate::{ + base::{self, Handler}, + syntax::syntax_tree::statement::{ + execute_block::{ExecuteBlock, ExecuteBlockTail}, + AssignmentDestination, Block, SemicolonStatement, Statement, + }, + transpile::{error::AssignmentError, Scope, TranspileError, TranspileResult, VariableData}, +}; + +impl Block { + pub(super) fn check_no_comptime_assignments( + &self, + scope: &Arc, + handler: &impl Handler, + ) -> TranspileResult<()> { + for statement in self.statements() { + match statement { + Statement::Semicolon(sem) => { + if let SemicolonStatement::Assignment(assignment) = sem.statement() { + if let AssignmentDestination::Identifier(ident) = assignment.destination() { + if let Some(VariableData::ComptimeValue { .. }) = + scope.get_variable(ident.span.str()).as_deref() + { + let err = TranspileError::AssignmentError(AssignmentError { + identifier: ident.span.clone(), + message: "cannot assign to a compile-time variable in a conditional execute block" + .to_string(), + }); + handler.receive(Box::new(err.clone())); + return Err(err); + } + } + } + } + Statement::Grouping(group) => { + group + .block() + .check_no_comptime_assignments(scope, handler)?; + } + Statement::ExecuteBlock(ex) => { + ex.check_no_comptime_assignments(scope, handler)?; + } + Statement::WhileLoop(while_loop) => { + while_loop + .block() + .check_no_comptime_assignments(scope, handler)?; + } + _ => (), + } + } + + Ok(()) + } +} + +impl ExecuteBlock { + pub(super) fn check_no_comptime_assignments( + &self, + scope: &Arc, + handler: &impl Handler, + ) -> TranspileResult<()> { + match self { + Self::HeadTail(_, tail) => match tail { + ExecuteBlockTail::Block(block) => { + block.check_no_comptime_assignments(scope, handler)?; + } + ExecuteBlockTail::ExecuteBlock(_, ex) => { + ex.check_no_comptime_assignments(scope, handler)?; + } + }, + Self::IfElse(_, then, el) => { + then.check_no_comptime_assignments(scope, handler)?; + el.block().check_no_comptime_assignments(scope, handler)?; + } + } + + Ok(()) + } +} diff --git a/src/transpile/lua.rs b/src/transpile/lua.rs index 81bf181..a08a360 100644 --- a/src/transpile/lua.rs +++ b/src/transpile/lua.rs @@ -87,14 +87,22 @@ mod enabled { scope: &Arc, handler: &impl Handler, ) -> TranspileResult> { + if let Some(res) = self.eval_result.get().cloned() { + return Ok(res); + } + // required to keep the lua instance alive let (lua_result, _lua) = self.eval(scope, handler)?; - self.handle_lua_result(lua_result, handler).map(|res| { + let res = self.handle_lua_result(lua_result, handler).map(|res| { res.ok_or_else(|| NotComptime { expression: self.span(), }) - }) + })?; + + self.eval_result.set(res.clone()).ok(); + + Ok(res) } fn add_globals(&self, lua: &Lua, scope: &Arc) -> TranspileResult<()> { diff --git a/src/transpile/mod.rs b/src/transpile/mod.rs index 5e0265b..3299a7a 100644 --- a/src/transpile/mod.rs +++ b/src/transpile/mod.rs @@ -49,6 +49,9 @@ pub use variables::{Scope, VariableData}; pub mod util; +#[cfg(feature = "shulkerbox")] +mod checks; + /// Data of a function. #[derive(Clone, PartialEq, Eq)] pub struct FunctionData { diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index c057de9..9d5bdbc 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -943,111 +943,104 @@ impl Transpiler { ) -> TranspileResult, ShulkerboxMacroStringMap, Execute)>> { match execute { ExecuteBlock::HeadTail(head, tail) => { - let tail = match tail { - ExecuteBlockTail::Block(block) => { - let mut errors = Vec::new(); - let commands = block - .statements() - .iter() - .flat_map(|s| { - self.transpile_statement(s, program_identifier, scope, handler) - .unwrap_or_else(|err| { - errors.push(err); - Vec::new() - }) - }) - .collect::>(); - - if !errors.is_empty() { - return Err(errors.remove(0)); - } - if commands.is_empty() { - Ok(None) - } else { - Ok(Some((Vec::new(), BTreeMap::new(), Execute::Runs(commands)))) + if let ExecuteBlockHead::Conditional(cond) = head { + let cond_eval = cond.condition().expression().comptime_eval(scope, handler); + if cond_eval.is_err() + || cond_eval.is_ok_and(|val| !matches!(val, ComptimeValue::Boolean(_))) + { + match tail { + ExecuteBlockTail::Block(block) => { + block.check_no_comptime_assignments(scope, handler)?; + } + ExecuteBlockTail::ExecuteBlock(_, ex) => { + ex.check_no_comptime_assignments(scope, handler)?; + } } } - ExecuteBlockTail::ExecuteBlock(_, execute_block) => self - .transpile_execute_block_internal( - execute_block, - program_identifier, - scope, - handler, - ), - }?; + } - self.combine_execute_head_tail(head, tail, program_identifier, scope, handler) + self.combine_execute_head_tail( + head, + |transpiler, program_identifier, scope, handler| match tail { + ExecuteBlockTail::Block(block) => { + let mut errors = Vec::new(); + let commands = block + .statements() + .iter() + .flat_map(|s| { + transpiler + .transpile_statement(s, program_identifier, scope, handler) + .unwrap_or_else(|err| { + errors.push(err); + Vec::new() + }) + }) + .collect::>(); + + if !errors.is_empty() { + return Err(errors.remove(0)); + } + if commands.is_empty() { + Ok(None) + } else { + Ok(Some((Vec::new(), BTreeMap::new(), Execute::Runs(commands)))) + } + } + ExecuteBlockTail::ExecuteBlock(_, execute_block) => transpiler + .transpile_execute_block_internal( + execute_block, + program_identifier, + scope, + handler, + ), + }, + program_identifier, + scope, + handler, + ) } ExecuteBlock::IfElse(cond, block, el) => { - let statements = block.statements(); - let then = if statements.is_empty() { - Some(Execute::Runs(Vec::new())) - } else if statements.len() > 1 { - let mut errors = Vec::new(); - let commands = statements - .iter() - .flat_map(|statement| { - self.transpile_statement(statement, program_identifier, scope, handler) - .unwrap_or_else(|err| { - errors.push(err); - Vec::new() - }) - }) - .collect(); - if !errors.is_empty() { - return Err(errors.remove(0)); - } - Some(Execute::Runs(commands)) - } else { - let cmds = self.transpile_statement( - &statements[0], - program_identifier, - scope, - handler, - )?; - if cmds.len() > 1 { - Some(Execute::Runs(cmds)) - } else { - cmds.into_iter() - .next() - .map(|cmd| Execute::Run(Box::new(cmd))) - } - }; + let cond_eval = cond.condition().expression().comptime_eval(scope, handler); + if cond_eval.is_err() + || cond_eval.is_ok_and(|val| !matches!(val, ComptimeValue::Boolean(_))) + { + block.check_no_comptime_assignments(scope, handler)?; + el.block().check_no_comptime_assignments(scope, handler)?; + } - then.map_or_else( - || Ok(None), - |then| { - self.transpile_conditional( - cond, - then, - Some(el), + self.transpile_conditional( + cond, + |transpiler, program_identifier, scope, handler| { + let ex = transpiler.transpile_conditional_block( + block, program_identifier, scope, handler, - ) + )?; + Ok(Some((Vec::new(), BTreeMap::new(), ex))) }, + Some(el), + program_identifier, + scope, + handler, ) } } } - fn transpile_conditional( + fn transpile_conditional_block( &mut self, - cond: &Conditional, - then: Execute, - el: Option<&Else>, + block: &Block, program_identifier: &str, scope: &Arc, handler: &impl Handler, - ) -> TranspileResult, ShulkerboxMacroStringMap, Execute)>> { - let cond_expression = cond.condition().expression().as_ref(); - - let mut errors = Vec::new(); - - let el = el.and_then(|el| { - let (_, block) = el.clone().dissolve(); - let statements = block.statements(); - let cmds = statements + ) -> TranspileResult { + let statements = block.statements(); + if statements.is_empty() { + Ok(Execute::Runs(Vec::new())) + } else if statements.len() > 1 { + let mut errors = Vec::new(); + let commands = statements .iter() .flat_map(|statement| { self.transpile_statement(statement, program_identifier, scope, handler) @@ -1056,118 +1049,226 @@ impl Transpiler { Vec::new() }) }) - .collect::>(); - - match cmds.len() { - 0 => None, - 1 => Some(Execute::Run(Box::new( - cmds.into_iter().next().expect("length is 1"), - ))), - _ => Some(Execute::Runs(cmds)), - } - }); - - if let Ok(ComptimeValue::Boolean(value)) = cond_expression.comptime_eval(scope, handler) { - if value { - Ok(Some((Vec::new(), BTreeMap::new(), then))) - } else { - Ok(el.map(|el| (Vec::new(), BTreeMap::new(), el))) - } - } else { + .collect(); if !errors.is_empty() { return Err(errors.remove(0)); } + Ok(Execute::Runs(commands)) + } else { + let cmds = + self.transpile_statement(&statements[0], program_identifier, scope, handler)?; - let (pre_cond_cmds, prepare_variables, cond) = - self.transpile_expression_as_condition(cond_expression, scope, handler)?; + if cmds.len() > 1 { + Ok(Execute::Runs(cmds)) + } else { + Ok(cmds.into_iter().next().map_or_else( + || Execute::Runs(Vec::new()), + |cmd| Execute::Run(Box::new(cmd)), + )) + } + } + } - match cond { - ExtendedCondition::Runtime(cond) => Ok(Some(( - pre_cond_cmds, - prepare_variables, - Execute::If(cond, Box::new(then), el.map(Box::new)), - ))), - ExtendedCondition::Comptime(cond) => { - if cond { - Ok(Some((Vec::new(), prepare_variables, then))) - } else { - Ok(el.map(|el| (Vec::new(), prepare_variables, el))) - } + fn transpile_conditional( + &mut self, + cond: &Conditional, + compile_then: F, + el: Option<&Else>, + program_identifier: &str, + scope: &Arc, + handler: &H, + ) -> TranspileResult, ShulkerboxMacroStringMap, Execute)>> + where + H: Handler, + F: FnOnce( + &mut Self, + &str, + &Arc, + &H, + ) + -> TranspileResult, ShulkerboxMacroStringMap, Execute)>>, + { + let cond_expression = cond.condition().expression().as_ref(); + + let mut errors = Vec::new(); + + if let Ok(ComptimeValue::Boolean(value)) = cond_expression.comptime_eval(scope, handler) { + return if value { + let then = compile_then(self, program_identifier, scope, handler)?; + + Ok(then) + } else { + let (el, mut else_errors) = el.map_or_else( + || (None, Vec::new()), + |el| self.transpile_else(el, program_identifier, scope, handler), + ); + if !else_errors.is_empty() { + return Err(else_errors.remove(0)); + } + + Ok(el.map(|el| (Vec::new(), BTreeMap::new(), el))) + }; + } + + let then = compile_then(self, program_identifier, scope, handler)?; + let (el, else_errors) = el.map_or_else( + || (None, Vec::new()), + |el| self.transpile_else(el, program_identifier, scope, handler), + ); + errors.extend(else_errors); + + if !errors.is_empty() { + return Err(errors.remove(0)); + } + + let (pre_cond_cmds, prepare_variables, cond) = + self.transpile_expression_as_condition(cond_expression, scope, handler)?; + + match cond { + ExtendedCondition::Runtime(cond) => { + if let Some((mut prepare_cmds, mut then_prepare_variables, then)) = then { + prepare_cmds.extend(pre_cond_cmds); + then_prepare_variables.extend(prepare_variables); + Ok(Some(( + prepare_cmds, + then_prepare_variables, + Execute::If(cond, Box::new(then), el.map(Box::new)), + ))) + } else { + Ok(Some(( + pre_cond_cmds, + prepare_variables, + Execute::If(cond, Box::new(Execute::Runs(Vec::new())), el.map(Box::new)), + ))) + } + } + ExtendedCondition::Comptime(cond) => { + if cond { + then.map_or_else( + || { + Ok(Some(( + Vec::new(), + BTreeMap::new(), + Execute::Runs(Vec::new()), + ))) + }, + |then| Ok(Some(then)), + ) + } else { + Ok(el.map(|el| (Vec::new(), prepare_variables, el))) } } } } - #[expect(clippy::too_many_lines)] - fn combine_execute_head_tail( + fn transpile_else( &mut self, - head: &ExecuteBlockHead, - tail: Option<(Vec, ShulkerboxMacroStringMap, Execute)>, + el: &Else, program_identifier: &str, scope: &Arc, handler: &impl Handler, - ) -> TranspileResult, ShulkerboxMacroStringMap, Execute)>> { - Ok(match head { - ExecuteBlockHead::Conditional(cond) => { - if let Some((mut pre_cmds, prepare_variables, tail)) = tail { - self.transpile_conditional( - cond, - tail, - None, - program_identifier, - scope, - handler, - )? - .map(|(pre_cond_cmds, mut prep_variables, cond)| { - pre_cmds.extend(pre_cond_cmds); - prep_variables.extend(prepare_variables); - (pre_cmds, prep_variables, cond) + ) -> (Option, Vec) { + let mut else_errors = Vec::new(); + + let block = el.block(); + let statements = block.statements(); + let cmds = statements + .iter() + .flat_map(|statement| { + self.transpile_statement(statement, program_identifier, scope, handler) + .unwrap_or_else(|err| { + else_errors.push(err); + Vec::new() }) - } else { - None - } - } + }) + .collect::>(); + + let el = match cmds.len() { + 0 => None, + 1 => Some(Execute::Run(Box::new( + cmds.into_iter().next().expect("length is 1"), + ))), + _ => Some(Execute::Runs(cmds)), + }; + + (el, else_errors) + } + + #[expect(clippy::too_many_lines)] + fn combine_execute_head_tail( + &mut self, + head: &ExecuteBlockHead, + compile_tail: F, + program_identifier: &str, + scope: &Arc, + handler: &H, + ) -> TranspileResult, ShulkerboxMacroStringMap, Execute)>> + where + H: Handler, + F: FnOnce( + &mut Self, + &str, + &Arc, + &H, + ) + -> TranspileResult, ShulkerboxMacroStringMap, Execute)>>, + { + Ok(match head { + ExecuteBlockHead::Conditional(cond) => self.transpile_conditional( + cond, + compile_tail, + None, + program_identifier, + scope, + handler, + )?, ExecuteBlockHead::As(r#as) => { let selector = r#as .as_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = selector.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::As(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::As(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::At(at) => { let selector = at .at_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = selector.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::At(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::At(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::Align(align) => { let align = align .align_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = align.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::Align(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::Align(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::Anchored(anchored) => { let anchor = @@ -1175,28 +1276,32 @@ impl Transpiler { .anchored_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = anchor.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::Anchored(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::Anchored(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::In(r#in) => { let dimension = r#in .in_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = dimension.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::In(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::In(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::Positioned(positioned) => { let position = @@ -1204,14 +1309,16 @@ impl Transpiler { .positioned_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = position.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::Positioned(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::Positioned(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::Rotated(rotated) => { let rotation = @@ -1219,14 +1326,16 @@ impl Transpiler { .rotated_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = rotation.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::Rotated(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::Rotated(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::Facing(facing) => { let facing = @@ -1234,56 +1343,64 @@ impl Transpiler { .facing_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = facing.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::Facing(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::Facing(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::AsAt(as_at) => { let selector = as_at .asat_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = selector.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::AsAt(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::AsAt(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::On(on) => { let dimension = on .on_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = dimension.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::On(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::On(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::Store(store) => { let store = store .store_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = store.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::Store(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::Store(macro_string, Box::new(tail)), + ) + }, + ) } ExecuteBlockHead::Summon(summon) => { let entity = @@ -1291,14 +1408,16 @@ impl Transpiler { .summon_selector() .to_macro_string(Some(self), scope, handler)?; let (macro_string, prepare_variables) = entity.into_sb(); - tail.map(|(pre_cmds, mut prep_variables, tail)| { - prep_variables.extend(prepare_variables); - ( - pre_cmds, - prep_variables, - Execute::Summon(macro_string, Box::new(tail)), - ) - }) + compile_tail(self, program_identifier, scope, handler)?.map( + |(pre_cmds, mut prep_variables, tail)| { + prep_variables.extend(prepare_variables); + ( + pre_cmds, + prep_variables, + Execute::Summon(macro_string, Box::new(tail)), + ) + }, + ) } }) }