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_json = { version = "1.0.138", optional = true }
# shulkerbox = { version = "0.1.0", default-features = false, optional = true }
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "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"
strum = { version = "0.27.0", features = ["derive"] }
thiserror = "2.0.11"
tracing = "0.1.41"
[dev-dependencies]
assert-struct = "0.2.0"
serde_json = "1.0.138"
[[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 {
/// 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(
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| {
["tick", "load", "uninstall"].contains(&a.assignment().identifier.span.str())
}) {
let err =
error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
span: incompatible.assignment().identifier.span.clone(),
reason:
"functions with the `tick`, `load` or `uninstall` annotation cannot have parameters"
.to_string(),
});
let err = error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
span: incompatible.assignment().identifier.span.clone(),
reason:
"functions with the `tick`, `load` or `uninstall` annotation cannot have parameters"
.to_string(),
});
handler.receive(err.clone());
return Err(err);
} else if parameters
@ -191,13 +190,12 @@ impl Function {
.iter()
.find(|a| a.assignment().identifier.span.str() == "deobfuscate")
{
let err =
error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
span: incompatible.assignment().identifier.span.clone(),
reason:
"functions with the `deobfuscate` annotation cannot have compile-time parameters"
.to_string(),
});
let err = error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
span: incompatible.assignment().identifier.span.clone(),
reason:
"functions with the `deobfuscate` annotation cannot have compile-time parameters"
.to_string(),
});
handler.receive(err.clone());
return Err(err);
}

View File

@ -415,6 +415,14 @@ impl TemplateStringLiteral {
pub fn parts(&self) -> &[TemplateStringLiteralPart] {
&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 {

View File

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

View File

@ -1,26 +1,37 @@
//! 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::{
base::{self, Handler},
base::{self, source_file::Span, Handler},
semantic::error::UnexpectedExpression,
syntax::syntax_tree::expression::{TemplateStringLiteral, TemplateStringLiteralPart},
transpile::{Scope, TranspileError, TranspileResult},
transpile::{expression::DataLocation, Scope, TranspileError, TranspileResult},
util,
};
use super::util::{MacroString, MacroStringPart};
impl From<MacroString> for ExtMacroString {
fn from(value: MacroString) -> Self {
match value {
MacroString::String(s) => Self::String(s),
MacroString::MacroString(parts) => {
Self::MacroString(parts.into_iter().map(ExtMacroStringPart::from).collect())
}
type ShulkerboxMacroStringMap = BTreeMap<String, (DataLocation, Vec<Command>, Span)>;
impl MacroString {
pub fn into_sb(self) -> (ExtMacroString, ShulkerboxMacroStringMap) {
match self {
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.")
}
Self::InvalidComptimeType { expected } => {
write!(
f,
"The expression can only be indexed with type {expected} that can be evaluated at compile time."
)
write!(f, "The expression can only be indexed with type {expected} that can be evaluated at compile time.")
}
Self::IndexOutOfBounds { index, length } => {
write!(

View File

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

View File

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

View File

@ -589,7 +589,9 @@ fn print_function(
let cmd = format!("tellraw {target} {print_args}");
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 {
Command::Raw(cmd)
};

View File

@ -7,10 +7,14 @@ use std::{
};
use itertools::Itertools;
use shulkerbox::datapack::{self, Command, Datapack, Execute};
use shulkerbox::datapack::{self, Command, Datapack, Execute, Group};
use crate::{
base::{self, source_file::SourceElement, Handler},
base::{
self,
source_file::{SourceElement, Span},
Handler,
},
semantic::error::UnexpectedExpression,
syntax::syntax_tree::{
declaration::{Declaration, FunctionVariableType, ImportItems},
@ -24,6 +28,7 @@ use crate::{
},
transpile::{
error::IllegalAnnotationContent,
expression::DataLocation,
util::{MacroString, MacroStringPart},
variables::FunctionVariableDataType,
},
@ -411,7 +416,7 @@ impl Transpiler {
if commands.is_empty() {
Ok(Vec::new())
} else {
Ok(vec![Command::Group(commands)])
Ok(vec![Command::Group(Group::new(commands))])
}
}
Statement::Semicolon(semi) => match semi.statement() {
@ -467,7 +472,8 @@ impl Transpiler {
.map(|val| val.to_macro_string());
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 {
match ret.expression() {
Expression::Primary(Primary::Prefix(prefix))
@ -478,7 +484,7 @@ impl Transpiler {
let cmd = if ret_cmds.len() == 1 {
ret_cmds.into_iter().next().unwrap()
} else {
Command::Group(ret_cmds)
Command::Group(Group::new(ret_cmds))
};
(Vec::new(), datapack::ReturnCommand::Command(Box::new(cmd)))
}
@ -487,7 +493,7 @@ impl Transpiler {
let cmd = if ret_cmds.len() == 1 {
ret_cmds.into_iter().next().unwrap()
} else {
Command::Group(ret_cmds)
Command::Group(Group::new(ret_cmds))
};
(Vec::new(), datapack::ReturnCommand::Command(Box::new(cmd)))
}
@ -513,7 +519,11 @@ impl Transpiler {
},
|val| {
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,
);
Ok((
@ -588,6 +598,7 @@ impl Transpiler {
Ok(cmds)
}
#[expect(clippy::too_many_lines)]
pub(super) fn transpile_run_expression(
&mut self,
expression: &Primary,
@ -600,22 +611,29 @@ impl Transpiler {
Some(VariableData::ComptimeValue {
value,
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 {
expression: ident.span.clone(),
});
handler.receive(Box::new(err.clone()));
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(_) => {
let err = TranspileError::UnexpectedExpression(UnexpectedExpression(Box::new(
Expression::Primary(expression.clone()),
@ -647,12 +665,26 @@ impl Transpiler {
Primary::StringLiteral(string) => {
Ok(vec![Command::Raw(string.str_content().to_string())])
}
Primary::TemplateStringLiteral(string) => Ok(vec![Command::UsesMacro(
string.to_macro_string(scope, handler)?.into(),
)]),
Primary::TemplateStringLiteral(string) => {
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)? {
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(_)) => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ExpectedType::String,
@ -676,7 +708,14 @@ impl Transpiler {
}
Expression::Binary(bin) => match bin.comptime_eval(scope, handler) {
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(_) => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: bin.span(),
@ -723,47 +762,67 @@ impl Transpiler {
TranspiledFunctionArguments::Static(arguments, mut setup_cmds) => {
use std::fmt::Write;
let cmd = if arguments.is_empty() {
Command::Raw(function_call)
let cmds = if arguments.is_empty() {
vec![Command::Raw(function_call)]
} else {
let arguments_iter = arguments.iter().map(|(ident, v)| match v {
MacroString::String(s) => MacroString::String(format!(
r#"{macro_name}:"{escaped}""#,
macro_name = crate::util::identifier_to_macro(ident),
escaped = crate::util::escape_str(s)
)),
MacroString::MacroString(parts) => MacroString::MacroString(
std::iter::once(MacroStringPart::String(format!(
r#"{macro_name}:""#,
macro_name = crate::util::identifier_to_macro(ident)
)))
.chain(parts.clone().into_iter().map(|part| match part {
MacroStringPart::String(s) => MacroStringPart::String(
crate::util::escape_str(&s).to_string(),
),
macro_usage @ MacroStringPart::MacroUsage(_) => macro_usage,
}))
.chain(std::iter::once(MacroStringPart::String('"'.to_string())))
.collect(),
),
});
let arguments_iter =
arguments
.into_iter()
.map(|(macro_name, value)| match value {
MacroString::String(s) => MacroString::String(format!(
r#"{macro_name}:"{escaped}""#,
escaped = crate::util::escape_str(&s)
)),
MacroString::MacroString {
parts,
prepare_variables: preparation_cmds,
} => MacroString::MacroString {
parts: std::iter::once(MacroStringPart::String(format!(
r#"{macro_name}:""#,
)))
.chain(parts.into_iter().map(|part| match part {
MacroStringPart::String(s) => MacroStringPart::String(
crate::util::escape_str(&s).to_string(),
),
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);
match arguments {
MacroString::String(arguments) => {
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(" {");
parts.insert(0, MacroStringPart::String(function_call));
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)
}
@ -963,6 +1022,7 @@ impl Transpiler {
}
}
#[expect(clippy::too_many_lines)]
fn combine_execute_head_tail(
&mut self,
head: &ExecuteBlockHead,
@ -991,84 +1051,158 @@ impl Transpiler {
}
}
ExecuteBlockHead::As(r#as) => {
let selector = r#as.as_selector().to_macro_string(scope, handler)?;
tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::As(selector.into(), Box::new(tail)))
})
let selector = r#as
.as_selector()
.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) => {
let selector = at.at_selector().to_macro_string(scope, handler)?;
tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::At(selector.into(), Box::new(tail)))
})
let selector = at
.at_selector()
.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) => {
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)| {
(pre_cmds, Execute::Align(align.into(), Box::new(tail)))
(pre_cmds, Execute::Align(macro_string, Box::new(tail)))
})
}
ExecuteBlockHead::Anchored(anchored) => {
let anchor = anchored
.anchored_selector()
.to_macro_string(scope, handler)?;
let anchor =
anchored
.anchored_selector()
.to_macro_string(Some(self), scope, handler)?;
let (macro_string, prepare_variables) = anchor.into_sb();
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) => {
let dimension = r#in.in_selector().to_macro_string(scope, handler)?;
tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::In(dimension.into(), Box::new(tail)))
})
let dimension = r#in
.in_selector()
.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) => {
let position = positioned
.positioned_selector()
.to_macro_string(scope, handler)?;
let position =
positioned
.positioned_selector()
.to_macro_string(Some(self), scope, handler)?;
let (macro_string, prepare_variables) = position.into_sb();
tail.map(|(pre_cmds, tail)| {
(
pre_cmds,
Execute::Positioned(position.into(), Box::new(tail)),
)
(pre_cmds, Execute::Positioned(macro_string, Box::new(tail)))
})
}
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)| {
(pre_cmds, Execute::Rotated(rotation.into(), Box::new(tail)))
(pre_cmds, Execute::Rotated(macro_string, Box::new(tail)))
})
}
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)| {
(pre_cmds, Execute::Facing(facing.into(), Box::new(tail)))
(pre_cmds, Execute::Facing(macro_string, Box::new(tail)))
})
}
ExecuteBlockHead::AsAt(as_at) => {
let selector = as_at.asat_selector().to_macro_string(scope, handler)?;
tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::AsAt(selector.into(), Box::new(tail)))
})
let selector = as_at
.asat_selector()
.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) => {
let dimension = on.on_selector().to_macro_string(scope, handler)?;
tail.map(|(pre_cmds, tail)| {
(pre_cmds, Execute::On(dimension.into(), Box::new(tail)))
})
let dimension = on
.on_selector()
.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) => {
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)| {
(pre_cmds, Execute::Store(store.into(), Box::new(tail)))
(pre_cmds, Execute::Store(macro_string, Box::new(tail)))
})
}
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)| {
(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
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")]
use crate::{
base::{self, source_file::SourceElement as _, Handler},
base::{
self,
source_file::{SourceElement as _, Span},
Handler,
},
syntax::syntax_tree::{
expression::{Expression, Primary, TemplateStringLiteral, TemplateStringLiteralPart},
AnyStringLiteral,
},
transpile::{
error::{TranspileError, UnknownIdentifier},
expression::ComptimeValue,
Scope, TranspileResult, VariableData,
expression::{ComptimeValue, DataLocation},
Scope, TranspileResult, Transpiler, VariableData,
},
util::identifier_to_macro,
};
#[cfg(feature = "shulkerbox")]
use std::sync::Arc;
/// String that can contain macros
#[cfg(feature = "shulkerbox")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MacroString {
/// A normal string
String(String),
/// 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`]
#[cfg(feature = "shulkerbox")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MacroStringPart {
@ -38,11 +52,12 @@ pub enum MacroStringPart {
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 {
match self {
Self::String(s) => s.fmt(f),
Self::MacroString(parts) => {
Self::MacroString { parts, .. } => {
for part in parts {
match part {
MacroStringPart::String(s) => s.fmt(f)?,
@ -55,13 +70,14 @@ impl Display for MacroString {
}
}
#[cfg(feature = "shulkerbox")]
impl MacroString {
/// Check if the macro string contains any macros
#[must_use]
pub fn contains_macros(&self) -> bool {
match self {
Self::String(_) => false,
Self::MacroString(parts) => parts
Self::MacroString { parts, .. } => parts
.iter()
.any(|p| matches!(p, MacroStringPart::MacroUsage(_))),
}
@ -71,11 +87,23 @@ impl MacroString {
///
/// # Errors
/// - 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 {
Self::String(s) => Ok(std::borrow::Cow::Borrowed(s)),
Self::MacroString(parts) if self.contains_macros() => Err(parts),
Self::MacroString(parts) => Ok(std::borrow::Cow::Owned(
Self::MacroString {
parts,
prepare_variables,
} if self.contains_macros() => Err((parts, prepare_variables)),
Self::MacroString { parts, .. } => Ok(std::borrow::Cow::Owned(
parts
.iter()
.map(|p| match p {
@ -126,6 +154,7 @@ where
}
/// Join multiple macro strings into one
#[cfg(feature = "shulkerbox")]
#[must_use]
pub fn join_macro_strings<I>(strings: I) -> MacroString
where
@ -139,20 +168,39 @@ where
s.push_str(&cur);
MacroString::String(s)
}
MacroString::MacroString(cur) => {
MacroString::MacroString {
parts: cur,
prepare_variables: preparation_cmds,
} => {
let mut parts = vec![MacroStringPart::String(s)];
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) => {
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);
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 = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -239,7 +288,10 @@ impl FromStr for MacroString {
.iter()
.any(|p| matches!(p, MacroStringPart::MacroUsage(_)))
{
Ok(Self::MacroString(parts))
Ok(Self::MacroString {
parts,
prepare_variables: BTreeMap::new(),
})
} else {
Ok(Self::String(s.to_string()))
}
@ -249,6 +301,7 @@ impl FromStr for MacroString {
}
}
#[cfg(feature = "shulkerbox")]
impl<S> From<S> for MacroString
where
S: Into<String>,
@ -266,12 +319,15 @@ impl AnyStringLiteral {
/// - If an identifier in a template string is not found in the scope
pub fn to_macro_string(
&self,
transpiler: Option<&mut Transpiler>,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<MacroString> {
match self {
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
/// - If an identifier in a template string is not found in the scope
#[expect(clippy::too_many_lines)]
pub fn to_macro_string(
&self,
mut transpiler: Option<&mut Transpiler>,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<MacroString> {
if self
.parts()
.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,
);
if self.contains_expression() {
let mut prepare_variables = BTreeMap::new();
match value.as_str() {
Ok(s) => Ok(MacroStringPart::String(s.into_owned())),
Err(_) => todo!("comptime value resulting in macro string with macros")
}
let parts = self
.parts()
.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 =
TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(identifier.span(), scope));
}
VariableData::BooleanStorage { storage_name, path } => {
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()));
Err(err)
}
}
Expression::Primary(Primary::MemberAccess(member_access)) => {
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();
value.as_str().map_or_else(|_| todo!("comptime value resulting in macro string with macros"), |s| Ok(MacroStringPart::String(s.into_owned())))
}
_ => todo!("other expressions in template strings"),
} else {
let err = TranspileError::UnknownIdentifier(
UnknownIdentifier::from_scope(identifier.span(), scope),
);
handler.receive(Box::new(err.clone()));
Err(err)
}
}
})
.collect::<TranspileResult<Vec<MacroStringPart>>>()?,
);
Expression::Primary(Primary::MemberAccess(member_access)) => {
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 {
Ok(MacroString::String(
self.as_str(scope, handler)?.into_owned(),
@ -354,46 +555,62 @@ impl TemplateStringLiteral {
}
}
#[cfg(test)]
#[cfg(all(test, feature = "shulkerbox"))]
mod tests {
use std::str::FromStr as _;
use assert_struct::assert_struct;
use super::*;
#[test]
fn test_parse_macro_string() {
assert_eq!(
assert_struct!(
MacroString::from_str("Hello, $(world)!").unwrap(),
MacroString::MacroString(vec![
MacroStringPart::String("Hello, ".to_string()),
MacroStringPart::MacroUsage("world".to_string()),
MacroStringPart::String("!".to_string())
])
MacroString::MacroString {
parts: vec![
MacroStringPart::String("Hello, ".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::MacroString(vec![
MacroStringPart::String("Hello, ".to_string()),
MacroStringPart::MacroUsage("world".to_string()),
MacroStringPart::String("! $(world".to_string()),
])
MacroString::MacroString {
parts: vec![
MacroStringPart::String("Hello, ".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::MacroString(vec![
MacroStringPart::String("Hello ".to_string()),
MacroStringPart::MacroUsage("a".to_string()),
MacroStringPart::String(" from ".to_string()),
MacroStringPart::MacroUsage("b".to_string()),
MacroStringPart::String(" and ".to_string()),
MacroStringPart::MacroUsage("c".to_string()),
])
MacroString::MacroString {
parts: vec![
MacroStringPart::String("Hello ".to_string()),
MacroStringPart::MacroUsage("a".to_string()),
MacroStringPart::String(" from ".to_string()),
MacroStringPart::MacroUsage("b".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::MacroString(vec![
MacroStringPart::String("Hello, $(world! ".to_string()),
MacroStringPart::MacroUsage("world".to_string()),
MacroStringPart::String("!".to_string()),
])
MacroString::MacroString {
parts: vec![
MacroStringPart::String("Hello, $(world! ".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,
MismatchedTypes, NotComptime,
},
expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
expression::{DataLocation, ExpectedType, StorageType},
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult,
};
#[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.
#[cfg(feature = "shulkerbox")]
@ -409,6 +409,8 @@ impl Transpiler {
handler,
)?;
if is_global {
use shulkerbox::datapack::Group;
let (temp_objective, [temp_target]) = self.get_temp_scoreboard_locations_array();
let test_cmd = match declaration.variable_type().keyword {
KeywordKind::Int => {
@ -425,7 +427,7 @@ impl Transpiler {
Condition::Atom(
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,
));
Ok(vec![test_exists_cmd, cond_cmd])
@ -634,9 +636,11 @@ impl Transpiler {
} else {
let index_span = match destination {
TranspileAssignmentTarget::Indexed(_, expr) => expr.span(),
TranspileAssignmentTarget::Identifier(_) => unreachable!(
"indexing value must be present (checked before)"
),
TranspileAssignmentTarget::Identifier(_) => {
unreachable!(
"indexing value must be present (checked before)"
)
}
};
let err = TranspileError::IllegalIndexing(IllegalIndexing {
expression: index_span,
@ -688,9 +692,11 @@ impl Transpiler {
} else {
let index_span = match destination {
TranspileAssignmentTarget::Indexed(_, expr) => expr.span(),
TranspileAssignmentTarget::Identifier(_) => unreachable!(
"indexing value must be present (checked before)"
),
TranspileAssignmentTarget::Identifier(_) => {
unreachable!(
"indexing value must be present (checked before)"
)
}
};
let err = TranspileError::IllegalIndexing(IllegalIndexing {
expression: index_span,
@ -847,18 +853,20 @@ impl Transpiler {
// TODO: change invalid criteria if boolean
if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation must be a valid scoreboard objective name.".to_string()
});
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation must be a valid scoreboard objective name."
.to_string(),
});
handler.receive(Box::new(err.clone()));
return Err(err);
}
Ok(name_eval)
} else {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation could not have been evaluated at compile time.".to_string()
});
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation could not have been evaluated at compile time."
.to_string(),
});
handler.receive(Box::new(err.clone()));
Err(err)
}
@ -950,34 +958,36 @@ impl Transpiler {
// TODO: change invalid criteria if boolean
if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'name' must be a valid scoreboard objective name.".to_string()
});
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'name' must be a valid scoreboard objective name.".to_string(),
});
handler.receive(Box::new(err.clone()));
return Err(err);
}
if !crate::util::is_valid_scoreboard_target(&target_eval) {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'target' must be a valid scoreboard player name.".to_string()
});
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'target' must be a valid scoreboard player name.".to_string(),
});
handler.receive(Box::new(err.clone()));
return Err(err);
}
Ok((name_eval, target_eval))
} else {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'name' or 'target' could not have been evaluated at compile time.".to_string()
});
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'name' or 'target' could not have been evaluated at compile time.".to_string(),
});
handler.receive(Box::new(err.clone()));
Err(err)
}
} else {
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'name' and 'target' must be compile time expressions.".to_string()
});
annotation: deobfuscate_annotation.span(),
message:
"Deobfuscate annotation 'name' and 'target' must be compile time expressions."
.to_string(),
});
handler.receive(Box::new(err.clone()));
Err(err)
}
@ -1180,9 +1190,7 @@ impl Transpiler {
{
Ok(Vec::new())
} else if matches!(target_type, StorageType::Boolean) {
let cmd = Command::Raw(format!(
"data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}"
));
let cmd = Command::Raw(format!("data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}"));
Ok(vec![cmd])
} else {
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 {
objective,
@ -1206,9 +1245,7 @@ impl Transpiler {
if objective == target_objective && score_target == target_target {
Ok(Vec::new())
} else {
let cmd = Command::Raw(format!(
"scoreboard players operation {target_target} {target_objective} = {score_target} {objective}"
));
let cmd = Command::Raw(format!("scoreboard players operation {target_target} {target_objective} = {score_target} {objective}"));
Ok(vec![cmd])
}
}