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"
|
pathdiff = "0.2.3"
|
||||||
serde = { version = "1.0.217", features = ["derive"], optional = true }
|
serde = { version = "1.0.217", features = ["derive"], optional = true }
|
||||||
# shulkerbox = { version = "0.1.0", default-features = false, 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"
|
strsim = "0.11.1"
|
||||||
strum = { version = "0.27.0", features = ["derive"] }
|
strum = { version = "0.27.0", features = ["derive"] }
|
||||||
thiserror = "2.0.11"
|
thiserror = "2.0.11"
|
||||||
|
|
|
@ -307,6 +307,10 @@ impl Semicolon {
|
||||||
SemicolonStatement::VariableDeclaration(decl) => {
|
SemicolonStatement::VariableDeclaration(decl) => {
|
||||||
decl.analyze_semantics(function_names, macro_names, handler)
|
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(_) => {
|
SemicolonStatement::Expression(_) => {
|
||||||
let err = Error::InvalidAnnotation(InvalidAnnotation {
|
let err = Error::InvalidAnnotation(InvalidAnnotation {
|
||||||
annotation: annotation.assignment.identifier.span,
|
annotation: annotation.assignment.identifier.span,
|
||||||
|
@ -272,7 +280,7 @@ impl Semicolon {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ``` ebnf
|
/// ``` ebnf
|
||||||
/// SemicolonStatement:
|
/// SemicolonStatement:
|
||||||
/// (Expression | VariableDeclaration)
|
/// (Expression | VariableDeclaration | Assignment)
|
||||||
/// ';'
|
/// ';'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -283,6 +291,8 @@ pub enum SemicolonStatement {
|
||||||
Expression(Expression),
|
Expression(Expression),
|
||||||
/// A variable declaration.
|
/// A variable declaration.
|
||||||
VariableDeclaration(VariableDeclaration),
|
VariableDeclaration(VariableDeclaration),
|
||||||
|
/// An assignment.
|
||||||
|
Assignment(Assignment),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for SemicolonStatement {
|
impl SourceElement for SemicolonStatement {
|
||||||
|
@ -290,6 +300,7 @@ impl SourceElement for SemicolonStatement {
|
||||||
match self {
|
match self {
|
||||||
Self::Expression(expression) => expression.span(),
|
Self::Expression(expression) => expression.span(),
|
||||||
Self::VariableDeclaration(declaration) => declaration.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> {
|
impl<'a> Parser<'a> {
|
||||||
/// Parses a [`Block`].
|
/// Parses a [`Block`].
|
||||||
///
|
///
|
||||||
|
@ -808,9 +857,27 @@ impl<'a> Parser<'a> {
|
||||||
self.parse_variable_declaration(handler)
|
self.parse_variable_declaration(handler)
|
||||||
.map(SemicolonStatement::VariableDeclaration)
|
.map(SemicolonStatement::VariableDeclaration)
|
||||||
}
|
}
|
||||||
_ => self
|
_ => {
|
||||||
.parse_expression(handler)
|
// try to parse assignment
|
||||||
.map(SemicolonStatement::Expression),
|
// 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)?;
|
let semicolon = self.parse_punctuation(';', true, handler)?;
|
||||||
|
|
|
@ -36,6 +36,8 @@ pub enum TranspileError {
|
||||||
MismatchedTypes(#[from] MismatchedTypes),
|
MismatchedTypes(#[from] MismatchedTypes),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
FunctionArgumentsNotAllowed(#[from] FunctionArgumentsNotAllowed),
|
FunctionArgumentsNotAllowed(#[from] FunctionArgumentsNotAllowed),
|
||||||
|
#[error(transparent)]
|
||||||
|
AssignmentError(#[from] AssignmentError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a transpilation operation.
|
/// The result of a transpilation operation.
|
||||||
|
@ -227,3 +229,25 @@ impl Display for FunctionArgumentsNotAllowed {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error 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`.
|
/// A transpiler for `Shulkerscript`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Transpiler {
|
pub struct Transpiler {
|
||||||
|
pub(super) main_namespace_name: String,
|
||||||
pub(super) datapack: shulkerbox::datapack::Datapack,
|
pub(super) datapack: shulkerbox::datapack::Datapack,
|
||||||
|
pub(super) setup_cmds: Vec<Command>,
|
||||||
/// Top-level [`Scope`] for each program identifier
|
/// Top-level [`Scope`] for each program identifier
|
||||||
scopes: BTreeMap<String, Arc<Scope<'static>>>,
|
scopes: BTreeMap<String, Arc<Scope<'static>>>,
|
||||||
/// Key: (program identifier, function name)
|
/// Key: (program identifier, function name)
|
||||||
|
@ -51,8 +53,11 @@ impl Transpiler {
|
||||||
/// Creates a new transpiler.
|
/// Creates a new transpiler.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(main_namespace_name: impl Into<String>, pack_format: u8) -> Self {
|
pub fn new(main_namespace_name: impl Into<String>, pack_format: u8) -> Self {
|
||||||
|
let main_namespace_name = main_namespace_name.into();
|
||||||
Self {
|
Self {
|
||||||
|
main_namespace_name: main_namespace_name.clone(),
|
||||||
datapack: shulkerbox::datapack::Datapack::new(main_namespace_name, pack_format),
|
datapack: shulkerbox::datapack::Datapack::new(main_namespace_name, pack_format),
|
||||||
|
setup_cmds: Vec::new(),
|
||||||
scopes: BTreeMap::new(),
|
scopes: BTreeMap::new(),
|
||||||
functions: BTreeMap::new(),
|
functions: BTreeMap::new(),
|
||||||
aliases: HashMap::new(),
|
aliases: HashMap::new(),
|
||||||
|
@ -119,6 +124,14 @@ impl Transpiler {
|
||||||
self.get_or_transpile_function(&identifier_span, None, &scope, handler)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,8 +231,8 @@ impl Transpiler {
|
||||||
Declaration::Tag(tag) => {
|
Declaration::Tag(tag) => {
|
||||||
let namespace = self
|
let namespace = self
|
||||||
.datapack
|
.datapack
|
||||||
.namespace_mut(&namespace.namespace_name().str_content());
|
.namespace_mut(namespace.namespace_name().str_content());
|
||||||
let sb_tag = namespace.tag_mut(&tag.name().str_content(), tag.tag_type());
|
let sb_tag = namespace.tag_mut(tag.name().str_content(), tag.tag_type());
|
||||||
|
|
||||||
if let Some(list) = &tag.entries().list {
|
if let Some(list) = &tag.entries().list {
|
||||||
for value in list.elements() {
|
for value in list.elements() {
|
||||||
|
@ -553,6 +566,12 @@ impl Transpiler {
|
||||||
SemicolonStatement::VariableDeclaration(decl) => {
|
SemicolonStatement::VariableDeclaration(decl) => {
|
||||||
self.transpile_variable_declaration(decl, program_identifier, scope, handler)
|
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 chksum_md5 as md5;
|
||||||
use shulkerbox::prelude::Command;
|
use shulkerbox::prelude::{Command, Condition, Execute};
|
||||||
use strum::EnumIs;
|
use strum::EnumIs;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{self, source_file::SourceElement as _, Handler},
|
base::{self, source_file::SourceElement as _, Handler},
|
||||||
lexical::token::KeywordKind,
|
lexical::token::{Identifier, KeywordKind},
|
||||||
syntax::syntax_tree::{
|
syntax::syntax_tree::{
|
||||||
expression::{Expression, Primary},
|
expression::{Expression, Primary},
|
||||||
statement::{SingleVariableDeclaration, VariableDeclaration},
|
statement::{SingleVariableDeclaration, VariableDeclaration},
|
||||||
|
@ -21,8 +21,9 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
error::IllegalAnnotationContent, expression::DataLocation, FunctionData,
|
error::{AssignmentError, IllegalAnnotationContent},
|
||||||
TranspileAnnotationValue, TranspileError, TranspileResult, Transpiler,
|
expression::DataLocation,
|
||||||
|
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult, Transpiler,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Stores the data required to access a variable.
|
/// Stores the data required to access a variable.
|
||||||
|
@ -87,6 +88,8 @@ pub struct Scope<'a> {
|
||||||
parent: Option<&'a Arc<Self>>,
|
parent: Option<&'a Arc<Self>>,
|
||||||
/// Variables stored in the scope.
|
/// Variables stored in the scope.
|
||||||
variables: RwLock<HashMap<String, Arc<VariableData>>>,
|
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> {
|
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.
|
/// Sets a variable in the scope.
|
||||||
pub fn set_variable(&self, name: &str, var: VariableData) {
|
pub fn set_variable(&self, name: &str, var: VariableData) {
|
||||||
self.variables
|
let prev = self
|
||||||
|
.variables
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.insert(name.to_string(), Arc::new(var));
|
.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.
|
/// 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("parent", &self.parent);
|
||||||
|
|
||||||
s.field("variables", &VariableWrapper(&self.variables));
|
s.field("variables", &VariableWrapper(&self.variables));
|
||||||
|
s.field("shadowed", &self.shadowed);
|
||||||
s.finish()
|
s.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,7 +211,6 @@ impl Transpiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::too_many_lines)]
|
|
||||||
fn transpile_single_variable_declaration(
|
fn transpile_single_variable_declaration(
|
||||||
&mut self,
|
&mut self,
|
||||||
single: &SingleVariableDeclaration,
|
single: &SingleVariableDeclaration,
|
||||||
|
@ -209,97 +235,14 @@ impl Transpiler {
|
||||||
handler.receive(error.clone());
|
handler.receive(error.clone());
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
let (name, target) = if let Some(deobfuscate_annotation) = deobfuscate_annotation {
|
let (name, target) =
|
||||||
let deobfuscate_annotation_value =
|
get_single_data_location_identifiers(single, program_identifier, scope, handler)?;
|
||||||
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
match variable_type {
|
match variable_type {
|
||||||
KeywordKind::Int => {
|
KeywordKind::Int => {
|
||||||
if !self.datapack.scoreboards().contains_key(&name) {
|
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(
|
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"),
|
_ => todo!("implement other variable types"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +269,7 @@ impl Transpiler {
|
||||||
|| Ok(Vec::new()),
|
|| Ok(Vec::new()),
|
||||||
|assignment| {
|
|assignment| {
|
||||||
self.transpile_assignment(
|
self.transpile_assignment(
|
||||||
single.identifier().span.str(),
|
single.identifier(),
|
||||||
assignment.expression(),
|
assignment.expression(),
|
||||||
scope,
|
scope,
|
||||||
handler,
|
handler,
|
||||||
|
@ -326,27 +278,166 @@ impl Transpiler {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transpile_assignment(
|
pub(super) fn transpile_assignment(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
identifier: &Identifier,
|
||||||
expression: &crate::syntax::syntax_tree::expression::Expression,
|
expression: &crate::syntax::syntax_tree::expression::Expression,
|
||||||
scope: &Arc<Scope>,
|
scope: &Arc<Scope>,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> TranspileResult<Vec<Command>> {
|
) -> TranspileResult<Vec<Command>> {
|
||||||
let target = scope.get_variable(name).unwrap();
|
if let Some(target) = scope.get_variable(identifier.span.str()) {
|
||||||
let data_location = match target.as_ref() {
|
let data_location = match target.as_ref() {
|
||||||
VariableData::BooleanStorage { storage_name, path } => DataLocation::Storage {
|
VariableData::BooleanStorage { storage_name, path } => Ok(DataLocation::Storage {
|
||||||
storage_name: storage_name.to_owned(),
|
storage_name: storage_name.to_owned(),
|
||||||
path: path.to_owned(),
|
path: path.to_owned(),
|
||||||
r#type: super::expression::StorageType::Boolean,
|
r#type: super::expression::StorageType::Boolean,
|
||||||
},
|
}),
|
||||||
VariableData::ScoreboardValue { objective, target } => DataLocation::ScoreboardValue {
|
VariableData::ScoreboardValue { objective, target } => {
|
||||||
objective: objective.to_owned(),
|
Ok(DataLocation::ScoreboardValue {
|
||||||
target: target.to_owned(),
|
objective: objective.to_owned(),
|
||||||
},
|
target: target.to_owned(),
|
||||||
_ => todo!("implement other variable types"),
|
})
|
||||||
};
|
}
|
||||||
self.transpile_expression(expression, &data_location, scope, handler)
|
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")
|
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