diff --git a/src/lexical/token.rs b/src/lexical/token.rs index a4523fe..02bf545 100644 --- a/src/lexical/token.rs +++ b/src/lexical/token.rs @@ -21,7 +21,18 @@ pub enum KeywordKind { Function, If, Else, + Align, + Anchored, As, + AsAt, + At, + Facing, + In, + On, + Positioned, + Rotated, + Store, + Summon, Group, Run, Lua, @@ -67,7 +78,18 @@ impl KeywordKind { Self::Function => "fn", Self::If => "if", Self::Else => "else", + Self::Align => "align", + Self::Anchored => "anchored", Self::As => "as", + Self::AsAt => "asat", + Self::At => "at", + Self::Facing => "facing", + Self::In => "in", + Self::On => "on", + Self::Positioned => "positioned", + Self::Rotated => "rotated", + Self::Store => "store", + Self::Summon => "summon", Self::Group => "group", Self::Run => "run", Self::Lua => "lua", @@ -78,7 +100,22 @@ impl KeywordKind { /// Whether the keyword starts an execute block. #[must_use] pub fn starts_execute_block(&self) -> bool { - matches!(self, Self::If | Self::As) + matches!( + self, + Self::If + | Self::Align + | Self::Anchored + | Self::As + | Self::AsAt + | Self::At + | Self::Facing + | Self::In + | Self::On + | Self::Positioned + | Self::Rotated + | Self::Store + | Self::Summon + ) } } diff --git a/src/syntax/syntax_tree/statement/execute_block.rs b/src/syntax/syntax_tree/statement/execute_block.rs index 941d5dd..5e79010 100644 --- a/src/syntax/syntax_tree/statement/execute_block.rs +++ b/src/syntax/syntax_tree/statement/execute_block.rs @@ -15,7 +15,7 @@ use crate::{ }, syntax::{ error::{Error, SyntaxKind, UnexpectedSyntax}, - parser::{Parser, Reading}, + parser::{DelimitedTree, Parser, Reading}, syntax_tree::condition::ParenthesizedCondition, }, }; @@ -25,7 +25,8 @@ use super::Block; /// Syntax Synopsis: /// ```ebnf /// ExecuteBlock: -/// ExecuteBlockHead ExecuteBlockTail +/// (ExecuteBlockHead ExecuteBlockTail) +/// | (Conditional Block Else) /// ; /// ``` #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -49,23 +50,57 @@ impl SourceElement for ExecuteBlock { } } +/// Syntax Synopsis: +/// ```ebnf +/// ExecuteBlockHead: +/// Conditional +/// | As +/// ; #[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), + Align(Align), + Anchored(Anchored), As(As), + AsAt(AsAt), + At(At), + Facing(Facing), + In(In), + On(On), + Positioned(Positioned), + Rotated(Rotated), + Store(Store), + Summon(Summon), } impl SourceElement for ExecuteBlockHead { fn span(&self) -> Span { match self { Self::Conditional(conditional) => conditional.span(), + Self::Align(align) => align.span(), + Self::Anchored(anchored) => anchored.span(), Self::As(as_) => as_.span(), + Self::AsAt(as_at) => as_at.span(), + Self::At(at) => at.span(), + Self::Facing(facing) => facing.span(), + Self::In(in_) => in_.span(), + Self::On(on) => on.span(), + Self::Positioned(positioned) => positioned.span(), + Self::Rotated(rotated) => rotated.span(), + Self::Store(store) => store.span(), + Self::Summon(summon) => summon.span(), } } } +/// Syntax Synopsis: +/// ```ebnf +/// ExecuteBlockTail: +/// ExecuteBlock +/// | Block +/// ; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner, From)] #[allow(missing_docs)] @@ -188,8 +223,487 @@ impl SourceElement for As { 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) + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.as_keyword, + self.open_paren, + self.as_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// Align: +/// 'align' '(' StringLiteral ')' +/// ; +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Align { + /// The `align` keyword. + #[get = "pub"] + align_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the align statement. + #[get = "pub"] + align_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} + +impl SourceElement for Align { + fn span(&self) -> Span { + self.align_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the align statement is invalid.") + } +} + +impl Align { + /// Dissolves the [`Align`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.align_keyword, + self.open_paren, + self.align_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// Anchored: +/// 'anchored' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Anchored { + /// The `anchored` keyword. + #[get = "pub"] + anchored_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the anchored statement. + #[get = "pub"] + anchored_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} +impl SourceElement for Anchored { + fn span(&self) -> Span { + self.anchored_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the anchored statement is invalid.") + } +} +impl Anchored { + /// Dissolves the [`Anchored`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.anchored_keyword, + self.open_paren, + self.anchored_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// AsAt: +/// 'asat' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct AsAt { + /// The `asat` keyword. + #[get = "pub"] + asat_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the asat statement. + #[get = "pub"] + asat_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} +impl SourceElement for AsAt { + fn span(&self) -> Span { + self.asat_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the asat statement is invalid.") + } +} +impl AsAt { + /// Dissolves the [`AsAt`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.asat_keyword, + self.open_paren, + self.asat_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// At: +/// 'at' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct At { + /// The `at` keyword. + #[get = "pub"] + at_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the at statement. + #[get = "pub"] + at_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} +impl SourceElement for At { + fn span(&self) -> Span { + self.at_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the at statement is invalid.") + } +} +impl At { + /// Dissolves the [`At`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.at_keyword, + self.open_paren, + self.at_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// Facing: +/// 'facing' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Facing { + /// The `facing` keyword. + #[get = "pub"] + facing_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the facing statement. + #[get = "pub"] + facing_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} +impl SourceElement for Facing { + fn span(&self) -> Span { + self.facing_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the facing statement is invalid.") + } +} +impl Facing { + /// Dissolves the [`Facing`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.facing_keyword, + self.open_paren, + self.facing_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// In: +/// 'in' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct In { + /// The `in` keyword. + #[get = "pub"] + in_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the in statement. + #[get = "pub"] + in_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} +impl SourceElement for In { + fn span(&self) -> Span { + self.in_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the in statement is invalid.") + } +} +impl In { + /// Dissolves the [`In`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.in_keyword, + self.open_paren, + self.in_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// On: +/// 'on' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct On { + /// The `on` keyword. + #[get = "pub"] + on_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the on statement. + #[get = "pub"] + on_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} +impl SourceElement for On { + fn span(&self) -> Span { + self.on_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the on statement is invalid.") + } +} +impl On { + /// Dissolves the [`On`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.on_keyword, + self.open_paren, + self.on_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// Positioned: +/// 'positioned' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Positioned { + /// The `positioned` keyword. + #[get = "pub"] + positioned_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the positioned statement. + #[get = "pub"] + positioned_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} +impl SourceElement for Positioned { + fn span(&self) -> Span { + self.positioned_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the positioned statement is invalid.") + } +} +impl Positioned { + /// Dissolves the [`Positioned`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.positioned_keyword, + self.open_paren, + self.positioned_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// Rotated: +/// 'rotated' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Rotated { + /// The `rotated` keyword. + #[get = "pub"] + rotated_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the rotated statement. + #[get = "pub"] + rotated_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} +impl SourceElement for Rotated { + fn span(&self) -> Span { + self.rotated_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the rotated statement is invalid.") + } +} +impl Rotated { + /// Dissolves the [`Rotated`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.rotated_keyword, + self.open_paren, + self.rotated_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// Store: +/// 'store' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Store { + /// The `store` keyword. + #[get = "pub"] + store_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the store statement. + #[get = "pub"] + store_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} +impl SourceElement for Store { + fn span(&self) -> Span { + self.store_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the store statement is invalid.") + } +} +impl Store { + /// Dissolves the [`Store`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.store_keyword, + self.open_paren, + self.store_selector, + self.close_paren, + ) + } +} + +/// Syntax Synopsis: +/// ```ebnf +/// Summon: +/// 'summon' '(' StringLiteral ')' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Summon { + /// The `summon` keyword. + #[get = "pub"] + summon_keyword: Keyword, + /// The open parenthesis. + #[get = "pub"] + open_paren: Punctuation, + /// The selector of the summon statement. + #[get = "pub"] + summon_selector: StringLiteral, + /// The close parenthesis. + #[get = "pub"] + close_paren: Punctuation, +} +impl SourceElement for Summon { + fn span(&self) -> Span { + self.summon_keyword + .span() + .join(&self.close_paren.span()) + .expect("The span of the summon statement is invalid.") + } +} +impl Summon { + /// Dissolves the [`Summon`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) { + ( + self.summon_keyword, + self.open_paren, + self.summon_selector, + self.close_paren, + ) } } @@ -250,13 +764,11 @@ impl<'a> Parser<'a> { } } - Reading::Atomic(Token::Keyword(as_keyword)) - if as_keyword.keyword == KeywordKind::As => - { + Reading::Atomic(Token::Keyword(keyword)) if keyword.keyword.starts_execute_block() => { // eat the as keyword self.forward(); - let as_selector = match self.stop_at_significant() { + let argument = match self.stop_at_significant() { Reading::IntoDelimited(punc) if punc.punctuation == '(' => self.step_into( Delimiter::Parenthesis, |parser| parser.parse_string_literal(handler), @@ -276,15 +788,9 @@ impl<'a> Parser<'a> { 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, - )) + let head = head_from_keyword(keyword, argument)?; + + Some(ExecuteBlock::HeadTail(head, tail)) } // unexpected @@ -339,3 +845,96 @@ impl<'a> Parser<'a> { } } } + +fn head_from_keyword( + keyword: Keyword, + argument: DelimitedTree, +) -> Option { + Some(match keyword.keyword { + KeywordKind::Align => Align { + align_keyword: keyword, + open_paren: argument.open, + align_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::Anchored => Anchored { + anchored_keyword: keyword, + open_paren: argument.open, + anchored_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::As => As { + as_keyword: keyword, + open_paren: argument.open, + as_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::AsAt => AsAt { + asat_keyword: keyword, + open_paren: argument.open, + asat_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::At => At { + at_keyword: keyword, + open_paren: argument.open, + at_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::Facing => Facing { + facing_keyword: keyword, + open_paren: argument.open, + facing_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::In => In { + in_keyword: keyword, + open_paren: argument.open, + in_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::On => On { + on_keyword: keyword, + open_paren: argument.open, + on_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::Positioned => Positioned { + positioned_keyword: keyword, + open_paren: argument.open, + positioned_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::Rotated => Rotated { + rotated_keyword: keyword, + open_paren: argument.open, + rotated_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::Store => Store { + store_keyword: keyword, + open_paren: argument.open, + store_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + KeywordKind::Summon => Summon { + summon_keyword: keyword, + open_paren: argument.open, + summon_selector: argument.tree?, + close_paren: argument.close, + } + .into(), + _ => unreachable!("The keyword is not a valid execute block head."), + }) +} diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 6fd5eeb..324de3a 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -341,21 +341,8 @@ impl Transpiler { 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) + self.combine_execute_head_tail(head, tail, handler) } ExecuteBlock::IfElse(cond, block, el) => { let statements = block.statements(); @@ -440,4 +427,69 @@ impl Transpiler { el, ))) } + + fn combine_execute_head_tail( + &mut self, + head: &ExecuteBlockHead, + tail: Option, + handler: &impl Handler, + ) -> TranspileResult> { + Ok(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))) + } + ExecuteBlockHead::At(at) => { + let selector = at.at_selector().str_content(); + tail.map(|tail| Execute::At(selector.to_string(), Box::new(tail))) + } + ExecuteBlockHead::Align(align) => { + let align = align.align_selector().str_content(); + tail.map(|tail| Execute::Align(align.to_string(), Box::new(tail))) + } + ExecuteBlockHead::Anchored(anchored) => { + let anchor = anchored.anchored_selector().str_content(); + tail.map(|tail| Execute::Anchored(anchor.to_string(), Box::new(tail))) + } + ExecuteBlockHead::In(in_) => { + let dimension = in_.in_selector().str_content(); + tail.map(|tail| Execute::In(dimension.to_string(), Box::new(tail))) + } + ExecuteBlockHead::Positioned(positioned) => { + let position = positioned.positioned_selector().str_content(); + tail.map(|tail| Execute::Positioned(position.to_string(), Box::new(tail))) + } + ExecuteBlockHead::Rotated(rotated) => { + let rotation = rotated.rotated_selector().str_content(); + tail.map(|tail| Execute::Rotated(rotation.to_string(), Box::new(tail))) + } + ExecuteBlockHead::Facing(facing) => { + let facing = facing.facing_selector().str_content(); + tail.map(|tail| Execute::Facing(facing.to_string(), Box::new(tail))) + } + ExecuteBlockHead::AsAt(as_at) => { + let selector = as_at.asat_selector().str_content(); + tail.map(|tail| Execute::AsAt(selector.to_string(), Box::new(tail))) + } + ExecuteBlockHead::On(on) => { + let dimension = on.on_selector().str_content(); + tail.map(|tail| Execute::On(dimension.to_string(), Box::new(tail))) + } + ExecuteBlockHead::Store(store) => { + let store = store.store_selector().str_content(); + tail.map(|tail| Execute::Store(store.to_string(), Box::new(tail))) + } + ExecuteBlockHead::Summon(summon) => { + let entity = summon.summon_selector().str_content(); + tail.map(|tail| Execute::Summon(entity.to_string(), Box::new(tail))) + } + }) + } }