implement dynamic (scoreboard, storages) values passed in as macro parameters
- does not compile without the 'shulkerbox' feature enabled
This commit is contained in:
		
							parent
							
								
									5740172ddb
								
							
						
					
					
						commit
						cebe3e9cb0
					
				| 
						 | 
				
			
			@ -22,10 +22,8 @@ serde = ["dep:serde", "dep:flexbuffers", "shulkerbox?/serde"]
 | 
			
		|||
shulkerbox = ["dep:shulkerbox", "dep:chksum-md5"]
 | 
			
		||||
zip = ["shulkerbox?/zip"]
 | 
			
		||||
 | 
			
		||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
 | 
			
		||||
path-absolutize = { version = "3.1.1", features = ["use_unix_paths_on_wasm"] }
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
cfg-if = "1.0.0"
 | 
			
		||||
chksum-md5 = { version = "0.1.0", optional = true }
 | 
			
		||||
colored = "3.0.0"
 | 
			
		||||
derive_more = { version = "2.0.1", default-features = false, features = ["deref", "deref_mut", "from"] }
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +32,6 @@ flexbuffers = { version = "25.2.10", optional = true }
 | 
			
		|||
getset = "0.1.2"
 | 
			
		||||
itertools = "0.14.0"
 | 
			
		||||
mlua = { version = "0.10.2", features = ["lua54", "vendored"], optional = true }
 | 
			
		||||
path-absolutize = "3.1.1"
 | 
			
		||||
pathdiff = "0.2.3"
 | 
			
		||||
serde = { version = "1.0.217", features = ["derive"], optional = true }
 | 
			
		||||
# shulkerbox = { version = "0.1.0", default-features = false, optional = true }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ use crate::{
 | 
			
		|||
    semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{expression::ValueType, FunctionData};
 | 
			
		||||
use super::{expression::ExpectedType, FunctionData};
 | 
			
		||||
 | 
			
		||||
/// Errors that can occur during transpilation.
 | 
			
		||||
#[allow(clippy::module_name_repetitions, missing_docs)]
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +40,8 @@ pub enum TranspileError {
 | 
			
		|||
    AssignmentError(#[from] AssignmentError),
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    UnknownIdentifier(#[from] UnknownIdentifier),
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    MissingValue(#[from] MissingValue),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The result of a transpilation operation.
 | 
			
		||||
| 
						 | 
				
			
			@ -193,7 +195,7 @@ impl std::error::Error for IllegalAnnotationContent {}
 | 
			
		|||
#[derive(Debug, Clone, PartialEq, Eq)]
 | 
			
		||||
pub struct MismatchedTypes {
 | 
			
		||||
    pub expression: Span,
 | 
			
		||||
    pub expected_type: ValueType,
 | 
			
		||||
    pub expected_type: ExpectedType,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Display for MismatchedTypes {
 | 
			
		||||
| 
						 | 
				
			
			@ -280,3 +282,30 @@ impl Display for UnknownIdentifier {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
impl std::error::Error for UnknownIdentifier {}
 | 
			
		||||
 | 
			
		||||
/// An error that occurs when there is a value expected but none provided.
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq)]
 | 
			
		||||
pub struct MissingValue {
 | 
			
		||||
    pub expression: Span,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Display for MissingValue {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        write!(
 | 
			
		||||
            f,
 | 
			
		||||
            "{}",
 | 
			
		||||
            Message::new(
 | 
			
		||||
                Severity::Error,
 | 
			
		||||
                "The expression is expected to return a value, but no value is found."
 | 
			
		||||
            )
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        write!(
 | 
			
		||||
            f,
 | 
			
		||||
            "\n{}",
 | 
			
		||||
            SourceCodeDisplay::new(&self.expression, Option::<u8>::None)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::error::Error for MissingValue {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,17 +4,21 @@ use std::{fmt::Display, sync::Arc};
 | 
			
		|||
 | 
			
		||||
use super::{Scope, VariableData};
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::VoidHandler,
 | 
			
		||||
    base::{self, Handler, VoidHandler},
 | 
			
		||||
    lexical::token::MacroStringLiteralPart,
 | 
			
		||||
    syntax::syntax_tree::expression::{
 | 
			
		||||
        Binary, BinaryOperator, Expression, PrefixOperator, Primary,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "shulkerbox")]
 | 
			
		||||
use enum_as_inner::EnumAsInner;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "shulkerbox")]
 | 
			
		||||
use shulkerbox::prelude::{Command, Condition, Execute};
 | 
			
		||||
use shulkerbox::{
 | 
			
		||||
    prelude::{Command, Condition, Execute},
 | 
			
		||||
    util::MacroString,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "shulkerbox")]
 | 
			
		||||
use super::{
 | 
			
		||||
| 
						 | 
				
			
			@ -23,8 +27,11 @@ use super::{
 | 
			
		|||
};
 | 
			
		||||
#[cfg(feature = "shulkerbox")]
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::{self, source_file::SourceElement, Handler},
 | 
			
		||||
    transpile::{error::FunctionArgumentsNotAllowed, TranspileError},
 | 
			
		||||
    base::source_file::SourceElement,
 | 
			
		||||
    transpile::{
 | 
			
		||||
        error::{FunctionArgumentsNotAllowed, MissingValue},
 | 
			
		||||
        TranspileError,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Compile-time evaluated value
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +41,7 @@ pub enum ComptimeValue {
 | 
			
		|||
    Boolean(bool),
 | 
			
		||||
    Integer(i64),
 | 
			
		||||
    String(String),
 | 
			
		||||
    MacroString(String),
 | 
			
		||||
    MacroString(MacroString),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Display for ComptimeValue {
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +50,7 @@ impl Display for ComptimeValue {
 | 
			
		|||
            Self::Boolean(boolean) => write!(f, "{boolean}"),
 | 
			
		||||
            Self::Integer(int) => write!(f, "{int}"),
 | 
			
		||||
            Self::String(string) => write!(f, "{string}"),
 | 
			
		||||
            Self::MacroString(macro_string) => write!(f, "{macro_string}"),
 | 
			
		||||
            Self::MacroString(macro_string) => write!(f, "{}", macro_string.compile()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -80,6 +87,69 @@ impl Display for ValueType {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(missing_docs)]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq)]
 | 
			
		||||
pub enum ExpectedType {
 | 
			
		||||
    Boolean,
 | 
			
		||||
    Integer,
 | 
			
		||||
    String,
 | 
			
		||||
    Any,
 | 
			
		||||
    AnyOf(Vec<ExpectedType>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ExpectedType {
 | 
			
		||||
    /// Add another expected type to the list of expected types.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn or(self, or: Self) -> Self {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Boolean | Self::Integer | Self::String => match or {
 | 
			
		||||
                Self::Boolean | Self::Integer | Self::String => Self::AnyOf(vec![self, or]),
 | 
			
		||||
                Self::Any => Self::Any,
 | 
			
		||||
                Self::AnyOf(mut types) => {
 | 
			
		||||
                    types.push(self);
 | 
			
		||||
                    Self::AnyOf(types)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            Self::Any => Self::Any,
 | 
			
		||||
            Self::AnyOf(mut types) => {
 | 
			
		||||
                types.push(or);
 | 
			
		||||
                Self::AnyOf(types)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Display for ExpectedType {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Boolean => write!(f, "boolean"),
 | 
			
		||||
            Self::Integer => write!(f, "integer"),
 | 
			
		||||
            Self::String => write!(f, "string"),
 | 
			
		||||
            Self::Any => write!(f, "any"),
 | 
			
		||||
            Self::AnyOf(types) => {
 | 
			
		||||
                write!(f, "any of [")?;
 | 
			
		||||
                for (i, r#type) in types.iter().enumerate() {
 | 
			
		||||
                    if i != 0 {
 | 
			
		||||
                        write!(f, ", ")?;
 | 
			
		||||
                    }
 | 
			
		||||
                    write!(f, "{type}")?;
 | 
			
		||||
                }
 | 
			
		||||
                write!(f, "]")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<ValueType> for ExpectedType {
 | 
			
		||||
    fn from(value: ValueType) -> Self {
 | 
			
		||||
        match value {
 | 
			
		||||
            ValueType::Boolean => Self::Boolean,
 | 
			
		||||
            ValueType::Integer => Self::Integer,
 | 
			
		||||
            ValueType::String => Self::String,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Location of data
 | 
			
		||||
#[allow(missing_docs)]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq)]
 | 
			
		||||
| 
						 | 
				
			
			@ -151,6 +221,7 @@ impl StorageType {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Condition
 | 
			
		||||
#[cfg(feature = "shulkerbox")]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)]
 | 
			
		||||
pub enum ExtendedCondition {
 | 
			
		||||
    /// Runtime condition
 | 
			
		||||
| 
						 | 
				
			
			@ -171,10 +242,14 @@ impl Expression {
 | 
			
		|||
 | 
			
		||||
    /// Evaluate at compile-time.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn comptime_eval(&self, scope: &Arc<Scope>) -> Option<ComptimeValue> {
 | 
			
		||||
    pub fn comptime_eval(
 | 
			
		||||
        &self,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> Option<ComptimeValue> {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Primary(primary) => primary.comptime_eval(scope),
 | 
			
		||||
            Self::Binary(binary) => binary.comptime_eval(scope),
 | 
			
		||||
            Self::Primary(primary) => primary.comptime_eval(scope, handler),
 | 
			
		||||
            Self::Binary(binary) => binary.comptime_eval(scope, handler),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -216,16 +291,19 @@ impl Primary {
 | 
			
		|||
                        && prefix.operand().can_yield_type(r#type, scope)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            #[cfg_attr(not(feature = "lua"), expect(unused_variables))]
 | 
			
		||||
            Self::Lua(lua) => {
 | 
			
		||||
                if let Ok(value) = lua.eval(&VoidHandler) {
 | 
			
		||||
                    match value {
 | 
			
		||||
                        mlua::Value::Boolean(_) => matches!(r#type, ValueType::Boolean),
 | 
			
		||||
                        mlua::Value::Integer(_) => matches!(r#type, ValueType::Integer),
 | 
			
		||||
                        mlua::Value::String(_) => matches!(r#type, ValueType::String),
 | 
			
		||||
                        _ => false,
 | 
			
		||||
                cfg_if::cfg_if! {
 | 
			
		||||
                    if #[cfg(feature = "lua")] {
 | 
			
		||||
                        lua.eval(&VoidHandler).map_or(false, |value| match value {
 | 
			
		||||
                            mlua::Value::Boolean(_) => matches!(r#type, ValueType::Boolean),
 | 
			
		||||
                            mlua::Value::Integer(_) => matches!(r#type, ValueType::Integer),
 | 
			
		||||
                            mlua::Value::String(_) => matches!(r#type, ValueType::String),
 | 
			
		||||
                            _ => false,
 | 
			
		||||
                        })
 | 
			
		||||
                    } else {
 | 
			
		||||
                        false
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Self::StringLiteral(_) | Self::MacroStringLiteral(_) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -236,7 +314,11 @@ impl Primary {
 | 
			
		|||
 | 
			
		||||
    /// Evaluate at compile-time.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn comptime_eval(&self, scope: &Arc<Scope>) -> Option<ComptimeValue> {
 | 
			
		||||
    pub fn comptime_eval(
 | 
			
		||||
        &self,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> Option<ComptimeValue> {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Boolean(boolean) => Some(ComptimeValue::Boolean(boolean.value())),
 | 
			
		||||
            Self::Integer(int) => Some(ComptimeValue::Integer(int.as_i64())),
 | 
			
		||||
| 
						 | 
				
			
			@ -244,20 +326,30 @@ impl Primary {
 | 
			
		|||
                string_literal.str_content().to_string(),
 | 
			
		||||
            )),
 | 
			
		||||
            Self::Identifier(_) | Self::FunctionCall(_) => None,
 | 
			
		||||
            Self::Parenthesized(parenthesized) => parenthesized.expression().comptime_eval(scope),
 | 
			
		||||
            Self::Prefix(prefix) => prefix.operand().comptime_eval(scope).and_then(|val| {
 | 
			
		||||
                match (prefix.operator(), val) {
 | 
			
		||||
                    (PrefixOperator::LogicalNot(_), ComptimeValue::Boolean(boolean)) => {
 | 
			
		||||
                        Some(ComptimeValue::Boolean(!boolean))
 | 
			
		||||
                    }
 | 
			
		||||
                    (PrefixOperator::Negate(_), ComptimeValue::Integer(int)) => {
 | 
			
		||||
                        Some(ComptimeValue::Integer(-int))
 | 
			
		||||
                    }
 | 
			
		||||
                    _ => None,
 | 
			
		||||
                }
 | 
			
		||||
            }),
 | 
			
		||||
            // TODO: throw error
 | 
			
		||||
            Self::Lua(lua) => lua.eval_comptime(&VoidHandler).ok().flatten(),
 | 
			
		||||
            Self::Parenthesized(parenthesized) => {
 | 
			
		||||
                parenthesized.expression().comptime_eval(scope, handler)
 | 
			
		||||
            }
 | 
			
		||||
            Self::Prefix(prefix) => {
 | 
			
		||||
                prefix
 | 
			
		||||
                    .operand()
 | 
			
		||||
                    .comptime_eval(scope, handler)
 | 
			
		||||
                    .and_then(|val| match (prefix.operator(), val) {
 | 
			
		||||
                        (PrefixOperator::LogicalNot(_), ComptimeValue::Boolean(boolean)) => {
 | 
			
		||||
                            Some(ComptimeValue::Boolean(!boolean))
 | 
			
		||||
                        }
 | 
			
		||||
                        (PrefixOperator::Negate(_), ComptimeValue::Integer(int)) => {
 | 
			
		||||
                            Some(ComptimeValue::Integer(-int))
 | 
			
		||||
                        }
 | 
			
		||||
                        _ => None,
 | 
			
		||||
                    })
 | 
			
		||||
            }
 | 
			
		||||
            Self::Lua(lua) => lua
 | 
			
		||||
                .eval_comptime(&VoidHandler)
 | 
			
		||||
                .inspect_err(|err| {
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
                })
 | 
			
		||||
                .ok()
 | 
			
		||||
                .flatten(),
 | 
			
		||||
            Self::MacroStringLiteral(macro_string_literal) => {
 | 
			
		||||
                if macro_string_literal
 | 
			
		||||
                    .parts()
 | 
			
		||||
| 
						 | 
				
			
			@ -265,7 +357,7 @@ impl Primary {
 | 
			
		|||
                    .any(|part| matches!(part, MacroStringLiteralPart::MacroUsage { .. }))
 | 
			
		||||
                {
 | 
			
		||||
                    Some(ComptimeValue::MacroString(
 | 
			
		||||
                        macro_string_literal.str_content(),
 | 
			
		||||
                        macro_string_literal.clone().into(),
 | 
			
		||||
                    ))
 | 
			
		||||
                } else {
 | 
			
		||||
                    Some(ComptimeValue::String(macro_string_literal.str_content()))
 | 
			
		||||
| 
						 | 
				
			
			@ -318,9 +410,13 @@ impl Binary {
 | 
			
		|||
 | 
			
		||||
    /// Evaluate at compile-time.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn comptime_eval(&self, scope: &Arc<Scope>) -> Option<ComptimeValue> {
 | 
			
		||||
        let left = self.left_operand().comptime_eval(scope)?;
 | 
			
		||||
        let right = self.right_operand().comptime_eval(scope)?;
 | 
			
		||||
    pub fn comptime_eval(
 | 
			
		||||
        &self,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> Option<ComptimeValue> {
 | 
			
		||||
        let left = self.left_operand().comptime_eval(scope, handler)?;
 | 
			
		||||
        let right = self.right_operand().comptime_eval(scope, handler)?;
 | 
			
		||||
 | 
			
		||||
        match (left, right) {
 | 
			
		||||
            (ComptimeValue::Boolean(true), _) | (_, ComptimeValue::Boolean(true)) if matches!(self.operator(), BinaryOperator::LogicalOr(..))
 | 
			
		||||
| 
						 | 
				
			
			@ -426,7 +522,7 @@ impl Transpiler {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    #[expect(clippy::too_many_lines)]
 | 
			
		||||
    fn transpile_primary_expression(
 | 
			
		||||
    pub(super) fn transpile_primary_expression(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        primary: &Primary,
 | 
			
		||||
        target: &DataLocation,
 | 
			
		||||
| 
						 | 
				
			
			@ -442,31 +538,39 @@ impl Transpiler {
 | 
			
		|||
            ),
 | 
			
		||||
            Primary::FunctionCall(func) => match target {
 | 
			
		||||
                DataLocation::ScoreboardValue { objective, target } => {
 | 
			
		||||
                    let call_cmd = self.transpile_function_call(func, scope, handler)?;
 | 
			
		||||
                    Ok(vec![Command::Execute(Execute::Store(
 | 
			
		||||
                        format!("result score {target} {objective}").into(),
 | 
			
		||||
                        Box::new(Execute::Run(Box::new(call_cmd))),
 | 
			
		||||
                    ))])
 | 
			
		||||
                    let mut cmds = self.transpile_function_call(func, scope, handler)?;
 | 
			
		||||
                    if let Some(call_cmd) = cmds.pop() {
 | 
			
		||||
                        let modified = Command::Execute(Execute::Store(
 | 
			
		||||
                            format!("result score {target} {objective}").into(),
 | 
			
		||||
                            Box::new(Execute::Run(Box::new(call_cmd))),
 | 
			
		||||
                        ));
 | 
			
		||||
                        cmds.push(modified);
 | 
			
		||||
                    }
 | 
			
		||||
                    Ok(cmds)
 | 
			
		||||
                }
 | 
			
		||||
                DataLocation::Storage {
 | 
			
		||||
                    storage_name,
 | 
			
		||||
                    path,
 | 
			
		||||
                    r#type,
 | 
			
		||||
                } => {
 | 
			
		||||
                    let call_cmd = self.transpile_function_call(func, scope, handler)?;
 | 
			
		||||
                    let result_success = if matches!(r#type, StorageType::Boolean) {
 | 
			
		||||
                        "success"
 | 
			
		||||
                    } else {
 | 
			
		||||
                        "result"
 | 
			
		||||
                    };
 | 
			
		||||
                    Ok(vec![Command::Execute(Execute::Store(
 | 
			
		||||
                        format!(
 | 
			
		||||
                            "{result_success} storage {storage_name} {path} {type} 1.0d",
 | 
			
		||||
                            r#type = r#type.as_str()
 | 
			
		||||
                        )
 | 
			
		||||
                        .into(),
 | 
			
		||||
                        Box::new(Execute::Run(Box::new(call_cmd))),
 | 
			
		||||
                    ))])
 | 
			
		||||
                    let mut cmds = self.transpile_function_call(func, scope, handler)?;
 | 
			
		||||
                    if let Some(call_cmd) = cmds.pop() {
 | 
			
		||||
                        let result_success = if matches!(r#type, StorageType::Boolean) {
 | 
			
		||||
                            "success"
 | 
			
		||||
                        } else {
 | 
			
		||||
                            "result"
 | 
			
		||||
                        };
 | 
			
		||||
                        let modified = Command::Execute(Execute::Store(
 | 
			
		||||
                            format!(
 | 
			
		||||
                                "{result_success} storage {storage_name} {path} {type} 1.0d",
 | 
			
		||||
                                r#type = r#type.as_str()
 | 
			
		||||
                            )
 | 
			
		||||
                            .into(),
 | 
			
		||||
                            Box::new(Execute::Run(Box::new(call_cmd))),
 | 
			
		||||
                        ));
 | 
			
		||||
                        cmds.push(modified);
 | 
			
		||||
                    }
 | 
			
		||||
                    Ok(cmds)
 | 
			
		||||
                }
 | 
			
		||||
                DataLocation::Tag { tag_name, entity } => {
 | 
			
		||||
                    if func
 | 
			
		||||
| 
						 | 
				
			
			@ -511,11 +615,17 @@ impl Transpiler {
 | 
			
		|||
            Primary::Parenthesized(parenthesized) => {
 | 
			
		||||
                self.transpile_expression(parenthesized.expression(), target, scope, handler)
 | 
			
		||||
            }
 | 
			
		||||
            Primary::Lua(lua) => {
 | 
			
		||||
            Primary::Lua(lua) =>
 | 
			
		||||
            {
 | 
			
		||||
                #[expect(clippy::option_if_let_else)]
 | 
			
		||||
                if let Some(value) = lua.eval_comptime(handler)? {
 | 
			
		||||
                    self.store_comptime_value(&value, target, lua, handler)
 | 
			
		||||
                } else {
 | 
			
		||||
                    todo!("handle no return value from lua")
 | 
			
		||||
                    let err = TranspileError::MissingValue(MissingValue {
 | 
			
		||||
                        expression: lua.span(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
                    Err(err)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Primary::StringLiteral(_) | Primary::MacroStringLiteral(_) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -536,7 +646,7 @@ impl Transpiler {
 | 
			
		|||
                    Ok(cmds)
 | 
			
		||||
                } else {
 | 
			
		||||
                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                        expected_type: target.value_type(),
 | 
			
		||||
                        expected_type: target.value_type().into(),
 | 
			
		||||
                        expression: primary.span(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -601,7 +711,7 @@ impl Transpiler {
 | 
			
		|||
                    _ => {
 | 
			
		||||
                        let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                            expression: prefix.span(),
 | 
			
		||||
                            expected_type: ValueType::Integer,
 | 
			
		||||
                            expected_type: ExpectedType::Integer,
 | 
			
		||||
                        });
 | 
			
		||||
                        handler.receive(err.clone());
 | 
			
		||||
                        Err(err)
 | 
			
		||||
| 
						 | 
				
			
			@ -665,7 +775,7 @@ impl Transpiler {
 | 
			
		|||
                                } else {
 | 
			
		||||
                                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                        expression: primary.span(),
 | 
			
		||||
                                        expected_type: target.value_type(),
 | 
			
		||||
                                        expected_type: target.value_type().into(),
 | 
			
		||||
                                    });
 | 
			
		||||
                                    handler.receive(err.clone());
 | 
			
		||||
                                    Err(err)
 | 
			
		||||
| 
						 | 
				
			
			@ -697,16 +807,21 @@ impl Transpiler {
 | 
			
		|||
                                        | StorageType::Int
 | 
			
		||||
                                        | StorageType::Long
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    let cmd = Command::Raw(format!(
 | 
			
		||||
                                        "data modify storage {storage_name} {path} set value {value}{suffix}",
 | 
			
		||||
                                        value = score_target,
 | 
			
		||||
                                        suffix = r#type.suffix()
 | 
			
		||||
                                    let cmd = Command::Execute(Execute::Store(
 | 
			
		||||
                                        format!(
 | 
			
		||||
                                            "result storage {storage_name} {path} {t} 1.0",
 | 
			
		||||
                                            t = r#type.as_str()
 | 
			
		||||
                                        )
 | 
			
		||||
                                        .into(),
 | 
			
		||||
                                        Box::new(Execute::Run(Box::new(Command::Raw(format!(
 | 
			
		||||
                                            "scoreboard players get {score_target} {objective}"
 | 
			
		||||
                                        ))))),
 | 
			
		||||
                                    ));
 | 
			
		||||
                                    Ok(vec![cmd])
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                        expression: primary.span(),
 | 
			
		||||
                                        expected_type: target.value_type(),
 | 
			
		||||
                                        expected_type: target.value_type().into(),
 | 
			
		||||
                                    });
 | 
			
		||||
                                    handler.receive(err.clone());
 | 
			
		||||
                                    Err(err)
 | 
			
		||||
| 
						 | 
				
			
			@ -714,7 +829,7 @@ impl Transpiler {
 | 
			
		|||
                            }
 | 
			
		||||
                            DataLocation::Tag { .. } => {
 | 
			
		||||
                                let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                    expected_type: ValueType::Boolean,
 | 
			
		||||
                                    expected_type: ExpectedType::Boolean,
 | 
			
		||||
                                    expression: primary.span(),
 | 
			
		||||
                                });
 | 
			
		||||
                                handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -723,7 +838,7 @@ impl Transpiler {
 | 
			
		|||
                        },
 | 
			
		||||
                        _ => {
 | 
			
		||||
                            let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                expected_type: target.value_type(),
 | 
			
		||||
                                expected_type: target.value_type().into(),
 | 
			
		||||
                                expression: primary.span(),
 | 
			
		||||
                            });
 | 
			
		||||
                            handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -748,7 +863,7 @@ impl Transpiler {
 | 
			
		|||
        scope: &Arc<super::Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        if let Some(value) = binary.comptime_eval(scope) {
 | 
			
		||||
        if let Some(value) = binary.comptime_eval(scope, handler) {
 | 
			
		||||
            self.transpile_comptime_value(&value, binary, target, scope, handler)
 | 
			
		||||
        } else {
 | 
			
		||||
            match binary.operator() {
 | 
			
		||||
| 
						 | 
				
			
			@ -808,7 +923,7 @@ impl Transpiler {
 | 
			
		|||
            }
 | 
			
		||||
            Primary::Integer(_) => {
 | 
			
		||||
                let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                    expected_type: ValueType::Boolean,
 | 
			
		||||
                    expected_type: ExpectedType::Boolean,
 | 
			
		||||
                    expression: primary.span(),
 | 
			
		||||
                });
 | 
			
		||||
                handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -874,7 +989,7 @@ impl Transpiler {
 | 
			
		|||
                        )),
 | 
			
		||||
                        _ => {
 | 
			
		||||
                            let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                expected_type: ValueType::Boolean,
 | 
			
		||||
                                expected_type: ExpectedType::Boolean,
 | 
			
		||||
                                expression: primary.span(),
 | 
			
		||||
                            });
 | 
			
		||||
                            handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -911,28 +1026,34 @@ impl Transpiler {
 | 
			
		|||
                }
 | 
			
		||||
                PrefixOperator::Negate(_) => {
 | 
			
		||||
                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                        expected_type: ValueType::Boolean,
 | 
			
		||||
                        expected_type: ExpectedType::Boolean,
 | 
			
		||||
                        expression: primary.span(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
                    Err(err)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            Primary::Lua(lua) => {
 | 
			
		||||
                match lua.eval_comptime(handler)? {
 | 
			
		||||
                    Some(ComptimeValue::String(value) | ComptimeValue::MacroString(value)) => {
 | 
			
		||||
                        // TODO: mark condition as containing macro if so
 | 
			
		||||
                        Ok((
 | 
			
		||||
                            Vec::new(),
 | 
			
		||||
                            ExtendedCondition::Runtime(Condition::Atom(value.into())),
 | 
			
		||||
                        ))
 | 
			
		||||
                    }
 | 
			
		||||
                    Some(ComptimeValue::Boolean(boolean)) => {
 | 
			
		||||
                        Ok((Vec::new(), ExtendedCondition::Comptime(boolean)))
 | 
			
		||||
                    }
 | 
			
		||||
                    _ => todo!("invalid or none lua return value"),
 | 
			
		||||
            Primary::Lua(lua) => match lua.eval_comptime(handler)? {
 | 
			
		||||
                Some(ComptimeValue::String(value)) => Ok((
 | 
			
		||||
                    Vec::new(),
 | 
			
		||||
                    ExtendedCondition::Runtime(Condition::Atom(value.into())),
 | 
			
		||||
                )),
 | 
			
		||||
                Some(ComptimeValue::MacroString(value)) => Ok((
 | 
			
		||||
                    Vec::new(),
 | 
			
		||||
                    ExtendedCondition::Runtime(Condition::Atom(value)),
 | 
			
		||||
                )),
 | 
			
		||||
                Some(ComptimeValue::Boolean(boolean)) => {
 | 
			
		||||
                    Ok((Vec::new(), ExtendedCondition::Comptime(boolean)))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
                _ => {
 | 
			
		||||
                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                        expected_type: ExpectedType::Boolean,
 | 
			
		||||
                        expression: primary.span(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
                    Err(err)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -956,7 +1077,7 @@ impl Transpiler {
 | 
			
		|||
            }
 | 
			
		||||
            _ => {
 | 
			
		||||
                let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                    expected_type: ValueType::Boolean,
 | 
			
		||||
                    expected_type: ExpectedType::Boolean,
 | 
			
		||||
                    expression: binary.span(),
 | 
			
		||||
                });
 | 
			
		||||
                handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -1025,7 +1146,7 @@ impl Transpiler {
 | 
			
		|||
            DataLocation::ScoreboardValue { .. } => None,
 | 
			
		||||
            DataLocation::Tag { .. } => {
 | 
			
		||||
                let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                    expected_type: ValueType::Boolean,
 | 
			
		||||
                    expected_type: ExpectedType::Boolean,
 | 
			
		||||
                    expression: binary.span(),
 | 
			
		||||
                });
 | 
			
		||||
                handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -1052,7 +1173,7 @@ impl Transpiler {
 | 
			
		|||
                }
 | 
			
		||||
                StorageType::Boolean => {
 | 
			
		||||
                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                        expected_type: ValueType::Boolean,
 | 
			
		||||
                        expected_type: ExpectedType::Boolean,
 | 
			
		||||
                        expression: binary.span(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -1201,7 +1322,7 @@ impl Transpiler {
 | 
			
		|||
                )]),
 | 
			
		||||
                DataLocation::Tag { .. } => {
 | 
			
		||||
                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                        expected_type: ValueType::Boolean,
 | 
			
		||||
                        expected_type: ExpectedType::Boolean,
 | 
			
		||||
                        expression: original.span(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -1226,7 +1347,7 @@ impl Transpiler {
 | 
			
		|||
                    } else {
 | 
			
		||||
                        let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                            expression: original.span(),
 | 
			
		||||
                            expected_type: target.value_type(),
 | 
			
		||||
                            expected_type: target.value_type().into(),
 | 
			
		||||
                        });
 | 
			
		||||
                        handler.receive(err.clone());
 | 
			
		||||
                        Err(err)
 | 
			
		||||
| 
						 | 
				
			
			@ -1258,7 +1379,7 @@ impl Transpiler {
 | 
			
		|||
                    } else {
 | 
			
		||||
                        let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                            expression: original.span(),
 | 
			
		||||
                            expected_type: target.value_type(),
 | 
			
		||||
                            expected_type: target.value_type().into(),
 | 
			
		||||
                        });
 | 
			
		||||
                        handler.receive(err.clone());
 | 
			
		||||
                        Err(err)
 | 
			
		||||
| 
						 | 
				
			
			@ -1267,7 +1388,7 @@ impl Transpiler {
 | 
			
		|||
            },
 | 
			
		||||
            ComptimeValue::String(_) | ComptimeValue::MacroString(_) => {
 | 
			
		||||
                let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                    expected_type: target.value_type(),
 | 
			
		||||
                    expected_type: target.value_type().into(),
 | 
			
		||||
                    expression: original.span(),
 | 
			
		||||
                });
 | 
			
		||||
                handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -1276,6 +1397,7 @@ impl Transpiler {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[expect(clippy::needless_pass_by_ref_mut)]
 | 
			
		||||
    fn store_comptime_value(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        value: &ComptimeValue,
 | 
			
		||||
| 
						 | 
				
			
			@ -1285,17 +1407,12 @@ impl Transpiler {
 | 
			
		|||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        match value {
 | 
			
		||||
            ComptimeValue::Integer(int) => match target {
 | 
			
		||||
                DataLocation::ScoreboardValue { objective, target } => {
 | 
			
		||||
                    Ok(vec![Command::Raw(format!(
 | 
			
		||||
                        "scoreboard players set {target} {objective} {value}",
 | 
			
		||||
                        target = target,
 | 
			
		||||
                        objective = objective,
 | 
			
		||||
                        value = int
 | 
			
		||||
                    ))])
 | 
			
		||||
                }
 | 
			
		||||
                DataLocation::ScoreboardValue { objective, target } => Ok(vec![Command::Raw(
 | 
			
		||||
                    format!("scoreboard players set {target} {objective} {int}"),
 | 
			
		||||
                )]),
 | 
			
		||||
                DataLocation::Tag { .. } => {
 | 
			
		||||
                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                        expected_type: ValueType::Boolean,
 | 
			
		||||
                        expected_type: ExpectedType::Boolean,
 | 
			
		||||
                        expression: source.span(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -1323,7 +1440,7 @@ impl Transpiler {
 | 
			
		|||
                    } else {
 | 
			
		||||
                        let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                            expression: source.span(),
 | 
			
		||||
                            expected_type: ValueType::Integer,
 | 
			
		||||
                            expected_type: ExpectedType::Integer,
 | 
			
		||||
                        });
 | 
			
		||||
                        handler.receive(err.clone());
 | 
			
		||||
                        Err(err)
 | 
			
		||||
| 
						 | 
				
			
			@ -1370,6 +1487,7 @@ impl Transpiler {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[expect(clippy::unused_self, clippy::needless_pass_by_ref_mut)]
 | 
			
		||||
    fn store_condition_success(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        cond: ExtendedCondition,
 | 
			
		||||
| 
						 | 
				
			
			@ -1394,7 +1512,7 @@ impl Transpiler {
 | 
			
		|||
                    )
 | 
			
		||||
                } else {
 | 
			
		||||
                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                        expected_type: ValueType::Boolean,
 | 
			
		||||
                        expected_type: ExpectedType::Boolean,
 | 
			
		||||
                        expression: source.span(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -1426,7 +1544,7 @@ impl Transpiler {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get temporary scoreboard locations.
 | 
			
		||||
    fn get_temp_scoreboard_locations(&mut self, amount: usize) -> (String, Vec<String>) {
 | 
			
		||||
    pub(super) fn get_temp_scoreboard_locations(&mut self, amount: usize) -> (String, Vec<String>) {
 | 
			
		||||
        let objective = "shu_temp_".to_string()
 | 
			
		||||
            + &chksum_md5::hash(&self.main_namespace_name).to_hex_lowercase();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1449,4 +1567,26 @@ impl Transpiler {
 | 
			
		|||
 | 
			
		||||
        (objective, targets)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get temporary storage locations.
 | 
			
		||||
    pub(super) fn get_temp_storage_locations(&mut self, amount: usize) -> (String, Vec<String>) {
 | 
			
		||||
        let storage_name = "shulkerscript:temp_".to_string()
 | 
			
		||||
            + &chksum_md5::hash(&self.main_namespace_name).to_hex_lowercase();
 | 
			
		||||
 | 
			
		||||
        let paths = (0..amount)
 | 
			
		||||
            .map(|i| {
 | 
			
		||||
                chksum_md5::hash(format!(
 | 
			
		||||
                    "{namespace}\0{j}",
 | 
			
		||||
                    namespace = self.main_namespace_name,
 | 
			
		||||
                    j = i + self.temp_counter
 | 
			
		||||
                ))
 | 
			
		||||
                .to_hex_lowercase()
 | 
			
		||||
                .split_off(16)
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        self.temp_counter = self.temp_counter.wrapping_add(amount);
 | 
			
		||||
 | 
			
		||||
        (storage_name, paths)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,9 +70,10 @@ mod enabled {
 | 
			
		|||
        ) -> TranspileResult<Option<ComptimeValue>> {
 | 
			
		||||
            let lua_result = self.eval(handler)?;
 | 
			
		||||
 | 
			
		||||
            self.handle_lua_result(lua_result).inspect_err(|err| {
 | 
			
		||||
                handler.receive(err.clone());
 | 
			
		||||
            })
 | 
			
		||||
            self.handle_lua_result(lua_result, handler)
 | 
			
		||||
                .inspect_err(|err| {
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
                })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn add_globals(&self, lua: &Lua) -> mlua::Result<()> {
 | 
			
		||||
| 
						 | 
				
			
			@ -88,19 +89,26 @@ mod enabled {
 | 
			
		|||
            Ok(())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn handle_lua_result(&self, value: Value) -> TranspileResult<Option<ComptimeValue>> {
 | 
			
		||||
        fn handle_lua_result(
 | 
			
		||||
            &self,
 | 
			
		||||
            value: Value,
 | 
			
		||||
            handler: &impl Handler<base::Error>,
 | 
			
		||||
        ) -> TranspileResult<Option<ComptimeValue>> {
 | 
			
		||||
            match value {
 | 
			
		||||
                Value::Nil => Ok(None),
 | 
			
		||||
                Value::String(s) => Ok(Some(ComptimeValue::String(s.to_string_lossy()))),
 | 
			
		||||
                Value::Integer(i) => Ok(Some(ComptimeValue::Integer(i))),
 | 
			
		||||
                // TODO: change when floating point comptime numbers are supported
 | 
			
		||||
                Value::Number(n) => Ok(Some(ComptimeValue::String(n.to_string()))),
 | 
			
		||||
                Value::Function(f) => self.handle_lua_result(f.call(()).map_err(|err| {
 | 
			
		||||
                    TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err(
 | 
			
		||||
                        &err,
 | 
			
		||||
                        self.span(),
 | 
			
		||||
                    ))
 | 
			
		||||
                })?),
 | 
			
		||||
                Value::Function(f) => self.handle_lua_result(
 | 
			
		||||
                    f.call(()).map_err(|err| {
 | 
			
		||||
                        TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err(
 | 
			
		||||
                            &err,
 | 
			
		||||
                            self.span(),
 | 
			
		||||
                        ))
 | 
			
		||||
                    })?,
 | 
			
		||||
                    handler,
 | 
			
		||||
                ),
 | 
			
		||||
                Value::Boolean(boolean) => Ok(Some(ComptimeValue::Boolean(boolean))),
 | 
			
		||||
                Value::Error(_)
 | 
			
		||||
                | Value::Table(_)
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +120,8 @@ mod enabled {
 | 
			
		|||
                        code_block: self.span(),
 | 
			
		||||
                        error_message: format!("invalid return type {}", value.type_name()),
 | 
			
		||||
                    });
 | 
			
		||||
                    todo!("pass error to handler: {err}")
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
                    Err(err)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -127,7 +136,7 @@ mod disabled {
 | 
			
		|||
        transpile::error::{TranspileError, TranspileResult},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    use super::expression::ComptimeValue;
 | 
			
		||||
    use crate::transpile::expression::ComptimeValue;
 | 
			
		||||
 | 
			
		||||
    impl LuaCode {
 | 
			
		||||
        /// Will always return an error because Lua code evaluation is disabled.
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +145,7 @@ mod disabled {
 | 
			
		|||
        /// # Errors
 | 
			
		||||
        /// - Always, as the lua feature is disabled
 | 
			
		||||
        #[tracing::instrument(level = "debug", name = "eval_lua", skip_all, ret)]
 | 
			
		||||
        pub fn eval(&self, handler: &impl Handler<base::Error>) -> TranspileResult<mlua::Value> {
 | 
			
		||||
        pub fn eval(&self, handler: &impl Handler<base::Error>) -> TranspileResult<()> {
 | 
			
		||||
            handler.receive(TranspileError::LuaDisabled);
 | 
			
		||||
            tracing::error!("Lua code evaluation is disabled");
 | 
			
		||||
            Err(TranspileError::LuaDisabled)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,17 @@
 | 
			
		|||
//! Transpiler for `Shulkerscript`
 | 
			
		||||
 | 
			
		||||
use chksum_md5 as md5;
 | 
			
		||||
use enum_as_inner::EnumAsInner;
 | 
			
		||||
use std::{
 | 
			
		||||
    collections::{BTreeMap, HashMap, HashSet},
 | 
			
		||||
    ops::Deref,
 | 
			
		||||
    sync::{Arc, OnceLock},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use shulkerbox::datapack::{self, Command, Datapack, Execute};
 | 
			
		||||
use shulkerbox::{
 | 
			
		||||
    datapack::{self, Command, Datapack, Execute},
 | 
			
		||||
    util::{MacroString, MacroStringPart},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::{
 | 
			
		||||
| 
						 | 
				
			
			@ -30,8 +34,8 @@ use crate::{
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
    error::{MismatchedTypes, TranspileError, TranspileResult},
 | 
			
		||||
    expression::{ComptimeValue, ExtendedCondition, ValueType},
 | 
			
		||||
    error::{MismatchedTypes, TranspileError, TranspileResult, UnknownIdentifier},
 | 
			
		||||
    expression::{ComptimeValue, ExpectedType, ExtendedCondition, StorageType},
 | 
			
		||||
    variables::{Scope, VariableData},
 | 
			
		||||
    FunctionData, TranspileAnnotationValue,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +56,13 @@ pub struct Transpiler {
 | 
			
		|||
    aliases: HashMap<(String, String), (String, String)>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum TranspiledFunctionArguments {
 | 
			
		||||
    None,
 | 
			
		||||
    Static(BTreeMap<String, MacroString>),
 | 
			
		||||
    Dynamic(Vec<Command>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Transpiler {
 | 
			
		||||
    /// Creates a new transpiler.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
| 
						 | 
				
			
			@ -265,22 +276,20 @@ impl Transpiler {
 | 
			
		|||
        arguments: Option<&[&Expression]>,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<(String, Option<BTreeMap<String, String>>)> {
 | 
			
		||||
    ) -> 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 = match scope
 | 
			
		||||
        let already_transpiled = scope
 | 
			
		||||
            .get_variable(identifier_span.str())
 | 
			
		||||
            .expect("called function should be in scope")
 | 
			
		||||
            .as_ref()
 | 
			
		||||
        {
 | 
			
		||||
            VariableData::Function { path, .. } => Some(path.get().is_some()),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
        .expect("called variable should be of type function");
 | 
			
		||||
            .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())
 | 
			
		||||
| 
						 | 
				
			
			@ -331,7 +340,7 @@ impl Transpiler {
 | 
			
		|||
                |val| match val {
 | 
			
		||||
                    TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()),
 | 
			
		||||
                    TranspileAnnotationValue::Expression(expr) => expr
 | 
			
		||||
                        .comptime_eval(scope)
 | 
			
		||||
                        .comptime_eval(scope, handler)
 | 
			
		||||
                        .and_then(|val| val.to_string_no_macro())
 | 
			
		||||
                        .ok_or_else(|| {
 | 
			
		||||
                            let err = TranspileError::IllegalAnnotationContent(
 | 
			
		||||
| 
						 | 
				
			
			@ -385,7 +394,7 @@ impl Transpiler {
 | 
			
		|||
            function_path.set(function_location.clone()).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let parameters = function_data.parameters.clone();
 | 
			
		||||
        let parameters = &function_data.parameters;
 | 
			
		||||
 | 
			
		||||
        let function_location = function_path
 | 
			
		||||
            .get()
 | 
			
		||||
| 
						 | 
				
			
			@ -402,71 +411,185 @@ impl Transpiler {
 | 
			
		|||
        if arg_count.is_some_and(|arg_count| arg_count != parameters.len()) {
 | 
			
		||||
            let err = TranspileError::InvalidFunctionArguments(InvalidFunctionArguments {
 | 
			
		||||
                expected: parameters.len(),
 | 
			
		||||
                actual: arg_count.expect("checked in if condition"),
 | 
			
		||||
                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) {
 | 
			
		||||
            let mut compiled_args = Vec::new();
 | 
			
		||||
            let mut errs = Vec::new();
 | 
			
		||||
            for expression in arguments.iter().flat_map(|x| x.iter()) {
 | 
			
		||||
                let value = match expression {
 | 
			
		||||
                    Expression::Primary(Primary::FunctionCall(func)) => self
 | 
			
		||||
                        .transpile_function_call(func, scope, handler)
 | 
			
		||||
                        .map(|cmd| match cmd {
 | 
			
		||||
                            Command::Raw(s) => s,
 | 
			
		||||
                            _ => unreachable!("Function call should always return a raw command"),
 | 
			
		||||
                        }),
 | 
			
		||||
                    Expression::Primary(Primary::Lua(lua)) => {
 | 
			
		||||
                        lua.eval_comptime(handler).and_then(|opt| {
 | 
			
		||||
                            opt.map_or_else(
 | 
			
		||||
                                || {
 | 
			
		||||
            {
 | 
			
		||||
                #[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(handler).and_then(|val| match val {
 | 
			
		||||
                                Some(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)),
 | 
			
		||||
                                Some(val) => Ok(Parameter::Static(val.to_string().into())),
 | 
			
		||||
                                None => {
 | 
			
		||||
                                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                        expression: expression.span(),
 | 
			
		||||
                                        expected_type: ValueType::String,
 | 
			
		||||
                                        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::FunctionArgument { .. } => {
 | 
			
		||||
                                    Ok(Parameter::Static(MacroString::MacroString(vec![
 | 
			
		||||
                                        MacroStringPart::MacroUsage(
 | 
			
		||||
                                            crate::util::identifier_to_macro(ident.span.str())
 | 
			
		||||
                                                .to_string(),
 | 
			
		||||
                                        ),
 | 
			
		||||
                                    ])))
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                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: StorageType::Int,
 | 
			
		||||
                                        },
 | 
			
		||||
                                        scope,
 | 
			
		||||
                                        handler,
 | 
			
		||||
                                    )?;
 | 
			
		||||
 | 
			
		||||
                                    Ok(Parameter::Dynamic {
 | 
			
		||||
                                        prepare_cmds: dbg!(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::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,
 | 
			
		||||
                                },
 | 
			
		||||
                                |val| Ok(val.to_string()),
 | 
			
		||||
                            )
 | 
			
		||||
                        })
 | 
			
		||||
                    }
 | 
			
		||||
                    Expression::Primary(Primary::Integer(num)) => Ok(num.span.str().to_string()),
 | 
			
		||||
                    Expression::Primary(Primary::Boolean(bool)) => Ok(bool.span.str().to_string()),
 | 
			
		||||
                    Expression::Primary(Primary::StringLiteral(string)) => {
 | 
			
		||||
                        Ok(string.str_content().to_string())
 | 
			
		||||
                    }
 | 
			
		||||
                    Expression::Primary(Primary::MacroStringLiteral(literal)) => {
 | 
			
		||||
                        Ok(literal.str_content())
 | 
			
		||||
                    }
 | 
			
		||||
                    Expression::Primary(
 | 
			
		||||
                        Primary::Identifier(_) | Primary::Parenthesized(_) | Primary::Prefix(_),
 | 
			
		||||
                    ) => {
 | 
			
		||||
                        todo!("allow identifiers, parenthesized & prefix expressions as arguments")
 | 
			
		||||
                    }
 | 
			
		||||
                                scope,
 | 
			
		||||
                                handler,
 | 
			
		||||
                            )?;
 | 
			
		||||
 | 
			
		||||
                    Expression::Binary(_) => todo!("allow binary expressions as arguments"),
 | 
			
		||||
                };
 | 
			
		||||
                            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(String::new());
 | 
			
		||||
                        errs.push(err.clone());
 | 
			
		||||
                    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) = parameters.clone().into_iter().zip(compiled_args).fold(
 | 
			
		||||
                        (Vec::new(), Vec::new()),
 | 
			
		||||
                        |(mut acc_setup, mut acc_move), (arg_name, data)| {
 | 
			
		||||
                            let arg_name = crate::util::identifier_to_macro(&arg_name);
 | 
			
		||||
                            match data {
 | 
			
		||||
                            Parameter::Static(s) => {
 | 
			
		||||
                                // TODO: optimize by combining into single `data merge` command
 | 
			
		||||
                                let move_cmd = match s {
 | 
			
		||||
                                    MacroString::String(value) => Command::Raw(format!(r#"data modify storage shulkerscript:function_arguments {arg_name} set value "{value}""#, value = crate::util::escape_str(&value))),
 | 
			
		||||
                                    MacroString::MacroString(mut parts) => {
 | 
			
		||||
                                        parts.insert(0, MacroStringPart::String(format!(r#"data modify storage shulkerscript:function_arguments {arg_name} set value ""#)));
 | 
			
		||||
                                        parts.push(MacroStringPart::String('"'.to_string()));
 | 
			
		||||
                                        Command::UsesMacro(MacroString::MacroString(parts))
 | 
			
		||||
                                    }
 | 
			
		||||
                                };
 | 
			
		||||
                                acc_move.push(move_cmd);
 | 
			
		||||
                            }
 | 
			
		||||
                            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)},
 | 
			
		||||
                    );
 | 
			
		||||
                    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),
 | 
			
		||||
                    ))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if let Some(err) = errs.first() {
 | 
			
		||||
                return Err(err.clone());
 | 
			
		||||
            }
 | 
			
		||||
            let function_args = parameters.into_iter().zip(compiled_args).collect();
 | 
			
		||||
            Ok((function_location, Some(function_args)))
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok((function_location, None))
 | 
			
		||||
            Ok((function_location, TranspiledFunctionArguments::None))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -556,9 +679,9 @@ impl Transpiler {
 | 
			
		|||
            }
 | 
			
		||||
            Statement::Semicolon(semi) => match semi.statement() {
 | 
			
		||||
                SemicolonStatement::Expression(expr) => match expr {
 | 
			
		||||
                    Expression::Primary(Primary::FunctionCall(func)) => self
 | 
			
		||||
                        .transpile_function_call(func, scope, handler)
 | 
			
		||||
                        .map(|cmd| vec![cmd]),
 | 
			
		||||
                    Expression::Primary(Primary::FunctionCall(func)) => {
 | 
			
		||||
                        self.transpile_function_call(func, scope, handler)
 | 
			
		||||
                    }
 | 
			
		||||
                    unexpected => {
 | 
			
		||||
                        let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
 | 
			
		||||
                            unexpected.clone(),
 | 
			
		||||
| 
						 | 
				
			
			@ -589,9 +712,9 @@ impl Transpiler {
 | 
			
		|||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        match expression {
 | 
			
		||||
            Expression::Primary(Primary::FunctionCall(func)) => self
 | 
			
		||||
                .transpile_function_call(func, scope, handler)
 | 
			
		||||
                .map(|cmd| vec![cmd]),
 | 
			
		||||
            Expression::Primary(Primary::FunctionCall(func)) => {
 | 
			
		||||
                self.transpile_function_call(func, scope, handler)
 | 
			
		||||
            }
 | 
			
		||||
            expression @ Expression::Primary(
 | 
			
		||||
                Primary::Integer(_)
 | 
			
		||||
                | Primary::Boolean(_)
 | 
			
		||||
| 
						 | 
				
			
			@ -610,13 +733,11 @@ impl Transpiler {
 | 
			
		|||
                Ok(vec![Command::UsesMacro(string.into())])
 | 
			
		||||
            }
 | 
			
		||||
            Expression::Primary(Primary::Lua(code)) => match code.eval_comptime(handler)? {
 | 
			
		||||
                Some(ComptimeValue::String(cmd) | ComptimeValue::MacroString(cmd)) => {
 | 
			
		||||
                    // TODO: mark command as containing macro if so
 | 
			
		||||
                    Ok(vec![Command::Raw(cmd)])
 | 
			
		||||
                }
 | 
			
		||||
                Some(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
 | 
			
		||||
                Some(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd)]),
 | 
			
		||||
                Some(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => {
 | 
			
		||||
                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                        expected_type: ValueType::String,
 | 
			
		||||
                        expected_type: ExpectedType::String,
 | 
			
		||||
                        expression: code.span(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
| 
						 | 
				
			
			@ -632,16 +753,11 @@ impl Transpiler {
 | 
			
		|||
                    scope,
 | 
			
		||||
                    handler,
 | 
			
		||||
                ),
 | 
			
		||||
            Expression::Binary(bin) => {
 | 
			
		||||
                if let Some(ComptimeValue::String(cmd) | ComptimeValue::MacroString(cmd)) =
 | 
			
		||||
                    bin.comptime_eval(scope)
 | 
			
		||||
                {
 | 
			
		||||
                    // TODO: mark as containing macro if so
 | 
			
		||||
                    Ok(vec![Command::Raw(cmd)])
 | 
			
		||||
                } else {
 | 
			
		||||
                    todo!("run binary expression")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Expression::Binary(bin) => match bin.comptime_eval(scope, handler) {
 | 
			
		||||
                Some(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
 | 
			
		||||
                Some(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd)]),
 | 
			
		||||
                _ => todo!("run binary expression"),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -650,7 +766,7 @@ impl Transpiler {
 | 
			
		|||
        func: &FunctionCall,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Command> {
 | 
			
		||||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        let arguments = func
 | 
			
		||||
            .arguments()
 | 
			
		||||
            .as_ref()
 | 
			
		||||
| 
						 | 
				
			
			@ -662,22 +778,78 @@ impl Transpiler {
 | 
			
		|||
            handler,
 | 
			
		||||
        )?;
 | 
			
		||||
        let mut function_call = format!("function {location}");
 | 
			
		||||
        if let Some(arguments) = arguments {
 | 
			
		||||
            use std::fmt::Write;
 | 
			
		||||
            let arguments = arguments
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|(ident, v)| {
 | 
			
		||||
                    format!(
 | 
			
		||||
                        r#"{macro_name}:"{escaped}""#,
 | 
			
		||||
                        macro_name = crate::util::identifier_to_macro(ident),
 | 
			
		||||
                        escaped = crate::util::escape_str(v)
 | 
			
		||||
                    )
 | 
			
		||||
                })
 | 
			
		||||
                .collect::<Vec<_>>()
 | 
			
		||||
                .join(",");
 | 
			
		||||
            write!(function_call, " {{{arguments}}}").unwrap();
 | 
			
		||||
        match arguments {
 | 
			
		||||
            TranspiledFunctionArguments::Static(arguments) => {
 | 
			
		||||
                use std::fmt::Write;
 | 
			
		||||
                let arguments = arguments
 | 
			
		||||
                    .iter()
 | 
			
		||||
                    .map(|(ident, v)| match v {
 | 
			
		||||
                        MacroString::String(s) => MacroString::String(format!(
 | 
			
		||||
                            r#"{macro_name}:"{escaped}""#,
 | 
			
		||||
                            macro_name = crate::util::identifier_to_macro(ident),
 | 
			
		||||
                            escaped = crate::util::escape_str(s)
 | 
			
		||||
                        )),
 | 
			
		||||
                        MacroString::MacroString(parts) => MacroString::MacroString(
 | 
			
		||||
                            std::iter::once(MacroStringPart::String(format!(
 | 
			
		||||
                                r#"{macro_name}:""#,
 | 
			
		||||
                                macro_name = crate::util::identifier_to_macro(ident)
 | 
			
		||||
                            )))
 | 
			
		||||
                            .chain(parts.clone().into_iter().map(|part| match part {
 | 
			
		||||
                                MacroStringPart::String(s) => {
 | 
			
		||||
                                    MacroStringPart::String(crate::util::escape_str(&s).to_string())
 | 
			
		||||
                                }
 | 
			
		||||
                                macro_usage @ MacroStringPart::MacroUsage(_) => macro_usage,
 | 
			
		||||
                            }))
 | 
			
		||||
                            .chain(std::iter::once(MacroStringPart::String('"'.to_string())))
 | 
			
		||||
                            .collect(),
 | 
			
		||||
                        ),
 | 
			
		||||
                    })
 | 
			
		||||
                    .fold(MacroString::String(String::new()), |acc, cur| match acc {
 | 
			
		||||
                        MacroString::String(mut s) => match cur {
 | 
			
		||||
                            MacroString::String(cur) => {
 | 
			
		||||
                                s.push_str(&cur);
 | 
			
		||||
                                MacroString::String(s)
 | 
			
		||||
                            }
 | 
			
		||||
                            MacroString::MacroString(cur) => {
 | 
			
		||||
                                let mut parts = vec![MacroStringPart::String(s)];
 | 
			
		||||
                                parts.extend(cur);
 | 
			
		||||
                                MacroString::MacroString(parts)
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        MacroString::MacroString(mut parts) => match cur {
 | 
			
		||||
                            MacroString::String(cur) => {
 | 
			
		||||
                                parts.push(MacroStringPart::String(cur));
 | 
			
		||||
                                MacroString::MacroString(parts)
 | 
			
		||||
                            }
 | 
			
		||||
                            MacroString::MacroString(cur) => {
 | 
			
		||||
                                parts.extend(cur);
 | 
			
		||||
                                MacroString::MacroString(parts)
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                let cmd = match arguments {
 | 
			
		||||
                    MacroString::String(arguments) => {
 | 
			
		||||
                        write!(function_call, " {{{arguments}}}").unwrap();
 | 
			
		||||
                        Command::Raw(function_call)
 | 
			
		||||
                    }
 | 
			
		||||
                    MacroString::MacroString(mut parts) => {
 | 
			
		||||
                        function_call.push_str(" {");
 | 
			
		||||
                        parts.insert(0, MacroStringPart::String(function_call));
 | 
			
		||||
                        parts.push(MacroStringPart::String('}'.to_string()));
 | 
			
		||||
                        Command::UsesMacro(MacroString::MacroString(parts))
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                Ok(vec![cmd])
 | 
			
		||||
            }
 | 
			
		||||
            TranspiledFunctionArguments::Dynamic(mut cmds) => {
 | 
			
		||||
                function_call.push_str(" with storage shulkerscript:function_arguments");
 | 
			
		||||
                cmds.push(Command::Raw(function_call));
 | 
			
		||||
                Ok(cmds)
 | 
			
		||||
            }
 | 
			
		||||
            TranspiledFunctionArguments::None => Ok(vec![Command::Raw(function_call)]),
 | 
			
		||||
        }
 | 
			
		||||
        Ok(Command::Raw(function_call))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn transpile_execute_block(
 | 
			
		||||
| 
						 | 
				
			
			@ -830,7 +1002,7 @@ impl Transpiler {
 | 
			
		|||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if let Some(ComptimeValue::Boolean(value)) = cond_expression.comptime_eval(scope) {
 | 
			
		||||
        if let Some(ComptimeValue::Boolean(value)) = cond_expression.comptime_eval(scope, handler) {
 | 
			
		||||
            if value {
 | 
			
		||||
                Ok(Some((Vec::new(), then)))
 | 
			
		||||
            } else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -380,8 +380,12 @@ fn get_single_data_location_identifiers(
 | 
			
		|||
                ) = (name, target)
 | 
			
		||||
                {
 | 
			
		||||
                    if let (Some(name_eval), Some(target_eval)) = (
 | 
			
		||||
                        objective.comptime_eval(scope).map(|val| val.to_string()),
 | 
			
		||||
                        target.comptime_eval(scope).map(|val| val.to_string()),
 | 
			
		||||
                        objective
 | 
			
		||||
                            .comptime_eval(scope, handler)
 | 
			
		||||
                            .map(|val| val.to_string()),
 | 
			
		||||
                        target
 | 
			
		||||
                            .comptime_eval(scope, handler)
 | 
			
		||||
                            .map(|val| val.to_string()),
 | 
			
		||||
                    ) {
 | 
			
		||||
                        // TODO: change invalid criteria if boolean
 | 
			
		||||
                        if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue