remove duplicate function, optimize function calls with mixed static and dynamic parameters

This commit is contained in:
Moritz Hölting 2025-03-13 14:35:59 +01:00
parent cebe3e9cb0
commit 863bc784cc
4 changed files with 328 additions and 319 deletions

View File

@ -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,24 +420,26 @@ 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
(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)*/ => {
.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
(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)*/ => {
.can_yield_type(ValueType::Boolean, scope) =>
{
Some(ComptimeValue::Boolean(false))
}
(ComptimeValue::Boolean(left), ComptimeValue::Boolean(right)) => {
@ -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<base::Error>,
) -> TranspileResult<Vec<Command>> {
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,13 +1306,11 @@ impl Transpiler {
}
}
#[expect(clippy::unused_self)]
fn transpile_comptime_value(
&self,
fn store_comptime_value(
&mut self,
value: &ComptimeValue,
original: &impl SourceElement,
target: &DataLocation,
_scope: &Arc<super::Scope>,
source: &impl SourceElement,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
match value {
@ -1323,7 +1321,7 @@ impl Transpiler {
DataLocation::Tag { .. } => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ExpectedType::Boolean,
expression: original.span(),
expression: source.span(),
});
handler.receive(err.clone());
Err(err)
@ -1346,7 +1344,7 @@ impl Transpiler {
))])
} else {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: original.span(),
expression: source.span(),
expected_type: target.value_type().into(),
});
handler.receive(err.clone());
@ -1355,12 +1353,6 @@ impl Transpiler {
}
},
&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" }
@ -1378,111 +1370,48 @@ impl Transpiler {
))])
} else {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: original.span(),
expression: source.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,
target: &DataLocation,
source: &impl SourceElement,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
match value {
ComptimeValue::Integer(int) => match target {
DataLocation::ScoreboardValue { objective, target } => Ok(vec![Command::Raw(
format!("scoreboard players set {target} {objective} {int}"),
)]),
DataLocation::Tag { .. } => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ExpectedType::Boolean,
expression: source.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} {path} set value {value}{suffix}",
storage = storage_name,
path = path,
value = int,
suffix = r#type.suffix()
))])
} else {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: source.span(),
expected_type: ExpectedType::Integer,
});
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)])
}
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)])
}
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)
}
}
}
}
}

View File

@ -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,9 +838,7 @@ impl Transpiler {
match arguments {
TranspiledFunctionArguments::Static(arguments) => {
use std::fmt::Write;
let arguments = arguments
.iter()
.map(|(ident, v)| match v {
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),
@ -803,30 +858,8 @@ impl Transpiler {
.chain(std::iter::once(MacroStringPart::String('"'.to_string())))
.collect(),
),
})
.fold(MacroString::String(String::new()), |acc, cur| match acc {
MacroString::String(mut s) => match cur {
MacroString::String(cur) => {
s.push_str(&cur);
MacroString::String(s)
}
MacroString::MacroString(cur) => {
let mut parts = vec![MacroStringPart::String(s)];
parts.extend(cur);
MacroString::MacroString(parts)
}
},
MacroString::MacroString(mut parts) => match cur {
MacroString::String(cur) => {
parts.push(MacroStringPart::String(cur));
MacroString::MacroString(parts)
}
MacroString::MacroString(cur) => {
parts.extend(cur);
MacroString::MacroString(parts)
}
},
});
let arguments = super::util::join_macro_strings(arguments_iter);
let cmd = match arguments {
MacroString::String(arguments) => {

View File

@ -1,5 +1,7 @@
//! Utility methods for transpiling
use shulkerbox::util::{MacroString, MacroStringPart};
fn normalize_program_identifier<S>(identifier: S) -> String
where
S: AsRef<str>,
@ -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<I>(strings: I) -> MacroString
where
I: IntoIterator<Item = MacroString>,
{
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)
}
},
})
}

View File

@ -42,10 +42,12 @@ pub enum VariableData {
/// The path to the function once it is generated.
path: OnceLock<String>,
},
/// 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,16 +334,16 @@ impl Transpiler {
Err(err)
}
}
}
#[expect(clippy::too_many_lines)]
#[cfg(feature = "shulkerbox")]
fn get_single_data_location_identifiers(
#[expect(clippy::too_many_lines)]
#[cfg(feature = "shulkerbox")]
fn get_single_data_location_identifiers(
&mut self,
single: &SingleVariableDeclaration,
program_identifier: &str,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<(String, String)> {
) -> TranspileResult<(String, String)> {
let mut deobfuscate_annotations = single
.annotations()
.iter()
@ -365,7 +367,8 @@ fn get_single_data_location_identifiers(
if let TranspileAnnotationValue::Map(map) = deobfuscate_annotation_value {
if map.len() > 2 {
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
let error =
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation must have at most 2 key-value pairs."
.to_string(),
@ -422,9 +425,11 @@ fn get_single_data_location_identifiers(
Err(error)
}
} else {
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
let error =
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation must have both 'name' and 'target' keys."
message:
"Deobfuscate annotation must have both 'name' and 'target' keys."
.to_string(),
});
handler.receive(error.clone());
@ -440,12 +445,18 @@ fn get_single_data_location_identifiers(
}
} else {
let hashed = md5::hash(program_identifier).to_hex_lowercase();
let name = "shu_values_".to_string() + &hashed;
let name = if matches!(variable_type, KeywordKind::Int) {
"shu_values_"
} else {
"shulkerbox:values_"
}
.to_string()
+ &hashed;
let identifier_name = single.identifier().span.str();
// TODO: generate same name each time (not dependent on pointer)
let scope_ident = self.temp_counter;
self.temp_counter = self.temp_counter.wrapping_add(1);
let mut target = md5::hash(format!(
"{scope}\0{identifier_name}\0{shadowed}",
scope = Arc::as_ptr(scope) as usize,
"{scope_ident}\0{identifier_name}\0{shadowed}",
shadowed = scope.get_variable_shadow_count(identifier_name)
))
.to_hex_lowercase();
@ -456,6 +467,7 @@ fn get_single_data_location_identifiers(
Ok((name, target))
}
}
}
#[cfg(test)]