implement compile-time function arguments
This commit is contained in:
parent
bd8270bd5d
commit
3bc3ca180f
|
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Member access (e.g. `.objective` to get objective name where int is stored)
|
- Member access (e.g. `.objective` to get objective name where int is stored)
|
||||||
- Return statement
|
- Return statement
|
||||||
- internal `print` function
|
- internal `print` function
|
||||||
|
- reserve `while` and `for` keyword
|
||||||
- Example: barebones compiler
|
- Example: barebones compiler
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -219,7 +219,7 @@ Semicolon:
|
||||||
|
|
||||||
```ebnf
|
```ebnf
|
||||||
FunctionVariableType:
|
FunctionVariableType:
|
||||||
'macro' | 'int' | 'bool'
|
'macro' | 'int' | 'bool' | 'val'
|
||||||
;
|
;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,8 @@ pub enum KeywordKind {
|
||||||
Macro,
|
Macro,
|
||||||
Val,
|
Val,
|
||||||
Return,
|
Return,
|
||||||
|
While,
|
||||||
|
For,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for KeywordKind {
|
impl Display for KeywordKind {
|
||||||
|
@ -121,6 +123,8 @@ impl KeywordKind {
|
||||||
Self::Macro => "macro",
|
Self::Macro => "macro",
|
||||||
Self::Val => "val",
|
Self::Val => "val",
|
||||||
Self::Return => "return",
|
Self::Return => "return",
|
||||||
|
Self::While => "while",
|
||||||
|
Self::For => "for",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,12 @@ impl Function {
|
||||||
) -> Result<(), error::Error> {
|
) -> Result<(), error::Error> {
|
||||||
let child_scope = SemanticScope::with_parent(scope);
|
let child_scope = SemanticScope::with_parent(scope);
|
||||||
|
|
||||||
if let Some(parameters) = self.parameters().as_ref().map(ConnectedList::elements) {
|
if let Some(parameters) = self
|
||||||
|
.parameters()
|
||||||
|
.as_ref()
|
||||||
|
.map(ConnectedList::elements)
|
||||||
|
.map(Iterator::collect::<Vec<_>>)
|
||||||
|
{
|
||||||
if let Some(incompatible) = self.annotations().iter().find(|a| {
|
if let Some(incompatible) = self.annotations().iter().find(|a| {
|
||||||
["tick", "load", "uninstall"].contains(&a.assignment().identifier.span.str())
|
["tick", "load", "uninstall"].contains(&a.assignment().identifier.span.str())
|
||||||
}) {
|
}) {
|
||||||
|
@ -174,6 +179,25 @@ impl Function {
|
||||||
});
|
});
|
||||||
handler.receive(err.clone());
|
handler.receive(err.clone());
|
||||||
return Err(err);
|
return Err(err);
|
||||||
|
} else if parameters
|
||||||
|
.iter()
|
||||||
|
.any(|param| matches!(param.variable_type(), FunctionVariableType::Value(_)))
|
||||||
|
{
|
||||||
|
if let Some(incompatible) = self
|
||||||
|
.annotations()
|
||||||
|
.iter()
|
||||||
|
.find(|a| a.assignment().identifier.span.str() == "deobfuscate")
|
||||||
|
{
|
||||||
|
let err =
|
||||||
|
error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
|
||||||
|
span: incompatible.assignment().identifier.span.clone(),
|
||||||
|
reason:
|
||||||
|
"functions with the `deobfuscate` annotation cannot have compile-time parameters"
|
||||||
|
.to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for param in parameters {
|
for param in parameters {
|
||||||
|
@ -182,6 +206,7 @@ impl Function {
|
||||||
FunctionVariableType::Boolean(_) => VariableType::BooleanStorage,
|
FunctionVariableType::Boolean(_) => VariableType::BooleanStorage,
|
||||||
FunctionVariableType::Integer(_) => VariableType::ScoreboardValue,
|
FunctionVariableType::Integer(_) => VariableType::ScoreboardValue,
|
||||||
FunctionVariableType::Macro(_) => VariableType::MacroParameter,
|
FunctionVariableType::Macro(_) => VariableType::MacroParameter,
|
||||||
|
FunctionVariableType::Value(_) => VariableType::ComptimeValue,
|
||||||
};
|
};
|
||||||
child_scope.set_variable(name, var);
|
child_scope.set_variable(name, var);
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,7 +179,7 @@ impl SourceElement for Function {
|
||||||
///
|
///
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// FunctionVariableType:
|
/// FunctionVariableType:
|
||||||
/// 'macro' | 'int' | 'bool'
|
/// 'macro' | 'int' | 'bool' | 'val'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -188,6 +188,7 @@ pub enum FunctionVariableType {
|
||||||
Macro(Keyword),
|
Macro(Keyword),
|
||||||
Integer(Keyword),
|
Integer(Keyword),
|
||||||
Boolean(Keyword),
|
Boolean(Keyword),
|
||||||
|
Value(Keyword),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a function argument in the syntax tree.
|
/// Represents a function argument in the syntax tree.
|
||||||
|
@ -607,12 +608,24 @@ impl Parser<'_> {
|
||||||
identifier,
|
identifier,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Reading::Atomic(Token::Keyword(keyword)) if keyword.keyword == KeywordKind::Val => {
|
||||||
|
let variable_type = FunctionVariableType::Value(keyword);
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
let identifier = self.parse_identifier(handler)?;
|
||||||
|
|
||||||
|
Ok(FunctionParameter {
|
||||||
|
variable_type,
|
||||||
|
identifier,
|
||||||
|
})
|
||||||
|
}
|
||||||
unexpected => {
|
unexpected => {
|
||||||
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
||||||
expected: SyntaxKind::Either(&[
|
expected: SyntaxKind::Either(&[
|
||||||
SyntaxKind::Keyword(KeywordKind::Int),
|
SyntaxKind::Keyword(KeywordKind::Int),
|
||||||
SyntaxKind::Keyword(KeywordKind::Bool),
|
SyntaxKind::Keyword(KeywordKind::Bool),
|
||||||
SyntaxKind::Keyword(KeywordKind::Macro),
|
SyntaxKind::Keyword(KeywordKind::Macro),
|
||||||
|
SyntaxKind::Keyword(KeywordKind::Val),
|
||||||
]),
|
]),
|
||||||
found: unexpected.into_token(),
|
found: unexpected.into_token(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,16 +17,17 @@ use super::{
|
||||||
},
|
},
|
||||||
Scope, TranspileResult, Transpiler, VariableData,
|
Scope, TranspileResult, Transpiler, VariableData,
|
||||||
};
|
};
|
||||||
use crate::syntax::syntax_tree::expression::{Indexed, Parenthesized};
|
|
||||||
#[cfg(feature = "shulkerbox")]
|
#[cfg(feature = "shulkerbox")]
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{self, source_file::SourceElement, Handler, VoidHandler},
|
base::{self, source_file::SourceElement, Handler, VoidHandler},
|
||||||
lexical::token::{Identifier, MacroStringLiteralPart, StringLiteral},
|
lexical::token::{Identifier, MacroStringLiteralPart, StringLiteral},
|
||||||
syntax::syntax_tree::expression::{
|
syntax::syntax_tree::expression::{
|
||||||
Binary, BinaryOperator, Expression, MemberAccess, PrefixOperator, Primary,
|
Binary, BinaryOperator, Expression, Indexed, MemberAccess, Parenthesized, PrefixOperator,
|
||||||
|
Primary,
|
||||||
},
|
},
|
||||||
transpile::{
|
transpile::{
|
||||||
error::{FunctionArgumentsNotAllowed, MissingValue},
|
error::{FunctionArgumentsNotAllowed, MissingValue},
|
||||||
|
variables::FunctionVariableDataType,
|
||||||
TranspileError,
|
TranspileError,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -579,7 +580,10 @@ impl Identifier {
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VariableData::Function { path, .. } => {
|
VariableData::Function {
|
||||||
|
variable_data: FunctionVariableDataType::Simple { path, .. },
|
||||||
|
..
|
||||||
|
} => {
|
||||||
match member_access.member().span.str() {
|
match member_access.member().span.str() {
|
||||||
"path" => {
|
"path" => {
|
||||||
#[expect(clippy::option_if_let_else)]
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
@ -597,7 +601,11 @@ impl Identifier {
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VariableData::InternalFunction { .. } => Err(NotComptime {
|
VariableData::Function {
|
||||||
|
variable_data: FunctionVariableDataType::ComptimeArguments { .. },
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| VariableData::InternalFunction { .. } => Err(NotComptime {
|
||||||
expression: member_access.member().span(),
|
expression: member_access.member().span(),
|
||||||
}),
|
}),
|
||||||
VariableData::MacroParameter { macro_name, .. } => {
|
VariableData::MacroParameter { macro_name, .. } => {
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use chksum_md5 as md5;
|
use chksum_md5 as md5;
|
||||||
use enum_as_inner::EnumAsInner;
|
use enum_as_inner::EnumAsInner;
|
||||||
use std::{borrow::ToOwned, collections::BTreeMap, sync::Arc};
|
use itertools::Itertools;
|
||||||
|
use std::{
|
||||||
|
borrow::{Cow, ToOwned},
|
||||||
|
collections::BTreeMap,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
};
|
||||||
|
|
||||||
use shulkerbox::datapack::{Command, Execute};
|
use shulkerbox::datapack::{Command, Execute};
|
||||||
|
|
||||||
|
@ -19,6 +24,7 @@ use crate::{
|
||||||
transpile::{
|
transpile::{
|
||||||
error::{IllegalAnnotationContent, MissingFunctionDeclaration},
|
error::{IllegalAnnotationContent, MissingFunctionDeclaration},
|
||||||
util::{MacroString, MacroStringPart},
|
util::{MacroString, MacroStringPart},
|
||||||
|
variables::FunctionVariableDataType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,7 +45,6 @@ pub enum TranspiledFunctionArguments {
|
||||||
impl Transpiler {
|
impl Transpiler {
|
||||||
/// Gets the function at the given path, or transpiles it if it hasn't been transpiled yet.
|
/// Gets the function at the given path, or transpiles it if it hasn't been transpiled yet.
|
||||||
/// Returns the location of the function or None if the function does not exist.
|
/// Returns the location of the function or None if the function does not exist.
|
||||||
#[expect(clippy::too_many_lines)]
|
|
||||||
#[tracing::instrument(level = "trace", skip(self, handler))]
|
#[tracing::instrument(level = "trace", skip(self, handler))]
|
||||||
pub(super) fn get_or_transpile_function(
|
pub(super) fn get_or_transpile_function(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -50,13 +55,6 @@ impl Transpiler {
|
||||||
) -> TranspileResult<(String, TranspiledFunctionArguments)> {
|
) -> TranspileResult<(String, TranspiledFunctionArguments)> {
|
||||||
let program_identifier = identifier_span.source_file().identifier();
|
let program_identifier = identifier_span.source_file().identifier();
|
||||||
let function = 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())
|
|
||||||
.expect("called variable should be of type function");
|
|
||||||
|
|
||||||
let function_data = function.ok_or_else(|| {
|
let function_data = function.ok_or_else(|| {
|
||||||
let err = TranspileError::MissingFunctionDeclaration(
|
let err = TranspileError::MissingFunctionDeclaration(
|
||||||
|
@ -66,150 +64,246 @@ impl Transpiler {
|
||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let VariableData::Function {
|
let (function_data, function_scope, function_type) = function_data
|
||||||
function_data,
|
.as_ref()
|
||||||
path: function_path,
|
.as_function()
|
||||||
function_scope,
|
.expect("called variable should be of type function");
|
||||||
} = function_data.as_ref()
|
|
||||||
else {
|
|
||||||
unreachable!("must be of correct type, otherwise errored out before");
|
|
||||||
};
|
|
||||||
|
|
||||||
if !already_transpiled {
|
match function_type {
|
||||||
tracing::trace!("Function not transpiled yet, transpiling.");
|
FunctionVariableDataType::Simple {
|
||||||
|
path: function_path,
|
||||||
|
} => {
|
||||||
|
let already_transpiled = function_path.get().is_some();
|
||||||
|
|
||||||
let statements = function_data.statements.clone();
|
if !already_transpiled {
|
||||||
|
self.prepare_transpile_function(
|
||||||
|
function_data,
|
||||||
|
program_identifier,
|
||||||
|
identifier_span,
|
||||||
|
|path| function_path.set(path).expect("not set before"),
|
||||||
|
std::iter::repeat(None),
|
||||||
|
None,
|
||||||
|
function_scope,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
let modified_name = function_data.annotations.get("deobfuscate").map_or_else(
|
let function_location = function_path
|
||||||
|| {
|
.get()
|
||||||
let hash_data = program_identifier.to_string() + "\0" + identifier_span.str();
|
.ok_or_else(|| {
|
||||||
Ok("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase())
|
let err = TranspileError::MissingFunctionDeclaration(
|
||||||
},
|
MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope),
|
||||||
|val| match val {
|
);
|
||||||
TranspileAnnotationValue::None(_) => Ok(identifier_span.str().to_string()),
|
handler.receive(Box::new(err.clone()));
|
||||||
TranspileAnnotationValue::Expression(expr, _) => expr
|
err
|
||||||
.comptime_eval(scope, handler)
|
})
|
||||||
.ok()
|
.map(String::to_owned)?;
|
||||||
.and_then(|val| val.to_string_no_macro())
|
|
||||||
.ok_or_else(|| {
|
let args = self.transpile_function_arguments(
|
||||||
let err = TranspileError::IllegalAnnotationContent(
|
function_data,
|
||||||
IllegalAnnotationContent {
|
&function_location,
|
||||||
annotation: identifier_span.clone(),
|
arguments,
|
||||||
message: "Cannot evaluate annotation at compile time"
|
scope,
|
||||||
.to_string(),
|
handler,
|
||||||
},
|
)?;
|
||||||
);
|
|
||||||
handler.receive(Box::new(err.clone()));
|
Ok((function_location, args))
|
||||||
err
|
}
|
||||||
}),
|
FunctionVariableDataType::ComptimeArguments { function_paths } => {
|
||||||
TranspileAnnotationValue::Map(_, span) => {
|
let comptime_args = self.transpile_comptime_function_arguments(
|
||||||
|
function_data,
|
||||||
|
arguments,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
let hash = comptime_args_hash(&comptime_args);
|
||||||
|
|
||||||
|
let read_guard = function_paths.read().unwrap();
|
||||||
|
let function_location = if let Some(data) = read_guard.get(&hash) {
|
||||||
|
data.to_owned()
|
||||||
|
} else {
|
||||||
|
drop(read_guard);
|
||||||
|
let function_scope = Scope::with_parent(function_scope.to_owned());
|
||||||
|
let mut path = String::new();
|
||||||
|
|
||||||
|
self.prepare_transpile_function(
|
||||||
|
function_data,
|
||||||
|
program_identifier,
|
||||||
|
identifier_span,
|
||||||
|
|p| path = p,
|
||||||
|
comptime_args.into_iter(),
|
||||||
|
Some(&hash),
|
||||||
|
&function_scope,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
function_paths
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.insert(hash.clone(), path.clone());
|
||||||
|
|
||||||
|
path
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = self.transpile_function_arguments(
|
||||||
|
function_data,
|
||||||
|
&function_location,
|
||||||
|
arguments,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok((function_location, args))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_arguments, clippy::too_many_lines)]
|
||||||
|
fn prepare_transpile_function(
|
||||||
|
&mut self,
|
||||||
|
function_data: &FunctionData,
|
||||||
|
program_identifier: &str,
|
||||||
|
identifier_span: &Span,
|
||||||
|
set_function_path: impl FnOnce(String),
|
||||||
|
comptime_args: impl Iterator<Item = Option<ComptimeValue>>,
|
||||||
|
function_suffix: Option<&str>,
|
||||||
|
function_scope: &Arc<Scope>,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<()> {
|
||||||
|
tracing::trace!("Function not transpiled yet, transpiling.");
|
||||||
|
|
||||||
|
let statements = function_data.statements.clone();
|
||||||
|
|
||||||
|
let mut modified_name = function_data.annotations.get("deobfuscate").map_or_else(
|
||||||
|
|| {
|
||||||
|
let hash_data = program_identifier.to_string() + "\0" + identifier_span.str();
|
||||||
|
Ok("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase())
|
||||||
|
},
|
||||||
|
|val| match val {
|
||||||
|
TranspileAnnotationValue::None(_) => Ok(identifier_span.str().to_string()),
|
||||||
|
TranspileAnnotationValue::Expression(expr, _) => expr
|
||||||
|
.comptime_eval(scope, handler)
|
||||||
|
.ok()
|
||||||
|
.and_then(|val| val.to_string_no_macro())
|
||||||
|
.ok_or_else(|| {
|
||||||
let err =
|
let err =
|
||||||
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
annotation: span.clone(),
|
annotation: identifier_span.clone(),
|
||||||
message: "Deobfuscate annotation cannot be a map.".to_string(),
|
message: "Cannot evaluate annotation at compile time".to_string(),
|
||||||
});
|
});
|
||||||
handler.receive(Box::new(err.clone()));
|
handler.receive(Box::new(err.clone()));
|
||||||
Err(err)
|
err
|
||||||
}
|
}),
|
||||||
},
|
TranspileAnnotationValue::Map(_, span) => {
|
||||||
)?;
|
let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: span.clone(),
|
||||||
let function_location = format!(
|
message: "Deobfuscate annotation cannot be a map.".to_string(),
|
||||||
"{namespace}:{modified_name}",
|
});
|
||||||
namespace = function_data.namespace
|
handler.receive(Box::new(err.clone()));
|
||||||
);
|
Err(err)
|
||||||
|
|
||||||
function_path.set(function_location.clone()).unwrap();
|
|
||||||
|
|
||||||
for (i, param) in function_data.parameters.iter().enumerate() {
|
|
||||||
let param_str = param.identifier().span.str();
|
|
||||||
match param.variable_type() {
|
|
||||||
FunctionVariableType::Macro(_) => {
|
|
||||||
function_scope.set_variable(
|
|
||||||
param_str,
|
|
||||||
VariableData::MacroParameter {
|
|
||||||
index: i,
|
|
||||||
macro_name: crate::util::identifier_to_macro(param_str).to_string(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
FunctionVariableType::Integer(_) => {
|
|
||||||
let objective = format!(
|
|
||||||
"shu_arguments_{}",
|
|
||||||
function_location.replace(['/', ':'], "_")
|
|
||||||
);
|
|
||||||
function_scope.set_variable(
|
|
||||||
param_str,
|
|
||||||
VariableData::ScoreboardValue {
|
|
||||||
objective: objective.clone(),
|
|
||||||
target: crate::util::identifier_to_scoreboard_target(param_str)
|
|
||||||
.into_owned(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
FunctionVariableType::Boolean(_) => {
|
|
||||||
let storage_name = format!(
|
|
||||||
"shulkerscript:arguments_{}",
|
|
||||||
function_location.replace(['/', ':'], "_")
|
|
||||||
);
|
|
||||||
// TODO: replace with proper path
|
|
||||||
function_scope.set_variable(
|
|
||||||
param_str,
|
|
||||||
VariableData::BooleanStorage {
|
|
||||||
storage_name,
|
|
||||||
path: crate::util::identifier_to_scoreboard_target(param_str)
|
|
||||||
.into_owned(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
let commands =
|
if let Some(suffix) = function_suffix {
|
||||||
self.transpile_function(&statements, program_identifier, function_scope, handler)?;
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
let namespace = self.datapack.namespace_mut(&function_data.namespace);
|
let _ = write!(modified_name, "/{suffix}");
|
||||||
|
}
|
||||||
|
|
||||||
if namespace.function(&modified_name).is_some() {
|
let function_location = format!(
|
||||||
let err = TranspileError::ConflictingFunctionNames(ConflictingFunctionNames {
|
"{namespace}:{modified_name}",
|
||||||
name: modified_name,
|
namespace = function_data.namespace
|
||||||
definition: identifier_span.clone(),
|
);
|
||||||
});
|
|
||||||
handler.receive(Box::new(err.clone()));
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let function = namespace.function_mut(&modified_name);
|
set_function_path(function_location.clone());
|
||||||
function.get_commands_mut().extend(commands);
|
|
||||||
|
|
||||||
if function_data.annotations.contains_key("tick") {
|
for (i, (param, comptime_arg)) in function_data
|
||||||
self.datapack.add_tick(&function_location);
|
.parameters
|
||||||
}
|
.iter()
|
||||||
if function_data.annotations.contains_key("load") {
|
.zip(comptime_args)
|
||||||
self.datapack.add_load(&function_location);
|
.enumerate()
|
||||||
|
{
|
||||||
|
let param_str = param.identifier().span.str();
|
||||||
|
match param.variable_type() {
|
||||||
|
FunctionVariableType::Macro(_) => {
|
||||||
|
function_scope.set_variable(
|
||||||
|
param_str,
|
||||||
|
VariableData::MacroParameter {
|
||||||
|
index: i,
|
||||||
|
macro_name: crate::util::identifier_to_macro(param_str).to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
FunctionVariableType::Integer(_) => {
|
||||||
|
let objective = format!(
|
||||||
|
"shu_arguments_{}",
|
||||||
|
function_location.replace(['/', ':'], "_")
|
||||||
|
);
|
||||||
|
function_scope.set_variable(
|
||||||
|
param_str,
|
||||||
|
VariableData::ScoreboardValue {
|
||||||
|
objective: objective.clone(),
|
||||||
|
target: crate::util::identifier_to_scoreboard_target(param_str)
|
||||||
|
.into_owned(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
FunctionVariableType::Boolean(_) => {
|
||||||
|
let storage_name = format!(
|
||||||
|
"shulkerscript:arguments_{}",
|
||||||
|
function_location.replace(['/', ':'], "_")
|
||||||
|
);
|
||||||
|
// TODO: replace with proper path
|
||||||
|
function_scope.set_variable(
|
||||||
|
param_str,
|
||||||
|
VariableData::BooleanStorage {
|
||||||
|
storage_name,
|
||||||
|
path: crate::util::identifier_to_scoreboard_target(param_str)
|
||||||
|
.into_owned(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
FunctionVariableType::Value(_) => {
|
||||||
|
function_scope.set_variable(
|
||||||
|
param_str,
|
||||||
|
VariableData::ComptimeValue {
|
||||||
|
value: Arc::new(RwLock::new(comptime_arg)),
|
||||||
|
read_only: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let function_location = function_path
|
let commands =
|
||||||
.get()
|
self.transpile_function(&statements, program_identifier, function_scope, handler)?;
|
||||||
.ok_or_else(|| {
|
|
||||||
let err = TranspileError::MissingFunctionDeclaration(
|
|
||||||
MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope),
|
|
||||||
);
|
|
||||||
handler.receive(Box::new(err.clone()));
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.map(String::to_owned)?;
|
|
||||||
|
|
||||||
let args = self.transpile_function_arguments(
|
let namespace = self.datapack.namespace_mut(&function_data.namespace);
|
||||||
function_data,
|
|
||||||
&function_location,
|
|
||||||
arguments,
|
|
||||||
scope,
|
|
||||||
handler,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok((function_location, args))
|
if namespace.function(&modified_name).is_some() {
|
||||||
|
let err = TranspileError::ConflictingFunctionNames(ConflictingFunctionNames {
|
||||||
|
name: modified_name,
|
||||||
|
definition: identifier_span.clone(),
|
||||||
|
});
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let function = namespace.function_mut(&modified_name);
|
||||||
|
function.get_commands_mut().extend(commands);
|
||||||
|
|
||||||
|
if function_data.annotations.contains_key("tick") {
|
||||||
|
self.datapack.add_tick(&function_location);
|
||||||
|
}
|
||||||
|
if function_data.annotations.contains_key("load") {
|
||||||
|
self.datapack.add_load(&function_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transpile_function(
|
fn transpile_function(
|
||||||
|
@ -238,6 +332,51 @@ impl Transpiler {
|
||||||
Ok(commands)
|
Ok(commands)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::unused_self, clippy::needless_pass_by_ref_mut)]
|
||||||
|
fn transpile_comptime_function_arguments(
|
||||||
|
&mut self,
|
||||||
|
function_data: &FunctionData,
|
||||||
|
arguments: Option<&[&Expression]>,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<Vec<Option<ComptimeValue>>> {
|
||||||
|
let parameters = &function_data.parameters;
|
||||||
|
let arg_count = arguments.map(<[&Expression]>::len).unwrap_or_default();
|
||||||
|
|
||||||
|
let mut has_comptime = parameters
|
||||||
|
.iter()
|
||||||
|
.any(|param| matches!(param.variable_type(), FunctionVariableType::Value(_)));
|
||||||
|
|
||||||
|
if has_comptime {
|
||||||
|
if parameters.len() == arg_count {
|
||||||
|
let vals = parameters
|
||||||
|
.iter()
|
||||||
|
.zip(arguments.iter().flat_map(|args| args.iter()))
|
||||||
|
.map(|(param, arg)| match param.variable_type() {
|
||||||
|
FunctionVariableType::Value(_) => {
|
||||||
|
has_comptime = true;
|
||||||
|
let val = arg.comptime_eval(scope, handler)?;
|
||||||
|
Ok(Some(val))
|
||||||
|
}
|
||||||
|
_ => Ok(None),
|
||||||
|
})
|
||||||
|
.collect::<TranspileResult<Vec<Option<ComptimeValue>>>>()?;
|
||||||
|
|
||||||
|
Ok(vals)
|
||||||
|
} else {
|
||||||
|
let err = TranspileError::InvalidFunctionArguments(InvalidFunctionArguments {
|
||||||
|
expected: parameters.len(),
|
||||||
|
actual: arg_count,
|
||||||
|
span: function_data.identifier_span.clone(),
|
||||||
|
});
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(vec![None; parameters.len()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[expect(clippy::too_many_lines)]
|
#[expect(clippy::too_many_lines)]
|
||||||
fn transpile_function_arguments(
|
fn transpile_function_arguments(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -264,6 +403,7 @@ impl Transpiler {
|
||||||
Some(arg_count) if arg_count > 0 => {
|
Some(arg_count) if arg_count > 0 => {
|
||||||
#[derive(Debug, Clone, EnumAsInner)]
|
#[derive(Debug, Clone, EnumAsInner)]
|
||||||
enum Parameter {
|
enum Parameter {
|
||||||
|
Comptime,
|
||||||
Static(MacroString),
|
Static(MacroString),
|
||||||
Storage {
|
Storage {
|
||||||
prepare_cmds: Vec<Command>,
|
prepare_cmds: Vec<Command>,
|
||||||
|
@ -275,64 +415,119 @@ impl Transpiler {
|
||||||
let mut compiled_args = Vec::<Parameter>::new();
|
let mut compiled_args = Vec::<Parameter>::new();
|
||||||
let mut errs = Vec::new();
|
let mut errs = Vec::new();
|
||||||
|
|
||||||
for expression in arguments.iter().flat_map(|expressions| expressions.iter()) {
|
for (expression, is_comptime) in arguments
|
||||||
let value = match expression {
|
.iter()
|
||||||
Expression::Primary(Primary::Lua(lua)) => {
|
.flat_map(|expressions| expressions.iter())
|
||||||
lua.eval_comptime(scope, handler).and_then(|val| match val {
|
.zip(
|
||||||
Ok(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)),
|
parameters
|
||||||
Ok(val) => Ok(Parameter::Static(val.to_macro_string())),
|
.iter()
|
||||||
Err(err) => {
|
.map(|p| matches!(p.variable_type(), FunctionVariableType::Value(_))),
|
||||||
let err = TranspileError::NotComptime(err);
|
)
|
||||||
handler.receive(Box::new(err.clone()));
|
{
|
||||||
Err(err)
|
let value = if is_comptime {
|
||||||
}
|
Ok(Parameter::Comptime)
|
||||||
})
|
} else {
|
||||||
}
|
match expression {
|
||||||
Expression::Primary(Primary::Integer(num)) => {
|
Expression::Primary(Primary::Lua(lua)) => {
|
||||||
Ok(Parameter::Static(num.span.str().to_string().into()))
|
lua.eval_comptime(scope, handler).and_then(|val| match val {
|
||||||
}
|
Ok(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)),
|
||||||
Expression::Primary(Primary::Boolean(bool)) => {
|
Ok(val) => Ok(Parameter::Static(val.to_macro_string())),
|
||||||
Ok(Parameter::Static(bool.span.str().to_string().into()))
|
Err(err) => {
|
||||||
}
|
let err = TranspileError::NotComptime(err);
|
||||||
Expression::Primary(Primary::StringLiteral(string)) => {
|
handler.receive(Box::new(err.clone()));
|
||||||
Ok(Parameter::Static(string.str_content().to_string().into()))
|
Err(err)
|
||||||
}
|
}
|
||||||
Expression::Primary(Primary::MacroStringLiteral(literal)) => {
|
})
|
||||||
Ok(Parameter::Static(literal.into()))
|
}
|
||||||
}
|
Expression::Primary(Primary::Integer(num)) => {
|
||||||
Expression::Primary(primary @ Primary::Identifier(ident)) => {
|
Ok(Parameter::Static(num.span.str().to_string().into()))
|
||||||
let var = scope.get_variable(ident.span.str()).ok_or_else(|| {
|
}
|
||||||
let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
|
Expression::Primary(Primary::Boolean(bool)) => {
|
||||||
identifier: ident.span(),
|
Ok(Parameter::Static(bool.span.str().to_string().into()))
|
||||||
});
|
}
|
||||||
handler.receive(Box::new(err.clone()));
|
Expression::Primary(Primary::StringLiteral(string)) => {
|
||||||
err
|
Ok(Parameter::Static(string.str_content().to_string().into()))
|
||||||
})?;
|
}
|
||||||
match var.as_ref() {
|
Expression::Primary(Primary::MacroStringLiteral(literal)) => {
|
||||||
VariableData::MacroParameter { macro_name, .. } => {
|
Ok(Parameter::Static(literal.into()))
|
||||||
Ok(Parameter::Static(MacroString::MacroString(vec![
|
}
|
||||||
MacroStringPart::MacroUsage(macro_name.clone()),
|
Expression::Primary(primary @ Primary::Identifier(ident)) => {
|
||||||
])))
|
let var =
|
||||||
}
|
scope.get_variable(ident.span.str()).ok_or_else(|| {
|
||||||
|
let err =
|
||||||
|
TranspileError::UnknownIdentifier(UnknownIdentifier {
|
||||||
|
identifier: ident.span(),
|
||||||
|
});
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
err
|
||||||
|
})?;
|
||||||
|
match var.as_ref() {
|
||||||
|
VariableData::MacroParameter { macro_name, .. } => {
|
||||||
|
Ok(Parameter::Static(MacroString::MacroString(vec![
|
||||||
|
MacroStringPart::MacroUsage(macro_name.clone()),
|
||||||
|
])))
|
||||||
|
}
|
||||||
|
|
||||||
VariableData::BooleanStorage { .. }
|
VariableData::BooleanStorage { .. }
|
||||||
| VariableData::ScoreboardValue { .. } => {
|
| VariableData::ScoreboardValue { .. } => {
|
||||||
let (temp_storage, [temp_path]) =
|
let (temp_storage, [temp_path]) =
|
||||||
self.get_temp_storage_locations_array();
|
self.get_temp_storage_locations_array();
|
||||||
let prepare_cmds = self.transpile_primary_expression(
|
let prepare_cmds = self.transpile_primary_expression(
|
||||||
primary,
|
primary,
|
||||||
&super::expression::DataLocation::Storage {
|
&super::expression::DataLocation::Storage {
|
||||||
storage_name: temp_storage.clone(),
|
storage_name: temp_storage.clone(),
|
||||||
path: temp_path.clone(),
|
path: temp_path.clone(),
|
||||||
r#type: match var.as_ref() {
|
r#type: match var.as_ref() {
|
||||||
VariableData::BooleanStorage { .. } => {
|
VariableData::BooleanStorage { .. } => {
|
||||||
StorageType::Boolean
|
StorageType::Boolean
|
||||||
}
|
}
|
||||||
VariableData::ScoreboardValue { .. } => {
|
VariableData::ScoreboardValue { .. } => {
|
||||||
StorageType::Int
|
StorageType::Int
|
||||||
}
|
}
|
||||||
_ => unreachable!("checked in parent match"),
|
_ => unreachable!("checked in parent match"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Parameter::Storage {
|
||||||
|
prepare_cmds,
|
||||||
|
storage_name: temp_storage,
|
||||||
|
path: temp_path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let err =
|
||||||
|
TranspileError::MismatchedTypes(MismatchedTypes {
|
||||||
|
expression: expression.span(),
|
||||||
|
expected_type: ExpectedType::AnyOf(vec![
|
||||||
|
ExpectedType::Integer,
|
||||||
|
ExpectedType::Boolean,
|
||||||
|
ExpectedType::String,
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::Primary(Primary::MemberAccess(member_access)) => {
|
||||||
|
if let Ok(value) = member_access.parent().comptime_member_access(
|
||||||
|
member_access,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
) {
|
||||||
|
Ok(Parameter::Static(value.to_macro_string()))
|
||||||
|
} else {
|
||||||
|
let (storage_name, [path]) =
|
||||||
|
self.get_temp_storage_locations_array();
|
||||||
|
let prepare_cmds = self.transpile_expression(
|
||||||
|
expression,
|
||||||
|
&super::expression::DataLocation::Storage {
|
||||||
|
storage_name: storage_name.clone(),
|
||||||
|
path: path.clone(),
|
||||||
|
r#type: StorageType::Int,
|
||||||
},
|
},
|
||||||
scope,
|
scope,
|
||||||
handler,
|
handler,
|
||||||
|
@ -340,39 +535,25 @@ impl Transpiler {
|
||||||
|
|
||||||
Ok(Parameter::Storage {
|
Ok(Parameter::Storage {
|
||||||
prepare_cmds,
|
prepare_cmds,
|
||||||
storage_name: temp_storage,
|
storage_name,
|
||||||
path: temp_path,
|
path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
let err = TranspileError::MismatchedTypes(MismatchedTypes {
|
|
||||||
expression: expression.span(),
|
|
||||||
expected_type: ExpectedType::AnyOf(vec![
|
|
||||||
ExpectedType::Integer,
|
|
||||||
ExpectedType::Boolean,
|
|
||||||
ExpectedType::String,
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
handler.receive(Box::new(err.clone()));
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
Expression::Primary(
|
||||||
Expression::Primary(Primary::MemberAccess(member_access)) => {
|
Primary::Parenthesized(_)
|
||||||
if let Ok(value) = member_access.parent().comptime_member_access(
|
| Primary::Prefix(_)
|
||||||
member_access,
|
| Primary::Indexed(_)
|
||||||
scope,
|
| Primary::FunctionCall(_),
|
||||||
handler,
|
)
|
||||||
) {
|
| Expression::Binary(_) => {
|
||||||
Ok(Parameter::Static(value.to_macro_string()))
|
let (temp_storage, [temp_path]) =
|
||||||
} else {
|
|
||||||
let (storage_name, [path]) =
|
|
||||||
self.get_temp_storage_locations_array();
|
self.get_temp_storage_locations_array();
|
||||||
let prepare_cmds = self.transpile_expression(
|
let prepare_cmds = self.transpile_expression(
|
||||||
expression,
|
expression,
|
||||||
&super::expression::DataLocation::Storage {
|
&super::expression::DataLocation::Storage {
|
||||||
storage_name: storage_name.clone(),
|
storage_name: temp_storage.clone(),
|
||||||
path: path.clone(),
|
path: temp_path.clone(),
|
||||||
r#type: StorageType::Int,
|
r#type: StorageType::Int,
|
||||||
},
|
},
|
||||||
scope,
|
scope,
|
||||||
|
@ -381,37 +562,11 @@ impl Transpiler {
|
||||||
|
|
||||||
Ok(Parameter::Storage {
|
Ok(Parameter::Storage {
|
||||||
prepare_cmds,
|
prepare_cmds,
|
||||||
storage_name,
|
storage_name: temp_storage,
|
||||||
path,
|
path: temp_path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::Primary(
|
|
||||||
Primary::Parenthesized(_)
|
|
||||||
| Primary::Prefix(_)
|
|
||||||
| Primary::Indexed(_)
|
|
||||||
| Primary::FunctionCall(_),
|
|
||||||
)
|
|
||||||
| Expression::Binary(_) => {
|
|
||||||
let (temp_storage, [temp_path]) =
|
|
||||||
self.get_temp_storage_locations_array();
|
|
||||||
let prepare_cmds = self.transpile_expression(
|
|
||||||
expression,
|
|
||||||
&super::expression::DataLocation::Storage {
|
|
||||||
storage_name: temp_storage.clone(),
|
|
||||||
path: temp_path.clone(),
|
|
||||||
r#type: StorageType::Int,
|
|
||||||
},
|
|
||||||
scope,
|
|
||||||
handler,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(Parameter::Storage {
|
|
||||||
prepare_cmds,
|
|
||||||
storage_name: temp_storage,
|
|
||||||
path: temp_path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
|
@ -437,6 +592,7 @@ impl Transpiler {
|
||||||
FunctionVariableType::Macro(_) => {
|
FunctionVariableType::Macro(_) => {
|
||||||
let arg_name = crate::util::identifier_to_macro(param.identifier().span.str());
|
let arg_name = crate::util::identifier_to_macro(param.identifier().span.str());
|
||||||
match data {
|
match data {
|
||||||
|
Parameter::Comptime => {}
|
||||||
Parameter::Static(s) => {
|
Parameter::Static(s) => {
|
||||||
match s {
|
match s {
|
||||||
MacroString::String(value) => statics.insert(
|
MacroString::String(value) => statics.insert(
|
||||||
|
@ -469,6 +625,7 @@ impl Transpiler {
|
||||||
let target = crate::util::identifier_to_scoreboard_target(param_str);
|
let target = crate::util::identifier_to_scoreboard_target(param_str);
|
||||||
|
|
||||||
match data {
|
match data {
|
||||||
|
Parameter::Comptime => {}
|
||||||
Parameter::Static(s) => {
|
Parameter::Static(s) => {
|
||||||
match s.as_str() {
|
match s.as_str() {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
|
@ -502,6 +659,7 @@ impl Transpiler {
|
||||||
let target_path = crate::util::identifier_to_scoreboard_target(param_str);
|
let target_path = crate::util::identifier_to_scoreboard_target(param_str);
|
||||||
|
|
||||||
match data {
|
match data {
|
||||||
|
Parameter::Comptime => {}
|
||||||
Parameter::Static(s) => {
|
Parameter::Static(s) => {
|
||||||
match s.as_str() {
|
match s.as_str() {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
|
@ -525,6 +683,9 @@ impl Transpiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
FunctionVariableType::Value(_) => {
|
||||||
|
// handled before in `transpile_comptime_function_arguments`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(require_dyn_params, acc_setup, acc_move, statics)},
|
(require_dyn_params, acc_setup, acc_move, statics)},
|
||||||
);
|
);
|
||||||
|
@ -608,3 +769,31 @@ impl Transpiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn comptime_args_hash(args: &[Option<ComptimeValue>]) -> String {
|
||||||
|
let combined = args
|
||||||
|
.iter()
|
||||||
|
.filter_map(Option::as_ref)
|
||||||
|
.map(|arg| match arg {
|
||||||
|
ComptimeValue::Boolean(b) => Cow::Owned(b.to_string()),
|
||||||
|
ComptimeValue::Integer(i) => Cow::Owned(i.to_string()),
|
||||||
|
ComptimeValue::String(s) => Cow::Borrowed(s.as_str()),
|
||||||
|
ComptimeValue::MacroString(s) => match s.as_str() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(parts) => {
|
||||||
|
let s = parts
|
||||||
|
.iter()
|
||||||
|
.map(|p| match p {
|
||||||
|
MacroStringPart::String(s) => Cow::Borrowed(s.as_str()),
|
||||||
|
MacroStringPart::MacroUsage(u) => Cow::Owned(format!("`{u}`")),
|
||||||
|
})
|
||||||
|
.join("\0\0");
|
||||||
|
|
||||||
|
Cow::Owned(s)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.join("\0");
|
||||||
|
|
||||||
|
md5::hash(combined).to_hex_lowercase()
|
||||||
|
}
|
||||||
|
|
|
@ -426,7 +426,7 @@ mod disabled {
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> TranspileResult<((), ())> {
|
) -> TranspileResult<((), ())> {
|
||||||
let _ = scope;
|
let _ = scope;
|
||||||
handler.receive(TranspileError::LuaDisabled);
|
handler.receive(Box::new(TranspileError::LuaDisabled));
|
||||||
tracing::error!("Lua code evaluation is disabled");
|
tracing::error!("Lua code evaluation is disabled");
|
||||||
Err(TranspileError::LuaDisabled)
|
Err(TranspileError::LuaDisabled)
|
||||||
}
|
}
|
||||||
|
@ -440,9 +440,9 @@ mod disabled {
|
||||||
&self,
|
&self,
|
||||||
scope: &Arc<Scope>,
|
scope: &Arc<Scope>,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> TranspileResult<Option<ComptimeValue>> {
|
) -> TranspileResult<Result<ComptimeValue, crate::transpile::error::NotComptime>> {
|
||||||
let _ = scope;
|
let _ = scope;
|
||||||
handler.receive(TranspileError::LuaDisabled);
|
handler.receive(Box::new(TranspileError::LuaDisabled));
|
||||||
tracing::error!("Lua code evaluation is disabled");
|
tracing::error!("Lua code evaluation is disabled");
|
||||||
Err(TranspileError::LuaDisabled)
|
Err(TranspileError::LuaDisabled)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use crate::{
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[cfg(feature = "shulkerbox")]
|
#[cfg(feature = "shulkerbox")]
|
||||||
pub mod conversions;
|
pub mod conversions;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
pub mod expression;
|
pub mod expression;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
//! Transpiler for `Shulkerscript`
|
//! Transpiler for `Shulkerscript`
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, BTreeSet, HashSet},
|
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
sync::{Arc, OnceLock},
|
sync::{Arc, OnceLock, RwLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -13,7 +13,7 @@ use crate::{
|
||||||
base::{self, source_file::SourceElement, Handler},
|
base::{self, source_file::SourceElement, Handler},
|
||||||
semantic::error::UnexpectedExpression,
|
semantic::error::UnexpectedExpression,
|
||||||
syntax::syntax_tree::{
|
syntax::syntax_tree::{
|
||||||
declaration::{Declaration, ImportItems},
|
declaration::{Declaration, FunctionVariableType, ImportItems},
|
||||||
expression::{Expression, FunctionCall, PrefixOperator, Primary},
|
expression::{Expression, FunctionCall, PrefixOperator, Primary},
|
||||||
program::{Namespace, ProgramFile},
|
program::{Namespace, ProgramFile},
|
||||||
statement::{
|
statement::{
|
||||||
|
@ -25,6 +25,7 @@ use crate::{
|
||||||
transpile::{
|
transpile::{
|
||||||
error::IllegalAnnotationContent,
|
error::IllegalAnnotationContent,
|
||||||
util::{MacroString, MacroStringPart},
|
util::{MacroString, MacroStringPart},
|
||||||
|
variables::FunctionVariableDataType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -240,6 +241,7 @@ impl Transpiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transpiles the given declaration.
|
/// Transpiles the given declaration.
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
fn transpile_declaration(
|
fn transpile_declaration(
|
||||||
&mut self,
|
&mut self,
|
||||||
declaration: &Declaration,
|
declaration: &Declaration,
|
||||||
|
@ -283,12 +285,25 @@ impl Transpiler {
|
||||||
public: function.is_public(),
|
public: function.is_public(),
|
||||||
annotations,
|
annotations,
|
||||||
};
|
};
|
||||||
|
let has_comptime_params = function_data
|
||||||
|
.parameters
|
||||||
|
.iter()
|
||||||
|
.any(|param| matches!(param.variable_type(), FunctionVariableType::Value(_)));
|
||||||
|
let function_scope = Scope::with_parent(scope.clone());
|
||||||
scope.set_variable(
|
scope.set_variable(
|
||||||
&name,
|
&name,
|
||||||
VariableData::Function {
|
VariableData::Function {
|
||||||
function_data,
|
function_data,
|
||||||
path: OnceLock::new(),
|
function_scope,
|
||||||
function_scope: Scope::with_parent(scope.clone()),
|
variable_data: if has_comptime_params {
|
||||||
|
FunctionVariableDataType::ComptimeArguments {
|
||||||
|
function_paths: RwLock::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FunctionVariableDataType::Simple {
|
||||||
|
path: OnceLock::new(),
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -297,8 +312,6 @@ impl Transpiler {
|
||||||
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;
|
|
||||||
|
|
||||||
match import.items() {
|
match import.items() {
|
||||||
ImportItems::All(_) => {
|
ImportItems::All(_) => {
|
||||||
handler.receive(base::Error::Other(
|
handler.receive(base::Error::Other(
|
||||||
|
|
|
@ -47,10 +47,10 @@ pub enum VariableData {
|
||||||
Function {
|
Function {
|
||||||
/// The function data.
|
/// The function data.
|
||||||
function_data: FunctionData,
|
function_data: FunctionData,
|
||||||
/// The path to the function once it is generated.
|
|
||||||
path: OnceLock<String>,
|
|
||||||
/// The scope of the function.
|
/// The scope of the function.
|
||||||
function_scope: Arc<Scope>,
|
function_scope: Arc<Scope>,
|
||||||
|
/// The variable data.
|
||||||
|
variable_data: FunctionVariableDataType,
|
||||||
},
|
},
|
||||||
/// A macro function parameter.
|
/// A macro function parameter.
|
||||||
MacroParameter {
|
MacroParameter {
|
||||||
|
@ -111,6 +111,34 @@ pub enum VariableData {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FunctionVariableDataType {
|
||||||
|
Simple {
|
||||||
|
/// The path to the function once it is generated.
|
||||||
|
path: OnceLock<String>,
|
||||||
|
},
|
||||||
|
ComptimeArguments {
|
||||||
|
/// The paths of the functions with different comptime arguments by hash.
|
||||||
|
function_paths: RwLock<HashMap<String, String>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
impl Clone for FunctionVariableDataType {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Simple { path } => Self::Simple { path: path.clone() },
|
||||||
|
Self::ComptimeArguments { function_paths } => {
|
||||||
|
let function_paths = function_paths.read().unwrap();
|
||||||
|
Self::ComptimeArguments {
|
||||||
|
function_paths: RwLock::new(function_paths.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, EnumAsInner)]
|
#[derive(Debug, Clone, Copy, EnumAsInner)]
|
||||||
pub enum TranspileAssignmentTarget<'a> {
|
pub enum TranspileAssignmentTarget<'a> {
|
||||||
Identifier(&'a Identifier),
|
Identifier(&'a Identifier),
|
||||||
|
|
|
@ -62,7 +62,7 @@ pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<'_, str> {
|
||||||
/// Does only strip invalid characters if the `shulkerbox` feature is not enabled.
|
/// Does only strip invalid characters if the `shulkerbox` feature is not enabled.
|
||||||
#[cfg(not(feature = "shulkerbox"))]
|
#[cfg(not(feature = "shulkerbox"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<str> {
|
pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<'_, str> {
|
||||||
if ident.contains("__")
|
if ident.contains("__")
|
||||||
|| ident
|
|| ident
|
||||||
.chars()
|
.chars()
|
||||||
|
@ -98,7 +98,7 @@ pub fn identifier_to_scoreboard_target(ident: &str) -> std::borrow::Cow<'_, str>
|
||||||
/// Does only strip invalid characters if the `shulkerbox` feature is not enabled.
|
/// Does only strip invalid characters if the `shulkerbox` feature is not enabled.
|
||||||
#[cfg(not(feature = "shulkerbox"))]
|
#[cfg(not(feature = "shulkerbox"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn identifier_to_scoreboard_target(ident: &str) -> std::borrow::Cow<str> {
|
pub fn identifier_to_scoreboard_target(ident: &str) -> std::borrow::Cow<'_, str> {
|
||||||
if !(..=16).contains(&ident.len())
|
if !(..=16).contains(&ident.len())
|
||||||
|| ident
|
|| ident
|
||||||
.chars()
|
.chars()
|
||||||
|
|
Loading…
Reference in New Issue