Compare commits
	
		
			3 Commits
		
	
	
		
			3bc3ca180f
			...
			b7d50f8222
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								 | 
						b7d50f8222 | |
| 
							
							
								 | 
						183d3e85c6 | |
| 
							
							
								 | 
						08ed56b673 | 
							
								
								
									
										17
									
								
								grammar.md
								
								
								
								
							
							
						
						
									
										17
									
								
								grammar.md
								
								
								
								
							| 
						 | 
					@ -170,7 +170,7 @@ VariableDeclarationAssignment:
 | 
				
			||||||
## AnyStringLiteral
 | 
					## AnyStringLiteral
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ebnf
 | 
					```ebnf
 | 
				
			||||||
AnyStringLiteral: StringLiteral | MacroStringLiteral ;
 | 
					AnyStringLiteral: StringLiteral | TemplateStringLiteral ;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## AnnotationValue
 | 
					## AnnotationValue
 | 
				
			||||||
| 
						 | 
					@ -230,11 +230,11 @@ Expression:
 | 
				
			||||||
    Primary | Binary ;
 | 
					    Primary | Binary ;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## MacroStringLiteral
 | 
					## TemplateStringLiteral
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ebnf
 | 
					```ebnf
 | 
				
			||||||
MacroStringLiteral:
 | 
					TemplateStringLiteral:
 | 
				
			||||||
  '`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
 | 
					  '`' ( TemplateStringLiteralText | '$(' Expression ')' )* '`';
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Else
 | 
					## Else
 | 
				
			||||||
| 
						 | 
					@ -304,10 +304,17 @@ Primary:
 | 
				
			||||||
    | StringLiteral
 | 
					    | StringLiteral
 | 
				
			||||||
    | FunctionCall
 | 
					    | FunctionCall
 | 
				
			||||||
    | MemberAccess
 | 
					    | MemberAccess
 | 
				
			||||||
    | MacroStringLiteral
 | 
					    | TemplateStringLiteral
 | 
				
			||||||
    | LuaCode
 | 
					    | LuaCode
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## TemplateStringLiteralText
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```ebnf
 | 
				
			||||||
 | 
					TemplateStringLiteralText:
 | 
				
			||||||
 | 
					  TEXT ;
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Align
 | 
					## Align
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```ebnf
 | 
					```ebnf
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
    cmp::Ordering,
 | 
					    cmp::Ordering,
 | 
				
			||||||
    fmt::Debug,
 | 
					    fmt::Debug,
 | 
				
			||||||
    iter::{Iterator, Peekable},
 | 
					    iter::Iterator,
 | 
				
			||||||
    ops::Range,
 | 
					    ops::Range,
 | 
				
			||||||
    path::{Path, PathBuf},
 | 
					    path::{Path, PathBuf},
 | 
				
			||||||
    str::CharIndices,
 | 
					    str::CharIndices,
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ use std::{
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use getset::{CopyGetters, Getters};
 | 
					use getset::{CopyGetters, Getters};
 | 
				
			||||||
 | 
					use itertools::{structs::MultiPeek, Itertools as _};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::{file_provider::FileProvider, Error};
 | 
					use super::{file_provider::FileProvider, Error};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,8 +73,9 @@ impl SourceFile {
 | 
				
			||||||
    pub fn iter(self: &Arc<Self>) -> SourceIterator<'_> {
 | 
					    pub fn iter(self: &Arc<Self>) -> SourceIterator<'_> {
 | 
				
			||||||
        SourceIterator {
 | 
					        SourceIterator {
 | 
				
			||||||
            source_file: self,
 | 
					            source_file: self,
 | 
				
			||||||
            iterator: self.content().char_indices().peekable(),
 | 
					            iterator: self.content().char_indices().multipeek(),
 | 
				
			||||||
            prev: None,
 | 
					            prev: None,
 | 
				
			||||||
 | 
					            in_template_string_expression_open_count: Vec::new(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -337,16 +339,69 @@ pub struct SourceIterator<'a> {
 | 
				
			||||||
    /// Get the source file that the iterator is iterating over.
 | 
					    /// Get the source file that the iterator is iterating over.
 | 
				
			||||||
    #[get_copy = "pub"]
 | 
					    #[get_copy = "pub"]
 | 
				
			||||||
    source_file: &'a Arc<SourceFile>,
 | 
					    source_file: &'a Arc<SourceFile>,
 | 
				
			||||||
    iterator: Peekable<CharIndices<'a>>,
 | 
					    iterator: MultiPeek<CharIndices<'a>>,
 | 
				
			||||||
    /// Get the previous character that was iterated over.
 | 
					    /// Get the previous character that was iterated over.
 | 
				
			||||||
    #[get_copy = "pub"]
 | 
					    #[get_copy = "pub"]
 | 
				
			||||||
    prev: Option<(usize, char)>,
 | 
					    prev: Option<(usize, char)>,
 | 
				
			||||||
 | 
					    /// Current state for parsing template strings.
 | 
				
			||||||
 | 
					    in_template_string_expression_open_count: Vec<u32>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
impl SourceIterator<'_> {
 | 
					impl SourceIterator<'_> {
 | 
				
			||||||
    /// Peek at the next character in the source file.
 | 
					    /// Peek at the next character in the source file.
 | 
				
			||||||
    pub fn peek(&mut self) -> Option<(usize, char)> {
 | 
					    pub fn peek(&mut self) -> Option<(usize, char)> {
 | 
				
			||||||
 | 
					        self.iterator.reset_peek();
 | 
				
			||||||
        self.iterator.peek().copied()
 | 
					        self.iterator.peek().copied()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Peek at the next character in the source file.
 | 
				
			||||||
 | 
					    pub fn multipeek(&mut self) -> Option<(usize, char)> {
 | 
				
			||||||
 | 
					        self.iterator.peek().copied()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Reset the multipeek state of the iterator.
 | 
				
			||||||
 | 
					    pub fn reset_multipeek(&mut self) {
 | 
				
			||||||
 | 
					        self.iterator.reset_peek();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Increase the count of open parentheses in the current template string expression.
 | 
				
			||||||
 | 
					    pub fn increase_template_string_expression_open_paren_count(&mut self) {
 | 
				
			||||||
 | 
					        if let Some(count) = self.in_template_string_expression_open_count.last_mut() {
 | 
				
			||||||
 | 
					            *count += 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Decrease the count of open parentheses in the current template string expression.
 | 
				
			||||||
 | 
					    pub fn decrease_template_string_expression_open_paren_count(&mut self) {
 | 
				
			||||||
 | 
					        if let Some(count) = self.in_template_string_expression_open_count.last_mut() {
 | 
				
			||||||
 | 
					            *count = count.saturating_sub(1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Enter a template string expression.
 | 
				
			||||||
 | 
					    pub fn enter_template_string(&mut self) {
 | 
				
			||||||
 | 
					        self.in_template_string_expression_open_count.push(0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Exit a template string expression.
 | 
				
			||||||
 | 
					    pub fn exit_template_string(&mut self) {
 | 
				
			||||||
 | 
					        self.in_template_string_expression_open_count.pop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Check if the iterator is currently in a template string expression.
 | 
				
			||||||
 | 
					    #[must_use]
 | 
				
			||||||
 | 
					    pub fn is_in_template_string_expression(&self) -> Option<bool> {
 | 
				
			||||||
 | 
					        self.in_template_string_expression_open_count
 | 
				
			||||||
 | 
					            .last()
 | 
				
			||||||
 | 
					            .map(|&count| count > 0)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get the number of open parentheses in the current template string expression.
 | 
				
			||||||
 | 
					    #[must_use]
 | 
				
			||||||
 | 
					    pub fn template_string_expression_open_paren_count(&self) -> Option<u32> {
 | 
				
			||||||
 | 
					        self.in_template_string_expression_open_count
 | 
				
			||||||
 | 
					            .last()
 | 
				
			||||||
 | 
					            .copied()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
impl Iterator for SourceIterator<'_> {
 | 
					impl Iterator for SourceIterator<'_> {
 | 
				
			||||||
    type Item = (usize, char);
 | 
					    type Item = (usize, char);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,6 @@ use std::{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::base::{
 | 
					use crate::base::{
 | 
				
			||||||
    self,
 | 
					    self,
 | 
				
			||||||
    log::SourceCodeDisplay,
 | 
					 | 
				
			||||||
    source_file::{SourceElement, SourceIterator, Span},
 | 
					    source_file::{SourceElement, SourceIterator, Span},
 | 
				
			||||||
    Handler,
 | 
					    Handler,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -165,7 +164,7 @@ pub enum Token {
 | 
				
			||||||
    DocComment(DocComment),
 | 
					    DocComment(DocComment),
 | 
				
			||||||
    CommandLiteral(CommandLiteral),
 | 
					    CommandLiteral(CommandLiteral),
 | 
				
			||||||
    StringLiteral(StringLiteral),
 | 
					    StringLiteral(StringLiteral),
 | 
				
			||||||
    MacroStringLiteral(Box<MacroStringLiteral>),
 | 
					    TemplateStringText(TemplateStringLiteralText),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SourceElement for Token {
 | 
					impl SourceElement for Token {
 | 
				
			||||||
| 
						 | 
					@ -181,7 +180,7 @@ 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(),
 | 
					            Self::TemplateStringText(token) => token.span(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -352,80 +351,25 @@ impl SourceElement for StringLiteral {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Represents a hardcoded macro string literal value in the source code.
 | 
					/// Represents a hardcoded template string text value in the source code.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// MacroStringLiteral:
 | 
					/// TemplateStringLiteralText:
 | 
				
			||||||
///   '`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
 | 
					///   TEXT ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[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)]
 | 
					#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
				
			||||||
pub struct MacroStringLiteral {
 | 
					pub struct TemplateStringLiteralText {
 | 
				
			||||||
    /// The backtick that starts the macro string literal.
 | 
					    /// Is the span that makes up the token.
 | 
				
			||||||
    starting_backtick: Punctuation,
 | 
					    pub span: Span,
 | 
				
			||||||
    /// 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 {
 | 
					impl SourceElement for TemplateStringLiteralText {
 | 
				
			||||||
    /// Returns the string content without escapement characters, leading and trailing double quotes.
 | 
					 | 
				
			||||||
    #[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::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 {
 | 
					    fn span(&self) -> Span {
 | 
				
			||||||
        self.starting_backtick
 | 
					        self.span.clone()
 | 
				
			||||||
            .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)]
 | 
				
			||||||
| 
						 | 
					@ -500,101 +444,15 @@ 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, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
 | 
				
			||||||
#[allow(missing_docs)]
 | 
					#[allow(missing_docs)]
 | 
				
			||||||
 | 
					#[expect(missing_copy_implementations)]
 | 
				
			||||||
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.")]
 | 
				
			||||||
    FatalLexicalError,
 | 
					    FatalLexicalError,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[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) {
 | 
				
			||||||
| 
						 | 
					@ -616,26 +474,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 == '_'
 | 
				
			||||||
| 
						 | 
					@ -792,110 +630,84 @@ impl Token {
 | 
				
			||||||
        .into()
 | 
					        .into()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Handles a sequence of characters that are enclosed in backticks and contain macro usages
 | 
					    /// Handles a backticks for opening and closing template strings
 | 
				
			||||||
    fn handle_macro_string_literal(
 | 
					    fn handle_template_string_quotes(iter: &mut SourceIterator, start: usize) -> Self {
 | 
				
			||||||
        iter: &mut SourceIterator,
 | 
					        if iter
 | 
				
			||||||
        mut start: usize,
 | 
					            .is_in_template_string_expression()
 | 
				
			||||||
    ) -> Result<Self, TokenizeError> {
 | 
					            .is_some_and(|last| !last)
 | 
				
			||||||
        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 == '`' {
 | 
					            // in template string text
 | 
				
			||||||
                        return Err(UnclosedMacroUsage {
 | 
					            iter.exit_template_string();
 | 
				
			||||||
                            span: Span::new(iter.source_file().clone(), start, start + 2).unwrap(),
 | 
					        } else {
 | 
				
			||||||
                        }
 | 
					            // outside template string or in expression
 | 
				
			||||||
                        .into());
 | 
					            iter.enter_template_string();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    Self::walk_iter(iter, |c| c != ')' && !Self::is_identifier_character(c));
 | 
					        Punctuation {
 | 
				
			||||||
                    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(Box::new(MacroStringLiteral {
 | 
					 | 
				
			||||||
                starting_backtick,
 | 
					 | 
				
			||||||
                parts,
 | 
					 | 
				
			||||||
                ending_backtick: Punctuation {
 | 
					 | 
				
			||||||
            span: Self::create_span(start, iter),
 | 
					            span: Self::create_span(start, iter),
 | 
				
			||||||
            punctuation: '`',
 | 
					            punctuation: '`',
 | 
				
			||||||
                },
 | 
					        }
 | 
				
			||||||
            })
 | 
					        .into()
 | 
				
			||||||
            .into())
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn handle_template_string_inner(
 | 
				
			||||||
 | 
					        iter: &mut SourceIterator,
 | 
				
			||||||
 | 
					        start: usize,
 | 
				
			||||||
 | 
					        character: char,
 | 
				
			||||||
 | 
					        prev_token: Option<&Self>,
 | 
				
			||||||
 | 
					    ) -> Self {
 | 
				
			||||||
 | 
					        let prev_was_backslash = iter.prev().is_some_and(|(_, c)| c == '\\');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if !prev_was_backslash && character == '$' && iter.peek().is_some_and(|(_, c)| c == '(') {
 | 
				
			||||||
 | 
					            // starts immediately with expression, return punctuation
 | 
				
			||||||
 | 
					            return Punctuation {
 | 
				
			||||||
 | 
					                span: Self::create_span(start, iter),
 | 
				
			||||||
 | 
					                punctuation: '$',
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .into();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match (character, prev_token) {
 | 
				
			||||||
 | 
					            ('(', Some(Self::Punctuation(punc)))
 | 
				
			||||||
 | 
					                if !prev_was_backslash && punc.punctuation == '$' =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Found expression opening parenthesis
 | 
				
			||||||
 | 
					                iter.increase_template_string_expression_open_paren_count();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return Punctuation {
 | 
				
			||||||
 | 
					                    span: Self::create_span(start, iter),
 | 
				
			||||||
 | 
					                    punctuation: '(',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .into();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        loop {
 | 
				
			||||||
 | 
					            if character != '`' {
 | 
				
			||||||
 | 
					                iter.reset_multipeek();
 | 
				
			||||||
 | 
					                Self::walk_iter(iter, |c| !matches!(c, '$' | '`' | '\\'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            iter.reset_multipeek();
 | 
				
			||||||
 | 
					            let first_peek = iter.multipeek().map(|(_, c)| c);
 | 
				
			||||||
 | 
					            let second_peek_open_paren = iter.multipeek().is_some_and(|(_, c)| c == '(');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if first_peek.is_some_and(|c| c == '\\') {
 | 
				
			||||||
 | 
					                iter.next();
 | 
				
			||||||
 | 
					                iter.next();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if character == '`' || first_peek.is_none_or(|c| c == '`') || second_peek_open_paren {
 | 
				
			||||||
 | 
					                // Found expression start, end of text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                break TemplateStringLiteralText {
 | 
				
			||||||
 | 
					                    span: Self::create_span(start, iter),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .into();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            iter.next();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -929,8 +741,13 @@ impl Token {
 | 
				
			||||||
            .next()
 | 
					            .next()
 | 
				
			||||||
            .ok_or(TokenizeError::EndOfSourceCodeIteratorArgument)?;
 | 
					            .ok_or(TokenizeError::EndOfSourceCodeIteratorArgument)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if iter.is_in_template_string_expression().is_some_and(|b| !b) && character != '`' {
 | 
				
			||||||
 | 
					            Ok(Self::handle_template_string_inner(
 | 
				
			||||||
 | 
					                iter, start, character, prev_token,
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        // Found white spaces
 | 
					        // Found white spaces
 | 
				
			||||||
        if character.is_whitespace() {
 | 
					        else if character.is_whitespace() {
 | 
				
			||||||
            Ok(Self::handle_whitespace(iter, start))
 | 
					            Ok(Self::handle_whitespace(iter, start))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // Found identifier/keyword
 | 
					        // Found identifier/keyword
 | 
				
			||||||
| 
						 | 
					@ -947,7 +764,7 @@ impl Token {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // Found macro string literal
 | 
					        // Found macro string literal
 | 
				
			||||||
        else if character == '`' {
 | 
					        else if character == '`' {
 | 
				
			||||||
            Self::handle_macro_string_literal(iter, start)
 | 
					            Ok(Self::handle_template_string_quotes(iter, start))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // Found integer literal
 | 
					        // Found integer literal
 | 
				
			||||||
        else if character.is_ascii_digit() {
 | 
					        else if character.is_ascii_digit() {
 | 
				
			||||||
| 
						 | 
					@ -955,6 +772,12 @@ impl Token {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // Found a punctuation
 | 
					        // Found a punctuation
 | 
				
			||||||
        else if character.is_ascii_punctuation() {
 | 
					        else if character.is_ascii_punctuation() {
 | 
				
			||||||
 | 
					            if character == '(' {
 | 
				
			||||||
 | 
					                iter.increase_template_string_expression_open_paren_count();
 | 
				
			||||||
 | 
					            } else if character == ')' {
 | 
				
			||||||
 | 
					                iter.decrease_template_string_expression_open_paren_count();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok(Punctuation {
 | 
					            Ok(Punctuation {
 | 
				
			||||||
                span: Self::create_span(start, iter),
 | 
					                span: Self::create_span(start, iter),
 | 
				
			||||||
                punctuation: character,
 | 
					                punctuation: character,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,10 +4,13 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    base::{self, source_file::SourceElement as _, Handler},
 | 
					    base::{self, source_file::SourceElement as _, Handler},
 | 
				
			||||||
    lexical::token::{KeywordKind, MacroStringLiteral, MacroStringLiteralPart},
 | 
					    lexical::token::KeywordKind,
 | 
				
			||||||
    syntax::syntax_tree::{
 | 
					    syntax::syntax_tree::{
 | 
				
			||||||
        declaration::{Declaration, Function, FunctionVariableType, ImportItems},
 | 
					        declaration::{Declaration, Function, FunctionVariableType, ImportItems},
 | 
				
			||||||
        expression::{Binary, BinaryOperator, Expression, LuaCode, PrefixOperator, Primary},
 | 
					        expression::{
 | 
				
			||||||
 | 
					            Binary, BinaryOperator, Expression, LuaCode, PrefixOperator, Primary,
 | 
				
			||||||
 | 
					            TemplateStringLiteral, TemplateStringLiteralPart,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        program::{Namespace, ProgramFile},
 | 
					        program::{Namespace, ProgramFile},
 | 
				
			||||||
        statement::{
 | 
					        statement::{
 | 
				
			||||||
            execute_block::{
 | 
					            execute_block::{
 | 
				
			||||||
| 
						 | 
					@ -581,7 +584,7 @@ impl Primary {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Self::Boolean(_) | Self::Integer(_) | Self::StringLiteral(_) => Ok(()),
 | 
					            Self::Boolean(_) | Self::Integer(_) | Self::StringLiteral(_) => Ok(()),
 | 
				
			||||||
            Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler),
 | 
					            Self::TemplateStringLiteral(lit) => lit.analyze_semantics(scope, handler),
 | 
				
			||||||
            Self::FunctionCall(call) => {
 | 
					            Self::FunctionCall(call) => {
 | 
				
			||||||
                let var = scope.get_variable(call.identifier().span.str());
 | 
					                let var = scope.get_variable(call.identifier().span.str());
 | 
				
			||||||
                var.map_or_else(
 | 
					                var.map_or_else(
 | 
				
			||||||
| 
						 | 
					@ -719,7 +722,7 @@ impl Primary {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
            Self::Boolean(_) => expected == ValueType::Boolean,
 | 
					            Self::Boolean(_) => expected == ValueType::Boolean,
 | 
				
			||||||
            Self::Integer(_) => expected == ValueType::Integer,
 | 
					            Self::Integer(_) => expected == ValueType::Integer,
 | 
				
			||||||
            Self::StringLiteral(_) | Self::MacroStringLiteral(_) => {
 | 
					            Self::StringLiteral(_) | Self::TemplateStringLiteral(_) => {
 | 
				
			||||||
                matches!(expected, ValueType::String | ValueType::Boolean)
 | 
					                matches!(expected, ValueType::String | ValueType::Boolean)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Self::FunctionCall(_) => matches!(expected, ValueType::Boolean | ValueType::Integer),
 | 
					            Self::FunctionCall(_) => matches!(expected, ValueType::Boolean | ValueType::Integer),
 | 
				
			||||||
| 
						 | 
					@ -948,12 +951,12 @@ impl AnyStringLiteral {
 | 
				
			||||||
    ) -> Result<(), error::Error> {
 | 
					    ) -> Result<(), error::Error> {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
            Self::StringLiteral(_) => Ok(()),
 | 
					            Self::StringLiteral(_) => Ok(()),
 | 
				
			||||||
            Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler),
 | 
					            Self::TemplateStringLiteral(lit) => lit.analyze_semantics(scope, handler),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MacroStringLiteral {
 | 
					impl TemplateStringLiteral {
 | 
				
			||||||
    fn analyze_semantics(
 | 
					    fn analyze_semantics(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        scope: &SemanticScope,
 | 
					        scope: &SemanticScope,
 | 
				
			||||||
| 
						 | 
					@ -963,16 +966,20 @@ impl MacroStringLiteral {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for part in self.parts() {
 | 
					        for part in self.parts() {
 | 
				
			||||||
            match part {
 | 
					            match part {
 | 
				
			||||||
                MacroStringLiteralPart::MacroUsage { identifier, .. } => {
 | 
					                TemplateStringLiteralPart::Expression { expression, .. } => {
 | 
				
			||||||
 | 
					                    match expression.as_ref() {
 | 
				
			||||||
 | 
					                        Expression::Primary(Primary::Identifier(identifier)) => {
 | 
				
			||||||
                            if let Some(variable_type) = scope.get_variable(identifier.span.str()) {
 | 
					                            if let Some(variable_type) = scope.get_variable(identifier.span.str()) {
 | 
				
			||||||
                        if variable_type != VariableType::MacroParameter {
 | 
					                                // TODO: template string correct checks
 | 
				
			||||||
                            let err =
 | 
					                                // if variable_type != VariableType::MacroParameter {
 | 
				
			||||||
                                error::Error::UnexpectedExpression(UnexpectedExpression(Box::new(
 | 
					                                //     let err = error::Error::UnexpectedExpression(UnexpectedExpression(
 | 
				
			||||||
                                    Expression::Primary(Primary::Identifier(identifier.clone())),
 | 
					                                //         Box::new(Expression::Primary(Primary::Identifier(
 | 
				
			||||||
                                )));
 | 
					                                //             identifier.clone(),
 | 
				
			||||||
                            handler.receive(err.clone());
 | 
					                                //         ))),
 | 
				
			||||||
                            errs.push(err);
 | 
					                                //     ));
 | 
				
			||||||
                        }
 | 
					                                //     handler.receive(err.clone());
 | 
				
			||||||
 | 
					                                //     errs.push(err);
 | 
				
			||||||
 | 
					                                // }
 | 
				
			||||||
                            } else {
 | 
					                            } else {
 | 
				
			||||||
                                let err = error::Error::UnknownIdentifier(UnknownIdentifier {
 | 
					                                let err = error::Error::UnknownIdentifier(UnknownIdentifier {
 | 
				
			||||||
                                    identifier: identifier.span.clone(),
 | 
					                                    identifier: identifier.span.clone(),
 | 
				
			||||||
| 
						 | 
					@ -981,7 +988,54 @@ impl MacroStringLiteral {
 | 
				
			||||||
                                errs.push(err);
 | 
					                                errs.push(err);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                MacroStringLiteralPart::Text(_) => {}
 | 
					                        Expression::Primary(Primary::Indexed(indexed)) => {
 | 
				
			||||||
 | 
					                            if let Primary::Identifier(identifier) = indexed.object().as_ref() {
 | 
				
			||||||
 | 
					                                if let Some(variable_type) =
 | 
				
			||||||
 | 
					                                    scope.get_variable(identifier.span.str())
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    match variable_type {
 | 
				
			||||||
 | 
					                                        VariableType::BooleanStorageArray
 | 
				
			||||||
 | 
					                                        | VariableType::ScoreboardArray
 | 
				
			||||||
 | 
					                                        | VariableType::Tag
 | 
				
			||||||
 | 
					                                        | VariableType::Scoreboard => {
 | 
				
			||||||
 | 
					                                            // Valid types
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                        _ => {
 | 
				
			||||||
 | 
					                                            let err = error::Error::UnexpectedExpression(
 | 
				
			||||||
 | 
					                                                UnexpectedExpression(expression.clone()),
 | 
				
			||||||
 | 
					                                            );
 | 
				
			||||||
 | 
					                                            handler.receive(err.clone());
 | 
				
			||||||
 | 
					                                            errs.push(err);
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                } else {
 | 
				
			||||||
 | 
					                                    let err = error::Error::UnknownIdentifier(UnknownIdentifier {
 | 
				
			||||||
 | 
					                                        identifier: identifier.span.clone(),
 | 
				
			||||||
 | 
					                                    });
 | 
				
			||||||
 | 
					                                    handler.receive(err.clone());
 | 
				
			||||||
 | 
					                                    errs.push(err);
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                let err = error::Error::UnexpectedExpression(UnexpectedExpression(
 | 
				
			||||||
 | 
					                                    expression.clone(),
 | 
				
			||||||
 | 
					                                ));
 | 
				
			||||||
 | 
					                                handler.receive(err.clone());
 | 
				
			||||||
 | 
					                                errs.push(err);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            if let Err(err) = indexed.index().analyze_semantics(scope, handler) {
 | 
				
			||||||
 | 
					                                errs.push(err);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        _ => {
 | 
				
			||||||
 | 
					                            let err = error::Error::UnexpectedExpression(UnexpectedExpression(
 | 
				
			||||||
 | 
					                                expression.clone(),
 | 
				
			||||||
 | 
					                            ));
 | 
				
			||||||
 | 
					                            handler.receive(err.clone());
 | 
				
			||||||
 | 
					                            errs.push(err);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                TemplateStringLiteralPart::Text(_) => {}
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/serde.rs
								
								
								
								
							
							
						
						
									
										10
									
								
								src/serde.rs
								
								
								
								
							| 
						 | 
					@ -275,14 +275,8 @@ mod tests {
 | 
				
			||||||
            serde_json::from_str::<SerdeWrapper<ProgramFile>>(&serialized).unwrap();
 | 
					            serde_json::from_str::<SerdeWrapper<ProgramFile>>(&serialized).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            Arc::as_ptr(
 | 
					            Arc::as_ptr(deserialized.namespace().keyword().span.source_file()),
 | 
				
			||||||
                deserialized
 | 
					            Arc::as_ptr(deserialized.namespace().name().span.source_file())
 | 
				
			||||||
                    .namespace()
 | 
					 | 
				
			||||||
                    .namespace_keyword()
 | 
					 | 
				
			||||||
                    .span
 | 
					 | 
				
			||||||
                    .source_file()
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            Arc::as_ptr(deserialized.namespace().namespace_name().span.source_file())
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,7 @@ pub enum SyntaxKind {
 | 
				
			||||||
    Integer,
 | 
					    Integer,
 | 
				
			||||||
    Boolean,
 | 
					    Boolean,
 | 
				
			||||||
    StringLiteral,
 | 
					    StringLiteral,
 | 
				
			||||||
    MacroStringLiteral,
 | 
					    TemplateStringLiteralPart,
 | 
				
			||||||
    AnyStringLiteral,
 | 
					    AnyStringLiteral,
 | 
				
			||||||
    Statement,
 | 
					    Statement,
 | 
				
			||||||
    Expression,
 | 
					    Expression,
 | 
				
			||||||
| 
						 | 
					@ -76,8 +76,8 @@ impl SyntaxKind {
 | 
				
			||||||
            Self::Integer => "an integer token".to_string(),
 | 
					            Self::Integer => "an integer token".to_string(),
 | 
				
			||||||
            Self::Boolean => "a boolean token".to_string(),
 | 
					            Self::Boolean => "a boolean 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::TemplateStringLiteralPart => "part of a template string literal".to_string(),
 | 
				
			||||||
            Self::AnyStringLiteral => "a (macro) string literal".to_string(),
 | 
					            Self::AnyStringLiteral => "a (template) 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::Operator => "an operator".to_string(),
 | 
					            Self::Operator => "an operator".to_string(),
 | 
				
			||||||
| 
						 | 
					@ -116,7 +116,7 @@ impl Display for UnexpectedSyntax {
 | 
				
			||||||
            Some(Token::Boolean(..)) => "a boolean token".to_string(),
 | 
					            Some(Token::Boolean(..)) => "a boolean 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(),
 | 
					            Some(Token::TemplateStringText(..)) => "a template string text token".to_string(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            None => "EOF".to_string(),
 | 
					            None => "EOF".to_string(),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,18 +6,12 @@ use enum_as_inner::EnumAsInner;
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    base::{self, Handler},
 | 
					    base::{self, Handler},
 | 
				
			||||||
    lexical::{
 | 
					    lexical::{
 | 
				
			||||||
        token::{
 | 
					        token::{Identifier, Integer, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
 | 
				
			||||||
            Identifier, Integer, Keyword, KeywordKind, MacroStringLiteral, 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 Frame<'_> {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// 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(Box::new(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(Box::new(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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -344,7 +344,6 @@ impl Parser<'_> {
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Errors
 | 
					    /// # Errors
 | 
				
			||||||
    /// - cannot parse declaration from current position
 | 
					    /// - cannot parse declaration from current position
 | 
				
			||||||
    #[expect(clippy::too_many_lines)]
 | 
					 | 
				
			||||||
    #[tracing::instrument(level = "trace", skip_all)]
 | 
					    #[tracing::instrument(level = "trace", skip_all)]
 | 
				
			||||||
    pub fn parse_declaration(
 | 
					    pub fn parse_declaration(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,15 +13,16 @@ use crate::{
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    lexical::{
 | 
					    lexical::{
 | 
				
			||||||
        token::{
 | 
					        token::{
 | 
				
			||||||
            Boolean, Identifier, Integer, Keyword, KeywordKind, MacroStringLiteral, Punctuation,
 | 
					            Boolean, Identifier, Integer, Keyword, KeywordKind, Punctuation, StringLiteral,
 | 
				
			||||||
            StringLiteral, Token,
 | 
					            TemplateStringLiteralText, Token,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        token_stream::Delimiter,
 | 
					        token_stream::Delimiter,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    syntax::{
 | 
					    syntax::{
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        error::{Error, ParseResult, UnexpectedSyntax},
 | 
					        error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
 | 
				
			||||||
        parser::{Parser, Reading},
 | 
					        parser::{Parser, Reading},
 | 
				
			||||||
 | 
					        syntax_tree::AnyStringLiteral,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -177,7 +178,7 @@ impl SourceElement for Expression {
 | 
				
			||||||
///     | StringLiteral
 | 
					///     | StringLiteral
 | 
				
			||||||
///     | FunctionCall
 | 
					///     | FunctionCall
 | 
				
			||||||
///     | MemberAccess
 | 
					///     | MemberAccess
 | 
				
			||||||
///     | MacroStringLiteral
 | 
					///     | TemplateStringLiteral
 | 
				
			||||||
///     | LuaCode
 | 
					///     | LuaCode
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[allow(missing_docs)]
 | 
					#[allow(missing_docs)]
 | 
				
			||||||
| 
						 | 
					@ -193,7 +194,7 @@ pub enum Primary {
 | 
				
			||||||
    StringLiteral(StringLiteral),
 | 
					    StringLiteral(StringLiteral),
 | 
				
			||||||
    FunctionCall(FunctionCall),
 | 
					    FunctionCall(FunctionCall),
 | 
				
			||||||
    MemberAccess(MemberAccess),
 | 
					    MemberAccess(MemberAccess),
 | 
				
			||||||
    MacroStringLiteral(MacroStringLiteral),
 | 
					    TemplateStringLiteral(TemplateStringLiteral),
 | 
				
			||||||
    Lua(Box<LuaCode>),
 | 
					    Lua(Box<LuaCode>),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -209,7 +210,7 @@ impl SourceElement for Primary {
 | 
				
			||||||
            Self::StringLiteral(string_literal) => string_literal.span(),
 | 
					            Self::StringLiteral(string_literal) => string_literal.span(),
 | 
				
			||||||
            Self::FunctionCall(function_call) => function_call.span(),
 | 
					            Self::FunctionCall(function_call) => function_call.span(),
 | 
				
			||||||
            Self::MemberAccess(member_access) => member_access.span(),
 | 
					            Self::MemberAccess(member_access) => member_access.span(),
 | 
				
			||||||
            Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
 | 
					            Self::TemplateStringLiteral(template_string_literal) => template_string_literal.span(),
 | 
				
			||||||
            Self::Lua(lua_code) => lua_code.span(),
 | 
					            Self::Lua(lua_code) => lua_code.span(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -391,6 +392,70 @@ impl SourceElement for FunctionCall {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Represents a hardcoded template string literal value in the source code.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// ```ebnf
 | 
				
			||||||
 | 
					/// TemplateStringLiteral:
 | 
				
			||||||
 | 
					///   '`' ( TemplateStringLiteralText | '$(' Expression ')' )* '`';
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
				
			||||||
 | 
					pub struct TemplateStringLiteral {
 | 
				
			||||||
 | 
					    /// The backtick that starts the template string literal.
 | 
				
			||||||
 | 
					    pub(crate) starting_backtick: Punctuation,
 | 
				
			||||||
 | 
					    /// The parts that make up the template string literal.
 | 
				
			||||||
 | 
					    pub(crate) parts: Vec<TemplateStringLiteralPart>,
 | 
				
			||||||
 | 
					    /// The backtick that ends the template string literal.
 | 
				
			||||||
 | 
					    pub(crate) ending_backtick: Punctuation,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl TemplateStringLiteral {
 | 
				
			||||||
 | 
					    /// Returns the parts that make up the template string literal.
 | 
				
			||||||
 | 
					    #[must_use]
 | 
				
			||||||
 | 
					    pub fn parts(&self) -> &[TemplateStringLiteralPart] {
 | 
				
			||||||
 | 
					        &self.parts
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl SourceElement for TemplateStringLiteral {
 | 
				
			||||||
 | 
					    fn span(&self) -> Span {
 | 
				
			||||||
 | 
					        self.starting_backtick
 | 
				
			||||||
 | 
					            .span
 | 
				
			||||||
 | 
					            .join(&self.ending_backtick.span)
 | 
				
			||||||
 | 
					            .expect("Invalid template string literal span")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Represents a part of a template 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 TemplateStringLiteralPart {
 | 
				
			||||||
 | 
					    Text(TemplateStringLiteralText),
 | 
				
			||||||
 | 
					    Expression {
 | 
				
			||||||
 | 
					        dollar: Punctuation,
 | 
				
			||||||
 | 
					        open_brace: Punctuation,
 | 
				
			||||||
 | 
					        expression: Box<Expression>,
 | 
				
			||||||
 | 
					        close_brace: Punctuation,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl SourceElement for TemplateStringLiteralPart {
 | 
				
			||||||
 | 
					    fn span(&self) -> Span {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Self::Text(text) => text.span(),
 | 
				
			||||||
 | 
					            Self::Expression {
 | 
				
			||||||
 | 
					                dollar,
 | 
				
			||||||
 | 
					                close_brace,
 | 
				
			||||||
 | 
					                ..
 | 
				
			||||||
 | 
					            } => dollar
 | 
				
			||||||
 | 
					                .span()
 | 
				
			||||||
 | 
					                .join(&close_brace.span())
 | 
				
			||||||
 | 
					                .expect("Invalid template string literal part span"),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Represents a lua code block in the syntax tree.
 | 
					/// Represents a lua code block in the syntax tree.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
| 
						 | 
					@ -589,6 +654,13 @@ impl Parser<'_> {
 | 
				
			||||||
                }))
 | 
					                }))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // template string literal
 | 
				
			||||||
 | 
					            Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '`' => {
 | 
				
			||||||
 | 
					                let template_string_literal = self.parse_template_string_literal(handler)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Ok(Primary::TemplateStringLiteral(template_string_literal))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // parenthesized expression
 | 
					            // parenthesized expression
 | 
				
			||||||
            Reading::IntoDelimited(left_parenthesis) if left_parenthesis.punctuation == '(' => self
 | 
					            Reading::IntoDelimited(left_parenthesis) if left_parenthesis.punctuation == '(' => self
 | 
				
			||||||
                .parse_parenthesized(handler)
 | 
					                .parse_parenthesized(handler)
 | 
				
			||||||
| 
						 | 
					@ -661,14 +733,6 @@ impl Parser<'_> {
 | 
				
			||||||
                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 =>
 | 
				
			||||||
| 
						 | 
					@ -843,4 +907,88 @@ impl Parser<'_> {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Expects the next [`Token`] to be an [`TemplateStringLiteral`], and returns it.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    /// If the next [`Token`] is not an [`TemplateStringLiteral`].
 | 
				
			||||||
 | 
					    pub fn parse_template_string_literal(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        handler: &impl Handler<base::Error>,
 | 
				
			||||||
 | 
					    ) -> ParseResult<TemplateStringLiteral> {
 | 
				
			||||||
 | 
					        let starting_backtick = self.parse_punctuation('`', true, handler)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut parts = Vec::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        loop {
 | 
				
			||||||
 | 
					            match self.stop_at_significant() {
 | 
				
			||||||
 | 
					                Reading::Atomic(Token::Punctuation(ending_backtick))
 | 
				
			||||||
 | 
					                    if ending_backtick.punctuation == '`' =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    self.forward();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // closing tick
 | 
				
			||||||
 | 
					                    return Ok(TemplateStringLiteral {
 | 
				
			||||||
 | 
					                        starting_backtick,
 | 
				
			||||||
 | 
					                        parts,
 | 
				
			||||||
 | 
					                        ending_backtick,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Reading::Atomic(Token::Punctuation(dollar)) if dollar.punctuation == '$' => {
 | 
				
			||||||
 | 
					                    self.forward();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    let delimited_expression = self.step_into(
 | 
				
			||||||
 | 
					                        Delimiter::Parenthesis,
 | 
				
			||||||
 | 
					                        |parser| parser.parse_expression(handler),
 | 
				
			||||||
 | 
					                        handler,
 | 
				
			||||||
 | 
					                    )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    parts.push(TemplateStringLiteralPart::Expression {
 | 
				
			||||||
 | 
					                        dollar,
 | 
				
			||||||
 | 
					                        open_brace: delimited_expression.open,
 | 
				
			||||||
 | 
					                        expression: Box::new(delimited_expression.tree?),
 | 
				
			||||||
 | 
					                        close_brace: delimited_expression.close,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Reading::Atomic(Token::TemplateStringText(text)) => {
 | 
				
			||||||
 | 
					                    self.forward();
 | 
				
			||||||
 | 
					                    parts.push(TemplateStringLiteralPart::Text(text));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                unexpected => {
 | 
				
			||||||
 | 
					                    let err = Error::UnexpectedSyntax(UnexpectedSyntax {
 | 
				
			||||||
 | 
					                        expected: syntax::error::SyntaxKind::TemplateStringLiteralPart,
 | 
				
			||||||
 | 
					                        found: unexpected.into_token(),
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    handler.receive(Box::new(err.clone()));
 | 
				
			||||||
 | 
					                    return 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::Punctuation(punc)) if punc.punctuation == '`' => self
 | 
				
			||||||
 | 
					                .parse_template_string_literal(handler)
 | 
				
			||||||
 | 
					                .map(AnyStringLiteral::TemplateStringLiteral),
 | 
				
			||||||
 | 
					            found => {
 | 
				
			||||||
 | 
					                let err = Error::UnexpectedSyntax(UnexpectedSyntax {
 | 
				
			||||||
 | 
					                    expected: SyntaxKind::AnyStringLiteral,
 | 
				
			||||||
 | 
					                    found: found.into_token(),
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                handler.receive(Box::new(err.clone()));
 | 
				
			||||||
 | 
					                Err(err)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,10 +12,10 @@ use crate::{
 | 
				
			||||||
        Handler, VoidHandler,
 | 
					        Handler, VoidHandler,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    lexical::{
 | 
					    lexical::{
 | 
				
			||||||
        token::{Identifier, MacroStringLiteral, Punctuation, StringLiteral, Token},
 | 
					        token::{Identifier, Punctuation, StringLiteral, Token},
 | 
				
			||||||
        token_stream::Delimiter,
 | 
					        token_stream::Delimiter,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    syntax::parser::Reading,
 | 
					    syntax::{parser::Reading, syntax_tree::expression::TemplateStringLiteral},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::{
 | 
					use super::{
 | 
				
			||||||
| 
						 | 
					@ -69,25 +69,25 @@ 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.
 | 
					/// Represents a syntax tree node that can be either a string literal or a template string literal.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// Syntax Synopsis:
 | 
					/// Syntax Synopsis:
 | 
				
			||||||
/// ```ebnf
 | 
					/// ```ebnf
 | 
				
			||||||
/// AnyStringLiteral: StringLiteral | MacroStringLiteral ;
 | 
					/// AnyStringLiteral: StringLiteral | TemplateStringLiteral ;
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[allow(missing_docs)]
 | 
					#[allow(missing_docs)]
 | 
				
			||||||
#[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, From)]
 | 
					#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From)]
 | 
				
			||||||
pub enum AnyStringLiteral {
 | 
					pub enum AnyStringLiteral {
 | 
				
			||||||
    StringLiteral(StringLiteral),
 | 
					    StringLiteral(StringLiteral),
 | 
				
			||||||
    MacroStringLiteral(MacroStringLiteral),
 | 
					    TemplateStringLiteral(TemplateStringLiteral),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SourceElement for AnyStringLiteral {
 | 
					impl SourceElement for AnyStringLiteral {
 | 
				
			||||||
    fn span(&self) -> Span {
 | 
					    fn span(&self) -> Span {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
            Self::StringLiteral(string_literal) => string_literal.span(),
 | 
					            Self::StringLiteral(string_literal) => string_literal.span(),
 | 
				
			||||||
            Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
 | 
					            Self::TemplateStringLiteral(template_string_literal) => template_string_literal.span(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1010,7 +1010,6 @@ impl Parser<'_> {
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Errors
 | 
					    /// # Errors
 | 
				
			||||||
    /// - cannot parse variable declaration from current position
 | 
					    /// - cannot parse variable declaration from current position
 | 
				
			||||||
    #[expect(clippy::too_many_lines)]
 | 
					 | 
				
			||||||
    #[tracing::instrument(level = "trace", skip_all)]
 | 
					    #[tracing::instrument(level = "trace", skip_all)]
 | 
				
			||||||
    pub fn parse_variable_declaration(
 | 
					    pub fn parse_variable_declaration(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,16 @@
 | 
				
			||||||
//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
 | 
					//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::{borrow::Cow, sync::Arc};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use shulkerbox::util::{MacroString as ExtMacroString, MacroStringPart as ExtMacroStringPart};
 | 
					use shulkerbox::util::{MacroString as ExtMacroString, MacroStringPart as ExtMacroStringPart};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{lexical::token::MacroStringLiteral, syntax::syntax_tree::AnyStringLiteral};
 | 
					use crate::{
 | 
				
			||||||
 | 
					    base::{self, Handler},
 | 
				
			||||||
 | 
					    semantic::error::UnexpectedExpression,
 | 
				
			||||||
 | 
					    syntax::syntax_tree::expression::{TemplateStringLiteral, TemplateStringLiteralPart},
 | 
				
			||||||
 | 
					    transpile::{Scope, TranspileError, TranspileResult},
 | 
				
			||||||
 | 
					    util,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::util::{MacroString, MacroStringPart};
 | 
					use super::util::{MacroString, MacroStringPart};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,26 +34,38 @@ impl From<MacroStringPart> for ExtMacroStringPart {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<&AnyStringLiteral> for ExtMacroString {
 | 
					impl TemplateStringLiteral {
 | 
				
			||||||
    fn from(value: &AnyStringLiteral) -> Self {
 | 
					    pub fn as_str(
 | 
				
			||||||
        Self::from(MacroString::from(value))
 | 
					        &self,
 | 
				
			||||||
    }
 | 
					        scope: &Arc<Scope>,
 | 
				
			||||||
}
 | 
					        handler: &impl Handler<base::Error>,
 | 
				
			||||||
 | 
					    ) -> TranspileResult<Cow<'_, str>> {
 | 
				
			||||||
 | 
					        let mut res = Cow::Borrowed("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<AnyStringLiteral> for ExtMacroString {
 | 
					        for part in &self.parts {
 | 
				
			||||||
    fn from(value: AnyStringLiteral) -> Self {
 | 
					            match part {
 | 
				
			||||||
        Self::from(&value)
 | 
					                TemplateStringLiteralPart::Text(s) => {
 | 
				
			||||||
 | 
					                    let s = util::unescape_template_string(s.span.str());
 | 
				
			||||||
 | 
					                    if res.is_empty() {
 | 
				
			||||||
 | 
					                        res = s;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        res.to_mut().push_str(&s);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                TemplateStringLiteralPart::Expression { expression, .. } => {
 | 
				
			||||||
 | 
					                    let compiled = expression.comptime_eval(scope, handler)?;
 | 
				
			||||||
 | 
					                    let s = compiled.to_string_no_macro().ok_or_else(|| {
 | 
				
			||||||
 | 
					                        let err = TranspileError::UnexpectedExpression(UnexpectedExpression(
 | 
				
			||||||
 | 
					                            expression.clone(),
 | 
				
			||||||
 | 
					                        ));
 | 
				
			||||||
 | 
					                        handler.receive(Box::new(err.clone()));
 | 
				
			||||||
 | 
					                        err
 | 
				
			||||||
 | 
					                    })?;
 | 
				
			||||||
 | 
					                    res.to_mut().push_str(&s);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<&MacroStringLiteral> for ExtMacroString {
 | 
					        Ok(res)
 | 
				
			||||||
    fn from(value: &MacroStringLiteral) -> Self {
 | 
					 | 
				
			||||||
        Self::from(MacroString::from(value))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<MacroStringLiteral> for ExtMacroString {
 | 
					 | 
				
			||||||
    fn from(value: MacroStringLiteral) -> Self {
 | 
					 | 
				
			||||||
        Self::from(&value)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,7 @@ use super::{
 | 
				
			||||||
#[cfg(feature = "shulkerbox")]
 | 
					#[cfg(feature = "shulkerbox")]
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    base::{self, source_file::SourceElement, Handler, VoidHandler},
 | 
					    base::{self, source_file::SourceElement, Handler, VoidHandler},
 | 
				
			||||||
    lexical::token::{Identifier, MacroStringLiteralPart, StringLiteral},
 | 
					    lexical::token::{Identifier, StringLiteral},
 | 
				
			||||||
    syntax::syntax_tree::expression::{
 | 
					    syntax::syntax_tree::expression::{
 | 
				
			||||||
        Binary, BinaryOperator, Expression, Indexed, MemberAccess, Parenthesized, PrefixOperator,
 | 
					        Binary, BinaryOperator, Expression, Indexed, MemberAccess, Parenthesized, PrefixOperator,
 | 
				
			||||||
        Primary,
 | 
					        Primary,
 | 
				
			||||||
| 
						 | 
					@ -337,7 +337,7 @@ impl Primary {
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Self::StringLiteral(_) | Self::MacroStringLiteral(_) => {
 | 
					            Self::StringLiteral(_) | Self::TemplateStringLiteral(_) => {
 | 
				
			||||||
                matches!(r#type, ValueType::String | ValueType::Boolean)
 | 
					                matches!(r#type, ValueType::String | ValueType::Boolean)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -412,17 +412,29 @@ impl Primary {
 | 
				
			||||||
                    expression: lua.span(),
 | 
					                    expression: lua.span(),
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
                .and_then(|val| val),
 | 
					                .and_then(|val| val),
 | 
				
			||||||
            Self::MacroStringLiteral(macro_string_literal) => {
 | 
					            Self::TemplateStringLiteral(template_string_literal) => {
 | 
				
			||||||
                if macro_string_literal
 | 
					                use crate::syntax::syntax_tree::expression::TemplateStringLiteralPart;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if template_string_literal
 | 
				
			||||||
                    .parts()
 | 
					                    .parts()
 | 
				
			||||||
                    .iter()
 | 
					                    .iter()
 | 
				
			||||||
                    .any(|part| matches!(part, MacroStringLiteralPart::MacroUsage { .. }))
 | 
					                    .any(|part| matches!(part, TemplateStringLiteralPart::Expression { .. }))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    Ok(ComptimeValue::MacroString(
 | 
					                    template_string_literal
 | 
				
			||||||
                        macro_string_literal.clone().into(),
 | 
					                        .to_macro_string(scope, handler)
 | 
				
			||||||
                    ))
 | 
					                        .map(ComptimeValue::MacroString)
 | 
				
			||||||
 | 
					                        .map_err(|_| NotComptime {
 | 
				
			||||||
 | 
					                            expression: template_string_literal.span(),
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    Ok(ComptimeValue::String(macro_string_literal.str_content()))
 | 
					                    Ok(ComptimeValue::String(
 | 
				
			||||||
 | 
					                        template_string_literal
 | 
				
			||||||
 | 
					                            .as_str(scope, handler)
 | 
				
			||||||
 | 
					                            .map_err(|_| NotComptime {
 | 
				
			||||||
 | 
					                                expression: template_string_literal.span(),
 | 
				
			||||||
 | 
					                            })?
 | 
				
			||||||
 | 
					                            .into_owned(),
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -446,7 +458,7 @@ impl Primary {
 | 
				
			||||||
            | Self::FunctionCall(_)
 | 
					            | Self::FunctionCall(_)
 | 
				
			||||||
            | Self::Integer(_)
 | 
					            | Self::Integer(_)
 | 
				
			||||||
            | Self::Lua(_)
 | 
					            | Self::Lua(_)
 | 
				
			||||||
            | Self::MacroStringLiteral(_)
 | 
					            | Self::TemplateStringLiteral(_)
 | 
				
			||||||
            | Self::MemberAccess(_)
 | 
					            | Self::MemberAccess(_)
 | 
				
			||||||
            | Self::Prefix(_) => Err(NotComptime {
 | 
					            | Self::Prefix(_) => Err(NotComptime {
 | 
				
			||||||
                expression: self.span(),
 | 
					                expression: self.span(),
 | 
				
			||||||
| 
						 | 
					@ -476,7 +488,7 @@ impl Primary {
 | 
				
			||||||
            | Self::FunctionCall(_)
 | 
					            | Self::FunctionCall(_)
 | 
				
			||||||
            | Self::Integer(_)
 | 
					            | Self::Integer(_)
 | 
				
			||||||
            | Self::Lua(_)
 | 
					            | Self::Lua(_)
 | 
				
			||||||
            | Self::MacroStringLiteral(_)
 | 
					            | Self::TemplateStringLiteral(_)
 | 
				
			||||||
            | Self::MemberAccess(_)
 | 
					            | Self::MemberAccess(_)
 | 
				
			||||||
            | Self::Prefix(_) => false,
 | 
					            | Self::Prefix(_) => false,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -1260,7 +1272,7 @@ impl Transpiler {
 | 
				
			||||||
                    Err(err)
 | 
					                    Err(err)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Primary::StringLiteral(_) | Primary::MacroStringLiteral(_) => {
 | 
					            Primary::StringLiteral(_) | Primary::TemplateStringLiteral(_) => {
 | 
				
			||||||
                if matches!(
 | 
					                if matches!(
 | 
				
			||||||
                    target,
 | 
					                    target,
 | 
				
			||||||
                    DataLocation::Storage {
 | 
					                    DataLocation::Storage {
 | 
				
			||||||
| 
						 | 
					@ -1662,9 +1674,11 @@ impl Transpiler {
 | 
				
			||||||
                Vec::new(),
 | 
					                Vec::new(),
 | 
				
			||||||
                ExtendedCondition::Runtime(Condition::Atom(s.str_content().to_string().into())),
 | 
					                ExtendedCondition::Runtime(Condition::Atom(s.str_content().to_string().into())),
 | 
				
			||||||
            )),
 | 
					            )),
 | 
				
			||||||
            Primary::MacroStringLiteral(macro_string) => Ok((
 | 
					            Primary::TemplateStringLiteral(template_string) => Ok((
 | 
				
			||||||
                Vec::new(),
 | 
					                Vec::new(),
 | 
				
			||||||
                ExtendedCondition::Runtime(Condition::Atom(macro_string.into())),
 | 
					                ExtendedCondition::Runtime(Condition::Atom(
 | 
				
			||||||
 | 
					                    template_string.to_macro_string(scope, handler)?.into(),
 | 
				
			||||||
 | 
					                )),
 | 
				
			||||||
            )),
 | 
					            )),
 | 
				
			||||||
            Primary::FunctionCall(func) => {
 | 
					            Primary::FunctionCall(func) => {
 | 
				
			||||||
                if func
 | 
					                if func
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -448,8 +448,8 @@ impl Transpiler {
 | 
				
			||||||
                            Expression::Primary(Primary::StringLiteral(string)) => {
 | 
					                            Expression::Primary(Primary::StringLiteral(string)) => {
 | 
				
			||||||
                                Ok(Parameter::Static(string.str_content().to_string().into()))
 | 
					                                Ok(Parameter::Static(string.str_content().to_string().into()))
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            Expression::Primary(Primary::MacroStringLiteral(literal)) => {
 | 
					                            Expression::Primary(Primary::TemplateStringLiteral(literal)) => {
 | 
				
			||||||
                                Ok(Parameter::Static(literal.into()))
 | 
					                                Ok(Parameter::Static(literal.to_macro_string(scope, handler)?))
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            Expression::Primary(primary @ Primary::Identifier(ident)) => {
 | 
					                            Expression::Primary(primary @ Primary::Identifier(ident)) => {
 | 
				
			||||||
                                let var =
 | 
					                                let var =
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,11 +12,13 @@ use serde_json::{json, Value as JsonValue};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    base::{source_file::SourceElement as _, VoidHandler},
 | 
					    base::{source_file::SourceElement as _, VoidHandler},
 | 
				
			||||||
    lexical::token::{Identifier, MacroStringLiteralPart},
 | 
					    lexical::token::Identifier,
 | 
				
			||||||
    semantic::error::{InvalidFunctionArguments, UnexpectedExpression},
 | 
					    semantic::error::{InvalidFunctionArguments, UnexpectedExpression},
 | 
				
			||||||
    syntax::syntax_tree::expression::{Expression, FunctionCall, Primary},
 | 
					    syntax::syntax_tree::expression::{
 | 
				
			||||||
 | 
					        Expression, FunctionCall, Primary, TemplateStringLiteralPart,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    transpile::{
 | 
					    transpile::{
 | 
				
			||||||
        error::{IllegalIndexing, IllegalIndexingReason, UnknownIdentifier},
 | 
					        error::{IllegalIndexing, IllegalIndexingReason, NotComptime, UnknownIdentifier},
 | 
				
			||||||
        expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
 | 
					        expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
 | 
				
			||||||
        util::MacroString,
 | 
					        util::MacroString,
 | 
				
			||||||
        TranspileError,
 | 
					        TranspileError,
 | 
				
			||||||
| 
						 | 
					@ -95,7 +97,6 @@ fn print_function(
 | 
				
			||||||
) -> TranspileResult<Vec<Command>> {
 | 
					) -> TranspileResult<Vec<Command>> {
 | 
				
			||||||
    const PARAM_COLOR: &str = "gray";
 | 
					    const PARAM_COLOR: &str = "gray";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[expect(clippy::option_if_let_else)]
 | 
					 | 
				
			||||||
    fn get_identifier_part(
 | 
					    fn get_identifier_part(
 | 
				
			||||||
        ident: &Identifier,
 | 
					        ident: &Identifier,
 | 
				
			||||||
        transpiler: &mut Transpiler,
 | 
					        transpiler: &mut Transpiler,
 | 
				
			||||||
| 
						 | 
					@ -131,6 +132,158 @@ fn print_function(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    Ok((false, cmd, value))
 | 
					                    Ok((false, cmd, value))
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                VariableData::ComptimeValue { value, .. } => {
 | 
				
			||||||
 | 
					                    let value = {
 | 
				
			||||||
 | 
					                        let guard = value.read().map_err(|_| {
 | 
				
			||||||
 | 
					                            TranspileError::NotComptime(NotComptime {
 | 
				
			||||||
 | 
					                                expression: ident.span(),
 | 
				
			||||||
 | 
					                            })
 | 
				
			||||||
 | 
					                        })?;
 | 
				
			||||||
 | 
					                        guard.as_ref().map_or_else(
 | 
				
			||||||
 | 
					                            || "null".into(),
 | 
				
			||||||
 | 
					                            super::expression::ComptimeValue::to_macro_string,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Ok((
 | 
				
			||||||
 | 
					                        value.contains_macros(),
 | 
				
			||||||
 | 
					                        None,
 | 
				
			||||||
 | 
					                        json!({"text": value.to_string(), "color": PARAM_COLOR}),
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                _ => Err(TranspileError::UnexpectedExpression(UnexpectedExpression(
 | 
				
			||||||
 | 
					                    Box::new(Expression::Primary(Primary::Identifier(ident.to_owned()))),
 | 
				
			||||||
 | 
					                ))),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Err(TranspileError::UnknownIdentifier(UnknownIdentifier {
 | 
				
			||||||
 | 
					                identifier: ident.span(),
 | 
				
			||||||
 | 
					            }))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_indexed_part(
 | 
				
			||||||
 | 
					        ident: &Identifier,
 | 
				
			||||||
 | 
					        index: &Expression,
 | 
				
			||||||
 | 
					        transpiler: &mut Transpiler,
 | 
				
			||||||
 | 
					        scope: &Arc<Scope>,
 | 
				
			||||||
 | 
					    ) -> TranspileResult<(bool, Option<Command>, JsonValue)> {
 | 
				
			||||||
 | 
					        if let Some(var) = scope.get_variable(ident.span.str()).as_deref() {
 | 
				
			||||||
 | 
					            match var {
 | 
				
			||||||
 | 
					                VariableData::Scoreboard { objective } => {
 | 
				
			||||||
 | 
					                    let Ok(ComptimeValue::String(target)) =
 | 
				
			||||||
 | 
					                        index.comptime_eval(scope, &VoidHandler)
 | 
				
			||||||
 | 
					                    else {
 | 
				
			||||||
 | 
					                        return Err(TranspileError::IllegalIndexing(IllegalIndexing {
 | 
				
			||||||
 | 
					                            reason: IllegalIndexingReason::InvalidComptimeType {
 | 
				
			||||||
 | 
					                                expected: ExpectedType::String,
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            expression: index.span(),
 | 
				
			||||||
 | 
					                        }));
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    let (cmd, value) = get_data_location(
 | 
				
			||||||
 | 
					                        &DataLocation::ScoreboardValue {
 | 
				
			||||||
 | 
					                            objective: objective.to_string(),
 | 
				
			||||||
 | 
					                            target,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        transpiler,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Ok((false, cmd, value))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                VariableData::ScoreboardArray { objective, targets } => {
 | 
				
			||||||
 | 
					                    let Ok(ComptimeValue::Integer(idx)) = index.comptime_eval(scope, &VoidHandler)
 | 
				
			||||||
 | 
					                    else {
 | 
				
			||||||
 | 
					                        return Err(TranspileError::IllegalIndexing(IllegalIndexing {
 | 
				
			||||||
 | 
					                            reason: IllegalIndexingReason::InvalidComptimeType {
 | 
				
			||||||
 | 
					                                expected: ExpectedType::Integer,
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            expression: index.span(),
 | 
				
			||||||
 | 
					                        }));
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    #[expect(clippy::option_if_let_else)]
 | 
				
			||||||
 | 
					                    if let Some(target) = usize::try_from(idx)
 | 
				
			||||||
 | 
					                        .ok()
 | 
				
			||||||
 | 
					                        .and_then(|index| targets.get(index))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        let (cmd, value) = get_data_location(
 | 
				
			||||||
 | 
					                            &DataLocation::ScoreboardValue {
 | 
				
			||||||
 | 
					                                objective: objective.to_string(),
 | 
				
			||||||
 | 
					                                target: target.to_string(),
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            transpiler,
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                        Ok((false, cmd, value))
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        Err(TranspileError::IllegalIndexing(IllegalIndexing {
 | 
				
			||||||
 | 
					                            reason: IllegalIndexingReason::IndexOutOfBounds {
 | 
				
			||||||
 | 
					                                index: usize::try_from(idx).unwrap_or(usize::MAX),
 | 
				
			||||||
 | 
					                                length: targets.len(),
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            expression: index.span(),
 | 
				
			||||||
 | 
					                        }))
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                VariableData::BooleanStorageArray {
 | 
				
			||||||
 | 
					                    storage_name,
 | 
				
			||||||
 | 
					                    paths,
 | 
				
			||||||
 | 
					                } => {
 | 
				
			||||||
 | 
					                    let Ok(ComptimeValue::Integer(idx)) = index.comptime_eval(scope, &VoidHandler)
 | 
				
			||||||
 | 
					                    else {
 | 
				
			||||||
 | 
					                        return Err(TranspileError::IllegalIndexing(IllegalIndexing {
 | 
				
			||||||
 | 
					                            reason: IllegalIndexingReason::InvalidComptimeType {
 | 
				
			||||||
 | 
					                                expected: ExpectedType::Integer,
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            expression: index.span(),
 | 
				
			||||||
 | 
					                        }));
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    #[expect(clippy::option_if_let_else)]
 | 
				
			||||||
 | 
					                    if let Some(path) = usize::try_from(idx).ok().and_then(|index| paths.get(index))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        let (cmd, value) = get_data_location(
 | 
				
			||||||
 | 
					                            &DataLocation::Storage {
 | 
				
			||||||
 | 
					                                storage_name: storage_name.to_string(),
 | 
				
			||||||
 | 
					                                path: path.to_string(),
 | 
				
			||||||
 | 
					                                r#type: StorageType::Boolean,
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            transpiler,
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                        Ok((false, cmd, value))
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        Err(TranspileError::IllegalIndexing(IllegalIndexing {
 | 
				
			||||||
 | 
					                            reason: IllegalIndexingReason::IndexOutOfBounds {
 | 
				
			||||||
 | 
					                                index: usize::try_from(idx).unwrap_or(usize::MAX),
 | 
				
			||||||
 | 
					                                length: paths.len(),
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            expression: index.span(),
 | 
				
			||||||
 | 
					                        }))
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                VariableData::Tag { tag_name } => {
 | 
				
			||||||
 | 
					                    let Ok(ComptimeValue::String(entity)) =
 | 
				
			||||||
 | 
					                        index.comptime_eval(scope, &VoidHandler)
 | 
				
			||||||
 | 
					                    else {
 | 
				
			||||||
 | 
					                        return Err(TranspileError::IllegalIndexing(IllegalIndexing {
 | 
				
			||||||
 | 
					                            reason: IllegalIndexingReason::InvalidComptimeType {
 | 
				
			||||||
 | 
					                                expected: ExpectedType::String,
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            expression: index.span(),
 | 
				
			||||||
 | 
					                        }));
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    let (cmd, value) = get_data_location(
 | 
				
			||||||
 | 
					                        &DataLocation::Tag {
 | 
				
			||||||
 | 
					                            tag_name: tag_name.clone(),
 | 
				
			||||||
 | 
					                            entity,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        transpiler,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Ok((false, cmd, value))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                _ => Err(TranspileError::UnexpectedExpression(UnexpectedExpression(
 | 
					                _ => Err(TranspileError::UnexpectedExpression(UnexpectedExpression(
 | 
				
			||||||
                    Box::new(Expression::Primary(Primary::Identifier(ident.to_owned()))),
 | 
					                    Box::new(Expression::Primary(Primary::Identifier(ident.to_owned()))),
 | 
				
			||||||
                ))),
 | 
					                ))),
 | 
				
			||||||
| 
						 | 
					@ -192,7 +345,7 @@ fn print_function(
 | 
				
			||||||
        ("@a".into(), first)
 | 
					        ("@a".into(), first)
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut contains_macro = matches!(target, MacroString::MacroString(_));
 | 
					    let mut contains_macro = target.contains_macros();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let (mut cmds, parts) = match message_expression {
 | 
					    let (mut cmds, parts) = match message_expression {
 | 
				
			||||||
        Expression::Primary(primary) => match primary {
 | 
					        Expression::Primary(primary) => match primary {
 | 
				
			||||||
| 
						 | 
					@ -343,21 +496,44 @@ fn print_function(
 | 
				
			||||||
                    reason: IllegalIndexingReason::NotIdentifier,
 | 
					                    reason: IllegalIndexingReason::NotIdentifier,
 | 
				
			||||||
                })),
 | 
					                })),
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            Primary::MacroStringLiteral(macro_string) => {
 | 
					            Primary::TemplateStringLiteral(template_string) => {
 | 
				
			||||||
                let mut cmds = Vec::new();
 | 
					                let mut cmds = Vec::new();
 | 
				
			||||||
                let mut parts = Vec::new();
 | 
					                let mut parts = Vec::new();
 | 
				
			||||||
                for part in macro_string.parts() {
 | 
					                for part in template_string.parts() {
 | 
				
			||||||
                    match part {
 | 
					                    match part {
 | 
				
			||||||
                        MacroStringLiteralPart::Text(text) => {
 | 
					                        TemplateStringLiteralPart::Text(text) => {
 | 
				
			||||||
                            parts.push(JsonValue::String(text.str().to_string()));
 | 
					                            parts.push(JsonValue::String(text.span.str().to_string()));
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        MacroStringLiteralPart::MacroUsage { identifier, .. } => {
 | 
					                        TemplateStringLiteralPart::Expression { expression, .. } => {
 | 
				
			||||||
 | 
					                            match expression.as_ref() {
 | 
				
			||||||
 | 
					                                Expression::Primary(Primary::Identifier(identifier)) => {
 | 
				
			||||||
                                    let (cur_contains_macro, cur_cmds, part) =
 | 
					                                    let (cur_contains_macro, cur_cmds, part) =
 | 
				
			||||||
                                        get_identifier_part(identifier, transpiler, scope)?;
 | 
					                                        get_identifier_part(identifier, transpiler, scope)?;
 | 
				
			||||||
                                    contains_macro |= cur_contains_macro;
 | 
					                                    contains_macro |= cur_contains_macro;
 | 
				
			||||||
                                    cmds.extend(cur_cmds);
 | 
					                                    cmds.extend(cur_cmds);
 | 
				
			||||||
                                    parts.push(part);
 | 
					                                    parts.push(part);
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
 | 
					                                Expression::Primary(Primary::Indexed(indexed)) => {
 | 
				
			||||||
 | 
					                                    match indexed.object().as_ref() {
 | 
				
			||||||
 | 
					                                        Primary::Identifier(ident) => {
 | 
				
			||||||
 | 
					                                            let (cur_contains_macro, cur_cmds, part) =
 | 
				
			||||||
 | 
					                                                get_indexed_part(
 | 
				
			||||||
 | 
					                                                    ident,
 | 
				
			||||||
 | 
					                                                    indexed.index(),
 | 
				
			||||||
 | 
					                                                    transpiler,
 | 
				
			||||||
 | 
					                                                    scope,
 | 
				
			||||||
 | 
					                                                )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                            contains_macro |= cur_contains_macro;
 | 
				
			||||||
 | 
					                                            cmds.extend(cur_cmds);
 | 
				
			||||||
 | 
					                                            parts.push(part);
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                        _ => todo!("other expression in indexed"),
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                _ => todo!("other expression in template string literal"),
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                Ok((cmds, parts))
 | 
					                Ok((cmds, parts))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -149,6 +149,8 @@ mod enabled {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            table.set("version", crate::VERSION)?;
 | 
					            table.set("version", crate::VERSION)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // TODO: add functions for requesting data/scoreboard locations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok(table)
 | 
					            Ok(table)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -323,11 +325,14 @@ mod enabled {
 | 
				
			||||||
                    handler,
 | 
					                    handler,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                Value::Boolean(boolean) => Ok(Some(ComptimeValue::Boolean(boolean))),
 | 
					                Value::Boolean(boolean) => Ok(Some(ComptimeValue::Boolean(boolean))),
 | 
				
			||||||
                Value::Table(table) => match table.get::<Value>("value") {
 | 
					                Value::Table(table) => {
 | 
				
			||||||
 | 
					                    // TODO: allow to return arrays when comptime arrays are implemented
 | 
				
			||||||
 | 
					                    match table.get::<Value>("value") {
 | 
				
			||||||
                        Ok(Value::Nil) => {
 | 
					                        Ok(Value::Nil) => {
 | 
				
			||||||
                            let err = TranspileError::LuaRuntimeError(LuaRuntimeError {
 | 
					                            let err = TranspileError::LuaRuntimeError(LuaRuntimeError {
 | 
				
			||||||
                                code_block: self.span(),
 | 
					                                code_block: self.span(),
 | 
				
			||||||
                            error_message: "return table must contain non-nil 'value'".to_string(),
 | 
					                                error_message: "return table must contain non-nil 'value'"
 | 
				
			||||||
 | 
					                                    .to_string(),
 | 
				
			||||||
                            });
 | 
					                            });
 | 
				
			||||||
                            handler.receive(Box::new(err.clone()));
 | 
					                            handler.receive(Box::new(err.clone()));
 | 
				
			||||||
                            Err(err)
 | 
					                            Err(err)
 | 
				
			||||||
| 
						 | 
					@ -335,7 +340,8 @@ mod enabled {
 | 
				
			||||||
                        Ok(value) => {
 | 
					                        Ok(value) => {
 | 
				
			||||||
                            let value = match self.handle_lua_result(value, handler)? {
 | 
					                            let value = match self.handle_lua_result(value, handler)? {
 | 
				
			||||||
                                Some(ComptimeValue::String(s)) => {
 | 
					                                Some(ComptimeValue::String(s)) => {
 | 
				
			||||||
                                let contains_macro = match table.get::<Value>("contains_macro") {
 | 
					                                    let contains_macro = match table.get::<Value>("contains_macro")
 | 
				
			||||||
 | 
					                                    {
 | 
				
			||||||
                                        Ok(Value::Boolean(boolean)) => Ok(boolean),
 | 
					                                        Ok(Value::Boolean(boolean)) => Ok(boolean),
 | 
				
			||||||
                                        Ok(value) => {
 | 
					                                        Ok(value) => {
 | 
				
			||||||
                                            if let Some(ComptimeValue::Boolean(boolean)) =
 | 
					                                            if let Some(ComptimeValue::Boolean(boolean)) =
 | 
				
			||||||
| 
						 | 
					@ -343,11 +349,12 @@ mod enabled {
 | 
				
			||||||
                                            {
 | 
					                                            {
 | 
				
			||||||
                                                Ok(boolean)
 | 
					                                                Ok(boolean)
 | 
				
			||||||
                                            } else {
 | 
					                                            } else {
 | 
				
			||||||
                                            let err =
 | 
					                                                let err = TranspileError::MismatchedTypes(
 | 
				
			||||||
                                                TranspileError::MismatchedTypes(MismatchedTypes {
 | 
					                                                    MismatchedTypes {
 | 
				
			||||||
                                                        expression: self.span(),
 | 
					                                                        expression: self.span(),
 | 
				
			||||||
                                                        expected_type: ExpectedType::Boolean,
 | 
					                                                        expected_type: ExpectedType::Boolean,
 | 
				
			||||||
                                                });
 | 
					                                                    },
 | 
				
			||||||
 | 
					                                                );
 | 
				
			||||||
                                                handler.receive(Box::new(err.clone()));
 | 
					                                                handler.receive(Box::new(err.clone()));
 | 
				
			||||||
                                                Err(err)
 | 
					                                                Err(err)
 | 
				
			||||||
                                            }
 | 
					                                            }
 | 
				
			||||||
| 
						 | 
					@ -376,14 +383,14 @@ mod enabled {
 | 
				
			||||||
                            Ok(value)
 | 
					                            Ok(value)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        Err(err) => {
 | 
					                        Err(err) => {
 | 
				
			||||||
                        let err = TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err(
 | 
					                            let err = TranspileError::LuaRuntimeError(
 | 
				
			||||||
                            &err,
 | 
					                                LuaRuntimeError::from_lua_err(&err, self.span()),
 | 
				
			||||||
                            self.span(),
 | 
					                            );
 | 
				
			||||||
                        ));
 | 
					 | 
				
			||||||
                            handler.receive(Box::new(err.clone()));
 | 
					                            handler.receive(Box::new(err.clone()));
 | 
				
			||||||
                            Err(err)
 | 
					                            Err(err)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                },
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                Value::Error(_)
 | 
					                Value::Error(_)
 | 
				
			||||||
                | Value::Thread(_)
 | 
					                | Value::Thread(_)
 | 
				
			||||||
                | Value::UserData(_)
 | 
					                | Value::UserData(_)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -69,7 +69,6 @@ impl Transpiler {
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Errors
 | 
					    /// # Errors
 | 
				
			||||||
    /// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing
 | 
					    /// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing
 | 
				
			||||||
    #[expect(clippy::too_many_lines)]
 | 
					 | 
				
			||||||
    #[tracing::instrument(level = "trace", skip_all)]
 | 
					    #[tracing::instrument(level = "trace", skip_all)]
 | 
				
			||||||
    pub fn transpile(
 | 
					    pub fn transpile(
 | 
				
			||||||
        mut self,
 | 
					        mut self,
 | 
				
			||||||
| 
						 | 
					@ -645,7 +644,9 @@ impl Transpiler {
 | 
				
			||||||
            Primary::StringLiteral(string) => {
 | 
					            Primary::StringLiteral(string) => {
 | 
				
			||||||
                Ok(vec![Command::Raw(string.str_content().to_string())])
 | 
					                Ok(vec![Command::Raw(string.str_content().to_string())])
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Primary::MacroStringLiteral(string) => Ok(vec![Command::UsesMacro(string.into())]),
 | 
					            Primary::TemplateStringLiteral(string) => Ok(vec![Command::UsesMacro(
 | 
				
			||||||
 | 
					                string.to_macro_string(scope, handler)?.into(),
 | 
				
			||||||
 | 
					            )]),
 | 
				
			||||||
            Primary::Lua(code) => match code.eval_comptime(scope, handler)? {
 | 
					            Primary::Lua(code) => match code.eval_comptime(scope, handler)? {
 | 
				
			||||||
                Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
 | 
					                Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
 | 
				
			||||||
                Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]),
 | 
					                Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]),
 | 
				
			||||||
| 
						 | 
					@ -987,37 +988,41 @@ impl Transpiler {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::As(r#as) => {
 | 
					            ExecuteBlockHead::As(r#as) => {
 | 
				
			||||||
                let selector = r#as.as_selector();
 | 
					                let selector = r#as.as_selector().to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::As(selector.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::As(selector.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::At(at) => {
 | 
					            ExecuteBlockHead::At(at) => {
 | 
				
			||||||
                let selector = at.at_selector();
 | 
					                let selector = at.at_selector().to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::At(selector.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::At(selector.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Align(align) => {
 | 
					            ExecuteBlockHead::Align(align) => {
 | 
				
			||||||
                let align = align.align_selector();
 | 
					                let align = align.align_selector().to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::Align(align.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::Align(align.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Anchored(anchored) => {
 | 
					            ExecuteBlockHead::Anchored(anchored) => {
 | 
				
			||||||
                let anchor = anchored.anchored_selector();
 | 
					                let anchor = anchored
 | 
				
			||||||
 | 
					                    .anchored_selector()
 | 
				
			||||||
 | 
					                    .to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::Anchored(anchor.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::Anchored(anchor.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::In(r#in) => {
 | 
					            ExecuteBlockHead::In(r#in) => {
 | 
				
			||||||
                let dimension = r#in.in_selector();
 | 
					                let dimension = r#in.in_selector().to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::In(dimension.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::In(dimension.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Positioned(positioned) => {
 | 
					            ExecuteBlockHead::Positioned(positioned) => {
 | 
				
			||||||
                let position = positioned.positioned_selector();
 | 
					                let position = positioned
 | 
				
			||||||
 | 
					                    .positioned_selector()
 | 
				
			||||||
 | 
					                    .to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (
 | 
					                    (
 | 
				
			||||||
                        pre_cmds,
 | 
					                        pre_cmds,
 | 
				
			||||||
| 
						 | 
					@ -1026,37 +1031,37 @@ impl Transpiler {
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Rotated(rotated) => {
 | 
					            ExecuteBlockHead::Rotated(rotated) => {
 | 
				
			||||||
                let rotation = rotated.rotated_selector();
 | 
					                let rotation = rotated.rotated_selector().to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::Rotated(rotation.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::Rotated(rotation.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Facing(facing) => {
 | 
					            ExecuteBlockHead::Facing(facing) => {
 | 
				
			||||||
                let facing = facing.facing_selector();
 | 
					                let facing = facing.facing_selector().to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::Facing(facing.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::Facing(facing.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::AsAt(as_at) => {
 | 
					            ExecuteBlockHead::AsAt(as_at) => {
 | 
				
			||||||
                let selector = as_at.asat_selector();
 | 
					                let selector = as_at.asat_selector().to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::AsAt(selector.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::AsAt(selector.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::On(on) => {
 | 
					            ExecuteBlockHead::On(on) => {
 | 
				
			||||||
                let dimension = on.on_selector();
 | 
					                let dimension = on.on_selector().to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::On(dimension.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::On(dimension.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Store(store) => {
 | 
					            ExecuteBlockHead::Store(store) => {
 | 
				
			||||||
                let store = store.store_selector();
 | 
					                let store = store.store_selector().to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::Store(store.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::Store(store.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ExecuteBlockHead::Summon(summon) => {
 | 
					            ExecuteBlockHead::Summon(summon) => {
 | 
				
			||||||
                let entity = summon.summon_selector();
 | 
					                let entity = summon.summon_selector().to_macro_string(scope, handler)?;
 | 
				
			||||||
                tail.map(|(pre_cmds, tail)| {
 | 
					                tail.map(|(pre_cmds, tail)| {
 | 
				
			||||||
                    (pre_cmds, Execute::Summon(entity.into(), Box::new(tail)))
 | 
					                    (pre_cmds, Execute::Summon(entity.into(), Box::new(tail)))
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,21 @@
 | 
				
			||||||
//! Utility methods for transpiling
 | 
					//! Utility methods for transpiling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::{fmt::Display, str::FromStr};
 | 
					use std::{fmt::Display, str::FromStr, sync::Arc};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
 | 
					    base::{self, source_file::SourceElement as _, Handler},
 | 
				
			||||||
    syntax::syntax_tree::AnyStringLiteral,
 | 
					    syntax::syntax_tree::{
 | 
				
			||||||
 | 
					        expression::{Expression, Primary, TemplateStringLiteral, TemplateStringLiteralPart},
 | 
				
			||||||
 | 
					        AnyStringLiteral,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    transpile::{
 | 
				
			||||||
 | 
					        error::{TranspileError, UnknownIdentifier},
 | 
				
			||||||
 | 
					        Scope, TranspileResult, VariableData,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::expression::ComptimeValue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// String that can contain macros
 | 
					/// String that can contain macros
 | 
				
			||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
					#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
				
			||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 | 
					#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 | 
				
			||||||
| 
						 | 
					@ -247,53 +256,90 @@ where
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<&AnyStringLiteral> for MacroString {
 | 
					impl AnyStringLiteral {
 | 
				
			||||||
    fn from(value: &AnyStringLiteral) -> Self {
 | 
					    /// Convert the any string literal to a macro string, using the provided scope to resolve variables
 | 
				
			||||||
        match value {
 | 
					    ///
 | 
				
			||||||
            AnyStringLiteral::StringLiteral(literal) => Self::from(literal.str_content().as_ref()),
 | 
					    /// # Errors
 | 
				
			||||||
            AnyStringLiteral::MacroStringLiteral(literal) => Self::from(literal),
 | 
					    /// - If an identifier in a template string is not found in the scope
 | 
				
			||||||
 | 
					    pub fn to_macro_string(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        scope: &Arc<Scope>,
 | 
				
			||||||
 | 
					        handler: &impl Handler<base::Error>,
 | 
				
			||||||
 | 
					    ) -> TranspileResult<MacroString> {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Self::StringLiteral(literal) => Ok(MacroString::from(literal.str_content().as_ref())),
 | 
				
			||||||
 | 
					            Self::TemplateStringLiteral(literal) => literal.to_macro_string(scope, handler),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<AnyStringLiteral> for MacroString {
 | 
					impl TemplateStringLiteral {
 | 
				
			||||||
    fn from(value: AnyStringLiteral) -> Self {
 | 
					    /// Convert the template string literal to a macro string, using the provided scope to resolve variables
 | 
				
			||||||
        Self::from(&value)
 | 
					    ///
 | 
				
			||||||
    }
 | 
					    /// # Errors
 | 
				
			||||||
}
 | 
					    /// - If an identifier in a template string is not found in the scope
 | 
				
			||||||
 | 
					    pub fn to_macro_string(
 | 
				
			||||||
impl From<&MacroStringLiteral> for MacroString {
 | 
					        &self,
 | 
				
			||||||
    fn from(value: &MacroStringLiteral) -> Self {
 | 
					        scope: &Arc<Scope>,
 | 
				
			||||||
        if value
 | 
					        handler: &impl Handler<base::Error>,
 | 
				
			||||||
 | 
					    ) -> TranspileResult<MacroString> {
 | 
				
			||||||
 | 
					        if self
 | 
				
			||||||
            .parts()
 | 
					            .parts()
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
            .any(|p| matches!(p, MacroStringLiteralPart::MacroUsage { .. }))
 | 
					            .any(|p| matches!(p, TemplateStringLiteralPart::Expression { .. }))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Self::MacroString(
 | 
					            let macro_string = MacroString::MacroString(
 | 
				
			||||||
                value
 | 
					                self.parts()
 | 
				
			||||||
                    .parts()
 | 
					 | 
				
			||||||
                    .iter()
 | 
					                    .iter()
 | 
				
			||||||
                    .map(|part| match part {
 | 
					                    .map(|part| match part {
 | 
				
			||||||
                        MacroStringLiteralPart::Text(span) => MacroStringPart::String(
 | 
					                        TemplateStringLiteralPart::Text(text) => Ok(MacroStringPart::String(
 | 
				
			||||||
                            crate::util::unescape_macro_string(span.str()).to_string(),
 | 
					                            crate::util::unescape_template_string(text.span.str()).into_owned(),
 | 
				
			||||||
 | 
					                        )),
 | 
				
			||||||
 | 
					                        TemplateStringLiteralPart::Expression { expression, .. } => {
 | 
				
			||||||
 | 
					                            match expression.as_ref() {
 | 
				
			||||||
 | 
					                                Expression::Primary(Primary::Identifier(identifier)) =>
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    #[expect(clippy::option_if_let_else)]
 | 
				
			||||||
 | 
					                                    if let Some(var_data) =
 | 
				
			||||||
 | 
					                                        scope.get_variable(identifier.span.str())
 | 
				
			||||||
 | 
					                                    {
 | 
				
			||||||
 | 
					                                        match var_data.as_ref() {
 | 
				
			||||||
 | 
					                                            VariableData::MacroParameter { macro_name, .. } => Ok(
 | 
				
			||||||
 | 
					                                                MacroStringPart::MacroUsage(macro_name.to_owned()),
 | 
				
			||||||
                                            ),
 | 
					                                            ),
 | 
				
			||||||
                        MacroStringLiteralPart::MacroUsage { identifier, .. } => {
 | 
					                                            VariableData::ComptimeValue { value, .. } => {
 | 
				
			||||||
                            MacroStringPart::MacroUsage(
 | 
					                                                let value = value.read().unwrap().as_ref().map_or_else(
 | 
				
			||||||
                                crate::util::identifier_to_macro(identifier.span.str()).to_string(),
 | 
					                                                    || "null".into(),
 | 
				
			||||||
                            )
 | 
					                                                    ComptimeValue::to_macro_string,
 | 
				
			||||||
 | 
					                                                );
 | 
				
			||||||
 | 
					                                                
 | 
				
			||||||
 | 
					                                                match value.as_str() {
 | 
				
			||||||
 | 
					                                                    Ok(s) => Ok(MacroStringPart::String(s.into_owned())),
 | 
				
			||||||
 | 
					                                                    Err(_) => todo!("comptime value resulting in macro string with macros")
 | 
				
			||||||
 | 
					                                                }
 | 
				
			||||||
 | 
					                                            }
 | 
				
			||||||
 | 
					                                            _ => todo!("other identifiers in template strings"),
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    } else {
 | 
				
			||||||
 | 
					                                        let err =
 | 
				
			||||||
 | 
					                                            TranspileError::UnknownIdentifier(UnknownIdentifier {
 | 
				
			||||||
 | 
					                                                identifier: identifier.span(),
 | 
				
			||||||
 | 
					                                            });
 | 
				
			||||||
 | 
					                                        handler.receive(Box::new(err.clone()));
 | 
				
			||||||
 | 
					                                        Err(err)
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                _ => todo!("other expressions in template strings"),
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                    .collect(),
 | 
					                    .collect::<TranspileResult<Vec<MacroStringPart>>>()?,
 | 
				
			||||||
            )
 | 
					            );
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Self::String(value.str_content())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<MacroStringLiteral> for MacroString {
 | 
					            Ok(macro_string)
 | 
				
			||||||
    fn from(value: MacroStringLiteral) -> Self {
 | 
					        } else {
 | 
				
			||||||
        Self::from(&value)
 | 
					            Ok(MacroString::String(self.as_str(scope, handler)?.into_owned()))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										21
									
								
								src/util.rs
								
								
								
								
							
							
						
						
									
										21
									
								
								src/util.rs
								
								
								
								
							| 
						 | 
					@ -20,15 +20,16 @@ pub fn escape_str(s: &str) -> Cow<'_, str> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Unescapes '\`', `\`, `\n`, `\r` and `\t` in a string.
 | 
					/// Unescapes '\`', `\`, `\n`, `\r` and `\t`, `\$` in a string.
 | 
				
			||||||
#[must_use]
 | 
					#[must_use]
 | 
				
			||||||
pub fn unescape_macro_string(s: &str) -> Cow<'_, str> {
 | 
					pub fn unescape_template_string(s: &str) -> Cow<'_, str> {
 | 
				
			||||||
    if s.contains('\\') || s.contains('`') {
 | 
					    if s.contains('\\') || s.contains('`') {
 | 
				
			||||||
        Cow::Owned(
 | 
					        Cow::Owned(
 | 
				
			||||||
            s.replace("\\n", "\n")
 | 
					            s.replace("\\n", "\n")
 | 
				
			||||||
                .replace("\\r", "\r")
 | 
					                .replace("\\r", "\r")
 | 
				
			||||||
                .replace("\\t", "\t")
 | 
					                .replace("\\t", "\t")
 | 
				
			||||||
                .replace("\\`", "`")
 | 
					                .replace("\\`", "`")
 | 
				
			||||||
 | 
					                .replace("\\$", "$")
 | 
				
			||||||
                .replace("\\\\", "\\"),
 | 
					                .replace("\\\\", "\\"),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
| 
						 | 
					@ -147,33 +148,33 @@ mod tests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn test_unescape_macro_string() {
 | 
					    fn test_unescape_macro_string() {
 | 
				
			||||||
        assert_eq!(unescape_macro_string("Hello, world!"), "Hello, world!");
 | 
					        assert_eq!(unescape_template_string("Hello, world!"), "Hello, world!");
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            unescape_macro_string(r#"Hello, "world"!"#),
 | 
					            unescape_template_string(r#"Hello, "world"!"#),
 | 
				
			||||||
            r#"Hello, "world"!"#
 | 
					            r#"Hello, "world"!"#
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            unescape_macro_string(r"Hello, \world\!"),
 | 
					            unescape_template_string(r"Hello, \world\!"),
 | 
				
			||||||
            r"Hello, \world\!"
 | 
					            r"Hello, \world\!"
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            unescape_macro_string(r"Hello, \nworld\!"),
 | 
					            unescape_template_string(r"Hello, \nworld\!"),
 | 
				
			||||||
            "Hello, \nworld\\!"
 | 
					            "Hello, \nworld\\!"
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            unescape_macro_string(r"Hello, \rworld\!"),
 | 
					            unescape_template_string(r"Hello, \rworld\!"),
 | 
				
			||||||
            "Hello, \rworld\\!"
 | 
					            "Hello, \rworld\\!"
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            unescape_macro_string(r"Hello, \tworld\!"),
 | 
					            unescape_template_string(r"Hello, \tworld\!"),
 | 
				
			||||||
            "Hello, \tworld\\!"
 | 
					            "Hello, \tworld\\!"
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            unescape_macro_string(r"Hello, \`world\!"),
 | 
					            unescape_template_string(r"Hello, \`world\!"),
 | 
				
			||||||
            r"Hello, `world\!"
 | 
					            r"Hello, `world\!"
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            unescape_macro_string(r"Hello, \\world\!"),
 | 
					            unescape_template_string(r"Hello, \\world\!"),
 | 
				
			||||||
            r"Hello, \world\!"
 | 
					            r"Hello, \world\!"
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue