allow importing global variables, get rid of functions and aliases field of transpiler

This commit is contained in:
Moritz Hölting 2025-04-07 01:09:34 +02:00
parent 0a8bf37e40
commit 1e82d2321f
6 changed files with 108 additions and 99 deletions

View File

@ -115,9 +115,6 @@ pub fn parse(
/// Transpiles the given source code into a shulkerbox [`Datapack`]. /// 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 /// # Errors
/// - If an error occurs during [`parse()`] /// - If an error occurs during [`parse()`]
/// - If an error occurs while transpiling the source code. /// - If an error occurs while transpiling the source code.
@ -164,15 +161,7 @@ where
Ok(program) Ok(program)
}) })
.collect::<Vec<_>>(); .collect::<Result<Vec<_>>>()?;
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::<Vec<_>>();
tracing::info!("Transpiling the source code."); tracing::info!("Transpiling the source code.");

View File

@ -61,7 +61,7 @@ fn get_global_scope(declarations: &[Declaration]) -> SemanticScope<'static> {
let scope = SemanticScope::new(); let scope = SemanticScope::new();
for declaration in declarations { for declaration in declarations {
declaration.add_to_scope(&scope); declaration.add_to_semantic_scope(&scope);
} }
scope scope
@ -83,23 +83,13 @@ impl Namespace {
} }
impl Declaration { impl Declaration {
fn add_to_scope(&self, scope: &SemanticScope) { fn add_to_semantic_scope(&self, scope: &SemanticScope) {
match self { match self {
Self::Function(func) => { Self::Function(func) => {
let name = func.identifier(); let name = func.identifier();
scope.set_variable(name.span.str(), VariableType::Function); scope.set_variable(name.span.str(), VariableType::Function);
} }
Self::Import(imp) => match imp.items() { Self::GlobalVariable((_, var, _)) => {
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, _)) => {
let name = var.identifier(); let name = var.identifier();
let var_type = match var { let var_type = match var {
VariableDeclaration::Array(arr) => match arr.variable_type().keyword { VariableDeclaration::Array(arr) => match arr.variable_type().keyword {
@ -118,7 +108,7 @@ impl Declaration {
}; };
scope.set_variable(name.span.str(), var_type); 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(()), Self::Tag(_) => Ok(()),
} }

View File

@ -37,7 +37,7 @@ use super::{
/// Function /// Function
/// | Import /// | Import
/// | Tag /// | Tag
/// | (VariableDeclaration ';') /// | ('pub'? VariableDeclaration ';')
/// ; /// ;
/// ``` /// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -46,7 +46,7 @@ pub enum Declaration {
Function(Function), Function(Function),
Import(Import), Import(Import),
Tag(Tag), Tag(Tag),
GlobalVariable((VariableDeclaration, Punctuation)), GlobalVariable((Option<Keyword>, VariableDeclaration, Punctuation)),
} }
impl SourceElement for Declaration { impl SourceElement for Declaration {
@ -55,8 +55,9 @@ impl SourceElement for Declaration {
Self::Function(function) => function.span(), Self::Function(function) => function.span(),
Self::Import(import) => import.span(), Self::Import(import) => import.span(),
Self::Tag(tag) => tag.span(), Self::Tag(tag) => tag.span(),
Self::GlobalVariable((variable, semicolon)) => variable Self::GlobalVariable((pub_kw, variable, semicolon)) => pub_kw
.span() .as_ref()
.map_or_else(|| variable.span(), SourceElement::span)
.join(&semicolon.span) .join(&semicolon.span)
.expect("invalid declaration span"), .expect("invalid declaration span"),
} }
@ -75,10 +76,10 @@ impl Declaration {
Ok(Self::Function(function)) Ok(Self::Function(function))
} }
Self::GlobalVariable((var, semi)) => { Self::GlobalVariable((pub_kw, var, semi)) => {
let var_with_annotation = var.with_annotation(annotation)?; 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 { let err = Error::InvalidAnnotation(InvalidAnnotation {
@ -356,11 +357,19 @@ impl Parser<'_> {
Reading::Atomic(Token::Keyword(pub_keyword)) Reading::Atomic(Token::Keyword(pub_keyword))
if pub_keyword.keyword == KeywordKind::Pub => 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)) Ok(Declaration::Function(function))
} else {
// eat the pub keyword
self.forward();
let var = self.parse_variable_declaration(handler)?;
let semi = self.parse_punctuation(';', true, handler)?;
Ok(Declaration::GlobalVariable((Some(pub_keyword), var, semi)))
}
} }
// parse annotations // parse annotations
@ -480,7 +489,7 @@ impl Parser<'_> {
let var = self.parse_variable_declaration(handler)?; let var = self.parse_variable_declaration(handler)?;
let semi = self.parse_punctuation(';', true, handler)?; let semi = self.parse_punctuation(';', true, handler)?;
Ok(Declaration::GlobalVariable((var, semi))) Ok(Declaration::GlobalVariable((None, var, semi)))
} }
unexpected => { unexpected => {

View File

@ -1,6 +1,6 @@
use chksum_md5 as md5; use chksum_md5 as md5;
use enum_as_inner::EnumAsInner; use enum_as_inner::EnumAsInner;
use std::{collections::BTreeMap, sync::Arc}; use std::{borrow::ToOwned, collections::BTreeMap, sync::Arc};
use shulkerbox::datapack::{Command, Execute}; use shulkerbox::datapack::{Command, Execute};
@ -48,31 +48,16 @@ impl Transpiler {
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<(String, TranspiledFunctionArguments)> { ) -> TranspileResult<(String, TranspiledFunctionArguments)> {
let program_identifier = identifier_span.source_file().identifier(); let program_identifier = identifier_span.source_file().identifier();
let program_query = ( let function = scope.get_variable(identifier_span.str());
program_identifier.to_string(), let already_transpiled = function
identifier_span.str().to_string(), .as_ref()
);
let alias_query = self.aliases.get(&program_query).cloned();
let already_transpiled = scope
.get_variable(identifier_span.str())
.expect("called function should be in scope") .expect("called function should be in scope")
.as_ref() .as_ref()
.as_function() .as_function()
.map(|(_, path)| path.get().is_some()) .map(|(_, path, _)| path.get().is_some())
.expect("called variable should be of type function"); .expect("called variable should be of type function");
let function_data = scope let function_data = function.ok_or_else(|| {
.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( let error = TranspileError::MissingFunctionDeclaration(
MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope), MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope),
); );
@ -83,6 +68,7 @@ impl Transpiler {
let VariableData::Function { let VariableData::Function {
function_data, function_data,
path: function_path, path: function_path,
function_scope,
} = function_data.as_ref() } = function_data.as_ref()
else { else {
unreachable!("must be of correct type, otherwise errored out before"); unreachable!("must be of correct type, otherwise errored out before");
@ -134,8 +120,6 @@ impl Transpiler {
function_path.set(function_location.clone()).unwrap(); function_path.set(function_location.clone()).unwrap();
let function_scope = Scope::with_parent(scope);
for (i, param) in function_data.parameters.iter().enumerate() { for (i, param) in function_data.parameters.iter().enumerate() {
let param_str = param.identifier().span.str(); let param_str = param.identifier().span.str();
match param.variable_type() { match param.variable_type() {
@ -181,7 +165,7 @@ impl Transpiler {
} }
let commands = 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); let namespace = self.datapack.namespace_mut(&function_data.namespace);

View File

@ -1,7 +1,7 @@
//! Transpiler for `Shulkerscript` //! Transpiler for `Shulkerscript`
use std::{ use std::{
collections::{BTreeMap, HashMap, HashSet}, collections::{BTreeMap, HashSet},
ops::Deref, ops::Deref,
sync::{Arc, OnceLock}, sync::{Arc, OnceLock},
}; };
@ -40,11 +40,7 @@ pub struct Transpiler {
pub(super) initialized_constant_scores: HashSet<i64>, pub(super) initialized_constant_scores: HashSet<i64>,
pub(super) temp_counter: usize, pub(super) temp_counter: usize,
/// Top-level [`Scope`] for each program identifier /// Top-level [`Scope`] for each program identifier
pub(super) scopes: BTreeMap<String, Arc<Scope<'static>>>, pub(super) scopes: BTreeMap<String, Arc<Scope>>,
/// Key: (program identifier, function name)
pub(super) functions: BTreeMap<(String, String), FunctionData>,
/// Key: alias, Value: target
pub(super) aliases: HashMap<(String, String), (String, String)>,
} }
impl Transpiler { impl Transpiler {
@ -59,8 +55,6 @@ impl Transpiler {
initialized_constant_scores: HashSet::new(), initialized_constant_scores: HashSet::new(),
temp_counter: 0, temp_counter: 0,
scopes: BTreeMap::new(), scopes: BTreeMap::new(),
functions: BTreeMap::new(),
aliases: HashMap::new(),
} }
} }
@ -75,7 +69,11 @@ impl Transpiler {
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> Result<Datapack, TranspileError> { ) -> Result<Datapack, TranspileError> {
tracing::trace!("Transpiling program declarations"); tracing::trace!("Transpiling program declarations");
let mut aliases = Vec::new();
for program in programs { for program in programs {
let mut program_aliases = BTreeMap::new();
let program_identifier = program let program_identifier = program
.namespace() .namespace()
.span() .span()
@ -84,17 +82,48 @@ impl Transpiler {
.clone(); .clone();
let scope = self let scope = self
.scopes .scopes
.entry(program_identifier) .entry(program_identifier.clone())
.or_insert_with(Scope::with_internal_functions) .or_insert_with(Scope::with_internal_functions)
.to_owned(); .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 mut always_transpile_functions = Vec::new();
{ {
let functions = &mut self.functions; let functions = self.scopes.iter().flat_map(|(_, scope)| {
for (_, data) in functions.iter() { scope
.get_local_variables()
.read()
.unwrap()
.values()
.filter_map(|data| data.as_function().map(|(data, _, _)| data))
.cloned()
.collect::<Vec<_>>()
});
for data in functions {
let always_transpile_function = data.annotations.contains_key("tick") let always_transpile_function = data.annotations.contains_key("tick")
|| data.annotations.contains_key("load") || data.annotations.contains_key("load")
|| data.annotations.contains_key("deobfuscate"); || data.annotations.contains_key("deobfuscate");
@ -141,24 +170,25 @@ impl Transpiler {
fn transpile_program_declarations( fn transpile_program_declarations(
&mut self, &mut self,
program: &ProgramFile, program: &ProgramFile,
program_aliases: &mut BTreeMap<String, (String, String)>,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<()> { ) -> TranspileResult<()> {
let namespace = program.namespace(); let namespace = program.namespace();
for declaration in program.declarations() { for declaration in program.declarations() {
self.transpile_declaration(declaration, namespace, scope, handler)?; self.transpile_declaration(declaration, namespace, program_aliases, scope, handler)?;
} }
Ok(()) Ok(())
} }
/// Transpiles the given declaration. /// Transpiles the given declaration.
#[allow(clippy::needless_pass_by_ref_mut)]
fn transpile_declaration( fn transpile_declaration(
&mut self, &mut self,
declaration: &Declaration, declaration: &Declaration,
namespace: &Namespace, namespace: &Namespace,
program_aliases: &mut BTreeMap<String, (String, String)>,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<()> { ) -> TranspileResult<()> {
@ -197,19 +227,18 @@ impl Transpiler {
scope.set_variable( scope.set_variable(
&name, &name,
VariableData::Function { VariableData::Function {
function_data: function_data.clone(), function_data,
path: OnceLock::new(), path: OnceLock::new(),
function_scope: scope.clone(),
}, },
); );
self.functions
.insert((program_identifier, name), function_data);
} }
Declaration::Import(import) => { Declaration::Import(import) => {
let path = import.module().str_content(); let path = import.module().str_content();
let import_identifier = let import_identifier =
super::util::calculate_import_identifier(&program_identifier, path); super::util::calculate_import_identifier(&program_identifier, path);
let aliases = &mut self.aliases; // let aliases = &mut self.aliases;
match import.items() { match import.items() {
ImportItems::All(_) => { ImportItems::All(_) => {
@ -220,8 +249,8 @@ impl Transpiler {
ImportItems::Named(list) => { ImportItems::Named(list) => {
for item in list.elements() { for item in list.elements() {
let name = item.span.str(); let name = item.span.str();
aliases.insert( program_aliases.insert(
(program_identifier.clone(), name.to_string()), name.to_string(),
(import_identifier.clone(), name.to_string()), (import_identifier.clone(), name.to_string()),
); );
} }
@ -244,7 +273,7 @@ impl Transpiler {
sb_tag.set_replace(true); sb_tag.set_replace(true);
} }
} }
Declaration::GlobalVariable((declaration, _)) => { Declaration::GlobalVariable((_, declaration, _)) => {
let setup_variable_cmds = self.transpile_variable_declaration( let setup_variable_cmds = self.transpile_variable_declaration(
declaration, declaration,
true, true,
@ -278,7 +307,7 @@ impl Transpiler {
unreachable!("Only literal commands are allowed in functions at this time.") unreachable!("Only literal commands are allowed in functions at this time.")
} }
Statement::ExecuteBlock(execute) => { Statement::ExecuteBlock(execute) => {
let child_scope = Scope::with_parent(scope); let child_scope = Scope::with_parent(scope.clone());
Ok(self.transpile_execute_block( Ok(self.transpile_execute_block(
execute, execute,
program_identifier, program_identifier,
@ -291,7 +320,7 @@ impl Transpiler {
Ok(vec![Command::Comment(content.to_string())]) Ok(vec![Command::Comment(content.to_string())])
} }
Statement::Grouping(group) => { Statement::Grouping(group) => {
let child_scope = Scope::with_parent(scope); let child_scope = Scope::with_parent(scope.clone());
let statements = group.block().statements(); let statements = group.block().statements();
let mut errors = Vec::new(); let mut errors = Vec::new();
let commands = statements let commands = statements

View File

@ -49,6 +49,8 @@ pub enum VariableData {
function_data: FunctionData, function_data: FunctionData,
/// The path to the function once it is generated. /// The path to the function once it is generated.
path: OnceLock<String>, path: OnceLock<String>,
/// The scope of the function.
function_scope: Arc<Scope>,
}, },
/// A macro function parameter. /// A macro function parameter.
MacroParameter { MacroParameter {
@ -131,9 +133,9 @@ impl<'a> From<&'a AssignmentDestination> for TranspileAssignmentTarget<'a> {
/// A scope that stores variables. /// A scope that stores variables.
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
#[derive(Default)] #[derive(Default)]
pub struct Scope<'a> { pub struct Scope {
/// Parent scope where variables are inherited from. /// Parent scope where variables are inherited from.
parent: Option<&'a Arc<Self>>, parent: Option<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. /// How many times the variable has been shadowed in the current scope.
@ -141,7 +143,7 @@ pub struct Scope<'a> {
} }
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
impl<'a> Scope<'a> { impl Scope {
/// Creates a new scope. /// Creates a new scope.
#[must_use] #[must_use]
pub fn new() -> Arc<Self> { pub fn new() -> Arc<Self> {
@ -163,7 +165,7 @@ impl<'a> Scope<'a> {
/// Creates a new scope with a parent. /// Creates a new scope with a parent.
#[must_use] #[must_use]
pub fn with_parent(parent: &'a Arc<Self>) -> Arc<Self> { pub fn with_parent(parent: Arc<Self>) -> Arc<Self> {
Arc::new(Self { Arc::new(Self {
parent: Some(parent), parent: Some(parent),
..Default::default() ..Default::default()
@ -200,11 +202,17 @@ impl<'a> Scope<'a> {
/// 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.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<VariableData>) {
let prev = self let prev = self
.variables .variables
.write() .write()
.unwrap() .unwrap()
.insert(name.to_string(), Arc::new(var)); .insert(name.to_string(), var);
*self *self
.shadowed .shadowed
.write() .write()
@ -231,12 +239,12 @@ impl<'a> Scope<'a> {
/// Gets the parent scope. /// Gets the parent scope.
pub fn get_parent(&self) -> Option<Arc<Self>> { pub fn get_parent(&self) -> Option<Arc<Self>> {
self.parent.cloned() self.parent.clone()
} }
} }
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
impl Debug for Scope<'_> { impl Debug for Scope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct VariableWrapper<'a>(&'a RwLock<HashMap<String, Arc<VariableData>>>); struct VariableWrapper<'a>(&'a RwLock<HashMap<String, Arc<VariableData>>>);
impl Debug for VariableWrapper<'_> { impl Debug for VariableWrapper<'_> {
@ -1263,7 +1271,7 @@ mod tests {
objective: "test".to_string(), objective: "test".to_string(),
}, },
); );
let child = Scope::with_parent(&scope); let child = Scope::with_parent(scope);
if let Some(var) = child.get_variable("test") { if let Some(var) = child.get_variable("test") {
match var.as_ref() { match var.as_ref() {
VariableData::Scoreboard { objective } => assert_eq!(objective, "test"), VariableData::Scoreboard { objective } => assert_eq!(objective, "test"),
@ -1283,7 +1291,7 @@ mod tests {
objective: "test1".to_string(), objective: "test1".to_string(),
}, },
); );
let child = Scope::with_parent(&scope); let child = Scope::with_parent(scope);
child.set_variable( child.set_variable(
"test", "test",
VariableData::Scoreboard { VariableData::Scoreboard {