implement integer and boolean function arguments
This commit is contained in:
		
							parent
							
								
									ca0edfc5bc
								
							
						
					
					
						commit
						f3b3d5d3b6
					
				| 
						 | 
				
			
			@ -52,6 +52,7 @@ pub enum KeywordKind {
 | 
			
		|||
    Replace,
 | 
			
		||||
    Int,
 | 
			
		||||
    Bool,
 | 
			
		||||
    Macro,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Display for KeywordKind {
 | 
			
		||||
| 
						 | 
				
			
			@ -117,6 +118,7 @@ impl KeywordKind {
 | 
			
		|||
            Self::Replace => "replace",
 | 
			
		||||
            Self::Int => "int",
 | 
			
		||||
            Self::Bool => "bool",
 | 
			
		||||
            Self::Macro => "macro",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -165,7 +165,7 @@ impl Function {
 | 
			
		|||
 | 
			
		||||
            parameters
 | 
			
		||||
                .elements()
 | 
			
		||||
                .map(|el| el.span.str().to_string())
 | 
			
		||||
                .map(|el| el.identifier().span.str().to_string())
 | 
			
		||||
                .collect()
 | 
			
		||||
        } else {
 | 
			
		||||
            HashSet::new()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
 | 
			
		||||
use std::collections::VecDeque;
 | 
			
		||||
 | 
			
		||||
use enum_as_inner::EnumAsInner;
 | 
			
		||||
use getset::Getters;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +88,7 @@ impl Declaration {
 | 
			
		|||
///     ;
 | 
			
		||||
///
 | 
			
		||||
/// ParameterList:
 | 
			
		||||
///     Identifier (',' Identifier)* ','?  
 | 
			
		||||
///     FunctionArgument (',' FunctionArgument)* ','?  
 | 
			
		||||
///     ;
 | 
			
		||||
/// ```
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +105,7 @@ pub struct Function {
 | 
			
		|||
    #[get = "pub"]
 | 
			
		||||
    open_paren: Punctuation,
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    parameters: Option<ConnectedList<Identifier, Punctuation>>,
 | 
			
		||||
    parameters: Option<ConnectedList<FunctionParameter, Punctuation>>,
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    close_paren: Punctuation,
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +124,7 @@ impl Function {
 | 
			
		|||
        Keyword,
 | 
			
		||||
        Identifier,
 | 
			
		||||
        Punctuation,
 | 
			
		||||
        Option<ConnectedList<Identifier, Punctuation>>,
 | 
			
		||||
        Option<ConnectedList<FunctionParameter, Punctuation>>,
 | 
			
		||||
        Punctuation,
 | 
			
		||||
        Block,
 | 
			
		||||
    ) {
 | 
			
		||||
| 
						 | 
				
			
			@ -156,6 +157,41 @@ impl SourceElement for Function {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Represents a variable type keyword for function arguments.
 | 
			
		||||
///
 | 
			
		||||
/// Syntax Synopsis:
 | 
			
		||||
///
 | 
			
		||||
/// ``` ebnf
 | 
			
		||||
/// FunctionVariableType:
 | 
			
		||||
///     'macro' | 'int' | 'bool'
 | 
			
		||||
///     ;
 | 
			
		||||
/// ```
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
 | 
			
		||||
pub enum FunctionVariableType {
 | 
			
		||||
    Macro(Keyword),
 | 
			
		||||
    Integer(Keyword),
 | 
			
		||||
    Boolean(Keyword),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a function argument in the syntax tree.
 | 
			
		||||
///
 | 
			
		||||
/// Syntax Synopsis:
 | 
			
		||||
///
 | 
			
		||||
/// ``` ebnf
 | 
			
		||||
/// FunctionArgument:
 | 
			
		||||
///     FunctionVariableType Identifier
 | 
			
		||||
///     ;
 | 
			
		||||
/// ```
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
 | 
			
		||||
pub struct FunctionParameter {
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    variable_type: FunctionVariableType,
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    identifier: Identifier,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents an import declaration in the syntax tree.
 | 
			
		||||
///
 | 
			
		||||
/// Syntax Synopsis:
 | 
			
		||||
| 
						 | 
				
			
			@ -451,7 +487,7 @@ impl Parser<'_> {
 | 
			
		|||
                let delimited_tree = self.parse_enclosed_list(
 | 
			
		||||
                    Delimiter::Parenthesis,
 | 
			
		||||
                    ',',
 | 
			
		||||
                    |parser: &mut Parser<'_>| parser.parse_identifier(handler),
 | 
			
		||||
                    |parser: &mut Parser<'_>| parser.parse_function_parameter(handler),
 | 
			
		||||
                    handler,
 | 
			
		||||
                )?;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -479,4 +515,57 @@ impl Parser<'_> {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn parse_function_parameter(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> ParseResult<FunctionParameter> {
 | 
			
		||||
        match self.stop_at_significant() {
 | 
			
		||||
            Reading::Atomic(Token::Keyword(keyword)) if keyword.keyword == KeywordKind::Int => {
 | 
			
		||||
                let variable_type = FunctionVariableType::Integer(keyword);
 | 
			
		||||
                self.forward();
 | 
			
		||||
 | 
			
		||||
                let identifier = self.parse_identifier(handler)?;
 | 
			
		||||
 | 
			
		||||
                Ok(FunctionParameter {
 | 
			
		||||
                    variable_type,
 | 
			
		||||
                    identifier,
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            Reading::Atomic(Token::Keyword(keyword)) if keyword.keyword == KeywordKind::Bool => {
 | 
			
		||||
                let variable_type = FunctionVariableType::Boolean(keyword);
 | 
			
		||||
                self.forward();
 | 
			
		||||
 | 
			
		||||
                let identifier = self.parse_identifier(handler)?;
 | 
			
		||||
 | 
			
		||||
                Ok(FunctionParameter {
 | 
			
		||||
                    variable_type,
 | 
			
		||||
                    identifier,
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            Reading::Atomic(Token::Keyword(keyword)) if keyword.keyword == KeywordKind::Macro => {
 | 
			
		||||
                let variable_type = FunctionVariableType::Macro(keyword);
 | 
			
		||||
                self.forward();
 | 
			
		||||
 | 
			
		||||
                let identifier = self.parse_identifier(handler)?;
 | 
			
		||||
 | 
			
		||||
                Ok(FunctionParameter {
 | 
			
		||||
                    variable_type,
 | 
			
		||||
                    identifier,
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            unexpected => {
 | 
			
		||||
                let err = Error::UnexpectedSyntax(UnexpectedSyntax {
 | 
			
		||||
                    expected: SyntaxKind::Either(&[
 | 
			
		||||
                        SyntaxKind::Keyword(KeywordKind::Int),
 | 
			
		||||
                        SyntaxKind::Keyword(KeywordKind::Bool),
 | 
			
		||||
                        SyntaxKind::Keyword(KeywordKind::Macro),
 | 
			
		||||
                    ]),
 | 
			
		||||
                    found: unexpected.into_token(),
 | 
			
		||||
                });
 | 
			
		||||
                handler.receive(err.clone());
 | 
			
		||||
                Err(err)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,565 @@
 | 
			
		|||
use chksum_md5 as md5;
 | 
			
		||||
use enum_as_inner::EnumAsInner;
 | 
			
		||||
use std::{collections::BTreeMap, sync::Arc};
 | 
			
		||||
 | 
			
		||||
use shulkerbox::datapack::{Command, Execute};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::{
 | 
			
		||||
        self,
 | 
			
		||||
        source_file::{SourceElement, Span},
 | 
			
		||||
        Handler,
 | 
			
		||||
    },
 | 
			
		||||
    semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments},
 | 
			
		||||
    syntax::syntax_tree::{
 | 
			
		||||
        declaration::FunctionVariableType,
 | 
			
		||||
        expression::{Expression, Primary},
 | 
			
		||||
        statement::Statement,
 | 
			
		||||
    },
 | 
			
		||||
    transpile::{
 | 
			
		||||
        error::{IllegalAnnotationContent, MissingFunctionDeclaration},
 | 
			
		||||
        util::{MacroString, MacroStringPart},
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
    error::{MismatchedTypes, TranspileError, TranspileResult, UnknownIdentifier},
 | 
			
		||||
    expression::{ComptimeValue, ExpectedType, StorageType},
 | 
			
		||||
    variables::{Scope, VariableData},
 | 
			
		||||
    FunctionData, TranspileAnnotationValue, Transpiler,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum TranspiledFunctionArguments {
 | 
			
		||||
    None,
 | 
			
		||||
    Static(BTreeMap<String, MacroString>),
 | 
			
		||||
    Dynamic(Vec<Command>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Transpiler {
 | 
			
		||||
    /// Gets the function at the given path, or transpiles it if it hasn't been transpiled yet.
 | 
			
		||||
    /// Returns the location of the function or None if the function does not exist.
 | 
			
		||||
    #[tracing::instrument(level = "trace", skip(self, handler))]
 | 
			
		||||
    pub(super) fn get_or_transpile_function(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        identifier_span: &Span,
 | 
			
		||||
        arguments: Option<&[&Expression]>,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<(String, TranspiledFunctionArguments)> {
 | 
			
		||||
        let program_identifier = identifier_span.source_file().identifier();
 | 
			
		||||
        let program_query = (
 | 
			
		||||
            program_identifier.to_string(),
 | 
			
		||||
            identifier_span.str().to_string(),
 | 
			
		||||
        );
 | 
			
		||||
        let alias_query = self.aliases.get(&program_query).cloned();
 | 
			
		||||
        let already_transpiled = scope
 | 
			
		||||
            .get_variable(identifier_span.str())
 | 
			
		||||
            .expect("called function should be in scope")
 | 
			
		||||
            .as_ref()
 | 
			
		||||
            .as_function()
 | 
			
		||||
            .map(|(_, path)| path.get().is_some())
 | 
			
		||||
            .expect("called variable should be of type function");
 | 
			
		||||
 | 
			
		||||
        let function_data = scope
 | 
			
		||||
            .get_variable(identifier_span.str())
 | 
			
		||||
            .or_else(|| {
 | 
			
		||||
                alias_query
 | 
			
		||||
                    .clone()
 | 
			
		||||
                    .and_then(|(alias_program_identifier, alias_function_name)| {
 | 
			
		||||
                        self.scopes
 | 
			
		||||
                            .get(&alias_program_identifier)
 | 
			
		||||
                            .and_then(|s| s.get_variable(&alias_function_name))
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .ok_or_else(|| {
 | 
			
		||||
                let error = TranspileError::MissingFunctionDeclaration(
 | 
			
		||||
                    MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope),
 | 
			
		||||
                );
 | 
			
		||||
                handler.receive(error.clone());
 | 
			
		||||
                error
 | 
			
		||||
            })?;
 | 
			
		||||
 | 
			
		||||
        let VariableData::Function {
 | 
			
		||||
            function_data,
 | 
			
		||||
            path: function_path,
 | 
			
		||||
        } = function_data.as_ref()
 | 
			
		||||
        else {
 | 
			
		||||
            unreachable!("must be of correct type, otherwise errored out before");
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if !already_transpiled {
 | 
			
		||||
            tracing::trace!("Function not transpiled yet, transpiling.");
 | 
			
		||||
 | 
			
		||||
            let statements = function_data.statements.clone();
 | 
			
		||||
 | 
			
		||||
            let modified_name = function_data.annotations.get("deobfuscate").map_or_else(
 | 
			
		||||
                || {
 | 
			
		||||
                    let hash_data = program_identifier.to_string() + "\0" + identifier_span.str();
 | 
			
		||||
                    Ok("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase())
 | 
			
		||||
                },
 | 
			
		||||
                |val| match val {
 | 
			
		||||
                    TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()),
 | 
			
		||||
                    TranspileAnnotationValue::Expression(expr) => expr
 | 
			
		||||
                        .comptime_eval(scope, handler)
 | 
			
		||||
                        .and_then(|val| val.to_string_no_macro())
 | 
			
		||||
                        .ok_or_else(|| {
 | 
			
		||||
                            let err = TranspileError::IllegalAnnotationContent(
 | 
			
		||||
                                IllegalAnnotationContent {
 | 
			
		||||
                                    annotation: identifier_span.clone(),
 | 
			
		||||
                                    message: "Cannot evaluate annotation at compile time"
 | 
			
		||||
                                        .to_string(),
 | 
			
		||||
                                },
 | 
			
		||||
                            );
 | 
			
		||||
                            handler.receive(err.clone());
 | 
			
		||||
                            err
 | 
			
		||||
                        }),
 | 
			
		||||
                    TranspileAnnotationValue::Map(_) => {
 | 
			
		||||
                        let err =
 | 
			
		||||
                            TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
 | 
			
		||||
                                annotation: identifier_span.clone(),
 | 
			
		||||
                                message: "Deobfuscate annotation cannot be a map.".to_string(),
 | 
			
		||||
                            });
 | 
			
		||||
                        handler.receive(err.clone());
 | 
			
		||||
                        Err(err)
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            )?;
 | 
			
		||||
 | 
			
		||||
            let function_location = format!(
 | 
			
		||||
                "{namespace}:{modified_name}",
 | 
			
		||||
                namespace = function_data.namespace
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            function_path.set(function_location.clone()).unwrap();
 | 
			
		||||
 | 
			
		||||
            let function_scope = Scope::with_parent(scope);
 | 
			
		||||
 | 
			
		||||
            for (i, param) in function_data.parameters.iter().enumerate() {
 | 
			
		||||
                let param_str = param.identifier().span.str();
 | 
			
		||||
                match param.variable_type() {
 | 
			
		||||
                    FunctionVariableType::Macro(_) => {
 | 
			
		||||
                        function_scope.set_variable(
 | 
			
		||||
                            param_str,
 | 
			
		||||
                            VariableData::MacroParameter {
 | 
			
		||||
                                index: i,
 | 
			
		||||
                                macro_name: crate::util::identifier_to_macro(param_str).to_string(),
 | 
			
		||||
                            },
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
                    FunctionVariableType::Integer(_) => {
 | 
			
		||||
                        let objective = format!(
 | 
			
		||||
                            "shu_arguments_{}",
 | 
			
		||||
                            function_location.replace(['/', ':'], "_")
 | 
			
		||||
                        );
 | 
			
		||||
                        function_scope.set_variable(
 | 
			
		||||
                            param_str,
 | 
			
		||||
                            VariableData::ScoreboardValue {
 | 
			
		||||
                                objective: objective.clone(),
 | 
			
		||||
                                target: crate::util::identifier_to_scoreboard_target(param_str)
 | 
			
		||||
                                    .into_owned(),
 | 
			
		||||
                            },
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
                    FunctionVariableType::Boolean(_) => {
 | 
			
		||||
                        let storage_name = format!(
 | 
			
		||||
                            "shulkerscript:arguments_{}",
 | 
			
		||||
                            function_location.replace(['/', ':'], "_")
 | 
			
		||||
                        );
 | 
			
		||||
                        // TODO: replace with proper path
 | 
			
		||||
                        function_scope.set_variable(
 | 
			
		||||
                            param_str,
 | 
			
		||||
                            VariableData::BooleanStorage {
 | 
			
		||||
                                storage_name,
 | 
			
		||||
                                path: crate::util::identifier_to_scoreboard_target(param_str)
 | 
			
		||||
                                    .into_owned(),
 | 
			
		||||
                            },
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let commands =
 | 
			
		||||
                self.transpile_function(&statements, program_identifier, &function_scope, handler)?;
 | 
			
		||||
 | 
			
		||||
            let namespace = self.datapack.namespace_mut(&function_data.namespace);
 | 
			
		||||
 | 
			
		||||
            if namespace.function(&modified_name).is_some() {
 | 
			
		||||
                let err = TranspileError::ConflictingFunctionNames(ConflictingFunctionNames {
 | 
			
		||||
                    name: modified_name,
 | 
			
		||||
                    definition: identifier_span.clone(),
 | 
			
		||||
                });
 | 
			
		||||
                handler.receive(err.clone());
 | 
			
		||||
                return Err(err);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let function = namespace.function_mut(&modified_name);
 | 
			
		||||
            function.get_commands_mut().extend(commands);
 | 
			
		||||
 | 
			
		||||
            if function_data.annotations.contains_key("tick") {
 | 
			
		||||
                self.datapack.add_tick(&function_location);
 | 
			
		||||
            }
 | 
			
		||||
            if function_data.annotations.contains_key("load") {
 | 
			
		||||
                self.datapack.add_load(&function_location);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let function_location = function_path
 | 
			
		||||
            .get()
 | 
			
		||||
            .ok_or_else(|| {
 | 
			
		||||
                let error = TranspileError::MissingFunctionDeclaration(
 | 
			
		||||
                    MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope),
 | 
			
		||||
                );
 | 
			
		||||
                handler.receive(error.clone());
 | 
			
		||||
                error
 | 
			
		||||
            })
 | 
			
		||||
            .map(String::to_owned)?;
 | 
			
		||||
 | 
			
		||||
        let args = self.transpile_function_arguments(
 | 
			
		||||
            function_data,
 | 
			
		||||
            &function_location,
 | 
			
		||||
            arguments,
 | 
			
		||||
            scope,
 | 
			
		||||
            handler,
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        Ok((function_location, args))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn transpile_function(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        statements: &[Statement],
 | 
			
		||||
        program_identifier: &str,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        let mut errors = Vec::new();
 | 
			
		||||
        let commands = statements
 | 
			
		||||
            .iter()
 | 
			
		||||
            .flat_map(|statement| {
 | 
			
		||||
                self.transpile_statement(statement, program_identifier, scope, handler)
 | 
			
		||||
                    .unwrap_or_else(|err| {
 | 
			
		||||
                        errors.push(err);
 | 
			
		||||
                        Vec::new()
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        if !errors.is_empty() {
 | 
			
		||||
            return Err(errors.remove(0));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(commands)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[expect(clippy::too_many_lines)]
 | 
			
		||||
    fn transpile_function_arguments(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        function_data: &FunctionData,
 | 
			
		||||
        function_location: &str,
 | 
			
		||||
        arguments: Option<&[&Expression]>,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<TranspiledFunctionArguments> {
 | 
			
		||||
        let parameters = &function_data.parameters;
 | 
			
		||||
        let identifier_span = &function_data.identifier_span;
 | 
			
		||||
        let arg_count = arguments.map(<[&Expression]>::len);
 | 
			
		||||
 | 
			
		||||
        match arg_count {
 | 
			
		||||
            Some(arg_count) if arg_count != parameters.len() => {
 | 
			
		||||
                let err = TranspileError::InvalidFunctionArguments(InvalidFunctionArguments {
 | 
			
		||||
                    expected: parameters.len(),
 | 
			
		||||
                    actual: arg_count,
 | 
			
		||||
                    span: identifier_span.clone(),
 | 
			
		||||
                });
 | 
			
		||||
                handler.receive(err.clone());
 | 
			
		||||
                Err(err)
 | 
			
		||||
            }
 | 
			
		||||
            Some(arg_count) if arg_count > 0 => {
 | 
			
		||||
                #[derive(Debug, Clone, EnumAsInner)]
 | 
			
		||||
                enum Parameter {
 | 
			
		||||
                    Static(MacroString),
 | 
			
		||||
                    Storage {
 | 
			
		||||
                        prepare_cmds: Vec<Command>,
 | 
			
		||||
                        storage_name: String,
 | 
			
		||||
                        path: String,
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let mut compiled_args = Vec::<Parameter>::new();
 | 
			
		||||
                let mut errs = Vec::new();
 | 
			
		||||
 | 
			
		||||
                for expression in arguments.iter().flat_map(|expressions| expressions.iter()) {
 | 
			
		||||
                    let value = match expression {
 | 
			
		||||
                        Expression::Primary(Primary::Lua(lua)) => {
 | 
			
		||||
                            lua.eval_comptime(scope, handler).and_then(|val| match val {
 | 
			
		||||
                                Some(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)),
 | 
			
		||||
                                Some(val) => Ok(Parameter::Static(val.to_macro_string())),
 | 
			
		||||
                                None => {
 | 
			
		||||
                                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                        expression: expression.span(),
 | 
			
		||||
                                        expected_type: ExpectedType::String,
 | 
			
		||||
                                    });
 | 
			
		||||
                                    handler.receive(err.clone());
 | 
			
		||||
                                    Err(err)
 | 
			
		||||
                                }
 | 
			
		||||
                            })
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(Primary::Integer(num)) => {
 | 
			
		||||
                            Ok(Parameter::Static(num.span.str().to_string().into()))
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(Primary::Boolean(bool)) => {
 | 
			
		||||
                            Ok(Parameter::Static(bool.span.str().to_string().into()))
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(Primary::StringLiteral(string)) => {
 | 
			
		||||
                            Ok(Parameter::Static(string.str_content().to_string().into()))
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(Primary::MacroStringLiteral(literal)) => {
 | 
			
		||||
                            Ok(Parameter::Static(literal.into()))
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(primary @ Primary::Identifier(ident)) => {
 | 
			
		||||
                            let var = scope.get_variable(ident.span.str()).ok_or_else(|| {
 | 
			
		||||
                                let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
 | 
			
		||||
                                    identifier: ident.span(),
 | 
			
		||||
                                });
 | 
			
		||||
                                handler.receive(err.clone());
 | 
			
		||||
                                err
 | 
			
		||||
                            })?;
 | 
			
		||||
                            match var.as_ref() {
 | 
			
		||||
                                VariableData::MacroParameter { macro_name, .. } => {
 | 
			
		||||
                                    Ok(Parameter::Static(MacroString::MacroString(vec![
 | 
			
		||||
                                        MacroStringPart::MacroUsage(macro_name.clone()),
 | 
			
		||||
                                    ])))
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                VariableData::BooleanStorage { .. }
 | 
			
		||||
                                | VariableData::ScoreboardValue { .. } => {
 | 
			
		||||
                                    let (temp_storage, mut temp_path) =
 | 
			
		||||
                                        self.get_temp_storage_locations(1);
 | 
			
		||||
                                    let prepare_cmds = self.transpile_primary_expression(
 | 
			
		||||
                                        primary,
 | 
			
		||||
                                        &super::expression::DataLocation::Storage {
 | 
			
		||||
                                            storage_name: temp_storage.clone(),
 | 
			
		||||
                                            path: temp_path[0].clone(),
 | 
			
		||||
                                            r#type: match var.as_ref() {
 | 
			
		||||
                                                VariableData::BooleanStorage { .. } => {
 | 
			
		||||
                                                    StorageType::Boolean
 | 
			
		||||
                                                }
 | 
			
		||||
                                                VariableData::ScoreboardValue { .. } => {
 | 
			
		||||
                                                    StorageType::Int
 | 
			
		||||
                                                }
 | 
			
		||||
                                                _ => unreachable!("checked in parent match"),
 | 
			
		||||
                                            },
 | 
			
		||||
                                        },
 | 
			
		||||
                                        scope,
 | 
			
		||||
                                        handler,
 | 
			
		||||
                                    )?;
 | 
			
		||||
 | 
			
		||||
                                    Ok(Parameter::Storage {
 | 
			
		||||
                                        prepare_cmds,
 | 
			
		||||
                                        storage_name: temp_storage,
 | 
			
		||||
                                        path: std::mem::take(&mut temp_path[0]),
 | 
			
		||||
                                    })
 | 
			
		||||
                                }
 | 
			
		||||
                                _ => todo!("other variable types"),
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(
 | 
			
		||||
                            Primary::Parenthesized(_)
 | 
			
		||||
                            | Primary::Prefix(_)
 | 
			
		||||
                            | Primary::Indexed(_)
 | 
			
		||||
                            | Primary::FunctionCall(_),
 | 
			
		||||
                        )
 | 
			
		||||
                        | Expression::Binary(_) => {
 | 
			
		||||
                            let (temp_storage, mut temp_path) = self.get_temp_storage_locations(1);
 | 
			
		||||
                            let prepare_cmds = self.transpile_expression(
 | 
			
		||||
                                expression,
 | 
			
		||||
                                &super::expression::DataLocation::Storage {
 | 
			
		||||
                                    storage_name: temp_storage.clone(),
 | 
			
		||||
                                    path: temp_path[0].clone(),
 | 
			
		||||
                                    r#type: StorageType::Int,
 | 
			
		||||
                                },
 | 
			
		||||
                                scope,
 | 
			
		||||
                                handler,
 | 
			
		||||
                            )?;
 | 
			
		||||
 | 
			
		||||
                            Ok(Parameter::Storage {
 | 
			
		||||
                                prepare_cmds,
 | 
			
		||||
                                storage_name: temp_storage,
 | 
			
		||||
                                path: std::mem::take(&mut temp_path[0]),
 | 
			
		||||
                            })
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    match value {
 | 
			
		||||
                        Ok(value) => {
 | 
			
		||||
                            compiled_args.push(value);
 | 
			
		||||
                        }
 | 
			
		||||
                        Err(err) => {
 | 
			
		||||
                            compiled_args
 | 
			
		||||
                                .push(Parameter::Static(MacroString::String(String::new())));
 | 
			
		||||
                            errs.push(err.clone());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if let Some(err) = errs.first() {
 | 
			
		||||
                    return Err(err.clone());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if compiled_args.iter().any(|arg| !arg.is_static()) {
 | 
			
		||||
                    let (mut setup_cmds, move_cmds, static_params) = parameters.clone().into_iter().zip(compiled_args).fold(
 | 
			
		||||
                        (Vec::new(), Vec::new(), BTreeMap::new()),
 | 
			
		||||
                        |(mut acc_setup, mut acc_move, mut statics), (param, data)| {
 | 
			
		||||
                            match param.variable_type() {
 | 
			
		||||
                                FunctionVariableType::Macro(_) => {
 | 
			
		||||
                                    let arg_name = crate::util::identifier_to_macro(param.identifier().span.str());
 | 
			
		||||
                                    match data {
 | 
			
		||||
                                        Parameter::Static(s) => {
 | 
			
		||||
                                            match s {
 | 
			
		||||
                                                MacroString::String(value) => statics.insert(
 | 
			
		||||
                                                    arg_name.to_string(),
 | 
			
		||||
                                                    MacroString::String(crate::util::escape_str(&value).to_string())
 | 
			
		||||
                                                ),
 | 
			
		||||
                                                MacroString::MacroString(parts) => {
 | 
			
		||||
                                                    let parts = parts.into_iter().map(|part| {
 | 
			
		||||
                                                        match part {
 | 
			
		||||
                                                            MacroStringPart::String(s) => MacroStringPart::String(crate::util::escape_str(&s).to_string()),
 | 
			
		||||
                                                            MacroStringPart::MacroUsage(m) => MacroStringPart::MacroUsage(m),
 | 
			
		||||
                                                        }
 | 
			
		||||
                                                    }).collect();
 | 
			
		||||
                                                    statics.insert(arg_name.to_string(), MacroString::MacroString(parts))
 | 
			
		||||
                                                }
 | 
			
		||||
                                            };
 | 
			
		||||
                                        }
 | 
			
		||||
                                        Parameter::Storage { prepare_cmds, storage_name, path } => {
 | 
			
		||||
                                            acc_setup.extend(prepare_cmds);
 | 
			
		||||
                                            acc_move.push(Command::Raw(
 | 
			
		||||
                                                format!(r"data modify storage shulkerscript:function_arguments {arg_name} set from storage {storage_name} {path}")
 | 
			
		||||
                                            ));
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                FunctionVariableType::Integer(_) => {
 | 
			
		||||
                                    let objective = format!("shu_arguments_{}", function_location.replace(['/', ':'], "_"));
 | 
			
		||||
                                    let param_str = param.identifier().span.str();
 | 
			
		||||
                                    let target = crate::util::identifier_to_scoreboard_target(param_str);
 | 
			
		||||
 | 
			
		||||
                                    match data {
 | 
			
		||||
                                        Parameter::Static(s) => {
 | 
			
		||||
                                            match s.as_str() {
 | 
			
		||||
                                                Ok(s) => {
 | 
			
		||||
                                                    if s.parse::<i32>().is_ok() {
 | 
			
		||||
                                                        acc_move.push(Command::Raw(format!(r"scoreboard players set {target} {objective} {s}")));
 | 
			
		||||
                                                    } else {
 | 
			
		||||
                                                        panic!("non-integer static argument")
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                }
 | 
			
		||||
                                                Err(parts) => {
 | 
			
		||||
                                                    acc_move.push(Command::UsesMacro(MacroString::MacroString(
 | 
			
		||||
                                                        std::iter::once(MacroStringPart::String(format!("scoreboard players set {target} {objective} ")))
 | 
			
		||||
                                                        .chain(parts.iter().cloned()).collect()
 | 
			
		||||
                                                    ).into()));
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                        Parameter::Storage { prepare_cmds, storage_name, path } => {
 | 
			
		||||
                                            acc_setup.extend(prepare_cmds);
 | 
			
		||||
                                            acc_move.push(Command::Execute(Execute::Store(
 | 
			
		||||
                                                format!("result score {target} {objective}").into(), 
 | 
			
		||||
                                                Box::new(Execute::Run(Box::new(Command::Raw(format!("data get storage {storage_name} {path}")))))
 | 
			
		||||
                                            )));
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                },
 | 
			
		||||
                                FunctionVariableType::Boolean(_) => {
 | 
			
		||||
                                    let target_storage_name = format!("shulkerscript:arguments_{}", function_location.replace(['/', ':'], "_"));
 | 
			
		||||
                                    let param_str = param.identifier().span.str();
 | 
			
		||||
                                    let target_path = crate::util::identifier_to_scoreboard_target(param_str);
 | 
			
		||||
 | 
			
		||||
                                    match data {
 | 
			
		||||
                                        Parameter::Static(s) => {
 | 
			
		||||
                                            match s.as_str() {
 | 
			
		||||
                                                Ok(s) => {
 | 
			
		||||
                                                    if let Ok(b) = s.parse::<bool>() {
 | 
			
		||||
                                                        acc_move.push(Command::Raw(format!("data modify storage {target_storage_name} {target_path} set value {}", if b { "1b" } else { "0b" })));
 | 
			
		||||
                                                    } else {
 | 
			
		||||
                                                        panic!("non-integer static argument")
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                }
 | 
			
		||||
                                                Err(parts) => {
 | 
			
		||||
                                                    acc_move.push(Command::UsesMacro(MacroString::MacroString(
 | 
			
		||||
                                                        std::iter::once(MacroStringPart::String(format!("data modify storage {target_storage_name} {target_path} set value ")))
 | 
			
		||||
                                                        .chain(parts.iter().cloned()).collect()
 | 
			
		||||
                                                    ).into()));
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                        Parameter::Storage { prepare_cmds, storage_name, path } => {
 | 
			
		||||
                                            acc_setup.extend(prepare_cmds);
 | 
			
		||||
                                            acc_move.push(Command::Raw(format!("data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}")));
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                },
 | 
			
		||||
                            }
 | 
			
		||||
                        (acc_setup, acc_move, statics)},
 | 
			
		||||
                    );
 | 
			
		||||
                    let statics_len = static_params.len();
 | 
			
		||||
                    let joined_statics =
 | 
			
		||||
                        super::util::join_macro_strings(static_params.into_iter().enumerate().map(
 | 
			
		||||
                            |(i, (k, v))| match v {
 | 
			
		||||
                                MacroString::String(s) => {
 | 
			
		||||
                                    let mut s = format!(r#"{k}:"{s}""#);
 | 
			
		||||
                                    if i < statics_len - 1 {
 | 
			
		||||
                                        s.push(',');
 | 
			
		||||
                                    }
 | 
			
		||||
                                    MacroString::String(s)
 | 
			
		||||
                                }
 | 
			
		||||
                                MacroString::MacroString(mut parts) => {
 | 
			
		||||
                                    parts.insert(0, MacroStringPart::String(format!(r#"{k}:""#)));
 | 
			
		||||
                                    let mut ending = '"'.to_string();
 | 
			
		||||
                                    if i < statics_len - 1 {
 | 
			
		||||
                                        ending.push(',');
 | 
			
		||||
                                    }
 | 
			
		||||
                                    parts.push(MacroStringPart::String(ending));
 | 
			
		||||
                                    MacroString::MacroString(parts)
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                        ));
 | 
			
		||||
                    let statics_cmd = match joined_statics {
 | 
			
		||||
                        MacroString::String(s) => Command::Raw(format!(
 | 
			
		||||
                            r"data merge storage shulkerscript:function_arguments {{{s}}}"
 | 
			
		||||
                        )),
 | 
			
		||||
                        MacroString::MacroString(_) => Command::UsesMacro(
 | 
			
		||||
                            super::util::join_macro_strings([
 | 
			
		||||
                                MacroString::String(
 | 
			
		||||
                                    "data merge storage shulkerscript:function_arguments {"
 | 
			
		||||
                                        .to_string(),
 | 
			
		||||
                                ),
 | 
			
		||||
                                joined_statics,
 | 
			
		||||
                                MacroString::String("}".to_string()),
 | 
			
		||||
                            ])
 | 
			
		||||
                            .into(),
 | 
			
		||||
                        ),
 | 
			
		||||
                    };
 | 
			
		||||
                    setup_cmds.push(statics_cmd);
 | 
			
		||||
                    setup_cmds.extend(move_cmds);
 | 
			
		||||
 | 
			
		||||
                    Ok(TranspiledFunctionArguments::Dynamic(setup_cmds))
 | 
			
		||||
                } else {
 | 
			
		||||
                    let function_args = parameters
 | 
			
		||||
                        .clone()
 | 
			
		||||
                        .into_iter()
 | 
			
		||||
                        .zip(
 | 
			
		||||
                            compiled_args
 | 
			
		||||
                                .into_iter()
 | 
			
		||||
                                .map(|arg| arg.into_static().expect("checked in if condition")),
 | 
			
		||||
                        )
 | 
			
		||||
                        .map(|(k, v)| (k.identifier().span.str().to_string(), v))
 | 
			
		||||
                        .collect();
 | 
			
		||||
                    Ok(TranspiledFunctionArguments::Static(function_args))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => Ok(TranspiledFunctionArguments::None),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -311,7 +311,7 @@ fn print_function(
 | 
			
		|||
                                todo!("throw error when index is not constant integer")
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        _ => todo!(),
 | 
			
		||||
                        _ => todo!("catch illegal indexing"),
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(TranspileError::IllegalIndexing(IllegalIndexing {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,10 @@ use std::{
 | 
			
		|||
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::source_file::{SourceElement, Span},
 | 
			
		||||
    syntax::syntax_tree::{expression::Expression, statement::Statement, AnnotationValue},
 | 
			
		||||
    syntax::syntax_tree::{
 | 
			
		||||
        declaration::FunctionParameter, expression::Expression, statement::Statement,
 | 
			
		||||
        AnnotationValue,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[doc(hidden)]
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +35,11 @@ pub use transpiler::Transpiler;
 | 
			
		|||
#[cfg(feature = "shulkerbox")]
 | 
			
		||||
pub mod internal_functions;
 | 
			
		||||
 | 
			
		||||
#[doc(hidden)]
 | 
			
		||||
#[cfg(feature = "shulkerbox")]
 | 
			
		||||
pub mod function;
 | 
			
		||||
pub use function::TranspiledFunctionArguments;
 | 
			
		||||
 | 
			
		||||
mod variables;
 | 
			
		||||
pub use variables::{Scope, VariableData};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +50,7 @@ pub mod util;
 | 
			
		|||
pub struct FunctionData {
 | 
			
		||||
    pub(super) namespace: String,
 | 
			
		||||
    pub(super) identifier_span: Span,
 | 
			
		||||
    pub(super) parameters: Vec<String>,
 | 
			
		||||
    pub(super) parameters: Vec<FunctionParameter>,
 | 
			
		||||
    pub(super) statements: Vec<Statement>,
 | 
			
		||||
    pub(super) public: bool,
 | 
			
		||||
    pub(super) annotations: HashMap<String, TranspileAnnotationValue>,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,5 @@
 | 
			
		|||
//! Transpiler for `Shulkerscript`
 | 
			
		||||
 | 
			
		||||
use chksum_md5 as md5;
 | 
			
		||||
use enum_as_inner::EnumAsInner;
 | 
			
		||||
use std::{
 | 
			
		||||
    collections::{BTreeMap, HashMap, HashSet},
 | 
			
		||||
    ops::Deref,
 | 
			
		||||
| 
						 | 
				
			
			@ -11,12 +9,8 @@ use std::{
 | 
			
		|||
use shulkerbox::datapack::{self, Command, Datapack, Execute};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::{
 | 
			
		||||
        self,
 | 
			
		||||
        source_file::{SourceElement, Span},
 | 
			
		||||
        Handler,
 | 
			
		||||
    },
 | 
			
		||||
    semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
 | 
			
		||||
    base::{self, source_file::SourceElement, Handler},
 | 
			
		||||
    semantic::error::UnexpectedExpression,
 | 
			
		||||
    syntax::syntax_tree::{
 | 
			
		||||
        declaration::{Declaration, ImportItems},
 | 
			
		||||
        expression::{Expression, FunctionCall, Primary},
 | 
			
		||||
| 
						 | 
				
			
			@ -27,17 +21,14 @@ use crate::{
 | 
			
		|||
        },
 | 
			
		||||
        AnnotationAssignment,
 | 
			
		||||
    },
 | 
			
		||||
    transpile::{
 | 
			
		||||
        error::{IllegalAnnotationContent, MissingFunctionDeclaration},
 | 
			
		||||
        util::{MacroString, MacroStringPart},
 | 
			
		||||
    },
 | 
			
		||||
    transpile::util::{MacroString, MacroStringPart},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
    error::{MismatchedTypes, TranspileError, TranspileResult, UnknownIdentifier},
 | 
			
		||||
    expression::{ComptimeValue, ExpectedType, ExtendedCondition, StorageType},
 | 
			
		||||
    error::{MismatchedTypes, TranspileError, TranspileResult},
 | 
			
		||||
    expression::{ComptimeValue, ExpectedType, ExtendedCondition},
 | 
			
		||||
    variables::{Scope, TranspileAssignmentTarget, VariableData},
 | 
			
		||||
    FunctionData, TranspileAnnotationValue,
 | 
			
		||||
    FunctionData, TranspileAnnotationValue, TranspiledFunctionArguments,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// A transpiler for `Shulkerscript`.
 | 
			
		||||
| 
						 | 
				
			
			@ -49,18 +40,11 @@ pub struct Transpiler {
 | 
			
		|||
    pub(super) initialized_constant_scores: HashSet<i64>,
 | 
			
		||||
    pub(super) temp_counter: usize,
 | 
			
		||||
    /// Top-level [`Scope`] for each program identifier
 | 
			
		||||
    scopes: BTreeMap<String, Arc<Scope<'static>>>,
 | 
			
		||||
    pub(super) scopes: BTreeMap<String, Arc<Scope<'static>>>,
 | 
			
		||||
    /// Key: (program identifier, function name)
 | 
			
		||||
    functions: BTreeMap<(String, String), FunctionData>,
 | 
			
		||||
    pub(super) functions: BTreeMap<(String, String), FunctionData>,
 | 
			
		||||
    /// Key: alias, Value: target
 | 
			
		||||
    aliases: HashMap<(String, String), (String, String)>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum TranspiledFunctionArguments {
 | 
			
		||||
    None,
 | 
			
		||||
    Static(BTreeMap<String, MacroString>),
 | 
			
		||||
    Dynamic(Vec<Command>),
 | 
			
		||||
    pub(super) aliases: HashMap<(String, String), (String, String)>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Transpiler {
 | 
			
		||||
| 
						 | 
				
			
			@ -202,11 +186,7 @@ impl Transpiler {
 | 
			
		|||
                    parameters: function
 | 
			
		||||
                        .parameters()
 | 
			
		||||
                        .as_ref()
 | 
			
		||||
                        .map(|l| {
 | 
			
		||||
                            l.elements()
 | 
			
		||||
                                .map(|i| i.span.str().to_string())
 | 
			
		||||
                                .collect::<Vec<_>>()
 | 
			
		||||
                        })
 | 
			
		||||
                        .map(|l| l.elements().cloned().collect::<Vec<_>>())
 | 
			
		||||
                        .unwrap_or_default(),
 | 
			
		||||
                    statements,
 | 
			
		||||
                    public: function.is_public(),
 | 
			
		||||
| 
						 | 
				
			
			@ -266,412 +246,7 @@ impl Transpiler {
 | 
			
		|||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Gets the function at the given path, or transpiles it if it hasn't been transpiled yet.
 | 
			
		||||
    /// Returns the location of the function or None if the function does not exist.
 | 
			
		||||
    #[tracing::instrument(level = "trace", skip(self, handler))]
 | 
			
		||||
    pub(super) fn get_or_transpile_function(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        identifier_span: &Span,
 | 
			
		||||
        arguments: Option<&[&Expression]>,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<(String, TranspiledFunctionArguments)> {
 | 
			
		||||
        let program_identifier = identifier_span.source_file().identifier();
 | 
			
		||||
        let program_query = (
 | 
			
		||||
            program_identifier.to_string(),
 | 
			
		||||
            identifier_span.str().to_string(),
 | 
			
		||||
        );
 | 
			
		||||
        let alias_query = self.aliases.get(&program_query).cloned();
 | 
			
		||||
        let already_transpiled = scope
 | 
			
		||||
            .get_variable(identifier_span.str())
 | 
			
		||||
            .expect("called function should be in scope")
 | 
			
		||||
            .as_ref()
 | 
			
		||||
            .as_function()
 | 
			
		||||
            .map(|(_, path)| path.get().is_some())
 | 
			
		||||
            .expect("called variable should be of type function");
 | 
			
		||||
 | 
			
		||||
        let function_data = scope
 | 
			
		||||
            .get_variable(identifier_span.str())
 | 
			
		||||
            .or_else(|| {
 | 
			
		||||
                alias_query
 | 
			
		||||
                    .clone()
 | 
			
		||||
                    .and_then(|(alias_program_identifier, alias_function_name)| {
 | 
			
		||||
                        self.scopes
 | 
			
		||||
                            .get(&alias_program_identifier)
 | 
			
		||||
                            .and_then(|s| s.get_variable(&alias_function_name))
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .ok_or_else(|| {
 | 
			
		||||
                let error = TranspileError::MissingFunctionDeclaration(
 | 
			
		||||
                    MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope),
 | 
			
		||||
                );
 | 
			
		||||
                handler.receive(error.clone());
 | 
			
		||||
                error
 | 
			
		||||
            })?;
 | 
			
		||||
 | 
			
		||||
        let VariableData::Function {
 | 
			
		||||
            function_data,
 | 
			
		||||
            path: function_path,
 | 
			
		||||
        } = function_data.as_ref()
 | 
			
		||||
        else {
 | 
			
		||||
            unreachable!("must be of correct type, otherwise errored out before");
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if !already_transpiled {
 | 
			
		||||
            tracing::trace!("Function not transpiled yet, transpiling.");
 | 
			
		||||
 | 
			
		||||
            let function_scope = Scope::with_parent(scope);
 | 
			
		||||
 | 
			
		||||
            for (i, param) in function_data.parameters.iter().enumerate() {
 | 
			
		||||
                function_scope.set_variable(
 | 
			
		||||
                    param,
 | 
			
		||||
                    VariableData::MacroParameter {
 | 
			
		||||
                        index: i,
 | 
			
		||||
                        macro_name: crate::util::identifier_to_macro(param).to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let statements = function_data.statements.clone();
 | 
			
		||||
 | 
			
		||||
            let modified_name = function_data.annotations.get("deobfuscate").map_or_else(
 | 
			
		||||
                || {
 | 
			
		||||
                    let hash_data = program_identifier.to_string() + "\0" + identifier_span.str();
 | 
			
		||||
                    Ok("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase())
 | 
			
		||||
                },
 | 
			
		||||
                |val| match val {
 | 
			
		||||
                    TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()),
 | 
			
		||||
                    TranspileAnnotationValue::Expression(expr) => expr
 | 
			
		||||
                        .comptime_eval(scope, handler)
 | 
			
		||||
                        .and_then(|val| val.to_string_no_macro())
 | 
			
		||||
                        .ok_or_else(|| {
 | 
			
		||||
                            let err = TranspileError::IllegalAnnotationContent(
 | 
			
		||||
                                IllegalAnnotationContent {
 | 
			
		||||
                                    annotation: identifier_span.clone(),
 | 
			
		||||
                                    message: "Cannot evaluate annotation at compile time"
 | 
			
		||||
                                        .to_string(),
 | 
			
		||||
                                },
 | 
			
		||||
                            );
 | 
			
		||||
                            handler.receive(err.clone());
 | 
			
		||||
                            err
 | 
			
		||||
                        }),
 | 
			
		||||
                    TranspileAnnotationValue::Map(_) => {
 | 
			
		||||
                        let err =
 | 
			
		||||
                            TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
 | 
			
		||||
                                annotation: identifier_span.clone(),
 | 
			
		||||
                                message: "Deobfuscate annotation cannot be a map.".to_string(),
 | 
			
		||||
                            });
 | 
			
		||||
                        handler.receive(err.clone());
 | 
			
		||||
                        Err(err)
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            )?;
 | 
			
		||||
 | 
			
		||||
            let function_location = format!(
 | 
			
		||||
                "{namespace}:{modified_name}",
 | 
			
		||||
                namespace = function_data.namespace
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            function_path.set(function_location.clone()).unwrap();
 | 
			
		||||
 | 
			
		||||
            let commands =
 | 
			
		||||
                self.transpile_function(&statements, program_identifier, &function_scope, handler)?;
 | 
			
		||||
 | 
			
		||||
            let namespace = self.datapack.namespace_mut(&function_data.namespace);
 | 
			
		||||
 | 
			
		||||
            if namespace.function(&modified_name).is_some() {
 | 
			
		||||
                let err = TranspileError::ConflictingFunctionNames(ConflictingFunctionNames {
 | 
			
		||||
                    name: modified_name,
 | 
			
		||||
                    definition: identifier_span.clone(),
 | 
			
		||||
                });
 | 
			
		||||
                handler.receive(err.clone());
 | 
			
		||||
                return Err(err);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let function = namespace.function_mut(&modified_name);
 | 
			
		||||
            function.get_commands_mut().extend(commands);
 | 
			
		||||
 | 
			
		||||
            if function_data.annotations.contains_key("tick") {
 | 
			
		||||
                self.datapack.add_tick(&function_location);
 | 
			
		||||
            }
 | 
			
		||||
            if function_data.annotations.contains_key("load") {
 | 
			
		||||
                self.datapack.add_load(&function_location);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let parameters = &function_data.parameters;
 | 
			
		||||
 | 
			
		||||
        let function_location = function_path
 | 
			
		||||
            .get()
 | 
			
		||||
            .ok_or_else(|| {
 | 
			
		||||
                let error = TranspileError::MissingFunctionDeclaration(
 | 
			
		||||
                    MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope),
 | 
			
		||||
                );
 | 
			
		||||
                handler.receive(error.clone());
 | 
			
		||||
                error
 | 
			
		||||
            })
 | 
			
		||||
            .map(String::to_owned)?;
 | 
			
		||||
 | 
			
		||||
        let arg_count = arguments.map(<[&Expression]>::len);
 | 
			
		||||
        if arg_count.is_some_and(|arg_count| arg_count != parameters.len()) {
 | 
			
		||||
            let err = TranspileError::InvalidFunctionArguments(InvalidFunctionArguments {
 | 
			
		||||
                expected: parameters.len(),
 | 
			
		||||
                actual: arg_count.unwrap_or_default(),
 | 
			
		||||
                span: identifier_span.clone(),
 | 
			
		||||
            });
 | 
			
		||||
            handler.receive(err.clone());
 | 
			
		||||
            Err(err)
 | 
			
		||||
        } else if arg_count.is_some_and(|arg_count| arg_count > 0) {
 | 
			
		||||
            {
 | 
			
		||||
                #[derive(Debug, Clone, EnumAsInner)]
 | 
			
		||||
                enum Parameter {
 | 
			
		||||
                    Static(MacroString),
 | 
			
		||||
                    Dynamic {
 | 
			
		||||
                        prepare_cmds: Vec<Command>,
 | 
			
		||||
                        storage_name: String,
 | 
			
		||||
                        path: String,
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let mut compiled_args = Vec::new();
 | 
			
		||||
                let mut errs = Vec::new();
 | 
			
		||||
 | 
			
		||||
                for expression in arguments.iter().flat_map(|x| x.iter()) {
 | 
			
		||||
                    let value = match expression {
 | 
			
		||||
                        Expression::Primary(Primary::Lua(lua)) => {
 | 
			
		||||
                            lua.eval_comptime(scope, handler).and_then(|val| match val {
 | 
			
		||||
                                Some(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)),
 | 
			
		||||
                                Some(val) => Ok(Parameter::Static(val.to_macro_string())),
 | 
			
		||||
                                None => {
 | 
			
		||||
                                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                        expression: expression.span(),
 | 
			
		||||
                                        expected_type: ExpectedType::String,
 | 
			
		||||
                                    });
 | 
			
		||||
                                    handler.receive(err.clone());
 | 
			
		||||
                                    Err(err)
 | 
			
		||||
                                }
 | 
			
		||||
                            })
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(Primary::Integer(num)) => {
 | 
			
		||||
                            Ok(Parameter::Static(num.span.str().to_string().into()))
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(Primary::Boolean(bool)) => {
 | 
			
		||||
                            Ok(Parameter::Static(bool.span.str().to_string().into()))
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(Primary::StringLiteral(string)) => {
 | 
			
		||||
                            Ok(Parameter::Static(string.str_content().to_string().into()))
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(Primary::MacroStringLiteral(literal)) => {
 | 
			
		||||
                            Ok(Parameter::Static(literal.into()))
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(primary @ Primary::Identifier(ident)) => {
 | 
			
		||||
                            let var = scope.get_variable(ident.span.str()).ok_or_else(|| {
 | 
			
		||||
                                let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
 | 
			
		||||
                                    identifier: ident.span(),
 | 
			
		||||
                                });
 | 
			
		||||
                                handler.receive(err.clone());
 | 
			
		||||
                                err
 | 
			
		||||
                            })?;
 | 
			
		||||
                            match var.as_ref() {
 | 
			
		||||
                                VariableData::MacroParameter { macro_name, .. } => {
 | 
			
		||||
                                    Ok(Parameter::Static(MacroString::MacroString(vec![
 | 
			
		||||
                                        MacroStringPart::MacroUsage(macro_name.clone()),
 | 
			
		||||
                                    ])))
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                VariableData::BooleanStorage { .. }
 | 
			
		||||
                                | VariableData::ScoreboardValue { .. } => {
 | 
			
		||||
                                    let (temp_storage, mut temp_path) =
 | 
			
		||||
                                        self.get_temp_storage_locations(1);
 | 
			
		||||
                                    let prepare_cmds = self.transpile_primary_expression(
 | 
			
		||||
                                        primary,
 | 
			
		||||
                                        &super::expression::DataLocation::Storage {
 | 
			
		||||
                                            storage_name: temp_storage.clone(),
 | 
			
		||||
                                            path: temp_path[0].clone(),
 | 
			
		||||
                                            r#type: match var.as_ref() {
 | 
			
		||||
                                                VariableData::BooleanStorage { .. } => {
 | 
			
		||||
                                                    StorageType::Boolean
 | 
			
		||||
                                                }
 | 
			
		||||
                                                VariableData::ScoreboardValue { .. } => {
 | 
			
		||||
                                                    StorageType::Int
 | 
			
		||||
                                                }
 | 
			
		||||
                                                _ => unreachable!("checked in parent match"),
 | 
			
		||||
                                            },
 | 
			
		||||
                                        },
 | 
			
		||||
                                        scope,
 | 
			
		||||
                                        handler,
 | 
			
		||||
                                    )?;
 | 
			
		||||
 | 
			
		||||
                                    Ok(Parameter::Dynamic {
 | 
			
		||||
                                        prepare_cmds,
 | 
			
		||||
                                        storage_name: temp_storage,
 | 
			
		||||
                                        path: std::mem::take(&mut temp_path[0]),
 | 
			
		||||
                                    })
 | 
			
		||||
                                }
 | 
			
		||||
                                _ => todo!("other variable types"),
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        Expression::Primary(
 | 
			
		||||
                            Primary::Parenthesized(_)
 | 
			
		||||
                            | Primary::Prefix(_)
 | 
			
		||||
                            | Primary::Indexed(_)
 | 
			
		||||
                            | Primary::FunctionCall(_),
 | 
			
		||||
                        )
 | 
			
		||||
                        | Expression::Binary(_) => {
 | 
			
		||||
                            let (temp_storage, mut temp_path) = self.get_temp_storage_locations(1);
 | 
			
		||||
                            let prepare_cmds = self.transpile_expression(
 | 
			
		||||
                                expression,
 | 
			
		||||
                                &super::expression::DataLocation::Storage {
 | 
			
		||||
                                    storage_name: temp_storage.clone(),
 | 
			
		||||
                                    path: temp_path[0].clone(),
 | 
			
		||||
                                    r#type: StorageType::Int,
 | 
			
		||||
                                },
 | 
			
		||||
                                scope,
 | 
			
		||||
                                handler,
 | 
			
		||||
                            )?;
 | 
			
		||||
 | 
			
		||||
                            Ok(Parameter::Dynamic {
 | 
			
		||||
                                prepare_cmds,
 | 
			
		||||
                                storage_name: temp_storage,
 | 
			
		||||
                                path: std::mem::take(&mut temp_path[0]),
 | 
			
		||||
                            })
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    match value {
 | 
			
		||||
                        Ok(value) => {
 | 
			
		||||
                            compiled_args.push(value);
 | 
			
		||||
                        }
 | 
			
		||||
                        Err(err) => {
 | 
			
		||||
                            compiled_args
 | 
			
		||||
                                .push(Parameter::Static(MacroString::String(String::new())));
 | 
			
		||||
                            errs.push(err.clone());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if let Some(err) = errs.first() {
 | 
			
		||||
                    return Err(err.clone());
 | 
			
		||||
                }
 | 
			
		||||
                if compiled_args.iter().any(|arg| !arg.is_static()) {
 | 
			
		||||
                    let (mut setup_cmds, move_cmds, static_params) = parameters.clone().into_iter().zip(compiled_args).fold(
 | 
			
		||||
                        (Vec::new(), Vec::new(), BTreeMap::new()),
 | 
			
		||||
                        |(mut acc_setup, mut acc_move, mut statics), (arg_name, data)| {
 | 
			
		||||
                            let arg_name = crate::util::identifier_to_macro(&arg_name);
 | 
			
		||||
                            match data {
 | 
			
		||||
                            Parameter::Static(s) => {
 | 
			
		||||
                                match s {
 | 
			
		||||
                                    MacroString::String(value) => statics.insert(arg_name.to_string(), MacroString::String(crate::util::escape_str(&value).to_string())),
 | 
			
		||||
                                    MacroString::MacroString(parts) => {
 | 
			
		||||
                                        let parts = parts.into_iter().map(|part| {
 | 
			
		||||
                                            match part {
 | 
			
		||||
                                                MacroStringPart::String(s) => MacroStringPart::String(crate::util::escape_str(&s).to_string()),
 | 
			
		||||
                                                MacroStringPart::MacroUsage(m) => MacroStringPart::MacroUsage(m),
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }).collect();
 | 
			
		||||
                                        statics.insert(arg_name.to_string(), MacroString::MacroString(parts))
 | 
			
		||||
                                    }
 | 
			
		||||
                                };
 | 
			
		||||
                            }
 | 
			
		||||
                            Parameter::Dynamic { prepare_cmds, storage_name, path } => {
 | 
			
		||||
                                acc_setup.extend(prepare_cmds);
 | 
			
		||||
                                acc_move.push(Command::Raw(format!(r"data modify storage shulkerscript:function_arguments {arg_name} set from storage {storage_name} {path}")));
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        (acc_setup, acc_move, statics)},
 | 
			
		||||
                    );
 | 
			
		||||
                    let statics_len = static_params.len();
 | 
			
		||||
                    let joined_statics =
 | 
			
		||||
                        super::util::join_macro_strings(static_params.into_iter().enumerate().map(
 | 
			
		||||
                            |(i, (k, v))| match v {
 | 
			
		||||
                                MacroString::String(s) => {
 | 
			
		||||
                                    let mut s = format!(r#"{k}:"{s}""#);
 | 
			
		||||
                                    if i < statics_len - 1 {
 | 
			
		||||
                                        s.push(',');
 | 
			
		||||
                                    }
 | 
			
		||||
                                    MacroString::String(s)
 | 
			
		||||
                                }
 | 
			
		||||
                                MacroString::MacroString(mut parts) => {
 | 
			
		||||
                                    parts.insert(0, MacroStringPart::String(format!(r#"{k}:""#)));
 | 
			
		||||
                                    let mut ending = '"'.to_string();
 | 
			
		||||
                                    if i < statics_len - 1 {
 | 
			
		||||
                                        ending.push(',');
 | 
			
		||||
                                    }
 | 
			
		||||
                                    parts.push(MacroStringPart::String(ending));
 | 
			
		||||
                                    MacroString::MacroString(parts)
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                        ));
 | 
			
		||||
                    let statics_cmd = match joined_statics {
 | 
			
		||||
                        MacroString::String(s) => Command::Raw(format!(
 | 
			
		||||
                            r"data merge storage shulkerscript:function_arguments {{{s}}}"
 | 
			
		||||
                        )),
 | 
			
		||||
                        MacroString::MacroString(_) => Command::UsesMacro(
 | 
			
		||||
                            super::util::join_macro_strings([
 | 
			
		||||
                                MacroString::String(
 | 
			
		||||
                                    "data merge storage shulkerscript:function_arguments {"
 | 
			
		||||
                                        .to_string(),
 | 
			
		||||
                                ),
 | 
			
		||||
                                joined_statics,
 | 
			
		||||
                                MacroString::String("}".to_string()),
 | 
			
		||||
                            ])
 | 
			
		||||
                            .into(),
 | 
			
		||||
                        ),
 | 
			
		||||
                    };
 | 
			
		||||
                    setup_cmds.push(statics_cmd);
 | 
			
		||||
                    setup_cmds.extend(move_cmds);
 | 
			
		||||
 | 
			
		||||
                    Ok((
 | 
			
		||||
                        function_location,
 | 
			
		||||
                        TranspiledFunctionArguments::Dynamic(setup_cmds),
 | 
			
		||||
                    ))
 | 
			
		||||
                } else {
 | 
			
		||||
                    let function_args = parameters
 | 
			
		||||
                        .clone()
 | 
			
		||||
                        .into_iter()
 | 
			
		||||
                        .zip(
 | 
			
		||||
                            compiled_args
 | 
			
		||||
                                .into_iter()
 | 
			
		||||
                                .map(|arg| arg.into_static().expect("checked in if condition")),
 | 
			
		||||
                        )
 | 
			
		||||
                        .collect();
 | 
			
		||||
                    Ok((
 | 
			
		||||
                        function_location,
 | 
			
		||||
                        TranspiledFunctionArguments::Static(function_args),
 | 
			
		||||
                    ))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok((function_location, TranspiledFunctionArguments::None))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn transpile_function(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        statements: &[Statement],
 | 
			
		||||
        program_identifier: &str,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        let mut errors = Vec::new();
 | 
			
		||||
        let commands = statements
 | 
			
		||||
            .iter()
 | 
			
		||||
            .flat_map(|statement| {
 | 
			
		||||
                self.transpile_statement(statement, program_identifier, scope, handler)
 | 
			
		||||
                    .unwrap_or_else(|err| {
 | 
			
		||||
                        errors.push(err);
 | 
			
		||||
                        Vec::new()
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        if !errors.is_empty() {
 | 
			
		||||
            return Err(errors.remove(0));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(commands)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn transpile_statement(
 | 
			
		||||
    pub(super) fn transpile_statement(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        statement: &Statement,
 | 
			
		||||
        program_identifier: &str,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,6 +44,39 @@ impl Display for MacroString {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MacroString {
 | 
			
		||||
    /// Check if the macro string contains any macros
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn contains_macros(&self) -> bool {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::String(_) => false,
 | 
			
		||||
            Self::MacroString(parts) => parts
 | 
			
		||||
                .iter()
 | 
			
		||||
                .any(|p| matches!(p, MacroStringPart::MacroUsage(_))),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the string representation of the macro string or the parts if it contains macros
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Errors
 | 
			
		||||
    /// - If the macro string contains macros
 | 
			
		||||
    pub fn as_str(&self) -> Result<std::borrow::Cow<str>, &[MacroStringPart]> {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::String(s) => Ok(std::borrow::Cow::Borrowed(s)),
 | 
			
		||||
            Self::MacroString(parts) if self.contains_macros() => Err(parts),
 | 
			
		||||
            Self::MacroString(parts) => Ok(std::borrow::Cow::Owned(
 | 
			
		||||
                parts
 | 
			
		||||
                    .iter()
 | 
			
		||||
                    .map(|p| match p {
 | 
			
		||||
                        MacroStringPart::String(s) => s.clone(),
 | 
			
		||||
                        MacroStringPart::MacroUsage(m) => format!("$({m})"),
 | 
			
		||||
                    })
 | 
			
		||||
                    .collect::<String>(),
 | 
			
		||||
            )),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn normalize_program_identifier<S>(identifier: S) -> String
 | 
			
		||||
where
 | 
			
		||||
    S: AsRef<str>,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										42
									
								
								src/util.rs
								
								
								
								
							
							
						
						
									
										42
									
								
								src/util.rs
								
								
								
								
							| 
						 | 
				
			
			@ -79,6 +79,48 @@ pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<str> {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Transforms an identifier to a macro name that only contains `a-zA-Z0-9_`.
 | 
			
		||||
#[cfg(feature = "shulkerbox")]
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub fn identifier_to_scoreboard_target(ident: &str) -> std::borrow::Cow<str> {
 | 
			
		||||
    if !(..=16).contains(&ident.len())
 | 
			
		||||
        || ident
 | 
			
		||||
            .chars()
 | 
			
		||||
            .any(|c| c != '_' || !c.is_ascii_alphanumeric())
 | 
			
		||||
    {
 | 
			
		||||
        std::borrow::Cow::Owned(chksum_md5::hash(ident).to_hex_lowercase().split_off(16))
 | 
			
		||||
    } else {
 | 
			
		||||
        std::borrow::Cow::Borrowed(ident)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Transforms an identifier to a name that only contains `a-zA-Z0-9_`.
 | 
			
		||||
/// Does only strip invalid characters if the `shulkerbox` feature is not enabled.
 | 
			
		||||
#[cfg(not(feature = "shulkerbox"))]
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub fn identifier_to_scoreboard_target(ident: &str) -> std::borrow::Cow<str> {
 | 
			
		||||
    if !(..=16).contains(&ident.len())
 | 
			
		||||
        || ident
 | 
			
		||||
            .chars()
 | 
			
		||||
            .any(|c| c != '_' || !c.is_ascii_alphanumeric())
 | 
			
		||||
    {
 | 
			
		||||
        let new_ident = ident
 | 
			
		||||
            .chars()
 | 
			
		||||
            .map(|c| {
 | 
			
		||||
                if *c != '_' && !c.is_ascii_alphanumeric() {
 | 
			
		||||
                    '_'
 | 
			
		||||
                } else {
 | 
			
		||||
                    c
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .collect::<String>();
 | 
			
		||||
 | 
			
		||||
        std::borrow::Cow::Owned(new_ident)
 | 
			
		||||
    } else {
 | 
			
		||||
        std::borrow::Cow::Borrowed(ident)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Returns whether a string is a valid scoreboard name.
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub fn is_valid_scoreboard_objective_name(name: &str) -> bool {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue