Compare commits
	
		
			No commits in common. "0885665bafc42ca717a32b872f3936ae19de7289" and "713977390d8211a31e0c0abb54d9265bcccf3b53" have entirely different histories.
		
	
	
		
			0885665baf
			...
			713977390d
		
	
		| 
						 | 
				
			
			@ -444,7 +444,7 @@ impl Primary {
 | 
			
		|||
            }
 | 
			
		||||
            Self::Lua(_) | Self::StringLiteral(_) | Self::Integer(_) | Self::Boolean(_) => Ok(()),
 | 
			
		||||
            Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler),
 | 
			
		||||
            Self::Identifier(_) | Self::Parenthesized(_) | Self::Prefix(_) | Self::Indexed(_) => {
 | 
			
		||||
            Self::Identifier(_) | Self::Parenthesized(_) | Self::Prefix(_) => {
 | 
			
		||||
                // TODO: correctly analyze the semantics of the primary expression
 | 
			
		||||
                Ok(())
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -185,7 +185,6 @@ pub enum Primary {
 | 
			
		|||
    Identifier(Identifier),
 | 
			
		||||
    Prefix(Prefix),
 | 
			
		||||
    Parenthesized(Parenthesized),
 | 
			
		||||
    Indexed(Indexed),
 | 
			
		||||
    Integer(Integer),
 | 
			
		||||
    Boolean(Boolean),
 | 
			
		||||
    StringLiteral(StringLiteral),
 | 
			
		||||
| 
						 | 
				
			
			@ -200,7 +199,6 @@ impl SourceElement for Primary {
 | 
			
		|||
            Self::Identifier(identifier) => identifier.span(),
 | 
			
		||||
            Self::Prefix(prefix) => prefix.span(),
 | 
			
		||||
            Self::Parenthesized(parenthesized) => parenthesized.span(),
 | 
			
		||||
            Self::Indexed(indexed) => indexed.span(),
 | 
			
		||||
            Self::Integer(int) => int.span(),
 | 
			
		||||
            Self::Boolean(bool) => bool.span(),
 | 
			
		||||
            Self::StringLiteral(string_literal) => string_literal.span(),
 | 
			
		||||
| 
						 | 
				
			
			@ -247,50 +245,6 @@ impl SourceElement for Parenthesized {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a indexed expression in the syntax tree.
 | 
			
		||||
///
 | 
			
		||||
/// Syntax Synopsis:    
 | 
			
		||||
/// ```ebnf
 | 
			
		||||
/// Indexed:
 | 
			
		||||
///     PrimaryExpression '[' Expression ']'
 | 
			
		||||
///     ;
 | 
			
		||||
/// ```
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
 | 
			
		||||
pub struct Indexed {
 | 
			
		||||
    /// The object that is indexed.
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    object: Box<Primary>,
 | 
			
		||||
    /// The left bracket.
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    left_bracket: Punctuation,
 | 
			
		||||
    /// The index expression.
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    index: Box<Expression>,
 | 
			
		||||
    /// The right bracket.
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    right_bracket: Punctuation,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Indexed {
 | 
			
		||||
    /// Dissolves the indexed expression into its components
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn dissolve(self) -> (Primary, Punctuation, Expression, Punctuation) {
 | 
			
		||||
        (
 | 
			
		||||
            *self.object,
 | 
			
		||||
            self.left_bracket,
 | 
			
		||||
            *self.index,
 | 
			
		||||
            self.right_bracket,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SourceElement for Indexed {
 | 
			
		||||
    fn span(&self) -> Span {
 | 
			
		||||
        self.object.span().join(&self.right_bracket.span).unwrap()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a prefix operator in the syntax tree.
 | 
			
		||||
///
 | 
			
		||||
/// Syntax Synopsis:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ use crate::{
 | 
			
		|||
    lexical::{
 | 
			
		||||
        token::{
 | 
			
		||||
            CommandLiteral, DocComment, Identifier, Integer, Keyword, KeywordKind, Punctuation,
 | 
			
		||||
            StringLiteral, Token,
 | 
			
		||||
            Token,
 | 
			
		||||
        },
 | 
			
		||||
        token_stream::Delimiter,
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -542,7 +542,7 @@ impl ArrayVariableDeclaration {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CriteriaSelection = (Punctuation, StringLiteral, Punctuation);
 | 
			
		||||
type CriteriaSelection = (Punctuation, AnyStringLiteral, Punctuation);
 | 
			
		||||
 | 
			
		||||
/// Represents a scoreboard variable declaration in the syntax tree.
 | 
			
		||||
///
 | 
			
		||||
| 
						 | 
				
			
			@ -550,7 +550,7 @@ type CriteriaSelection = (Punctuation, StringLiteral, Punctuation);
 | 
			
		|||
///
 | 
			
		||||
/// ```ebnf
 | 
			
		||||
/// ScoreVariableDeclaration:
 | 
			
		||||
///     'int' ('<' StringLiteral '>')? identifier '[' AnyStringLiteral? ']' VariableDeclarationAssignment?
 | 
			
		||||
///     'int' ('<' AnyStringLiteral '>')? identifier '[' AnyStringLiteral? ']' VariableDeclarationAssignment?
 | 
			
		||||
/// ```
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
 | 
			
		||||
| 
						 | 
				
			
			@ -685,14 +685,14 @@ impl TagVariableDeclaration {
 | 
			
		|||
/// Syntax Synopsis:
 | 
			
		||||
/// ```ebnf
 | 
			
		||||
/// Assignment:
 | 
			
		||||
///    AssignmentDestination '=' Expression
 | 
			
		||||
///    Identifier '=' Expression
 | 
			
		||||
/// ```
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
 | 
			
		||||
pub struct Assignment {
 | 
			
		||||
    /// The identifier of the assignment.
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    destination: AssignmentDestination,
 | 
			
		||||
    identifier: Identifier,
 | 
			
		||||
    /// The equals sign of the assignment.
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    equals: Punctuation,
 | 
			
		||||
| 
						 | 
				
			
			@ -703,7 +703,7 @@ pub struct Assignment {
 | 
			
		|||
 | 
			
		||||
impl SourceElement for Assignment {
 | 
			
		||||
    fn span(&self) -> Span {
 | 
			
		||||
        self.destination
 | 
			
		||||
        self.identifier
 | 
			
		||||
            .span()
 | 
			
		||||
            .join(&self.expression.span())
 | 
			
		||||
            .expect("The span of the assignment is invalid.")
 | 
			
		||||
| 
						 | 
				
			
			@ -713,38 +713,8 @@ impl SourceElement for Assignment {
 | 
			
		|||
impl Assignment {
 | 
			
		||||
    /// Dissolves the [`Assignment`] into its components.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn dissolve(self) -> (AssignmentDestination, Punctuation, Expression) {
 | 
			
		||||
        (self.destination, self.equals, self.expression)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents an assignment destination in the syntax tree.
 | 
			
		||||
///
 | 
			
		||||
/// Syntax Synopsis:
 | 
			
		||||
/// ```ebnf
 | 
			
		||||
/// AssignmentDestination:
 | 
			
		||||
///     Identifier
 | 
			
		||||
///     | Identifier '[' Expression ']'
 | 
			
		||||
///     ;
 | 
			
		||||
/// ```
 | 
			
		||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
			
		||||
pub enum AssignmentDestination {
 | 
			
		||||
    /// Assignment to an identifier.
 | 
			
		||||
    Identifier(Identifier),
 | 
			
		||||
    /// Assignment to an indexed identifier.
 | 
			
		||||
    Indexed(Identifier, Punctuation, Expression, Punctuation),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SourceElement for AssignmentDestination {
 | 
			
		||||
    fn span(&self) -> Span {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Identifier(identifier) => identifier.span(),
 | 
			
		||||
            Self::Indexed(identifier, _, _, close) => identifier
 | 
			
		||||
                .span()
 | 
			
		||||
                .join(&close.span())
 | 
			
		||||
                .expect("The span of the indexed assignment destination is invalid."),
 | 
			
		||||
        }
 | 
			
		||||
    pub fn dissolve(self) -> (Identifier, Punctuation, Expression) {
 | 
			
		||||
        (self.identifier, self.equals, self.expression)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -889,30 +859,15 @@ impl<'a> Parser<'a> {
 | 
			
		|||
            }
 | 
			
		||||
            _ => {
 | 
			
		||||
                // try to parse assignment
 | 
			
		||||
                // TODO: improve
 | 
			
		||||
                #[expect(clippy::option_if_let_else)]
 | 
			
		||||
                if let Ok(assignment) = self.try_parse(|p| {
 | 
			
		||||
                    let destination = {
 | 
			
		||||
                        let identifier = p.parse_identifier(&VoidHandler)?;
 | 
			
		||||
 | 
			
		||||
                        if let Ok(tree) = p.step_into(
 | 
			
		||||
                            Delimiter::Bracket,
 | 
			
		||||
                            |pp| pp.parse_expression(&VoidHandler),
 | 
			
		||||
                            &VoidHandler,
 | 
			
		||||
                        ) {
 | 
			
		||||
                            let open = tree.open;
 | 
			
		||||
                            let close = tree.close;
 | 
			
		||||
                            let expression = tree.tree?;
 | 
			
		||||
 | 
			
		||||
                            AssignmentDestination::Indexed(identifier, open, expression, close)
 | 
			
		||||
                        } else {
 | 
			
		||||
                            AssignmentDestination::Identifier(identifier)
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                    let identifier = p.parse_identifier(&VoidHandler)?;
 | 
			
		||||
                    let equals = p.parse_punctuation('=', true, &VoidHandler)?;
 | 
			
		||||
                    let expression = p.parse_expression(&VoidHandler)?;
 | 
			
		||||
 | 
			
		||||
                    Ok(SemicolonStatement::Assignment(Assignment {
 | 
			
		||||
                        destination,
 | 
			
		||||
                        identifier,
 | 
			
		||||
                        equals,
 | 
			
		||||
                        expression,
 | 
			
		||||
                    }))
 | 
			
		||||
| 
						 | 
				
			
			@ -968,7 +923,7 @@ impl<'a> Parser<'a> {
 | 
			
		|||
        let criteria_selection = if variable_type.keyword == KeywordKind::Int {
 | 
			
		||||
            self.try_parse(|p| {
 | 
			
		||||
                let open = p.parse_punctuation('<', true, &VoidHandler)?;
 | 
			
		||||
                let criteria = p.parse_string_literal(&VoidHandler)?;
 | 
			
		||||
                let criteria = p.parse_any_string_literal(&VoidHandler)?;
 | 
			
		||||
                let close = p.parse_punctuation('>', true, &VoidHandler)?;
 | 
			
		||||
                Ok((open, criteria, close))
 | 
			
		||||
            })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
//! The expression transpiler.
 | 
			
		||||
 | 
			
		||||
use std::{fmt::Display, string::ToString, sync::Arc};
 | 
			
		||||
use std::{fmt::Display, sync::Arc};
 | 
			
		||||
 | 
			
		||||
use super::{util::MacroString, Scope, VariableData};
 | 
			
		||||
use crate::{
 | 
			
		||||
| 
						 | 
				
			
			@ -265,7 +265,10 @@ impl Primary {
 | 
			
		|||
                    .get_variable(ident.span.str())
 | 
			
		||||
                    .map_or(false, |variable| match r#type {
 | 
			
		||||
                        ValueType::Boolean => {
 | 
			
		||||
                            matches!(variable.as_ref(), VariableData::BooleanStorage { .. })
 | 
			
		||||
                            matches!(
 | 
			
		||||
                                variable.as_ref(),
 | 
			
		||||
                                VariableData::Tag { .. } | VariableData::BooleanStorage { .. }
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                        ValueType::Integer => {
 | 
			
		||||
                            matches!(variable.as_ref(), VariableData::ScoreboardValue { .. })
 | 
			
		||||
| 
						 | 
				
			
			@ -286,29 +289,6 @@ impl Primary {
 | 
			
		|||
                        && prefix.operand().can_yield_type(r#type, scope)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            Self::Indexed(indexed) => {
 | 
			
		||||
                let Self::Identifier(ident) = indexed.object().as_ref() else {
 | 
			
		||||
                    todo!("throw error: cannot index anything except identifiers")
 | 
			
		||||
                };
 | 
			
		||||
                scope
 | 
			
		||||
                    .get_variable(ident.span.str())
 | 
			
		||||
                    .map_or(false, |variable| match r#type {
 | 
			
		||||
                        ValueType::Boolean => {
 | 
			
		||||
                            matches!(
 | 
			
		||||
                                variable.as_ref(),
 | 
			
		||||
                                VariableData::Tag { .. } | VariableData::BooleanStorageArray { .. }
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                        ValueType::Integer => {
 | 
			
		||||
                            matches!(
 | 
			
		||||
                                variable.as_ref(),
 | 
			
		||||
                                VariableData::Scoreboard { .. }
 | 
			
		||||
                                    | VariableData::ScoreboardArray { .. }
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                        ValueType::String => false,
 | 
			
		||||
                    })
 | 
			
		||||
            }
 | 
			
		||||
            #[cfg_attr(not(feature = "lua"), expect(unused_variables))]
 | 
			
		||||
            Self::Lua(lua) => {
 | 
			
		||||
                cfg_if::cfg_if! {
 | 
			
		||||
| 
						 | 
				
			
			@ -343,7 +323,7 @@ impl Primary {
 | 
			
		|||
            Self::StringLiteral(string_literal) => Some(ComptimeValue::String(
 | 
			
		||||
                string_literal.str_content().to_string(),
 | 
			
		||||
            )),
 | 
			
		||||
            Self::Identifier(_) | Self::FunctionCall(_) | Self::Indexed(_) => None,
 | 
			
		||||
            Self::Identifier(_) | Self::FunctionCall(_) => None,
 | 
			
		||||
            Self::Parenthesized(parenthesized) => {
 | 
			
		||||
                parenthesized.expression().comptime_eval(scope, handler)
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -749,105 +729,110 @@ impl Transpiler {
 | 
			
		|||
                }
 | 
			
		||||
            },
 | 
			
		||||
            Primary::Identifier(ident) => {
 | 
			
		||||
                let variable = scope.get_variable(ident.span.str());
 | 
			
		||||
                if let Some(variable) = variable.as_deref() {
 | 
			
		||||
                    let from = match variable {
 | 
			
		||||
                        VariableData::BooleanStorage { storage_name, path } => {
 | 
			
		||||
                            Ok(DataLocation::Storage {
 | 
			
		||||
                                storage_name: storage_name.to_string(),
 | 
			
		||||
                                path: path.to_string(),
 | 
			
		||||
                                r#type: StorageType::Boolean,
 | 
			
		||||
                            })
 | 
			
		||||
                        }
 | 
			
		||||
                        VariableData::ScoreboardValue { objective, target } => {
 | 
			
		||||
                            Ok(DataLocation::ScoreboardValue {
 | 
			
		||||
                                objective: objective.to_string(),
 | 
			
		||||
                                target: target.to_string(),
 | 
			
		||||
                            })
 | 
			
		||||
                        }
 | 
			
		||||
                        _ => {
 | 
			
		||||
                            let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                expected_type: target.value_type().into(),
 | 
			
		||||
                                expression: primary.span(),
 | 
			
		||||
                            });
 | 
			
		||||
                            handler.receive(err.clone());
 | 
			
		||||
                            Err(err)
 | 
			
		||||
                        }
 | 
			
		||||
                    }?;
 | 
			
		||||
 | 
			
		||||
                    self.move_data(&from, target, primary, handler)
 | 
			
		||||
                } else {
 | 
			
		||||
                    let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
 | 
			
		||||
                        identifier: ident.span.clone(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
                    Err(err)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Primary::Indexed(indexed) => {
 | 
			
		||||
                let Primary::Identifier(ident) = indexed.object().as_ref() else {
 | 
			
		||||
                    todo!("can only index identifier")
 | 
			
		||||
                };
 | 
			
		||||
                let variable = scope.get_variable(ident.span.str());
 | 
			
		||||
                #[expect(clippy::option_if_let_else)]
 | 
			
		||||
                if let Some(variable) = variable.as_deref() {
 | 
			
		||||
                    let from = match variable {
 | 
			
		||||
                        VariableData::Scoreboard { objective } => {
 | 
			
		||||
                            if let Some(ComptimeValue::String(target)) =
 | 
			
		||||
                                indexed.index().comptime_eval(scope, handler)
 | 
			
		||||
                            {
 | 
			
		||||
                                Ok(DataLocation::ScoreboardValue {
 | 
			
		||||
                                    objective: objective.to_string(),
 | 
			
		||||
                                    target,
 | 
			
		||||
                                })
 | 
			
		||||
                            } else {
 | 
			
		||||
                                todo!("can only index scoreboard with comptime string")
 | 
			
		||||
                    match variable {
 | 
			
		||||
                        VariableData::BooleanStorage { storage_name, path } => match target {
 | 
			
		||||
                            DataLocation::ScoreboardValue { objective, target } => {
 | 
			
		||||
                                let cmd = Command::Execute(Execute::Store(
 | 
			
		||||
                                    format!("store result score {target} {objective}").into(),
 | 
			
		||||
                                    Box::new(Execute::Run(Box::new(Command::Raw(format!(
 | 
			
		||||
                                        "data get storage {storage_name} {path}"
 | 
			
		||||
                                    ))))),
 | 
			
		||||
                                ));
 | 
			
		||||
                                Ok(vec![cmd])
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        VariableData::ScoreboardArray { objective, targets } => {
 | 
			
		||||
                            if let Some(ComptimeValue::Integer(index)) =
 | 
			
		||||
                                indexed.index().comptime_eval(scope, handler)
 | 
			
		||||
                            {
 | 
			
		||||
                                if let Some(target) = usize::try_from(index)
 | 
			
		||||
                                    .ok()
 | 
			
		||||
                                    .and_then(|index| targets.get(index))
 | 
			
		||||
                                    .map(ToString::to_string)
 | 
			
		||||
                                {
 | 
			
		||||
                                    Ok(DataLocation::ScoreboardValue {
 | 
			
		||||
                                        objective: objective.to_string(),
 | 
			
		||||
                                        target,
 | 
			
		||||
                                    })
 | 
			
		||||
                            DataLocation::Tag { tag_name, entity } => {
 | 
			
		||||
                                let cmd = Command::Execute(Execute::If(
 | 
			
		||||
                                    Condition::Atom(
 | 
			
		||||
                                        format!("data storage {storage_name} {{{path}: 1b}}")
 | 
			
		||||
                                            .into(),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    Box::new(Execute::Run(Box::new(Command::Raw(format!(
 | 
			
		||||
                                        "tag {entity} add {tag_name}"
 | 
			
		||||
                                    ))))),
 | 
			
		||||
                                    Some(Box::new(Execute::Run(Box::new(Command::Raw(format!(
 | 
			
		||||
                                        "tag {entity} remove {tag_name}"
 | 
			
		||||
                                    )))))),
 | 
			
		||||
                                ));
 | 
			
		||||
 | 
			
		||||
                                Ok(vec![cmd])
 | 
			
		||||
                            }
 | 
			
		||||
                            DataLocation::Storage {
 | 
			
		||||
                                storage_name: target_storage_name,
 | 
			
		||||
                                path: target_path,
 | 
			
		||||
                                r#type,
 | 
			
		||||
                            } => {
 | 
			
		||||
                                if matches!(r#type, StorageType::Boolean) {
 | 
			
		||||
                                    let cmd = Command::Raw(format!(
 | 
			
		||||
                                        "data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}"
 | 
			
		||||
                                    ));
 | 
			
		||||
                                    Ok(vec![cmd])
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    todo!("index out of bounds")
 | 
			
		||||
                                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                        expression: primary.span(),
 | 
			
		||||
                                        expected_type: target.value_type().into(),
 | 
			
		||||
                                    });
 | 
			
		||||
                                    handler.receive(err.clone());
 | 
			
		||||
                                    Err(err)
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                todo!("can only index array with comptime integer")
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        VariableData::BooleanStorageArray {
 | 
			
		||||
                            storage_name,
 | 
			
		||||
                            paths,
 | 
			
		||||
                        } => {
 | 
			
		||||
                            if let Some(ComptimeValue::Integer(index)) =
 | 
			
		||||
                                indexed.index().comptime_eval(scope, handler)
 | 
			
		||||
                            {
 | 
			
		||||
                                if let Some(path) = usize::try_from(index)
 | 
			
		||||
                                    .ok()
 | 
			
		||||
                                    .and_then(|index| paths.get(index))
 | 
			
		||||
                                    .map(ToString::to_string)
 | 
			
		||||
                                {
 | 
			
		||||
                                    Ok(DataLocation::Storage {
 | 
			
		||||
                                        storage_name: storage_name.to_string(),
 | 
			
		||||
                                        path,
 | 
			
		||||
                                        r#type: StorageType::Boolean,
 | 
			
		||||
                                    })
 | 
			
		||||
                        },
 | 
			
		||||
                        VariableData::ScoreboardValue {
 | 
			
		||||
                            objective,
 | 
			
		||||
                            target: score_target,
 | 
			
		||||
                        } => match target {
 | 
			
		||||
                            DataLocation::ScoreboardValue {
 | 
			
		||||
                                objective: target_objective,
 | 
			
		||||
                                target: target_target,
 | 
			
		||||
                            } => {
 | 
			
		||||
                                let cmd = Command::Raw(format!(
 | 
			
		||||
                                    "scoreboard players operation {target_target} {target_objective} = {score_target} {objective}"
 | 
			
		||||
                                ));
 | 
			
		||||
                                Ok(vec![cmd])
 | 
			
		||||
                            }
 | 
			
		||||
                            DataLocation::Storage {
 | 
			
		||||
                                storage_name,
 | 
			
		||||
                                path,
 | 
			
		||||
                                r#type,
 | 
			
		||||
                            } => {
 | 
			
		||||
                                if matches!(
 | 
			
		||||
                                    r#type,
 | 
			
		||||
                                    StorageType::Byte
 | 
			
		||||
                                        | StorageType::Double
 | 
			
		||||
                                        | StorageType::Int
 | 
			
		||||
                                        | StorageType::Long
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    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 {
 | 
			
		||||
                                    todo!("index out of bounds")
 | 
			
		||||
                                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                        expression: primary.span(),
 | 
			
		||||
                                        expected_type: target.value_type().into(),
 | 
			
		||||
                                    });
 | 
			
		||||
                                    handler.receive(err.clone());
 | 
			
		||||
                                    Err(err)
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                todo!("can only index array with comptime integer")
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                            DataLocation::Tag { .. } => {
 | 
			
		||||
                                let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                    expected_type: ExpectedType::Boolean,
 | 
			
		||||
                                    expression: primary.span(),
 | 
			
		||||
                                });
 | 
			
		||||
                                handler.receive(err.clone());
 | 
			
		||||
                                Err(err)
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        _ => {
 | 
			
		||||
                            let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                expected_type: target.value_type().into(),
 | 
			
		||||
| 
						 | 
				
			
			@ -856,9 +841,7 @@ impl Transpiler {
 | 
			
		|||
                            handler.receive(err.clone());
 | 
			
		||||
                            Err(err)
 | 
			
		||||
                        }
 | 
			
		||||
                    }?;
 | 
			
		||||
 | 
			
		||||
                    self.move_data(&from, target, primary, handler)
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
 | 
			
		||||
                        identifier: ident.span.clone(),
 | 
			
		||||
| 
						 | 
				
			
			@ -1021,57 +1004,6 @@ impl Transpiler {
 | 
			
		|||
            Primary::Parenthesized(parenthesized) => {
 | 
			
		||||
                self.transpile_expression_as_condition(parenthesized.expression(), scope, handler)
 | 
			
		||||
            }
 | 
			
		||||
            Primary::Indexed(indexed) => {
 | 
			
		||||
                let Primary::Identifier(ident) = indexed.object().as_ref() else {
 | 
			
		||||
                    todo!("can only index identifier")
 | 
			
		||||
                };
 | 
			
		||||
                #[expect(clippy::option_if_let_else)]
 | 
			
		||||
                if let Some(variable) = scope.get_variable(ident.span.str()).as_deref() {
 | 
			
		||||
                    #[expect(clippy::single_match_else)]
 | 
			
		||||
                    match variable {
 | 
			
		||||
                        VariableData::BooleanStorageArray {
 | 
			
		||||
                            storage_name,
 | 
			
		||||
                            paths,
 | 
			
		||||
                        } => {
 | 
			
		||||
                            if let Some(ComptimeValue::Integer(index)) =
 | 
			
		||||
                                indexed.index().comptime_eval(scope, handler)
 | 
			
		||||
                            {
 | 
			
		||||
                                if let Some(path) = usize::try_from(index)
 | 
			
		||||
                                    .ok()
 | 
			
		||||
                                    .and_then(|index| paths.get(index))
 | 
			
		||||
                                    .map(ToString::to_string)
 | 
			
		||||
                                {
 | 
			
		||||
                                    Ok((
 | 
			
		||||
                                        Vec::new(),
 | 
			
		||||
                                        ExtendedCondition::Runtime(Condition::Atom(
 | 
			
		||||
                                            format!("data storage {storage_name} {{{path}: 1b}}")
 | 
			
		||||
                                                .into(),
 | 
			
		||||
                                        )),
 | 
			
		||||
                                    ))
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    todo!("index out of bounds")
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                todo!("can only index array with comptime integer")
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        _ => {
 | 
			
		||||
                            let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                expected_type: ExpectedType::Boolean,
 | 
			
		||||
                                expression: primary.span(),
 | 
			
		||||
                            });
 | 
			
		||||
                            handler.receive(err.clone());
 | 
			
		||||
                            Err(err)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
 | 
			
		||||
                        identifier: ident.span.clone(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
                    Err(err)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Primary::Prefix(prefix) => match prefix.operator() {
 | 
			
		||||
                PrefixOperator::LogicalNot(_) => {
 | 
			
		||||
                    let (cmds, cond) = self.transpile_primary_expression_as_condition(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,37 +98,12 @@ mod enabled {
 | 
			
		|||
        fn add_globals(&self, lua: &Lua, scope: &Arc<Scope>) -> mlua::Result<()> {
 | 
			
		||||
            let globals = lua.globals();
 | 
			
		||||
 | 
			
		||||
            let shulkerscript_globals = {
 | 
			
		||||
                let table = lua.create_table()?;
 | 
			
		||||
 | 
			
		||||
                let (location_path, location_start, location_end) = {
 | 
			
		||||
                    let span = self.span();
 | 
			
		||||
                    let file = span.source_file();
 | 
			
		||||
                    let path = file.path().to_owned();
 | 
			
		||||
                    let start_location = span.start_location();
 | 
			
		||||
                    let end_location = span.end_location().unwrap_or_else(|| {
 | 
			
		||||
                        let line_amount = file.line_amount();
 | 
			
		||||
                        let column = file.get_line(line_amount).expect("line amount used").len();
 | 
			
		||||
 | 
			
		||||
                        crate::base::source_file::Location {
 | 
			
		||||
                            line: line_amount,
 | 
			
		||||
                            column,
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    (path, start_location, end_location)
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                table.set("file_path", location_path.to_string_lossy())?;
 | 
			
		||||
                table.set("start_line", location_start.line)?;
 | 
			
		||||
                table.set("start_column", location_start.column)?;
 | 
			
		||||
                table.set("end_line", location_end.line)?;
 | 
			
		||||
                table.set("end_column", location_end.column)?;
 | 
			
		||||
 | 
			
		||||
                table.set("version", crate::VERSION)?;
 | 
			
		||||
 | 
			
		||||
                table
 | 
			
		||||
            let location = {
 | 
			
		||||
                let span = self.span();
 | 
			
		||||
                let file = span.source_file();
 | 
			
		||||
                file.path_relative().unwrap_or_else(|| file.path().clone())
 | 
			
		||||
            };
 | 
			
		||||
            globals.set("shu_location", location.to_string_lossy())?;
 | 
			
		||||
 | 
			
		||||
            if let Some(inputs) = self.inputs() {
 | 
			
		||||
                for x in inputs.elements() {
 | 
			
		||||
| 
						 | 
				
			
			@ -137,66 +112,25 @@ mod enabled {
 | 
			
		|||
                        Some(VariableData::MacroParameter { macro_name, .. }) => {
 | 
			
		||||
                            Value::String(lua.create_string(format!("$({macro_name})"))?)
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(VariableData::Scoreboard { objective }) => {
 | 
			
		||||
                            let table = lua.create_table()?;
 | 
			
		||||
                            table.set("objective", objective.as_str())?;
 | 
			
		||||
                            Value::Table(table)
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(VariableData::ScoreboardValue { objective, target }) => {
 | 
			
		||||
                            let table = lua.create_table()?;
 | 
			
		||||
                            table.set("objective", lua.create_string(objective)?)?;
 | 
			
		||||
                            table.set("target", lua.create_string(target)?)?;
 | 
			
		||||
                            Value::Table(table)
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(VariableData::ScoreboardArray { objective, targets }) => {
 | 
			
		||||
                            let table = lua.create_table()?;
 | 
			
		||||
                            table.set("objective", objective.as_str())?;
 | 
			
		||||
                            let values = lua.create_table_from(
 | 
			
		||||
                                targets
 | 
			
		||||
                                    .iter()
 | 
			
		||||
                                    .enumerate()
 | 
			
		||||
                                    .map(|(i, target)| (i + 1, target.as_str())),
 | 
			
		||||
                            )?;
 | 
			
		||||
                            table.set("targets", values)?;
 | 
			
		||||
                            Value::Table(table)
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(VariableData::BooleanStorage { storage_name, path }) => {
 | 
			
		||||
                            let table = lua.create_table()?;
 | 
			
		||||
                            table.set("storage", lua.create_string(storage_name)?)?;
 | 
			
		||||
                            table.set("path", lua.create_string(path)?)?;
 | 
			
		||||
                            Value::Table(table)
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(VariableData::BooleanStorageArray {
 | 
			
		||||
                            storage_name,
 | 
			
		||||
                            paths,
 | 
			
		||||
                        }) => {
 | 
			
		||||
                            let table = lua.create_table()?;
 | 
			
		||||
                            table.set("storage", storage_name.as_str())?;
 | 
			
		||||
                            let values = lua.create_table_from(
 | 
			
		||||
                                paths
 | 
			
		||||
                                    .iter()
 | 
			
		||||
                                    .enumerate()
 | 
			
		||||
                                    .map(|(i, path)| (i + 1, path.as_str())),
 | 
			
		||||
                            )?;
 | 
			
		||||
                            table.set("paths", values)?;
 | 
			
		||||
                            Value::Table(table)
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(VariableData::Tag { tag_name }) => {
 | 
			
		||||
                            let table = lua.create_table()?;
 | 
			
		||||
                            table.set("name", tag_name.as_str())?;
 | 
			
		||||
                            Value::Table(table)
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(VariableData::Function { .. }) => {
 | 
			
		||||
                            todo!("allow function variable type")
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(_) => todo!("allow other variable types"),
 | 
			
		||||
                        None => todo!("throw correct error"),
 | 
			
		||||
                    };
 | 
			
		||||
                    globals.set(name, value)?;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            globals.set("shulkerscript", shulkerscript_globals)?;
 | 
			
		||||
 | 
			
		||||
            Ok(())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ use crate::{
 | 
			
		|||
use super::{
 | 
			
		||||
    error::{MismatchedTypes, TranspileError, TranspileResult, UnknownIdentifier},
 | 
			
		||||
    expression::{ComptimeValue, ExpectedType, ExtendedCondition, StorageType},
 | 
			
		||||
    variables::{Scope, TranspileAssignmentTarget, VariableData},
 | 
			
		||||
    variables::{Scope, VariableData},
 | 
			
		||||
    FunctionData, TranspileAnnotationValue,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -514,7 +514,6 @@ impl Transpiler {
 | 
			
		|||
                        Expression::Primary(
 | 
			
		||||
                            Primary::Parenthesized(_)
 | 
			
		||||
                            | Primary::Prefix(_)
 | 
			
		||||
                            | Primary::Indexed(_)
 | 
			
		||||
                            | Primary::FunctionCall(_),
 | 
			
		||||
                        )
 | 
			
		||||
                        | Expression::Binary(_) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -746,7 +745,7 @@ impl Transpiler {
 | 
			
		|||
                    self.transpile_variable_declaration(decl, program_identifier, scope, handler)
 | 
			
		||||
                }
 | 
			
		||||
                SemicolonStatement::Assignment(assignment) => self.transpile_assignment(
 | 
			
		||||
                    TranspileAssignmentTarget::from(assignment.destination()),
 | 
			
		||||
                    assignment.identifier(),
 | 
			
		||||
                    assignment.expression(),
 | 
			
		||||
                    scope,
 | 
			
		||||
                    handler,
 | 
			
		||||
| 
						 | 
				
			
			@ -771,8 +770,7 @@ impl Transpiler {
 | 
			
		|||
                Primary::Integer(_)
 | 
			
		||||
                | Primary::Boolean(_)
 | 
			
		||||
                | Primary::Prefix(_)
 | 
			
		||||
                | Primary::Identifier(_)
 | 
			
		||||
                | Primary::Indexed(_),
 | 
			
		||||
                | Primary::Identifier(_),
 | 
			
		||||
            ) => {
 | 
			
		||||
                let error =
 | 
			
		||||
                    TranspileError::UnexpectedExpression(UnexpectedExpression(expression.clone()));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,20 +15,17 @@ use shulkerbox::prelude::{Command, Condition, Execute};
 | 
			
		|||
use enum_as_inner::EnumAsInner;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    base::{self, source_file::SourceElement, Handler},
 | 
			
		||||
    base::{self, source_file::SourceElement as _, Handler},
 | 
			
		||||
    lexical::token::{Identifier, KeywordKind},
 | 
			
		||||
    syntax::syntax_tree::{
 | 
			
		||||
        expression::{Expression, Primary},
 | 
			
		||||
        statement::{
 | 
			
		||||
            AssignmentDestination, ScoreVariableDeclaration, SingleVariableDeclaration,
 | 
			
		||||
            VariableDeclaration,
 | 
			
		||||
        },
 | 
			
		||||
        statement::{SingleVariableDeclaration, VariableDeclaration},
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
    error::{AssignmentError, IllegalAnnotationContent, MismatchedTypes},
 | 
			
		||||
    expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
 | 
			
		||||
    error::{AssignmentError, IllegalAnnotationContent},
 | 
			
		||||
    expression::{ComptimeValue, DataLocation},
 | 
			
		||||
    FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -92,25 +89,6 @@ pub enum VariableData {
 | 
			
		|||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, EnumAsInner)]
 | 
			
		||||
pub enum TranspileAssignmentTarget<'a> {
 | 
			
		||||
    Identifier(&'a Identifier),
 | 
			
		||||
    Indexed(&'a Identifier, &'a Expression),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> From<&'a AssignmentDestination> for TranspileAssignmentTarget<'a> {
 | 
			
		||||
    fn from(destination: &'a AssignmentDestination) -> Self {
 | 
			
		||||
        match destination {
 | 
			
		||||
            AssignmentDestination::Identifier(ident) => {
 | 
			
		||||
                TranspileAssignmentTarget::Identifier(ident)
 | 
			
		||||
            }
 | 
			
		||||
            AssignmentDestination::Indexed(ident, _, expr, _) => {
 | 
			
		||||
                TranspileAssignmentTarget::Indexed(ident, expr)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A scope that stores variables.
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct Scope<'a> {
 | 
			
		||||
| 
						 | 
				
			
			@ -232,14 +210,8 @@ impl Transpiler {
 | 
			
		|||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        match declaration {
 | 
			
		||||
            VariableDeclaration::Single(declaration) => self.transpile_single_variable_declaration(
 | 
			
		||||
                declaration,
 | 
			
		||||
                program_identifier,
 | 
			
		||||
                scope,
 | 
			
		||||
                handler,
 | 
			
		||||
            ),
 | 
			
		||||
            VariableDeclaration::Score(declaration) => self.transpile_score_variable_declaration(
 | 
			
		||||
                declaration,
 | 
			
		||||
            VariableDeclaration::Single(single) => self.transpile_single_variable_declaration(
 | 
			
		||||
                single,
 | 
			
		||||
                program_identifier,
 | 
			
		||||
                scope,
 | 
			
		||||
                handler,
 | 
			
		||||
| 
						 | 
				
			
			@ -250,72 +222,18 @@ impl Transpiler {
 | 
			
		|||
 | 
			
		||||
    fn transpile_single_variable_declaration(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        declaration: &SingleVariableDeclaration,
 | 
			
		||||
        single: &SingleVariableDeclaration,
 | 
			
		||||
        program_identifier: &str,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        let variable_type = declaration.variable_type().keyword;
 | 
			
		||||
 | 
			
		||||
        let (name, target) = self.get_data_location_identifier_pair(
 | 
			
		||||
            declaration,
 | 
			
		||||
            program_identifier,
 | 
			
		||||
            scope,
 | 
			
		||||
            handler,
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        match variable_type {
 | 
			
		||||
            KeywordKind::Int => {
 | 
			
		||||
                if !self.datapack.scoreboards().contains_key(&name) {
 | 
			
		||||
                    self.datapack
 | 
			
		||||
                        .register_scoreboard(&name, None::<&str>, None::<&str>);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                scope.set_variable(
 | 
			
		||||
                    declaration.identifier().span.str(),
 | 
			
		||||
                    VariableData::ScoreboardValue {
 | 
			
		||||
                        objective: name.clone(),
 | 
			
		||||
                        target,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            KeywordKind::Bool => {
 | 
			
		||||
                scope.set_variable(
 | 
			
		||||
                    declaration.identifier().span.str(),
 | 
			
		||||
                    VariableData::BooleanStorage {
 | 
			
		||||
                        storage_name: name,
 | 
			
		||||
                        path: target,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            _ => unreachable!("no other variable types"),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        declaration.assignment().as_ref().map_or_else(
 | 
			
		||||
            || Ok(Vec::new()),
 | 
			
		||||
            |assignment| {
 | 
			
		||||
                self.transpile_assignment(
 | 
			
		||||
                    TranspileAssignmentTarget::Identifier(declaration.identifier()),
 | 
			
		||||
                    assignment.expression(),
 | 
			
		||||
                    scope,
 | 
			
		||||
                    handler,
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn transpile_score_variable_declaration(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        declaration: &ScoreVariableDeclaration,
 | 
			
		||||
        program_identifier: &str,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        let mut deobfuscate_annotations = declaration
 | 
			
		||||
        let mut deobfuscate_annotations = single
 | 
			
		||||
            .annotations()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|a| a.has_identifier("deobfuscate"));
 | 
			
		||||
 | 
			
		||||
        let variable_type = single.variable_type().keyword;
 | 
			
		||||
 | 
			
		||||
        let deobfuscate_annotation = deobfuscate_annotations.next();
 | 
			
		||||
 | 
			
		||||
        if let Some(duplicate) = deobfuscate_annotations.next() {
 | 
			
		||||
| 
						 | 
				
			
			@ -326,73 +244,69 @@ impl Transpiler {
 | 
			
		|||
            handler.receive(error.clone());
 | 
			
		||||
            return Err(error);
 | 
			
		||||
        }
 | 
			
		||||
        let name =
 | 
			
		||||
            self.get_data_location_identifier(declaration, program_identifier, scope, handler)?;
 | 
			
		||||
        let (name, target) =
 | 
			
		||||
            self.get_single_data_location_identifiers(single, program_identifier, scope, handler)?;
 | 
			
		||||
 | 
			
		||||
        let criteria = declaration
 | 
			
		||||
            .criteria()
 | 
			
		||||
            .as_ref()
 | 
			
		||||
            .map(|(_, c, _)| c.str_content());
 | 
			
		||||
        match variable_type {
 | 
			
		||||
            KeywordKind::Int => {
 | 
			
		||||
                if !self.datapack.scoreboards().contains_key(&name) {
 | 
			
		||||
                    self.datapack
 | 
			
		||||
                        .register_scoreboard(&name, None::<&str>, None::<&str>);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        if !self.datapack.scoreboards().contains_key(&name) {
 | 
			
		||||
            self.datapack
 | 
			
		||||
                .register_scoreboard(&name, criteria, None::<&str>);
 | 
			
		||||
                scope.set_variable(
 | 
			
		||||
                    single.identifier().span.str(),
 | 
			
		||||
                    VariableData::ScoreboardValue {
 | 
			
		||||
                        objective: name.clone(),
 | 
			
		||||
                        target,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            KeywordKind::Bool => {
 | 
			
		||||
                scope.set_variable(
 | 
			
		||||
                    single.identifier().span.str(),
 | 
			
		||||
                    VariableData::BooleanStorage {
 | 
			
		||||
                        storage_name: name,
 | 
			
		||||
                        path: target,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            _ => unreachable!("no other variable types"),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        scope.set_variable(
 | 
			
		||||
            declaration.identifier().span.str(),
 | 
			
		||||
            VariableData::Scoreboard {
 | 
			
		||||
                objective: name.clone(),
 | 
			
		||||
        single.assignment().as_ref().map_or_else(
 | 
			
		||||
            || Ok(Vec::new()),
 | 
			
		||||
            |assignment| {
 | 
			
		||||
                self.transpile_assignment(
 | 
			
		||||
                    single.identifier(),
 | 
			
		||||
                    assignment.expression(),
 | 
			
		||||
                    scope,
 | 
			
		||||
                    handler,
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // TODO: implement assignment when map literal is implemented
 | 
			
		||||
        Ok(Vec::new())
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn transpile_assignment(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        destination: TranspileAssignmentTarget,
 | 
			
		||||
        identifier: &Identifier,
 | 
			
		||||
        expression: &crate::syntax::syntax_tree::expression::Expression,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        let (identifier, indexing_value) = match destination {
 | 
			
		||||
            TranspileAssignmentTarget::Identifier(ident) => (ident, None),
 | 
			
		||||
            TranspileAssignmentTarget::Indexed(ident, expression) => {
 | 
			
		||||
                (ident, expression.comptime_eval(scope, handler))
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        if let Some(target) = scope.get_variable(identifier.span.str()) {
 | 
			
		||||
            let data_location = match target.as_ref() {
 | 
			
		||||
                VariableData::BooleanStorage { storage_name, path } => {
 | 
			
		||||
                    // TODO: make sure that no indexing is done
 | 
			
		||||
                    Ok(DataLocation::Storage {
 | 
			
		||||
                        storage_name: storage_name.to_owned(),
 | 
			
		||||
                        path: path.to_owned(),
 | 
			
		||||
                        r#type: super::expression::StorageType::Boolean,
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
                VariableData::BooleanStorage { storage_name, path } => Ok(DataLocation::Storage {
 | 
			
		||||
                    storage_name: storage_name.to_owned(),
 | 
			
		||||
                    path: path.to_owned(),
 | 
			
		||||
                    r#type: super::expression::StorageType::Boolean,
 | 
			
		||||
                }),
 | 
			
		||||
                VariableData::ScoreboardValue { objective, target } => {
 | 
			
		||||
                    // TODO: make sure that no indexing is done
 | 
			
		||||
                    Ok(DataLocation::ScoreboardValue {
 | 
			
		||||
                        objective: objective.to_owned(),
 | 
			
		||||
                        target: target.to_owned(),
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
                VariableData::Scoreboard { objective } => match indexing_value {
 | 
			
		||||
                    Some(ComptimeValue::String(s)) => Ok(DataLocation::ScoreboardValue {
 | 
			
		||||
                        objective: objective.clone(),
 | 
			
		||||
                        target: s,
 | 
			
		||||
                    }),
 | 
			
		||||
                    Some(ComptimeValue::MacroString(s)) => {
 | 
			
		||||
                        todo!("indexing scoreboard with macro string: {s}")
 | 
			
		||||
                    }
 | 
			
		||||
                    Some(_) => todo!("invalid indexing value"),
 | 
			
		||||
                    None => {
 | 
			
		||||
                        todo!("cannot assign to scoreboard without indexing")
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                VariableData::Function { .. } | VariableData::MacroParameter { .. } => {
 | 
			
		||||
                    let err = TranspileError::AssignmentError(AssignmentError {
 | 
			
		||||
                        identifier: identifier.span(),
 | 
			
		||||
| 
						 | 
				
			
			@ -421,88 +335,9 @@ impl Transpiler {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_data_location_identifier(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        declaration: &ScoreVariableDeclaration,
 | 
			
		||||
        program_identifier: &str,
 | 
			
		||||
        scope: &Arc<Scope>,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<String> {
 | 
			
		||||
        let mut deobfuscate_annotations = declaration
 | 
			
		||||
            .annotations()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|a| a.has_identifier("deobfuscate"));
 | 
			
		||||
 | 
			
		||||
        let deobfuscate_annotation = deobfuscate_annotations.next();
 | 
			
		||||
 | 
			
		||||
        if let Some(duplicate) = deobfuscate_annotations.next() {
 | 
			
		||||
            let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
 | 
			
		||||
                annotation: duplicate.span(),
 | 
			
		||||
                message: "Multiple deobfuscate annotations are not allowed.".to_string(),
 | 
			
		||||
            });
 | 
			
		||||
            handler.receive(error.clone());
 | 
			
		||||
            return Err(error);
 | 
			
		||||
        }
 | 
			
		||||
        if let Some(deobfuscate_annotation) = deobfuscate_annotation {
 | 
			
		||||
            let deobfuscate_annotation_value =
 | 
			
		||||
                TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone());
 | 
			
		||||
 | 
			
		||||
            match deobfuscate_annotation_value {
 | 
			
		||||
                TranspileAnnotationValue::Expression(expr) => {
 | 
			
		||||
                    if let Some(name_eval) = expr
 | 
			
		||||
                        .comptime_eval(scope, handler)
 | 
			
		||||
                        .and_then(|val| val.to_string_no_macro())
 | 
			
		||||
                    {
 | 
			
		||||
                        if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
 | 
			
		||||
                            let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
 | 
			
		||||
                                                    annotation: deobfuscate_annotation.span(),
 | 
			
		||||
                                                    message: "Deobfuscate annotation must be a valid scoreboard objective name.".to_string()
 | 
			
		||||
                                                });
 | 
			
		||||
                            handler.receive(error.clone());
 | 
			
		||||
                            return Err(error);
 | 
			
		||||
                        }
 | 
			
		||||
                        Ok(name_eval)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
 | 
			
		||||
                                                annotation: deobfuscate_annotation.span(),
 | 
			
		||||
                                                message: "Deobfuscate annotation could not have been evaluated at compile time.".to_string()
 | 
			
		||||
                                            });
 | 
			
		||||
                        handler.receive(error.clone());
 | 
			
		||||
                        Err(error)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                TranspileAnnotationValue::None => {
 | 
			
		||||
                    Ok(declaration.identifier().span.str().to_string())
 | 
			
		||||
                }
 | 
			
		||||
                TranspileAnnotationValue::Map(_) => {
 | 
			
		||||
                    let error =
 | 
			
		||||
                        TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
 | 
			
		||||
                            annotation: deobfuscate_annotation.span(),
 | 
			
		||||
                            message: "Deobfuscate annotation must have no value or must be string."
 | 
			
		||||
                                .to_string(),
 | 
			
		||||
                        });
 | 
			
		||||
                    handler.receive(error.clone());
 | 
			
		||||
                    Err(error)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            let hashed = md5::hash(program_identifier).to_hex_lowercase();
 | 
			
		||||
            let name = "shu_values_".to_string() + &hashed;
 | 
			
		||||
            let identifier_name = declaration.identifier().span.str();
 | 
			
		||||
            let scope_ident = self.temp_counter;
 | 
			
		||||
            self.temp_counter = self.temp_counter.wrapping_add(1);
 | 
			
		||||
            let mut target = md5::hash(format!(
 | 
			
		||||
                "{scope_ident}\0{identifier_name}\0{shadowed}",
 | 
			
		||||
                shadowed = scope.get_variable_shadow_count(identifier_name)
 | 
			
		||||
            ))
 | 
			
		||||
            .to_hex_lowercase();
 | 
			
		||||
 | 
			
		||||
            Ok(name)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[expect(clippy::too_many_lines)]
 | 
			
		||||
    fn get_data_location_identifier_pair(
 | 
			
		||||
    #[cfg(feature = "shulkerbox")]
 | 
			
		||||
    fn get_single_data_location_identifiers(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        single: &SingleVariableDeclaration,
 | 
			
		||||
        program_identifier: &str,
 | 
			
		||||
| 
						 | 
				
			
			@ -550,10 +385,10 @@ impl Transpiler {
 | 
			
		|||
                        if let (Some(name_eval), Some(target_eval)) = (
 | 
			
		||||
                            objective
 | 
			
		||||
                                .comptime_eval(scope, handler)
 | 
			
		||||
                                .and_then(|val| val.to_string_no_macro()),
 | 
			
		||||
                                .map(|val| val.to_string()),
 | 
			
		||||
                            target
 | 
			
		||||
                                .comptime_eval(scope, handler)
 | 
			
		||||
                                .and_then(|val| val.to_string_no_macro()),
 | 
			
		||||
                                .map(|val| val.to_string()),
 | 
			
		||||
                        ) {
 | 
			
		||||
                            // TODO: change invalid criteria if boolean
 | 
			
		||||
                            if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
 | 
			
		||||
| 
						 | 
				
			
			@ -633,139 +468,6 @@ impl Transpiler {
 | 
			
		|||
            Ok((name, target))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Move data from location `from` to location `to`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Errors
 | 
			
		||||
    /// - if the data type does not match
 | 
			
		||||
    #[expect(clippy::too_many_lines)]
 | 
			
		||||
    pub fn move_data(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        from: &DataLocation,
 | 
			
		||||
        to: &DataLocation,
 | 
			
		||||
        expression: &impl SourceElement,
 | 
			
		||||
        handler: &impl Handler<base::Error>,
 | 
			
		||||
    ) -> TranspileResult<Vec<Command>> {
 | 
			
		||||
        match from {
 | 
			
		||||
            DataLocation::Storage {
 | 
			
		||||
                storage_name,
 | 
			
		||||
                path,
 | 
			
		||||
                r#type,
 | 
			
		||||
            } => match r#type {
 | 
			
		||||
                StorageType::Boolean
 | 
			
		||||
                | StorageType::Byte
 | 
			
		||||
                | StorageType::Int
 | 
			
		||||
                | StorageType::Long
 | 
			
		||||
                | StorageType::Double => match to {
 | 
			
		||||
                    DataLocation::ScoreboardValue { objective, target } => {
 | 
			
		||||
                        let cmd = Command::Execute(Execute::Store(
 | 
			
		||||
                            format!("store result score {target} {objective}").into(),
 | 
			
		||||
                            Box::new(Execute::Run(Box::new(Command::Raw(format!(
 | 
			
		||||
                                "data get storage {storage_name} {path}"
 | 
			
		||||
                            ))))),
 | 
			
		||||
                        ));
 | 
			
		||||
                        Ok(vec![cmd])
 | 
			
		||||
                    }
 | 
			
		||||
                    DataLocation::Tag { tag_name, entity } => {
 | 
			
		||||
                        let cmd = Command::Execute(Execute::If(
 | 
			
		||||
                            Condition::Atom(
 | 
			
		||||
                                format!("data storage {storage_name} {{{path}: 1b}}").into(),
 | 
			
		||||
                            ),
 | 
			
		||||
                            Box::new(Execute::Run(Box::new(Command::Raw(format!(
 | 
			
		||||
                                "tag {entity} add {tag_name}"
 | 
			
		||||
                            ))))),
 | 
			
		||||
                            Some(Box::new(Execute::Run(Box::new(Command::Raw(format!(
 | 
			
		||||
                                "tag {entity} remove {tag_name}"
 | 
			
		||||
                            )))))),
 | 
			
		||||
                        ));
 | 
			
		||||
 | 
			
		||||
                        Ok(vec![cmd])
 | 
			
		||||
                    }
 | 
			
		||||
                    DataLocation::Storage {
 | 
			
		||||
                        storage_name: target_storage_name,
 | 
			
		||||
                        path: target_path,
 | 
			
		||||
                        r#type,
 | 
			
		||||
                    } => {
 | 
			
		||||
                        if matches!(r#type, StorageType::Boolean) {
 | 
			
		||||
                            let cmd = Command::Raw(format!(
 | 
			
		||||
                                        "data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}"
 | 
			
		||||
                                    ));
 | 
			
		||||
                            Ok(vec![cmd])
 | 
			
		||||
                        } else {
 | 
			
		||||
                            let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                                expression: expression.span(),
 | 
			
		||||
                                expected_type: to.value_type().into(),
 | 
			
		||||
                            });
 | 
			
		||||
                            handler.receive(err.clone());
 | 
			
		||||
                            Err(err)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            DataLocation::ScoreboardValue {
 | 
			
		||||
                objective,
 | 
			
		||||
                target: score_target,
 | 
			
		||||
            } => match to {
 | 
			
		||||
                DataLocation::ScoreboardValue {
 | 
			
		||||
                    objective: target_objective,
 | 
			
		||||
                    target: target_target,
 | 
			
		||||
                } => {
 | 
			
		||||
                    let cmd = Command::Raw(format!(
 | 
			
		||||
                        "scoreboard players operation {target_target} {target_objective} = {score_target} {objective}"
 | 
			
		||||
                    ));
 | 
			
		||||
                    Ok(vec![cmd])
 | 
			
		||||
                }
 | 
			
		||||
                DataLocation::Storage {
 | 
			
		||||
                    storage_name,
 | 
			
		||||
                    path,
 | 
			
		||||
                    r#type,
 | 
			
		||||
                } => {
 | 
			
		||||
                    if matches!(
 | 
			
		||||
                        r#type,
 | 
			
		||||
                        StorageType::Byte
 | 
			
		||||
                            | StorageType::Double
 | 
			
		||||
                            | StorageType::Int
 | 
			
		||||
                            | StorageType::Long
 | 
			
		||||
                    ) {
 | 
			
		||||
                        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: expression.span(),
 | 
			
		||||
                            expected_type: to.value_type().into(),
 | 
			
		||||
                        });
 | 
			
		||||
                        handler.receive(err.clone());
 | 
			
		||||
                        Err(err)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                DataLocation::Tag { .. } => {
 | 
			
		||||
                    let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                        expected_type: ExpectedType::Boolean,
 | 
			
		||||
                        expression: expression.span(),
 | 
			
		||||
                    });
 | 
			
		||||
                    handler.receive(err.clone());
 | 
			
		||||
                    Err(err)
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            DataLocation::Tag { .. } => {
 | 
			
		||||
                let err = TranspileError::MismatchedTypes(MismatchedTypes {
 | 
			
		||||
                    expected_type: to.value_type().into(),
 | 
			
		||||
                    expression: expression.span(),
 | 
			
		||||
                });
 | 
			
		||||
                handler.receive(err.clone());
 | 
			
		||||
                Err(err)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue