Compare commits
	
		
			No commits in common. "96fe865ac1dcc72db7e98c197c21bb7e002ebf0b" and "f962f5c36b13c36f0380dc63e9bf177a0e2d126d" have entirely different histories.
		
	
	
		
			96fe865ac1
			...
			f962f5c36b
		
	
		| 
						 | 
					@ -9,9 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Added
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Macro strings
 | 
					 | 
				
			||||||
- Function parameters/arguments
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Changed
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Option to deduplicate source files during serialization when using `SerdeWrapper`
 | 
					- Option to deduplicate source files during serialization when using `SerdeWrapper`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,8 +37,7 @@ mlua = { version = "0.10.2", features = ["lua54", "vendored"], optional = true }
 | 
				
			||||||
path-absolutize = "3.1.1"
 | 
					path-absolutize = "3.1.1"
 | 
				
			||||||
pathdiff = "0.2.3"
 | 
					pathdiff = "0.2.3"
 | 
				
			||||||
serde = { version = "1.0.217", features = ["derive"], optional = true }
 | 
					serde = { version = "1.0.217", features = ["derive"], optional = true }
 | 
				
			||||||
# shulkerbox = { version = "0.1.0", default-features = false, optional = true }
 | 
					shulkerbox = { version = "0.1.0", default-features = false, optional = true }
 | 
				
			||||||
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "76d58c0766518fe5ab2635de60ba40972565a3e0", default-features = false, optional = true }
 | 
					 | 
				
			||||||
strsim = "0.11.1"
 | 
					strsim = "0.11.1"
 | 
				
			||||||
strum = { version = "0.27.0", features = ["derive"] }
 | 
					strum = { version = "0.27.0", features = ["derive"] }
 | 
				
			||||||
thiserror = "2.0.11"
 | 
					thiserror = "2.0.11"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								grammar.md
								
								
								
								
							
							
						
						
									
										19
									
								
								grammar.md
								
								
								
								
							| 
						 | 
					@ -12,21 +12,6 @@ Program: Namespace Declaration*;
 | 
				
			||||||
Namespace: 'namespace' StringLiteral;
 | 
					Namespace: 'namespace' StringLiteral;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### StringLiteral
 | 
					 | 
				
			||||||
```ebnf
 | 
					 | 
				
			||||||
StringLiteral: '"' TEXT '"';
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### MacroStringLiteral
 | 
					 | 
				
			||||||
```ebnf
 | 
					 | 
				
			||||||
MacroStringLiteral: '`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### AnyStringLiteral
 | 
					 | 
				
			||||||
```ebnf
 | 
					 | 
				
			||||||
AnyStringLiteral: StringLiteral | MacroStringLiteral;
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Declaration
 | 
					### Declaration
 | 
				
			||||||
```ebnf
 | 
					```ebnf
 | 
				
			||||||
Declaration: FunctionDeclaration | Import | TagDeclaration;
 | 
					Declaration: FunctionDeclaration | Import | TagDeclaration;
 | 
				
			||||||
| 
						 | 
					@ -102,7 +87,7 @@ Condition:
 | 
				
			||||||
PrimaryCondition:
 | 
					PrimaryCondition:
 | 
				
			||||||
    ConditionalPrefix
 | 
					    ConditionalPrefix
 | 
				
			||||||
    | ParenthesizedCondition
 | 
					    | ParenthesizedCondition
 | 
				
			||||||
    | AnyStringLiteral
 | 
					    | StringLiteral
 | 
				
			||||||
    ;
 | 
					    ;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -159,8 +144,6 @@ Expression:
 | 
				
			||||||
```ebnf
 | 
					```ebnf
 | 
				
			||||||
Primary:
 | 
					Primary:
 | 
				
			||||||
    FunctionCall
 | 
					    FunctionCall
 | 
				
			||||||
    | AnyStringLiteral
 | 
					 | 
				
			||||||
    | LuaCode
 | 
					 | 
				
			||||||
    ;
 | 
					    ;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,8 +9,6 @@ pub enum Error {
 | 
				
			||||||
    #[error(transparent)]
 | 
					    #[error(transparent)]
 | 
				
			||||||
    ParseError(#[from] crate::syntax::error::Error),
 | 
					    ParseError(#[from] crate::syntax::error::Error),
 | 
				
			||||||
    #[error(transparent)]
 | 
					    #[error(transparent)]
 | 
				
			||||||
    SemanticError(#[from] crate::semantic::error::Error),
 | 
					 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    TranspileError(#[from] crate::transpile::TranspileError),
 | 
					    TranspileError(#[from] crate::transpile::TranspileError),
 | 
				
			||||||
    #[error("An error occurred: {0}")]
 | 
					    #[error("An error occurred: {0}")]
 | 
				
			||||||
    Other(String),
 | 
					    Other(String),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -254,26 +254,6 @@ impl Span {
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Create a span from the given start byte index to the end of the source file with an offset.
 | 
					 | 
				
			||||||
    #[must_use]
 | 
					 | 
				
			||||||
    pub fn to_end_with_offset(
 | 
					 | 
				
			||||||
        source_file: Arc<SourceFile>,
 | 
					 | 
				
			||||||
        start: usize,
 | 
					 | 
				
			||||||
        end_offset: isize,
 | 
					 | 
				
			||||||
    ) -> Option<Self> {
 | 
					 | 
				
			||||||
        if !source_file.content().is_char_boundary(start) {
 | 
					 | 
				
			||||||
            return None;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Some(Self {
 | 
					 | 
				
			||||||
            start,
 | 
					 | 
				
			||||||
            end: source_file
 | 
					 | 
				
			||||||
                .content()
 | 
					 | 
				
			||||||
                .len()
 | 
					 | 
				
			||||||
                .saturating_add_signed(end_offset),
 | 
					 | 
				
			||||||
            source_file,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Get the string slice of the source code that the span represents.
 | 
					    /// Get the string slice of the source code that the span represents.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn str(&self) -> &str {
 | 
					    pub fn str(&self) -> &str {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,6 @@ use std::{borrow::Cow, collections::HashMap, fmt::Display, str::FromStr, sync::O
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::base::{
 | 
					use crate::base::{
 | 
				
			||||||
    self,
 | 
					    self,
 | 
				
			||||||
    log::SourceCodeDisplay,
 | 
					 | 
				
			||||||
    source_file::{SourceElement, SourceIterator, Span},
 | 
					    source_file::{SourceElement, SourceIterator, Span},
 | 
				
			||||||
    Handler,
 | 
					    Handler,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -146,7 +145,24 @@ pub enum Token {
 | 
				
			||||||
    DocComment(DocComment),
 | 
					    DocComment(DocComment),
 | 
				
			||||||
    CommandLiteral(CommandLiteral),
 | 
					    CommandLiteral(CommandLiteral),
 | 
				
			||||||
    StringLiteral(StringLiteral),
 | 
					    StringLiteral(StringLiteral),
 | 
				
			||||||
    MacroStringLiteral(MacroStringLiteral),
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Token {
 | 
				
			||||||
 | 
					    /// Returns the span of the token.
 | 
				
			||||||
 | 
					    #[must_use]
 | 
				
			||||||
 | 
					    pub fn span(&self) -> &Span {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Self::WhiteSpaces(token) => &token.span,
 | 
				
			||||||
 | 
					            Self::Identifier(token) => &token.span,
 | 
				
			||||||
 | 
					            Self::Keyword(token) => &token.span,
 | 
				
			||||||
 | 
					            Self::Punctuation(token) => &token.span,
 | 
				
			||||||
 | 
					            Self::Numeric(token) => &token.span,
 | 
				
			||||||
 | 
					            Self::Comment(token) => &token.span,
 | 
				
			||||||
 | 
					            Self::DocComment(token) => &token.span,
 | 
				
			||||||
 | 
					            Self::CommandLiteral(token) => &token.span,
 | 
				
			||||||
 | 
					            Self::StringLiteral(token) => &token.span,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SourceElement for Token {
 | 
					impl SourceElement for Token {
 | 
				
			||||||
| 
						 | 
					@ -161,7 +177,6 @@ impl SourceElement for Token {
 | 
				
			||||||
            Self::DocComment(token) => token.span(),
 | 
					            Self::DocComment(token) => token.span(),
 | 
				
			||||||
            Self::CommandLiteral(token) => token.span(),
 | 
					            Self::CommandLiteral(token) => token.span(),
 | 
				
			||||||
            Self::StringLiteral(token) => token.span(),
 | 
					            Self::StringLiteral(token) => token.span(),
 | 
				
			||||||
            Self::MacroStringLiteral(token) => token.span(),
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -275,76 +290,6 @@ impl SourceElement for StringLiteral {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Represents a hardcoded macro string literal value in the source code.
 | 
					 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
					 | 
				
			||||||
pub struct MacroStringLiteral {
 | 
					 | 
				
			||||||
    /// The backtick that starts the macro string literal.
 | 
					 | 
				
			||||||
    starting_backtick: Punctuation,
 | 
					 | 
				
			||||||
    /// The parts that make up the macro string literal.
 | 
					 | 
				
			||||||
    parts: Vec<MacroStringLiteralPart>,
 | 
					 | 
				
			||||||
    /// The backtick that ends the macro string literal.
 | 
					 | 
				
			||||||
    ending_backtick: Punctuation,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl MacroStringLiteral {
 | 
					 | 
				
			||||||
    /// Returns the string content without escapement characters, leading and trailing double quotes.
 | 
					 | 
				
			||||||
    #[cfg(feature = "shulkerbox")]
 | 
					 | 
				
			||||||
    #[must_use]
 | 
					 | 
				
			||||||
    pub fn str_content(&self) -> String {
 | 
					 | 
				
			||||||
        use std::fmt::Write;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut content = String::new();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for part in &self.parts {
 | 
					 | 
				
			||||||
            match part {
 | 
					 | 
				
			||||||
                MacroStringLiteralPart::Text(span) => {
 | 
					 | 
				
			||||||
                    content += &crate::util::unescape_macro_string(span.str());
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                MacroStringLiteralPart::MacroUsage { identifier, .. } => {
 | 
					 | 
				
			||||||
                    write!(
 | 
					 | 
				
			||||||
                        content,
 | 
					 | 
				
			||||||
                        "$({})",
 | 
					 | 
				
			||||||
                        crate::transpile::util::identifier_to_macro(identifier.span.str())
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    .expect("can always write to string");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        content
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Returns the parts that make up the macro string literal.
 | 
					 | 
				
			||||||
    #[must_use]
 | 
					 | 
				
			||||||
    pub fn parts(&self) -> &[MacroStringLiteralPart] {
 | 
					 | 
				
			||||||
        &self.parts
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl SourceElement for MacroStringLiteral {
 | 
					 | 
				
			||||||
    fn span(&self) -> Span {
 | 
					 | 
				
			||||||
        self.starting_backtick
 | 
					 | 
				
			||||||
            .span
 | 
					 | 
				
			||||||
            .join(&self.ending_backtick.span)
 | 
					 | 
				
			||||||
            .expect("Invalid macro string literal span")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Represents a part of a macro string literal value in the source code.
 | 
					 | 
				
			||||||
#[allow(missing_docs)]
 | 
					 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
					 | 
				
			||||||
pub enum MacroStringLiteralPart {
 | 
					 | 
				
			||||||
    Text(Span),
 | 
					 | 
				
			||||||
    MacroUsage {
 | 
					 | 
				
			||||||
        dollar: Punctuation,
 | 
					 | 
				
			||||||
        open_brace: Punctuation,
 | 
					 | 
				
			||||||
        identifier: Identifier,
 | 
					 | 
				
			||||||
        close_brace: Punctuation,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Is an enumeration representing the two kinds of comments in the Shulkerscript programming language.
 | 
					/// Is an enumeration representing the two kinds of comments in the Shulkerscript programming language.
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
				
			||||||
| 
						 | 
					@ -417,7 +362,7 @@ impl CommandLiteral {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Is an error that can occur when invoking the [`Token::tokenize`] method.
 | 
					/// Is an error that can occur when invoking the [`Token::tokenize`] method.
 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error, From)]
 | 
				
			||||||
#[allow(missing_docs)]
 | 
					#[allow(missing_docs)]
 | 
				
			||||||
pub enum TokenizeError {
 | 
					pub enum TokenizeError {
 | 
				
			||||||
    #[error("encountered a fatal lexical error that causes the process to stop.")]
 | 
					    #[error("encountered a fatal lexical error that causes the process to stop.")]
 | 
				
			||||||
| 
						 | 
					@ -425,95 +370,8 @@ pub enum TokenizeError {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[error("the iterator argument is at the end of the source code.")]
 | 
					    #[error("the iterator argument is at the end of the source code.")]
 | 
				
			||||||
    EndOfSourceCodeIteratorArgument,
 | 
					    EndOfSourceCodeIteratorArgument,
 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    InvalidMacroNameCharacter(#[from] InvalidMacroNameCharacter),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    UnclosedMacroUsage(#[from] UnclosedMacroUsage),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    EmptyMacroUsage(#[from] EmptyMacroUsage),
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Is an error that can occur when the macro name contains invalid characters.
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
					 | 
				
			||||||
pub struct InvalidMacroNameCharacter {
 | 
					 | 
				
			||||||
    /// The span of the invalid characters.
 | 
					 | 
				
			||||||
    pub span: Span,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for InvalidMacroNameCharacter {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "{}",
 | 
					 | 
				
			||||||
            base::log::Message::new(base::log::Severity::Error, format!("The macro name contains invalid characters: `{}`. Only alphanumeric characters and underscores are allowed.", self.span.str()))
 | 
					 | 
				
			||||||
        )?;
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "\n{}",
 | 
					 | 
				
			||||||
            SourceCodeDisplay::new(&self.span, Option::<u8>::None)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::error::Error for InvalidMacroNameCharacter {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Is an error that can occur when the macro usage is not closed.
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
					 | 
				
			||||||
pub struct UnclosedMacroUsage {
 | 
					 | 
				
			||||||
    /// The span of the unclosed macro usage.
 | 
					 | 
				
			||||||
    pub span: Span,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for UnclosedMacroUsage {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "{}",
 | 
					 | 
				
			||||||
            base::log::Message::new(
 | 
					 | 
				
			||||||
                base::log::Severity::Error,
 | 
					 | 
				
			||||||
                "A macro usage was opened with `$(` but never closed."
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )?;
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "\n{}",
 | 
					 | 
				
			||||||
            SourceCodeDisplay::new(&self.span, Option::<u8>::None)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::error::Error for UnclosedMacroUsage {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Is an error that can occur when the macro usage is not closed.
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
					 | 
				
			||||||
pub struct EmptyMacroUsage {
 | 
					 | 
				
			||||||
    /// The span of the unclosed macro usage.
 | 
					 | 
				
			||||||
    pub span: Span,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for EmptyMacroUsage {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "{}",
 | 
					 | 
				
			||||||
            base::log::Message::new(
 | 
					 | 
				
			||||||
                base::log::Severity::Error,
 | 
					 | 
				
			||||||
                "A macro usage was opened with `$(` but closed immediately with `)`."
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )?;
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "\n{}",
 | 
					 | 
				
			||||||
            SourceCodeDisplay::new(&self.span, Option::<u8>::None)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::error::Error for EmptyMacroUsage {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Token {
 | 
					impl Token {
 | 
				
			||||||
    /// Increments the iterator while the predicate returns true.
 | 
					    /// Increments the iterator while the predicate returns true.
 | 
				
			||||||
    pub fn walk_iter(iter: &mut SourceIterator, predicate: impl Fn(char) -> bool) {
 | 
					    pub fn walk_iter(iter: &mut SourceIterator, predicate: impl Fn(char) -> bool) {
 | 
				
			||||||
| 
						 | 
					@ -527,7 +385,6 @@ impl Token {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Creates a span from the given start location to the current location of the iterator.
 | 
					    /// Creates a span from the given start location to the current location of the iterator.
 | 
				
			||||||
    #[must_use]
 | 
					 | 
				
			||||||
    fn create_span(start: usize, iter: &mut SourceIterator) -> Span {
 | 
					    fn create_span(start: usize, iter: &mut SourceIterator) -> Span {
 | 
				
			||||||
        iter.peek().map_or_else(
 | 
					        iter.peek().map_or_else(
 | 
				
			||||||
            || Span::to_end(iter.source_file().clone(), start).unwrap(),
 | 
					            || Span::to_end(iter.source_file().clone(), start).unwrap(),
 | 
				
			||||||
| 
						 | 
					@ -535,26 +392,6 @@ impl Token {
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Creates a span from the given start location to the current location of the iterator with the given offset.
 | 
					 | 
				
			||||||
    #[must_use]
 | 
					 | 
				
			||||||
    fn create_span_with_end_offset(
 | 
					 | 
				
			||||||
        start: usize,
 | 
					 | 
				
			||||||
        iter: &mut SourceIterator,
 | 
					 | 
				
			||||||
        end_offset: isize,
 | 
					 | 
				
			||||||
    ) -> Span {
 | 
					 | 
				
			||||||
        iter.peek().map_or_else(
 | 
					 | 
				
			||||||
            || Span::to_end_with_offset(iter.source_file().clone(), start, end_offset).unwrap(),
 | 
					 | 
				
			||||||
            |(index, _)| {
 | 
					 | 
				
			||||||
                Span::new(
 | 
					 | 
				
			||||||
                    iter.source_file().clone(),
 | 
					 | 
				
			||||||
                    start,
 | 
					 | 
				
			||||||
                    index.saturating_add_signed(end_offset),
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .unwrap()
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Checks if the given character is a valid first character of an identifier.
 | 
					    /// Checks if the given character is a valid first character of an identifier.
 | 
				
			||||||
    fn is_first_identifier_character(character: char) -> bool {
 | 
					    fn is_first_identifier_character(character: char) -> bool {
 | 
				
			||||||
        character == '_'
 | 
					        character == '_'
 | 
				
			||||||
| 
						 | 
					@ -714,113 +551,6 @@ impl Token {
 | 
				
			||||||
        .into()
 | 
					        .into()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Handles a sequence of characters that are enclosed in backticks and contain macro usages
 | 
					 | 
				
			||||||
    fn handle_macro_string_literal(
 | 
					 | 
				
			||||||
        iter: &mut SourceIterator,
 | 
					 | 
				
			||||||
        mut start: usize,
 | 
					 | 
				
			||||||
    ) -> Result<Self, TokenizeError> {
 | 
					 | 
				
			||||||
        let mut is_escaped = false;
 | 
					 | 
				
			||||||
        let mut is_inside_macro = false;
 | 
					 | 
				
			||||||
        let mut encountered_open_parenthesis = false;
 | 
					 | 
				
			||||||
        let starting_backtick = Punctuation {
 | 
					 | 
				
			||||||
            span: Self::create_span(start, iter),
 | 
					 | 
				
			||||||
            punctuation: '`',
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        start += 1;
 | 
					 | 
				
			||||||
        let mut parts = Vec::new();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while iter.peek().is_some() {
 | 
					 | 
				
			||||||
            let (index, character) = iter.next().unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            #[expect(clippy::collapsible_else_if)]
 | 
					 | 
				
			||||||
            if is_inside_macro {
 | 
					 | 
				
			||||||
                if character == ')' {
 | 
					 | 
				
			||||||
                    // Check if the macro usage is empty
 | 
					 | 
				
			||||||
                    if start + 2 == index {
 | 
					 | 
				
			||||||
                        return Err(EmptyMacroUsage {
 | 
					 | 
				
			||||||
                            span: Span::new(iter.source_file().clone(), start, index + 1).unwrap(),
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        .into());
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    parts.push(MacroStringLiteralPart::MacroUsage {
 | 
					 | 
				
			||||||
                        dollar: Punctuation {
 | 
					 | 
				
			||||||
                            span: Span::new(iter.source_file().clone(), start, start + 1).unwrap(),
 | 
					 | 
				
			||||||
                            punctuation: '$',
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                        open_brace: Punctuation {
 | 
					 | 
				
			||||||
                            span: Span::new(iter.source_file().clone(), start + 1, start + 2)
 | 
					 | 
				
			||||||
                                .unwrap(),
 | 
					 | 
				
			||||||
                            punctuation: '(',
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                        identifier: Identifier {
 | 
					 | 
				
			||||||
                            span: Self::create_span_with_end_offset(start + 2, iter, -1),
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                        close_brace: Punctuation {
 | 
					 | 
				
			||||||
                            span: Span::new(iter.source_file().clone(), index, index + 1).unwrap(),
 | 
					 | 
				
			||||||
                            punctuation: ')',
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                    start = index + 1;
 | 
					 | 
				
			||||||
                    is_inside_macro = false;
 | 
					 | 
				
			||||||
                } else if !encountered_open_parenthesis && character == '(' {
 | 
					 | 
				
			||||||
                    encountered_open_parenthesis = true;
 | 
					 | 
				
			||||||
                } else if encountered_open_parenthesis && !Self::is_identifier_character(character)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    if character == '`' {
 | 
					 | 
				
			||||||
                        return Err(UnclosedMacroUsage {
 | 
					 | 
				
			||||||
                            span: Span::new(iter.source_file().clone(), start, start + 2).unwrap(),
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        .into());
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    Self::walk_iter(iter, |c| c != ')' && !Self::is_identifier_character(c));
 | 
					 | 
				
			||||||
                    return Err(InvalidMacroNameCharacter {
 | 
					 | 
				
			||||||
                        span: Self::create_span(index, iter),
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    .into());
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                if character == '$' && iter.peek().is_some_and(|(_, c)| c == '(') {
 | 
					 | 
				
			||||||
                    parts.push(MacroStringLiteralPart::Text(
 | 
					 | 
				
			||||||
                        Self::create_span_with_end_offset(start, iter, -1),
 | 
					 | 
				
			||||||
                    ));
 | 
					 | 
				
			||||||
                    start = index;
 | 
					 | 
				
			||||||
                    is_inside_macro = true;
 | 
					 | 
				
			||||||
                    encountered_open_parenthesis = false;
 | 
					 | 
				
			||||||
                } else if character == '\\' {
 | 
					 | 
				
			||||||
                    is_escaped = !is_escaped;
 | 
					 | 
				
			||||||
                } else if character == '`' && !is_escaped {
 | 
					 | 
				
			||||||
                    if start != index {
 | 
					 | 
				
			||||||
                        parts.push(MacroStringLiteralPart::Text(
 | 
					 | 
				
			||||||
                            Self::create_span_with_end_offset(start, iter, -1),
 | 
					 | 
				
			||||||
                        ));
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    start = index;
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    is_escaped = false;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if is_inside_macro {
 | 
					 | 
				
			||||||
            Err(UnclosedMacroUsage {
 | 
					 | 
				
			||||||
                span: Span::new(iter.source_file().clone(), start, start + 2).unwrap(),
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            .into())
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Ok(MacroStringLiteral {
 | 
					 | 
				
			||||||
                starting_backtick,
 | 
					 | 
				
			||||||
                parts,
 | 
					 | 
				
			||||||
                ending_backtick: Punctuation {
 | 
					 | 
				
			||||||
                    span: Self::create_span(start, iter),
 | 
					 | 
				
			||||||
                    punctuation: '`',
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            .into())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Handles a command that is preceeded by a slash
 | 
					    /// Handles a command that is preceeded by a slash
 | 
				
			||||||
    fn handle_command_literal(iter: &mut SourceIterator, start: usize) -> Self {
 | 
					    fn handle_command_literal(iter: &mut SourceIterator, start: usize) -> Self {
 | 
				
			||||||
        Self::walk_iter(iter, |c| !(c.is_whitespace() && c.is_ascii_control()));
 | 
					        Self::walk_iter(iter, |c| !(c.is_whitespace() && c.is_ascii_control()));
 | 
				
			||||||
| 
						 | 
					@ -862,15 +592,9 @@ impl Token {
 | 
				
			||||||
        // Found comment/single slash punctuation
 | 
					        // Found comment/single slash punctuation
 | 
				
			||||||
        else if character == '/' {
 | 
					        else if character == '/' {
 | 
				
			||||||
            Self::handle_comment(iter, start, character, prev_token, handler)
 | 
					            Self::handle_comment(iter, start, character, prev_token, handler)
 | 
				
			||||||
        }
 | 
					        } else if character == '"' {
 | 
				
			||||||
        // Found string literal
 | 
					 | 
				
			||||||
        else if character == '"' {
 | 
					 | 
				
			||||||
            Ok(Self::handle_string_literal(iter, start))
 | 
					            Ok(Self::handle_string_literal(iter, start))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // Found macro string literal
 | 
					 | 
				
			||||||
        else if character == '`' {
 | 
					 | 
				
			||||||
            Self::handle_macro_string_literal(iter, start)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // Found numeric literal
 | 
					        // Found numeric literal
 | 
				
			||||||
        else if character.is_ascii_digit() {
 | 
					        else if character.is_ascii_digit() {
 | 
				
			||||||
            Ok(Self::handle_numeric_literal(iter, start))
 | 
					            Ok(Self::handle_numeric_literal(iter, start))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,13 +5,10 @@ use std::{fmt::Debug, sync::Arc};
 | 
				
			||||||
use derive_more::{Deref, From};
 | 
					use derive_more::{Deref, From};
 | 
				
			||||||
use enum_as_inner::EnumAsInner;
 | 
					use enum_as_inner::EnumAsInner;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::base::{
 | 
				
			||||||
    base::{
 | 
					    self,
 | 
				
			||||||
        self,
 | 
					    source_file::{SourceElement, SourceFile, Span},
 | 
				
			||||||
        source_file::{SourceElement, SourceFile, Span},
 | 
					    Handler,
 | 
				
			||||||
        Handler,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    lexical::Error,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::{
 | 
					use super::{
 | 
				
			||||||
| 
						 | 
					@ -65,17 +62,6 @@ impl TokenStream {
 | 
				
			||||||
                Err(TokenizeError::FatalLexicalError) => {
 | 
					                Err(TokenizeError::FatalLexicalError) => {
 | 
				
			||||||
                    tracing::error!("Fatal lexical error encountered while tokenizing source code");
 | 
					                    tracing::error!("Fatal lexical error encountered while tokenizing source code");
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                Err(TokenizeError::InvalidMacroNameCharacter(err)) => {
 | 
					 | 
				
			||||||
                    handler.receive(Error::TokenizeError(
 | 
					 | 
				
			||||||
                        TokenizeError::InvalidMacroNameCharacter(err),
 | 
					 | 
				
			||||||
                    ));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                Err(TokenizeError::UnclosedMacroUsage(err)) => {
 | 
					 | 
				
			||||||
                    handler.receive(Error::TokenizeError(TokenizeError::UnclosedMacroUsage(err)));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                Err(TokenizeError::EmptyMacroUsage(err)) => {
 | 
					 | 
				
			||||||
                    handler.receive(Error::TokenizeError(TokenizeError::EmptyMacroUsage(err)));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -198,7 +184,7 @@ pub enum TokenTree {
 | 
				
			||||||
impl SourceElement for TokenTree {
 | 
					impl SourceElement for TokenTree {
 | 
				
			||||||
    fn span(&self) -> Span {
 | 
					    fn span(&self) -> Span {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
            Self::Token(token) => token.span(),
 | 
					            Self::Token(token) => token.span().to_owned(),
 | 
				
			||||||
            Self::Delimited(delimited) => delimited
 | 
					            Self::Delimited(delimited) => delimited
 | 
				
			||||||
                .open
 | 
					                .open
 | 
				
			||||||
                .span()
 | 
					                .span()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,10 +17,8 @@ pub use shulkerbox;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod base;
 | 
					pub mod base;
 | 
				
			||||||
pub mod lexical;
 | 
					pub mod lexical;
 | 
				
			||||||
pub mod semantic;
 | 
					 | 
				
			||||||
pub mod syntax;
 | 
					pub mod syntax;
 | 
				
			||||||
pub mod transpile;
 | 
					pub mod transpile;
 | 
				
			||||||
pub mod util;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(feature = "serde")]
 | 
					#[cfg(feature = "serde")]
 | 
				
			||||||
pub(crate) mod serde;
 | 
					pub(crate) mod serde;
 | 
				
			||||||
| 
						 | 
					@ -108,8 +106,6 @@ pub fn parse(
 | 
				
			||||||
        ));
 | 
					        ));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    program.analyze_semantics(handler)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(program)
 | 
					    Ok(program)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,286 +0,0 @@
 | 
				
			||||||
//! Error types for the semantic analysis phase of the compiler.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#![allow(missing_docs)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use std::{collections::HashSet, fmt::Display};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use getset::Getters;
 | 
					 | 
				
			||||||
use itertools::Itertools as _;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::{
 | 
					 | 
				
			||||||
    base::{
 | 
					 | 
				
			||||||
        log::{Message, Severity, SourceCodeDisplay},
 | 
					 | 
				
			||||||
        source_file::{SourceElement as _, Span},
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    lexical::token::StringLiteral,
 | 
					 | 
				
			||||||
    syntax::syntax_tree::expression::Expression,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
 | 
					 | 
				
			||||||
#[allow(missing_docs)]
 | 
					 | 
				
			||||||
pub enum Error {
 | 
					 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    MissingFunctionDeclaration(#[from] MissingFunctionDeclaration),
 | 
					 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    UnexpectedExpression(#[from] UnexpectedExpression),
 | 
					 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    ConflictingFunctionNames(#[from] ConflictingFunctionNames),
 | 
					 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    InvalidNamespaceName(#[from] InvalidNamespaceName),
 | 
					 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    UnresolvedMacroUsage(#[from] UnresolvedMacroUsage),
 | 
					 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    IncompatibleFunctionAnnotation(#[from] IncompatibleFunctionAnnotation),
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// An error that occurs when a function declaration is missing.
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)]
 | 
					 | 
				
			||||||
pub struct MissingFunctionDeclaration {
 | 
					 | 
				
			||||||
    #[get = "pub"]
 | 
					 | 
				
			||||||
    span: Span,
 | 
					 | 
				
			||||||
    #[get = "pub"]
 | 
					 | 
				
			||||||
    alternatives: Vec<String>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl MissingFunctionDeclaration {
 | 
					 | 
				
			||||||
    pub(super) fn from_context(identifier_span: Span, functions: &HashSet<String>) -> Self {
 | 
					 | 
				
			||||||
        let own_name = identifier_span.str();
 | 
					 | 
				
			||||||
        let alternatives = functions
 | 
					 | 
				
			||||||
            .iter()
 | 
					 | 
				
			||||||
            .filter_map(|function_name| {
 | 
					 | 
				
			||||||
                let normalized_distance =
 | 
					 | 
				
			||||||
                    strsim::normalized_damerau_levenshtein(own_name, function_name);
 | 
					 | 
				
			||||||
                (normalized_distance > 0.8
 | 
					 | 
				
			||||||
                    || strsim::damerau_levenshtein(own_name, function_name) < 3)
 | 
					 | 
				
			||||||
                    .then_some((normalized_distance, function_name))
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .sorted_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal))
 | 
					 | 
				
			||||||
            .map(|(_, data)| data)
 | 
					 | 
				
			||||||
            .take(8)
 | 
					 | 
				
			||||||
            .cloned()
 | 
					 | 
				
			||||||
            .collect::<Vec<_>>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            alternatives,
 | 
					 | 
				
			||||||
            span: identifier_span,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for MissingFunctionDeclaration {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        use std::fmt::Write;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let message = format!(
 | 
					 | 
				
			||||||
            "no matching function declaration found for invocation of function `{}`",
 | 
					 | 
				
			||||||
            self.span.str()
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        write!(f, "{}", Message::new(Severity::Error, message))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let help_message = if self.alternatives.is_empty() {
 | 
					 | 
				
			||||||
            None
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            let mut message = String::from("did you mean ");
 | 
					 | 
				
			||||||
            for (i, alternative) in self.alternatives.iter().enumerate() {
 | 
					 | 
				
			||||||
                if i > 0 {
 | 
					 | 
				
			||||||
                    message.push_str(", ");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                write!(message, "`{alternative}`")?;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Some(message + "?")
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "\n{}",
 | 
					 | 
				
			||||||
            SourceCodeDisplay::new(&self.span, help_message.as_ref())
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::error::Error for MissingFunctionDeclaration {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// An error that occurs when a function declaration is missing.
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 | 
					 | 
				
			||||||
pub struct UnexpectedExpression(pub Expression);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for UnexpectedExpression {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "{}",
 | 
					 | 
				
			||||||
            Message::new(Severity::Error, "encountered unexpected expression")
 | 
					 | 
				
			||||||
        )?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "\n{}",
 | 
					 | 
				
			||||||
            SourceCodeDisplay::new(&self.0.span(), Option::<u8>::None)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::error::Error for UnexpectedExpression {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 | 
					 | 
				
			||||||
pub struct ConflictingFunctionNames {
 | 
					 | 
				
			||||||
    pub definition: Span,
 | 
					 | 
				
			||||||
    pub name: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for ConflictingFunctionNames {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "{}",
 | 
					 | 
				
			||||||
            Message::new(
 | 
					 | 
				
			||||||
                Severity::Error,
 | 
					 | 
				
			||||||
                format!("the following function declaration conflicts with an existing function with name `{}`", self.name)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "\n{}",
 | 
					 | 
				
			||||||
            SourceCodeDisplay::new(&self.definition, Option::<u8>::None)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::error::Error for ConflictingFunctionNames {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 | 
					 | 
				
			||||||
pub struct InvalidNamespaceName {
 | 
					 | 
				
			||||||
    pub name: StringLiteral,
 | 
					 | 
				
			||||||
    pub invalid_chars: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for InvalidNamespaceName {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "{}",
 | 
					 | 
				
			||||||
            Message::new(
 | 
					 | 
				
			||||||
                Severity::Error,
 | 
					 | 
				
			||||||
                format!(
 | 
					 | 
				
			||||||
                    "Invalid characters in namespace `{}`. The following characters are not allowed in namespace definitions: `{}`",
 | 
					 | 
				
			||||||
                    self.name.str_content(),
 | 
					 | 
				
			||||||
                    self.invalid_chars
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "\n{}",
 | 
					 | 
				
			||||||
            SourceCodeDisplay::new(&self.name.span, Option::<u8>::None)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::error::Error for InvalidNamespaceName {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 | 
					 | 
				
			||||||
pub struct UnresolvedMacroUsage {
 | 
					 | 
				
			||||||
    pub span: Span,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for UnresolvedMacroUsage {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "{}",
 | 
					 | 
				
			||||||
            Message::new(
 | 
					 | 
				
			||||||
                Severity::Error,
 | 
					 | 
				
			||||||
                format!(
 | 
					 | 
				
			||||||
                    "Macro `{}` was used, but could not be resolved.",
 | 
					 | 
				
			||||||
                    self.span.str(),
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "\n{}",
 | 
					 | 
				
			||||||
            SourceCodeDisplay::new(
 | 
					 | 
				
			||||||
                &self.span,
 | 
					 | 
				
			||||||
                Some(format!(
 | 
					 | 
				
			||||||
                    "You might want to add `{}` to the function parameters.",
 | 
					 | 
				
			||||||
                    self.span.str()
 | 
					 | 
				
			||||||
                ))
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::error::Error for UnresolvedMacroUsage {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 | 
					 | 
				
			||||||
pub struct IncompatibleFunctionAnnotation {
 | 
					 | 
				
			||||||
    pub span: Span,
 | 
					 | 
				
			||||||
    pub reason: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for IncompatibleFunctionAnnotation {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "{}",
 | 
					 | 
				
			||||||
            Message::new(
 | 
					 | 
				
			||||||
                Severity::Error,
 | 
					 | 
				
			||||||
                format!(
 | 
					 | 
				
			||||||
                    "Annotation `{}` cannot be used here, because {}.",
 | 
					 | 
				
			||||||
                    self.span.str(),
 | 
					 | 
				
			||||||
                    self.reason
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        write!(f, "\n{}", SourceCodeDisplay::new(&self.span, None::<u8>))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::error::Error for IncompatibleFunctionAnnotation {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 | 
					 | 
				
			||||||
pub struct InvalidFunctionArguments {
 | 
					 | 
				
			||||||
    pub span: Span,
 | 
					 | 
				
			||||||
    pub expected: usize,
 | 
					 | 
				
			||||||
    pub actual: usize,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for InvalidFunctionArguments {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "{}",
 | 
					 | 
				
			||||||
            Message::new(
 | 
					 | 
				
			||||||
                Severity::Error,
 | 
					 | 
				
			||||||
                format!(
 | 
					 | 
				
			||||||
                    "Expected {} arguments, but got {}.",
 | 
					 | 
				
			||||||
                    self.expected, self.actual
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let help_message = if self.expected > self.actual {
 | 
					 | 
				
			||||||
            format!(
 | 
					 | 
				
			||||||
                "You might want to add {} more arguments.",
 | 
					 | 
				
			||||||
                self.expected - self.actual
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            format!(
 | 
					 | 
				
			||||||
                "You might want to remove {} arguments.",
 | 
					 | 
				
			||||||
                self.actual - self.expected
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        write!(
 | 
					 | 
				
			||||||
            f,
 | 
					 | 
				
			||||||
            "\n{}",
 | 
					 | 
				
			||||||
            SourceCodeDisplay::new(&self.span, Some(help_message))
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl std::error::Error for InvalidFunctionArguments {}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,574 +0,0 @@
 | 
				
			||||||
//! This module contains the semantic analysis of the AST.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#![allow(clippy::missing_errors_doc)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use std::collections::HashSet;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use error::{
 | 
					 | 
				
			||||||
    IncompatibleFunctionAnnotation, InvalidNamespaceName, MissingFunctionDeclaration,
 | 
					 | 
				
			||||||
    UnexpectedExpression, UnresolvedMacroUsage,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::{
 | 
					 | 
				
			||||||
    base::{self, source_file::SourceElement as _, Handler},
 | 
					 | 
				
			||||||
    lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
 | 
					 | 
				
			||||||
    syntax::syntax_tree::{
 | 
					 | 
				
			||||||
        condition::{
 | 
					 | 
				
			||||||
            BinaryCondition, Condition, ParenthesizedCondition, PrimaryCondition, UnaryCondition,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        declaration::{Declaration, Function, ImportItems},
 | 
					 | 
				
			||||||
        expression::{Expression, FunctionCall, Primary},
 | 
					 | 
				
			||||||
        program::{Namespace, ProgramFile},
 | 
					 | 
				
			||||||
        statement::{
 | 
					 | 
				
			||||||
            execute_block::{
 | 
					 | 
				
			||||||
                Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockHeadItem as _,
 | 
					 | 
				
			||||||
                ExecuteBlockTail,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            Block, Grouping, Run, Semicolon, Statement,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        AnyStringLiteral,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub mod error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ProgramFile {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the program.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        self.namespace().analyze_semantics(handler)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut errs = Vec::new();
 | 
					 | 
				
			||||||
        let function_names = extract_all_function_names(self.declarations(), handler)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for declaration in self.declarations() {
 | 
					 | 
				
			||||||
            if let Err(err) = declaration.analyze_semantics(&function_names, handler) {
 | 
					 | 
				
			||||||
                errs.push(err);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        #[expect(clippy::option_if_let_else)]
 | 
					 | 
				
			||||||
        if let Some(err) = errs.first() {
 | 
					 | 
				
			||||||
            Err(err.clone())
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Ok(())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn extract_all_function_names(
 | 
					 | 
				
			||||||
    declarations: &[Declaration],
 | 
					 | 
				
			||||||
    handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
) -> Result<HashSet<String>, error::Error> {
 | 
					 | 
				
			||||||
    let mut function_names = HashSet::new();
 | 
					 | 
				
			||||||
    let mut errs = Vec::new();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for declaration in declarations {
 | 
					 | 
				
			||||||
        match declaration {
 | 
					 | 
				
			||||||
            Declaration::Function(func) => {
 | 
					 | 
				
			||||||
                let name = func.identifier();
 | 
					 | 
				
			||||||
                if function_names.contains(name.span.str()) {
 | 
					 | 
				
			||||||
                    let err = error::Error::from(error::ConflictingFunctionNames {
 | 
					 | 
				
			||||||
                        name: name.span.str().to_string(),
 | 
					 | 
				
			||||||
                        definition: name.span(),
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                    handler.receive(err.clone());
 | 
					 | 
				
			||||||
                    errs.push(err);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                function_names.insert(name.span.str().to_string());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Declaration::Import(imp) => match imp.items() {
 | 
					 | 
				
			||||||
                ImportItems::All(_) => {
 | 
					 | 
				
			||||||
                    handler.receive(base::Error::Other(
 | 
					 | 
				
			||||||
                        "Importing all items is not yet supported.".to_string(),
 | 
					 | 
				
			||||||
                    ));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                ImportItems::Named(items) => {
 | 
					 | 
				
			||||||
                    for item in items.elements() {
 | 
					 | 
				
			||||||
                        if function_names.contains(item.span.str()) {
 | 
					 | 
				
			||||||
                            let err = error::Error::from(error::ConflictingFunctionNames {
 | 
					 | 
				
			||||||
                                name: item.span.str().to_string(),
 | 
					 | 
				
			||||||
                                definition: item.span(),
 | 
					 | 
				
			||||||
                            });
 | 
					 | 
				
			||||||
                            handler.receive(err.clone());
 | 
					 | 
				
			||||||
                            errs.push(err);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        function_names.insert(item.span.str().to_string());
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Declaration::Tag(_) => {}
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[expect(clippy::option_if_let_else)]
 | 
					 | 
				
			||||||
    if let Some(err) = errs.first() {
 | 
					 | 
				
			||||||
        Err(err.clone())
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        Ok(function_names)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Namespace {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the namespace.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        let name = self.namespace_name();
 | 
					 | 
				
			||||||
        Self::validate_str(name.str_content().as_ref()).map_err(|invalid_chars| {
 | 
					 | 
				
			||||||
            let err = error::Error::from(InvalidNamespaceName {
 | 
					 | 
				
			||||||
                name: name.clone(),
 | 
					 | 
				
			||||||
                invalid_chars,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            handler.receive(err.clone());
 | 
					 | 
				
			||||||
            err
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Declaration {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the declaration.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            Self::Function(func) => func.analyze_semantics(function_names, handler),
 | 
					 | 
				
			||||||
            Self::Import(_) | Self::Tag(_) => Ok(()),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Function {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the function.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        let macro_names = if let Some(parameters) = self.parameters() {
 | 
					 | 
				
			||||||
            if let Some(incompatible) = self
 | 
					 | 
				
			||||||
                .annotations()
 | 
					 | 
				
			||||||
                .iter()
 | 
					 | 
				
			||||||
                .find(|a| ["tick", "load"].contains(&a.identifier().span.str()))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                let err =
 | 
					 | 
				
			||||||
                    error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
 | 
					 | 
				
			||||||
                        span: incompatible.identifier().span(),
 | 
					 | 
				
			||||||
                        reason:
 | 
					 | 
				
			||||||
                            "functions with the `tick` or `load` annotation cannot have parameters"
 | 
					 | 
				
			||||||
                                .to_string(),
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                handler.receive(err.clone());
 | 
					 | 
				
			||||||
                return Err(err);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            parameters
 | 
					 | 
				
			||||||
                .elements()
 | 
					 | 
				
			||||||
                .map(|el| el.span.str().to_string())
 | 
					 | 
				
			||||||
                .collect()
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            HashSet::new()
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.block()
 | 
					 | 
				
			||||||
            .analyze_semantics(function_names, ¯o_names, handler)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Block {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of a block.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        let mut errs = Vec::new();
 | 
					 | 
				
			||||||
        for statement in &self.statements {
 | 
					 | 
				
			||||||
            if let Err(err) = match statement {
 | 
					 | 
				
			||||||
                Statement::Block(block) => {
 | 
					 | 
				
			||||||
                    block.analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                Statement::DocComment(_) | Statement::LiteralCommand(_) => Ok(()),
 | 
					 | 
				
			||||||
                Statement::ExecuteBlock(ex) => {
 | 
					 | 
				
			||||||
                    ex.analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                Statement::Grouping(group) => {
 | 
					 | 
				
			||||||
                    group.analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                Statement::Run(run) => run.analyze_semantics(function_names, macro_names, handler),
 | 
					 | 
				
			||||||
                Statement::Semicolon(sem) => {
 | 
					 | 
				
			||||||
                    sem.analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } {
 | 
					 | 
				
			||||||
                errs.push(err);
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        #[expect(clippy::option_if_let_else)]
 | 
					 | 
				
			||||||
        if let Some(err) = errs.first() {
 | 
					 | 
				
			||||||
            Err(err.clone())
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Ok(())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlock {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the execute block.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            Self::HeadTail(head, tail) => {
 | 
					 | 
				
			||||||
                let head_res = head.analyze_semantics(function_names, macro_names, handler);
 | 
					 | 
				
			||||||
                let tail_res = tail.analyze_semantics(function_names, macro_names, handler);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if head_res.is_err() {
 | 
					 | 
				
			||||||
                    head_res
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    tail_res
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Self::IfElse(cond, then, el) => {
 | 
					 | 
				
			||||||
                let cond_res = cond.analyze_semantics(function_names, macro_names, handler);
 | 
					 | 
				
			||||||
                let then_res = then.analyze_semantics(function_names, macro_names, handler);
 | 
					 | 
				
			||||||
                let else_res = el.analyze_semantics(function_names, macro_names, handler);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if cond_res.is_err() {
 | 
					 | 
				
			||||||
                    cond_res
 | 
					 | 
				
			||||||
                } else if then_res.is_err() {
 | 
					 | 
				
			||||||
                    then_res
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    else_res
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Grouping {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the grouping.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        self.block()
 | 
					 | 
				
			||||||
            .analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Run {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the run statement.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        self.expression()
 | 
					 | 
				
			||||||
            .analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Semicolon {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the semicolon statement.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        match self.expression() {
 | 
					 | 
				
			||||||
            Expression::Primary(Primary::FunctionCall(func)) => {
 | 
					 | 
				
			||||||
                func.analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Expression::Primary(unexpected) => {
 | 
					 | 
				
			||||||
                let error = error::Error::UnexpectedExpression(UnexpectedExpression(
 | 
					 | 
				
			||||||
                    Expression::Primary(unexpected.clone()),
 | 
					 | 
				
			||||||
                ));
 | 
					 | 
				
			||||||
                handler.receive(error.clone());
 | 
					 | 
				
			||||||
                Err(error)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHead {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the execute block head.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            Self::Align(align) => align.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::Anchored(anchored) => anchored.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::As(r#as) => r#as.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::At(at) => at.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::AsAt(asat) => asat.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::Conditional(cond) => cond.analyze_semantics(function_names, macro_names, handler),
 | 
					 | 
				
			||||||
            Self::Facing(facing) => facing.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::In(r#in) => r#in.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::On(on) => on.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::Positioned(pos) => pos.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::Rotated(rot) => rot.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::Store(store) => store.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
            Self::Summon(summon) => summon.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockTail {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the execute block tail.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            Self::Block(block) => block.analyze_semantics(function_names, macro_names, handler),
 | 
					 | 
				
			||||||
            Self::ExecuteBlock(_, ex) => ex.analyze_semantics(function_names, macro_names, handler),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Conditional {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the conditional.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        self.condition()
 | 
					 | 
				
			||||||
            .analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ParenthesizedCondition {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the parenthesized condition.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        self.condition
 | 
					 | 
				
			||||||
            .analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Condition {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the condition.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            Self::Primary(prim) => prim.analyze_semantics(function_names, macro_names, handler),
 | 
					 | 
				
			||||||
            Self::Binary(bin) => bin.analyze_semantics(function_names, macro_names, handler),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Else {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the else block.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        self.block()
 | 
					 | 
				
			||||||
            .analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl MacroStringLiteral {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the macro string literal.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        let mut errors = Vec::new();
 | 
					 | 
				
			||||||
        for part in self.parts() {
 | 
					 | 
				
			||||||
            if let MacroStringLiteralPart::MacroUsage { identifier, .. } = part {
 | 
					 | 
				
			||||||
                if !macro_names.contains(identifier.span.str()) {
 | 
					 | 
				
			||||||
                    let err = error::Error::UnresolvedMacroUsage(UnresolvedMacroUsage {
 | 
					 | 
				
			||||||
                        span: identifier.span(),
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                    handler.receive(err.clone());
 | 
					 | 
				
			||||||
                    errors.push(err);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        #[expect(clippy::option_if_let_else)]
 | 
					 | 
				
			||||||
        if let Some(err) = errors.first() {
 | 
					 | 
				
			||||||
            Err(err.clone())
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Ok(())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Expression {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of an expression.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            Self::Primary(prim) => prim.analyze_semantics(function_names, macro_names, handler),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Primary {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of a primary expression.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            Self::FunctionCall(func) => {
 | 
					 | 
				
			||||||
                func.analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Self::Lua(_) | Self::StringLiteral(_) => Ok(()),
 | 
					 | 
				
			||||||
            Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl FunctionCall {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of a function call.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        let mut errors = Vec::new();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if !function_names.contains(self.identifier().span.str()) {
 | 
					 | 
				
			||||||
            let err = error::Error::MissingFunctionDeclaration(
 | 
					 | 
				
			||||||
                MissingFunctionDeclaration::from_context(self.identifier().span(), function_names),
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            handler.receive(err.clone());
 | 
					 | 
				
			||||||
            errors.push(err);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for expression in self
 | 
					 | 
				
			||||||
            .arguments()
 | 
					 | 
				
			||||||
            .iter()
 | 
					 | 
				
			||||||
            .flat_map(super::syntax::syntax_tree::ConnectedList::elements)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if let Err(err) = expression.analyze_semantics(function_names, macro_names, handler) {
 | 
					 | 
				
			||||||
                handler.receive(err.clone());
 | 
					 | 
				
			||||||
                errors.push(err);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        #[expect(clippy::option_if_let_else)]
 | 
					 | 
				
			||||||
        if let Some(err) = errors.first() {
 | 
					 | 
				
			||||||
            Err(err.clone())
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Ok(())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl AnyStringLiteral {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of any string literal.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            Self::StringLiteral(_) => Ok(()),
 | 
					 | 
				
			||||||
            Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl PrimaryCondition {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of a primary condition.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            Self::Parenthesized(paren) => {
 | 
					 | 
				
			||||||
                paren.analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Self::StringLiteral(_) => Ok(()),
 | 
					 | 
				
			||||||
            Self::Unary(unary) => unary.analyze_semantics(function_names, macro_names, handler),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl UnaryCondition {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of an unary condition.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        self.operand()
 | 
					 | 
				
			||||||
            .analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl BinaryCondition {
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of a binary condition.
 | 
					 | 
				
			||||||
    pub fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        function_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					 | 
				
			||||||
        let a = self
 | 
					 | 
				
			||||||
            .left_operand()
 | 
					 | 
				
			||||||
            .analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
            .inspect_err(|err| {
 | 
					 | 
				
			||||||
                handler.receive(err.clone());
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        let b = self
 | 
					 | 
				
			||||||
            .right_operand()
 | 
					 | 
				
			||||||
            .analyze_semantics(function_names, macro_names, handler)
 | 
					 | 
				
			||||||
            .inspect_err(|err| {
 | 
					 | 
				
			||||||
                handler.receive(err.clone());
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        if a.is_err() {
 | 
					 | 
				
			||||||
            a
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            b
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ use std::fmt::Display;
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    base::{
 | 
					    base::{
 | 
				
			||||||
        log::{Message, Severity, SourceCodeDisplay},
 | 
					        log::{Message, Severity, SourceCodeDisplay},
 | 
				
			||||||
        source_file::{SourceElement as _, Span},
 | 
					        source_file::Span,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    lexical::token::{KeywordKind, Token},
 | 
					    lexical::token::{KeywordKind, Token},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -34,8 +34,6 @@ pub enum SyntaxKind {
 | 
				
			||||||
    Declaration,
 | 
					    Declaration,
 | 
				
			||||||
    Numeric,
 | 
					    Numeric,
 | 
				
			||||||
    StringLiteral,
 | 
					    StringLiteral,
 | 
				
			||||||
    MacroStringLiteral,
 | 
					 | 
				
			||||||
    AnyStringLiteral,
 | 
					 | 
				
			||||||
    Statement,
 | 
					    Statement,
 | 
				
			||||||
    Expression,
 | 
					    Expression,
 | 
				
			||||||
    Type,
 | 
					    Type,
 | 
				
			||||||
| 
						 | 
					@ -71,8 +69,6 @@ impl SyntaxKind {
 | 
				
			||||||
            Self::Declaration => "a declaration token".to_string(),
 | 
					            Self::Declaration => "a declaration token".to_string(),
 | 
				
			||||||
            Self::Numeric => "a numeric token".to_string(),
 | 
					            Self::Numeric => "a numeric token".to_string(),
 | 
				
			||||||
            Self::StringLiteral => "a string literal".to_string(),
 | 
					            Self::StringLiteral => "a string literal".to_string(),
 | 
				
			||||||
            Self::MacroStringLiteral => "a macro string literal".to_string(),
 | 
					 | 
				
			||||||
            Self::AnyStringLiteral => "a (macro) string literal".to_string(),
 | 
					 | 
				
			||||||
            Self::Statement => "a statement syntax".to_string(),
 | 
					            Self::Statement => "a statement syntax".to_string(),
 | 
				
			||||||
            Self::Expression => "an expression syntax".to_string(),
 | 
					            Self::Expression => "an expression syntax".to_string(),
 | 
				
			||||||
            Self::Type => "a type syntax".to_string(),
 | 
					            Self::Type => "a type syntax".to_string(),
 | 
				
			||||||
| 
						 | 
					@ -109,7 +105,6 @@ impl Display for UnexpectedSyntax {
 | 
				
			||||||
            Some(Token::Numeric(..)) => "a numeric token".to_string(),
 | 
					            Some(Token::Numeric(..)) => "a numeric token".to_string(),
 | 
				
			||||||
            Some(Token::CommandLiteral(..)) => "a literal command token".to_string(),
 | 
					            Some(Token::CommandLiteral(..)) => "a literal command token".to_string(),
 | 
				
			||||||
            Some(Token::StringLiteral(..)) => "a string literal token".to_string(),
 | 
					            Some(Token::StringLiteral(..)) => "a string literal token".to_string(),
 | 
				
			||||||
            Some(Token::MacroStringLiteral(..)) => "a macro string literal token".to_string(),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            None => "EOF".to_string(),
 | 
					            None => "EOF".to_string(),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
| 
						 | 
					@ -122,7 +117,7 @@ impl Display for UnexpectedSyntax {
 | 
				
			||||||
            write!(
 | 
					            write!(
 | 
				
			||||||
                f,
 | 
					                f,
 | 
				
			||||||
                "\n{}",
 | 
					                "\n{}",
 | 
				
			||||||
                SourceCodeDisplay::new(&span.span(), Option::<u8>::None)
 | 
					                SourceCodeDisplay::new(span.span(), Option::<u8>::None)
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,18 +6,12 @@ use enum_as_inner::EnumAsInner;
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    base::{self, Handler},
 | 
					    base::{self, Handler},
 | 
				
			||||||
    lexical::{
 | 
					    lexical::{
 | 
				
			||||||
        token::{
 | 
					        token::{Identifier, Keyword, KeywordKind, Numeric, Punctuation, StringLiteral, Token},
 | 
				
			||||||
            Identifier, Keyword, KeywordKind, MacroStringLiteral, Numeric, Punctuation,
 | 
					 | 
				
			||||||
            StringLiteral, Token,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        token_stream::{Delimited, Delimiter, TokenStream, TokenTree},
 | 
					        token_stream::{Delimited, Delimiter, TokenStream, TokenTree},
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::{
 | 
					use super::error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax};
 | 
				
			||||||
    error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
 | 
					 | 
				
			||||||
    syntax_tree::AnyStringLiteral,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Represents a parser that reads a token stream and constructs an abstract syntax tree.
 | 
					/// Represents a parser that reads a token stream and constructs an abstract syntax tree.
 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut)]
 | 
					#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut)]
 | 
				
			||||||
| 
						 | 
					@ -438,49 +432,6 @@ impl<'a> Frame<'a> {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Expects the next [`Token`] to be an [`MacroStringLiteral`], and returns it.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// # Errors
 | 
					 | 
				
			||||||
    /// If the next [`Token`] is not an [`MacroStringLiteral`].
 | 
					 | 
				
			||||||
    pub fn parse_macro_string_literal(
 | 
					 | 
				
			||||||
        &mut self,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> ParseResult<MacroStringLiteral> {
 | 
					 | 
				
			||||||
        match self.next_significant_token() {
 | 
					 | 
				
			||||||
            Reading::Atomic(Token::MacroStringLiteral(literal)) => Ok(literal),
 | 
					 | 
				
			||||||
            found => {
 | 
					 | 
				
			||||||
                let err = Error::UnexpectedSyntax(UnexpectedSyntax {
 | 
					 | 
				
			||||||
                    expected: SyntaxKind::MacroStringLiteral,
 | 
					 | 
				
			||||||
                    found: found.into_token(),
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                handler.receive(err.clone());
 | 
					 | 
				
			||||||
                Err(err)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Expects the next [`Token`] to be an [`AnyStringLiteral`], and returns it.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// # Errors
 | 
					 | 
				
			||||||
    /// If the next [`Token`] is not an [`AnyStringLiteral`].
 | 
					 | 
				
			||||||
    pub fn parse_any_string_literal(
 | 
					 | 
				
			||||||
        &mut self,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> ParseResult<AnyStringLiteral> {
 | 
					 | 
				
			||||||
        match self.next_significant_token() {
 | 
					 | 
				
			||||||
            Reading::Atomic(Token::StringLiteral(literal)) => Ok(literal.into()),
 | 
					 | 
				
			||||||
            Reading::Atomic(Token::MacroStringLiteral(literal)) => Ok(literal.into()),
 | 
					 | 
				
			||||||
            found => {
 | 
					 | 
				
			||||||
                let err = Error::UnexpectedSyntax(UnexpectedSyntax {
 | 
					 | 
				
			||||||
                    expected: SyntaxKind::AnyStringLiteral,
 | 
					 | 
				
			||||||
                    found: found.into_token(),
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                handler.receive(err.clone());
 | 
					 | 
				
			||||||
                Err(err)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Expects the next [`Token`] to be a [`Keyword`] of specific kind, and returns it.
 | 
					    /// Expects the next [`Token`] to be a [`Keyword`] of specific kind, and returns it.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Errors
 | 
					    /// # Errors
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ use crate::{
 | 
				
			||||||
        Handler, VoidHandler,
 | 
					        Handler, VoidHandler,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    lexical::{
 | 
					    lexical::{
 | 
				
			||||||
        token::{Punctuation, Token},
 | 
					        token::{Punctuation, StringLiteral, Token},
 | 
				
			||||||
        token_stream::Delimiter,
 | 
					        token_stream::Delimiter,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    syntax::{
 | 
					    syntax::{
 | 
				
			||||||
| 
						 | 
					@ -23,8 +23,6 @@ use crate::{
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::AnyStringLiteral;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Condition that is viewed as a single entity during precedence parsing.
 | 
					/// Condition that is viewed as a single entity during precedence parsing.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
| 
						 | 
					@ -33,7 +31,7 @@ use super::AnyStringLiteral;
 | 
				
			||||||
/// PrimaryCondition:
 | 
					/// PrimaryCondition:
 | 
				
			||||||
///     UnaryCondition
 | 
					///     UnaryCondition
 | 
				
			||||||
///     | ParenthesizedCondition
 | 
					///     | ParenthesizedCondition
 | 
				
			||||||
///     | AnyStringLiteral
 | 
					///     | StringLiteral
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[allow(missing_docs)]
 | 
					#[allow(missing_docs)]
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -41,7 +39,7 @@ use super::AnyStringLiteral;
 | 
				
			||||||
pub enum PrimaryCondition {
 | 
					pub enum PrimaryCondition {
 | 
				
			||||||
    Unary(UnaryCondition),
 | 
					    Unary(UnaryCondition),
 | 
				
			||||||
    Parenthesized(ParenthesizedCondition),
 | 
					    Parenthesized(ParenthesizedCondition),
 | 
				
			||||||
    StringLiteral(AnyStringLiteral),
 | 
					    StringLiteral(StringLiteral),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SourceElement for PrimaryCondition {
 | 
					impl SourceElement for PrimaryCondition {
 | 
				
			||||||
| 
						 | 
					@ -356,13 +354,7 @@ impl<'a> Parser<'a> {
 | 
				
			||||||
            // string literal
 | 
					            // string literal
 | 
				
			||||||
            Reading::Atomic(Token::StringLiteral(literal)) => {
 | 
					            Reading::Atomic(Token::StringLiteral(literal)) => {
 | 
				
			||||||
                self.forward();
 | 
					                self.forward();
 | 
				
			||||||
                Ok(PrimaryCondition::StringLiteral(literal.into()))
 | 
					                Ok(PrimaryCondition::StringLiteral(literal))
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // macro string literal
 | 
					 | 
				
			||||||
            Reading::Atomic(Token::MacroStringLiteral(literal)) => {
 | 
					 | 
				
			||||||
                self.forward();
 | 
					 | 
				
			||||||
                Ok(PrimaryCondition::StringLiteral(literal.into()))
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // parenthesized condition
 | 
					            // parenthesized condition
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,9 +10,7 @@ use crate::{
 | 
				
			||||||
        Handler,
 | 
					        Handler,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    lexical::{
 | 
					    lexical::{
 | 
				
			||||||
        token::{
 | 
					        token::{Identifier, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
 | 
				
			||||||
            Identifier, Keyword, KeywordKind, MacroStringLiteral, Punctuation, StringLiteral, Token,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        token_stream::Delimiter,
 | 
					        token_stream::Delimiter,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    syntax::{
 | 
					    syntax::{
 | 
				
			||||||
| 
						 | 
					@ -54,9 +52,6 @@ impl SourceElement for Expression {
 | 
				
			||||||
/// ``` ebnf
 | 
					/// ``` ebnf
 | 
				
			||||||
/// Primary:
 | 
					/// Primary:
 | 
				
			||||||
///     FunctionCall
 | 
					///     FunctionCall
 | 
				
			||||||
///     | StringLiteral
 | 
					 | 
				
			||||||
///     | MacroStringLiteral
 | 
					 | 
				
			||||||
///     | LuaCode
 | 
					 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[allow(missing_docs)]
 | 
					#[allow(missing_docs)]
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -64,7 +59,6 @@ impl SourceElement for Expression {
 | 
				
			||||||
pub enum Primary {
 | 
					pub enum Primary {
 | 
				
			||||||
    FunctionCall(FunctionCall),
 | 
					    FunctionCall(FunctionCall),
 | 
				
			||||||
    StringLiteral(StringLiteral),
 | 
					    StringLiteral(StringLiteral),
 | 
				
			||||||
    MacroStringLiteral(MacroStringLiteral),
 | 
					 | 
				
			||||||
    Lua(Box<LuaCode>),
 | 
					    Lua(Box<LuaCode>),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,7 +67,6 @@ impl SourceElement for Primary {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
            Self::FunctionCall(function_call) => function_call.span(),
 | 
					            Self::FunctionCall(function_call) => function_call.span(),
 | 
				
			||||||
            Self::StringLiteral(string_literal) => string_literal.span(),
 | 
					            Self::StringLiteral(string_literal) => string_literal.span(),
 | 
				
			||||||
            Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
 | 
					 | 
				
			||||||
            Self::Lua(lua_code) => lua_code.span(),
 | 
					            Self::Lua(lua_code) => lua_code.span(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -187,7 +180,6 @@ impl<'a> Parser<'a> {
 | 
				
			||||||
    /// # Errors
 | 
					    /// # Errors
 | 
				
			||||||
    /// - If the parser is not at a primary expression.
 | 
					    /// - If the parser is not at a primary expression.
 | 
				
			||||||
    /// - If the parser is not at a valid primary expression.
 | 
					    /// - If the parser is not at a valid primary expression.
 | 
				
			||||||
    #[expect(clippy::too_many_lines)]
 | 
					 | 
				
			||||||
    pub fn parse_primary(&mut self, handler: &impl Handler<base::Error>) -> ParseResult<Primary> {
 | 
					    pub fn parse_primary(&mut self, handler: &impl Handler<base::Error>) -> ParseResult<Primary> {
 | 
				
			||||||
        match self.stop_at_significant() {
 | 
					        match self.stop_at_significant() {
 | 
				
			||||||
            // identifier expression
 | 
					            // identifier expression
 | 
				
			||||||
| 
						 | 
					@ -232,14 +224,6 @@ impl<'a> Parser<'a> {
 | 
				
			||||||
                Ok(Primary::StringLiteral(literal))
 | 
					                Ok(Primary::StringLiteral(literal))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // macro string literal expression
 | 
					 | 
				
			||||||
            Reading::Atomic(Token::MacroStringLiteral(macro_string_literal)) => {
 | 
					 | 
				
			||||||
                // eat the macro string literal
 | 
					 | 
				
			||||||
                self.forward();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Ok(Primary::MacroStringLiteral(macro_string_literal))
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // lua code expression
 | 
					            // lua code expression
 | 
				
			||||||
            Reading::Atomic(Token::Keyword(lua_keyword))
 | 
					            Reading::Atomic(Token::Keyword(lua_keyword))
 | 
				
			||||||
                if lua_keyword.keyword == KeywordKind::Lua =>
 | 
					                if lua_keyword.keyword == KeywordKind::Lua =>
 | 
				
			||||||
| 
						 | 
					@ -283,11 +267,10 @@ impl<'a> Parser<'a> {
 | 
				
			||||||
                        let combined = first
 | 
					                        let combined = first
 | 
				
			||||||
                            .into_token()
 | 
					                            .into_token()
 | 
				
			||||||
                            .and_then(|first| {
 | 
					                            .and_then(|first| {
 | 
				
			||||||
                                first.span().join(
 | 
					                                first.span().join(&last.into_token().map_or_else(
 | 
				
			||||||
                                    &last
 | 
					                                    || first.span().to_owned(),
 | 
				
			||||||
                                        .into_token()
 | 
					                                    |last| last.span().to_owned(),
 | 
				
			||||||
                                        .map_or_else(|| first.span(), |last| last.span()),
 | 
					                                ))
 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            })
 | 
					                            })
 | 
				
			||||||
                            .expect("Invalid lua code span");
 | 
					                            .expect("Invalid lua code span");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
//! Contains the syntax tree nodes that represent the structure of the source code.
 | 
					//! Contains the syntax tree nodes that represent the structure of the source code.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use derive_more::derive::From;
 | 
					 | 
				
			||||||
use getset::Getters;
 | 
					use getset::Getters;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
| 
						 | 
					@ -10,7 +9,7 @@ use crate::{
 | 
				
			||||||
        Handler, VoidHandler,
 | 
					        Handler, VoidHandler,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    lexical::{
 | 
					    lexical::{
 | 
				
			||||||
        token::{MacroStringLiteral, Punctuation, StringLiteral, Token},
 | 
					        token::{Punctuation, Token},
 | 
				
			||||||
        token_stream::Delimiter,
 | 
					        token_stream::Delimiter,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    syntax::parser::Reading,
 | 
					    syntax::parser::Reading,
 | 
				
			||||||
| 
						 | 
					@ -65,29 +64,6 @@ pub struct DelimitedList<T> {
 | 
				
			||||||
    pub close: Punctuation,
 | 
					    pub close: Punctuation,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Represents a syntax tree node that can be either a string literal or a macro string literal.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// Syntax Synopsis:
 | 
					 | 
				
			||||||
/// ```ebnf
 | 
					 | 
				
			||||||
/// AnyStringLiteral: StringLiteral | MacroStringLiteral ;
 | 
					 | 
				
			||||||
/// ```
 | 
					 | 
				
			||||||
#[allow(missing_docs)]
 | 
					 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From)]
 | 
					 | 
				
			||||||
pub enum AnyStringLiteral {
 | 
					 | 
				
			||||||
    StringLiteral(StringLiteral),
 | 
					 | 
				
			||||||
    MacroStringLiteral(MacroStringLiteral),
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl SourceElement for AnyStringLiteral {
 | 
					 | 
				
			||||||
    fn span(&self) -> Span {
 | 
					 | 
				
			||||||
        match self {
 | 
					 | 
				
			||||||
            Self::StringLiteral(string_literal) => string_literal.span(),
 | 
					 | 
				
			||||||
            Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<'a> Parser<'a> {
 | 
					impl<'a> Parser<'a> {
 | 
				
			||||||
    /// Parses a list of elements enclosed by a pair of delimiters, separated by a separator.
 | 
					    /// Parses a list of elements enclosed by a pair of delimiters, separated by a separator.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ use crate::{
 | 
				
			||||||
    lexical::token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token},
 | 
					    lexical::token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token},
 | 
				
			||||||
    syntax::{
 | 
					    syntax::{
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        error::{ParseResult, SyntaxKind, UnexpectedSyntax},
 | 
					        error::{InvalidArgument, ParseResult, SyntaxKind, UnexpectedSyntax},
 | 
				
			||||||
        parser::{Parser, Reading},
 | 
					        parser::{Parser, Reading},
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -108,7 +108,22 @@ impl<'a> Parser<'a> {
 | 
				
			||||||
                // eat the keyword
 | 
					                // eat the keyword
 | 
				
			||||||
                self.forward();
 | 
					                self.forward();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let namespace_name = self.parse_string_literal(handler)?;
 | 
					                let namespace_name = self.parse_string_literal(handler).and_then(|name| {
 | 
				
			||||||
 | 
					                    Namespace::validate_str(name.str_content().as_ref())
 | 
				
			||||||
 | 
					                        .map(|()| name.clone())
 | 
				
			||||||
 | 
					                        .map_err(|invalid| {
 | 
				
			||||||
 | 
					                            let err = syntax::error::Error::InvalidArgument(InvalidArgument {
 | 
				
			||||||
 | 
					                                message: format!(
 | 
				
			||||||
 | 
					                                    "Invalid characters in namespace '{}'. The following characters are not allowed in namespace definitions: '{}'",
 | 
				
			||||||
 | 
					                                    name.str_content(),
 | 
				
			||||||
 | 
					                                    invalid
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
 | 
					                                span: name.span(),
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                            handler.receive(err.clone());
 | 
				
			||||||
 | 
					                            err
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let semicolon = self.parse_punctuation(';', true, handler)?;
 | 
					                let semicolon = self.parse_punctuation(';', true, handler)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,5 @@
 | 
				
			||||||
//! Execute block statement syntax tree.
 | 
					//! Execute block statement syntax tree.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::collections::HashSet;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use derive_more::From;
 | 
					use derive_more::From;
 | 
				
			||||||
use enum_as_inner::EnumAsInner;
 | 
					use enum_as_inner::EnumAsInner;
 | 
				
			||||||
use getset::Getters;
 | 
					use getset::Getters;
 | 
				
			||||||
| 
						 | 
					@ -13,13 +11,13 @@ use crate::{
 | 
				
			||||||
        Handler, VoidHandler,
 | 
					        Handler, VoidHandler,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    lexical::{
 | 
					    lexical::{
 | 
				
			||||||
        token::{Keyword, KeywordKind, Punctuation, Token},
 | 
					        token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token},
 | 
				
			||||||
        token_stream::Delimiter,
 | 
					        token_stream::Delimiter,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    syntax::{
 | 
					    syntax::{
 | 
				
			||||||
        error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
 | 
					        error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
 | 
				
			||||||
        parser::{DelimitedTree, Parser, Reading},
 | 
					        parser::{DelimitedTree, Parser, Reading},
 | 
				
			||||||
        syntax_tree::{condition::ParenthesizedCondition, AnyStringLiteral},
 | 
					        syntax_tree::condition::ParenthesizedCondition,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -219,7 +217,7 @@ impl SourceElement for Else {
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// As:
 | 
					/// As:
 | 
				
			||||||
/// 'as' '(' AnyStringLiteral ')'
 | 
					/// 'as' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -233,7 +231,7 @@ pub struct As {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the as statement.
 | 
					    /// The selector of the as statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    as_selector: AnyStringLiteral,
 | 
					    as_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -250,7 +248,7 @@ impl SourceElement for As {
 | 
				
			||||||
impl As {
 | 
					impl As {
 | 
				
			||||||
    /// Dissolves the [`As`] into its components.
 | 
					    /// Dissolves the [`As`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.as_keyword,
 | 
					            self.as_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -265,7 +263,7 @@ impl As {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// Align:
 | 
					/// Align:
 | 
				
			||||||
///     'align' '(' AnyStringLiteral ')'
 | 
					///     'align' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
 | 
					#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
 | 
				
			||||||
| 
						 | 
					@ -278,7 +276,7 @@ pub struct Align {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the align statement.
 | 
					    /// The selector of the align statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    align_selector: AnyStringLiteral,
 | 
					    align_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -296,7 +294,7 @@ impl SourceElement for Align {
 | 
				
			||||||
impl Align {
 | 
					impl Align {
 | 
				
			||||||
    /// Dissolves the [`Align`] into its components.
 | 
					    /// Dissolves the [`Align`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.align_keyword,
 | 
					            self.align_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -311,7 +309,7 @@ impl Align {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// Anchored:
 | 
					/// Anchored:
 | 
				
			||||||
///    'anchored' '(' AnyStringLiteral ')'
 | 
					///    'anchored' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -325,7 +323,7 @@ pub struct Anchored {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the anchored statement.
 | 
					    /// The selector of the anchored statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    anchored_selector: AnyStringLiteral,
 | 
					    anchored_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -341,7 +339,7 @@ impl SourceElement for Anchored {
 | 
				
			||||||
impl Anchored {
 | 
					impl Anchored {
 | 
				
			||||||
    /// Dissolves the [`Anchored`] into its components.
 | 
					    /// Dissolves the [`Anchored`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.anchored_keyword,
 | 
					            self.anchored_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -356,7 +354,7 @@ impl Anchored {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// AsAt:
 | 
					/// AsAt:
 | 
				
			||||||
///   'asat' '(' AnyStringLiteral ')'
 | 
					///   'asat' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -370,7 +368,7 @@ pub struct AsAt {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the asat statement.
 | 
					    /// The selector of the asat statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    asat_selector: AnyStringLiteral,
 | 
					    asat_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -386,7 +384,7 @@ impl SourceElement for AsAt {
 | 
				
			||||||
impl AsAt {
 | 
					impl AsAt {
 | 
				
			||||||
    /// Dissolves the [`AsAt`] into its components.
 | 
					    /// Dissolves the [`AsAt`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.asat_keyword,
 | 
					            self.asat_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -401,7 +399,7 @@ impl AsAt {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// At:
 | 
					/// At:
 | 
				
			||||||
///  'at' '(' AnyStringLiteral ')'
 | 
					///  'at' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -415,7 +413,7 @@ pub struct At {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the at statement.
 | 
					    /// The selector of the at statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    at_selector: AnyStringLiteral,
 | 
					    at_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -431,7 +429,7 @@ impl SourceElement for At {
 | 
				
			||||||
impl At {
 | 
					impl At {
 | 
				
			||||||
    /// Dissolves the [`At`] into its components.
 | 
					    /// Dissolves the [`At`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.at_keyword,
 | 
					            self.at_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -446,7 +444,7 @@ impl At {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// Facing:
 | 
					/// Facing:
 | 
				
			||||||
///  'facing' '(' AnyStringLiteral ')'
 | 
					///  'facing' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -460,7 +458,7 @@ pub struct Facing {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the facing statement.
 | 
					    /// The selector of the facing statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    facing_selector: AnyStringLiteral,
 | 
					    facing_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -476,7 +474,7 @@ impl SourceElement for Facing {
 | 
				
			||||||
impl Facing {
 | 
					impl Facing {
 | 
				
			||||||
    /// Dissolves the [`Facing`] into its components.
 | 
					    /// Dissolves the [`Facing`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.facing_keyword,
 | 
					            self.facing_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -491,7 +489,7 @@ impl Facing {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// In:
 | 
					/// In:
 | 
				
			||||||
/// 'in' '(' AnyStringLiteral ')'
 | 
					/// 'in' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -505,7 +503,7 @@ pub struct In {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the in statement.
 | 
					    /// The selector of the in statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    in_selector: AnyStringLiteral,
 | 
					    in_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -521,7 +519,7 @@ impl SourceElement for In {
 | 
				
			||||||
impl In {
 | 
					impl In {
 | 
				
			||||||
    /// Dissolves the [`In`] into its components.
 | 
					    /// Dissolves the [`In`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.in_keyword,
 | 
					            self.in_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -536,7 +534,7 @@ impl In {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// On:
 | 
					/// On:
 | 
				
			||||||
/// 'on' '(' AnyStringLiteral ')'
 | 
					/// 'on' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -550,7 +548,7 @@ pub struct On {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the on statement.
 | 
					    /// The selector of the on statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    on_selector: AnyStringLiteral,
 | 
					    on_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -566,7 +564,7 @@ impl SourceElement for On {
 | 
				
			||||||
impl On {
 | 
					impl On {
 | 
				
			||||||
    /// Dissolves the [`On`] into its components.
 | 
					    /// Dissolves the [`On`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.on_keyword,
 | 
					            self.on_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -581,7 +579,7 @@ impl On {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// Positioned:
 | 
					/// Positioned:
 | 
				
			||||||
/// 'positioned' '(' AnyStringLiteral ')'
 | 
					/// 'positioned' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -595,7 +593,7 @@ pub struct Positioned {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the positioned statement.
 | 
					    /// The selector of the positioned statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    positioned_selector: AnyStringLiteral,
 | 
					    positioned_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -611,7 +609,7 @@ impl SourceElement for Positioned {
 | 
				
			||||||
impl Positioned {
 | 
					impl Positioned {
 | 
				
			||||||
    /// Dissolves the [`Positioned`] into its components.
 | 
					    /// Dissolves the [`Positioned`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.positioned_keyword,
 | 
					            self.positioned_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -626,7 +624,7 @@ impl Positioned {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// Rotated:
 | 
					/// Rotated:
 | 
				
			||||||
/// 'rotated' '(' AnyStringLiteral ')'
 | 
					/// 'rotated' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -640,7 +638,7 @@ pub struct Rotated {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the rotated statement.
 | 
					    /// The selector of the rotated statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    rotated_selector: AnyStringLiteral,
 | 
					    rotated_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -656,7 +654,7 @@ impl SourceElement for Rotated {
 | 
				
			||||||
impl Rotated {
 | 
					impl Rotated {
 | 
				
			||||||
    /// Dissolves the [`Rotated`] into its components.
 | 
					    /// Dissolves the [`Rotated`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.rotated_keyword,
 | 
					            self.rotated_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -671,7 +669,7 @@ impl Rotated {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// Store:
 | 
					/// Store:
 | 
				
			||||||
/// 'store' '(' AnyStringLiteral ')'
 | 
					/// 'store' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -685,7 +683,7 @@ pub struct Store {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the store statement.
 | 
					    /// The selector of the store statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    store_selector: AnyStringLiteral,
 | 
					    store_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -701,7 +699,7 @@ impl SourceElement for Store {
 | 
				
			||||||
impl Store {
 | 
					impl Store {
 | 
				
			||||||
    /// Dissolves the [`Store`] into its components.
 | 
					    /// Dissolves the [`Store`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.store_keyword,
 | 
					            self.store_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -716,7 +714,7 @@ impl Store {
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// Summon:
 | 
					/// Summon:
 | 
				
			||||||
/// 'summon' '(' AnyStringLiteral ')'
 | 
					/// 'summon' '(' StringLiteral ')'
 | 
				
			||||||
/// ;
 | 
					/// ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
| 
						 | 
					@ -730,7 +728,7 @@ pub struct Summon {
 | 
				
			||||||
    open_paren: Punctuation,
 | 
					    open_paren: Punctuation,
 | 
				
			||||||
    /// The selector of the summon statement.
 | 
					    /// The selector of the summon statement.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    summon_selector: AnyStringLiteral,
 | 
					    summon_selector: StringLiteral,
 | 
				
			||||||
    /// The close parenthesis.
 | 
					    /// The close parenthesis.
 | 
				
			||||||
    #[get = "pub"]
 | 
					    #[get = "pub"]
 | 
				
			||||||
    close_paren: Punctuation,
 | 
					    close_paren: Punctuation,
 | 
				
			||||||
| 
						 | 
					@ -746,7 +744,7 @@ impl SourceElement for Summon {
 | 
				
			||||||
impl Summon {
 | 
					impl Summon {
 | 
				
			||||||
    /// Dissolves the [`Summon`] into its components.
 | 
					    /// Dissolves the [`Summon`] into its components.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
 | 
					    pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            self.summon_keyword,
 | 
					            self.summon_keyword,
 | 
				
			||||||
            self.open_paren,
 | 
					            self.open_paren,
 | 
				
			||||||
| 
						 | 
					@ -827,7 +825,7 @@ impl<'a> Parser<'a> {
 | 
				
			||||||
                let argument = match self.stop_at_significant() {
 | 
					                let argument = match self.stop_at_significant() {
 | 
				
			||||||
                    Reading::IntoDelimited(punc) if punc.punctuation == '(' => self.step_into(
 | 
					                    Reading::IntoDelimited(punc) if punc.punctuation == '(' => self.step_into(
 | 
				
			||||||
                        Delimiter::Parenthesis,
 | 
					                        Delimiter::Parenthesis,
 | 
				
			||||||
                        |parser| parser.parse_any_string_literal(handler),
 | 
					                        |parser| parser.parse_string_literal(handler),
 | 
				
			||||||
                        handler,
 | 
					                        handler,
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    unexpected => {
 | 
					                    unexpected => {
 | 
				
			||||||
| 
						 | 
					@ -898,7 +896,7 @@ impl<'a> Parser<'a> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn head_from_keyword(
 | 
					fn head_from_keyword(
 | 
				
			||||||
    keyword: Keyword,
 | 
					    keyword: Keyword,
 | 
				
			||||||
    argument: DelimitedTree<AnyStringLiteral>,
 | 
					    argument: DelimitedTree<StringLiteral>,
 | 
				
			||||||
) -> ParseResult<ExecuteBlockHead> {
 | 
					) -> ParseResult<ExecuteBlockHead> {
 | 
				
			||||||
    Ok(match keyword.keyword {
 | 
					    Ok(match keyword.keyword {
 | 
				
			||||||
        KeywordKind::Align => Align {
 | 
					        KeywordKind::Align => Align {
 | 
				
			||||||
| 
						 | 
					@ -988,91 +986,3 @@ fn head_from_keyword(
 | 
				
			||||||
        _ => unreachable!("The keyword is not a valid execute block head."),
 | 
					        _ => unreachable!("The keyword is not a valid execute block head."),
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Trait for the execute block head items with a [`AnyStringLiteral`] as their selector.
 | 
					 | 
				
			||||||
pub trait ExecuteBlockHeadItem {
 | 
					 | 
				
			||||||
    /// Returns a reference to the selector of the execute block head item.
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Analyzes the semantics of the execute block head item.
 | 
					 | 
				
			||||||
    #[expect(clippy::missing_errors_doc)]
 | 
					 | 
				
			||||||
    fn analyze_semantics(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        macro_names: &HashSet<String>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					 | 
				
			||||||
    ) -> Result<(), crate::semantic::error::Error> {
 | 
					 | 
				
			||||||
        self.selector().analyze_semantics(macro_names, handler)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for Align {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.align_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for Anchored {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.anchored_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for As {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.as_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for At {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.at_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for AsAt {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.asat_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for Facing {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.facing_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for In {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.in_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for On {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.on_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for Positioned {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.positioned_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for Rotated {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.rotated_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for Store {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.store_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ExecuteBlockHeadItem for Summon {
 | 
					 | 
				
			||||||
    fn selector(&self) -> &AnyStringLiteral {
 | 
					 | 
				
			||||||
        &self.summon_selector
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,19 +1,10 @@
 | 
				
			||||||
//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
 | 
					//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use shulkerbox::{
 | 
					use shulkerbox::datapack::Condition as DpCondition;
 | 
				
			||||||
    datapack::Condition as DpCondition,
 | 
					 | 
				
			||||||
    util::{MacroString, MacroStringPart},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::syntax::syntax_tree::condition::{
 | 
				
			||||||
    lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
 | 
					    BinaryCondition, Condition, ConditionalBinaryOperator, ConditionalPrefixOperator,
 | 
				
			||||||
    syntax::syntax_tree::{
 | 
					    PrimaryCondition,
 | 
				
			||||||
        condition::{
 | 
					 | 
				
			||||||
            BinaryCondition, Condition, ConditionalBinaryOperator, ConditionalPrefixOperator,
 | 
					 | 
				
			||||||
            PrimaryCondition,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        AnyStringLiteral,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<Condition> for DpCondition {
 | 
					impl From<Condition> for DpCondition {
 | 
				
			||||||
| 
						 | 
					@ -28,7 +19,9 @@ impl From<Condition> for DpCondition {
 | 
				
			||||||
impl From<PrimaryCondition> for DpCondition {
 | 
					impl From<PrimaryCondition> for DpCondition {
 | 
				
			||||||
    fn from(value: PrimaryCondition) -> Self {
 | 
					    fn from(value: PrimaryCondition) -> Self {
 | 
				
			||||||
        match value {
 | 
					        match value {
 | 
				
			||||||
            PrimaryCondition::StringLiteral(literal) => Self::Atom(literal.into()),
 | 
					            PrimaryCondition::StringLiteral(literal) => {
 | 
				
			||||||
 | 
					                Self::Atom(literal.str_content().to_string())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            PrimaryCondition::Parenthesized(cond) => cond.dissolve().1.into(),
 | 
					            PrimaryCondition::Parenthesized(cond) => cond.dissolve().1.into(),
 | 
				
			||||||
            PrimaryCondition::Unary(prefix) => match prefix.operator() {
 | 
					            PrimaryCondition::Unary(prefix) => match prefix.operator() {
 | 
				
			||||||
                ConditionalPrefixOperator::LogicalNot(_) => {
 | 
					                ConditionalPrefixOperator::LogicalNot(_) => {
 | 
				
			||||||
| 
						 | 
					@ -39,56 +32,6 @@ impl From<PrimaryCondition> for DpCondition {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<&AnyStringLiteral> for MacroString {
 | 
					 | 
				
			||||||
    fn from(value: &AnyStringLiteral) -> Self {
 | 
					 | 
				
			||||||
        match value {
 | 
					 | 
				
			||||||
            AnyStringLiteral::StringLiteral(literal) => Self::from(literal.str_content().as_ref()),
 | 
					 | 
				
			||||||
            AnyStringLiteral::MacroStringLiteral(literal) => Self::from(literal),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<AnyStringLiteral> for MacroString {
 | 
					 | 
				
			||||||
    fn from(value: AnyStringLiteral) -> Self {
 | 
					 | 
				
			||||||
        Self::from(&value)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<&MacroStringLiteral> for MacroString {
 | 
					 | 
				
			||||||
    fn from(value: &MacroStringLiteral) -> Self {
 | 
					 | 
				
			||||||
        if value
 | 
					 | 
				
			||||||
            .parts()
 | 
					 | 
				
			||||||
            .iter()
 | 
					 | 
				
			||||||
            .any(|p| matches!(p, MacroStringLiteralPart::MacroUsage { .. }))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Self::MacroString(
 | 
					 | 
				
			||||||
                value
 | 
					 | 
				
			||||||
                    .parts()
 | 
					 | 
				
			||||||
                    .iter()
 | 
					 | 
				
			||||||
                    .map(|part| match part {
 | 
					 | 
				
			||||||
                        MacroStringLiteralPart::Text(span) => MacroStringPart::String(
 | 
					 | 
				
			||||||
                            crate::util::unescape_macro_string(span.str()).to_string(),
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                        MacroStringLiteralPart::MacroUsage { identifier, .. } => {
 | 
					 | 
				
			||||||
                            MacroStringPart::MacroUsage(
 | 
					 | 
				
			||||||
                                super::util::identifier_to_macro(identifier.span.str()).to_string(),
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                    .collect(),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Self::String(value.str_content())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<MacroStringLiteral> for MacroString {
 | 
					 | 
				
			||||||
    fn from(value: MacroStringLiteral) -> Self {
 | 
					 | 
				
			||||||
        Self::from(&value)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<BinaryCondition> for DpCondition {
 | 
					impl From<BinaryCondition> for DpCondition {
 | 
				
			||||||
    fn from(value: BinaryCondition) -> Self {
 | 
					    fn from(value: BinaryCondition) -> Self {
 | 
				
			||||||
        let (lhs, op, rhs) = value.dissolve();
 | 
					        let (lhs, op, rhs) = value.dissolve();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,9 +8,9 @@ use itertools::Itertools;
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    base::{
 | 
					    base::{
 | 
				
			||||||
        log::{Message, Severity, SourceCodeDisplay},
 | 
					        log::{Message, Severity, SourceCodeDisplay},
 | 
				
			||||||
        source_file::Span,
 | 
					        source_file::{SourceElement, Span},
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
 | 
					    syntax::syntax_tree::expression::Expression,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::FunctionData;
 | 
					use super::FunctionData;
 | 
				
			||||||
| 
						 | 
					@ -29,8 +29,6 @@ pub enum TranspileError {
 | 
				
			||||||
    LuaRuntimeError(#[from] LuaRuntimeError),
 | 
					    LuaRuntimeError(#[from] LuaRuntimeError),
 | 
				
			||||||
    #[error(transparent)]
 | 
					    #[error(transparent)]
 | 
				
			||||||
    ConflictingFunctionNames(#[from] ConflictingFunctionNames),
 | 
					    ConflictingFunctionNames(#[from] ConflictingFunctionNames),
 | 
				
			||||||
    #[error(transparent)]
 | 
					 | 
				
			||||||
    InvalidFunctionArguments(#[from] InvalidFunctionArguments),
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// The result of a transpilation operation.
 | 
					/// The result of a transpilation operation.
 | 
				
			||||||
| 
						 | 
					@ -146,3 +144,52 @@ impl LuaRuntimeError {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// An error that occurs when a function declaration is missing.
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub struct UnexpectedExpression(pub Expression);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Display for UnexpectedExpression {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					        write!(
 | 
				
			||||||
 | 
					            f,
 | 
				
			||||||
 | 
					            "{}",
 | 
				
			||||||
 | 
					            Message::new(Severity::Error, "encountered unexpected expression")
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        write!(
 | 
				
			||||||
 | 
					            f,
 | 
				
			||||||
 | 
					            "\n{}",
 | 
				
			||||||
 | 
					            SourceCodeDisplay::new(&self.0.span(), Option::<u8>::None)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::error::Error for UnexpectedExpression {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub struct ConflictingFunctionNames {
 | 
				
			||||||
 | 
					    pub definition: Span,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Display for ConflictingFunctionNames {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					        write!(
 | 
				
			||||||
 | 
					            f,
 | 
				
			||||||
 | 
					            "{}",
 | 
				
			||||||
 | 
					            Message::new(
 | 
				
			||||||
 | 
					                Severity::Error,
 | 
				
			||||||
 | 
					                format!("the following function declaration conflicts with an existing function with name `{}`", self.name)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        write!(
 | 
				
			||||||
 | 
					            f,
 | 
				
			||||||
 | 
					            "\n{}",
 | 
				
			||||||
 | 
					            SourceCodeDisplay::new(&self.definition, Option::<u8>::None)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::error::Error for ConflictingFunctionNames {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,13 +20,13 @@ mod transpiler;
 | 
				
			||||||
#[cfg_attr(feature = "shulkerbox", doc(inline))]
 | 
					#[cfg_attr(feature = "shulkerbox", doc(inline))]
 | 
				
			||||||
pub use transpiler::Transpiler;
 | 
					pub use transpiler::Transpiler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod util;
 | 
					#[cfg(feature = "shulkerbox")]
 | 
				
			||||||
 | 
					mod util;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq)]
 | 
					#[derive(Debug, Clone, PartialEq, Eq)]
 | 
				
			||||||
pub(super) struct FunctionData {
 | 
					pub(super) struct FunctionData {
 | 
				
			||||||
    pub(super) namespace: String,
 | 
					    pub(super) namespace: String,
 | 
				
			||||||
    pub(super) identifier_span: Span,
 | 
					    pub(super) identifier_span: Span,
 | 
				
			||||||
    pub(super) parameters: Vec<String>,
 | 
					 | 
				
			||||||
    pub(super) statements: Vec<Statement>,
 | 
					    pub(super) statements: Vec<Statement>,
 | 
				
			||||||
    pub(super) public: bool,
 | 
					    pub(super) public: bool,
 | 
				
			||||||
    pub(super) annotations: HashMap<String, Option<String>>,
 | 
					    pub(super) annotations: HashMap<String, Option<String>>,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
use chksum_md5 as md5;
 | 
					use chksum_md5 as md5;
 | 
				
			||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
    collections::{BTreeMap, HashMap},
 | 
					    collections::{BTreeMap, HashMap},
 | 
				
			||||||
    ops::Deref,
 | 
					    iter,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use shulkerbox::datapack::{self, Command, Datapack, Execute};
 | 
					use shulkerbox::datapack::{self, Command, Datapack, Execute};
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,6 @@ use crate::{
 | 
				
			||||||
        source_file::{SourceElement, Span},
 | 
					        source_file::{SourceElement, Span},
 | 
				
			||||||
        Handler,
 | 
					        Handler,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
 | 
					 | 
				
			||||||
    syntax::syntax_tree::{
 | 
					    syntax::syntax_tree::{
 | 
				
			||||||
        declaration::{Declaration, ImportItems},
 | 
					        declaration::{Declaration, ImportItems},
 | 
				
			||||||
        expression::{Expression, FunctionCall, Primary},
 | 
					        expression::{Expression, FunctionCall, Primary},
 | 
				
			||||||
| 
						 | 
					@ -24,11 +23,11 @@ use crate::{
 | 
				
			||||||
            Statement,
 | 
					            Statement,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    transpile::error::MissingFunctionDeclaration,
 | 
					    transpile::error::{ConflictingFunctionNames, MissingFunctionDeclaration},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::{
 | 
					use super::{
 | 
				
			||||||
    error::{TranspileError, TranspileResult},
 | 
					    error::{TranspileError, TranspileResult, UnexpectedExpression},
 | 
				
			||||||
    FunctionData,
 | 
					    FunctionData,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -98,7 +97,7 @@ impl Transpiler {
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for identifier_span in always_transpile_functions {
 | 
					        for identifier_span in always_transpile_functions {
 | 
				
			||||||
            self.get_or_transpile_function(&identifier_span, None, handler)?;
 | 
					            self.get_or_transpile_function(&identifier_span, handler)?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
| 
						 | 
					@ -123,7 +122,7 @@ impl Transpiler {
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
        declaration: &Declaration,
 | 
					        declaration: &Declaration,
 | 
				
			||||||
        namespace: &Namespace,
 | 
					        namespace: &Namespace,
 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					        _handler: &impl Handler<base::Error>,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        let program_identifier = declaration.span().source_file().identifier().clone();
 | 
					        let program_identifier = declaration.span().source_file().identifier().clone();
 | 
				
			||||||
        match declaration {
 | 
					        match declaration {
 | 
				
			||||||
| 
						 | 
					@ -149,15 +148,6 @@ impl Transpiler {
 | 
				
			||||||
                    FunctionData {
 | 
					                    FunctionData {
 | 
				
			||||||
                        namespace: namespace.namespace_name().str_content().to_string(),
 | 
					                        namespace: namespace.namespace_name().str_content().to_string(),
 | 
				
			||||||
                        identifier_span: identifier_span.clone(),
 | 
					                        identifier_span: identifier_span.clone(),
 | 
				
			||||||
                        parameters: function
 | 
					 | 
				
			||||||
                            .parameters()
 | 
					 | 
				
			||||||
                            .as_ref()
 | 
					 | 
				
			||||||
                            .map(|l| {
 | 
					 | 
				
			||||||
                                l.elements()
 | 
					 | 
				
			||||||
                                    .map(|i| i.span.str().to_string())
 | 
					 | 
				
			||||||
                                    .collect::<Vec<_>>()
 | 
					 | 
				
			||||||
                            })
 | 
					 | 
				
			||||||
                            .unwrap_or_default(),
 | 
					 | 
				
			||||||
                        statements,
 | 
					                        statements,
 | 
				
			||||||
                        public: function.is_public(),
 | 
					                        public: function.is_public(),
 | 
				
			||||||
                        annotations,
 | 
					                        annotations,
 | 
				
			||||||
| 
						 | 
					@ -172,13 +162,12 @@ impl Transpiler {
 | 
				
			||||||
                let aliases = &mut self.aliases;
 | 
					                let aliases = &mut self.aliases;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match import.items() {
 | 
					                match import.items() {
 | 
				
			||||||
                    ImportItems::All(_) => {
 | 
					                    ImportItems::All(_) => todo!("Importing all items is not yet supported."),
 | 
				
			||||||
                        handler.receive(base::Error::Other(
 | 
					 | 
				
			||||||
                            "Importing all items is not yet supported.".to_string(),
 | 
					 | 
				
			||||||
                        ));
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    ImportItems::Named(list) => {
 | 
					                    ImportItems::Named(list) => {
 | 
				
			||||||
                        for item in list.elements() {
 | 
					                        let items = iter::once(list.first())
 | 
				
			||||||
 | 
					                            .chain(list.rest().iter().map(|(_, ident)| ident));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        for item in items {
 | 
				
			||||||
                            let name = item.span.str();
 | 
					                            let name = item.span.str();
 | 
				
			||||||
                            aliases.insert(
 | 
					                            aliases.insert(
 | 
				
			||||||
                                (program_identifier.clone(), name.to_string()),
 | 
					                                (program_identifier.clone(), name.to_string()),
 | 
				
			||||||
| 
						 | 
					@ -214,9 +203,8 @@ impl Transpiler {
 | 
				
			||||||
    fn get_or_transpile_function(
 | 
					    fn get_or_transpile_function(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
        identifier_span: &Span,
 | 
					        identifier_span: &Span,
 | 
				
			||||||
        arguments: Option<&[&Expression]>,
 | 
					 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					        handler: &impl Handler<base::Error>,
 | 
				
			||||||
    ) -> TranspileResult<(String, Option<BTreeMap<String, String>>)> {
 | 
					    ) -> TranspileResult<String> {
 | 
				
			||||||
        let program_identifier = identifier_span.source_file().identifier();
 | 
					        let program_identifier = identifier_span.source_file().identifier();
 | 
				
			||||||
        let program_query = (
 | 
					        let program_query = (
 | 
				
			||||||
            program_identifier.to_string(),
 | 
					            program_identifier.to_string(),
 | 
				
			||||||
| 
						 | 
					@ -256,7 +244,6 @@ impl Transpiler {
 | 
				
			||||||
                        handler.receive(error.clone());
 | 
					                        handler.receive(error.clone());
 | 
				
			||||||
                        error
 | 
					                        error
 | 
				
			||||||
                    })?;
 | 
					                    })?;
 | 
				
			||||||
 | 
					 | 
				
			||||||
                function_data.statements.clone()
 | 
					                function_data.statements.clone()
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            let commands = self.transpile_function(&statements, program_identifier, handler)?;
 | 
					            let commands = self.transpile_function(&statements, program_identifier, handler)?;
 | 
				
			||||||
| 
						 | 
					@ -328,35 +315,10 @@ impl Transpiler {
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let parameters = {
 | 
					        let locations = &self.function_locations;
 | 
				
			||||||
            let function_data = self
 | 
					        locations
 | 
				
			||||||
                .functions
 | 
					 | 
				
			||||||
                .get(&program_query)
 | 
					 | 
				
			||||||
                .or_else(|| {
 | 
					 | 
				
			||||||
                    alias_query
 | 
					 | 
				
			||||||
                        .clone()
 | 
					 | 
				
			||||||
                        .and_then(|q| self.functions.get(&q).filter(|f| f.public))
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .ok_or_else(|| {
 | 
					 | 
				
			||||||
                    let error = TranspileError::MissingFunctionDeclaration(
 | 
					 | 
				
			||||||
                        MissingFunctionDeclaration::from_context(
 | 
					 | 
				
			||||||
                            identifier_span.clone(),
 | 
					 | 
				
			||||||
                            &self.functions,
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                    handler.receive(error.clone());
 | 
					 | 
				
			||||||
                    error
 | 
					 | 
				
			||||||
                })?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            function_data.parameters.clone()
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let function_location = self
 | 
					 | 
				
			||||||
            .function_locations
 | 
					 | 
				
			||||||
            .get(&program_query)
 | 
					            .get(&program_query)
 | 
				
			||||||
            .or_else(|| {
 | 
					            .or_else(|| alias_query.and_then(|q| locations.get(&q).filter(|(_, p)| *p)))
 | 
				
			||||||
                alias_query.and_then(|q| self.function_locations.get(&q).filter(|(_, p)| *p))
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .ok_or_else(|| {
 | 
					            .ok_or_else(|| {
 | 
				
			||||||
                let error = TranspileError::MissingFunctionDeclaration(
 | 
					                let error = TranspileError::MissingFunctionDeclaration(
 | 
				
			||||||
                    MissingFunctionDeclaration::from_context(
 | 
					                    MissingFunctionDeclaration::from_context(
 | 
				
			||||||
| 
						 | 
					@ -367,57 +329,7 @@ impl Transpiler {
 | 
				
			||||||
                handler.receive(error.clone());
 | 
					                handler.receive(error.clone());
 | 
				
			||||||
                error
 | 
					                error
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .map(|(s, _)| s.to_owned())?;
 | 
					            .map(|(s, _)| s.to_owned())
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let arg_count = arguments.iter().flat_map(|x| x.iter()).count();
 | 
					 | 
				
			||||||
        if arg_count != parameters.len() {
 | 
					 | 
				
			||||||
            let err = TranspileError::InvalidFunctionArguments(InvalidFunctionArguments {
 | 
					 | 
				
			||||||
                expected: parameters.len(),
 | 
					 | 
				
			||||||
                actual: arg_count,
 | 
					 | 
				
			||||||
                span: identifier_span.clone(),
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            handler.receive(err.clone());
 | 
					 | 
				
			||||||
            Err(err)
 | 
					 | 
				
			||||||
        } else if arg_count > 0 {
 | 
					 | 
				
			||||||
            let mut compiled_args = Vec::new();
 | 
					 | 
				
			||||||
            let mut errs = Vec::new();
 | 
					 | 
				
			||||||
            for expression in arguments.iter().flat_map(|x| x.iter()) {
 | 
					 | 
				
			||||||
                let value = match expression {
 | 
					 | 
				
			||||||
                    Expression::Primary(Primary::FunctionCall(func)) => self
 | 
					 | 
				
			||||||
                        .transpile_function_call(func, handler)
 | 
					 | 
				
			||||||
                        .map(|cmd| match cmd {
 | 
					 | 
				
			||||||
                            Command::Raw(s) => s,
 | 
					 | 
				
			||||||
                            _ => unreachable!("Function call should always return a raw command"),
 | 
					 | 
				
			||||||
                        }),
 | 
					 | 
				
			||||||
                    Expression::Primary(Primary::Lua(lua)) => {
 | 
					 | 
				
			||||||
                        lua.eval_string(handler).map(Option::unwrap_or_default)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    Expression::Primary(Primary::StringLiteral(string)) => {
 | 
					 | 
				
			||||||
                        Ok(string.str_content().to_string())
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    Expression::Primary(Primary::MacroStringLiteral(literal)) => {
 | 
					 | 
				
			||||||
                        Ok(literal.str_content())
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                match value {
 | 
					 | 
				
			||||||
                    Ok(value) => {
 | 
					 | 
				
			||||||
                        compiled_args.push(value);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    Err(err) => {
 | 
					 | 
				
			||||||
                        compiled_args.push(String::new());
 | 
					 | 
				
			||||||
                        errs.push(err.clone());
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if let Some(err) = errs.first() {
 | 
					 | 
				
			||||||
                return Err(err.clone());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            let function_args = parameters.into_iter().zip(compiled_args).collect();
 | 
					 | 
				
			||||||
            Ok((function_location, Some(function_args)))
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Ok((function_location, None))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn transpile_function(
 | 
					    fn transpile_function(
 | 
				
			||||||
| 
						 | 
					@ -462,9 +374,6 @@ impl Transpiler {
 | 
				
			||||||
                Expression::Primary(Primary::StringLiteral(string)) => {
 | 
					                Expression::Primary(Primary::StringLiteral(string)) => {
 | 
				
			||||||
                    Ok(Some(Command::Raw(string.str_content().to_string())))
 | 
					                    Ok(Some(Command::Raw(string.str_content().to_string())))
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                Expression::Primary(Primary::MacroStringLiteral(string)) => {
 | 
					 | 
				
			||||||
                    Ok(Some(Command::UsesMacro(string.into())))
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                Expression::Primary(Primary::Lua(code)) => {
 | 
					                Expression::Primary(Primary::Lua(code)) => {
 | 
				
			||||||
                    Ok(code.eval_string(handler)?.map(Command::Raw))
 | 
					                    Ok(code.eval_string(handler)?.map(Command::Raw))
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
| 
						 | 
					@ -522,29 +431,8 @@ impl Transpiler {
 | 
				
			||||||
        func: &FunctionCall,
 | 
					        func: &FunctionCall,
 | 
				
			||||||
        handler: &impl Handler<base::Error>,
 | 
					        handler: &impl Handler<base::Error>,
 | 
				
			||||||
    ) -> TranspileResult<Command> {
 | 
					    ) -> TranspileResult<Command> {
 | 
				
			||||||
        let arguments = func
 | 
					        let location = self.get_or_transpile_function(&func.identifier().span, handler)?;
 | 
				
			||||||
            .arguments()
 | 
					        Ok(Command::Raw(format!("function {location}")))
 | 
				
			||||||
            .as_ref()
 | 
					 | 
				
			||||||
            .map(|l| l.elements().map(Deref::deref).collect::<Vec<_>>());
 | 
					 | 
				
			||||||
        let (location, arguments) =
 | 
					 | 
				
			||||||
            self.get_or_transpile_function(&func.identifier().span, arguments.as_deref(), handler)?;
 | 
					 | 
				
			||||||
        let mut function_call = format!("function {location}");
 | 
					 | 
				
			||||||
        if let Some(arguments) = arguments {
 | 
					 | 
				
			||||||
            use std::fmt::Write;
 | 
					 | 
				
			||||||
            let arguments = arguments
 | 
					 | 
				
			||||||
                .iter()
 | 
					 | 
				
			||||||
                .map(|(ident, v)| {
 | 
					 | 
				
			||||||
                    format!(
 | 
					 | 
				
			||||||
                        r#"{macro_name}:"{escaped}""#,
 | 
					 | 
				
			||||||
                        macro_name = super::util::identifier_to_macro(ident),
 | 
					 | 
				
			||||||
                        escaped = crate::util::escape_str(v)
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .collect::<Vec<_>>()
 | 
					 | 
				
			||||||
                .join(",");
 | 
					 | 
				
			||||||
            write!(function_call, " {{{arguments}}}").unwrap();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Ok(Command::Raw(function_call))
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn transpile_execute_block(
 | 
					    fn transpile_execute_block(
 | 
				
			||||||
| 
						 | 
					@ -707,53 +595,53 @@ impl Transpiler {
 | 
				
			||||||
                    None
 | 
					                    None
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::As(r#as) => {
 | 
					            ExecuteBlockHead::As(as_) => {
 | 
				
			||||||
                let selector = r#as.as_selector();
 | 
					                let selector = as_.as_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::As(selector.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::As(selector.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::At(at) => {
 | 
					            ExecuteBlockHead::At(at) => {
 | 
				
			||||||
                let selector = at.at_selector();
 | 
					                let selector = at.at_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::At(selector.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::At(selector.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Align(align) => {
 | 
					            ExecuteBlockHead::Align(align) => {
 | 
				
			||||||
                let align = align.align_selector();
 | 
					                let align = align.align_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::Align(align.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::Align(align.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Anchored(anchored) => {
 | 
					            ExecuteBlockHead::Anchored(anchored) => {
 | 
				
			||||||
                let anchor = anchored.anchored_selector();
 | 
					                let anchor = anchored.anchored_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::Anchored(anchor.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::Anchored(anchor.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::In(r#in) => {
 | 
					            ExecuteBlockHead::In(in_) => {
 | 
				
			||||||
                let dimension = r#in.in_selector();
 | 
					                let dimension = in_.in_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::In(dimension.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::In(dimension.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Positioned(positioned) => {
 | 
					            ExecuteBlockHead::Positioned(positioned) => {
 | 
				
			||||||
                let position = positioned.positioned_selector();
 | 
					                let position = positioned.positioned_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::Positioned(position.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::Positioned(position.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Rotated(rotated) => {
 | 
					            ExecuteBlockHead::Rotated(rotated) => {
 | 
				
			||||||
                let rotation = rotated.rotated_selector();
 | 
					                let rotation = rotated.rotated_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::Rotated(rotation.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::Rotated(rotation.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Facing(facing) => {
 | 
					            ExecuteBlockHead::Facing(facing) => {
 | 
				
			||||||
                let facing = facing.facing_selector();
 | 
					                let facing = facing.facing_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::Facing(facing.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::Facing(facing.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::AsAt(as_at) => {
 | 
					            ExecuteBlockHead::AsAt(as_at) => {
 | 
				
			||||||
                let selector = as_at.asat_selector();
 | 
					                let selector = as_at.asat_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::AsAt(selector.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::AsAt(selector.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::On(on) => {
 | 
					            ExecuteBlockHead::On(on) => {
 | 
				
			||||||
                let dimension = on.on_selector();
 | 
					                let dimension = on.on_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::On(dimension.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::On(dimension.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Store(store) => {
 | 
					            ExecuteBlockHead::Store(store) => {
 | 
				
			||||||
                let store = store.store_selector();
 | 
					                let store = store.store_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::Store(store.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::Store(store.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Summon(summon) => {
 | 
					            ExecuteBlockHead::Summon(summon) => {
 | 
				
			||||||
                let entity = summon.summon_selector();
 | 
					                let entity = summon.summon_selector().str_content();
 | 
				
			||||||
                tail.map(|tail| Execute::Summon(entity.into(), Box::new(tail)))
 | 
					                tail.map(|tail| Execute::Summon(entity.to_string(), Box::new(tail)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,3 @@
 | 
				
			||||||
//! Utility methods for transpiling
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[cfg(feature = "shulkerbox")]
 | 
					 | 
				
			||||||
use chksum_md5 as md5;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn normalize_program_identifier<S>(identifier: S) -> String
 | 
					fn normalize_program_identifier<S>(identifier: S) -> String
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
    S: AsRef<str>,
 | 
					    S: AsRef<str>,
 | 
				
			||||||
| 
						 | 
					@ -24,8 +19,6 @@ where
 | 
				
			||||||
        .join("/")
 | 
					        .join("/")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Calculate the identifier to import the function based on the current identifier and the import path
 | 
					 | 
				
			||||||
#[must_use]
 | 
					 | 
				
			||||||
pub fn calculate_import_identifier<S, T>(current_identifier: S, import_path: T) -> String
 | 
					pub fn calculate_import_identifier<S, T>(current_identifier: S, import_path: T) -> String
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
    S: AsRef<str>,
 | 
					    S: AsRef<str>,
 | 
				
			||||||
| 
						 | 
					@ -39,25 +32,3 @@ where
 | 
				
			||||||
        normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref())
 | 
					        normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Transforms an identifier to a macro name that only contains `a-zA-Z0-9_`.
 | 
					 | 
				
			||||||
#[cfg(feature = "shulkerbox")]
 | 
					 | 
				
			||||||
#[must_use]
 | 
					 | 
				
			||||||
pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<str> {
 | 
					 | 
				
			||||||
    if ident.contains("__")
 | 
					 | 
				
			||||||
        || ident
 | 
					 | 
				
			||||||
            .chars()
 | 
					 | 
				
			||||||
            .any(|c| !(c == '_' && c.is_ascii_alphanumeric()))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        let new_ident = ident
 | 
					 | 
				
			||||||
            .chars()
 | 
					 | 
				
			||||||
            .filter(|c| *c == '_' || c.is_ascii_alphanumeric())
 | 
					 | 
				
			||||||
            .collect::<String>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let chksum = md5::hash(ident).to_hex_lowercase();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::borrow::Cow::Owned(new_ident + "__" + &chksum[..8])
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        std::borrow::Cow::Borrowed(ident)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/util.rs
								
								
								
								
							
							
						
						
									
										82
									
								
								src/util.rs
								
								
								
								
							| 
						 | 
					@ -1,82 +0,0 @@
 | 
				
			||||||
//! Utility functions for the `Shulkerscript` language.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use std::borrow::Cow;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Escapes `"` and `\` in a string.
 | 
					 | 
				
			||||||
#[must_use]
 | 
					 | 
				
			||||||
pub fn escape_str(s: &str) -> Cow<str> {
 | 
					 | 
				
			||||||
    if s.contains('"') || s.contains('\\') {
 | 
					 | 
				
			||||||
        let mut escaped = String::with_capacity(s.len());
 | 
					 | 
				
			||||||
        for c in s.chars() {
 | 
					 | 
				
			||||||
            match c {
 | 
					 | 
				
			||||||
                '"' => escaped.push_str("\\\""),
 | 
					 | 
				
			||||||
                '\\' => escaped.push_str("\\\\"),
 | 
					 | 
				
			||||||
                _ => escaped.push(c),
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Cow::Owned(escaped)
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        Cow::Borrowed(s)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Unescapes '\`', `\`, `\n`, `\r` and `\t` in a string.
 | 
					 | 
				
			||||||
#[must_use]
 | 
					 | 
				
			||||||
pub fn unescape_macro_string(s: &str) -> Cow<str> {
 | 
					 | 
				
			||||||
    if s.contains('\\') || s.contains('`') {
 | 
					 | 
				
			||||||
        Cow::Owned(
 | 
					 | 
				
			||||||
            s.replace("\\n", "\n")
 | 
					 | 
				
			||||||
                .replace("\\r", "\r")
 | 
					 | 
				
			||||||
                .replace("\\t", "\t")
 | 
					 | 
				
			||||||
                .replace("\\`", "`")
 | 
					 | 
				
			||||||
                .replace("\\\\", "\\"),
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        Cow::Borrowed(s)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[cfg(test)]
 | 
					 | 
				
			||||||
mod tests {
 | 
					 | 
				
			||||||
    use super::*;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[test]
 | 
					 | 
				
			||||||
    fn test_escape_str() {
 | 
					 | 
				
			||||||
        assert_eq!(escape_str("Hello, world!"), "Hello, world!");
 | 
					 | 
				
			||||||
        assert_eq!(escape_str(r#"Hello, "world"!"#), r#"Hello, \"world\"!"#);
 | 
					 | 
				
			||||||
        assert_eq!(escape_str(r"Hello, \world\!"), r"Hello, \\world\\!");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[test]
 | 
					 | 
				
			||||||
    fn test_unescape_macro_string() {
 | 
					 | 
				
			||||||
        assert_eq!(unescape_macro_string("Hello, world!"), "Hello, world!");
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					 | 
				
			||||||
            unescape_macro_string(r#"Hello, "world"!"#),
 | 
					 | 
				
			||||||
            r#"Hello, "world"!"#
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					 | 
				
			||||||
            unescape_macro_string(r"Hello, \world\!"),
 | 
					 | 
				
			||||||
            r"Hello, \world\!"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					 | 
				
			||||||
            unescape_macro_string(r"Hello, \nworld\!"),
 | 
					 | 
				
			||||||
            "Hello, \nworld\\!"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					 | 
				
			||||||
            unescape_macro_string(r"Hello, \rworld\!"),
 | 
					 | 
				
			||||||
            "Hello, \rworld\\!"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					 | 
				
			||||||
            unescape_macro_string(r"Hello, \tworld\!"),
 | 
					 | 
				
			||||||
            "Hello, \tworld\\!"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					 | 
				
			||||||
            unescape_macro_string(r"Hello, \`world\!"),
 | 
					 | 
				
			||||||
            r"Hello, `world\!"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        assert_eq!(
 | 
					 | 
				
			||||||
            unescape_macro_string(r"Hello, \\world\!"),
 | 
					 | 
				
			||||||
            r"Hello, \world\!"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -28,9 +28,9 @@ fn transpile_test1() {
 | 
				
			||||||
        main_fn.add_command(Command::Raw("say Hello, World!".to_string()));
 | 
					        main_fn.add_command(Command::Raw("say Hello, World!".to_string()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let exec_cmd = Command::Execute(Execute::As(
 | 
					        let exec_cmd = Command::Execute(Execute::As(
 | 
				
			||||||
            "@a".to_string().into(),
 | 
					            "@a".to_string(),
 | 
				
			||||||
            Box::new(Execute::If(
 | 
					            Box::new(Execute::If(
 | 
				
			||||||
                Condition::Atom("entity @p[distance=..5]".to_string().into()),
 | 
					                Condition::Atom("entity @p[distance=..5]".to_string()),
 | 
				
			||||||
                Box::new(Execute::Run(Box::new(Command::Raw(
 | 
					                Box::new(Execute::Run(Box::new(Command::Raw(
 | 
				
			||||||
                    "say You are close to me!".to_string(),
 | 
					                    "say You are close to me!".to_string(),
 | 
				
			||||||
                )))),
 | 
					                )))),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue