From 1e82d2321f036dfa71c8df4a1741cc6cb5fbf0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Mon, 7 Apr 2025 01:09:34 +0200 Subject: [PATCH] allow importing global variables, get rid of functions and aliases field of transpiler --- src/lib.rs | 13 +---- src/semantic/mod.rs | 20 ++----- src/syntax/syntax_tree/declaration.rs | 29 +++++++---- src/transpile/function.rs | 44 +++++----------- src/transpile/transpiler.rs | 75 +++++++++++++++++++-------- src/transpile/variables.rs | 26 ++++++---- 6 files changed, 108 insertions(+), 99 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2898c96..b2dfacf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,9 +115,6 @@ pub fn parse( /// Transpiles the given source code into a shulkerbox [`Datapack`]. /// -/// # Parameters: -/// - `script_paths`: A list of tuples containing the identifier and the path of each script file. -/// /// # Errors /// - If an error occurs during [`parse()`] /// - If an error occurs while transpiling the source code. @@ -164,15 +161,7 @@ where Ok(program) }) - .collect::>(); - - if programs.iter().any(Result::is_err) { - return Err(programs.into_iter().find_map(Result::err).unwrap()); - } - let programs = programs - .into_iter() - .filter_map(Result::ok) - .collect::>(); + .collect::>>()?; tracing::info!("Transpiling the source code."); diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index 1d18ed7..b1c9f0d 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -61,7 +61,7 @@ fn get_global_scope(declarations: &[Declaration]) -> SemanticScope<'static> { let scope = SemanticScope::new(); for declaration in declarations { - declaration.add_to_scope(&scope); + declaration.add_to_semantic_scope(&scope); } scope @@ -83,23 +83,13 @@ impl Namespace { } impl Declaration { - fn add_to_scope(&self, scope: &SemanticScope) { + fn add_to_semantic_scope(&self, scope: &SemanticScope) { match self { Self::Function(func) => { let name = func.identifier(); scope.set_variable(name.span.str(), VariableType::Function); } - Self::Import(imp) => match imp.items() { - ImportItems::All(_) => {} - ImportItems::Named(items) => { - for item in items.elements() { - if scope.get_variable(item.span.str()) != Some(VariableType::Function) { - scope.set_variable(item.span.str(), VariableType::Function); - } - } - } - }, - Self::GlobalVariable((var, _)) => { + Self::GlobalVariable((_, var, _)) => { let name = var.identifier(); let var_type = match var { VariableDeclaration::Array(arr) => match arr.variable_type().keyword { @@ -118,7 +108,7 @@ impl Declaration { }; scope.set_variable(name.span.str(), var_type); } - Self::Tag(_) => {} + Self::Import(_) | Self::Tag(_) => {} } } @@ -156,7 +146,7 @@ impl Declaration { } }, - Self::GlobalVariable((var, _)) => var.analyze_semantics(scope, handler), + Self::GlobalVariable((_, var, _)) => var.analyze_semantics(scope, handler), Self::Tag(_) => Ok(()), } diff --git a/src/syntax/syntax_tree/declaration.rs b/src/syntax/syntax_tree/declaration.rs index bfb8865..fe2baac 100644 --- a/src/syntax/syntax_tree/declaration.rs +++ b/src/syntax/syntax_tree/declaration.rs @@ -37,7 +37,7 @@ use super::{ /// Function /// | Import /// | Tag -/// | (VariableDeclaration ';') +/// | ('pub'? VariableDeclaration ';') /// ; /// ``` #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -46,7 +46,7 @@ pub enum Declaration { Function(Function), Import(Import), Tag(Tag), - GlobalVariable((VariableDeclaration, Punctuation)), + GlobalVariable((Option, VariableDeclaration, Punctuation)), } impl SourceElement for Declaration { @@ -55,8 +55,9 @@ impl SourceElement for Declaration { Self::Function(function) => function.span(), Self::Import(import) => import.span(), Self::Tag(tag) => tag.span(), - Self::GlobalVariable((variable, semicolon)) => variable - .span() + Self::GlobalVariable((pub_kw, variable, semicolon)) => pub_kw + .as_ref() + .map_or_else(|| variable.span(), SourceElement::span) .join(&semicolon.span) .expect("invalid declaration span"), } @@ -75,10 +76,10 @@ impl Declaration { Ok(Self::Function(function)) } - Self::GlobalVariable((var, semi)) => { + Self::GlobalVariable((pub_kw, var, semi)) => { let var_with_annotation = var.with_annotation(annotation)?; - Ok(Self::GlobalVariable((var_with_annotation, semi))) + Ok(Self::GlobalVariable((pub_kw, var_with_annotation, semi))) } _ => { let err = Error::InvalidAnnotation(InvalidAnnotation { @@ -356,11 +357,19 @@ impl Parser<'_> { Reading::Atomic(Token::Keyword(pub_keyword)) if pub_keyword.keyword == KeywordKind::Pub => { - let function = self.parse_function(handler)?; + if let Ok(function) = self.try_parse(|parser| parser.parse_function(&VoidHandler)) { + tracing::trace!("Parsed function '{:?}'", function.identifier.span.str()); - tracing::trace!("Parsed function '{:?}'", function.identifier.span.str()); + Ok(Declaration::Function(function)) + } else { + // eat the pub keyword + self.forward(); - Ok(Declaration::Function(function)) + let var = self.parse_variable_declaration(handler)?; + let semi = self.parse_punctuation(';', true, handler)?; + + Ok(Declaration::GlobalVariable((Some(pub_keyword), var, semi))) + } } // parse annotations @@ -480,7 +489,7 @@ impl Parser<'_> { let var = self.parse_variable_declaration(handler)?; let semi = self.parse_punctuation(';', true, handler)?; - Ok(Declaration::GlobalVariable((var, semi))) + Ok(Declaration::GlobalVariable((None, var, semi))) } unexpected => { diff --git a/src/transpile/function.rs b/src/transpile/function.rs index e2eb30e..630f7d1 100644 --- a/src/transpile/function.rs +++ b/src/transpile/function.rs @@ -1,6 +1,6 @@ use chksum_md5 as md5; use enum_as_inner::EnumAsInner; -use std::{collections::BTreeMap, sync::Arc}; +use std::{borrow::ToOwned, collections::BTreeMap, sync::Arc}; use shulkerbox::datapack::{Command, Execute}; @@ -48,41 +48,27 @@ impl Transpiler { handler: &impl Handler, ) -> TranspileResult<(String, TranspiledFunctionArguments)> { let program_identifier = identifier_span.source_file().identifier(); - let program_query = ( - program_identifier.to_string(), - identifier_span.str().to_string(), - ); - let alias_query = self.aliases.get(&program_query).cloned(); - let already_transpiled = scope - .get_variable(identifier_span.str()) + let function = scope.get_variable(identifier_span.str()); + let already_transpiled = function + .as_ref() .expect("called function should be in scope") .as_ref() .as_function() - .map(|(_, path)| path.get().is_some()) + .map(|(_, path, _)| path.get().is_some()) .expect("called variable should be of type function"); - let function_data = scope - .get_variable(identifier_span.str()) - .or_else(|| { - alias_query - .clone() - .and_then(|(alias_program_identifier, alias_function_name)| { - self.scopes - .get(&alias_program_identifier) - .and_then(|s| s.get_variable(&alias_function_name)) - }) - }) - .ok_or_else(|| { - let error = TranspileError::MissingFunctionDeclaration( - MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope), - ); - handler.receive(error.clone()); - error - })?; + let function_data = function.ok_or_else(|| { + let error = TranspileError::MissingFunctionDeclaration( + MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope), + ); + handler.receive(error.clone()); + error + })?; let VariableData::Function { function_data, path: function_path, + function_scope, } = function_data.as_ref() else { unreachable!("must be of correct type, otherwise errored out before"); @@ -134,8 +120,6 @@ impl Transpiler { function_path.set(function_location.clone()).unwrap(); - let function_scope = Scope::with_parent(scope); - for (i, param) in function_data.parameters.iter().enumerate() { let param_str = param.identifier().span.str(); match param.variable_type() { @@ -181,7 +165,7 @@ impl Transpiler { } let commands = - self.transpile_function(&statements, program_identifier, &function_scope, handler)?; + self.transpile_function(&statements, program_identifier, function_scope, handler)?; let namespace = self.datapack.namespace_mut(&function_data.namespace); diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 44f4322..89b2300 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -1,7 +1,7 @@ //! Transpiler for `Shulkerscript` use std::{ - collections::{BTreeMap, HashMap, HashSet}, + collections::{BTreeMap, HashSet}, ops::Deref, sync::{Arc, OnceLock}, }; @@ -40,11 +40,7 @@ pub struct Transpiler { pub(super) initialized_constant_scores: HashSet, pub(super) temp_counter: usize, /// Top-level [`Scope`] for each program identifier - pub(super) scopes: BTreeMap>>, - /// Key: (program identifier, function name) - pub(super) functions: BTreeMap<(String, String), FunctionData>, - /// Key: alias, Value: target - pub(super) aliases: HashMap<(String, String), (String, String)>, + pub(super) scopes: BTreeMap>, } impl Transpiler { @@ -59,8 +55,6 @@ impl Transpiler { initialized_constant_scores: HashSet::new(), temp_counter: 0, scopes: BTreeMap::new(), - functions: BTreeMap::new(), - aliases: HashMap::new(), } } @@ -75,7 +69,11 @@ impl Transpiler { handler: &impl Handler, ) -> Result { tracing::trace!("Transpiling program declarations"); + + let mut aliases = Vec::new(); + for program in programs { + let mut program_aliases = BTreeMap::new(); let program_identifier = program .namespace() .span() @@ -84,17 +82,48 @@ impl Transpiler { .clone(); let scope = self .scopes - .entry(program_identifier) + .entry(program_identifier.clone()) .or_insert_with(Scope::with_internal_functions) .to_owned(); - self.transpile_program_declarations(program, &scope, handler)?; + self.transpile_program_declarations(program, &mut program_aliases, &scope, handler)?; + aliases.push((scope.clone(), program_aliases)); + } + + for (scope, program_aliases) in aliases { + for (alias_name, (alias_program_identifier, actual_name)) in program_aliases { + if let Some(alias_scope) = self.scopes.get(&alias_program_identifier) { + if let Some(var_data) = alias_scope.get_variable(&actual_name) { + scope.set_arc_variable(&alias_name, var_data); + } else { + tracing::error!( + "Importing a non-existent variable: {} from {}", + actual_name, + alias_program_identifier + ); + } + } else { + tracing::error!( + "Importing from a non-existent program: {}", + alias_program_identifier + ); + } + } } let mut always_transpile_functions = Vec::new(); { - let functions = &mut self.functions; - for (_, data) in functions.iter() { + let functions = self.scopes.iter().flat_map(|(_, scope)| { + scope + .get_local_variables() + .read() + .unwrap() + .values() + .filter_map(|data| data.as_function().map(|(data, _, _)| data)) + .cloned() + .collect::>() + }); + for data in functions { let always_transpile_function = data.annotations.contains_key("tick") || data.annotations.contains_key("load") || data.annotations.contains_key("deobfuscate"); @@ -141,24 +170,25 @@ impl Transpiler { fn transpile_program_declarations( &mut self, program: &ProgramFile, + program_aliases: &mut BTreeMap, scope: &Arc, handler: &impl Handler, ) -> TranspileResult<()> { let namespace = program.namespace(); for declaration in program.declarations() { - self.transpile_declaration(declaration, namespace, scope, handler)?; + self.transpile_declaration(declaration, namespace, program_aliases, scope, handler)?; } Ok(()) } /// Transpiles the given declaration. - #[allow(clippy::needless_pass_by_ref_mut)] fn transpile_declaration( &mut self, declaration: &Declaration, namespace: &Namespace, + program_aliases: &mut BTreeMap, scope: &Arc, handler: &impl Handler, ) -> TranspileResult<()> { @@ -197,19 +227,18 @@ impl Transpiler { scope.set_variable( &name, VariableData::Function { - function_data: function_data.clone(), + function_data, path: OnceLock::new(), + function_scope: scope.clone(), }, ); - self.functions - .insert((program_identifier, name), function_data); } Declaration::Import(import) => { let path = import.module().str_content(); let import_identifier = super::util::calculate_import_identifier(&program_identifier, path); - let aliases = &mut self.aliases; + // let aliases = &mut self.aliases; match import.items() { ImportItems::All(_) => { @@ -220,8 +249,8 @@ impl Transpiler { ImportItems::Named(list) => { for item in list.elements() { let name = item.span.str(); - aliases.insert( - (program_identifier.clone(), name.to_string()), + program_aliases.insert( + name.to_string(), (import_identifier.clone(), name.to_string()), ); } @@ -244,7 +273,7 @@ impl Transpiler { sb_tag.set_replace(true); } } - Declaration::GlobalVariable((declaration, _)) => { + Declaration::GlobalVariable((_, declaration, _)) => { let setup_variable_cmds = self.transpile_variable_declaration( declaration, true, @@ -278,7 +307,7 @@ impl Transpiler { unreachable!("Only literal commands are allowed in functions at this time.") } Statement::ExecuteBlock(execute) => { - let child_scope = Scope::with_parent(scope); + let child_scope = Scope::with_parent(scope.clone()); Ok(self.transpile_execute_block( execute, program_identifier, @@ -291,7 +320,7 @@ impl Transpiler { Ok(vec![Command::Comment(content.to_string())]) } Statement::Grouping(group) => { - let child_scope = Scope::with_parent(scope); + let child_scope = Scope::with_parent(scope.clone()); let statements = group.block().statements(); let mut errors = Vec::new(); let commands = statements diff --git a/src/transpile/variables.rs b/src/transpile/variables.rs index 047a814..5755894 100644 --- a/src/transpile/variables.rs +++ b/src/transpile/variables.rs @@ -49,6 +49,8 @@ pub enum VariableData { function_data: FunctionData, /// The path to the function once it is generated. path: OnceLock, + /// The scope of the function. + function_scope: Arc, }, /// A macro function parameter. MacroParameter { @@ -131,9 +133,9 @@ impl<'a> From<&'a AssignmentDestination> for TranspileAssignmentTarget<'a> { /// A scope that stores variables. #[cfg(feature = "shulkerbox")] #[derive(Default)] -pub struct Scope<'a> { +pub struct Scope { /// Parent scope where variables are inherited from. - parent: Option<&'a Arc>, + parent: Option>, /// Variables stored in the scope. variables: RwLock>>, /// How many times the variable has been shadowed in the current scope. @@ -141,7 +143,7 @@ pub struct Scope<'a> { } #[cfg(feature = "shulkerbox")] -impl<'a> Scope<'a> { +impl Scope { /// Creates a new scope. #[must_use] pub fn new() -> Arc { @@ -163,7 +165,7 @@ impl<'a> Scope<'a> { /// Creates a new scope with a parent. #[must_use] - pub fn with_parent(parent: &'a Arc) -> Arc { + pub fn with_parent(parent: Arc) -> Arc { Arc::new(Self { parent: Some(parent), ..Default::default() @@ -200,11 +202,17 @@ impl<'a> Scope<'a> { /// Sets a variable in the scope. pub fn set_variable(&self, name: &str, var: VariableData) { + self.set_arc_variable(name, Arc::new(var)); + } + + /// Sets a variable in the scope. + /// This function is used to set a variable that is already wrapped in an `Arc`. + pub fn set_arc_variable(&self, name: &str, var: Arc) { let prev = self .variables .write() .unwrap() - .insert(name.to_string(), Arc::new(var)); + .insert(name.to_string(), var); *self .shadowed .write() @@ -231,12 +239,12 @@ impl<'a> Scope<'a> { /// Gets the parent scope. pub fn get_parent(&self) -> Option> { - self.parent.cloned() + self.parent.clone() } } #[cfg(feature = "shulkerbox")] -impl Debug for Scope<'_> { +impl Debug for Scope { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { struct VariableWrapper<'a>(&'a RwLock>>); impl Debug for VariableWrapper<'_> { @@ -1263,7 +1271,7 @@ mod tests { objective: "test".to_string(), }, ); - let child = Scope::with_parent(&scope); + let child = Scope::with_parent(scope); if let Some(var) = child.get_variable("test") { match var.as_ref() { VariableData::Scoreboard { objective } => assert_eq!(objective, "test"), @@ -1283,7 +1291,7 @@ mod tests { objective: "test1".to_string(), }, ); - let child = Scope::with_parent(&scope); + let child = Scope::with_parent(scope); child.set_variable( "test", VariableData::Scoreboard {