From 68149d9ddf1a5c1cb0f453cebfbda4c34fe09b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Mon, 7 Apr 2025 14:05:54 +0200 Subject: [PATCH] implement uninstall annotation --- Cargo.toml | 2 +- src/semantic/mod.rs | 10 ++--- src/transpile/expression.rs | 8 +++- src/transpile/function.rs | 8 ++-- src/transpile/mod.rs | 32 ++++++++----- src/transpile/transpiler.rs | 89 ++++++++++++++++++++++++++++++------- src/transpile/variables.rs | 36 ++++++++------- 7 files changed, 131 insertions(+), 54 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5197f0b..ec83427 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ pathdiff = "0.2.3" serde = { version = "1.0.217", features = ["derive"], optional = true } serde_json = { version = "1.0.138", optional = true } # shulkerbox = { version = "0.1.0", default-features = false, optional = true } -shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "e9f2b9b91d72322ec2e063ce7b83415071306468", default-features = false, optional = true } +shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "7392887c127b1aae0d78d573a02332c2fa2591c8", default-features = false, optional = true } strsim = "0.11.1" strum = { version = "0.27.0", features = ["derive"] } thiserror = "2.0.11" diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index b1c9f0d..8266fb3 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -162,16 +162,14 @@ impl Function { let child_scope = SemanticScope::with_parent(scope); if let Some(parameters) = self.parameters().as_ref().map(ConnectedList::elements) { - if let Some(incompatible) = self - .annotations() - .iter() - .find(|a| ["tick", "load"].contains(&a.assignment().identifier.span.str())) - { + if let Some(incompatible) = self.annotations().iter().find(|a| { + ["tick", "load", "uninstall"].contains(&a.assignment().identifier.span.str()) + }) { let err = error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation { span: incompatible.assignment().identifier.span.clone(), reason: - "functions with the `tick` or `load` annotation cannot have parameters" + "functions with the `tick`, `load` or `uninstall` annotation cannot have parameters" .to_string(), }); handler.receive(err.clone()); diff --git a/src/transpile/expression.rs b/src/transpile/expression.rs index 9b8fd5a..729ff83 100644 --- a/src/transpile/expression.rs +++ b/src/transpile/expression.rs @@ -1722,10 +1722,16 @@ impl Transpiler { .to_hex_lowercase() .split_off(16) }) - .collect(); + .collect::>(); self.temp_counter = self.temp_counter.wrapping_add(amount); + self.temp_data_storage_locations.extend( + paths + .iter() + .map(|path| (storage_name.clone(), path.clone())), + ); + (storage_name, paths) } } diff --git a/src/transpile/function.rs b/src/transpile/function.rs index 630f7d1..994c9f2 100644 --- a/src/transpile/function.rs +++ b/src/transpile/function.rs @@ -85,8 +85,8 @@ impl Transpiler { Ok("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase()) }, |val| match val { - TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()), - TranspileAnnotationValue::Expression(expr) => expr + TranspileAnnotationValue::None(_) => Ok(identifier_span.str().to_string()), + TranspileAnnotationValue::Expression(expr, _) => expr .comptime_eval(scope, handler) .ok() .and_then(|val| val.to_string_no_macro()) @@ -101,10 +101,10 @@ impl Transpiler { handler.receive(err.clone()); err }), - TranspileAnnotationValue::Map(_) => { + TranspileAnnotationValue::Map(_, span) => { let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { - annotation: identifier_span.clone(), + annotation: span.clone(), message: "Deobfuscate annotation cannot be a map.".to_string(), }); handler.receive(err.clone()); diff --git a/src/transpile/mod.rs b/src/transpile/mod.rs index ea9d503..7779460 100644 --- a/src/transpile/mod.rs +++ b/src/transpile/mod.rs @@ -64,28 +64,34 @@ pub struct FunctionData { #[derive(Debug, Clone, PartialEq, Eq, Hash, EnumIs)] pub enum TranspileAnnotationValue { /// No value. - None, + None(Span), /// A single expression. - Expression(Expression), + Expression(Expression, Span), /// A map of key-value pairs. - Map(BTreeMap), + Map(BTreeMap, Span), } -impl From> for TranspileAnnotationValue { - fn from(value: Option) -> Self { +impl TranspileAnnotationValue { + /// Creates a new `TranspileAnnotationValue` from an [`AnnotationValue`] and [`Span`] of the key. + #[must_use] + pub fn from_annotation_value(value: Option, key_span: &Span) -> Self { match value { - None => Self::None, - Some(AnnotationValue::Single { value, .. }) => Self::Expression(value), + None => Self::None(key_span.clone()), + Some(AnnotationValue::Single { value, .. }) => { + let span = value.span(); + Self::Expression(value, span) + } Some(AnnotationValue::Multiple { list, .. }) => { + let span = list.span(); let map = list .into_elements() .map(|elem| { let key = elem.identifier.span.str().to_string(); - let value = Self::from(elem.value); + let value = Self::from_annotation_value(elem.value, &elem.identifier.span); (key, value) }) .collect(); - Self::Map(map) + Self::Map(map, span) } } } @@ -112,9 +118,11 @@ impl Debug for FunctionData { impl Debug for AnnotationValueWrapper<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.0 { - TranspileAnnotationValue::None => None::.fmt(f), - TranspileAnnotationValue::Expression(expr) => expr.span().str().fmt(f), - TranspileAnnotationValue::Map(map) => AnnotationsWrapper(map).fmt(f), + TranspileAnnotationValue::None(_) => None::.fmt(f), + TranspileAnnotationValue::Expression(expr, _) => { + expr.span().str().fmt(f) + } + TranspileAnnotationValue::Map(map, _) => AnnotationsWrapper(map).fmt(f), } } } diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 89b2300..83e79f5 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -1,11 +1,12 @@ //! Transpiler for `Shulkerscript` use std::{ - collections::{BTreeMap, HashSet}, + collections::{BTreeMap, BTreeSet, HashSet}, ops::Deref, sync::{Arc, OnceLock}, }; +use itertools::Itertools; use shulkerbox::datapack::{self, Command, Datapack, Execute}; use crate::{ @@ -21,7 +22,10 @@ use crate::{ }, AnnotationAssignment, }, - transpile::util::{MacroString, MacroStringPart}, + transpile::{ + error::IllegalAnnotationContent, + util::{MacroString, MacroStringPart}, + }, }; use super::{ @@ -38,6 +42,7 @@ pub struct Transpiler { pub(super) datapack: shulkerbox::datapack::Datapack, pub(super) setup_cmds: Vec, pub(super) initialized_constant_scores: HashSet, + pub(super) temp_data_storage_locations: BTreeSet<(String, String)>, pub(super) temp_counter: usize, /// Top-level [`Scope`] for each program identifier pub(super) scopes: BTreeMap>, @@ -53,6 +58,7 @@ impl Transpiler { datapack: shulkerbox::datapack::Datapack::new(main_namespace_name, pack_format), setup_cmds: Vec::new(), initialized_constant_scores: HashSet::new(), + temp_data_storage_locations: BTreeSet::new(), temp_counter: 0, scopes: BTreeMap::new(), } @@ -110,10 +116,10 @@ impl Transpiler { } } - let mut always_transpile_functions = Vec::new(); - - { - let functions = self.scopes.iter().flat_map(|(_, scope)| { + let functions = self + .scopes + .iter() + .flat_map(|(_, scope)| { scope .get_local_variables() .read() @@ -122,16 +128,24 @@ impl Transpiler { .filter_map(|data| data.as_function().map(|(data, _, _)| data)) .cloned() .collect::>() - }); - for data in functions { + }) + .unique_by(|data| (data.namespace.clone(), data.identifier_span.clone())) + .collect::>(); + + let always_transpile_functions = functions + .iter() + .filter_map(|data| { let always_transpile_function = data.annotations.contains_key("tick") || data.annotations.contains_key("load") - || data.annotations.contains_key("deobfuscate"); + || data.annotations.contains_key("deobfuscate") + || data.annotations.contains_key("uninstall"); if always_transpile_function { - always_transpile_functions.push(data.identifier_span.clone()); - }; - } - } + Some(data.identifier_span.clone()) + } else { + None + } + }) + .collect::>(); tracing::trace!( "Transpiling functions requested by user: {:?}", @@ -163,6 +177,48 @@ impl Transpiler { ); } + let uninstall_function_cmds = functions + .iter() + .filter_map(|data| data.annotations.get("uninstall").map(|val| (data, val))) + .map(|(function, val)| match val { + TranspileAnnotationValue::None(_) => { + let identifier_span = &function.identifier_span; + let scope = self + .scopes + .entry(identifier_span.source_file().identifier().to_owned()) + .or_insert_with(Scope::with_internal_functions) + .to_owned(); + let (function_path, _) = + self.get_or_transpile_function(identifier_span, None, &scope, handler)?; + let uninstall_cmd = Command::Raw(format!("function {function_path}")); + Ok(uninstall_cmd) + } + TranspileAnnotationValue::Expression(_, span) + | TranspileAnnotationValue::Map(_, span) => { + let error = + TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { + annotation: span.clone(), + message: "uninstall annotation must not have a value".to_string(), + }); + handler.receive(error.clone()); + Err(error) + } + }) + .collect::>>()?; + + self.datapack + .add_uninstall_commands(uninstall_function_cmds); + + let temp_data_uninstall_cmds = self + .temp_data_storage_locations + .iter() + .map(|(storage_name, path)| { + Command::Raw(format!("data remove storage {storage_name} {path}")) + }) + .collect(); + self.datapack + .add_uninstall_commands(temp_data_uninstall_cmds); + Ok(self.datapack) } @@ -208,7 +264,10 @@ impl Transpiler { } = annotation.assignment(); ( key.span().str().to_string(), - TranspileAnnotationValue::from(value.clone()), + TranspileAnnotationValue::from_annotation_value( + value.clone(), + &key.span, + ), ) }) .collect(); @@ -229,7 +288,7 @@ impl Transpiler { VariableData::Function { function_data, path: OnceLock::new(), - function_scope: scope.clone(), + function_scope: Scope::with_parent(scope.clone()), }, ); } diff --git a/src/transpile/variables.rs b/src/transpile/variables.rs index 5755894..a397c79 100644 --- a/src/transpile/variables.rs +++ b/src/transpile/variables.rs @@ -805,11 +805,13 @@ impl Transpiler { return Err(error); } if let Some(deobfuscate_annotation) = deobfuscate_annotation { - let deobfuscate_annotation_value = - TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone()); + let deobfuscate_annotation_value = TranspileAnnotationValue::from_annotation_value( + deobfuscate_annotation.assignment().value.clone(), + &deobfuscate_annotation.assignment().identifier.span, + ); match deobfuscate_annotation_value { - TranspileAnnotationValue::Expression(expr) => { + TranspileAnnotationValue::Expression(expr, _) => { if let Some(name_eval) = expr .comptime_eval(scope, handler) .ok() @@ -834,8 +836,8 @@ impl Transpiler { Err(error) } } - TranspileAnnotationValue::None => Ok(identifier.span.str().to_string()), - TranspileAnnotationValue::Map(_) => { + TranspileAnnotationValue::None(_) => Ok(identifier.span.str().to_string()), + TranspileAnnotationValue::Map(_, _) => { let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { annotation: deobfuscate_annotation.span(), @@ -888,10 +890,12 @@ impl Transpiler { return Err(error); } if let Some(deobfuscate_annotation) = deobfuscate_annotation { - let deobfuscate_annotation_value = - TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone()); + let deobfuscate_annotation_value = TranspileAnnotationValue::from_annotation_value( + deobfuscate_annotation.assignment().value.clone(), + &deobfuscate_annotation.assignment().identifier.span, + ); - if let TranspileAnnotationValue::Map(map) = deobfuscate_annotation_value { + if let TranspileAnnotationValue::Map(map, _) = deobfuscate_annotation_value { if map.len() > 2 { let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { @@ -904,8 +908,8 @@ impl Transpiler { } if let (Some(name), Some(target)) = (map.get("name"), map.get("target")) { if let ( - TranspileAnnotationValue::Expression(objective), - TranspileAnnotationValue::Expression(target), + TranspileAnnotationValue::Expression(objective, _), + TranspileAnnotationValue::Expression(target, _), ) = (name, target) { if let (Some(name_eval), Some(target_eval)) = ( @@ -1022,11 +1026,13 @@ impl Transpiler { return Err(error); } if let Some(deobfuscate_annotation) = deobfuscate_annotation { - let deobfuscate_annotation_value = - TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone()); + let deobfuscate_annotation_value = TranspileAnnotationValue::from_annotation_value( + deobfuscate_annotation.assignment().value.clone(), + &deobfuscate_annotation.assignment().identifier.span, + ); match deobfuscate_annotation_value { - TranspileAnnotationValue::None => { + TranspileAnnotationValue::None(_) => { let ident_str = declaration.identifier().span.str(); let name = if matches!(variable_type, KeywordKind::Int) { ident_str.to_string() @@ -1041,7 +1047,7 @@ impl Transpiler { let targets = (0..len).map(|i| i.to_string()).collect(); Ok((name, targets)) } - TranspileAnnotationValue::Map(map) => { + TranspileAnnotationValue::Map(map, _) => { // TODO: implement when map deobfuscate annotation is implemented let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { @@ -1052,7 +1058,7 @@ impl Transpiler { handler.receive(error.clone()); Err(error) } - TranspileAnnotationValue::Expression(_) => { + TranspileAnnotationValue::Expression(_, _) => { let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { annotation: deobfuscate_annotation.span(),