basic transpilation of template string working
This commit is contained in:
parent
ef7bf95447
commit
6043a4add5
|
@ -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]]
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
)?;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue