Compare commits
	
		
			No commits in common. "b7d50f822276a7465833d0b187614086fa64f319" and "3bc3ca180f4fa417c603ae19775934da47fac0c1" have entirely different histories.
		
	
	
		
			b7d50f8222
			...
			3bc3ca180f
		
	
		
							
								
								
									
										17
									
								
								grammar.md
								
								
								
								
							
							
						
						
									
										17
									
								
								grammar.md
								
								
								
								
							| 
						 | 
				
			
			@ -170,7 +170,7 @@ VariableDeclarationAssignment:
 | 
			
		|||
## AnyStringLiteral
 | 
			
		||||
 | 
			
		||||
```ebnf
 | 
			
		||||
AnyStringLiteral: StringLiteral | TemplateStringLiteral ;
 | 
			
		||||
AnyStringLiteral: StringLiteral | MacroStringLiteral ;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## AnnotationValue
 | 
			
		||||
| 
						 | 
				
			
			@ -230,11 +230,11 @@ Expression:
 | 
			
		|||
    Primary | Binary ;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## TemplateStringLiteral
 | 
			
		||||
## MacroStringLiteral
 | 
			
		||||
 | 
			
		||||
```ebnf
 | 
			
		||||
TemplateStringLiteral:
 | 
			
		||||
  '`' ( TemplateStringLiteralText | '$(' Expression ')' )* '`';
 | 
			
		||||
MacroStringLiteral:
 | 
			
		||||
  '`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Else
 | 
			
		||||
| 
						 | 
				
			
			@ -304,17 +304,10 @@ Primary:
 | 
			
		|||
    | StringLiteral
 | 
			
		||||
    | FunctionCall
 | 
			
		||||
    | MemberAccess
 | 
			
		||||
    | TemplateStringLiteral
 | 
			
		||||
    | MacroStringLiteral
 | 
			
		||||
    | LuaCode
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## TemplateStringLiteralText
 | 
			
		||||
 | 
			
		||||
```ebnf
 | 
			
		||||
TemplateStringLiteralText:
 | 
			
		||||
  TEXT ;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Align
 | 
			
		||||
 | 
			
		||||
```ebnf
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
use std::{
 | 
			
		||||
    cmp::Ordering,
 | 
			
		||||
    fmt::Debug,
 | 
			
		||||
    iter::Iterator,
 | 
			
		||||
    iter::{Iterator, Peekable},
 | 
			
		||||
    ops::Range,
 | 
			
		||||
    path::{Path, PathBuf},
 | 
			
		||||
    str::CharIndices,
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +11,6 @@ use std::{
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
use getset::{CopyGetters, Getters};
 | 
			
		||||
use itertools::{structs::MultiPeek, Itertools as _};
 | 
			
		||||
 | 
			
		||||
use super::{file_provider::FileProvider, Error};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -73,9 +72,8 @@ impl SourceFile {
 | 
			
		|||
    pub fn iter(self: &Arc<Self>) -> SourceIterator<'_> {
 | 
			
		||||
        SourceIterator {
 | 
			
		||||
            source_file: self,
 | 
			
		||||
            iterator: self.content().char_indices().multipeek(),
 | 
			
		||||
            iterator: self.content().char_indices().peekable(),
 | 
			
		||||
            prev: None,
 | 
			
		||||
            in_template_string_expression_open_count: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -339,69 +337,16 @@ pub struct SourceIterator<'a> {
 | 
			
		|||
    /// Get the source file that the iterator is iterating over.
 | 
			
		||||
    #[get_copy = "pub"]
 | 
			
		||||
    source_file: &'a Arc<SourceFile>,
 | 
			
		||||
    iterator: MultiPeek<CharIndices<'a>>,
 | 
			
		||||
    iterator: Peekable<CharIndices<'a>>,
 | 
			
		||||
    /// Get the previous character that was iterated over.
 | 
			
		||||
    #[get_copy = "pub"]
 | 
			
		||||
    prev: Option<(usize, char)>,
 | 
			
		||||
    /// Current state for parsing template strings.
 | 
			
		||||
    in_template_string_expression_open_count: Vec<u32>,
 | 
			
		||||
}
 | 
			
		||||
impl SourceIterator<'_> {
 | 
			
		||||
    /// Peek at the next character in the source file.
 | 
			
		||||
    pub fn peek(&mut self) -> Option<(usize, char)> {
 | 
			
		||||
        self.iterator.reset_peek();
 | 
			
		||||
        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<'_> {
 | 
			
		||||
    type Item = (usize, char);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ use std::{
 | 
			
		|||
 | 
			
		||||
use crate::base::{
 | 
			
		||||
    self,
 | 
			
		||||
    log::SourceCodeDisplay,
 | 
			
		||||
    source_file::{SourceElement, SourceIterator, Span},
 | 
			
		||||
    Handler,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -164,7 +165,7 @@ pub enum Token {
 | 
			
		|||
    DocComment(DocComment),
 | 
			
		||||
    CommandLiteral(CommandLiteral),
 | 
			
		||||
    StringLiteral(StringLiteral),
 | 
			
		||||
    TemplateStringText(TemplateStringLiteralText),
 | 
			
		||||
    MacroStringLiteral(Box<MacroStringLiteral>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SourceElement for Token {
 | 
			
		||||
| 
						 | 
				
			
			@ -180,7 +181,7 @@ impl SourceElement for Token {
 | 
			
		|||
            Self::DocComment(token) => token.span(),
 | 
			
		||||
            Self::CommandLiteral(token) => token.span(),
 | 
			
		||||
            Self::StringLiteral(token) => token.span(),
 | 
			
		||||
            Self::TemplateStringText(token) => token.span(),
 | 
			
		||||
            Self::MacroStringLiteral(token) => token.span(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -351,23 +352,78 @@ impl SourceElement for StringLiteral {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a hardcoded template string text value in the source code.
 | 
			
		||||
/// Represents a hardcoded macro string literal value in the source code.
 | 
			
		||||
///
 | 
			
		||||
/// ```ebnf
 | 
			
		||||
/// TemplateStringLiteralText:
 | 
			
		||||
///   TEXT ;
 | 
			
		||||
/// MacroStringLiteral:
 | 
			
		||||
///   '`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
 | 
			
		||||
/// ```
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
			
		||||
pub struct TemplateStringLiteralText {
 | 
			
		||||
    /// Is the span that makes up the token.
 | 
			
		||||
    pub span: Span,
 | 
			
		||||
pub struct MacroStringLiteral {
 | 
			
		||||
    /// The backtick that starts the macro string literal.
 | 
			
		||||
    starting_backtick: Punctuation,
 | 
			
		||||
    /// The parts that make up the macro string literal.
 | 
			
		||||
    parts: Vec<MacroStringLiteralPart>,
 | 
			
		||||
    /// The backtick that ends the macro string literal.
 | 
			
		||||
    ending_backtick: Punctuation,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SourceElement for TemplateStringLiteralText {
 | 
			
		||||
    fn span(&self) -> Span {
 | 
			
		||||
        self.span.clone()
 | 
			
		||||
impl MacroStringLiteral {
 | 
			
		||||
    /// 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 {
 | 
			
		||||
        self.starting_backtick
 | 
			
		||||
            .span
 | 
			
		||||
            .join(&self.ending_backtick.span)
 | 
			
		||||
            .expect("Invalid macro string literal span")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a part of a macro string literal value in the source code.
 | 
			
		||||
#[allow(missing_docs)]
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
			
		||||
pub enum MacroStringLiteralPart {
 | 
			
		||||
    Text(Span),
 | 
			
		||||
    MacroUsage {
 | 
			
		||||
        dollar: Punctuation,
 | 
			
		||||
        open_brace: Punctuation,
 | 
			
		||||
        identifier: Identifier,
 | 
			
		||||
        close_brace: Punctuation,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Is an enumeration representing the two kinds of comments in the Shulkerscript programming language.
 | 
			
		||||
| 
						 | 
				
			
			@ -444,15 +500,101 @@ impl CommandLiteral {
 | 
			
		|||
/// Is an error that can occur when invoking the [`Token::tokenize`] method.
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
 | 
			
		||||
#[allow(missing_docs)]
 | 
			
		||||
#[expect(missing_copy_implementations)]
 | 
			
		||||
pub enum TokenizeError {
 | 
			
		||||
    #[error("encountered a fatal lexical error that causes the process to stop.")]
 | 
			
		||||
    FatalLexicalError,
 | 
			
		||||
 | 
			
		||||
    #[error("the iterator argument is at the end of the source code.")]
 | 
			
		||||
    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 {
 | 
			
		||||
    /// Increments the iterator while the predicate returns true.
 | 
			
		||||
    pub fn walk_iter(iter: &mut SourceIterator, predicate: impl Fn(char) -> bool) {
 | 
			
		||||
| 
						 | 
				
			
			@ -474,6 +616,26 @@ 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.
 | 
			
		||||
    fn is_first_identifier_character(character: char) -> bool {
 | 
			
		||||
        character == '_'
 | 
			
		||||
| 
						 | 
				
			
			@ -630,84 +792,110 @@ impl Token {
 | 
			
		|||
        .into()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Handles a backticks for opening and closing template strings
 | 
			
		||||
    fn handle_template_string_quotes(iter: &mut SourceIterator, start: usize) -> Self {
 | 
			
		||||
        if iter
 | 
			
		||||
            .is_in_template_string_expression()
 | 
			
		||||
            .is_some_and(|last| !last)
 | 
			
		||||
        {
 | 
			
		||||
            // in template string text
 | 
			
		||||
            iter.exit_template_string();
 | 
			
		||||
        } else {
 | 
			
		||||
            // outside template string or in expression
 | 
			
		||||
            iter.enter_template_string();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Punctuation {
 | 
			
		||||
    /// Handles a sequence of characters that are enclosed in backticks and contain macro usages
 | 
			
		||||
    fn handle_macro_string_literal(
 | 
			
		||||
        iter: &mut SourceIterator,
 | 
			
		||||
        mut start: usize,
 | 
			
		||||
    ) -> Result<Self, TokenizeError> {
 | 
			
		||||
        let mut is_escaped = false;
 | 
			
		||||
        let mut is_inside_macro = false;
 | 
			
		||||
        let mut encountered_open_parenthesis = false;
 | 
			
		||||
        let starting_backtick = Punctuation {
 | 
			
		||||
            span: Self::create_span(start, iter),
 | 
			
		||||
            punctuation: '`',
 | 
			
		||||
        }
 | 
			
		||||
        .into()
 | 
			
		||||
    }
 | 
			
		||||
        };
 | 
			
		||||
        start += 1;
 | 
			
		||||
        let mut parts = Vec::new();
 | 
			
		||||
 | 
			
		||||
    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 == '\\');
 | 
			
		||||
        while iter.peek().is_some() {
 | 
			
		||||
            let (index, character) = iter.next().unwrap();
 | 
			
		||||
 | 
			
		||||
        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();
 | 
			
		||||
        }
 | 
			
		||||
            #[expect(clippy::collapsible_else_if)]
 | 
			
		||||
            if is_inside_macro {
 | 
			
		||||
                if character == ')' {
 | 
			
		||||
                    // Check if the macro usage is empty
 | 
			
		||||
                    if start + 2 == index {
 | 
			
		||||
                        return Err(EmptyMacroUsage {
 | 
			
		||||
                            span: Span::new(iter.source_file().clone(), start, index + 1).unwrap(),
 | 
			
		||||
                        }
 | 
			
		||||
                        .into());
 | 
			
		||||
                    }
 | 
			
		||||
                    parts.push(MacroStringLiteralPart::MacroUsage {
 | 
			
		||||
                        dollar: Punctuation {
 | 
			
		||||
                            span: Span::new(iter.source_file().clone(), start, start + 1).unwrap(),
 | 
			
		||||
                            punctuation: '$',
 | 
			
		||||
                        },
 | 
			
		||||
                        open_brace: Punctuation {
 | 
			
		||||
                            span: Span::new(iter.source_file().clone(), start + 1, start + 2)
 | 
			
		||||
                                .unwrap(),
 | 
			
		||||
                            punctuation: '(',
 | 
			
		||||
                        },
 | 
			
		||||
                        identifier: Identifier {
 | 
			
		||||
                            span: Self::create_span_with_end_offset(start + 2, iter, -1),
 | 
			
		||||
                        },
 | 
			
		||||
                        close_brace: Punctuation {
 | 
			
		||||
                            span: Span::new(iter.source_file().clone(), index, index + 1).unwrap(),
 | 
			
		||||
                            punctuation: ')',
 | 
			
		||||
                        },
 | 
			
		||||
                    });
 | 
			
		||||
                    start = index + 1;
 | 
			
		||||
                    is_inside_macro = false;
 | 
			
		||||
                } else if !encountered_open_parenthesis && character == '(' {
 | 
			
		||||
                    encountered_open_parenthesis = true;
 | 
			
		||||
                } else if encountered_open_parenthesis && !Self::is_identifier_character(character)
 | 
			
		||||
                {
 | 
			
		||||
                    if character == '`' {
 | 
			
		||||
                        return Err(UnclosedMacroUsage {
 | 
			
		||||
                            span: Span::new(iter.source_file().clone(), start, start + 2).unwrap(),
 | 
			
		||||
                        }
 | 
			
		||||
                        .into());
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
        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: '(',
 | 
			
		||||
                    Self::walk_iter(iter, |c| c != ')' && !Self::is_identifier_character(c));
 | 
			
		||||
                    return Err(InvalidMacroNameCharacter {
 | 
			
		||||
                        span: Self::create_span(index, iter),
 | 
			
		||||
                    }
 | 
			
		||||
                    .into());
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if character == '$' && iter.peek().is_some_and(|(_, c)| c == '(') {
 | 
			
		||||
                    parts.push(MacroStringLiteralPart::Text(
 | 
			
		||||
                        Self::create_span_with_end_offset(start, iter, -1),
 | 
			
		||||
                    ));
 | 
			
		||||
                    start = index;
 | 
			
		||||
                    is_inside_macro = true;
 | 
			
		||||
                    encountered_open_parenthesis = false;
 | 
			
		||||
                } else if character == '\\' {
 | 
			
		||||
                    is_escaped = !is_escaped;
 | 
			
		||||
                } else if character == '`' && !is_escaped {
 | 
			
		||||
                    if start != index {
 | 
			
		||||
                        parts.push(MacroStringLiteralPart::Text(
 | 
			
		||||
                            Self::create_span_with_end_offset(start, iter, -1),
 | 
			
		||||
                        ));
 | 
			
		||||
                    }
 | 
			
		||||
                    start = index;
 | 
			
		||||
                    break;
 | 
			
		||||
                } else {
 | 
			
		||||
                    is_escaped = false;
 | 
			
		||||
                }
 | 
			
		||||
                .into();
 | 
			
		||||
            }
 | 
			
		||||
            _ => {}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        loop {
 | 
			
		||||
            if character != '`' {
 | 
			
		||||
                iter.reset_multipeek();
 | 
			
		||||
                Self::walk_iter(iter, |c| !matches!(c, '$' | '`' | '\\'));
 | 
			
		||||
        if is_inside_macro {
 | 
			
		||||
            Err(UnclosedMacroUsage {
 | 
			
		||||
                span: Span::new(iter.source_file().clone(), start, start + 2).unwrap(),
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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 {
 | 
			
		||||
            .into())
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(Box::new(MacroStringLiteral {
 | 
			
		||||
                starting_backtick,
 | 
			
		||||
                parts,
 | 
			
		||||
                ending_backtick: Punctuation {
 | 
			
		||||
                    span: Self::create_span(start, iter),
 | 
			
		||||
                }
 | 
			
		||||
                .into();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            iter.next();
 | 
			
		||||
                    punctuation: '`',
 | 
			
		||||
                },
 | 
			
		||||
            })
 | 
			
		||||
            .into())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -741,13 +929,8 @@ impl Token {
 | 
			
		|||
            .next()
 | 
			
		||||
            .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
 | 
			
		||||
        else if character.is_whitespace() {
 | 
			
		||||
        if character.is_whitespace() {
 | 
			
		||||
            Ok(Self::handle_whitespace(iter, start))
 | 
			
		||||
        }
 | 
			
		||||
        // Found identifier/keyword
 | 
			
		||||
| 
						 | 
				
			
			@ -764,7 +947,7 @@ impl Token {
 | 
			
		|||
        }
 | 
			
		||||
        // Found macro string literal
 | 
			
		||||
        else if character == '`' {
 | 
			
		||||
            Ok(Self::handle_template_string_quotes(iter, start))
 | 
			
		||||
            Self::handle_macro_string_literal(iter, start)
 | 
			
		||||
        }
 | 
			
		||||
        // Found integer literal
 | 
			
		||||
        else if character.is_ascii_digit() {
 | 
			
		||||
| 
						 | 
				
			
			@ -772,12 +955,6 @@ impl Token {
 | 
			
		|||
        }
 | 
			
		||||
        // Found a 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 {
 | 
			
		||||
                span: Self::create_span(start, iter),
 | 
			
		||||
                punctuation: character,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,10 +5,13 @@ use std::{fmt::Debug, sync::Arc};
 | 
			
		|||
use derive_more::{Deref, From};
 | 
			
		||||
use enum_as_inner::EnumAsInner;
 | 
			
		||||
 | 
			
		||||
use crate::base::{
 | 
			
		||||
    self,
 | 
			
		||||
    source_file::{SourceElement, SourceFile, Span},
 | 
			
		||||
    Handler,
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::{
 | 
			
		||||
        self,
 | 
			
		||||
        source_file::{SourceElement, SourceFile, Span},
 | 
			
		||||
        Handler,
 | 
			
		||||
    },
 | 
			
		||||
    lexical::Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
| 
						 | 
				
			
			@ -62,6 +65,17 @@ impl TokenStream {
 | 
			
		|||
                Err(TokenizeError::FatalLexicalError) => {
 | 
			
		||||
                    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,13 +4,10 @@
 | 
			
		|||
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::{self, source_file::SourceElement as _, Handler},
 | 
			
		||||
    lexical::token::KeywordKind,
 | 
			
		||||
    lexical::token::{KeywordKind, MacroStringLiteral, MacroStringLiteralPart},
 | 
			
		||||
    syntax::syntax_tree::{
 | 
			
		||||
        declaration::{Declaration, Function, FunctionVariableType, ImportItems},
 | 
			
		||||
        expression::{
 | 
			
		||||
            Binary, BinaryOperator, Expression, LuaCode, PrefixOperator, Primary,
 | 
			
		||||
            TemplateStringLiteral, TemplateStringLiteralPart,
 | 
			
		||||
        },
 | 
			
		||||
        expression::{Binary, BinaryOperator, Expression, LuaCode, PrefixOperator, Primary},
 | 
			
		||||
        program::{Namespace, ProgramFile},
 | 
			
		||||
        statement::{
 | 
			
		||||
            execute_block::{
 | 
			
		||||
| 
						 | 
				
			
			@ -584,7 +581,7 @@ impl Primary {
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Self::Boolean(_) | Self::Integer(_) | Self::StringLiteral(_) => Ok(()),
 | 
			
		||||
            Self::TemplateStringLiteral(lit) => lit.analyze_semantics(scope, handler),
 | 
			
		||||
            Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler),
 | 
			
		||||
            Self::FunctionCall(call) => {
 | 
			
		||||
                let var = scope.get_variable(call.identifier().span.str());
 | 
			
		||||
                var.map_or_else(
 | 
			
		||||
| 
						 | 
				
			
			@ -722,7 +719,7 @@ impl Primary {
 | 
			
		|||
        match self {
 | 
			
		||||
            Self::Boolean(_) => expected == ValueType::Boolean,
 | 
			
		||||
            Self::Integer(_) => expected == ValueType::Integer,
 | 
			
		||||
            Self::StringLiteral(_) | Self::TemplateStringLiteral(_) => {
 | 
			
		||||
            Self::StringLiteral(_) | Self::MacroStringLiteral(_) => {
 | 
			
		||||
                matches!(expected, ValueType::String | ValueType::Boolean)
 | 
			
		||||
            }
 | 
			
		||||
            Self::FunctionCall(_) => matches!(expected, ValueType::Boolean | ValueType::Integer),
 | 
			
		||||
| 
						 | 
				
			
			@ -951,12 +948,12 @@ impl AnyStringLiteral {
 | 
			
		|||
    ) -> Result<(), error::Error> {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::StringLiteral(_) => Ok(()),
 | 
			
		||||
            Self::TemplateStringLiteral(lit) => lit.analyze_semantics(scope, handler),
 | 
			
		||||
            Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TemplateStringLiteral {
 | 
			
		||||
impl MacroStringLiteral {
 | 
			
		||||
    fn analyze_semantics(
 | 
			
		||||
        &self,
 | 
			
		||||
        scope: &SemanticScope,
 | 
			
		||||
| 
						 | 
				
			
			@ -966,76 +963,25 @@ impl TemplateStringLiteral {
 | 
			
		|||
 | 
			
		||||
        for part in self.parts() {
 | 
			
		||||
            match part {
 | 
			
		||||
                TemplateStringLiteralPart::Expression { expression, .. } => {
 | 
			
		||||
                    match expression.as_ref() {
 | 
			
		||||
                        Expression::Primary(Primary::Identifier(identifier)) => {
 | 
			
		||||
                            if let Some(variable_type) = scope.get_variable(identifier.span.str()) {
 | 
			
		||||
                                // TODO: template string correct checks
 | 
			
		||||
                                // if variable_type != VariableType::MacroParameter {
 | 
			
		||||
                                //     let err = error::Error::UnexpectedExpression(UnexpectedExpression(
 | 
			
		||||
                                //         Box::new(Expression::Primary(Primary::Identifier(
 | 
			
		||||
                                //             identifier.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);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        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(),
 | 
			
		||||
                            ));
 | 
			
		||||
                MacroStringLiteralPart::MacroUsage { identifier, .. } => {
 | 
			
		||||
                    if let Some(variable_type) = scope.get_variable(identifier.span.str()) {
 | 
			
		||||
                        if variable_type != VariableType::MacroParameter {
 | 
			
		||||
                            let err =
 | 
			
		||||
                                error::Error::UnexpectedExpression(UnexpectedExpression(Box::new(
 | 
			
		||||
                                    Expression::Primary(Primary::Identifier(identifier.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);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                TemplateStringLiteralPart::Text(_) => {}
 | 
			
		||||
                MacroStringLiteralPart::Text(_) => {}
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								src/serde.rs
								
								
								
								
							
							
						
						
									
										10
									
								
								src/serde.rs
								
								
								
								
							| 
						 | 
				
			
			@ -275,8 +275,14 @@ mod tests {
 | 
			
		|||
            serde_json::from_str::<SerdeWrapper<ProgramFile>>(&serialized).unwrap();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            Arc::as_ptr(deserialized.namespace().keyword().span.source_file()),
 | 
			
		||||
            Arc::as_ptr(deserialized.namespace().name().span.source_file())
 | 
			
		||||
            Arc::as_ptr(
 | 
			
		||||
                deserialized
 | 
			
		||||
                    .namespace()
 | 
			
		||||
                    .namespace_keyword()
 | 
			
		||||
                    .span
 | 
			
		||||
                    .source_file()
 | 
			
		||||
            ),
 | 
			
		||||
            Arc::as_ptr(deserialized.namespace().namespace_name().span.source_file())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,7 +37,7 @@ pub enum SyntaxKind {
 | 
			
		|||
    Integer,
 | 
			
		||||
    Boolean,
 | 
			
		||||
    StringLiteral,
 | 
			
		||||
    TemplateStringLiteralPart,
 | 
			
		||||
    MacroStringLiteral,
 | 
			
		||||
    AnyStringLiteral,
 | 
			
		||||
    Statement,
 | 
			
		||||
    Expression,
 | 
			
		||||
| 
						 | 
				
			
			@ -76,8 +76,8 @@ impl SyntaxKind {
 | 
			
		|||
            Self::Integer => "an integer token".to_string(),
 | 
			
		||||
            Self::Boolean => "a boolean token".to_string(),
 | 
			
		||||
            Self::StringLiteral => "a string literal".to_string(),
 | 
			
		||||
            Self::TemplateStringLiteralPart => "part of a template string literal".to_string(),
 | 
			
		||||
            Self::AnyStringLiteral => "a (template) string literal".to_string(),
 | 
			
		||||
            Self::MacroStringLiteral => "a macro string literal".to_string(),
 | 
			
		||||
            Self::AnyStringLiteral => "a (macro) string literal".to_string(),
 | 
			
		||||
            Self::Statement => "a statement syntax".to_string(),
 | 
			
		||||
            Self::Expression => "an expression syntax".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::CommandLiteral(..)) => "a literal command token".to_string(),
 | 
			
		||||
            Some(Token::StringLiteral(..)) => "a string literal token".to_string(),
 | 
			
		||||
            Some(Token::TemplateStringText(..)) => "a template string text token".to_string(),
 | 
			
		||||
            Some(Token::MacroStringLiteral(..)) => "a macro string literal token".to_string(),
 | 
			
		||||
 | 
			
		||||
            None => "EOF".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,12 +6,18 @@ use enum_as_inner::EnumAsInner;
 | 
			
		|||
use crate::{
 | 
			
		||||
    base::{self, Handler},
 | 
			
		||||
    lexical::{
 | 
			
		||||
        token::{Identifier, Integer, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
 | 
			
		||||
        token::{
 | 
			
		||||
            Identifier, Integer, Keyword, KeywordKind, MacroStringLiteral, Punctuation,
 | 
			
		||||
            StringLiteral, Token,
 | 
			
		||||
        },
 | 
			
		||||
        token_stream::{Delimited, Delimiter, TokenStream, TokenTree},
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax};
 | 
			
		||||
use super::{
 | 
			
		||||
    error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
 | 
			
		||||
    syntax_tree::AnyStringLiteral,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Represents a parser that reads a token stream and constructs an abstract syntax tree.
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut)]
 | 
			
		||||
| 
						 | 
				
			
			@ -432,6 +438,49 @@ 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.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Errors
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -344,6 +344,7 @@ impl Parser<'_> {
 | 
			
		|||
    ///
 | 
			
		||||
    /// # Errors
 | 
			
		||||
    /// - cannot parse declaration from current position
 | 
			
		||||
    #[expect(clippy::too_many_lines)]
 | 
			
		||||
    #[tracing::instrument(level = "trace", skip_all)]
 | 
			
		||||
    pub fn parse_declaration(
 | 
			
		||||
        &mut self,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,16 +13,15 @@ use crate::{
 | 
			
		|||
    },
 | 
			
		||||
    lexical::{
 | 
			
		||||
        token::{
 | 
			
		||||
            Boolean, Identifier, Integer, Keyword, KeywordKind, Punctuation, StringLiteral,
 | 
			
		||||
            TemplateStringLiteralText, Token,
 | 
			
		||||
            Boolean, Identifier, Integer, Keyword, KeywordKind, MacroStringLiteral, Punctuation,
 | 
			
		||||
            StringLiteral, Token,
 | 
			
		||||
        },
 | 
			
		||||
        token_stream::Delimiter,
 | 
			
		||||
    },
 | 
			
		||||
    syntax::{
 | 
			
		||||
        self,
 | 
			
		||||
        error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
 | 
			
		||||
        error::{Error, ParseResult, UnexpectedSyntax},
 | 
			
		||||
        parser::{Parser, Reading},
 | 
			
		||||
        syntax_tree::AnyStringLiteral,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -178,7 +177,7 @@ impl SourceElement for Expression {
 | 
			
		|||
///     | StringLiteral
 | 
			
		||||
///     | FunctionCall
 | 
			
		||||
///     | MemberAccess
 | 
			
		||||
///     | TemplateStringLiteral
 | 
			
		||||
///     | MacroStringLiteral
 | 
			
		||||
///     | LuaCode
 | 
			
		||||
/// ```
 | 
			
		||||
#[allow(missing_docs)]
 | 
			
		||||
| 
						 | 
				
			
			@ -194,7 +193,7 @@ pub enum Primary {
 | 
			
		|||
    StringLiteral(StringLiteral),
 | 
			
		||||
    FunctionCall(FunctionCall),
 | 
			
		||||
    MemberAccess(MemberAccess),
 | 
			
		||||
    TemplateStringLiteral(TemplateStringLiteral),
 | 
			
		||||
    MacroStringLiteral(MacroStringLiteral),
 | 
			
		||||
    Lua(Box<LuaCode>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -210,7 +209,7 @@ impl SourceElement for Primary {
 | 
			
		|||
            Self::StringLiteral(string_literal) => string_literal.span(),
 | 
			
		||||
            Self::FunctionCall(function_call) => function_call.span(),
 | 
			
		||||
            Self::MemberAccess(member_access) => member_access.span(),
 | 
			
		||||
            Self::TemplateStringLiteral(template_string_literal) => template_string_literal.span(),
 | 
			
		||||
            Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
 | 
			
		||||
            Self::Lua(lua_code) => lua_code.span(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -392,70 +391,6 @@ 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.
 | 
			
		||||
///
 | 
			
		||||
/// Syntax Synopsis:
 | 
			
		||||
| 
						 | 
				
			
			@ -654,13 +589,6 @@ 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
 | 
			
		||||
            Reading::IntoDelimited(left_parenthesis) if left_parenthesis.punctuation == '(' => self
 | 
			
		||||
                .parse_parenthesized(handler)
 | 
			
		||||
| 
						 | 
				
			
			@ -733,6 +661,14 @@ impl Parser<'_> {
 | 
			
		|||
                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
 | 
			
		||||
            Reading::Atomic(Token::Keyword(lua_keyword))
 | 
			
		||||
                if lua_keyword.keyword == KeywordKind::Lua =>
 | 
			
		||||
| 
						 | 
				
			
			@ -907,88 +843,4 @@ 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,
 | 
			
		||||
    },
 | 
			
		||||
    lexical::{
 | 
			
		||||
        token::{Identifier, Punctuation, StringLiteral, Token},
 | 
			
		||||
        token::{Identifier, MacroStringLiteral, Punctuation, StringLiteral, Token},
 | 
			
		||||
        token_stream::Delimiter,
 | 
			
		||||
    },
 | 
			
		||||
    syntax::{parser::Reading, syntax_tree::expression::TemplateStringLiteral},
 | 
			
		||||
    syntax::parser::Reading,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
| 
						 | 
				
			
			@ -69,25 +69,25 @@ pub struct DelimitedList<T> {
 | 
			
		|||
    pub close: Punctuation,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a syntax tree node that can be either a string literal or a template string literal.
 | 
			
		||||
/// Represents a syntax tree node that can be either a string literal or a macro string literal.
 | 
			
		||||
///
 | 
			
		||||
/// Syntax Synopsis:
 | 
			
		||||
/// ```ebnf
 | 
			
		||||
/// AnyStringLiteral: StringLiteral | TemplateStringLiteral ;
 | 
			
		||||
/// AnyStringLiteral: StringLiteral | MacroStringLiteral ;
 | 
			
		||||
/// ```
 | 
			
		||||
#[allow(missing_docs)]
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From)]
 | 
			
		||||
pub enum AnyStringLiteral {
 | 
			
		||||
    StringLiteral(StringLiteral),
 | 
			
		||||
    TemplateStringLiteral(TemplateStringLiteral),
 | 
			
		||||
    MacroStringLiteral(MacroStringLiteral),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SourceElement for AnyStringLiteral {
 | 
			
		||||
    fn span(&self) -> Span {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::StringLiteral(string_literal) => string_literal.span(),
 | 
			
		||||
            Self::TemplateStringLiteral(template_string_literal) => template_string_literal.span(),
 | 
			
		||||
            Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1010,6 +1010,7 @@ impl Parser<'_> {
 | 
			
		|||
    ///
 | 
			
		||||
    /// # Errors
 | 
			
		||||
    /// - cannot parse variable declaration from current position
 | 
			
		||||
    #[expect(clippy::too_many_lines)]
 | 
			
		||||
    #[tracing::instrument(level = "trace", skip_all)]
 | 
			
		||||
    pub fn parse_variable_declaration(
 | 
			
		||||
        &mut self,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,16 +1,8 @@
 | 
			
		|||
//! 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 crate::{
 | 
			
		||||
    base::{self, Handler},
 | 
			
		||||
    semantic::error::UnexpectedExpression,
 | 
			
		||||
    syntax::syntax_tree::expression::{TemplateStringLiteral, TemplateStringLiteralPart},
 | 
			
		||||
    transpile::{Scope, TranspileError, TranspileResult},
 | 
			
		||||
    util,
 | 
			
		||||
};
 | 
			
		||||
use crate::{lexical::token::MacroStringLiteral, syntax::syntax_tree::AnyStringLiteral};
 | 
			
		||||
 | 
			
		||||
use super::util::{MacroString, MacroStringPart};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -34,38 +26,26 @@ impl From<MacroStringPart> for ExtMacroStringPart {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TemplateStringLiteral {
 | 
			
		||||
    pub fn as_str(
 | 
			
		||||
        &self,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Cow<'_, str>> {
 | 
			
		||||
        let mut res = Cow::Borrowed("");
 | 
			
		||||
 | 
			
		||||
        for part in &self.parts {
 | 
			
		||||
            match part {
 | 
			
		||||
                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);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(res)
 | 
			
		||||
impl From<&AnyStringLiteral> for ExtMacroString {
 | 
			
		||||
    fn from(value: &AnyStringLiteral) -> Self {
 | 
			
		||||
        Self::from(MacroString::from(value))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<AnyStringLiteral> for ExtMacroString {
 | 
			
		||||
    fn from(value: AnyStringLiteral) -> Self {
 | 
			
		||||
        Self::from(&value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&MacroStringLiteral> for ExtMacroString {
 | 
			
		||||
    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")]
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::{self, source_file::SourceElement, Handler, VoidHandler},
 | 
			
		||||
    lexical::token::{Identifier, StringLiteral},
 | 
			
		||||
    lexical::token::{Identifier, MacroStringLiteralPart, StringLiteral},
 | 
			
		||||
    syntax::syntax_tree::expression::{
 | 
			
		||||
        Binary, BinaryOperator, Expression, Indexed, MemberAccess, Parenthesized, PrefixOperator,
 | 
			
		||||
        Primary,
 | 
			
		||||
| 
						 | 
				
			
			@ -337,7 +337,7 @@ impl Primary {
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Self::StringLiteral(_) | Self::TemplateStringLiteral(_) => {
 | 
			
		||||
            Self::StringLiteral(_) | Self::MacroStringLiteral(_) => {
 | 
			
		||||
                matches!(r#type, ValueType::String | ValueType::Boolean)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -412,29 +412,17 @@ impl Primary {
 | 
			
		|||
                    expression: lua.span(),
 | 
			
		||||
                })
 | 
			
		||||
                .and_then(|val| val),
 | 
			
		||||
            Self::TemplateStringLiteral(template_string_literal) => {
 | 
			
		||||
                use crate::syntax::syntax_tree::expression::TemplateStringLiteralPart;
 | 
			
		||||
 | 
			
		||||
                if template_string_literal
 | 
			
		||||
            Self::MacroStringLiteral(macro_string_literal) => {
 | 
			
		||||
                if macro_string_literal
 | 
			
		||||
                    .parts()
 | 
			
		||||
                    .iter()
 | 
			
		||||
                    .any(|part| matches!(part, TemplateStringLiteralPart::Expression { .. }))
 | 
			
		||||
                    .any(|part| matches!(part, MacroStringLiteralPart::MacroUsage { .. }))
 | 
			
		||||
                {
 | 
			
		||||
                    template_string_literal
 | 
			
		||||
                        .to_macro_string(scope, handler)
 | 
			
		||||
                        .map(ComptimeValue::MacroString)
 | 
			
		||||
                        .map_err(|_| NotComptime {
 | 
			
		||||
                            expression: template_string_literal.span(),
 | 
			
		||||
                        })
 | 
			
		||||
                } else {
 | 
			
		||||
                    Ok(ComptimeValue::String(
 | 
			
		||||
                        template_string_literal
 | 
			
		||||
                            .as_str(scope, handler)
 | 
			
		||||
                            .map_err(|_| NotComptime {
 | 
			
		||||
                                expression: template_string_literal.span(),
 | 
			
		||||
                            })?
 | 
			
		||||
                            .into_owned(),
 | 
			
		||||
                    Ok(ComptimeValue::MacroString(
 | 
			
		||||
                        macro_string_literal.clone().into(),
 | 
			
		||||
                    ))
 | 
			
		||||
                } else {
 | 
			
		||||
                    Ok(ComptimeValue::String(macro_string_literal.str_content()))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -458,7 +446,7 @@ impl Primary {
 | 
			
		|||
            | Self::FunctionCall(_)
 | 
			
		||||
            | Self::Integer(_)
 | 
			
		||||
            | Self::Lua(_)
 | 
			
		||||
            | Self::TemplateStringLiteral(_)
 | 
			
		||||
            | Self::MacroStringLiteral(_)
 | 
			
		||||
            | Self::MemberAccess(_)
 | 
			
		||||
            | Self::Prefix(_) => Err(NotComptime {
 | 
			
		||||
                expression: self.span(),
 | 
			
		||||
| 
						 | 
				
			
			@ -488,7 +476,7 @@ impl Primary {
 | 
			
		|||
            | Self::FunctionCall(_)
 | 
			
		||||
            | Self::Integer(_)
 | 
			
		||||
            | Self::Lua(_)
 | 
			
		||||
            | Self::TemplateStringLiteral(_)
 | 
			
		||||
            | Self::MacroStringLiteral(_)
 | 
			
		||||
            | Self::MemberAccess(_)
 | 
			
		||||
            | Self::Prefix(_) => false,
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1272,7 +1260,7 @@ impl Transpiler {
 | 
			
		|||
                    Err(err)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Primary::StringLiteral(_) | Primary::TemplateStringLiteral(_) => {
 | 
			
		||||
            Primary::StringLiteral(_) | Primary::MacroStringLiteral(_) => {
 | 
			
		||||
                if matches!(
 | 
			
		||||
                    target,
 | 
			
		||||
                    DataLocation::Storage {
 | 
			
		||||
| 
						 | 
				
			
			@ -1674,11 +1662,9 @@ impl Transpiler {
 | 
			
		|||
                Vec::new(),
 | 
			
		||||
                ExtendedCondition::Runtime(Condition::Atom(s.str_content().to_string().into())),
 | 
			
		||||
            )),
 | 
			
		||||
            Primary::TemplateStringLiteral(template_string) => Ok((
 | 
			
		||||
            Primary::MacroStringLiteral(macro_string) => Ok((
 | 
			
		||||
                Vec::new(),
 | 
			
		||||
                ExtendedCondition::Runtime(Condition::Atom(
 | 
			
		||||
                    template_string.to_macro_string(scope, handler)?.into(),
 | 
			
		||||
                )),
 | 
			
		||||
                ExtendedCondition::Runtime(Condition::Atom(macro_string.into())),
 | 
			
		||||
            )),
 | 
			
		||||
            Primary::FunctionCall(func) => {
 | 
			
		||||
                if func
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -448,8 +448,8 @@ impl Transpiler {
 | 
			
		|||
                            Expression::Primary(Primary::StringLiteral(string)) => {
 | 
			
		||||
                                Ok(Parameter::Static(string.str_content().to_string().into()))
 | 
			
		||||
                            }
 | 
			
		||||
                            Expression::Primary(Primary::TemplateStringLiteral(literal)) => {
 | 
			
		||||
                                Ok(Parameter::Static(literal.to_macro_string(scope, handler)?))
 | 
			
		||||
                            Expression::Primary(Primary::MacroStringLiteral(literal)) => {
 | 
			
		||||
                                Ok(Parameter::Static(literal.into()))
 | 
			
		||||
                            }
 | 
			
		||||
                            Expression::Primary(primary @ Primary::Identifier(ident)) => {
 | 
			
		||||
                                let var =
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,13 +12,11 @@ use serde_json::{json, Value as JsonValue};
 | 
			
		|||
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::{source_file::SourceElement as _, VoidHandler},
 | 
			
		||||
    lexical::token::Identifier,
 | 
			
		||||
    lexical::token::{Identifier, MacroStringLiteralPart},
 | 
			
		||||
    semantic::error::{InvalidFunctionArguments, UnexpectedExpression},
 | 
			
		||||
    syntax::syntax_tree::expression::{
 | 
			
		||||
        Expression, FunctionCall, Primary, TemplateStringLiteralPart,
 | 
			
		||||
    },
 | 
			
		||||
    syntax::syntax_tree::expression::{Expression, FunctionCall, Primary},
 | 
			
		||||
    transpile::{
 | 
			
		||||
        error::{IllegalIndexing, IllegalIndexingReason, NotComptime, UnknownIdentifier},
 | 
			
		||||
        error::{IllegalIndexing, IllegalIndexingReason, UnknownIdentifier},
 | 
			
		||||
        expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
 | 
			
		||||
        util::MacroString,
 | 
			
		||||
        TranspileError,
 | 
			
		||||
| 
						 | 
				
			
			@ -97,6 +95,7 @@ fn print_function(
 | 
			
		|||
) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
    const PARAM_COLOR: &str = "gray";
 | 
			
		||||
 | 
			
		||||
    #[expect(clippy::option_if_let_else)]
 | 
			
		||||
    fn get_identifier_part(
 | 
			
		||||
        ident: &Identifier,
 | 
			
		||||
        transpiler: &mut Transpiler,
 | 
			
		||||
| 
						 | 
				
			
			@ -132,158 +131,6 @@ fn print_function(
 | 
			
		|||
 | 
			
		||||
                    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(
 | 
			
		||||
                    Box::new(Expression::Primary(Primary::Identifier(ident.to_owned()))),
 | 
			
		||||
                ))),
 | 
			
		||||
| 
						 | 
				
			
			@ -345,7 +192,7 @@ fn print_function(
 | 
			
		|||
        ("@a".into(), first)
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let mut contains_macro = target.contains_macros();
 | 
			
		||||
    let mut contains_macro = matches!(target, MacroString::MacroString(_));
 | 
			
		||||
 | 
			
		||||
    let (mut cmds, parts) = match message_expression {
 | 
			
		||||
        Expression::Primary(primary) => match primary {
 | 
			
		||||
| 
						 | 
				
			
			@ -496,43 +343,20 @@ fn print_function(
 | 
			
		|||
                    reason: IllegalIndexingReason::NotIdentifier,
 | 
			
		||||
                })),
 | 
			
		||||
            },
 | 
			
		||||
            Primary::TemplateStringLiteral(template_string) => {
 | 
			
		||||
            Primary::MacroStringLiteral(macro_string) => {
 | 
			
		||||
                let mut cmds = Vec::new();
 | 
			
		||||
                let mut parts = Vec::new();
 | 
			
		||||
                for part in template_string.parts() {
 | 
			
		||||
                for part in macro_string.parts() {
 | 
			
		||||
                    match part {
 | 
			
		||||
                        TemplateStringLiteralPart::Text(text) => {
 | 
			
		||||
                            parts.push(JsonValue::String(text.span.str().to_string()));
 | 
			
		||||
                        MacroStringLiteralPart::Text(text) => {
 | 
			
		||||
                            parts.push(JsonValue::String(text.str().to_string()));
 | 
			
		||||
                        }
 | 
			
		||||
                        TemplateStringLiteralPart::Expression { expression, .. } => {
 | 
			
		||||
                            match expression.as_ref() {
 | 
			
		||||
                                Expression::Primary(Primary::Identifier(identifier)) => {
 | 
			
		||||
                                    let (cur_contains_macro, cur_cmds, part) =
 | 
			
		||||
                                        get_identifier_part(identifier, transpiler, scope)?;
 | 
			
		||||
                                    contains_macro |= cur_contains_macro;
 | 
			
		||||
                                    cmds.extend(cur_cmds);
 | 
			
		||||
                                    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"),
 | 
			
		||||
                            }
 | 
			
		||||
                        MacroStringLiteralPart::MacroUsage { identifier, .. } => {
 | 
			
		||||
                            let (cur_contains_macro, cur_cmds, part) =
 | 
			
		||||
                                get_identifier_part(identifier, transpiler, scope)?;
 | 
			
		||||
                            contains_macro |= cur_contains_macro;
 | 
			
		||||
                            cmds.extend(cur_cmds);
 | 
			
		||||
                            parts.push(part);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -149,8 +149,6 @@ mod enabled {
 | 
			
		|||
 | 
			
		||||
            table.set("version", crate::VERSION)?;
 | 
			
		||||
 | 
			
		||||
            // TODO: add functions for requesting data/scoreboard locations
 | 
			
		||||
 | 
			
		||||
            Ok(table)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -325,41 +323,26 @@ mod enabled {
 | 
			
		|||
                    handler,
 | 
			
		||||
                ),
 | 
			
		||||
                Value::Boolean(boolean) => Ok(Some(ComptimeValue::Boolean(boolean))),
 | 
			
		||||
                Value::Table(table) => {
 | 
			
		||||
                    // TODO: allow to return arrays when comptime arrays are implemented
 | 
			
		||||
                    match table.get::<Value>("value") {
 | 
			
		||||
                        Ok(Value::Nil) => {
 | 
			
		||||
                            let err = TranspileError::LuaRuntimeError(LuaRuntimeError {
 | 
			
		||||
                                code_block: self.span(),
 | 
			
		||||
                                error_message: "return table must contain non-nil 'value'"
 | 
			
		||||
                                    .to_string(),
 | 
			
		||||
                            });
 | 
			
		||||
                            handler.receive(Box::new(err.clone()));
 | 
			
		||||
                            Err(err)
 | 
			
		||||
                        }
 | 
			
		||||
                        Ok(value) => {
 | 
			
		||||
                            let value = match self.handle_lua_result(value, handler)? {
 | 
			
		||||
                                Some(ComptimeValue::String(s)) => {
 | 
			
		||||
                                    let contains_macro = match table.get::<Value>("contains_macro")
 | 
			
		||||
                                    {
 | 
			
		||||
                                        Ok(Value::Boolean(boolean)) => Ok(boolean),
 | 
			
		||||
                                        Ok(value) => {
 | 
			
		||||
                                            if let Some(ComptimeValue::Boolean(boolean)) =
 | 
			
		||||
                                                self.handle_lua_result(value, handler)?
 | 
			
		||||
                                            {
 | 
			
		||||
                                                Ok(boolean)
 | 
			
		||||
                                            } else {
 | 
			
		||||
                                                let err = TranspileError::MismatchedTypes(
 | 
			
		||||
                                                    MismatchedTypes {
 | 
			
		||||
                                                        expression: self.span(),
 | 
			
		||||
                                                        expected_type: ExpectedType::Boolean,
 | 
			
		||||
                                                    },
 | 
			
		||||
                                                );
 | 
			
		||||
                                                handler.receive(Box::new(err.clone()));
 | 
			
		||||
                                                Err(err)
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                        _ => {
 | 
			
		||||
                Value::Table(table) => match table.get::<Value>("value") {
 | 
			
		||||
                    Ok(Value::Nil) => {
 | 
			
		||||
                        let err = TranspileError::LuaRuntimeError(LuaRuntimeError {
 | 
			
		||||
                            code_block: self.span(),
 | 
			
		||||
                            error_message: "return table must contain non-nil 'value'".to_string(),
 | 
			
		||||
                        });
 | 
			
		||||
                        handler.receive(Box::new(err.clone()));
 | 
			
		||||
                        Err(err)
 | 
			
		||||
                    }
 | 
			
		||||
                    Ok(value) => {
 | 
			
		||||
                        let value = match self.handle_lua_result(value, handler)? {
 | 
			
		||||
                            Some(ComptimeValue::String(s)) => {
 | 
			
		||||
                                let contains_macro = match table.get::<Value>("contains_macro") {
 | 
			
		||||
                                    Ok(Value::Boolean(boolean)) => Ok(boolean),
 | 
			
		||||
                                    Ok(value) => {
 | 
			
		||||
                                        if let Some(ComptimeValue::Boolean(boolean)) =
 | 
			
		||||
                                            self.handle_lua_result(value, handler)?
 | 
			
		||||
                                        {
 | 
			
		||||
                                            Ok(boolean)
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            let err =
 | 
			
		||||
                                                TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                                    expression: self.span(),
 | 
			
		||||
| 
						 | 
				
			
			@ -368,29 +351,39 @@ mod enabled {
 | 
			
		|||
                                            handler.receive(Box::new(err.clone()));
 | 
			
		||||
                                            Err(err)
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }?;
 | 
			
		||||
 | 
			
		||||
                                    if contains_macro {
 | 
			
		||||
                                        Some(ComptimeValue::MacroString(
 | 
			
		||||
                                            s.parse().expect("parsing cannot fail"),
 | 
			
		||||
                                        ))
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        Some(ComptimeValue::String(s))
 | 
			
		||||
                                    }
 | 
			
		||||
                                    _ => {
 | 
			
		||||
                                        let err =
 | 
			
		||||
                                            TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                                expression: self.span(),
 | 
			
		||||
                                                expected_type: ExpectedType::Boolean,
 | 
			
		||||
                                            });
 | 
			
		||||
                                        handler.receive(Box::new(err.clone()));
 | 
			
		||||
                                        Err(err)
 | 
			
		||||
                                    }
 | 
			
		||||
                                }?;
 | 
			
		||||
 | 
			
		||||
                                if contains_macro {
 | 
			
		||||
                                    Some(ComptimeValue::MacroString(
 | 
			
		||||
                                        s.parse().expect("parsing cannot fail"),
 | 
			
		||||
                                    ))
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    Some(ComptimeValue::String(s))
 | 
			
		||||
                                }
 | 
			
		||||
                                value => value,
 | 
			
		||||
                            };
 | 
			
		||||
                            Ok(value)
 | 
			
		||||
                        }
 | 
			
		||||
                        Err(err) => {
 | 
			
		||||
                            let err = TranspileError::LuaRuntimeError(
 | 
			
		||||
                                LuaRuntimeError::from_lua_err(&err, self.span()),
 | 
			
		||||
                            );
 | 
			
		||||
                            handler.receive(Box::new(err.clone()));
 | 
			
		||||
                            Err(err)
 | 
			
		||||
                        }
 | 
			
		||||
                            }
 | 
			
		||||
                            value => value,
 | 
			
		||||
                        };
 | 
			
		||||
                        Ok(value)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                    Err(err) => {
 | 
			
		||||
                        let err = TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err(
 | 
			
		||||
                            &err,
 | 
			
		||||
                            self.span(),
 | 
			
		||||
                        ));
 | 
			
		||||
                        handler.receive(Box::new(err.clone()));
 | 
			
		||||
                        Err(err)
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                Value::Error(_)
 | 
			
		||||
                | Value::Thread(_)
 | 
			
		||||
                | Value::UserData(_)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -69,6 +69,7 @@ impl Transpiler {
 | 
			
		|||
    ///
 | 
			
		||||
    /// # Errors
 | 
			
		||||
    /// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing
 | 
			
		||||
    #[expect(clippy::too_many_lines)]
 | 
			
		||||
    #[tracing::instrument(level = "trace", skip_all)]
 | 
			
		||||
    pub fn transpile(
 | 
			
		||||
        mut self,
 | 
			
		||||
| 
						 | 
				
			
			@ -644,9 +645,7 @@ impl Transpiler {
 | 
			
		|||
            Primary::StringLiteral(string) => {
 | 
			
		||||
                Ok(vec![Command::Raw(string.str_content().to_string())])
 | 
			
		||||
            }
 | 
			
		||||
            Primary::TemplateStringLiteral(string) => Ok(vec![Command::UsesMacro(
 | 
			
		||||
                string.to_macro_string(scope, handler)?.into(),
 | 
			
		||||
            )]),
 | 
			
		||||
            Primary::MacroStringLiteral(string) => Ok(vec![Command::UsesMacro(string.into())]),
 | 
			
		||||
            Primary::Lua(code) => match code.eval_comptime(scope, handler)? {
 | 
			
		||||
                Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
 | 
			
		||||
                Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]),
 | 
			
		||||
| 
						 | 
				
			
			@ -988,41 +987,37 @@ impl Transpiler {
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::As(r#as) => {
 | 
			
		||||
                let selector = r#as.as_selector().to_macro_string(scope, handler)?;
 | 
			
		||||
                let selector = r#as.as_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::As(selector.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::At(at) => {
 | 
			
		||||
                let selector = at.at_selector().to_macro_string(scope, handler)?;
 | 
			
		||||
                let selector = at.at_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::At(selector.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::Align(align) => {
 | 
			
		||||
                let align = align.align_selector().to_macro_string(scope, handler)?;
 | 
			
		||||
                let align = align.align_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::Align(align.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::Anchored(anchored) => {
 | 
			
		||||
                let anchor = anchored
 | 
			
		||||
                    .anchored_selector()
 | 
			
		||||
                    .to_macro_string(scope, handler)?;
 | 
			
		||||
                let anchor = anchored.anchored_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::Anchored(anchor.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::In(r#in) => {
 | 
			
		||||
                let dimension = r#in.in_selector().to_macro_string(scope, handler)?;
 | 
			
		||||
                let dimension = r#in.in_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::In(dimension.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::Positioned(positioned) => {
 | 
			
		||||
                let position = positioned
 | 
			
		||||
                    .positioned_selector()
 | 
			
		||||
                    .to_macro_string(scope, handler)?;
 | 
			
		||||
                let position = positioned.positioned_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (
 | 
			
		||||
                        pre_cmds,
 | 
			
		||||
| 
						 | 
				
			
			@ -1031,37 +1026,37 @@ impl Transpiler {
 | 
			
		|||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::Rotated(rotated) => {
 | 
			
		||||
                let rotation = rotated.rotated_selector().to_macro_string(scope, handler)?;
 | 
			
		||||
                let rotation = rotated.rotated_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::Rotated(rotation.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::Facing(facing) => {
 | 
			
		||||
                let facing = facing.facing_selector().to_macro_string(scope, handler)?;
 | 
			
		||||
                let facing = facing.facing_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::Facing(facing.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::AsAt(as_at) => {
 | 
			
		||||
                let selector = as_at.asat_selector().to_macro_string(scope, handler)?;
 | 
			
		||||
                let selector = as_at.asat_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::AsAt(selector.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::On(on) => {
 | 
			
		||||
                let dimension = on.on_selector().to_macro_string(scope, handler)?;
 | 
			
		||||
                let dimension = on.on_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::On(dimension.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::Store(store) => {
 | 
			
		||||
                let store = store.store_selector().to_macro_string(scope, handler)?;
 | 
			
		||||
                let store = store.store_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::Store(store.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            ExecuteBlockHead::Summon(summon) => {
 | 
			
		||||
                let entity = summon.summon_selector().to_macro_string(scope, handler)?;
 | 
			
		||||
                let entity = summon.summon_selector();
 | 
			
		||||
                tail.map(|(pre_cmds, tail)| {
 | 
			
		||||
                    (pre_cmds, Execute::Summon(entity.into(), Box::new(tail)))
 | 
			
		||||
                })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,21 +1,12 @@
 | 
			
		|||
//! Utility methods for transpiling
 | 
			
		||||
 | 
			
		||||
use std::{fmt::Display, str::FromStr, sync::Arc};
 | 
			
		||||
use std::{fmt::Display, str::FromStr};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::{self, source_file::SourceElement as _, Handler},
 | 
			
		||||
    syntax::syntax_tree::{
 | 
			
		||||
        expression::{Expression, Primary, TemplateStringLiteral, TemplateStringLiteralPart},
 | 
			
		||||
        AnyStringLiteral,
 | 
			
		||||
    },
 | 
			
		||||
    transpile::{
 | 
			
		||||
        error::{TranspileError, UnknownIdentifier},
 | 
			
		||||
        Scope, TranspileResult, VariableData,
 | 
			
		||||
    },
 | 
			
		||||
    lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
 | 
			
		||||
    syntax::syntax_tree::AnyStringLiteral,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::expression::ComptimeValue;
 | 
			
		||||
 | 
			
		||||
/// String that can contain macros
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 | 
			
		||||
| 
						 | 
				
			
			@ -256,93 +247,56 @@ where
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AnyStringLiteral {
 | 
			
		||||
    /// Convert the any string literal to a macro string, using the provided scope to resolve variables
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Errors
 | 
			
		||||
    /// - 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 {
 | 
			
		||||
    fn from(value: &AnyStringLiteral) -> Self {
 | 
			
		||||
        match value {
 | 
			
		||||
            AnyStringLiteral::StringLiteral(literal) => Self::from(literal.str_content().as_ref()),
 | 
			
		||||
            AnyStringLiteral::MacroStringLiteral(literal) => Self::from(literal),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TemplateStringLiteral {
 | 
			
		||||
    /// Convert the template string literal to a macro string, using the provided scope to resolve variables
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Errors
 | 
			
		||||
    /// - 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> {
 | 
			
		||||
        if self
 | 
			
		||||
impl From<AnyStringLiteral> for MacroString {
 | 
			
		||||
    fn from(value: AnyStringLiteral) -> Self {
 | 
			
		||||
        Self::from(&value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&MacroStringLiteral> for MacroString {
 | 
			
		||||
    fn from(value: &MacroStringLiteral) -> Self {
 | 
			
		||||
        if value
 | 
			
		||||
            .parts()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .any(|p| matches!(p, TemplateStringLiteralPart::Expression { .. }))
 | 
			
		||||
            .any(|p| matches!(p, MacroStringLiteralPart::MacroUsage { .. }))
 | 
			
		||||
        {
 | 
			
		||||
            let macro_string = MacroString::MacroString(
 | 
			
		||||
                self.parts()
 | 
			
		||||
            Self::MacroString(
 | 
			
		||||
                value
 | 
			
		||||
                    .parts()
 | 
			
		||||
                    .iter()
 | 
			
		||||
                    .map(|part| match part {
 | 
			
		||||
                        TemplateStringLiteralPart::Text(text) => Ok(MacroStringPart::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()),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                            VariableData::ComptimeValue { value, .. } => {
 | 
			
		||||
                                                let value = value.read().unwrap().as_ref().map_or_else(
 | 
			
		||||
                                                    || "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"),
 | 
			
		||||
                            }
 | 
			
		||||
                        MacroStringLiteralPart::Text(span) => MacroStringPart::String(
 | 
			
		||||
                            crate::util::unescape_macro_string(span.str()).to_string(),
 | 
			
		||||
                        ),
 | 
			
		||||
                        MacroStringLiteralPart::MacroUsage { identifier, .. } => {
 | 
			
		||||
                            MacroStringPart::MacroUsage(
 | 
			
		||||
                                crate::util::identifier_to_macro(identifier.span.str()).to_string(),
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                    .collect::<TranspileResult<Vec<MacroStringPart>>>()?,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            Ok(macro_string)
 | 
			
		||||
                    .collect(),
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(MacroString::String(self.as_str(scope, handler)?.into_owned()))
 | 
			
		||||
            Self::String(value.str_content())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<MacroStringLiteral> for MacroString {
 | 
			
		||||
    fn from(value: MacroStringLiteral) -> Self {
 | 
			
		||||
        Self::from(&value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										21
									
								
								src/util.rs
								
								
								
								
							
							
						
						
									
										21
									
								
								src/util.rs
								
								
								
								
							| 
						 | 
				
			
			@ -20,16 +20,15 @@ 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]
 | 
			
		||||
pub fn unescape_template_string(s: &str) -> Cow<'_, str> {
 | 
			
		||||
pub fn unescape_macro_string(s: &str) -> Cow<'_, str> {
 | 
			
		||||
    if s.contains('\\') || s.contains('`') {
 | 
			
		||||
        Cow::Owned(
 | 
			
		||||
            s.replace("\\n", "\n")
 | 
			
		||||
                .replace("\\r", "\r")
 | 
			
		||||
                .replace("\\t", "\t")
 | 
			
		||||
                .replace("\\`", "`")
 | 
			
		||||
                .replace("\\$", "$")
 | 
			
		||||
                .replace("\\\\", "\\"),
 | 
			
		||||
        )
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -148,33 +147,33 @@ mod tests {
 | 
			
		|||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_unescape_macro_string() {
 | 
			
		||||
        assert_eq!(unescape_template_string("Hello, world!"), "Hello, world!");
 | 
			
		||||
        assert_eq!(unescape_macro_string("Hello, world!"), "Hello, world!");
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            unescape_template_string(r#"Hello, "world"!"#),
 | 
			
		||||
            unescape_macro_string(r#"Hello, "world"!"#),
 | 
			
		||||
            r#"Hello, "world"!"#
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            unescape_template_string(r"Hello, \world\!"),
 | 
			
		||||
            unescape_macro_string(r"Hello, \world\!"),
 | 
			
		||||
            r"Hello, \world\!"
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            unescape_template_string(r"Hello, \nworld\!"),
 | 
			
		||||
            unescape_macro_string(r"Hello, \nworld\!"),
 | 
			
		||||
            "Hello, \nworld\\!"
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            unescape_template_string(r"Hello, \rworld\!"),
 | 
			
		||||
            unescape_macro_string(r"Hello, \rworld\!"),
 | 
			
		||||
            "Hello, \rworld\\!"
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            unescape_template_string(r"Hello, \tworld\!"),
 | 
			
		||||
            unescape_macro_string(r"Hello, \tworld\!"),
 | 
			
		||||
            "Hello, \tworld\\!"
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            unescape_template_string(r"Hello, \`world\!"),
 | 
			
		||||
            unescape_macro_string(r"Hello, \`world\!"),
 | 
			
		||||
            r"Hello, `world\!"
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            unescape_template_string(r"Hello, \\world\!"),
 | 
			
		||||
            unescape_macro_string(r"Hello, \\world\!"),
 | 
			
		||||
            r"Hello, \world\!"
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue