implement uninstall annotation

This commit is contained in:
Moritz Hölting 2025-04-07 14:05:54 +02:00
parent 1e82d2321f
commit 68149d9ddf
7 changed files with 131 additions and 54 deletions

View File

@ -35,7 +35,7 @@ pathdiff = "0.2.3"
serde = { version = "1.0.217", features = ["derive"], optional = true } serde = { version = "1.0.217", features = ["derive"], optional = true }
serde_json = { version = "1.0.138", optional = true } serde_json = { version = "1.0.138", optional = true }
# shulkerbox = { version = "0.1.0", default-features = false, 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" strsim = "0.11.1"
strum = { version = "0.27.0", features = ["derive"] } strum = { version = "0.27.0", features = ["derive"] }
thiserror = "2.0.11" thiserror = "2.0.11"

View File

@ -162,16 +162,14 @@ impl Function {
let child_scope = SemanticScope::with_parent(scope); let child_scope = SemanticScope::with_parent(scope);
if let Some(parameters) = self.parameters().as_ref().map(ConnectedList::elements) { if let Some(parameters) = self.parameters().as_ref().map(ConnectedList::elements) {
if let Some(incompatible) = self if let Some(incompatible) = self.annotations().iter().find(|a| {
.annotations() ["tick", "load", "uninstall"].contains(&a.assignment().identifier.span.str())
.iter() }) {
.find(|a| ["tick", "load"].contains(&a.assignment().identifier.span.str()))
{
let err = let err =
error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation { error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
span: incompatible.assignment().identifier.span.clone(), span: incompatible.assignment().identifier.span.clone(),
reason: reason:
"functions with the `tick` or `load` annotation cannot have parameters" "functions with the `tick`, `load` or `uninstall` annotation cannot have parameters"
.to_string(), .to_string(),
}); });
handler.receive(err.clone()); handler.receive(err.clone());

View File

@ -1722,10 +1722,16 @@ impl Transpiler {
.to_hex_lowercase() .to_hex_lowercase()
.split_off(16) .split_off(16)
}) })
.collect(); .collect::<Vec<_>>();
self.temp_counter = self.temp_counter.wrapping_add(amount); 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) (storage_name, paths)
} }
} }

View File

@ -85,8 +85,8 @@ impl Transpiler {
Ok("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase()) Ok("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase())
}, },
|val| match val { |val| match val {
TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()), TranspileAnnotationValue::None(_) => Ok(identifier_span.str().to_string()),
TranspileAnnotationValue::Expression(expr) => expr TranspileAnnotationValue::Expression(expr, _) => expr
.comptime_eval(scope, handler) .comptime_eval(scope, handler)
.ok() .ok()
.and_then(|val| val.to_string_no_macro()) .and_then(|val| val.to_string_no_macro())
@ -101,10 +101,10 @@ impl Transpiler {
handler.receive(err.clone()); handler.receive(err.clone());
err err
}), }),
TranspileAnnotationValue::Map(_) => { TranspileAnnotationValue::Map(_, span) => {
let err = let err =
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: identifier_span.clone(), annotation: span.clone(),
message: "Deobfuscate annotation cannot be a map.".to_string(), message: "Deobfuscate annotation cannot be a map.".to_string(),
}); });
handler.receive(err.clone()); handler.receive(err.clone());

View File

@ -64,28 +64,34 @@ pub struct FunctionData {
#[derive(Debug, Clone, PartialEq, Eq, Hash, EnumIs)] #[derive(Debug, Clone, PartialEq, Eq, Hash, EnumIs)]
pub enum TranspileAnnotationValue { pub enum TranspileAnnotationValue {
/// No value. /// No value.
None, None(Span),
/// A single expression. /// A single expression.
Expression(Expression), Expression(Expression, Span),
/// A map of key-value pairs. /// A map of key-value pairs.
Map(BTreeMap<String, TranspileAnnotationValue>), Map(BTreeMap<String, TranspileAnnotationValue>, Span),
} }
impl From<Option<AnnotationValue>> for TranspileAnnotationValue { impl TranspileAnnotationValue {
fn from(value: Option<AnnotationValue>) -> Self { /// Creates a new `TranspileAnnotationValue` from an [`AnnotationValue`] and [`Span`] of the key.
#[must_use]
pub fn from_annotation_value(value: Option<AnnotationValue>, key_span: &Span) -> Self {
match value { match value {
None => Self::None, None => Self::None(key_span.clone()),
Some(AnnotationValue::Single { value, .. }) => Self::Expression(value), Some(AnnotationValue::Single { value, .. }) => {
let span = value.span();
Self::Expression(value, span)
}
Some(AnnotationValue::Multiple { list, .. }) => { Some(AnnotationValue::Multiple { list, .. }) => {
let span = list.span();
let map = list let map = list
.into_elements() .into_elements()
.map(|elem| { .map(|elem| {
let key = elem.identifier.span.str().to_string(); 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) (key, value)
}) })
.collect(); .collect();
Self::Map(map) Self::Map(map, span)
} }
} }
} }
@ -112,9 +118,11 @@ impl Debug for FunctionData {
impl Debug for AnnotationValueWrapper<'_> { impl Debug for AnnotationValueWrapper<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 { match self.0 {
TranspileAnnotationValue::None => None::<u8>.fmt(f), TranspileAnnotationValue::None(_) => None::<u8>.fmt(f),
TranspileAnnotationValue::Expression(expr) => expr.span().str().fmt(f), TranspileAnnotationValue::Expression(expr, _) => {
TranspileAnnotationValue::Map(map) => AnnotationsWrapper(map).fmt(f), expr.span().str().fmt(f)
}
TranspileAnnotationValue::Map(map, _) => AnnotationsWrapper(map).fmt(f),
} }
} }
} }

View File

@ -1,11 +1,12 @@
//! Transpiler for `Shulkerscript` //! Transpiler for `Shulkerscript`
use std::{ use std::{
collections::{BTreeMap, HashSet}, collections::{BTreeMap, BTreeSet, HashSet},
ops::Deref, ops::Deref,
sync::{Arc, OnceLock}, sync::{Arc, OnceLock},
}; };
use itertools::Itertools;
use shulkerbox::datapack::{self, Command, Datapack, Execute}; use shulkerbox::datapack::{self, Command, Datapack, Execute};
use crate::{ use crate::{
@ -21,7 +22,10 @@ use crate::{
}, },
AnnotationAssignment, AnnotationAssignment,
}, },
transpile::util::{MacroString, MacroStringPart}, transpile::{
error::IllegalAnnotationContent,
util::{MacroString, MacroStringPart},
},
}; };
use super::{ use super::{
@ -38,6 +42,7 @@ pub struct Transpiler {
pub(super) datapack: shulkerbox::datapack::Datapack, pub(super) datapack: shulkerbox::datapack::Datapack,
pub(super) setup_cmds: Vec<Command>, pub(super) setup_cmds: Vec<Command>,
pub(super) initialized_constant_scores: HashSet<i64>, pub(super) initialized_constant_scores: HashSet<i64>,
pub(super) temp_data_storage_locations: BTreeSet<(String, String)>,
pub(super) temp_counter: usize, pub(super) temp_counter: usize,
/// Top-level [`Scope`] for each program identifier /// Top-level [`Scope`] for each program identifier
pub(super) scopes: BTreeMap<String, Arc<Scope>>, pub(super) scopes: BTreeMap<String, Arc<Scope>>,
@ -53,6 +58,7 @@ impl Transpiler {
datapack: shulkerbox::datapack::Datapack::new(main_namespace_name, pack_format), datapack: shulkerbox::datapack::Datapack::new(main_namespace_name, pack_format),
setup_cmds: Vec::new(), setup_cmds: Vec::new(),
initialized_constant_scores: HashSet::new(), initialized_constant_scores: HashSet::new(),
temp_data_storage_locations: BTreeSet::new(),
temp_counter: 0, temp_counter: 0,
scopes: BTreeMap::new(), scopes: BTreeMap::new(),
} }
@ -110,10 +116,10 @@ impl Transpiler {
} }
} }
let mut always_transpile_functions = Vec::new(); let functions = self
.scopes
{ .iter()
let functions = self.scopes.iter().flat_map(|(_, scope)| { .flat_map(|(_, scope)| {
scope scope
.get_local_variables() .get_local_variables()
.read() .read()
@ -122,16 +128,24 @@ impl Transpiler {
.filter_map(|data| data.as_function().map(|(data, _, _)| data)) .filter_map(|data| data.as_function().map(|(data, _, _)| data))
.cloned() .cloned()
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); })
for data in functions { .unique_by(|data| (data.namespace.clone(), data.identifier_span.clone()))
.collect::<Vec<_>>();
let always_transpile_functions = functions
.iter()
.filter_map(|data| {
let always_transpile_function = data.annotations.contains_key("tick") let always_transpile_function = data.annotations.contains_key("tick")
|| data.annotations.contains_key("load") || data.annotations.contains_key("load")
|| data.annotations.contains_key("deobfuscate"); || data.annotations.contains_key("deobfuscate")
|| data.annotations.contains_key("uninstall");
if always_transpile_function { if always_transpile_function {
always_transpile_functions.push(data.identifier_span.clone()); Some(data.identifier_span.clone())
}; } else {
} None
} }
})
.collect::<Vec<_>>();
tracing::trace!( tracing::trace!(
"Transpiling functions requested by user: {:?}", "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::<TranspileResult<Vec<_>>>()?;
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) Ok(self.datapack)
} }
@ -208,7 +264,10 @@ impl Transpiler {
} = annotation.assignment(); } = annotation.assignment();
( (
key.span().str().to_string(), key.span().str().to_string(),
TranspileAnnotationValue::from(value.clone()), TranspileAnnotationValue::from_annotation_value(
value.clone(),
&key.span,
),
) )
}) })
.collect(); .collect();
@ -229,7 +288,7 @@ impl Transpiler {
VariableData::Function { VariableData::Function {
function_data, function_data,
path: OnceLock::new(), path: OnceLock::new(),
function_scope: scope.clone(), function_scope: Scope::with_parent(scope.clone()),
}, },
); );
} }

View File

@ -805,11 +805,13 @@ impl Transpiler {
return Err(error); return Err(error);
} }
if let Some(deobfuscate_annotation) = deobfuscate_annotation { if let Some(deobfuscate_annotation) = deobfuscate_annotation {
let deobfuscate_annotation_value = let deobfuscate_annotation_value = TranspileAnnotationValue::from_annotation_value(
TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone()); deobfuscate_annotation.assignment().value.clone(),
&deobfuscate_annotation.assignment().identifier.span,
);
match deobfuscate_annotation_value { match deobfuscate_annotation_value {
TranspileAnnotationValue::Expression(expr) => { TranspileAnnotationValue::Expression(expr, _) => {
if let Some(name_eval) = expr if let Some(name_eval) = expr
.comptime_eval(scope, handler) .comptime_eval(scope, handler)
.ok() .ok()
@ -834,8 +836,8 @@ impl Transpiler {
Err(error) Err(error)
} }
} }
TranspileAnnotationValue::None => Ok(identifier.span.str().to_string()), TranspileAnnotationValue::None(_) => Ok(identifier.span.str().to_string()),
TranspileAnnotationValue::Map(_) => { TranspileAnnotationValue::Map(_, _) => {
let error = let error =
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(), annotation: deobfuscate_annotation.span(),
@ -888,10 +890,12 @@ impl Transpiler {
return Err(error); return Err(error);
} }
if let Some(deobfuscate_annotation) = deobfuscate_annotation { if let Some(deobfuscate_annotation) = deobfuscate_annotation {
let deobfuscate_annotation_value = let deobfuscate_annotation_value = TranspileAnnotationValue::from_annotation_value(
TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone()); 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 { if map.len() > 2 {
let error = let error =
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
@ -904,8 +908,8 @@ impl Transpiler {
} }
if let (Some(name), Some(target)) = (map.get("name"), map.get("target")) { if let (Some(name), Some(target)) = (map.get("name"), map.get("target")) {
if let ( if let (
TranspileAnnotationValue::Expression(objective), TranspileAnnotationValue::Expression(objective, _),
TranspileAnnotationValue::Expression(target), TranspileAnnotationValue::Expression(target, _),
) = (name, target) ) = (name, target)
{ {
if let (Some(name_eval), Some(target_eval)) = ( if let (Some(name_eval), Some(target_eval)) = (
@ -1022,11 +1026,13 @@ impl Transpiler {
return Err(error); return Err(error);
} }
if let Some(deobfuscate_annotation) = deobfuscate_annotation { if let Some(deobfuscate_annotation) = deobfuscate_annotation {
let deobfuscate_annotation_value = let deobfuscate_annotation_value = TranspileAnnotationValue::from_annotation_value(
TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone()); deobfuscate_annotation.assignment().value.clone(),
&deobfuscate_annotation.assignment().identifier.span,
);
match deobfuscate_annotation_value { match deobfuscate_annotation_value {
TranspileAnnotationValue::None => { TranspileAnnotationValue::None(_) => {
let ident_str = declaration.identifier().span.str(); let ident_str = declaration.identifier().span.str();
let name = if matches!(variable_type, KeywordKind::Int) { let name = if matches!(variable_type, KeywordKind::Int) {
ident_str.to_string() ident_str.to_string()
@ -1041,7 +1047,7 @@ impl Transpiler {
let targets = (0..len).map(|i| i.to_string()).collect(); let targets = (0..len).map(|i| i.to_string()).collect();
Ok((name, targets)) Ok((name, targets))
} }
TranspileAnnotationValue::Map(map) => { TranspileAnnotationValue::Map(map, _) => {
// TODO: implement when map deobfuscate annotation is implemented // TODO: implement when map deobfuscate annotation is implemented
let error = let error =
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
@ -1052,7 +1058,7 @@ impl Transpiler {
handler.receive(error.clone()); handler.receive(error.clone());
Err(error) Err(error)
} }
TranspileAnnotationValue::Expression(_) => { TranspileAnnotationValue::Expression(_, _) => {
let error = let error =
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(), annotation: deobfuscate_annotation.span(),