basic transpilation of template string working

This commit is contained in:
Moritz Hölting 2025-09-02 18:05:21 +02:00
parent ef7bf95447
commit 6043a4add5
14 changed files with 841 additions and 340 deletions

View File

@ -35,13 +35,14 @@ 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 = "6ff544131b2518b8c92bc4d2de7682efd7141ec4", default-features = false, optional = true } shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "89709834da6f39840caa9c6a2eadbdececdc7d44", 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"
tracing = "0.1.41" tracing = "0.1.41"
[dev-dependencies] [dev-dependencies]
assert-struct = "0.2.0"
serde_json = "1.0.138" serde_json = "1.0.138"
[[example]] [[example]]

View File

@ -219,6 +219,12 @@ impl std::hash::Hash for Span {
} }
} }
impl SourceElement for Span {
fn span(&self) -> Span {
self.clone()
}
}
impl Span { impl Span {
/// Create a span from the given start and end byte indices in the source file. /// Create a span from the given start and end byte indices in the source file.
/// ///

View File

@ -81,7 +81,10 @@ impl Display for ConflictingFunctionNames {
"{}", "{}",
Message::new( Message::new(
Severity::Error, Severity::Error,
format!("the following function declaration conflicts with an existing function with name `{}`", self.name) format!(
"the following function declaration conflicts with an existing function with name `{}`",
self.name
)
) )
)?; )?;

View File

@ -173,13 +173,12 @@ impl Function {
if let Some(incompatible) = self.annotations().iter().find(|a| { if let Some(incompatible) = self.annotations().iter().find(|a| {
["tick", "load", "uninstall"].contains(&a.assignment().identifier.span.str()) ["tick", "load", "uninstall"].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`, `load` or `uninstall` 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());
return Err(err); return Err(err);
} else if parameters } else if parameters
@ -191,13 +190,12 @@ impl Function {
.iter() .iter()
.find(|a| a.assignment().identifier.span.str() == "deobfuscate") .find(|a| a.assignment().identifier.span.str() == "deobfuscate")
{ {
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 `deobfuscate` annotation cannot have compile-time parameters"
"functions with the `deobfuscate` annotation cannot have compile-time parameters" .to_string(),
.to_string(), });
});
handler.receive(err.clone()); handler.receive(err.clone());
return Err(err); return Err(err);
} }

View File

@ -415,6 +415,14 @@ impl TemplateStringLiteral {
pub fn parts(&self) -> &[TemplateStringLiteralPart] { pub fn parts(&self) -> &[TemplateStringLiteralPart] {
&self.parts &self.parts
} }
/// Returns whether the template string literal contains any expressions.
#[must_use]
pub fn contains_expression(&self) -> bool {
self.parts
.iter()
.any(|part| matches!(part, TemplateStringLiteralPart::Expression { .. }))
}
} }
impl SourceElement for TemplateStringLiteral { impl SourceElement for TemplateStringLiteral {

View File

@ -828,13 +828,15 @@ impl Parser<'_> {
parser.parse_statement(handler).map_or_else( parser.parse_statement(handler).map_or_else(
|_| { |_| {
// error recovery // error recovery
parser.stop_at(|reading| matches!( parser.stop_at(|reading| {
reading, matches!(
Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == ';' reading,
) || matches!( Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == ';'
reading, ) || matches!(
Reading::IntoDelimited(punc) if punc.punctuation == '{' reading,
)); Reading::IntoDelimited(punc) if punc.punctuation == '{'
)
});
// goes after the semicolon or the open brace // goes after the semicolon or the open brace
parser.forward(); parser.forward();

View File

@ -1,26 +1,37 @@
//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types //! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
use std::{borrow::Cow, sync::Arc}; use std::{borrow::Cow, collections::BTreeMap, sync::Arc};
use shulkerbox::util::{MacroString as ExtMacroString, MacroStringPart as ExtMacroStringPart}; use shulkerbox::{
prelude::Command,
util::{MacroString as ExtMacroString, MacroStringPart as ExtMacroStringPart},
};
use crate::{ use crate::{
base::{self, Handler}, base::{self, source_file::Span, Handler},
semantic::error::UnexpectedExpression, semantic::error::UnexpectedExpression,
syntax::syntax_tree::expression::{TemplateStringLiteral, TemplateStringLiteralPart}, syntax::syntax_tree::expression::{TemplateStringLiteral, TemplateStringLiteralPart},
transpile::{Scope, TranspileError, TranspileResult}, transpile::{expression::DataLocation, Scope, TranspileError, TranspileResult},
util, util,
}; };
use super::util::{MacroString, MacroStringPart}; use super::util::{MacroString, MacroStringPart};
impl From<MacroString> for ExtMacroString { type ShulkerboxMacroStringMap = BTreeMap<String, (DataLocation, Vec<Command>, Span)>;
fn from(value: MacroString) -> Self {
match value { impl MacroString {
MacroString::String(s) => Self::String(s), pub fn into_sb(self) -> (ExtMacroString, ShulkerboxMacroStringMap) {
MacroString::MacroString(parts) => { match self {
Self::MacroString(parts.into_iter().map(ExtMacroStringPart::from).collect()) Self::String(s) => (ExtMacroString::String(s), BTreeMap::new()),
} Self::MacroString {
parts,
prepare_variables,
} => (
ExtMacroString::MacroString(
parts.into_iter().map(ExtMacroStringPart::from).collect(),
),
prepare_variables,
),
} }
} }
} }

View File

@ -465,10 +465,7 @@ impl Display for IllegalIndexingReason {
write!(f, "The expression cannot be indexed.") write!(f, "The expression cannot be indexed.")
} }
Self::InvalidComptimeType { expected } => { Self::InvalidComptimeType { expected } => {
write!( write!(f, "The expression can only be indexed with type {expected} that can be evaluated at compile time.")
f,
"The expression can only be indexed with type {expected} that can be evaluated at compile time."
)
} }
Self::IndexOutOfBounds { index, length } => { Self::IndexOutOfBounds { index, length } => {
write!( write!(

View File

@ -1,8 +1,6 @@
//! The expression transpiler. //! The expression transpiler.
use std::{fmt::Display, string::ToString}; use std::fmt::Display;
use super::util::MacroString;
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use enum_as_inner::EnumAsInner; use enum_as_inner::EnumAsInner;
@ -15,6 +13,7 @@ use super::{
error::{ error::{
IllegalIndexing, IllegalIndexingReason, MismatchedTypes, NotComptime, UnknownIdentifier, IllegalIndexing, IllegalIndexingReason, MismatchedTypes, NotComptime, UnknownIdentifier,
}, },
util::MacroString,
Scope, TranspileResult, Transpiler, VariableData, Scope, TranspileResult, Transpiler, VariableData,
}; };
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
@ -35,6 +34,7 @@ use crate::{
use std::sync::Arc; use std::sync::Arc;
/// Compile-time evaluated value /// Compile-time evaluated value
#[cfg(feature = "shulkerbox")]
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum ComptimeValue { pub enum ComptimeValue {
@ -44,6 +44,7 @@ pub enum ComptimeValue {
MacroString(MacroString), MacroString(MacroString),
} }
#[cfg(feature = "shulkerbox")]
impl ComptimeValue { impl ComptimeValue {
/// Returns the value as a string not containing a macro. /// Returns the value as a string not containing a macro.
#[must_use] #[must_use]
@ -152,7 +153,8 @@ impl From<ValueType> for ExpectedType {
/// Location of data /// Location of data
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DataLocation { pub enum DataLocation {
ScoreboardValue { ScoreboardValue {
objective: String, objective: String,
@ -179,21 +181,34 @@ impl DataLocation {
Self::Storage { r#type, .. } => match r#type { Self::Storage { r#type, .. } => match r#type {
StorageType::Boolean => ValueType::Boolean, StorageType::Boolean => ValueType::Boolean,
StorageType::Byte | StorageType::Int | StorageType::Long => ValueType::Integer, StorageType::Byte | StorageType::Int | StorageType::Long => ValueType::Integer,
StorageType::String => ValueType::String,
StorageType::Double => todo!("Double storage type"), StorageType::Double => todo!("Double storage type"),
}, },
} }
} }
/// Returns the storage type of the data location.
#[must_use]
pub fn storage_type(&self) -> StorageType {
match self {
Self::ScoreboardValue { .. } => StorageType::Int,
Self::Tag { .. } => StorageType::Boolean,
Self::Storage { r#type, .. } => *r#type,
}
}
} }
/// The type of a storage. /// The type of a storage.
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StorageType { pub enum StorageType {
Boolean, Boolean,
Byte, Byte,
Int, Int,
Long, Long,
Double, Double,
String,
} }
impl StorageType { impl StorageType {
@ -205,6 +220,7 @@ impl StorageType {
Self::Int => "", Self::Int => "",
Self::Long => "l", Self::Long => "l",
Self::Double => "d", Self::Double => "d",
Self::String => "",
} }
} }
@ -216,6 +232,7 @@ impl StorageType {
Self::Int => "int", Self::Int => "int",
Self::Long => "long", Self::Long => "long",
Self::Double => "double", Self::Double => "double",
Self::String => "string",
} }
} }
} }
@ -413,15 +430,9 @@ impl Primary {
}) })
.and_then(|val| val), .and_then(|val| val),
Self::TemplateStringLiteral(template_string_literal) => { Self::TemplateStringLiteral(template_string_literal) => {
use crate::syntax::syntax_tree::expression::TemplateStringLiteralPart; if template_string_literal.contains_expression() {
if template_string_literal
.parts()
.iter()
.any(|part| matches!(part, TemplateStringLiteralPart::Expression { .. }))
{
template_string_literal template_string_literal
.to_macro_string(scope, handler) .to_macro_string(None, scope, handler)
.map(ComptimeValue::MacroString) .map(ComptimeValue::MacroString)
.map_err(|_| NotComptime { .map_err(|_| NotComptime {
expression: template_string_literal.span(), expression: template_string_literal.span(),
@ -1310,7 +1321,9 @@ impl Transpiler {
handler, handler,
)?; )?;
self.initialize_constant_score(-1); self.initialize_constant_score(-1);
let negate_cmd = Command::Raw(format!("scoreboard players operation {score_target} {objective} *= -1 shu_constants")); let negate_cmd = Command::Raw(format!(
"scoreboard players operation {score_target} {objective} *= -1 shu_constants"
));
expr_cmds.push(negate_cmd); expr_cmds.push(negate_cmd);
Ok(expr_cmds) Ok(expr_cmds)
@ -1377,7 +1390,9 @@ impl Transpiler {
let run_cmd = if run_cmds.len() == 1 { let run_cmd = if run_cmds.len() == 1 {
run_cmds.into_iter().next().expect("length is 1") run_cmds.into_iter().next().expect("length is 1")
} else { } else {
Command::Group(run_cmds) use shulkerbox::datapack::Group;
Command::Group(Group::new(run_cmds))
}; };
match target { match target {
DataLocation::ScoreboardValue { objective, target } => { DataLocation::ScoreboardValue { objective, target } => {
@ -1416,12 +1431,18 @@ impl Transpiler {
self.get_temp_storage_locations_array(); self.get_temp_storage_locations_array();
let store_cmd = Command::Execute(Execute::Store( let store_cmd = Command::Execute(Execute::Store(
format!("success storage {temp_storage_name} {temp_storage_path} boolean 1.0").into(), format!(
Box::new(Execute::Run(Box::new(run_cmd))) "success storage {temp_storage_name} {temp_storage_path} boolean 1.0"
)
.into(),
Box::new(Execute::Run(Box::new(run_cmd))),
)); ));
let if_cmd = Command::Execute(Execute::If( let if_cmd = Command::Execute(Execute::If(
Condition::Atom(format!("data storage {temp_storage_name} {{{temp_storage_name}:1b}}").into()), Condition::Atom(
format!("data storage {temp_storage_name} {{{temp_storage_name}:1b}}")
.into(),
),
Box::new(Execute::Run(Box::new(success_cmd))), Box::new(Execute::Run(Box::new(success_cmd))),
None, None,
)); ));
@ -1676,12 +1697,15 @@ impl Transpiler {
Vec::new(), Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(s.str_content().to_string().into())), ExtendedCondition::Runtime(Condition::Atom(s.str_content().to_string().into())),
)), )),
Primary::TemplateStringLiteral(template_string) => Ok(( Primary::TemplateStringLiteral(template_string) => {
Vec::new(), let (macro_string, prepare_variables) = template_string
ExtendedCondition::Runtime(Condition::Atom( .to_macro_string(Some(self), scope, handler)?
template_string.to_macro_string(scope, handler)?.into(), .into_sb();
)), Ok((
)), Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(macro_string)),
))
}
Primary::FunctionCall(func) => { Primary::FunctionCall(func) => {
if func if func
.arguments() .arguments()
@ -1858,13 +1882,16 @@ impl Transpiler {
ComptimeValue::String(s) => Ok(( ComptimeValue::String(s) => Ok((
Vec::new(), Vec::new(),
ExtendedCondition::Runtime(Condition::Atom( ExtendedCondition::Runtime(Condition::Atom(
MacroString::String(s).into(), shulkerbox::util::MacroString::String(s),
)), )),
)), )),
ComptimeValue::MacroString(s) => Ok(( ComptimeValue::MacroString(s) => {
Vec::new(), let (macro_string, prepare_variables) = s.into_sb();
ExtendedCondition::Runtime(Condition::Atom(s.into())), Ok((
)), Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(macro_string)),
))
}
}, },
), ),
Primary::Prefix(prefix) => match prefix.operator() { Primary::Prefix(prefix) => match prefix.operator() {
@ -1917,10 +1944,13 @@ impl Transpiler {
Vec::new(), Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(value.into())), ExtendedCondition::Runtime(Condition::Atom(value.into())),
)), )),
Ok(ComptimeValue::MacroString(value)) => Ok(( Ok(ComptimeValue::MacroString(value)) => {
Vec::new(), let (macro_string, prepare_variables) = value.into_sb();
ExtendedCondition::Runtime(Condition::Atom(value.into())), Ok((
)), Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(macro_string)),
))
}
Ok(ComptimeValue::Boolean(boolean)) => { Ok(ComptimeValue::Boolean(boolean)) => {
Ok((Vec::new(), ExtendedCondition::Comptime(boolean))) Ok((Vec::new(), ExtendedCondition::Comptime(boolean)))
} }
@ -2078,6 +2108,14 @@ impl Transpiler {
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));
return Err(err); return Err(err);
} }
StorageType::String => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ExpectedType::String,
expression: binary.span(),
});
handler.receive(Box::new(err.clone()));
return Err(err);
}
}, },
}; };
@ -2204,6 +2242,7 @@ impl Transpiler {
} }
} }
#[expect(clippy::too_many_lines)]
fn store_comptime_value( fn store_comptime_value(
&mut self, &mut self,
value: &ComptimeValue, value: &ComptimeValue,
@ -2294,12 +2333,15 @@ impl Transpiler {
r#type: StorageType::Boolean, r#type: StorageType::Boolean,
.. ..
} }
| DataLocation::Tag { .. } => self.store_condition_success( | DataLocation::Tag { .. } => {
ExtendedCondition::Runtime(Condition::Atom(value.clone().into())), let (macro_string, prepare_variables) = value.clone().into_sb();
target, self.store_condition_success(
source, ExtendedCondition::Runtime(Condition::Atom(macro_string)),
handler, target,
), source,
handler,
)
}
// DataLocation::Storage { storage_name, path, r#type: StorageType::String } => todo!("implement storage string") // DataLocation::Storage { storage_name, path, r#type: StorageType::String } => todo!("implement storage string")
_ => { _ => {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
@ -2370,6 +2412,12 @@ impl Transpiler {
Ok(vec![cmd]) Ok(vec![cmd])
} }
pub(super) fn get_temp_count(&mut self, amount: usize) -> usize {
let current = self.temp_counter;
self.temp_counter = self.temp_counter.wrapping_add(amount);
current
}
/// Get temporary scoreboard locations. /// Get temporary scoreboard locations.
pub(super) fn get_temp_scoreboard_locations(&mut self, amount: usize) -> (String, Vec<String>) { pub(super) fn get_temp_scoreboard_locations(&mut self, amount: usize) -> (String, Vec<String>) {
let objective = "shu_temp_".to_string() let objective = "shu_temp_".to_string()
@ -2378,20 +2426,20 @@ impl Transpiler {
self.datapack self.datapack
.register_scoreboard(&objective, None::<&str>, None::<&str>); .register_scoreboard(&objective, None::<&str>, None::<&str>);
let temp_count = self.get_temp_count(amount);
let targets = (0..amount) let targets = (0..amount)
.map(|i| { .map(|i| {
chksum_md5::hash(format!( chksum_md5::hash(format!(
"{namespace}\0{j}", "{namespace}\0{j}",
namespace = self.main_namespace_name, namespace = self.main_namespace_name,
j = i + self.temp_counter j = i + temp_count
)) ))
.to_hex_lowercase() .to_hex_lowercase()
.split_off(16) .split_off(16)
}) })
.collect(); .collect();
self.temp_counter = self.temp_counter.wrapping_add(amount);
(objective, targets) (objective, targets)
} }
@ -2411,20 +2459,20 @@ impl Transpiler {
let storage_name = "shulkerscript:temp_".to_string() let storage_name = "shulkerscript:temp_".to_string()
+ &chksum_md5::hash(&self.main_namespace_name).to_hex_lowercase(); + &chksum_md5::hash(&self.main_namespace_name).to_hex_lowercase();
let temp_count = self.get_temp_count(amount);
let paths = (0..amount) let paths = (0..amount)
.map(|i| { .map(|i| {
chksum_md5::hash(format!( chksum_md5::hash(format!(
"{namespace}\0{j}", "{namespace}\0{j}",
namespace = self.main_namespace_name, namespace = self.main_namespace_name,
j = i + self.temp_counter j = i + temp_count
)) ))
.to_hex_lowercase() .to_hex_lowercase()
.split_off(16) .split_off(16)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
self.temp_counter = self.temp_counter.wrapping_add(amount);
self.temp_data_storage_locations.extend( self.temp_data_storage_locations.extend(
paths paths
.iter() .iter()

View File

@ -449,21 +449,29 @@ impl Transpiler {
Ok(Parameter::Static(string.str_content().to_string().into())) Ok(Parameter::Static(string.str_content().to_string().into()))
} }
Expression::Primary(Primary::TemplateStringLiteral(literal)) => { Expression::Primary(Primary::TemplateStringLiteral(literal)) => {
Ok(Parameter::Static(literal.to_macro_string(scope, handler)?)) Ok(Parameter::Static(literal.to_macro_string(
Some(self),
scope,
handler,
)?))
} }
Expression::Primary(primary @ Primary::Identifier(ident)) => { Expression::Primary(primary @ Primary::Identifier(ident)) => {
let var = let var =
scope.get_variable(ident.span.str()).ok_or_else(|| { scope.get_variable(ident.span.str()).ok_or_else(|| {
let err = let err = TranspileError::UnknownIdentifier(
TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(ident.span(), scope)); UnknownIdentifier::from_scope(ident.span(), scope),
);
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));
err err
})?; })?;
match var.as_ref() { match var.as_ref() {
VariableData::MacroParameter { macro_name, .. } => { VariableData::MacroParameter { macro_name, .. } => {
Ok(Parameter::Static(MacroString::MacroString(vec![ Ok(Parameter::Static(MacroString::MacroString {
MacroStringPart::MacroUsage(macro_name.clone()), parts: vec![MacroStringPart::MacroUsage(
]))) macro_name.clone(),
)],
prepare_variables: BTreeMap::new(),
}))
} }
VariableData::BooleanStorage { .. } VariableData::BooleanStorage { .. }
@ -605,7 +613,10 @@ impl Transpiler {
crate::util::escape_str(&value).to_string(), crate::util::escape_str(&value).to_string(),
), ),
), ),
MacroString::MacroString(parts) => { MacroString::MacroString {
parts,
prepare_variables: preparation_cmds,
} => {
let parts = parts let parts = parts
.into_iter() .into_iter()
.map(|part| match part { .map(|part| match part {
@ -622,7 +633,10 @@ impl Transpiler {
.collect(); .collect();
statics.insert( statics.insert(
arg_name.to_string(), arg_name.to_string(),
MacroString::MacroString(parts), MacroString::MacroString {
parts,
prepare_variables: preparation_cmds,
},
) )
} }
}; };
@ -634,9 +648,9 @@ impl Transpiler {
} => { } => {
require_dyn_params = true; require_dyn_params = true;
setup_cmds.extend(prepare_cmds); setup_cmds.extend(prepare_cmds);
move_cmds.push(Command::Raw( move_cmds.push(Command::Raw(format!(
format!(r"data modify storage shulkerscript:function_arguments {arg_name} set from storage {storage_name} {path}") r"data modify storage shulkerscript:function_arguments {arg_name} set from storage {storage_name} {path}"
)); )));
} }
} }
} }
@ -654,7 +668,9 @@ impl Transpiler {
Parameter::Static(s) => match s.as_str() { Parameter::Static(s) => match s.as_str() {
Ok(s) => { Ok(s) => {
if s.parse::<i32>().is_ok() { if s.parse::<i32>().is_ok() {
move_cmds.push(Command::Raw(format!(r"scoreboard players set {target} {objective} {s}"))); move_cmds.push(Command::Raw(format!(
r"scoreboard players set {target} {objective} {s}"
)));
} else { } else {
let err = TranspileError::MismatchedTypes( let err = TranspileError::MismatchedTypes(
MismatchedTypes { MismatchedTypes {
@ -666,11 +682,19 @@ impl Transpiler {
return Err(err); return Err(err);
} }
} }
Err(parts) => { Err((parts, preparation_variables)) => {
move_cmds.push(Command::UsesMacro(MacroString::MacroString( let (macro_string, preparation_variables) = MacroString::MacroString {
std::iter::once(MacroStringPart::String(format!("scoreboard players set {target} {objective} "))) parts: std::iter::once(MacroStringPart::String(
.chain(parts.iter().cloned()).collect() format!(
).into())); "scoreboard players set {target} {objective} "
),
))
.chain(parts.iter().cloned())
.collect(),
prepare_variables: preparation_variables.to_owned(),
}
.into_sb();
move_cmds.push(Command::UsesMacro(macro_string));
} }
}, },
Parameter::Storage { Parameter::Storage {
@ -703,7 +727,10 @@ impl Transpiler {
Parameter::Static(s) => match s.as_str() { Parameter::Static(s) => match s.as_str() {
Ok(s) => { Ok(s) => {
if let Ok(b) = s.parse::<bool>() { if let Ok(b) = s.parse::<bool>() {
move_cmds.push(Command::Raw(format!("data modify storage {target_storage_name} {target_path} set value {}", if b { "1b" } else { "0b" }))); move_cmds.push(Command::Raw(format!(
"data modify storage {target_storage_name} {target_path} set value {}",
if b { "1b" } else { "0b" }
)));
} else { } else {
let err = TranspileError::MismatchedTypes( let err = TranspileError::MismatchedTypes(
MismatchedTypes { MismatchedTypes {
@ -715,11 +742,15 @@ impl Transpiler {
return Err(err); return Err(err);
} }
} }
Err(parts) => { Err((parts, preparation_cmds)) => {
move_cmds.push(Command::UsesMacro(MacroString::MacroString( let (macro_string, preparation_variables) = MacroString::MacroString {
std::iter::once(MacroStringPart::String(format!("data modify storage {target_storage_name} {target_path} set value "))) parts: std::iter::once(MacroStringPart::String(format!("data modify storage {target_storage_name} {target_path} set value ")))
.chain(parts.iter().cloned()).collect() .chain(parts.iter().cloned())
).into())); .collect(),
prepare_variables: preparation_cmds.to_owned(),
}
.into_sb();
move_cmds.push(Command::UsesMacro(macro_string));
} }
}, },
Parameter::Storage { Parameter::Storage {
@ -756,7 +787,10 @@ impl Transpiler {
} }
MacroString::String(s) MacroString::String(s)
} }
MacroString::MacroString(mut parts) => { MacroString::MacroString {
mut parts,
prepare_variables: preparation_cmds,
} => {
parts.insert( parts.insert(
0, 0,
MacroStringPart::String(format!(r#"{k}:""#)), MacroStringPart::String(format!(r#"{k}:""#)),
@ -766,7 +800,10 @@ impl Transpiler {
ending.push(','); ending.push(',');
} }
parts.push(MacroStringPart::String(ending)); parts.push(MacroStringPart::String(ending));
MacroString::MacroString(parts) MacroString::MacroString {
parts,
prepare_variables: preparation_cmds,
}
} }
}), }),
); );
@ -775,18 +812,18 @@ impl Transpiler {
MacroString::String(s) => Command::Raw(format!( MacroString::String(s) => Command::Raw(format!(
r"data merge storage shulkerscript:function_arguments_{storage_suffix} {{{s}}}" r"data merge storage shulkerscript:function_arguments_{storage_suffix} {{{s}}}"
)), )),
MacroString::MacroString(_) => { MacroString::MacroString { .. } => {
let prefix = MacroString::String( let prefix = MacroString::String(format!(
format!("data merge storage shulkerscript:function_arguments_{storage_suffix} {{"), "data merge storage shulkerscript:function_arguments_{storage_suffix} {{"
); ));
Command::UsesMacro( let (macro_string, prepare_variables) =
super::util::join_macro_strings([ super::util::join_macro_strings([
prefix, prefix,
joined_statics, joined_statics,
MacroString::String("}".to_string()), MacroString::String("}".to_string()),
]) ])
.into(), .into_sb();
) Command::UsesMacro(macro_string)
} }
}; };
setup_cmds.push(statics_cmd); setup_cmds.push(statics_cmd);
@ -832,7 +869,7 @@ fn comptime_args_hash(args: &[Option<ComptimeValue>]) -> String {
ComptimeValue::String(s) => Cow::Borrowed(s.as_str()), ComptimeValue::String(s) => Cow::Borrowed(s.as_str()),
ComptimeValue::MacroString(s) => match s.as_str() { ComptimeValue::MacroString(s) => match s.as_str() {
Ok(s) => s, Ok(s) => s,
Err(parts) => { Err((parts, _)) => {
let s = parts let s = parts
.iter() .iter()
.map(|p| match p { .map(|p| match p {

View File

@ -589,7 +589,9 @@ fn print_function(
let cmd = format!("tellraw {target} {print_args}"); let cmd = format!("tellraw {target} {print_args}");
let cmd = if contains_macro { let cmd = if contains_macro {
Command::UsesMacro(cmd.parse::<MacroString>().expect("cannot fail").into()) let (macro_string, prepare_variables) =
cmd.parse::<MacroString>().expect("cannot fail").into_sb();
Command::UsesMacro(macro_string)
} else { } else {
Command::Raw(cmd) Command::Raw(cmd)
}; };

View File

@ -7,10 +7,14 @@ use std::{
}; };
use itertools::Itertools; use itertools::Itertools;
use shulkerbox::datapack::{self, Command, Datapack, Execute}; use shulkerbox::datapack::{self, Command, Datapack, Execute, Group};
use crate::{ use crate::{
base::{self, source_file::SourceElement, Handler}, base::{
self,
source_file::{SourceElement, Span},
Handler,
},
semantic::error::UnexpectedExpression, semantic::error::UnexpectedExpression,
syntax::syntax_tree::{ syntax::syntax_tree::{
declaration::{Declaration, FunctionVariableType, ImportItems}, declaration::{Declaration, FunctionVariableType, ImportItems},
@ -24,6 +28,7 @@ use crate::{
}, },
transpile::{ transpile::{
error::IllegalAnnotationContent, error::IllegalAnnotationContent,
expression::DataLocation,
util::{MacroString, MacroStringPart}, util::{MacroString, MacroStringPart},
variables::FunctionVariableDataType, variables::FunctionVariableDataType,
}, },
@ -411,7 +416,7 @@ impl Transpiler {
if commands.is_empty() { if commands.is_empty() {
Ok(Vec::new()) Ok(Vec::new())
} else { } else {
Ok(vec![Command::Group(commands)]) Ok(vec![Command::Group(Group::new(commands))])
} }
} }
Statement::Semicolon(semi) => match semi.statement() { Statement::Semicolon(semi) => match semi.statement() {
@ -467,7 +472,8 @@ impl Transpiler {
.map(|val| val.to_macro_string()); .map(|val| val.to_macro_string());
let (prepare_cmds, ret_cmd) = if let Ok(val) = comptime_val { let (prepare_cmds, ret_cmd) = if let Ok(val) = comptime_val {
(Vec::new(), datapack::ReturnCommand::Value(val.into())) let (macro_string, prepare_variables) = val.into_sb();
(Vec::new(), datapack::ReturnCommand::Value(macro_string))
} else { } else {
match ret.expression() { match ret.expression() {
Expression::Primary(Primary::Prefix(prefix)) Expression::Primary(Primary::Prefix(prefix))
@ -478,7 +484,7 @@ impl Transpiler {
let cmd = if ret_cmds.len() == 1 { let cmd = if ret_cmds.len() == 1 {
ret_cmds.into_iter().next().unwrap() ret_cmds.into_iter().next().unwrap()
} else { } else {
Command::Group(ret_cmds) Command::Group(Group::new(ret_cmds))
}; };
(Vec::new(), datapack::ReturnCommand::Command(Box::new(cmd))) (Vec::new(), datapack::ReturnCommand::Command(Box::new(cmd)))
} }
@ -487,7 +493,7 @@ impl Transpiler {
let cmd = if ret_cmds.len() == 1 { let cmd = if ret_cmds.len() == 1 {
ret_cmds.into_iter().next().unwrap() ret_cmds.into_iter().next().unwrap()
} else { } else {
Command::Group(ret_cmds) Command::Group(Group::new(ret_cmds))
}; };
(Vec::new(), datapack::ReturnCommand::Command(Box::new(cmd))) (Vec::new(), datapack::ReturnCommand::Command(Box::new(cmd)))
} }
@ -513,7 +519,11 @@ impl Transpiler {
}, },
|val| { |val| {
let cmd = val.to_string_no_macro().map_or_else( let cmd = val.to_string_no_macro().map_or_else(
|| Command::UsesMacro(val.to_macro_string().into()), || {
let (macro_string, prepare_variables) =
val.to_macro_string().into_sb();
Command::UsesMacro(macro_string)
},
Command::Raw, Command::Raw,
); );
Ok(( Ok((
@ -588,6 +598,7 @@ impl Transpiler {
Ok(cmds) Ok(cmds)
} }
#[expect(clippy::too_many_lines)]
pub(super) fn transpile_run_expression( pub(super) fn transpile_run_expression(
&mut self, &mut self,
expression: &Primary, expression: &Primary,
@ -600,22 +611,29 @@ impl Transpiler {
Some(VariableData::ComptimeValue { Some(VariableData::ComptimeValue {
value, value,
read_only: _, read_only: _,
}) => value.read().unwrap().as_ref().map_or_else( }) => {
|| { if let Some(val) = value.read().unwrap().as_ref() {
let cmds = val.to_string_no_macro().map_or_else(
|| {
let (macro_string, prepare_variables) =
val.to_macro_string().into_sb();
self.transpile_commands_with_variable_macros(
vec![Command::UsesMacro(macro_string)],
prepare_variables,
handler,
)
},
|s| Ok(vec![Command::Raw(s)]),
)?;
Ok(cmds)
} else {
let err = TranspileError::MissingValue(MissingValue { let err = TranspileError::MissingValue(MissingValue {
expression: ident.span.clone(), expression: ident.span.clone(),
}); });
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));
Err(err) Err(err)
}, }
|val| { }
let cmd = val.to_string_no_macro().map_or_else(
|| Command::UsesMacro(val.to_macro_string().into()),
Command::Raw,
);
Ok(vec![cmd])
},
),
Some(_) => { Some(_) => {
let err = TranspileError::UnexpectedExpression(UnexpectedExpression(Box::new( let err = TranspileError::UnexpectedExpression(UnexpectedExpression(Box::new(
Expression::Primary(expression.clone()), Expression::Primary(expression.clone()),
@ -647,12 +665,26 @@ impl Transpiler {
Primary::StringLiteral(string) => { Primary::StringLiteral(string) => {
Ok(vec![Command::Raw(string.str_content().to_string())]) Ok(vec![Command::Raw(string.str_content().to_string())])
} }
Primary::TemplateStringLiteral(string) => Ok(vec![Command::UsesMacro( Primary::TemplateStringLiteral(string) => {
string.to_macro_string(scope, handler)?.into(), let (macro_string, prepare_variables) = string
)]), .to_macro_string(Some(self), scope, handler)?
.into_sb();
self.transpile_commands_with_variable_macros(
vec![Command::UsesMacro(macro_string)],
prepare_variables,
handler,
)
}
Primary::Lua(code) => match code.eval_comptime(scope, handler)? { Primary::Lua(code) => match code.eval_comptime(scope, handler)? {
Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), Ok(ComptimeValue::MacroString(cmd)) => {
let (macro_string, prepare_variables) = cmd.into_sb();
self.transpile_commands_with_variable_macros(
vec![Command::UsesMacro(macro_string)],
prepare_variables,
handler,
)
}
Ok(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => { Ok(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ExpectedType::String, expected_type: ExpectedType::String,
@ -676,7 +708,14 @@ impl Transpiler {
} }
Expression::Binary(bin) => match bin.comptime_eval(scope, handler) { Expression::Binary(bin) => match bin.comptime_eval(scope, handler) {
Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), Ok(ComptimeValue::MacroString(cmd)) => {
let (macro_string, prepare_variables) = cmd.into_sb();
self.transpile_commands_with_variable_macros(
vec![Command::UsesMacro(macro_string)],
prepare_variables,
handler,
)
}
Ok(_) => { Ok(_) => {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: bin.span(), expression: bin.span(),
@ -723,47 +762,67 @@ impl Transpiler {
TranspiledFunctionArguments::Static(arguments, mut setup_cmds) => { TranspiledFunctionArguments::Static(arguments, mut setup_cmds) => {
use std::fmt::Write; use std::fmt::Write;
let cmd = if arguments.is_empty() { let cmds = if arguments.is_empty() {
Command::Raw(function_call) vec![Command::Raw(function_call)]
} else { } else {
let arguments_iter = arguments.iter().map(|(ident, v)| match v { let arguments_iter =
MacroString::String(s) => MacroString::String(format!( arguments
r#"{macro_name}:"{escaped}""#, .into_iter()
macro_name = crate::util::identifier_to_macro(ident), .map(|(macro_name, value)| match value {
escaped = crate::util::escape_str(s) MacroString::String(s) => MacroString::String(format!(
)), r#"{macro_name}:"{escaped}""#,
MacroString::MacroString(parts) => MacroString::MacroString( escaped = crate::util::escape_str(&s)
std::iter::once(MacroStringPart::String(format!( )),
r#"{macro_name}:""#, MacroString::MacroString {
macro_name = crate::util::identifier_to_macro(ident) parts,
))) prepare_variables: preparation_cmds,
.chain(parts.clone().into_iter().map(|part| match part { } => MacroString::MacroString {
MacroStringPart::String(s) => MacroStringPart::String( parts: std::iter::once(MacroStringPart::String(format!(
crate::util::escape_str(&s).to_string(), r#"{macro_name}:""#,
), )))
macro_usage @ MacroStringPart::MacroUsage(_) => macro_usage, .chain(parts.into_iter().map(|part| match part {
})) MacroStringPart::String(s) => MacroStringPart::String(
.chain(std::iter::once(MacroStringPart::String('"'.to_string()))) crate::util::escape_str(&s).to_string(),
.collect(), ),
), MacroStringPart::MacroUsage(_) => part,
}); }))
.chain(std::iter::once(MacroStringPart::String(
'"'.to_string(),
)))
.collect(),
prepare_variables: preparation_cmds,
},
});
let arguments = super::util::join_macro_strings(arguments_iter); let arguments = super::util::join_macro_strings(arguments_iter);
match arguments { match arguments {
MacroString::String(arguments) => { MacroString::String(arguments) => {
write!(function_call, " {{{arguments}}}").unwrap(); write!(function_call, " {{{arguments}}}").unwrap();
Command::Raw(function_call) vec![Command::Raw(function_call)]
} }
MacroString::MacroString(mut parts) => { MacroString::MacroString {
mut parts,
prepare_variables,
} => {
function_call.push_str(" {"); function_call.push_str(" {");
parts.insert(0, MacroStringPart::String(function_call)); parts.insert(0, MacroStringPart::String(function_call));
parts.push(MacroStringPart::String('}'.to_string())); parts.push(MacroStringPart::String('}'.to_string()));
Command::UsesMacro(MacroString::MacroString(parts).into())
let (macro_string, prepare_variables) = MacroString::MacroString {
parts,
prepare_variables,
}
.into_sb();
self.transpile_commands_with_variable_macros(
vec![Command::UsesMacro(macro_string)],
prepare_variables,
handler,
)?
} }
} }
}; };
setup_cmds.push(cmd); setup_cmds.extend(cmds);
Ok(setup_cmds) Ok(setup_cmds)
} }
@ -963,6 +1022,7 @@ impl Transpiler {
} }
} }
#[expect(clippy::too_many_lines)]
fn combine_execute_head_tail( fn combine_execute_head_tail(
&mut self, &mut self,
head: &ExecuteBlockHead, head: &ExecuteBlockHead,
@ -991,84 +1051,158 @@ impl Transpiler {
} }
} }
ExecuteBlockHead::As(r#as) => { ExecuteBlockHead::As(r#as) => {
let selector = r#as.as_selector().to_macro_string(scope, handler)?; let selector = r#as
tail.map(|(pre_cmds, tail)| { .as_selector()
(pre_cmds, Execute::As(selector.into(), Box::new(tail))) .to_macro_string(Some(self), scope, handler)?;
}) let (macro_string, prepare_variables) = selector.into_sb();
tail.map(|(pre_cmds, tail)| (pre_cmds, Execute::As(macro_string, Box::new(tail))))
} }
ExecuteBlockHead::At(at) => { ExecuteBlockHead::At(at) => {
let selector = at.at_selector().to_macro_string(scope, handler)?; let selector = at
tail.map(|(pre_cmds, tail)| { .at_selector()
(pre_cmds, Execute::At(selector.into(), Box::new(tail))) .to_macro_string(Some(self), scope, handler)?;
}) let (macro_string, prepare_variables) = selector.into_sb();
tail.map(|(pre_cmds, tail)| (pre_cmds, Execute::At(macro_string, Box::new(tail))))
} }
ExecuteBlockHead::Align(align) => { ExecuteBlockHead::Align(align) => {
let align = align.align_selector().to_macro_string(scope, handler)?; let align = align
.align_selector()
.to_macro_string(Some(self), scope, handler)?;
let (macro_string, prepare_variables) = align.into_sb();
tail.map(|(pre_cmds, tail)| { tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::Align(align.into(), Box::new(tail))) (pre_cmds, Execute::Align(macro_string, Box::new(tail)))
}) })
} }
ExecuteBlockHead::Anchored(anchored) => { ExecuteBlockHead::Anchored(anchored) => {
let anchor = anchored let anchor =
.anchored_selector() anchored
.to_macro_string(scope, handler)?; .anchored_selector()
.to_macro_string(Some(self), scope, handler)?;
let (macro_string, prepare_variables) = anchor.into_sb();
tail.map(|(pre_cmds, tail)| { tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::Anchored(anchor.into(), Box::new(tail))) (pre_cmds, Execute::Anchored(macro_string, Box::new(tail)))
}) })
} }
ExecuteBlockHead::In(r#in) => { ExecuteBlockHead::In(r#in) => {
let dimension = r#in.in_selector().to_macro_string(scope, handler)?; let dimension = r#in
tail.map(|(pre_cmds, tail)| { .in_selector()
(pre_cmds, Execute::In(dimension.into(), Box::new(tail))) .to_macro_string(Some(self), scope, handler)?;
}) let (macro_string, prepare_variables) = dimension.into_sb();
tail.map(|(pre_cmds, tail)| (pre_cmds, Execute::In(macro_string, Box::new(tail))))
} }
ExecuteBlockHead::Positioned(positioned) => { ExecuteBlockHead::Positioned(positioned) => {
let position = positioned let position =
.positioned_selector() positioned
.to_macro_string(scope, handler)?; .positioned_selector()
.to_macro_string(Some(self), scope, handler)?;
let (macro_string, prepare_variables) = position.into_sb();
tail.map(|(pre_cmds, tail)| { tail.map(|(pre_cmds, tail)| {
( (pre_cmds, Execute::Positioned(macro_string, Box::new(tail)))
pre_cmds,
Execute::Positioned(position.into(), Box::new(tail)),
)
}) })
} }
ExecuteBlockHead::Rotated(rotated) => { ExecuteBlockHead::Rotated(rotated) => {
let rotation = rotated.rotated_selector().to_macro_string(scope, handler)?; let rotation =
rotated
.rotated_selector()
.to_macro_string(Some(self), scope, handler)?;
let (macro_string, prepare_variables) = rotation.into_sb();
tail.map(|(pre_cmds, tail)| { tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::Rotated(rotation.into(), Box::new(tail))) (pre_cmds, Execute::Rotated(macro_string, Box::new(tail)))
}) })
} }
ExecuteBlockHead::Facing(facing) => { ExecuteBlockHead::Facing(facing) => {
let facing = facing.facing_selector().to_macro_string(scope, handler)?; let facing =
facing
.facing_selector()
.to_macro_string(Some(self), scope, handler)?;
let (macro_string, prepare_variables) = facing.into_sb();
tail.map(|(pre_cmds, tail)| { tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::Facing(facing.into(), Box::new(tail))) (pre_cmds, Execute::Facing(macro_string, Box::new(tail)))
}) })
} }
ExecuteBlockHead::AsAt(as_at) => { ExecuteBlockHead::AsAt(as_at) => {
let selector = as_at.asat_selector().to_macro_string(scope, handler)?; let selector = as_at
tail.map(|(pre_cmds, tail)| { .asat_selector()
(pre_cmds, Execute::AsAt(selector.into(), Box::new(tail))) .to_macro_string(Some(self), scope, handler)?;
}) let (macro_string, prepare_variables) = selector.into_sb();
tail.map(|(pre_cmds, tail)| (pre_cmds, Execute::AsAt(macro_string, Box::new(tail))))
} }
ExecuteBlockHead::On(on) => { ExecuteBlockHead::On(on) => {
let dimension = on.on_selector().to_macro_string(scope, handler)?; let dimension = on
tail.map(|(pre_cmds, tail)| { .on_selector()
(pre_cmds, Execute::On(dimension.into(), Box::new(tail))) .to_macro_string(Some(self), scope, handler)?;
}) let (macro_string, prepare_variables) = dimension.into_sb();
tail.map(|(pre_cmds, tail)| (pre_cmds, Execute::On(macro_string, Box::new(tail))))
} }
ExecuteBlockHead::Store(store) => { ExecuteBlockHead::Store(store) => {
let store = store.store_selector().to_macro_string(scope, handler)?; let store = store
.store_selector()
.to_macro_string(Some(self), scope, handler)?;
let (macro_string, prepare_variables) = store.into_sb();
tail.map(|(pre_cmds, tail)| { tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::Store(store.into(), Box::new(tail))) (pre_cmds, Execute::Store(macro_string, Box::new(tail)))
}) })
} }
ExecuteBlockHead::Summon(summon) => { ExecuteBlockHead::Summon(summon) => {
let entity = summon.summon_selector().to_macro_string(scope, handler)?; let entity =
summon
.summon_selector()
.to_macro_string(Some(self), scope, handler)?;
let (macro_string, prepare_variables) = entity.into_sb();
tail.map(|(pre_cmds, tail)| { tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::Summon(entity.into(), Box::new(tail))) (pre_cmds, Execute::Summon(macro_string, Box::new(tail)))
}) })
} }
}) })
} }
pub(crate) fn transpile_commands_with_variable_macros(
&mut self,
cmds: Vec<Command>,
prepare_variables: BTreeMap<String, (DataLocation, Vec<Command>, Span)>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
let storage_name = {
let temp = self.get_temp_count(1);
let hash = chksum_md5::hash(temp.to_le_bytes());
format!("shulkerscript:arguments_{hash}")
};
let (macro_names, mut prepare_cmds, move_cmds) = prepare_variables.into_iter().try_fold(
(HashSet::new(), Vec::new(), Vec::new()),
|(mut vars, mut prepare_cmds, mut move_cmds),
(macro_name, (data_location, var_cmds, span))| {
vars.insert(macro_name.clone());
prepare_cmds.extend(var_cmds);
let cur_move_cmds = self.move_data(
&data_location,
&DataLocation::Storage {
storage_name: storage_name.clone(),
path: macro_name,
r#type: data_location.storage_type(),
},
&span,
handler,
)?;
move_cmds.extend(cur_move_cmds);
TranspileResult::Ok((vars, prepare_cmds, move_cmds))
},
)?;
prepare_cmds.extend(move_cmds);
if prepare_cmds.is_empty() && macro_names.is_empty() {
Ok(cmds)
} else {
prepare_cmds.push(Command::Group(
Group::new(cmds)
.always_create_function(true)
.block_pass_macros(macro_names)
.data_storage_name(storage_name),
));
Ok(prepare_cmds)
}
}
} }

View File

@ -1,34 +1,48 @@
//! Utility methods for transpiling //! Utility methods for transpiling
use std::{fmt::Display, str::FromStr}; #[cfg(feature = "shulkerbox")]
use std::{collections::BTreeMap, sync::Arc};
#[cfg(feature = "shulkerbox")]
use shulkerbox::prelude::Command;
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use crate::{ use crate::{
base::{self, source_file::SourceElement as _, Handler}, base::{
self,
source_file::{SourceElement as _, Span},
Handler,
},
syntax::syntax_tree::{ syntax::syntax_tree::{
expression::{Expression, Primary, TemplateStringLiteral, TemplateStringLiteralPart}, expression::{Expression, Primary, TemplateStringLiteral, TemplateStringLiteralPart},
AnyStringLiteral, AnyStringLiteral,
}, },
transpile::{ transpile::{
error::{TranspileError, UnknownIdentifier}, error::{TranspileError, UnknownIdentifier},
expression::ComptimeValue, expression::{ComptimeValue, DataLocation},
Scope, TranspileResult, VariableData, Scope, TranspileResult, Transpiler, VariableData,
}, },
util::identifier_to_macro,
}; };
#[cfg(feature = "shulkerbox")]
use std::sync::Arc;
/// String that can contain macros /// String that can contain macros
#[cfg(feature = "shulkerbox")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MacroString { pub enum MacroString {
/// A normal string /// A normal string
String(String), String(String),
/// A string containing expressions /// A string containing expressions
MacroString(Vec<MacroStringPart>), MacroString {
/// Parts that make up the macro string
parts: Vec<MacroStringPart>,
/// Variables that need special preparation before using the macro string
prepare_variables: BTreeMap<String, (DataLocation, Vec<Command>, Span)>,
},
} }
/// Part of a [`MacroString`] /// Part of a [`MacroString`]
#[cfg(feature = "shulkerbox")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MacroStringPart { pub enum MacroStringPart {
@ -38,11 +52,12 @@ pub enum MacroStringPart {
MacroUsage(String), MacroUsage(String),
} }
impl Display for MacroString { #[cfg(feature = "shulkerbox")]
impl std::fmt::Display for MacroString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::String(s) => s.fmt(f), Self::String(s) => s.fmt(f),
Self::MacroString(parts) => { Self::MacroString { parts, .. } => {
for part in parts { for part in parts {
match part { match part {
MacroStringPart::String(s) => s.fmt(f)?, MacroStringPart::String(s) => s.fmt(f)?,
@ -55,13 +70,14 @@ impl Display for MacroString {
} }
} }
#[cfg(feature = "shulkerbox")]
impl MacroString { impl MacroString {
/// Check if the macro string contains any macros /// Check if the macro string contains any macros
#[must_use] #[must_use]
pub fn contains_macros(&self) -> bool { pub fn contains_macros(&self) -> bool {
match self { match self {
Self::String(_) => false, Self::String(_) => false,
Self::MacroString(parts) => parts Self::MacroString { parts, .. } => parts
.iter() .iter()
.any(|p| matches!(p, MacroStringPart::MacroUsage(_))), .any(|p| matches!(p, MacroStringPart::MacroUsage(_))),
} }
@ -71,11 +87,23 @@ impl MacroString {
/// ///
/// # Errors /// # Errors
/// - If the macro string contains macros /// - If the macro string contains macros
pub fn as_str(&self) -> Result<std::borrow::Cow<'_, str>, &[MacroStringPart]> { #[expect(clippy::type_complexity)]
pub fn as_str(
&self,
) -> Result<
std::borrow::Cow<'_, str>,
(
&[MacroStringPart],
&BTreeMap<String, (DataLocation, Vec<Command>, Span)>,
),
> {
match self { match self {
Self::String(s) => Ok(std::borrow::Cow::Borrowed(s)), Self::String(s) => Ok(std::borrow::Cow::Borrowed(s)),
Self::MacroString(parts) if self.contains_macros() => Err(parts), Self::MacroString {
Self::MacroString(parts) => Ok(std::borrow::Cow::Owned( parts,
prepare_variables,
} if self.contains_macros() => Err((parts, prepare_variables)),
Self::MacroString { parts, .. } => Ok(std::borrow::Cow::Owned(
parts parts
.iter() .iter()
.map(|p| match p { .map(|p| match p {
@ -126,6 +154,7 @@ where
} }
/// Join multiple macro strings into one /// Join multiple macro strings into one
#[cfg(feature = "shulkerbox")]
#[must_use] #[must_use]
pub fn join_macro_strings<I>(strings: I) -> MacroString pub fn join_macro_strings<I>(strings: I) -> MacroString
where where
@ -139,20 +168,39 @@ where
s.push_str(&cur); s.push_str(&cur);
MacroString::String(s) MacroString::String(s)
} }
MacroString::MacroString(cur) => { MacroString::MacroString {
parts: cur,
prepare_variables: preparation_cmds,
} => {
let mut parts = vec![MacroStringPart::String(s)]; let mut parts = vec![MacroStringPart::String(s)];
parts.extend(cur); parts.extend(cur);
MacroString::MacroString(parts) MacroString::MacroString {
parts,
prepare_variables: preparation_cmds,
}
} }
}, },
MacroString::MacroString(mut parts) => match cur { MacroString::MacroString {
mut parts,
prepare_variables: mut preparation_cmds,
} => match cur {
MacroString::String(cur) => { MacroString::String(cur) => {
parts.push(MacroStringPart::String(cur)); parts.push(MacroStringPart::String(cur));
MacroString::MacroString(parts) MacroString::MacroString {
parts,
prepare_variables: preparation_cmds,
}
} }
MacroString::MacroString(cur) => { MacroString::MacroString {
parts: cur,
prepare_variables: cur_preparation_cmds,
} => {
parts.extend(cur); parts.extend(cur);
MacroString::MacroString(parts) preparation_cmds.extend(cur_preparation_cmds);
MacroString::MacroString {
parts,
prepare_variables: preparation_cmds,
}
} }
}, },
}) })
@ -173,7 +221,8 @@ pub fn add_to_entity_selector(selector: impl Into<String>, additional: &str) ->
} }
} }
impl FromStr for MacroString { #[cfg(feature = "shulkerbox")]
impl std::str::FromStr for MacroString {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -239,7 +288,10 @@ impl FromStr for MacroString {
.iter() .iter()
.any(|p| matches!(p, MacroStringPart::MacroUsage(_))) .any(|p| matches!(p, MacroStringPart::MacroUsage(_)))
{ {
Ok(Self::MacroString(parts)) Ok(Self::MacroString {
parts,
prepare_variables: BTreeMap::new(),
})
} else { } else {
Ok(Self::String(s.to_string())) Ok(Self::String(s.to_string()))
} }
@ -249,6 +301,7 @@ impl FromStr for MacroString {
} }
} }
#[cfg(feature = "shulkerbox")]
impl<S> From<S> for MacroString impl<S> From<S> for MacroString
where where
S: Into<String>, S: Into<String>,
@ -266,12 +319,15 @@ impl AnyStringLiteral {
/// - If an identifier in a template string is not found in the scope /// - If an identifier in a template string is not found in the scope
pub fn to_macro_string( pub fn to_macro_string(
&self, &self,
transpiler: Option<&mut Transpiler>,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<MacroString> { ) -> TranspileResult<MacroString> {
match self { match self {
Self::StringLiteral(literal) => Ok(MacroString::from(literal.str_content().as_ref())), Self::StringLiteral(literal) => Ok(MacroString::from(literal.str_content().as_ref())),
Self::TemplateStringLiteral(literal) => literal.to_macro_string(scope, handler), Self::TemplateStringLiteral(literal) => {
literal.to_macro_string(transpiler, scope, handler)
}
} }
} }
} }
@ -282,70 +338,215 @@ impl TemplateStringLiteral {
/// ///
/// # Errors /// # Errors
/// - If an identifier in a template string is not found in the scope /// - If an identifier in a template string is not found in the scope
#[expect(clippy::too_many_lines)]
pub fn to_macro_string( pub fn to_macro_string(
&self, &self,
mut transpiler: Option<&mut Transpiler>,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<MacroString> { ) -> TranspileResult<MacroString> {
if self if self.contains_expression() {
.parts() let mut prepare_variables = BTreeMap::new();
.iter()
.any(|p| matches!(p, TemplateStringLiteralPart::Expression { .. }))
{
let macro_string = MacroString::MacroString(
self.parts()
.iter()
.map(|part| match part {
TemplateStringLiteralPart::Text(text) => Ok(MacroStringPart::String(
crate::util::unescape_template_string(text.span.str()).into_owned(),
)),
TemplateStringLiteralPart::Expression { expression, .. } => {
match expression.as_ref() {
Expression::Primary(Primary::Identifier(identifier)) =>
{
#[expect(clippy::option_if_let_else)]
if let Some(var_data) =
scope.get_variable(identifier.span.str())
{
match var_data.as_ref() {
VariableData::MacroParameter { macro_name, .. } => Ok(
MacroStringPart::MacroUsage(macro_name.to_owned()),
),
VariableData::ComptimeValue { value, .. } => {
let value = value.read().unwrap().as_ref().map_or_else(
|| "null".into(),
ComptimeValue::to_macro_string,
);
match value.as_str() { let parts = self
Ok(s) => Ok(MacroStringPart::String(s.into_owned())), .parts()
Err(_) => todo!("comptime value resulting in macro string with macros") .iter()
} .map(|part| match part {
TemplateStringLiteralPart::Text(text) => Ok(vec![MacroStringPart::String(
crate::util::unescape_template_string(text.span.str()).into_owned(),
)]),
TemplateStringLiteralPart::Expression { expression, .. } => match expression
.as_ref()
{
Expression::Primary(Primary::Identifier(identifier)) => {
#[expect(clippy::option_if_let_else)]
if let Some(var_data) = scope.get_variable(identifier.span.str()) {
match var_data.as_ref() {
VariableData::MacroParameter { macro_name, .. } => {
Ok(vec![MacroStringPart::MacroUsage(macro_name.to_owned())])
}
VariableData::ComptimeValue { value, .. } => {
let value = value.read().unwrap().as_ref().map_or_else(
|| "null".into(),
ComptimeValue::to_macro_string,
);
match value.as_str() {
Ok(s) => {
Ok(vec![MacroStringPart::String(s.into_owned())])
}
Err((inner_parts, inner_prepare_variables)) => {
prepare_variables
.extend(inner_prepare_variables.to_owned());
Ok(inner_parts.to_vec())
} }
_ => todo!("other identifiers in template strings"),
} }
} else { }
let err = VariableData::BooleanStorage { storage_name, path } => {
TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(identifier.span(), scope)); use crate::transpile::expression::StorageType;
let macro_name = if let Some(transpiler) = &mut transpiler {
let temp_count = transpiler.get_temp_count(1);
format!(
"shu_temp_{hash}",
hash = chksum_md5::hash(temp_count.to_le_bytes())
)
} else {
identifier_to_macro(identifier.span.str()).into_owned()
};
let data_location = DataLocation::Storage {
storage_name: storage_name.to_owned(),
path: path.to_owned(),
r#type: StorageType::Boolean,
};
prepare_variables.insert(
macro_name.clone(),
(data_location, Vec::new(), expression.span()),
);
Ok(vec![MacroStringPart::MacroUsage(macro_name)])
}
VariableData::ScoreboardValue { objective, target } => {
let macro_name = if let Some(transpiler) = &mut transpiler {
let temp_count = transpiler.get_temp_count(1);
format!(
"shu_temp_{hash}",
hash = chksum_md5::hash(temp_count.to_le_bytes())
)
} else {
identifier_to_macro(identifier.span.str()).into_owned()
};
let data_location = DataLocation::ScoreboardValue {
objective: objective.to_owned(),
target: target.to_owned(),
};
prepare_variables.insert(
macro_name.clone(),
(data_location, Vec::new(), expression.span()),
);
Ok(vec![MacroStringPart::MacroUsage(macro_name)])
}
_ => {
use crate::semantic::error::UnexpectedExpression;
let err = TranspileError::UnexpectedExpression(
UnexpectedExpression(expression.to_owned()),
);
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));
Err(err) Err(err)
} }
} }
Expression::Primary(Primary::MemberAccess(member_access)) => { } else {
let value = member_access.parent().comptime_member_access(member_access, scope, handler).inspect_err(|err| { let err = TranspileError::UnknownIdentifier(
handler.receive(Box::new(TranspileError::NotComptime(err.clone()))); UnknownIdentifier::from_scope(identifier.span(), scope),
})?.to_macro_string(); );
handler.receive(Box::new(err.clone()));
value.as_str().map_or_else(|_| todo!("comptime value resulting in macro string with macros"), |s| Ok(MacroStringPart::String(s.into_owned()))) Err(err)
}
_ => todo!("other expressions in template strings"),
} }
} }
}) Expression::Primary(Primary::MemberAccess(member_access)) => {
.collect::<TranspileResult<Vec<MacroStringPart>>>()?, let value = member_access
); .parent()
.comptime_member_access(member_access, scope, handler)
.inspect_err(|err| {
handler.receive(Box::new(TranspileError::NotComptime(
err.clone(),
)));
})?
.to_macro_string();
Ok(macro_string) match value.as_str() {
Ok(s) => Ok(vec![MacroStringPart::String(s.into_owned())]),
Err((inner_parts, inner_prepare_variables)) => {
prepare_variables.extend(inner_prepare_variables.to_owned());
Ok(inner_parts.to_vec())
}
}
}
_ => {
if let Some(transpiler) = &mut transpiler {
use crate::transpile::expression::{StorageType, ValueType};
let temp_count = transpiler.get_temp_count(1);
let macro_name = format!(
"shu_temp_{hash}",
hash = chksum_md5::hash(temp_count.to_le_bytes())
);
let data_location =
if expression.can_yield_type(ValueType::Integer, scope) {
let (scoreboard_name, [scoreboard_target]) =
transpiler.get_temp_scoreboard_locations_array();
DataLocation::ScoreboardValue {
objective: scoreboard_name,
target: scoreboard_target,
}
} else if expression.can_yield_type(ValueType::Boolean, scope) {
let (storage_name, [storage_path]) =
transpiler.get_temp_storage_locations_array();
DataLocation::Storage {
storage_name,
path: storage_path,
r#type: StorageType::Boolean,
}
} else if expression.can_yield_type(ValueType::String, scope) {
let (storage_name, [storage_path]) =
transpiler.get_temp_storage_locations_array();
DataLocation::Storage {
storage_name,
path: storage_path,
r#type: StorageType::String,
}
} else {
use crate::semantic::error::UnexpectedExpression;
let err = TranspileError::UnexpectedExpression(
UnexpectedExpression(expression.to_owned()),
);
handler.receive(Box::new(err.clone()));
return Err(err);
};
let commands = transpiler.transpile_expression(
expression,
&data_location,
scope,
handler,
)?;
prepare_variables.insert(
macro_name.clone(),
(data_location, commands, expression.span()),
);
Ok(vec![MacroStringPart::MacroUsage(macro_name)])
} else {
use crate::semantic::error::UnexpectedExpression;
let err = TranspileError::UnexpectedExpression(
UnexpectedExpression(expression.to_owned()),
);
handler.receive(Box::new(err.clone()));
Err(err)
}
}
},
})
.flat_map(|res| match res {
Ok(parts) => parts.into_iter().map(Ok).collect(),
Err(err) => vec![Err(err)],
})
.collect::<TranspileResult<Vec<MacroStringPart>>>()?;
Ok(MacroString::MacroString {
parts,
prepare_variables,
})
} else { } else {
Ok(MacroString::String( Ok(MacroString::String(
self.as_str(scope, handler)?.into_owned(), self.as_str(scope, handler)?.into_owned(),
@ -354,46 +555,62 @@ impl TemplateStringLiteral {
} }
} }
#[cfg(test)] #[cfg(all(test, feature = "shulkerbox"))]
mod tests { mod tests {
use std::str::FromStr as _;
use assert_struct::assert_struct;
use super::*; use super::*;
#[test] #[test]
fn test_parse_macro_string() { fn test_parse_macro_string() {
assert_eq!( assert_struct!(
MacroString::from_str("Hello, $(world)!").unwrap(), MacroString::from_str("Hello, $(world)!").unwrap(),
MacroString::MacroString(vec![ MacroString::MacroString {
MacroStringPart::String("Hello, ".to_string()), parts: vec![
MacroStringPart::MacroUsage("world".to_string()), MacroStringPart::String("Hello, ".to_string()),
MacroStringPart::String("!".to_string()) MacroStringPart::MacroUsage("world".to_string()),
]) MacroStringPart::String("!".to_string())
],
prepare_variables.is_empty(): true,
}
); );
assert_eq!( assert_struct!(
MacroString::from_str("Hello, $(world)! $(world").unwrap(), MacroString::from_str("Hello, $(world)! $(world").unwrap(),
MacroString::MacroString(vec![ MacroString::MacroString {
MacroStringPart::String("Hello, ".to_string()), parts: vec![
MacroStringPart::MacroUsage("world".to_string()), MacroStringPart::String("Hello, ".to_string()),
MacroStringPart::String("! $(world".to_string()), MacroStringPart::MacroUsage("world".to_string()),
]) MacroStringPart::String("! $(world".to_string()),
],
prepare_variables.is_empty(): true,
}
); );
assert_eq!( assert_struct!(
MacroString::from_str("Hello $(a) from $(b) and $(c)").unwrap(), MacroString::from_str("Hello $(a) from $(b) and $(c)").unwrap(),
MacroString::MacroString(vec![ MacroString::MacroString {
MacroStringPart::String("Hello ".to_string()), parts: vec![
MacroStringPart::MacroUsage("a".to_string()), MacroStringPart::String("Hello ".to_string()),
MacroStringPart::String(" from ".to_string()), MacroStringPart::MacroUsage("a".to_string()),
MacroStringPart::MacroUsage("b".to_string()), MacroStringPart::String(" from ".to_string()),
MacroStringPart::String(" and ".to_string()), MacroStringPart::MacroUsage("b".to_string()),
MacroStringPart::MacroUsage("c".to_string()), MacroStringPart::String(" and ".to_string()),
]) MacroStringPart::MacroUsage("c".to_string()),
],
prepare_variables.is_empty(): true,
}
); );
assert_eq!( assert_struct!(
MacroString::from_str("Hello, $(world! $(world)!").unwrap(), MacroString::from_str("Hello, $(world! $(world)!").unwrap(),
MacroString::MacroString(vec![ MacroString::MacroString {
MacroStringPart::String("Hello, $(world! ".to_string()), parts: vec![
MacroStringPart::MacroUsage("world".to_string()), MacroStringPart::String("Hello, $(world! ".to_string()),
MacroStringPart::String("!".to_string()), MacroStringPart::MacroUsage("world".to_string()),
]) MacroStringPart::String("!".to_string()),
],
prepare_variables.is_empty(): true,
}
); );
} }
} }

View File

@ -32,12 +32,12 @@ use super::{
AssignmentError, IllegalAnnotationContent, IllegalIndexing, IllegalIndexingReason, AssignmentError, IllegalAnnotationContent, IllegalIndexing, IllegalIndexingReason,
MismatchedTypes, NotComptime, MismatchedTypes, NotComptime,
}, },
expression::{ComptimeValue, DataLocation, ExpectedType, StorageType}, expression::{DataLocation, ExpectedType, StorageType},
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult, FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult,
}; };
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use super::{internal_functions::InternalFunction, Transpiler}; use super::{expression::ComptimeValue, internal_functions::InternalFunction, Transpiler};
/// Stores the data required to access a variable. /// Stores the data required to access a variable.
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
@ -409,6 +409,8 @@ impl Transpiler {
handler, handler,
)?; )?;
if is_global { if is_global {
use shulkerbox::datapack::Group;
let (temp_objective, [temp_target]) = self.get_temp_scoreboard_locations_array(); let (temp_objective, [temp_target]) = self.get_temp_scoreboard_locations_array();
let test_cmd = match declaration.variable_type().keyword { let test_cmd = match declaration.variable_type().keyword {
KeywordKind::Int => { KeywordKind::Int => {
@ -425,7 +427,7 @@ impl Transpiler {
Condition::Atom( Condition::Atom(
format!("score {temp_target} {temp_objective} matches 0").into(), format!("score {temp_target} {temp_objective} matches 0").into(),
), ),
Box::new(Execute::Run(Box::new(Command::Group(cmds)))), Box::new(Execute::Run(Box::new(Command::Group(Group::new(cmds))))),
None, None,
)); ));
Ok(vec![test_exists_cmd, cond_cmd]) Ok(vec![test_exists_cmd, cond_cmd])
@ -634,9 +636,11 @@ impl Transpiler {
} else { } else {
let index_span = match destination { let index_span = match destination {
TranspileAssignmentTarget::Indexed(_, expr) => expr.span(), TranspileAssignmentTarget::Indexed(_, expr) => expr.span(),
TranspileAssignmentTarget::Identifier(_) => unreachable!( TranspileAssignmentTarget::Identifier(_) => {
"indexing value must be present (checked before)" unreachable!(
), "indexing value must be present (checked before)"
)
}
}; };
let err = TranspileError::IllegalIndexing(IllegalIndexing { let err = TranspileError::IllegalIndexing(IllegalIndexing {
expression: index_span, expression: index_span,
@ -688,9 +692,11 @@ impl Transpiler {
} else { } else {
let index_span = match destination { let index_span = match destination {
TranspileAssignmentTarget::Indexed(_, expr) => expr.span(), TranspileAssignmentTarget::Indexed(_, expr) => expr.span(),
TranspileAssignmentTarget::Identifier(_) => unreachable!( TranspileAssignmentTarget::Identifier(_) => {
"indexing value must be present (checked before)" unreachable!(
), "indexing value must be present (checked before)"
)
}
}; };
let err = TranspileError::IllegalIndexing(IllegalIndexing { let err = TranspileError::IllegalIndexing(IllegalIndexing {
expression: index_span, expression: index_span,
@ -847,18 +853,20 @@ impl Transpiler {
// TODO: change invalid criteria if boolean // TODO: change invalid criteria if boolean
if !crate::util::is_valid_scoreboard_objective_name(&name_eval) { if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(), annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation must be a valid scoreboard objective name.".to_string() message: "Deobfuscate annotation must be a valid scoreboard objective name."
}); .to_string(),
});
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));
return Err(err); return Err(err);
} }
Ok(name_eval) Ok(name_eval)
} else { } else {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(), annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation could not have been evaluated at compile time.".to_string() message: "Deobfuscate annotation could not have been evaluated at compile time."
}); .to_string(),
});
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));
Err(err) Err(err)
} }
@ -950,34 +958,36 @@ impl Transpiler {
// TODO: change invalid criteria if boolean // TODO: change invalid criteria if boolean
if !crate::util::is_valid_scoreboard_objective_name(&name_eval) { if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(), annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'name' must be a valid scoreboard objective name.".to_string() message: "Deobfuscate annotation 'name' must be a valid scoreboard objective name.".to_string(),
}); });
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));
return Err(err); return Err(err);
} }
if !crate::util::is_valid_scoreboard_target(&target_eval) { if !crate::util::is_valid_scoreboard_target(&target_eval) {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(), annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'target' must be a valid scoreboard player name.".to_string() message: "Deobfuscate annotation 'target' must be a valid scoreboard player name.".to_string(),
}); });
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));
return Err(err); return Err(err);
} }
Ok((name_eval, target_eval)) Ok((name_eval, target_eval))
} else { } else {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(), annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'name' or 'target' could not have been evaluated at compile time.".to_string() message: "Deobfuscate annotation 'name' or 'target' could not have been evaluated at compile time.".to_string(),
}); });
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));
Err(err) Err(err)
} }
} else { } else {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(), annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'name' and 'target' must be compile time expressions.".to_string() message:
}); "Deobfuscate annotation 'name' and 'target' must be compile time expressions."
.to_string(),
});
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));
Err(err) Err(err)
} }
@ -1180,9 +1190,7 @@ impl Transpiler {
{ {
Ok(Vec::new()) Ok(Vec::new())
} else if matches!(target_type, StorageType::Boolean) { } else if matches!(target_type, StorageType::Boolean) {
let cmd = Command::Raw(format!( let cmd = Command::Raw(format!("data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}"));
"data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}"
));
Ok(vec![cmd]) Ok(vec![cmd])
} else { } else {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
@ -1194,6 +1202,37 @@ impl Transpiler {
} }
} }
}, },
StorageType::String => match to {
DataLocation::ScoreboardValue { .. } | DataLocation::Tag { .. } => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: expression.span(),
expected_type: to.value_type().into(),
});
handler.receive(Box::new(err.clone()));
Err(err)
}
DataLocation::Storage {
storage_name: to_storage_name,
path: to_path,
r#type,
} => {
if r#type == &StorageType::String {
if storage_name == to_storage_name && path == to_path {
Ok(Vec::new())
} else {
let cmd = Command::Raw(format!("data modify storage {to_storage_name} {to_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(Box::new(err.clone()));
Err(err)
}
}
},
}, },
DataLocation::ScoreboardValue { DataLocation::ScoreboardValue {
objective, objective,
@ -1206,9 +1245,7 @@ impl Transpiler {
if objective == target_objective && score_target == target_target { if objective == target_objective && score_target == target_target {
Ok(Vec::new()) Ok(Vec::new())
} else { } else {
let cmd = Command::Raw(format!( let cmd = Command::Raw(format!("scoreboard players operation {target_target} {target_objective} = {score_target} {objective}"));
"scoreboard players operation {target_target} {target_objective} = {score_target} {objective}"
));
Ok(vec![cmd]) Ok(vec![cmd])
} }
} }