implement scoreboard operations for variables

This commit is contained in:
Moritz Hölting 2025-03-11 13:38:21 +01:00
parent 58998b4246
commit 2a41796405
3 changed files with 261 additions and 87 deletions

View File

@ -5,13 +5,12 @@ use std::{fmt::Display, sync::Arc};
use super::{Scope, VariableData}; use super::{Scope, VariableData};
use crate::{ use crate::{
base::VoidHandler, base::VoidHandler,
lexical::token::MacroStringLiteralPart,
syntax::syntax_tree::expression::{ syntax::syntax_tree::expression::{
Binary, BinaryOperator, Expression, PrefixOperator, Primary, Binary, BinaryOperator, Expression, PrefixOperator, Primary,
}, },
}; };
use derive_more::From;
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use shulkerbox::prelude::{Command, Condition, Execute}; use shulkerbox::prelude::{Command, Condition, Execute};
@ -29,11 +28,12 @@ use crate::{
/// Compile-time evaluated value /// Compile-time evaluated value
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, From)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum ComptimeValue { pub enum ComptimeValue {
Boolean(bool), Boolean(bool),
Integer(i64), Integer(i64),
String(String), String(String),
MacroString(String),
} }
impl Display for ComptimeValue { impl Display for ComptimeValue {
@ -42,6 +42,20 @@ impl Display for ComptimeValue {
Self::Boolean(boolean) => write!(f, "{boolean}"), Self::Boolean(boolean) => write!(f, "{boolean}"),
Self::Integer(int) => write!(f, "{int}"), Self::Integer(int) => write!(f, "{int}"),
Self::String(string) => write!(f, "{string}"), Self::String(string) => write!(f, "{string}"),
Self::MacroString(macro_string) => write!(f, "{macro_string}"),
}
}
}
impl ComptimeValue {
/// Returns the value as a string not containing a macro.
#[must_use]
pub fn to_string_no_macro(&self) -> Option<String> {
match self {
Self::Boolean(boolean) => Some(boolean.to_string()),
Self::Integer(int) => Some(int.to_string()),
Self::String(string) => Some(string.clone()),
Self::MacroString(_) => None,
} }
} }
} }
@ -50,19 +64,17 @@ impl Display for ComptimeValue {
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum ValueType { pub enum ValueType {
ScoreboardValue, Boolean,
Tag, Integer,
NumberStorage, String,
BooleanStorage,
} }
impl Display for ValueType { impl Display for ValueType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::ScoreboardValue => write!(f, "scoreboard value"), Self::Boolean => write!(f, "boolean"),
Self::Tag => write!(f, "tag"), Self::Integer => write!(f, "integer"),
Self::BooleanStorage => write!(f, "boolean storage"), Self::String => write!(f, "string"),
Self::NumberStorage => write!(f, "number storage"),
} }
} }
} }
@ -86,6 +98,22 @@ pub enum DataLocation {
}, },
} }
impl DataLocation {
/// Returns the type of the data location.
#[must_use]
pub fn value_type(&self) -> ValueType {
match self {
Self::ScoreboardValue { .. } => ValueType::Integer,
Self::Tag { .. } => ValueType::Boolean,
Self::Storage { r#type, .. } => match r#type {
StorageType::Boolean => ValueType::Boolean,
StorageType::Byte | StorageType::Int | StorageType::Long => ValueType::Integer,
StorageType::Double => todo!("Double storage type"),
},
}
}
}
/// The type of a storage. /// The type of a storage.
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Copy)]
@ -133,10 +161,10 @@ impl Expression {
/// Evaluate at compile-time. /// Evaluate at compile-time.
#[must_use] #[must_use]
pub fn comptime_eval(&self) -> Option<ComptimeValue> { pub fn comptime_eval(&self, scope: &Arc<Scope>) -> Option<ComptimeValue> {
match self { match self {
Self::Primary(primary) => primary.comptime_eval(), Self::Primary(primary) => primary.comptime_eval(scope),
Self::Binary(binary) => binary.comptime_eval(), Self::Binary(binary) => binary.comptime_eval(scope),
} }
} }
} }
@ -146,30 +174,38 @@ impl Primary {
#[must_use] #[must_use]
pub fn can_yield_type(&self, r#type: ValueType, scope: &Arc<Scope>) -> bool { pub fn can_yield_type(&self, r#type: ValueType, scope: &Arc<Scope>) -> bool {
match self { match self {
Self::Boolean(_) => matches!(r#type, ValueType::Tag | ValueType::BooleanStorage), Self::Boolean(_) => matches!(r#type, ValueType::Boolean),
Self::Integer(_) => matches!(r#type, ValueType::ScoreboardValue), Self::Integer(_) => matches!(r#type, ValueType::Integer),
Self::FunctionCall(_) => matches!( Self::FunctionCall(_) => matches!(r#type, ValueType::Integer | ValueType::Boolean),
r#type,
ValueType::ScoreboardValue | ValueType::Tag | ValueType::BooleanStorage
),
Self::Identifier(ident) => { Self::Identifier(ident) => {
scope scope
.get_variable(ident.span.str()) .get_variable(ident.span.str())
.map_or(false, |variable| match r#type { .map_or(false, |variable| match r#type {
ValueType::BooleanStorage => { ValueType::Boolean => {
matches!(variable.as_ref(), VariableData::BooleanStorage { .. }) matches!(
variable.as_ref(),
VariableData::Tag { .. } | VariableData::BooleanStorage { .. }
)
} }
ValueType::NumberStorage => false, ValueType::Integer => {
ValueType::ScoreboardValue => {
matches!(variable.as_ref(), VariableData::ScoreboardValue { .. }) matches!(variable.as_ref(), VariableData::ScoreboardValue { .. })
} }
ValueType::Tag => matches!(variable.as_ref(), VariableData::Tag { .. }), ValueType::String => false,
}) })
} }
Self::Parenthesized(parenthesized) => { Self::Parenthesized(parenthesized) => {
parenthesized.expression().can_yield_type(r#type, scope) parenthesized.expression().can_yield_type(r#type, scope)
} }
Self::Prefix(_) => todo!(), Self::Prefix(prefix) => match prefix.operator() {
PrefixOperator::LogicalNot(_) => {
matches!(r#type, ValueType::Boolean)
&& prefix.operand().can_yield_type(r#type, scope)
}
PrefixOperator::Negate(_) => {
matches!(r#type, ValueType::Integer)
&& prefix.operand().can_yield_type(r#type, scope)
}
},
// TODO: Add support for Lua. // TODO: Add support for Lua.
#[expect(clippy::match_same_arms)] #[expect(clippy::match_same_arms)]
Self::Lua(_) => false, Self::Lua(_) => false,
@ -179,7 +215,7 @@ impl Primary {
/// Evaluate at compile-time. /// Evaluate at compile-time.
#[must_use] #[must_use]
pub fn comptime_eval(&self) -> Option<ComptimeValue> { pub fn comptime_eval(&self, scope: &Arc<Scope>) -> Option<ComptimeValue> {
#[expect(clippy::match_same_arms)] #[expect(clippy::match_same_arms)]
match self { match self {
Self::Boolean(boolean) => Some(ComptimeValue::Boolean(boolean.value())), Self::Boolean(boolean) => Some(ComptimeValue::Boolean(boolean.value())),
@ -188,21 +224,18 @@ impl Primary {
string_literal.str_content().to_string(), string_literal.str_content().to_string(),
)), )),
Self::Identifier(_) => None, Self::Identifier(_) => None,
Self::Parenthesized(parenthesized) => parenthesized.expression().comptime_eval(), Self::Parenthesized(parenthesized) => parenthesized.expression().comptime_eval(scope),
Self::Prefix(prefix) => { Self::Prefix(prefix) => prefix.operand().comptime_eval(scope).and_then(|val| {
prefix match (prefix.operator(), val) {
.operand() (PrefixOperator::LogicalNot(_), ComptimeValue::Boolean(boolean)) => {
.comptime_eval() Some(ComptimeValue::Boolean(!boolean))
.and_then(|val| match (prefix.operator(), val) { }
(PrefixOperator::LogicalNot(_), ComptimeValue::Boolean(boolean)) => { (PrefixOperator::Negate(_), ComptimeValue::Integer(int)) => {
Some(ComptimeValue::Boolean(!boolean)) Some(ComptimeValue::Integer(-int))
} }
(PrefixOperator::Negate(_), ComptimeValue::Integer(int)) => { _ => None,
Some(ComptimeValue::Integer(-int)) }
} }),
_ => None,
})
}
// TODO: correctly evaluate lua code // TODO: correctly evaluate lua code
Self::Lua(lua) => lua Self::Lua(lua) => lua
.eval_string(&VoidHandler) .eval_string(&VoidHandler)
@ -210,8 +243,17 @@ impl Primary {
.flatten() .flatten()
.map(ComptimeValue::String), .map(ComptimeValue::String),
Self::MacroStringLiteral(macro_string_literal) => { Self::MacroStringLiteral(macro_string_literal) => {
// TODO: mark as containing macros if macro_string_literal
Some(ComptimeValue::String(macro_string_literal.str_content())) .parts()
.iter()
.any(|part| matches!(part, MacroStringLiteralPart::MacroUsage { .. }))
{
Some(ComptimeValue::MacroString(
macro_string_literal.str_content(),
))
} else {
Some(ComptimeValue::String(macro_string_literal.str_content()))
}
} }
// TODO: correctly evaluate function calls // TODO: correctly evaluate function calls
Self::FunctionCall(_) => None, Self::FunctionCall(_) => None,
@ -222,9 +264,9 @@ impl Primary {
impl Binary { impl Binary {
/// Evaluate at compile-time. /// Evaluate at compile-time.
#[must_use] #[must_use]
pub fn comptime_eval(&self) -> Option<ComptimeValue> { pub fn comptime_eval(&self, scope: &Arc<Scope>) -> Option<ComptimeValue> {
let left = self.left_operand().comptime_eval()?; let left = self.left_operand().comptime_eval(scope)?;
let right = self.right_operand().comptime_eval()?; let right = self.right_operand().comptime_eval(scope)?;
match (left, right) { match (left, right) {
(ComptimeValue::Boolean(left), ComptimeValue::Boolean(right)) => { (ComptimeValue::Boolean(left), ComptimeValue::Boolean(right)) => {
@ -236,9 +278,15 @@ impl Binary {
_ => None, _ => None,
} }
} }
// TODO: check that the other value will be boolean (even if not comptime)
(ComptimeValue::Boolean(true), _) | (_, ComptimeValue::Boolean(true)) => { (ComptimeValue::Boolean(true), _) | (_, ComptimeValue::Boolean(true)) => {
if matches!(self.operator(), BinaryOperator::LogicalOr(..)) { if matches!(self.operator(), BinaryOperator::LogicalOr(..))
&& self
.left_operand()
.can_yield_type(ValueType::Boolean, scope)
&& self
.right_operand()
.can_yield_type(ValueType::Boolean, scope)
{
Some(ComptimeValue::Boolean(true)) Some(ComptimeValue::Boolean(true))
} else { } else {
None None
@ -437,7 +485,7 @@ impl Transpiler {
))]) ))])
} }
DataLocation::Tag { .. } => Err(TranspileError::MismatchedTypes(MismatchedTypes { DataLocation::Tag { .. } => Err(TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::Tag, expected_type: ValueType::Boolean,
expression: primary.span(), expression: primary.span(),
})), })),
DataLocation::Storage { DataLocation::Storage {
@ -462,7 +510,7 @@ impl Transpiler {
} else { } else {
Err(TranspileError::MismatchedTypes(MismatchedTypes { Err(TranspileError::MismatchedTypes(MismatchedTypes {
expression: primary.span(), expression: primary.span(),
expected_type: ValueType::NumberStorage, expected_type: ValueType::Integer,
})) }))
} }
} }
@ -478,11 +526,7 @@ impl Transpiler {
} }
Primary::StringLiteral(_) | Primary::MacroStringLiteral(_) => { Primary::StringLiteral(_) | Primary::MacroStringLiteral(_) => {
Err(TranspileError::MismatchedTypes(MismatchedTypes { Err(TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: match target { expected_type: target.value_type(),
DataLocation::ScoreboardValue { .. } => ValueType::ScoreboardValue,
DataLocation::Tag { .. } => ValueType::Tag,
DataLocation::Storage { .. } => ValueType::NumberStorage,
},
expression: primary.span(), expression: primary.span(),
})) }))
} }
@ -552,7 +596,7 @@ impl Transpiler {
} else { } else {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: primary.span(), expression: primary.span(),
expected_type: ValueType::BooleanStorage, expected_type: target.value_type(),
}); });
handler.receive(err.clone()); handler.receive(err.clone());
Err(err) Err(err)
@ -593,7 +637,7 @@ impl Transpiler {
} else { } else {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: primary.span(), expression: primary.span(),
expected_type: ValueType::NumberStorage, expected_type: target.value_type(),
}); });
handler.receive(err.clone()); handler.receive(err.clone());
Err(err) Err(err)
@ -601,7 +645,7 @@ impl Transpiler {
} }
DataLocation::Tag { .. } => { DataLocation::Tag { .. } => {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::Tag, expected_type: ValueType::Boolean,
expression: primary.span(), expression: primary.span(),
}); });
handler.receive(err.clone()); handler.receive(err.clone());
@ -610,13 +654,7 @@ impl Transpiler {
}, },
_ => { _ => {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: match target { expected_type: target.value_type(),
DataLocation::ScoreboardValue { .. } => {
ValueType::ScoreboardValue
}
DataLocation::Tag { .. } => ValueType::Tag,
DataLocation::Storage { .. } => ValueType::NumberStorage,
},
expression: primary.span(), expression: primary.span(),
}); });
handler.receive(err.clone()); handler.receive(err.clone());
@ -634,21 +672,21 @@ impl Transpiler {
} }
} }
#[expect(clippy::needless_pass_by_ref_mut)] #[expect(clippy::too_many_lines)]
fn transpile_binary_expression( fn transpile_binary_expression(
&mut self, &mut self,
binary: &Binary, binary: &Binary,
target: &DataLocation, target: &DataLocation,
_scope: &Arc<super::Scope>, scope: &Arc<super::Scope>,
_handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> { ) -> TranspileResult<Vec<Command>> {
match binary.comptime_eval() { match binary.comptime_eval(scope) {
Some(ComptimeValue::Integer(value)) => match target { Some(ComptimeValue::Integer(value)) => match target {
DataLocation::ScoreboardValue { objective, target } => Ok(vec![Command::Raw( DataLocation::ScoreboardValue { objective, target } => Ok(vec![Command::Raw(
format!("scoreboard players set {target} {objective} {value}"), format!("scoreboard players set {target} {objective} {value}"),
)]), )]),
DataLocation::Tag { .. } => Err(TranspileError::MismatchedTypes(MismatchedTypes { DataLocation::Tag { .. } => Err(TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::Tag, expected_type: ValueType::Boolean,
expression: binary.span(), expression: binary.span(),
})), })),
DataLocation::Storage { DataLocation::Storage {
@ -670,7 +708,7 @@ impl Transpiler {
} else { } else {
Err(TranspileError::MismatchedTypes(MismatchedTypes { Err(TranspileError::MismatchedTypes(MismatchedTypes {
expression: binary.span(), expression: binary.span(),
expected_type: ValueType::NumberStorage, expected_type: target.value_type(),
})) }))
} }
} }
@ -700,28 +738,164 @@ impl Transpiler {
} else { } else {
Err(TranspileError::MismatchedTypes(MismatchedTypes { Err(TranspileError::MismatchedTypes(MismatchedTypes {
expression: binary.span(), expression: binary.span(),
expected_type: ValueType::NumberStorage, expected_type: target.value_type(),
})) }))
} }
} }
}, },
Some(ComptimeValue::String(_)) => { Some(ComptimeValue::String(_) | ComptimeValue::MacroString(_)) => {
Err(TranspileError::MismatchedTypes(MismatchedTypes { Err(TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: match target { expected_type: target.value_type(),
DataLocation::ScoreboardValue { .. } => ValueType::ScoreboardValue,
DataLocation::Tag { .. } => ValueType::Tag,
DataLocation::Storage { .. } => ValueType::NumberStorage,
},
expression: binary.span(), expression: binary.span(),
})) }))
} }
None => { None => {
let _left = binary.left_operand(); let left = binary.left_operand();
let _right = binary.right_operand(); let right = binary.right_operand();
let _operator = binary.operator(); let operator = binary.operator();
todo!("Transpile binary expression") let (temp_objective, temp_locations) = self.get_temp_scoreboard_locations(2);
let score_target_location = match target {
DataLocation::ScoreboardValue { objective, target } => (objective, target),
_ => (&temp_objective, &temp_locations[0]),
};
let left_cmds = self.transpile_expression(
left,
&DataLocation::ScoreboardValue {
objective: score_target_location.0.clone(),
target: score_target_location.1.clone(),
},
scope,
handler,
)?;
let right_cmds = self.transpile_expression(
right,
&DataLocation::ScoreboardValue {
objective: temp_objective.clone(),
target: temp_locations[1].clone(),
},
scope,
handler,
)?;
let calc_cmds = match operator {
BinaryOperator::Add(_) => {
vec![Command::Raw(format!(
"scoreboard players operation {target} {target_objective} += {source} {source_objective}",
target = score_target_location.1,
target_objective = score_target_location.0,
source = temp_locations[1],
source_objective = temp_objective
))]
}
BinaryOperator::Subtract(_) => {
vec![Command::Raw(format!(
"scoreboard players operation {target} {target_objective} -= {source} {source_objective}",
target = score_target_location.1,
target_objective = score_target_location.0,
source = temp_locations[1],
source_objective = temp_objective
))]
}
BinaryOperator::Multiply(_) => {
vec![Command::Raw(format!(
"scoreboard players operation {target} {target_objective} *= {source} {source_objective}",
target = score_target_location.1,
target_objective = score_target_location.0,
source = temp_locations[1],
source_objective = temp_objective
))]
}
BinaryOperator::Divide(_) => {
vec![Command::Raw(format!(
"scoreboard players operation {target} {target_objective} /= {source} {source_objective}",
target = score_target_location.1,
target_objective = score_target_location.0,
source = temp_locations[1],
source_objective = temp_objective
))]
}
BinaryOperator::Modulo(_) => {
vec![Command::Raw(format!(
"scoreboard players operation {target} {target_objective} %= {source} {source_objective}",
target = score_target_location.1,
target_objective = score_target_location.0,
source = temp_locations[1],
source_objective = temp_objective
))]
}
_ => todo!("Transpile binary expression"),
};
let transfer_cmd = match target {
DataLocation::ScoreboardValue { .. } => None,
DataLocation::Tag { .. } => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::Boolean,
expression: binary.span(),
});
handler.receive(err.clone());
return Err(err);
}
DataLocation::Storage {
storage_name,
path,
r#type,
} => match r#type {
StorageType::Byte
| StorageType::Double
| StorageType::Int
| StorageType::Long => Some(Command::Execute(Execute::Store(
format!(
"result storage {storage_name} {path} {t} 1",
t = r#type.as_str()
)
.into(),
Box::new(Execute::Run(Box::new(Command::Raw(format!(
"scoreboard players get {target} {objective}",
objective = score_target_location.0,
target = score_target_location.1
))))),
))),
StorageType::Boolean => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::Boolean,
expression: binary.span(),
});
handler.receive(err.clone());
return Err(err);
}
},
};
Ok(left_cmds
.into_iter()
.chain(right_cmds)
.chain(calc_cmds)
.chain(transfer_cmd)
.collect())
} }
} }
} }
/// Get temporary scoreboard locations.
fn get_temp_scoreboard_locations(&mut self, amount: usize) -> (String, Vec<String>) {
let objective = "shu_temp_".to_string()
+ &chksum_md5::hash(&self.main_namespace_name).to_hex_lowercase();
self.datapack
.register_scoreboard(&objective, None::<&str>, None::<&str>);
let targets = (0..amount)
.map(|i| {
chksum_md5::hash(format!("{}\0{i}", self.main_namespace_name))
.to_hex_lowercase()
.split_off(16)
})
.collect();
(objective, targets)
}
} }

View File

@ -334,8 +334,8 @@ impl Transpiler {
|val| match val { |val| match val {
TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()), TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()),
TranspileAnnotationValue::Expression(expr) => expr TranspileAnnotationValue::Expression(expr) => expr
.comptime_eval() .comptime_eval(scope)
.map(|val| val.to_string()) .and_then(|val| val.to_string_no_macro())
.ok_or_else(|| { .ok_or_else(|| {
let err = TranspileError::IllegalAnnotationContent( let err = TranspileError::IllegalAnnotationContent(
IllegalAnnotationContent { IllegalAnnotationContent {

View File

@ -376,8 +376,8 @@ fn get_single_data_location_identifiers(
) = (name, target) ) = (name, target)
{ {
if let (Some(name_eval), Some(target_eval)) = ( if let (Some(name_eval), Some(target_eval)) = (
objective.comptime_eval().map(|val| val.to_string()), objective.comptime_eval(scope).map(|val| val.to_string()),
target.comptime_eval().map(|val| val.to_string()), target.comptime_eval(scope).map(|val| val.to_string()),
) { ) {
// TODO: change invalid criteria if boolean // TODO: change invalid criteria if boolean
if !crate::util::is_valid_scoreboard_objective_name(&name_eval) { if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {