implement more primary expressions (identifier, parenthesized, prefixed)

This commit is contained in:
Moritz Hölting 2025-03-10 15:57:38 +01:00
parent 14b371b3b6
commit 58998b4246
9 changed files with 483 additions and 70 deletions

View File

@ -38,7 +38,7 @@ path-absolutize = "3.1.1"
pathdiff = "0.2.3"
serde = { version = "1.0.217", features = ["derive"], optional = true }
# shulkerbox = { version = "0.1.0", default-features = false, optional = true }
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "cf8c922704a38980def3c6267cee2eeba304394c", default-features = false, optional = true }
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "c36c87c3db311cea0b373501c370c12edc6d051f", default-features = false, optional = true }
strsim = "0.11.1"
strum = { version = "0.27.0", features = ["derive"] }
thiserror = "2.0.11"

View File

@ -462,6 +462,10 @@ impl Primary {
}
Self::Lua(_) | Self::StringLiteral(_) | Self::Integer(_) | Self::Boolean(_) => Ok(()),
Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler),
Self::Identifier(_) | Self::Parenthesized(_) | Self::Prefix(_) => {
// TODO: correctly analyze the semantics of the primary expression
Ok(())
}
}
}
}

View File

@ -168,7 +168,10 @@ impl SourceElement for Expression {
///
/// ``` ebnf
/// Primary:
/// Integer
/// Identifier
/// | Prefix
/// | Parenthesized
/// | Integer
/// | Boolean
/// | StringLiteral
/// | FunctionCall
@ -179,6 +182,9 @@ impl SourceElement for Expression {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
pub enum Primary {
Identifier(Identifier),
Prefix(Prefix),
Parenthesized(Parenthesized),
Integer(Integer),
Boolean(Boolean),
StringLiteral(StringLiteral),
@ -190,6 +196,9 @@ pub enum Primary {
impl SourceElement for Primary {
fn span(&self) -> Span {
match self {
Self::Identifier(identifier) => identifier.span(),
Self::Prefix(prefix) => prefix.span(),
Self::Parenthesized(parenthesized) => parenthesized.span(),
Self::Integer(int) => int.span(),
Self::Boolean(bool) => bool.span(),
Self::StringLiteral(string_literal) => string_literal.span(),
@ -200,6 +209,92 @@ impl SourceElement for Primary {
}
}
/// Represents a parenthesized expression in the syntax tree.
///
/// Syntax Synopsis:
/// ```ebnf
/// Parenthesized:
/// '(' Expression ')'
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Parenthesized {
/// The open parenthesis.
#[get = "pub"]
open: Punctuation,
/// The expression inside the parenthesis.
#[get = "pub"]
expression: Box<Expression>,
/// The close parenthesis.
#[get = "pub"]
close: Punctuation,
}
impl Parenthesized {
/// Dissolves the parenthesized expression into its components
#[must_use]
pub fn dissolve(self) -> (Punctuation, Expression, Punctuation) {
(self.open, *self.expression, self.close)
}
}
impl SourceElement for Parenthesized {
fn span(&self) -> Span {
self.open.span().join(&self.close.span).unwrap()
}
}
/// Represents a prefix operator in the syntax tree.
///
/// Syntax Synopsis:
/// ```ebnf
/// PrefixOperator:
/// '!' | '-'
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
pub enum PrefixOperator {
/// The logical not operator '!'.
LogicalNot(Punctuation),
/// The negate operator '-'.
Negate(Punctuation),
}
impl SourceElement for PrefixOperator {
fn span(&self) -> Span {
match self {
Self::LogicalNot(token) | Self::Negate(token) => token.span.clone(),
}
}
}
/// Represents a prefix expression in the syntax tree.
///
/// Syntax Synopsis:
/// ```ebnf
/// Prefix:
/// PrefixOperator Primary
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Prefix {
/// The prefix operator.
#[get = "pub"]
operator: PrefixOperator,
/// The operand.
#[get = "pub"]
operand: Box<Primary>,
}
impl SourceElement for Prefix {
fn span(&self) -> Span {
self.operator.span().join(&self.operand.span()).unwrap()
}
}
/// Represents a function call in the syntax tree.
///
/// Syntax Synopsis:
@ -358,6 +453,30 @@ impl<'a> Parser<'a> {
#[expect(clippy::too_many_lines)]
pub fn parse_primary(&mut self, handler: &impl Handler<base::Error>) -> ParseResult<Primary> {
match self.stop_at_significant() {
// prefixed expression
Reading::Atomic(Token::Punctuation(punc)) if matches!(punc.punctuation, '!' | '-') => {
// eat the prefix
self.forward();
let prefix_operator = match punc.punctuation {
'!' => PrefixOperator::LogicalNot(punc),
'-' => PrefixOperator::Negate(punc),
_ => unreachable!(),
};
let operand = Box::new(self.parse_primary(handler)?);
Ok(Primary::Prefix(Prefix {
operator: prefix_operator,
operand,
}))
}
// parenthesized expression
Reading::IntoDelimited(left_parenthesis) if left_parenthesis.punctuation == '(' => self
.parse_parenthesized(handler)
.map(Primary::Parenthesized),
// identifier expression
Reading::Atomic(Token::Identifier(identifier)) => {
// eat the identifier
@ -380,14 +499,9 @@ impl<'a> Parser<'a> {
arguments: token_tree.list,
}))
}
unexpected => {
// insert parser for regular identifier here
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
expected: syntax::error::SyntaxKind::Punctuation('('),
found: unexpected.into_token(),
});
handler.receive(err.clone());
Err(err)
_ => {
// regular identifier
Ok(Primary::Identifier(identifier))
}
}
}
@ -506,6 +620,23 @@ impl<'a> Parser<'a> {
}
}
fn parse_parenthesized(
&mut self,
handler: &impl Handler<base::Error>,
) -> ParseResult<Parenthesized> {
let token_tree = self.step_into(
Delimiter::Parenthesis,
|p| p.parse_expression(handler),
handler,
)?;
Ok(Parenthesized {
open: token_tree.open,
expression: Box::new(token_tree.tree?),
close: token_tree.close,
})
}
fn parse_binary_operator(
&mut self,
handler: &impl Handler<base::Error>,

View File

@ -38,6 +38,8 @@ pub enum TranspileError {
FunctionArgumentsNotAllowed(#[from] FunctionArgumentsNotAllowed),
#[error(transparent)]
AssignmentError(#[from] AssignmentError),
#[error(transparent)]
UnknownIdentifier(#[from] UnknownIdentifier),
}
/// The result of a transpilation operation.
@ -251,3 +253,30 @@ impl Display for AssignmentError {
}
impl std::error::Error for AssignmentError {}
/// An error that occurs when an unknown identifier is used.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnknownIdentifier {
pub identifier: Span,
}
impl Display for UnknownIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
Message::new(
Severity::Error,
format!("The identifier {} is not defined.", self.identifier.str())
)
)?;
write!(
f,
"\n{}",
SourceCodeDisplay::new(&self.identifier, Option::<u8>::None)
)
}
}
impl std::error::Error for UnknownIdentifier {}

View File

@ -1,21 +1,25 @@
//! The expression transpiler.
use std::fmt::Display;
use std::{fmt::Display, sync::Arc};
use super::{Scope, VariableData};
use crate::{
base::VoidHandler,
syntax::syntax_tree::expression::{Binary, BinaryOperator, Expression, Primary},
syntax::syntax_tree::expression::{
Binary, BinaryOperator, Expression, PrefixOperator, Primary,
},
};
#[cfg(feature = "shulkerbox")]
use std::sync::Arc;
use derive_more::From;
#[cfg(feature = "shulkerbox")]
use shulkerbox::prelude::{Command, Condition, Execute};
#[cfg(feature = "shulkerbox")]
use super::{error::MismatchedTypes, TranspileResult, Transpiler};
use super::{
error::{MismatchedTypes, UnknownIdentifier},
TranspileResult, Transpiler,
};
#[cfg(feature = "shulkerbox")]
use crate::{
base::{self, source_file::SourceElement, Handler},
@ -120,9 +124,9 @@ impl StorageType {
impl Expression {
/// Returns whether the expression can yield a certain type.
#[must_use]
pub fn can_yield_type(&self, r#type: ValueType) -> bool {
pub fn can_yield_type(&self, r#type: ValueType, scope: &Arc<Scope>) -> bool {
match self {
Self::Primary(primary) => primary.can_yield_type(r#type),
Self::Primary(primary) => primary.can_yield_type(r#type, scope),
Self::Binary(_binary) => todo!(),
}
}
@ -140,7 +144,7 @@ impl Expression {
impl Primary {
/// Returns whether the primary can yield a certain type.
#[must_use]
pub fn can_yield_type(&self, r#type: ValueType) -> bool {
pub fn can_yield_type(&self, r#type: ValueType, scope: &Arc<Scope>) -> bool {
match self {
Self::Boolean(_) => matches!(r#type, ValueType::Tag | ValueType::BooleanStorage),
Self::Integer(_) => matches!(r#type, ValueType::ScoreboardValue),
@ -148,6 +152,24 @@ impl Primary {
r#type,
ValueType::ScoreboardValue | ValueType::Tag | ValueType::BooleanStorage
),
Self::Identifier(ident) => {
scope
.get_variable(ident.span.str())
.map_or(false, |variable| match r#type {
ValueType::BooleanStorage => {
matches!(variable.as_ref(), VariableData::BooleanStorage { .. })
}
ValueType::NumberStorage => false,
ValueType::ScoreboardValue => {
matches!(variable.as_ref(), VariableData::ScoreboardValue { .. })
}
ValueType::Tag => matches!(variable.as_ref(), VariableData::Tag { .. }),
})
}
Self::Parenthesized(parenthesized) => {
parenthesized.expression().can_yield_type(r#type, scope)
}
Self::Prefix(_) => todo!(),
// TODO: Add support for Lua.
#[expect(clippy::match_same_arms)]
Self::Lua(_) => false,
@ -158,12 +180,29 @@ impl Primary {
/// Evaluate at compile-time.
#[must_use]
pub fn comptime_eval(&self) -> Option<ComptimeValue> {
#[expect(clippy::match_same_arms)]
match self {
Self::Boolean(boolean) => Some(ComptimeValue::Boolean(boolean.value())),
Self::Integer(int) => Some(ComptimeValue::Integer(int.as_i64())),
Self::StringLiteral(string_literal) => Some(ComptimeValue::String(
string_literal.str_content().to_string(),
)),
Self::Identifier(_) => None,
Self::Parenthesized(parenthesized) => parenthesized.expression().comptime_eval(),
Self::Prefix(prefix) => {
prefix
.operand()
.comptime_eval()
.and_then(|val| match (prefix.operator(), val) {
(PrefixOperator::LogicalNot(_), ComptimeValue::Boolean(boolean)) => {
Some(ComptimeValue::Boolean(!boolean))
}
(PrefixOperator::Negate(_), ComptimeValue::Integer(int)) => {
Some(ComptimeValue::Integer(-int))
}
_ => None,
})
}
// TODO: correctly evaluate lua code
Self::Lua(lua) => lua
.eval_string(&VoidHandler)
@ -197,6 +236,21 @@ impl Binary {
_ => None,
}
}
// TODO: check that the other value will be boolean (even if not comptime)
(ComptimeValue::Boolean(true), _) | (_, ComptimeValue::Boolean(true)) => {
if matches!(self.operator(), BinaryOperator::LogicalOr(..)) {
Some(ComptimeValue::Boolean(true))
} else {
None
}
}
(ComptimeValue::Boolean(false), _) | (_, ComptimeValue::Boolean(false)) => {
if matches!(self.operator(), BinaryOperator::LogicalAnd(..)) {
Some(ComptimeValue::Boolean(false))
} else {
None
}
}
(ComptimeValue::Integer(left), ComptimeValue::Integer(right)) => {
match self.operator() {
BinaryOperator::Add(..) => left.checked_add(right).map(ComptimeValue::Integer),
@ -238,6 +292,20 @@ impl Binary {
#[cfg(feature = "shulkerbox")]
impl Transpiler {
/// Initializes a constant score.
pub(crate) fn initialize_constant_score(&mut self, constant: i64) {
if self.initialized_constant_scores.is_empty() {
self.datapack
.register_scoreboard("shu_constants", None::<&str>, None::<&str>);
}
if self.initialized_constant_scores.insert(constant) {
self.setup_cmds.push(Command::Raw(format!(
"scoreboard players set {constant} shu_constants {constant}"
)));
}
}
/// Transpiles an expression.
pub(super) fn transpile_expression(
&mut self,
@ -399,6 +467,9 @@ impl Transpiler {
}
}
},
Primary::Parenthesized(parenthesized) => {
self.transpile_expression(parenthesized.expression(), target, scope, handler)
}
Primary::Lua(_) => {
// TODO: Add support for Lua.
Err(TranspileError::UnexpectedExpression(UnexpectedExpression(
@ -415,6 +486,151 @@ impl Transpiler {
expression: primary.span(),
}))
}
Primary::Prefix(prefix) => match prefix.operator() {
PrefixOperator::Negate(_) => match target {
DataLocation::ScoreboardValue {
objective,
target: score_target,
} => {
let mut expr_cmds = self.transpile_primary_expression(
prefix.operand(),
target,
scope,
handler,
)?;
self.initialize_constant_score(-1);
let negate_cmd = Command::Raw(format!("scoreboard players operation {score_target} {objective} *= -1 shu_constants"));
expr_cmds.push(negate_cmd);
Ok(expr_cmds)
}
_ => todo!("Negate operator for other types"),
},
PrefixOperator::LogicalNot(_) => todo!("Logical not operator"),
},
Primary::Identifier(ident) => {
let variable = scope.get_variable(ident.span.str());
#[expect(clippy::option_if_let_else)]
if let Some(variable) = variable.as_deref() {
match variable {
VariableData::BooleanStorage { storage_name, path } => match target {
DataLocation::ScoreboardValue { objective, target } => {
let cmd = Command::Execute(Execute::Store(
format!("store result score {target} {objective}").into(),
Box::new(Execute::Run(Box::new(Command::Raw(format!(
"data get storage {storage_name} {path}"
))))),
));
Ok(vec![cmd])
}
DataLocation::Tag { tag_name, entity } => {
let cmd = Command::Execute(Execute::If(
Condition::Atom(
format!("data storage {storage_name} {{{path}: 1b}}")
.into(),
),
Box::new(Execute::Run(Box::new(Command::Raw(format!(
"tag {entity} add {tag_name}"
))))),
Some(Box::new(Execute::Run(Box::new(Command::Raw(format!(
"tag {entity} remove {tag_name}"
)))))),
));
Ok(vec![cmd])
}
DataLocation::Storage {
storage_name: target_storage_name,
path: target_path,
r#type,
} => {
if matches!(r#type, StorageType::Boolean) {
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 {
expression: primary.span(),
expected_type: ValueType::BooleanStorage,
});
handler.receive(err.clone());
Err(err)
}
}
},
VariableData::ScoreboardValue {
objective,
target: score_target,
} => match target {
DataLocation::ScoreboardValue {
objective: target_objective,
target: target_target,
} => {
let cmd = Command::Raw(format!(
"scoreboard players operation {target_target} {target_objective} = {score_target} {objective}"
));
Ok(vec![cmd])
}
DataLocation::Storage {
storage_name,
path,
r#type,
} => {
if matches!(
r#type,
StorageType::Byte
| StorageType::Double
| StorageType::Int
| StorageType::Long
) {
let cmd = Command::Raw(format!(
"data modify storage {storage_name} {path} set value {value}{suffix}",
value = score_target,
suffix = r#type.suffix()
));
Ok(vec![cmd])
} else {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: primary.span(),
expected_type: ValueType::NumberStorage,
});
handler.receive(err.clone());
Err(err)
}
}
DataLocation::Tag { .. } => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ValueType::Tag,
expression: primary.span(),
});
handler.receive(err.clone());
Err(err)
}
},
_ => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: match target {
DataLocation::ScoreboardValue { .. } => {
ValueType::ScoreboardValue
}
DataLocation::Tag { .. } => ValueType::Tag,
DataLocation::Storage { .. } => ValueType::NumberStorage,
},
expression: primary.span(),
});
handler.receive(err.clone());
Err(err)
}
}
} else {
let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
handler.receive(err.clone());
Err(err)
}
}
}
}
@ -502,15 +718,9 @@ impl Transpiler {
None => {
let _left = binary.left_operand();
let _right = binary.right_operand();
let operator = binary.operator();
let _operator = binary.operator();
match operator {
BinaryOperator::Add(_) => {
// let temp_name
todo!()
}
_ => todo!(),
}
todo!("Transpile binary expression")
}
}
}

View File

@ -29,9 +29,7 @@ use strum::EnumIs;
#[cfg_attr(feature = "shulkerbox", doc(inline))]
pub use transpiler::Transpiler;
#[cfg(feature = "shulkerbox")]
mod variables;
#[cfg(feature = "shulkerbox")]
pub use variables::{Scope, VariableData};
pub mod util;

View File

@ -2,7 +2,7 @@
use chksum_md5 as md5;
use std::{
collections::{BTreeMap, HashMap},
collections::{BTreeMap, HashMap, HashSet},
ops::Deref,
sync::{Arc, OnceLock},
};
@ -41,6 +41,7 @@ pub struct Transpiler {
pub(super) main_namespace_name: String,
pub(super) datapack: shulkerbox::datapack::Datapack,
pub(super) setup_cmds: Vec<Command>,
pub(super) initialized_constant_scores: HashSet<i64>,
/// Top-level [`Scope`] for each program identifier
scopes: BTreeMap<String, Arc<Scope<'static>>>,
/// Key: (program identifier, function name)
@ -58,6 +59,7 @@ impl Transpiler {
main_namespace_name: main_namespace_name.clone(),
datapack: shulkerbox::datapack::Datapack::new(main_namespace_name, pack_format),
setup_cmds: Vec::new(),
initialized_constant_scores: HashSet::new(),
scopes: BTreeMap::new(),
functions: BTreeMap::new(),
aliases: HashMap::new(),
@ -128,8 +130,16 @@ impl Transpiler {
let main_namespace = self.datapack.namespace_mut(&self.main_namespace_name);
let setup_fn = main_namespace.function_mut("shu/setup");
setup_fn.get_commands_mut().extend(self.setup_cmds.clone());
self.datapack
.add_load(format!("{}:shu/setup", self.main_namespace_name));
// prepend setup function to load tag
let load_values = self
.datapack
.namespace_mut("minecraft")
.tag_mut("load", datapack::tag::TagType::Function)
.values_mut();
load_values.insert(
0,
datapack::tag::TagValue::Simple(format!("{}:shu/setup", self.main_namespace_name)),
);
}
Ok(())
@ -422,6 +432,12 @@ impl Transpiler {
Expression::Primary(Primary::MacroStringLiteral(literal)) => {
Ok(literal.str_content())
}
Expression::Primary(
Primary::Identifier(_) | Primary::Parenthesized(_) | Primary::Prefix(_),
) => {
todo!("allow identifiers, parenthesized & prefix expressions as arguments")
}
Expression::Binary(_) => todo!("allow binary expressions as arguments"),
};
@ -482,35 +498,9 @@ impl Transpiler {
Statement::LiteralCommand(literal_command) => {
Ok(vec![literal_command.clean_command().into()])
}
Statement::Run(run) => match run.expression() {
Expression::Primary(Primary::FunctionCall(func)) => self
.transpile_function_call(func, scope, handler)
.map(|cmd| vec![cmd]),
Expression::Primary(Primary::Integer(num)) => {
let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
Expression::Primary(Primary::Integer(num.clone())),
));
handler.receive(error.clone());
Err(error)
Statement::Run(run) => {
self.transpile_run_expression(run.expression(), program_identifier, scope, handler)
}
Expression::Primary(Primary::Boolean(bool)) => {
let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
Expression::Primary(Primary::Boolean(bool.clone())),
));
handler.receive(error.clone());
Err(error)
}
Expression::Primary(Primary::StringLiteral(string)) => {
Ok(vec![Command::Raw(string.str_content().to_string())])
}
Expression::Primary(Primary::MacroStringLiteral(string)) => {
Ok(vec![Command::UsesMacro(string.into())])
}
Expression::Primary(Primary::Lua(code)) => Ok(code
.eval_string(handler)?
.map_or_else(Vec::new, |cmd| vec![Command::Raw(cmd)])),
Expression::Binary(_) => todo!("transpile binary expression in run statement"),
},
Statement::Block(_) => {
unreachable!("Only literal commands are allowed in functions at this time.")
}
@ -578,6 +568,49 @@ impl Transpiler {
}
}
#[expect(clippy::only_used_in_recursion)]
fn transpile_run_expression(
&mut self,
expression: &Expression,
program_identifier: &str,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
match expression {
Expression::Primary(Primary::FunctionCall(func)) => self
.transpile_function_call(func, scope, handler)
.map(|cmd| vec![cmd]),
expression @ Expression::Primary(
Primary::Integer(_)
| Primary::Boolean(_)
| Primary::Prefix(_)
| Primary::Identifier(_),
) => {
let error =
TranspileError::UnexpectedExpression(UnexpectedExpression(expression.clone()));
handler.receive(error.clone());
Err(error)
}
Expression::Primary(Primary::StringLiteral(string)) => {
Ok(vec![Command::Raw(string.str_content().to_string())])
}
Expression::Primary(Primary::MacroStringLiteral(string)) => {
Ok(vec![Command::UsesMacro(string.into())])
}
Expression::Primary(Primary::Lua(code)) => Ok(code
.eval_string(handler)?
.map_or_else(Vec::new, |cmd| vec![Command::Raw(cmd)])),
Expression::Primary(Primary::Parenthesized(parenthesized)) => self
.transpile_run_expression(
parenthesized.expression(),
program_identifier,
scope,
handler,
),
Expression::Binary(_) => todo!("transpile binary expression in run statement"),
}
}
pub(super) fn transpile_function_call(
&mut self,
func: &FunctionCall,

View File

@ -7,8 +7,11 @@ use std::{
sync::{Arc, OnceLock, RwLock},
};
#[cfg(feature = "shulkerbox")]
use chksum_md5 as md5;
#[cfg(feature = "shulkerbox")]
use shulkerbox::prelude::{Command, Condition, Execute};
use strum::EnumIs;
use crate::{
@ -23,9 +26,12 @@ use crate::{
use super::{
error::{AssignmentError, IllegalAnnotationContent},
expression::{ComptimeValue, DataLocation},
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult, Transpiler,
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult,
};
#[cfg(feature = "shulkerbox")]
use super::Transpiler;
/// Stores the data required to access a variable.
#[derive(Debug, Clone, EnumIs)]
pub enum VariableData {
@ -192,6 +198,7 @@ impl<'a> Debug for Scope<'a> {
}
}
#[cfg(feature = "shulkerbox")]
impl Transpiler {
pub(super) fn transpile_variable_declaration(
&mut self,
@ -324,6 +331,7 @@ impl Transpiler {
}
#[expect(clippy::too_many_lines)]
#[cfg(feature = "shulkerbox")]
fn get_single_data_location_identifiers(
single: &SingleVariableDeclaration,
program_identifier: &str,
@ -372,18 +380,18 @@ fn get_single_data_location_identifiers(
target.comptime_eval().map(|val| val.to_string()),
) {
// TODO: change invalid criteria if boolean
if !crate::util::is_valid_scoreboard_name(&name_eval) {
if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'name' must be a valid scoreboard name.".to_string()
message: "Deobfuscate annotation 'name' must be a valid scoreboard objective name.".to_string()
});
handler.receive(error.clone());
return Err(error);
}
if !crate::util::is_valid_player_name(&target_eval) {
if !crate::util::is_valid_scoreboard_target(&target_eval) {
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation 'target' must be a valid player name.".to_string()
message: "Deobfuscate annotation 'target' must be a valid scoreboard player name.".to_string()
});
handler.receive(error.clone());
return Err(error);

View File

@ -81,15 +81,15 @@ pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<str> {
/// Returns whether a string is a valid scoreboard name.
#[must_use]
pub fn is_valid_scoreboard_name(name: &str) -> bool {
pub fn is_valid_scoreboard_objective_name(name: &str) -> bool {
name.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '+' | '.'))
}
/// Returns whether a string is a valid player name.
/// Returns whether a string is a valid scoreboard target.
#[must_use]
pub fn is_valid_player_name(name: &str) -> bool {
(3..=16).contains(&name.len()) && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
pub fn is_valid_scoreboard_target(name: &str) -> bool {
(..=16).contains(&name.len()) && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}
#[cfg(test)]