diff --git a/src/transpile/expression.rs b/src/transpile/expression.rs index 15c920c..95122aa 100644 --- a/src/transpile/expression.rs +++ b/src/transpile/expression.rs @@ -34,6 +34,7 @@ use crate::{ }, }; +// TODO: fix this leading to compile errors without 'shulkerbox' feature /// Compile-time evaluated value #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq)] @@ -419,25 +420,27 @@ impl Binary { let right = self.right_operand().comptime_eval(scope, handler)?; match (left, right) { - (ComptimeValue::Boolean(true), _) | (_, ComptimeValue::Boolean(true)) if matches!(self.operator(), BinaryOperator::LogicalOr(..)) - // TODO: re-enable if can_yield_type works properly - /*&& self - .left_operand() - .can_yield_type(ValueType::Boolean, scope) - && self - .right_operand() - .can_yield_type(ValueType::Boolean, scope)*/ => { - Some(ComptimeValue::Boolean(true)) + (ComptimeValue::Boolean(true), _) | (_, ComptimeValue::Boolean(true)) + if matches!(self.operator(), BinaryOperator::LogicalOr(..)) + && self + .left_operand() + .can_yield_type(ValueType::Boolean, scope) + && self + .right_operand() + .can_yield_type(ValueType::Boolean, scope) => + { + Some(ComptimeValue::Boolean(true)) } - (ComptimeValue::Boolean(false), _) | (_, ComptimeValue::Boolean(false)) if matches!(self.operator(), BinaryOperator::LogicalAnd(..)) - // TODO: re-enable if can_yield_type works properly - /*&& self - .left_operand() - .can_yield_type(ValueType::Boolean, scope) - && self - .right_operand() - .can_yield_type(ValueType::Boolean, scope)*/ => { - Some(ComptimeValue::Boolean(false)) + (ComptimeValue::Boolean(false), _) | (_, ComptimeValue::Boolean(false)) + if matches!(self.operator(), BinaryOperator::LogicalAnd(..)) + && self + .left_operand() + .can_yield_type(ValueType::Boolean, scope) + && self + .right_operand() + .can_yield_type(ValueType::Boolean, scope) => + { + Some(ComptimeValue::Boolean(false)) } (ComptimeValue::Boolean(left), ComptimeValue::Boolean(right)) => { match self.operator() { @@ -660,7 +663,7 @@ impl Transpiler { target: score_target, } => { let mut expr_cmds = self.transpile_primary_expression( - dbg!(prefix).operand(), + prefix.operand(), target, scope, handler, @@ -711,18 +714,15 @@ impl Transpiler { _ => { let err = TranspileError::MismatchedTypes(MismatchedTypes { expression: prefix.span(), - expected_type: ExpectedType::Integer, + expected_type: target.value_type().into(), }); handler.receive(err.clone()); Err(err) } }, PrefixOperator::LogicalNot(_) => { - let (mut cmds, cond) = self - .transpile_primary_expression_as_condition(primary, scope, handler) - .inspect_err(|err| { - dbg!(err); - })?; + let (mut cmds, cond) = + self.transpile_primary_expression_as_condition(primary, scope, handler)?; let store_cmds = self.store_condition_success(cond, target, primary, handler)?; @@ -864,7 +864,7 @@ impl Transpiler { handler: &impl Handler, ) -> TranspileResult> { if let Some(value) = binary.comptime_eval(scope, handler) { - self.transpile_comptime_value(&value, binary, target, scope, handler) + self.store_comptime_value(&value, target, binary, handler) } else { match binary.operator() { BinaryOperator::Add(_) @@ -977,12 +977,12 @@ impl Transpiler { format!("data storage {storage_name} {{{path}: 1b}}").into(), )), )), - VariableData::FunctionArgument { .. } => Ok(( + VariableData::MacroParameter { macro_name, .. } => Ok(( Vec::new(), ExtendedCondition::Runtime(Condition::Atom( shulkerbox::util::MacroString::MacroString(vec![ shulkerbox::util::MacroStringPart::MacroUsage( - ident.span.str().to_string(), + macro_name.clone(), ), ]), )), @@ -1306,98 +1306,6 @@ impl Transpiler { } } - #[expect(clippy::unused_self)] - fn transpile_comptime_value( - &self, - value: &ComptimeValue, - original: &impl SourceElement, - target: &DataLocation, - _scope: &Arc, - handler: &impl Handler, - ) -> TranspileResult> { - match value { - ComptimeValue::Integer(value) => match target { - DataLocation::ScoreboardValue { objective, target } => Ok(vec![Command::Raw( - format!("scoreboard players set {target} {objective} {value}"), - )]), - DataLocation::Tag { .. } => { - let err = TranspileError::MismatchedTypes(MismatchedTypes { - expected_type: ExpectedType::Boolean, - expression: original.span(), - }); - handler.receive(err.clone()); - Err(err) - } - DataLocation::Storage { - storage_name, - path, - r#type, - } => { - if matches!( - r#type, - StorageType::Byte - | StorageType::Double - | StorageType::Int - | StorageType::Long - ) { - Ok(vec![Command::Raw(format!( - "data modify storage {storage_name} {path} set value {value}{suffix}", - suffix = r#type.suffix(), - ))]) - } else { - let err = TranspileError::MismatchedTypes(MismatchedTypes { - expression: original.span(), - expected_type: target.value_type().into(), - }); - handler.receive(err.clone()); - Err(err) - } - } - }, - &ComptimeValue::Boolean(value) => match target { - DataLocation::ScoreboardValue { objective, target } => { - Ok(vec![Command::Raw(format!( - "scoreboard players set {target} {objective} {value}", - value = u8::from(value) - ))]) - } - DataLocation::Tag { tag_name, entity } => Ok(vec![Command::Raw(format!( - "tag {entity} {op} {tag_name}", - op = if value { "add" } else { "remove" } - ))]), - DataLocation::Storage { - storage_name, - path, - r#type, - } => { - if matches!(r#type, StorageType::Boolean) { - Ok(vec![Command::Raw(format!( - "data modify storage {storage_name} {path} set value {value}{suffix}", - value = u8::from(value), - suffix = r#type.suffix(), - ))]) - } else { - let err = TranspileError::MismatchedTypes(MismatchedTypes { - expression: original.span(), - expected_type: target.value_type().into(), - }); - handler.receive(err.clone()); - Err(err) - } - } - }, - ComptimeValue::String(_) | ComptimeValue::MacroString(_) => { - let err = TranspileError::MismatchedTypes(MismatchedTypes { - expected_type: target.value_type().into(), - expression: original.span(), - }); - handler.receive(err.clone()); - Err(err) - } - } - } - - #[expect(clippy::needless_pass_by_ref_mut)] fn store_comptime_value( &mut self, value: &ComptimeValue, @@ -1406,9 +1314,9 @@ impl Transpiler { handler: &impl Handler, ) -> TranspileResult> { match value { - ComptimeValue::Integer(int) => match target { + ComptimeValue::Integer(value) => match target { DataLocation::ScoreboardValue { objective, target } => Ok(vec![Command::Raw( - format!("scoreboard players set {target} {objective} {int}"), + format!("scoreboard players set {target} {objective} {value}"), )]), DataLocation::Tag { .. } => { let err = TranspileError::MismatchedTypes(MismatchedTypes { @@ -1431,58 +1339,79 @@ impl Transpiler { | StorageType::Long ) { Ok(vec![Command::Raw(format!( - "data modify storage {storage} {path} set value {value}{suffix}", - storage = storage_name, - path = path, - value = int, - suffix = r#type.suffix() + "data modify storage {storage_name} {path} set value {value}{suffix}", + suffix = r#type.suffix(), ))]) } else { let err = TranspileError::MismatchedTypes(MismatchedTypes { expression: source.span(), - expected_type: ExpectedType::Integer, + expected_type: target.value_type().into(), }); handler.receive(err.clone()); Err(err) } } }, - &ComptimeValue::Boolean(boolean) => match target { - DataLocation::Tag { tag_name, entity } => { - let cmd = format!( - "tag {target} {op} {tag}", - target = entity, - op = if boolean { "add" } else { "remove" }, - tag = tag_name - ); - Ok(vec![shulkerbox::prelude::Command::Raw(cmd)]) - } + &ComptimeValue::Boolean(value) => match target { + DataLocation::Tag { tag_name, entity } => Ok(vec![Command::Raw(format!( + "tag {entity} {op} {tag_name}", + op = if value { "add" } else { "remove" } + ))]), DataLocation::Storage { storage_name, path, r#type, } => { - let cmd = format!( - "data modify storage {storage} {path} set value {value}{suffix}", - storage = storage_name, - path = path, - value = if boolean { "1" } else { "0" }, - suffix = r#type.suffix() - ); - Ok(vec![shulkerbox::prelude::Command::Raw(cmd)]) + if matches!(r#type, StorageType::Boolean) { + Ok(vec![Command::Raw(format!( + "data modify storage {storage_name} {path} set value {value}{suffix}", + value = u8::from(value), + suffix = r#type.suffix(), + ))]) + } else { + let err = TranspileError::MismatchedTypes(MismatchedTypes { + expression: source.span(), + expected_type: target.value_type().into(), + }); + handler.receive(err.clone()); + Err(err) + } } DataLocation::ScoreboardValue { objective, target } => { - let cmd = format!( + Ok(vec![Command::Raw(format!( "scoreboard players set {target} {objective} {value}", - target = target, - objective = objective, - value = if boolean { "1" } else { "0" } - ); - Ok(vec![shulkerbox::prelude::Command::Raw(cmd)]) + value = u8::from(value) + ))]) } }, - ComptimeValue::String(_) | ComptimeValue::MacroString(_) => { - todo!("store string comptime value") + ComptimeValue::String(value) => self.store_comptime_value( + &ComptimeValue::MacroString(value.clone().into()), + target, + source, + handler, + ), + ComptimeValue::MacroString(value) => { + match target { + DataLocation::Storage { + r#type: StorageType::Boolean, + .. + } + | DataLocation::Tag { .. } => self.store_condition_success( + ExtendedCondition::Runtime(Condition::Atom(value.clone())), + target, + source, + handler, + ), + // DataLocation::Storage { storage_name, path, r#type: StorageType::String } => todo!("implement storage string") + _ => { + let err = TranspileError::MismatchedTypes(MismatchedTypes { + expected_type: target.value_type().into(), + expression: source.span(), + }); + handler.receive(err.clone()); + Err(err) + } + } } } } diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index ed76f55..4fdbac8 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -268,7 +268,6 @@ impl Transpiler { /// Gets the function at the given path, or transpiles it if it hasn't been transpiled yet. /// Returns the location of the function or None if the function does not exist. - #[allow(clippy::significant_drop_tightening)] #[tracing::instrument(level = "trace", skip(self, handler))] pub(super) fn get_or_transpile_function( &mut self, @@ -324,7 +323,13 @@ impl Transpiler { let function_scope = Scope::with_parent(scope); for (i, param) in function_data.parameters.iter().enumerate() { - function_scope.set_variable(param, VariableData::FunctionArgument { index: i }); + function_scope.set_variable( + param, + VariableData::MacroParameter { + index: i, + macro_name: crate::util::identifier_to_macro(param).to_string(), + }, + ); } let statements = function_data.statements.clone(); @@ -468,12 +473,9 @@ impl Transpiler { err })?; match var.as_ref() { - VariableData::FunctionArgument { .. } => { + VariableData::MacroParameter { macro_name, .. } => { Ok(Parameter::Static(MacroString::MacroString(vec![ - MacroStringPart::MacroUsage( - crate::util::identifier_to_macro(ident.span.str()) - .to_string(), - ), + MacroStringPart::MacroUsage(macro_name.clone()), ]))) } @@ -486,14 +488,22 @@ impl Transpiler { &super::expression::DataLocation::Storage { storage_name: temp_storage.clone(), path: temp_path[0].clone(), - r#type: StorageType::Int, + r#type: match var.as_ref() { + VariableData::BooleanStorage { .. } => { + StorageType::Boolean + } + VariableData::ScoreboardValue { .. } => { + StorageType::Int + } + _ => unreachable!("checked in parent match"), + }, }, scope, handler, )?; Ok(Parameter::Dynamic { - prepare_cmds: dbg!(prepare_cmds), + prepare_cmds, storage_name: temp_storage, path: std::mem::take(&mut temp_path[0]), }) @@ -542,30 +552,70 @@ impl Transpiler { 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 (mut setup_cmds, move_cmds, static_params) = parameters.clone().into_iter().zip(compiled_args).fold( + (Vec::new(), Vec::new(), BTreeMap::new()), + |(mut acc_setup, mut acc_move, mut statics), (arg_name, data)| { let arg_name = crate::util::identifier_to_macro(&arg_name); match data { Parameter::Static(s) => { - // 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)) + match s { + MacroString::String(value) => statics.insert(arg_name.to_string(), MacroString::String(crate::util::escape_str(&value).to_string())), + MacroString::MacroString(parts) => { + let parts = parts.into_iter().map(|part| { + match part { + MacroStringPart::String(s) => MacroStringPart::String(crate::util::escape_str(&s).to_string()), + MacroStringPart::MacroUsage(m) => MacroStringPart::MacroUsage(m), + } + }).collect(); + statics.insert(arg_name.to_string(), MacroString::MacroString(parts)) } }; - 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)}, + (acc_setup, acc_move, statics)}, ); + let statics_len = static_params.len(); + let joined_statics = + super::util::join_macro_strings(static_params.into_iter().enumerate().map( + |(i, (k, v))| match v { + MacroString::String(s) => { + let mut s = format!(r#"{k}:"{s}""#); + if i < statics_len - 1 { + s.push(','); + } + MacroString::String(s) + } + MacroString::MacroString(mut parts) => { + parts.insert(0, MacroStringPart::String(format!(r#"{k}:""#))); + let mut ending = '"'.to_string(); + if i < statics_len - 1 { + ending.push(','); + } + parts.push(MacroStringPart::String(ending)); + MacroString::MacroString(parts) + } + }, + )); + let statics_cmd = match joined_statics { + MacroString::String(s) => Command::Raw(format!( + r#"data merge storage shulkerscript:function_arguments {{{s}}}"# + )), + MacroString::MacroString(_) => { + Command::UsesMacro(super::util::join_macro_strings([ + MacroString::String( + "data merge storage shulkerscript:function_arguments {" + .to_string(), + ), + joined_statics, + MacroString::String("}".to_string()), + ])) + } + }; + setup_cmds.push(statics_cmd); setup_cmds.extend(move_cmds); Ok(( @@ -756,7 +806,14 @@ impl Transpiler { 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"), + _ => { + let err = TranspileError::MismatchedTypes(MismatchedTypes { + expression: bin.span(), + expected_type: ExpectedType::String, + }); + handler.receive(err.clone()); + Err(err) + } }, } } @@ -781,52 +838,28 @@ impl Transpiler { 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) + let arguments_iter = 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()) } - 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) - } - }, - }); + macro_usage @ MacroStringPart::MacroUsage(_) => macro_usage, + })) + .chain(std::iter::once(MacroStringPart::String('"'.to_string()))) + .collect(), + ), + }); + let arguments = super::util::join_macro_strings(arguments_iter); let cmd = match arguments { MacroString::String(arguments) => { diff --git a/src/transpile/util.rs b/src/transpile/util.rs index 15c78f0..d83711c 100644 --- a/src/transpile/util.rs +++ b/src/transpile/util.rs @@ -1,5 +1,7 @@ //! Utility methods for transpiling +use shulkerbox::util::{MacroString, MacroStringPart}; + fn normalize_program_identifier(identifier: S) -> String where S: AsRef, @@ -36,3 +38,36 @@ where normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref()) } } + +/// Join multiple macro strings into one +#[must_use] +pub fn join_macro_strings(strings: I) -> MacroString +where + I: IntoIterator, +{ + strings + .into_iter() + .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) + } + }, + }) +} diff --git a/src/transpile/variables.rs b/src/transpile/variables.rs index bb20d90..781c36b 100644 --- a/src/transpile/variables.rs +++ b/src/transpile/variables.rs @@ -42,10 +42,12 @@ pub enum VariableData { /// The path to the function once it is generated. path: OnceLock, }, - /// A function argument/parameter. - FunctionArgument { - /// The index of the argument. + /// A macro function parameter. + MacroParameter { + /// The index of the parameter. index: usize, + /// The macro name. + macro_name: String, }, /// A scoreboard. Scoreboard { @@ -214,7 +216,7 @@ impl Transpiler { scope, handler, ), - _ => todo!("declarations not supported yet: {declaration:?}"), + _ => todo!("declarations other than single not supported yet: {declaration:?}"), } } @@ -243,7 +245,7 @@ impl Transpiler { return Err(error); } let (name, target) = - get_single_data_location_identifiers(single, program_identifier, scope, handler)?; + self.get_single_data_location_identifiers(single, program_identifier, scope, handler)?; match variable_type { KeywordKind::Int => { @@ -305,7 +307,7 @@ impl Transpiler { target: target.to_owned(), }) } - VariableData::Function { .. } | VariableData::FunctionArgument { .. } => { + VariableData::Function { .. } | VariableData::MacroParameter { .. } => { let err = TranspileError::AssignmentError(AssignmentError { identifier: identifier.span(), message: format!( @@ -332,129 +334,139 @@ impl Transpiler { Err(err) } } -} -#[expect(clippy::too_many_lines)] -#[cfg(feature = "shulkerbox")] -fn get_single_data_location_identifiers( - single: &SingleVariableDeclaration, - program_identifier: &str, - scope: &Arc, - handler: &impl Handler, -) -> TranspileResult<(String, String)> { - let mut deobfuscate_annotations = single - .annotations() - .iter() - .filter(|a| a.has_identifier("deobfuscate")); + #[expect(clippy::too_many_lines)] + #[cfg(feature = "shulkerbox")] + fn get_single_data_location_identifiers( + &mut self, + single: &SingleVariableDeclaration, + program_identifier: &str, + scope: &Arc, + handler: &impl Handler, + ) -> TranspileResult<(String, String)> { + let mut deobfuscate_annotations = single + .annotations() + .iter() + .filter(|a| a.has_identifier("deobfuscate")); - let variable_type = single.variable_type().keyword; + let variable_type = single.variable_type().keyword; - let deobfuscate_annotation = deobfuscate_annotations.next(); + 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()); + 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()); - if let TranspileAnnotationValue::Map(map) = deobfuscate_annotation_value { - if map.len() > 2 { - let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { - annotation: deobfuscate_annotation.span(), - message: "Deobfuscate annotation must have at most 2 key-value pairs." - .to_string(), - }); - handler.receive(error.clone()); - return Err(error); - } - if let (Some(name), Some(target)) = (map.get("name"), map.get("target")) { - if let ( - TranspileAnnotationValue::Expression(objective), - TranspileAnnotationValue::Expression(target), - ) = (name, target) - { - if let (Some(name_eval), Some(target_eval)) = ( - 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) { - let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { + if let TranspileAnnotationValue::Map(map) = deobfuscate_annotation_value { + if map.len() > 2 { + let error = + TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { + annotation: deobfuscate_annotation.span(), + message: "Deobfuscate annotation must have at most 2 key-value pairs." + .to_string(), + }); + handler.receive(error.clone()); + return Err(error); + } + if let (Some(name), Some(target)) = (map.get("name"), map.get("target")) { + if let ( + TranspileAnnotationValue::Expression(objective), + TranspileAnnotationValue::Expression(target), + ) = (name, target) + { + if let (Some(name_eval), Some(target_eval)) = ( + 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) { + let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { annotation: deobfuscate_annotation.span(), message: "Deobfuscate annotation 'name' must be a valid scoreboard objective name.".to_string() }); - handler.receive(error.clone()); - return Err(error); - } - if !crate::util::is_valid_scoreboard_target(&target_eval) { - let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { + handler.receive(error.clone()); + return Err(error); + } + if !crate::util::is_valid_scoreboard_target(&target_eval) { + let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { annotation: deobfuscate_annotation.span(), message: "Deobfuscate annotation 'target' must be a valid scoreboard player name.".to_string() }); - handler.receive(error.clone()); - return Err(error); - } - Ok((name_eval, target_eval)) - } else { - let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { + handler.receive(error.clone()); + return Err(error); + } + Ok((name_eval, target_eval)) + } else { + let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { annotation: deobfuscate_annotation.span(), message: "Deobfuscate annotation 'name' or 'target' could not have been evaluated at compile time.".to_string() }); + handler.receive(error.clone()); + Err(error) + } + } else { + let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { + annotation: deobfuscate_annotation.span(), + message: "Deobfuscate annotation 'name' and 'target' must be compile time expressions.".to_string() + }); handler.receive(error.clone()); Err(error) } } else { - let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { - annotation: deobfuscate_annotation.span(), - message: "Deobfuscate annotation 'name' and 'target' must be compile time expressions.".to_string() - }); + let error = + TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { + annotation: deobfuscate_annotation.span(), + message: + "Deobfuscate annotation must have both 'name' and 'target' keys." + .to_string(), + }); handler.receive(error.clone()); Err(error) } } else { let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { annotation: deobfuscate_annotation.span(), - message: "Deobfuscate annotation must have both 'name' and 'target' keys." - .to_string(), + message: "Deobfuscate annotation must be a map.".to_string(), }); handler.receive(error.clone()); Err(error) } } else { - let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { - annotation: deobfuscate_annotation.span(), - message: "Deobfuscate annotation must be a map.".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 = single.identifier().span.str(); - // TODO: generate same name each time (not dependent on pointer) - let mut target = md5::hash(format!( - "{scope}\0{identifier_name}\0{shadowed}", - scope = Arc::as_ptr(scope) as usize, - shadowed = scope.get_variable_shadow_count(identifier_name) - )) - .to_hex_lowercase(); + let hashed = md5::hash(program_identifier).to_hex_lowercase(); + let name = if matches!(variable_type, KeywordKind::Int) { + "shu_values_" + } else { + "shulkerbox:values_" + } + .to_string() + + &hashed; + let identifier_name = single.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(); - if matches!(variable_type, KeywordKind::Int) { - target.split_off(16); - } + if matches!(variable_type, KeywordKind::Int) { + target.split_off(16); + } - Ok((name, target)) + Ok((name, target)) + } } }