From a818325ce9358180b72a3dc514bfee6f373e3bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Tue, 9 Apr 2024 20:42:11 +0200 Subject: [PATCH] Implement execute blocks - move conditionals to execute blocks - implement "as" in execute blocks --- src/base/diagnostic.rs | 4 +- src/base/mod.rs | 2 +- src/lexical/token.rs | 8 + src/syntax/error.rs | 4 + src/syntax/parser.rs | 25 +- src/syntax/syntax_tree/condition.rs | 6 +- src/syntax/syntax_tree/program.rs | 21 +- src/syntax/syntax_tree/statement.rs | 139 +------ .../syntax_tree/statement/execute_block.rs | 341 ++++++++++++++++++ src/transpile/transpiler.rs | 149 +++++--- 10 files changed, 504 insertions(+), 195 deletions(-) create mode 100644 src/syntax/syntax_tree/statement/execute_block.rs diff --git a/src/base/diagnostic.rs b/src/base/diagnostic.rs index 38e8ae0..eea017d 100644 --- a/src/base/diagnostic.rs +++ b/src/base/diagnostic.rs @@ -6,8 +6,8 @@ pub trait Handler { /// Is a struct that implements [`Handler`] trait by doing nothing with the errors. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct Dummy; +pub struct DummyHandler; -impl Handler for Dummy { +impl Handler for DummyHandler { fn receive(&self, _error: T) {} } diff --git a/src/base/mod.rs b/src/base/mod.rs index a357f24..a01328e 100644 --- a/src/base/mod.rs +++ b/src/base/mod.rs @@ -7,6 +7,6 @@ mod error; pub use error::{Error, Result}; mod diagnostic; -pub use diagnostic::{Dummy, Handler}; +pub use diagnostic::{DummyHandler, Handler}; pub mod log; diff --git a/src/lexical/token.rs b/src/lexical/token.rs index 50e15f3..a4523fe 100644 --- a/src/lexical/token.rs +++ b/src/lexical/token.rs @@ -21,6 +21,7 @@ pub enum KeywordKind { Function, If, Else, + As, Group, Run, Lua, @@ -66,12 +67,19 @@ impl KeywordKind { Self::Function => "fn", Self::If => "if", Self::Else => "else", + Self::As => "as", Self::Group => "group", Self::Run => "run", Self::Lua => "lua", Self::Namespace => "namespace", } } + + /// Whether the keyword starts an execute block. + #[must_use] + pub fn starts_execute_block(&self) -> bool { + matches!(self, Self::If | Self::As) + } } /// Is an enumeration containing all kinds of tokens in the Shulkerscript programming language. diff --git a/src/syntax/error.rs b/src/syntax/error.rs index c5fd3e3..7790114 100644 --- a/src/syntax/error.rs +++ b/src/syntax/error.rs @@ -20,6 +20,8 @@ pub enum SyntaxKind { Statement, Expression, Type, + ExecuteBlock, + ExecuteBlockTail, } /// A syntax/token is expected but found an other invalid token. @@ -44,6 +46,8 @@ impl Display for UnexpectedSyntax { SyntaxKind::Statement => "a statement syntax".to_string(), SyntaxKind::Expression => "an expression syntax".to_string(), SyntaxKind::Type => "a type syntax".to_string(), + SyntaxKind::ExecuteBlock => "an execute block syntax".to_string(), + SyntaxKind::ExecuteBlockTail => "an execute block tail syntax".to_string(), }; let found_binding = match self.found.clone() { Some(Token::Comment(..)) => "a comment token".to_string(), diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 976aff4..6ae3108 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -6,7 +6,7 @@ use enum_as_inner::EnumAsInner; use crate::{ base::Handler, lexical::{ - token::{Identifier, Keyword, KeywordKind, Numeric, Punctuation, Token}, + token::{Identifier, Keyword, KeywordKind, Numeric, Punctuation, StringLiteral, Token}, token_stream::{Delimited, Delimiter, TokenStream, TokenTree}, }, }; @@ -388,7 +388,7 @@ impl<'a> Frame<'a> { /// /// # Errors /// If the next [`Token`] is not an [`Identifier`]. - pub fn parse_numeric(&mut self, handler: &dyn Handler) -> Option { + pub fn parse_numeric(&mut self, handler: &impl Handler) -> Option { match self.next_significant_token() { Reading::Atomic(Token::Numeric(ident)) => Some(ident), found => { @@ -401,6 +401,23 @@ impl<'a> Frame<'a> { } } + /// Expects the next [`Token`] to be an [`StringLiteral`], and returns it. + /// + /// # Errors + /// If the next [`Token`] is not an [`StringLiteral`]. + pub fn parse_string_literal(&mut self, handler: &impl Handler) -> Option { + match self.next_significant_token() { + Reading::Atomic(Token::StringLiteral(literal)) => Some(literal), + found => { + handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::StringLiteral, + found: found.into_token(), + })); + None + } + } + } + /// Expects the next [`Token`] to be a [`Keyword`] of specific kind, and returns it. /// /// # Errors @@ -408,7 +425,7 @@ impl<'a> Frame<'a> { pub fn parse_keyword( &mut self, expected: KeywordKind, - handler: &dyn Handler, + handler: &impl Handler, ) -> Option { match self.next_significant_token() { Reading::Atomic(Token::Keyword(keyword_token)) if keyword_token.keyword == expected => { @@ -432,7 +449,7 @@ impl<'a> Frame<'a> { &mut self, expected: char, skip_insignificant: bool, - handler: &dyn Handler, + handler: &impl Handler, ) -> Option { match if skip_insignificant { self.next_significant_token() diff --git a/src/syntax/syntax_tree/condition.rs b/src/syntax/syntax_tree/condition.rs index b0d7dd0..8a7f41a 100644 --- a/src/syntax/syntax_tree/condition.rs +++ b/src/syntax/syntax_tree/condition.rs @@ -8,7 +8,7 @@ use getset::Getters; use crate::{ base::{ source_file::{SourceElement, Span}, - Dummy, Handler, + DummyHandler, Handler, }, lexical::{ token::{Punctuation, StringLiteral, Token}, @@ -373,11 +373,11 @@ impl<'a> Parser<'a> { self.try_parse(|parser| match parser.next_significant_token() { Reading::Atomic(Token::Punctuation(punc)) => match punc.punctuation { '&' => { - let b = parser.parse_punctuation('&', false, &Dummy)?; + let b = parser.parse_punctuation('&', false, &DummyHandler)?; Some(ConditionalBinaryOperator::LogicalAnd(punc, b)) } '|' => { - let b = parser.parse_punctuation('|', false, &Dummy)?; + let b = parser.parse_punctuation('|', false, &DummyHandler)?; Some(ConditionalBinaryOperator::LogicalOr(punc, b)) } _ => None, diff --git a/src/syntax/syntax_tree/program.rs b/src/syntax/syntax_tree/program.rs index 7e4ff6a..3809f77 100644 --- a/src/syntax/syntax_tree/program.rs +++ b/src/syntax/syntax_tree/program.rs @@ -86,24 +86,9 @@ impl<'a> Parser<'a> { // eat the keyword self.forward(); - let namespace_name = match self.stop_at_significant() { - Reading::Atomic(Token::StringLiteral(name)) => { - self.forward(); - Some(name) - } - unexpected => { - self.forward(); - handler.receive( - UnexpectedSyntax { - expected: SyntaxKind::StringLiteral, - found: unexpected.into_token(), - } - .into(), - ); - None - } - } - .and_then(|name| Namespace::validate_str(name.str_content()).then_some(name))?; + let namespace_name = self + .parse_string_literal(handler) + .and_then(|name| Namespace::validate_str(name.str_content()).then_some(name))?; let semicolon = self.parse_punctuation(';', true, handler)?; diff --git a/src/syntax/syntax_tree/statement.rs b/src/syntax/syntax_tree/statement.rs index 685fc08..a410e06 100644 --- a/src/syntax/syntax_tree/statement.rs +++ b/src/syntax/syntax_tree/statement.rs @@ -1,5 +1,8 @@ //! Syntax tree nodes for statements. +pub mod execute_block; + +use derive_more::From; use getset::Getters; use crate::{ @@ -17,7 +20,9 @@ use crate::{ }, }; -use super::{condition::ParenthesizedCondition, expression::Expression}; +use self::execute_block::ExecuteBlock; + +use super::expression::Expression; /// Syntax Synopsis: /// @@ -34,11 +39,11 @@ use super::{condition::ParenthesizedCondition, expression::Expression}; /// ``` #[allow(missing_docs)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From)] pub enum Statement { Block(Block), LiteralCommand(CommandLiteral), - Conditional(Conditional), + ExecuteBlock(ExecuteBlock), Grouping(Grouping), DocComment(DocComment), Semicolon(Semicolon), @@ -50,7 +55,7 @@ impl SourceElement for Statement { match self { Self::Block(block) => block.span(), Self::LiteralCommand(literal_command) => literal_command.span(), - Self::Conditional(conditional) => conditional.span(), + Self::ExecuteBlock(execute_block) => execute_block.span(), Self::Grouping(grouping) => grouping.span(), Self::DocComment(doc_comment) => doc_comment.span(), Self::Semicolon(semi) => semi.span(), @@ -135,89 +140,6 @@ impl Run { } } -/// Syntax Synopsis: -/// -/// ``` ebnf -/// Conditional: -/// 'if' ParenthizedCondition Block ('else' Block)? -/// ; -/// ``` -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] -pub struct Conditional { - /// The `if` keyword. - #[get = "pub"] - if_keyword: Keyword, - /// The condition of the conditional. - #[get = "pub"] - condition: ParenthesizedCondition, - /// The block of the conditional. - #[get = "pub"] - block: Block, - /// The `else` statement. - #[get = "pub"] - r#else: Option, -} - -impl Conditional { - /// Dissolves the [`Conditional`] into its components. - #[must_use] - pub fn dissolve(self) -> (Keyword, ParenthesizedCondition, Block, Option) { - (self.if_keyword, self.condition, self.block, self.r#else) - } -} - -impl SourceElement for Conditional { - fn span(&self) -> Span { - self.r#else.as_ref().map_or_else( - || { - self.if_keyword - .span() - .join(&self.block.span()) - .expect("The span of the conditional is invalid.") - }, - |r#else| { - self.if_keyword - .span() - .join(&r#else.span()) - .expect("The span of the else conditional is invalid.") - }, - ) - } -} - -/// Syntax Synopsis: -/// -/// ``` ebnf -/// Else: -/// 'else' Block -/// ; -/// ``` -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] -pub struct Else { - /// The `else` keyword. - #[get = "pub"] - else_keyword: Keyword, - /// The block of the else statement. - #[get = "pub"] - block: Box, -} - -impl Else { - /// Dissolves the [`Else`] into its components. - #[must_use] - pub fn dissolve(self) -> (Keyword, Box) { - (self.else_keyword, self.block) - } -} - -impl SourceElement for Else { - fn span(&self) -> Span { - self.else_keyword.span().join(&self.block.span()).unwrap() - } -} - /// Syntax Synopsis: /// /// ``` ebnf @@ -341,45 +263,12 @@ impl<'a> Parser<'a> { Some(Statement::Block(block)) } - // conditional statement - Reading::Atomic(Token::Keyword(if_keyword)) - if if_keyword.keyword == KeywordKind::If => + // execute block + Reading::Atomic(Token::Keyword(execute_keyword)) + if execute_keyword.keyword.starts_execute_block() => { - // eat the if keyword - self.forward(); - - let condition = self.parse_parenthesized_condition(handler)?; - - let block = self.parse_block(handler)?; - - match self.stop_at_significant() { - // else statement - Reading::Atomic(Token::Keyword(else_keyword)) - if else_keyword.keyword == KeywordKind::Else => - { - // eat the else keyword - self.forward(); - - let else_block = self.parse_block(handler)?; - - Some(Statement::Conditional(Conditional { - if_keyword, - condition, - block, - r#else: Some(Else { - else_keyword, - block: Box::new(else_block), - }), - })) - } - // no else statement - _ => Some(Statement::Conditional(Conditional { - if_keyword, - condition, - block, - r#else: None, - })), - } + self.parse_execute_block_statement(handler) + .map(Statement::ExecuteBlock) } // doc comment diff --git a/src/syntax/syntax_tree/statement/execute_block.rs b/src/syntax/syntax_tree/statement/execute_block.rs new file mode 100644 index 0000000..941d5dd --- /dev/null +++ b/src/syntax/syntax_tree/statement/execute_block.rs @@ -0,0 +1,341 @@ +//! Execute block statement syntax tree. + +use derive_more::From; +use enum_as_inner::EnumAsInner; +use getset::Getters; + +use crate::{ + base::{ + source_file::{SourceElement, Span}, + DummyHandler, Handler, + }, + lexical::{ + token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token}, + token_stream::Delimiter, + }, + syntax::{ + error::{Error, SyntaxKind, UnexpectedSyntax}, + parser::{Parser, Reading}, + syntax_tree::condition::ParenthesizedCondition, + }, +}; + +use super::Block; + +/// Syntax Synopsis: +/// ```ebnf +/// ExecuteBlock: +/// ExecuteBlockHead ExecuteBlockTail +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)] +#[allow(missing_docs)] +pub enum ExecuteBlock { + HeadTail(ExecuteBlockHead, ExecuteBlockTail), + IfElse(Conditional, Block, Else), +} +impl SourceElement for ExecuteBlock { + fn span(&self) -> Span { + match self { + Self::HeadTail(head, tail) => head.span().join(&tail.span()).unwrap(), + Self::IfElse(conditional, block, else_) => conditional + .span() + .join(&block.span()) + .unwrap() + .join(&else_.span()) + .unwrap(), + } + } +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner, From)] +#[allow(missing_docs)] +pub enum ExecuteBlockHead { + Conditional(Conditional), + As(As), +} + +impl SourceElement for ExecuteBlockHead { + fn span(&self) -> Span { + match self { + Self::Conditional(conditional) => conditional.span(), + Self::As(as_) => as_.span(), + } + } +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner, From)] +#[allow(missing_docs)] +pub enum ExecuteBlockTail { + ExecuteBlock(Punctuation, Box), + Block(Block), +} + +impl SourceElement for ExecuteBlockTail { + fn span(&self) -> Span { + match self { + Self::ExecuteBlock(punc, execute_block) => punc + .span + .join(&execute_block.span()) + .expect("The span of the execute block tail is invalid."), + Self::Block(block) => block.span(), + } + } +} + +/// Syntax Synopsis: +/// +/// ``` ebnf +/// Conditional: +/// 'if' ParenthizedCondition +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Conditional { + /// The `if` keyword. + #[get = "pub"] + if_keyword: Keyword, + /// The condition of the conditional. + #[get = "pub"] + condition: ParenthesizedCondition, +} + +impl Conditional { + /// Dissolves the [`Conditional`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, ParenthesizedCondition) { + (self.if_keyword, self.condition) + } +} + +impl SourceElement for Conditional { + fn span(&self) -> Span { + self.if_keyword + .span() + .join(&self.condition.span()) + .expect("The span of the conditional is invalid.") + } +} + +/// Syntax Synopsis: +/// +/// ``` ebnf +/// Else: +/// 'else' Block +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Else { + /// The `else` keyword. + #[get = "pub"] + else_keyword: Keyword, + /// The block of the else statement. + #[get = "pub"] + block: Box, +} + +impl Else { + /// Dissolves the [`Else`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Box) { + (self.else_keyword, self.block) + } +} + +impl SourceElement for Else { + fn span(&self) -> Span { + self.else_keyword.span().join(&self.block.span()).unwrap() + } +} + +/// Syntax Synopsis: +/// +/// ```ebnf +/// As: +/// 'as' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct As { + /// The `as` keyword. + #[get = "pub"] + as_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the as statement. + #[get = "pub"] + as_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} + +impl SourceElement for As { + fn span(&self) -> Span { + self.as_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the as statement is invalid.") + } +} +impl As { + /// Dissolves the [`As`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, StringLiteral, Punctuation) { + (self.as_keyword, self.as_selector, self.close_paren) + } +} + +impl<'a> Parser<'a> { + /// Parses an [`ExecuteBlock`]. + pub fn parse_execute_block_statement( + &mut self, + handler: &impl Handler, + ) -> Option { + match self.stop_at_significant() { + Reading::Atomic(Token::Keyword(if_keyword)) + if if_keyword.keyword == KeywordKind::If => + { + // eat the if keyword + self.forward(); + + let condition = self.parse_parenthesized_condition(handler)?; + + let conditional = Conditional { + if_keyword, + condition, + }; + + let else_tail = self.try_parse(|parser| { + let block = parser.parse_block(&DummyHandler)?; + let (else_keyword, else_block) = match parser.stop_at_significant() { + // else statement + Reading::Atomic(Token::Keyword(else_keyword)) + if else_keyword.keyword == KeywordKind::Else => + { + // eat the else keyword + parser.forward(); + + let else_block = parser.parse_block(handler)?; + + Some((else_keyword, else_block)) + } + _ => None, + }?; + + Some(( + block, + Else { + else_keyword, + block: Box::new(else_block), + }, + )) + }); + + if let Some((block, else_tail)) = else_tail { + Some(ExecuteBlock::IfElse(conditional, block, else_tail)) + } else { + let tail = self.parse_execute_block_tail(handler)?; + Some(ExecuteBlock::HeadTail( + ExecuteBlockHead::Conditional(conditional), + tail, + )) + } + } + + Reading::Atomic(Token::Keyword(as_keyword)) + if as_keyword.keyword == KeywordKind::As => + { + // eat the as keyword + self.forward(); + + let as_selector = match self.stop_at_significant() { + Reading::IntoDelimited(punc) if punc.punctuation == '(' => self.step_into( + Delimiter::Parenthesis, + |parser| parser.parse_string_literal(handler), + handler, + ), + unexpected => { + handler.receive( + UnexpectedSyntax { + expected: SyntaxKind::Punctuation('('), + found: unexpected.into_token(), + } + .into(), + ); + None + } + }?; + + let tail = self.parse_execute_block_tail(handler)?; + + Some(ExecuteBlock::HeadTail( + ExecuteBlockHead::As(As { + as_keyword, + open_paren: as_selector.open, + as_selector: as_selector.tree?, + close_paren: as_selector.close, + }), + tail, + )) + } + + // unexpected + unexpected => { + handler.receive( + UnexpectedSyntax { + expected: SyntaxKind::ExecuteBlock, + found: unexpected.into_token(), + } + .into(), + ); + None + } + } + } + + fn parse_execute_block_tail( + &mut self, + handler: &impl Handler, + ) -> Option { + match self.stop_at_significant() { + // nested execute block + Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == ',' => { + // eat the comma + self.forward(); + + let execute_block = self.parse_execute_block_statement(handler)?; + + Some(ExecuteBlockTail::ExecuteBlock( + punc, + Box::new(execute_block), + )) + } + + // end block + Reading::IntoDelimited(punc) if punc.punctuation == '{' => { + let block = self.parse_block(handler)?; + + Some(ExecuteBlockTail::Block(block)) + } + + unexpected => { + handler.receive( + UnexpectedSyntax { + expected: SyntaxKind::ExecuteBlockTail, + found: unexpected.into_token(), + } + .into(), + ); + None + } + } + } +} diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 5a57041..6fd5eeb 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -11,7 +11,10 @@ use crate::{ declaration::Declaration, expression::{Expression, FunctionCall, Primary}, program::{Namespace, ProgramFile}, - statement::{Conditional, Statement}, + statement::{ + execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail}, + Statement, + }, }, }; @@ -243,7 +246,7 @@ impl Transpiler { Statement::Block(_) => { unreachable!("Only literal commands are allowed in functions at this time.") } - Statement::Conditional(cond) => self.transpile_conditional(cond, handler), + Statement::ExecuteBlock(execute) => self.transpile_execute_block(execute, handler), Statement::DocComment(doccomment) => { let content = doccomment.content(); Ok(Some(Command::Comment(content.to_string()))) @@ -295,19 +298,112 @@ impl Transpiler { Ok(Command::Raw(format!("function {location}"))) } + fn transpile_execute_block( + &mut self, + execute: &ExecuteBlock, + handler: &impl Handler, + ) -> TranspileResult> { + self.transpile_execute_block_internal(execute, handler) + .map(|ex| ex.map(Command::Execute)) + } + + fn transpile_execute_block_internal( + &mut self, + execute: &ExecuteBlock, + handler: &impl Handler, + ) -> TranspileResult> { + match execute { + ExecuteBlock::HeadTail(head, tail) => { + let tail = match tail { + ExecuteBlockTail::Block(block) => { + let mut errors = Vec::new(); + let commands = block + .statements() + .iter() + .filter_map(|s| { + self.transpile_statement(s, handler).unwrap_or_else(|err| { + errors.push(err); + None + }) + }) + .collect::>(); + + if !errors.is_empty() { + return Err(errors.remove(0)); + } + if commands.is_empty() { + Ok(None) + } else { + Ok(Some(Execute::Runs(commands))) + } + } + ExecuteBlockTail::ExecuteBlock(_, execute_block) => { + self.transpile_execute_block_internal(execute_block, handler) + } + }?; + let combined = match head { + ExecuteBlockHead::Conditional(cond) => { + if let Some(tail) = tail { + self.transpile_conditional(cond, tail, None, handler)? + } else { + None + } + } + ExecuteBlockHead::As(as_) => { + let selector = as_.as_selector().str_content(); + tail.map(|tail| Execute::As(selector.to_string(), Box::new(tail))) + } + }; + + Ok(combined) + } + 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() + .filter_map(|statement| { + self.transpile_statement(statement, handler) + .unwrap_or_else(|err| { + errors.push(err); + None + }) + }) + .collect(); + if !errors.is_empty() { + return Err(errors.remove(0)); + } + Some(Execute::Runs(commands)) + } else { + self.transpile_statement(&statements[0], handler)? + .map(|cmd| Execute::Run(Box::new(cmd))) + }; + + then.map_or_else( + || Ok(None), + |then| self.transpile_conditional(cond, then, Some(el), handler), + ) + } + } + } + fn transpile_conditional( &mut self, cond: &Conditional, + then: Execute, + el: Option<&Else>, handler: &impl Handler, - ) -> TranspileResult> { - let (_, cond, block, el) = cond.clone().dissolve(); + ) -> TranspileResult> { + let (_, cond) = cond.clone().dissolve(); let (_, cond, _) = cond.dissolve(); - let statements = block.statements(); let mut errors = Vec::new(); let el = el .and_then(|el| { - let (_, block) = el.dissolve(); + let (_, block) = el.clone().dissolve(); let statements = block.statements(); if statements.is_empty() { None @@ -338,41 +434,10 @@ impl Transpiler { return Err(errors.remove(0)); } - if statements.is_empty() { - if el.is_none() { - Ok(None) - } else { - Ok(Some(Command::Execute(Execute::If( - datapack::Condition::from(cond), - Box::new(Execute::Runs(Vec::new())), - el, - )))) - } - } else { - let run = if statements.len() > 1 { - let commands = statements - .iter() - .filter_map(|statement| { - self.transpile_statement(statement, handler) - .unwrap_or_else(|err| { - errors.push(err); - None - }) - }) - .collect(); - Some(Execute::Runs(commands)) - } else { - self.transpile_statement(&statements[0], handler)? - .map(|cmd| Execute::Run(Box::new(cmd))) - }; - - Ok(run.map(|run| { - Command::Execute(Execute::If( - datapack::Condition::from(cond), - Box::new(run), - el, - )) - })) - } + Ok(Some(Execute::If( + datapack::Condition::from(cond), + Box::new(then), + el, + ))) } }