Compare commits
3 Commits
8ae065f582
...
e772c4b2c2
Author | SHA1 | Date |
---|---|---|
|
e772c4b2c2 | |
|
94693cce6c | |
|
c72fbfd148 |
|
@ -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 = "811d71508208f8415d881f7c4d73429f1a73f36a", default-features = false, optional = true }
|
||||
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "cf8c922704a38980def3c6267cee2eeba304394c", default-features = false, optional = true }
|
||||
strsim = "0.11.1"
|
||||
strum = { version = "0.27.0", features = ["derive"] }
|
||||
thiserror = "2.0.11"
|
||||
|
|
|
@ -307,6 +307,10 @@ impl Semicolon {
|
|||
SemicolonStatement::VariableDeclaration(decl) => {
|
||||
decl.analyze_semantics(function_names, macro_names, handler)
|
||||
}
|
||||
SemicolonStatement::Assignment(_assignment) => {
|
||||
// TODO: correctly analyze the semantics of the assignment
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,6 +93,14 @@ impl Statement {
|
|||
})
|
||||
})
|
||||
}
|
||||
SemicolonStatement::Assignment(_) => {
|
||||
let err = Error::InvalidAnnotation(InvalidAnnotation {
|
||||
annotation: annotation.assignment.identifier.span,
|
||||
target: "assignments".to_string(),
|
||||
});
|
||||
|
||||
Err(err)
|
||||
}
|
||||
SemicolonStatement::Expression(_) => {
|
||||
let err = Error::InvalidAnnotation(InvalidAnnotation {
|
||||
annotation: annotation.assignment.identifier.span,
|
||||
|
@ -272,7 +280,7 @@ impl Semicolon {
|
|||
/// Syntax Synopsis:
|
||||
/// ``` ebnf
|
||||
/// SemicolonStatement:
|
||||
/// (Expression | VariableDeclaration)
|
||||
/// (Expression | VariableDeclaration | Assignment)
|
||||
/// ';'
|
||||
/// ;
|
||||
/// ```
|
||||
|
@ -283,6 +291,8 @@ pub enum SemicolonStatement {
|
|||
Expression(Expression),
|
||||
/// A variable declaration.
|
||||
VariableDeclaration(VariableDeclaration),
|
||||
/// An assignment.
|
||||
Assignment(Assignment),
|
||||
}
|
||||
|
||||
impl SourceElement for SemicolonStatement {
|
||||
|
@ -290,6 +300,7 @@ impl SourceElement for SemicolonStatement {
|
|||
match self {
|
||||
Self::Expression(expression) => expression.span(),
|
||||
Self::VariableDeclaration(declaration) => declaration.span(),
|
||||
Self::Assignment(assignment) => assignment.span(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -669,6 +680,44 @@ impl TagVariableDeclaration {
|
|||
}
|
||||
}
|
||||
|
||||
/// Represents an assignment in the syntax tree.
|
||||
///
|
||||
/// Syntax Synopsis:
|
||||
/// ```ebnf
|
||||
/// Assignment:
|
||||
/// Identifier '=' Expression
|
||||
/// ```
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
||||
pub struct Assignment {
|
||||
/// The identifier of the assignment.
|
||||
#[get = "pub"]
|
||||
identifier: Identifier,
|
||||
/// The equals sign of the assignment.
|
||||
#[get = "pub"]
|
||||
equals: Punctuation,
|
||||
/// The expression of the assignment.
|
||||
#[get = "pub"]
|
||||
expression: Expression,
|
||||
}
|
||||
|
||||
impl SourceElement for Assignment {
|
||||
fn span(&self) -> Span {
|
||||
self.identifier
|
||||
.span()
|
||||
.join(&self.expression.span())
|
||||
.expect("The span of the assignment is invalid.")
|
||||
}
|
||||
}
|
||||
|
||||
impl Assignment {
|
||||
/// Dissolves the [`Assignment`] into its components.
|
||||
#[must_use]
|
||||
pub fn dissolve(self) -> (Identifier, Punctuation, Expression) {
|
||||
(self.identifier, self.equals, self.expression)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
/// Parses a [`Block`].
|
||||
///
|
||||
|
@ -808,9 +857,27 @@ impl<'a> Parser<'a> {
|
|||
self.parse_variable_declaration(handler)
|
||||
.map(SemicolonStatement::VariableDeclaration)
|
||||
}
|
||||
_ => self
|
||||
.parse_expression(handler)
|
||||
.map(SemicolonStatement::Expression),
|
||||
_ => {
|
||||
// try to parse assignment
|
||||
// TODO: improve
|
||||
#[expect(clippy::option_if_let_else)]
|
||||
if let Ok(assignment) = self.try_parse(|p| {
|
||||
let identifier = p.parse_identifier(&VoidHandler)?;
|
||||
let equals = p.parse_punctuation('=', true, &VoidHandler)?;
|
||||
let expression = p.parse_expression(&VoidHandler)?;
|
||||
|
||||
Ok(SemicolonStatement::Assignment(Assignment {
|
||||
identifier,
|
||||
equals,
|
||||
expression,
|
||||
}))
|
||||
}) {
|
||||
Ok(assignment)
|
||||
} else {
|
||||
self.parse_expression(handler)
|
||||
.map(SemicolonStatement::Expression)
|
||||
}
|
||||
}
|
||||
}?;
|
||||
|
||||
let semicolon = self.parse_punctuation(';', true, handler)?;
|
||||
|
|
|
@ -36,6 +36,8 @@ pub enum TranspileError {
|
|||
MismatchedTypes(#[from] MismatchedTypes),
|
||||
#[error(transparent)]
|
||||
FunctionArgumentsNotAllowed(#[from] FunctionArgumentsNotAllowed),
|
||||
#[error(transparent)]
|
||||
AssignmentError(#[from] AssignmentError),
|
||||
}
|
||||
|
||||
/// The result of a transpilation operation.
|
||||
|
@ -227,3 +229,25 @@ impl Display for FunctionArgumentsNotAllowed {
|
|||
}
|
||||
|
||||
impl std::error::Error for FunctionArgumentsNotAllowed {}
|
||||
|
||||
/// An error that occurs when an expression can not evaluate to the wanted type.
|
||||
#[expect(clippy::module_name_repetitions)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AssignmentError {
|
||||
pub identifier: Span,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl Display for AssignmentError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", Message::new(Severity::Error, &self.message))?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
"\n{}",
|
||||
SourceCodeDisplay::new(&self.identifier, Option::<u8>::None)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for AssignmentError {}
|
||||
|
|
|
@ -38,7 +38,9 @@ use super::{
|
|||
/// A transpiler for `Shulkerscript`.
|
||||
#[derive(Debug)]
|
||||
pub struct Transpiler {
|
||||
pub(super) main_namespace_name: String,
|
||||
pub(super) datapack: shulkerbox::datapack::Datapack,
|
||||
pub(super) setup_cmds: Vec<Command>,
|
||||
/// Top-level [`Scope`] for each program identifier
|
||||
scopes: BTreeMap<String, Arc<Scope<'static>>>,
|
||||
/// Key: (program identifier, function name)
|
||||
|
@ -51,8 +53,11 @@ impl Transpiler {
|
|||
/// Creates a new transpiler.
|
||||
#[must_use]
|
||||
pub fn new(main_namespace_name: impl Into<String>, pack_format: u8) -> Self {
|
||||
let main_namespace_name = main_namespace_name.into();
|
||||
Self {
|
||||
main_namespace_name: main_namespace_name.clone(),
|
||||
datapack: shulkerbox::datapack::Datapack::new(main_namespace_name, pack_format),
|
||||
setup_cmds: Vec::new(),
|
||||
scopes: BTreeMap::new(),
|
||||
functions: BTreeMap::new(),
|
||||
aliases: HashMap::new(),
|
||||
|
@ -119,6 +124,14 @@ impl Transpiler {
|
|||
self.get_or_transpile_function(&identifier_span, None, &scope, handler)?;
|
||||
}
|
||||
|
||||
if !self.setup_cmds.is_empty() {
|
||||
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));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -218,8 +231,8 @@ impl Transpiler {
|
|||
Declaration::Tag(tag) => {
|
||||
let namespace = self
|
||||
.datapack
|
||||
.namespace_mut(&namespace.namespace_name().str_content());
|
||||
let sb_tag = namespace.tag_mut(&tag.name().str_content(), tag.tag_type());
|
||||
.namespace_mut(namespace.namespace_name().str_content());
|
||||
let sb_tag = namespace.tag_mut(tag.name().str_content(), tag.tag_type());
|
||||
|
||||
if let Some(list) = &tag.entries().list {
|
||||
for value in list.elements() {
|
||||
|
@ -553,6 +566,12 @@ impl Transpiler {
|
|||
SemicolonStatement::VariableDeclaration(decl) => {
|
||||
self.transpile_variable_declaration(decl, program_identifier, scope, handler)
|
||||
}
|
||||
SemicolonStatement::Assignment(assignment) => self.transpile_assignment(
|
||||
assignment.identifier(),
|
||||
assignment.expression(),
|
||||
scope,
|
||||
handler,
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,12 @@ use std::{
|
|||
};
|
||||
|
||||
use chksum_md5 as md5;
|
||||
use shulkerbox::prelude::Command;
|
||||
use shulkerbox::prelude::{Command, Condition, Execute};
|
||||
use strum::EnumIs;
|
||||
|
||||
use crate::{
|
||||
base::{self, source_file::SourceElement as _, Handler},
|
||||
lexical::token::KeywordKind,
|
||||
lexical::token::{Identifier, KeywordKind},
|
||||
syntax::syntax_tree::{
|
||||
expression::{Expression, Primary},
|
||||
statement::{SingleVariableDeclaration, VariableDeclaration},
|
||||
|
@ -21,8 +21,9 @@ use crate::{
|
|||
};
|
||||
|
||||
use super::{
|
||||
error::IllegalAnnotationContent, expression::DataLocation, FunctionData,
|
||||
TranspileAnnotationValue, TranspileError, TranspileResult, Transpiler,
|
||||
error::{AssignmentError, IllegalAnnotationContent},
|
||||
expression::DataLocation,
|
||||
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult, Transpiler,
|
||||
};
|
||||
|
||||
/// Stores the data required to access a variable.
|
||||
|
@ -87,6 +88,8 @@ pub struct Scope<'a> {
|
|||
parent: Option<&'a Arc<Self>>,
|
||||
/// Variables stored in the scope.
|
||||
variables: RwLock<HashMap<String, Arc<VariableData>>>,
|
||||
/// How many times the variable has been shadowed in the current scope.
|
||||
shadowed: RwLock<HashMap<String, usize>>,
|
||||
}
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
|
@ -117,12 +120,35 @@ impl<'a> Scope<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the number of times a variable has been shadowed.
|
||||
pub fn get_variable_shadow_count(&self, name: &str) -> usize {
|
||||
let count = self
|
||||
.shadowed
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(name)
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
self.parent.as_ref().map_or(count, |parent| {
|
||||
count
|
||||
+ parent.get_variable_shadow_count(name)
|
||||
+ usize::from(parent.get_variable(name).is_some())
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets a variable in the scope.
|
||||
pub fn set_variable(&self, name: &str, var: VariableData) {
|
||||
self.variables
|
||||
let prev = self
|
||||
.variables
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(name.to_string(), Arc::new(var));
|
||||
*self
|
||||
.shadowed
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(name.to_string())
|
||||
.or_default() += 1;
|
||||
}
|
||||
|
||||
/// Gets the variables stored in the current scope.
|
||||
|
@ -161,6 +187,7 @@ impl<'a> Debug for Scope<'a> {
|
|||
s.field("parent", &self.parent);
|
||||
|
||||
s.field("variables", &VariableWrapper(&self.variables));
|
||||
s.field("shadowed", &self.shadowed);
|
||||
s.finish()
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +211,6 @@ impl Transpiler {
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn transpile_single_variable_declaration(
|
||||
&mut self,
|
||||
single: &SingleVariableDeclaration,
|
||||
|
@ -209,97 +235,14 @@ impl Transpiler {
|
|||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
let (name, target) = if let Some(deobfuscate_annotation) = deobfuscate_annotation {
|
||||
let deobfuscate_annotation_value =
|
||||
TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone());
|
||||
|
||||
if let TranspileAnnotationValue::Map(map) = deobfuscate_annotation_value {
|
||||
if map.len() > 2 {
|
||||
let error =
|
||||
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation must have at most 2 key-value pairs."
|
||||
.to_string(),
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
if let (Some(name), Some(target)) = (map.get("name"), map.get("target")) {
|
||||
if let (
|
||||
TranspileAnnotationValue::Expression(objective),
|
||||
TranspileAnnotationValue::Expression(target),
|
||||
) = (name, target)
|
||||
{
|
||||
if let (Some(name_eval), Some(target_eval)) =
|
||||
(objective.comptime_eval(), target.comptime_eval())
|
||||
{
|
||||
// TODO: change invalid criteria if boolean
|
||||
if !crate::util::is_valid_scoreboard_name(&name_eval) {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation 'name' must be a valid scoreboard name.".to_string()
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
if !crate::util::is_valid_player_name(&target_eval) {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation 'target' must be a valid player name.".to_string()
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
(name_eval, target_eval)
|
||||
} else {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation 'name' or 'target' could not have been evaluated at compile time.".to_string()
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
} else {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation 'name' and 'target' must be compile time expressions.".to_string()
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
} else {
|
||||
let error =
|
||||
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message:
|
||||
"Deobfuscate annotation must have both 'name' and 'target' keys."
|
||||
.to_string(),
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
} else {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation must be a map.".to_string(),
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
} else {
|
||||
let name =
|
||||
"shu_values_".to_string() + &md5::hash(program_identifier).to_hex_lowercase();
|
||||
let target = md5::hash((Arc::as_ptr(scope) as usize).to_le_bytes())
|
||||
.to_hex_lowercase()
|
||||
.split_off(16);
|
||||
|
||||
(name, target)
|
||||
};
|
||||
let (name, target) =
|
||||
get_single_data_location_identifiers(single, program_identifier, scope, handler)?;
|
||||
|
||||
match variable_type {
|
||||
KeywordKind::Int => {
|
||||
if !self.datapack.scoreboards().contains_key(&name) {
|
||||
self.datapack.register_scoreboard(&name, None, None);
|
||||
self.datapack
|
||||
.register_scoreboard(&name, None::<&str>, None::<&str>);
|
||||
}
|
||||
|
||||
scope.set_variable(
|
||||
|
@ -310,6 +253,15 @@ impl Transpiler {
|
|||
},
|
||||
);
|
||||
}
|
||||
KeywordKind::Bool => {
|
||||
scope.set_variable(
|
||||
single.identifier().span.str(),
|
||||
VariableData::BooleanStorage {
|
||||
storage_name: name,
|
||||
path: target,
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => todo!("implement other variable types"),
|
||||
}
|
||||
|
||||
|
@ -317,7 +269,7 @@ impl Transpiler {
|
|||
|| Ok(Vec::new()),
|
||||
|assignment| {
|
||||
self.transpile_assignment(
|
||||
single.identifier().span.str(),
|
||||
single.identifier(),
|
||||
assignment.expression(),
|
||||
scope,
|
||||
handler,
|
||||
|
@ -326,27 +278,166 @@ impl Transpiler {
|
|||
)
|
||||
}
|
||||
|
||||
fn transpile_assignment(
|
||||
pub(super) fn transpile_assignment(
|
||||
&mut self,
|
||||
name: &str,
|
||||
identifier: &Identifier,
|
||||
expression: &crate::syntax::syntax_tree::expression::Expression,
|
||||
scope: &Arc<Scope>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> TranspileResult<Vec<Command>> {
|
||||
let target = scope.get_variable(name).unwrap();
|
||||
let data_location = match target.as_ref() {
|
||||
VariableData::BooleanStorage { storage_name, path } => DataLocation::Storage {
|
||||
storage_name: storage_name.to_owned(),
|
||||
path: path.to_owned(),
|
||||
r#type: super::expression::StorageType::Boolean,
|
||||
},
|
||||
VariableData::ScoreboardValue { objective, target } => DataLocation::ScoreboardValue {
|
||||
objective: objective.to_owned(),
|
||||
target: target.to_owned(),
|
||||
},
|
||||
_ => todo!("implement other variable types"),
|
||||
};
|
||||
self.transpile_expression(expression, &data_location, scope, handler)
|
||||
if let Some(target) = scope.get_variable(identifier.span.str()) {
|
||||
let data_location = match target.as_ref() {
|
||||
VariableData::BooleanStorage { storage_name, path } => Ok(DataLocation::Storage {
|
||||
storage_name: storage_name.to_owned(),
|
||||
path: path.to_owned(),
|
||||
r#type: super::expression::StorageType::Boolean,
|
||||
}),
|
||||
VariableData::ScoreboardValue { objective, target } => {
|
||||
Ok(DataLocation::ScoreboardValue {
|
||||
objective: objective.to_owned(),
|
||||
target: target.to_owned(),
|
||||
})
|
||||
}
|
||||
VariableData::Function { .. } | VariableData::FunctionArgument { .. } => {
|
||||
Err(TranspileError::AssignmentError(AssignmentError {
|
||||
identifier: identifier.span(),
|
||||
message: format!(
|
||||
"Cannot assign to a {}.",
|
||||
if matches!(target.as_ref(), VariableData::Function { .. }) {
|
||||
"function"
|
||||
} else {
|
||||
"function argument"
|
||||
}
|
||||
),
|
||||
}))
|
||||
}
|
||||
_ => todo!("implement other variable types"),
|
||||
}?;
|
||||
self.transpile_expression(expression, &data_location, scope, handler)
|
||||
} else {
|
||||
Err(TranspileError::AssignmentError(AssignmentError {
|
||||
identifier: identifier.span(),
|
||||
message: "Variable does not exist.".to_string(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn get_single_data_location_identifiers(
|
||||
single: &SingleVariableDeclaration,
|
||||
program_identifier: &str,
|
||||
scope: &Arc<Scope>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) -> TranspileResult<(String, String)> {
|
||||
let mut deobfuscate_annotations = single
|
||||
.annotations()
|
||||
.iter()
|
||||
.filter(|a| a.has_identifier("deobfuscate"));
|
||||
|
||||
let variable_type = single.variable_type().keyword;
|
||||
|
||||
let deobfuscate_annotation = deobfuscate_annotations.next();
|
||||
|
||||
if let Some(duplicate) = deobfuscate_annotations.next() {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: duplicate.span(),
|
||||
message: "Multiple deobfuscate annotations are not allowed.".to_string(),
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
if let Some(deobfuscate_annotation) = deobfuscate_annotation {
|
||||
let deobfuscate_annotation_value =
|
||||
TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone());
|
||||
|
||||
if let TranspileAnnotationValue::Map(map) = deobfuscate_annotation_value {
|
||||
if map.len() > 2 {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation must have at most 2 key-value pairs."
|
||||
.to_string(),
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
if let (Some(name), Some(target)) = (map.get("name"), map.get("target")) {
|
||||
if let (
|
||||
TranspileAnnotationValue::Expression(objective),
|
||||
TranspileAnnotationValue::Expression(target),
|
||||
) = (name, target)
|
||||
{
|
||||
if let (Some(name_eval), Some(target_eval)) =
|
||||
(objective.comptime_eval(), target.comptime_eval())
|
||||
{
|
||||
// TODO: change invalid criteria if boolean
|
||||
if !crate::util::is_valid_scoreboard_name(&name_eval) {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation 'name' must be a valid scoreboard name.".to_string()
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
if !crate::util::is_valid_player_name(&target_eval) {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation 'target' must be a valid player name.".to_string()
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
return Err(error);
|
||||
}
|
||||
Ok((name_eval, target_eval))
|
||||
} else {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation 'name' or 'target' could not have been evaluated at compile time.".to_string()
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
Err(error)
|
||||
}
|
||||
} else {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation 'name' and 'target' must be compile time expressions.".to_string()
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
Err(error)
|
||||
}
|
||||
} else {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation must have both 'name' and 'target' keys."
|
||||
.to_string(),
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
Err(error)
|
||||
}
|
||||
} else {
|
||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||
annotation: deobfuscate_annotation.span(),
|
||||
message: "Deobfuscate annotation must be a map.".to_string(),
|
||||
});
|
||||
handler.receive(error.clone());
|
||||
Err(error)
|
||||
}
|
||||
} else {
|
||||
let hashed = md5::hash(program_identifier).to_hex_lowercase();
|
||||
let name = "shu_values_".to_string() + &hashed;
|
||||
let identifier_name = single.identifier().span.str();
|
||||
// TODO: generate same name each time (not dependent on pointer)
|
||||
let mut target = md5::hash(format!(
|
||||
"{scope}\0{identifier_name}\0{shadowed}",
|
||||
scope = Arc::as_ptr(scope) as usize,
|
||||
shadowed = scope.get_variable_shadow_count(identifier_name)
|
||||
))
|
||||
.to_hex_lowercase();
|
||||
|
||||
if matches!(variable_type, KeywordKind::Int) {
|
||||
target.split_off(16);
|
||||
}
|
||||
|
||||
Ok((name, target))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -392,4 +483,23 @@ mod tests {
|
|||
panic!("Variable missing")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shadowed_count() {
|
||||
let scope = Scope::new();
|
||||
scope.set_variable(
|
||||
"test",
|
||||
VariableData::Scoreboard {
|
||||
objective: "test1".to_string(),
|
||||
},
|
||||
);
|
||||
let child = Scope::with_parent(&scope);
|
||||
child.set_variable(
|
||||
"test",
|
||||
VariableData::Scoreboard {
|
||||
objective: "test2".to_string(),
|
||||
},
|
||||
);
|
||||
assert_eq!(child.get_variable_shadow_count("test"), 1);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue