Compare commits
3 Commits
0885665baf
...
0fd9dc432e
Author | SHA1 | Date |
---|---|---|
|
0fd9dc432e | |
|
237207a447 | |
|
b119ca33c7 |
|
@ -18,8 +18,8 @@ license = "MIT OR Apache-2.0"
|
||||||
default = ["fs_access", "lua", "shulkerbox", "zip"]
|
default = ["fs_access", "lua", "shulkerbox", "zip"]
|
||||||
fs_access = ["shulkerbox?/fs_access"]
|
fs_access = ["shulkerbox?/fs_access"]
|
||||||
lua = ["dep:mlua"]
|
lua = ["dep:mlua"]
|
||||||
serde = ["dep:serde", "dep:flexbuffers", "shulkerbox?/serde"]
|
serde = ["dep:serde", "dep:serde_json", "shulkerbox?/serde"]
|
||||||
shulkerbox = ["dep:shulkerbox", "dep:chksum-md5"]
|
shulkerbox = ["dep:shulkerbox", "dep:chksum-md5", "dep:serde_json"]
|
||||||
zip = ["shulkerbox?/zip"]
|
zip = ["shulkerbox?/zip"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -28,12 +28,12 @@ chksum-md5 = { version = "0.1.0", optional = true }
|
||||||
colored = "3.0.0"
|
colored = "3.0.0"
|
||||||
derive_more = { version = "2.0.1", default-features = false, features = ["deref", "deref_mut", "from"] }
|
derive_more = { version = "2.0.1", default-features = false, features = ["deref", "deref_mut", "from"] }
|
||||||
enum-as-inner = "0.6.0"
|
enum-as-inner = "0.6.0"
|
||||||
flexbuffers = { version = "25.2.10", optional = true }
|
|
||||||
getset = "0.1.2"
|
getset = "0.1.2"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
mlua = { version = "0.10.2", features = ["lua54", "vendored"], optional = true }
|
mlua = { version = "0.10.2", features = ["lua54", "vendored"], optional = true }
|
||||||
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 }
|
||||||
|
serde_json = { version = "1.0.138", 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 = "e9f2b9b91d72322ec2e063ce7b83415071306468", default-features = false, optional = true }
|
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "e9f2b9b91d72322ec2e063ce7b83415071306468", default-features = false, optional = true }
|
||||||
strsim = "0.11.1"
|
strsim = "0.11.1"
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub enum Error {
|
||||||
IncompatibleFunctionAnnotation(#[from] IncompatibleFunctionAnnotation),
|
IncompatibleFunctionAnnotation(#[from] IncompatibleFunctionAnnotation),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove duplicate error (also in transpile)
|
||||||
/// An error that occurs when a function declaration is missing.
|
/// An error that occurs when a function declaration is missing.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)]
|
||||||
pub struct MissingFunctionDeclaration {
|
pub struct MissingFunctionDeclaration {
|
||||||
|
@ -43,6 +44,7 @@ pub struct MissingFunctionDeclaration {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MissingFunctionDeclaration {
|
impl MissingFunctionDeclaration {
|
||||||
|
#[expect(dead_code)]
|
||||||
pub(super) fn from_context(identifier_span: Span, functions: &HashSet<String>) -> Self {
|
pub(super) fn from_context(identifier_span: Span, functions: &HashSet<String>) -> Self {
|
||||||
let own_name = identifier_span.str();
|
let own_name = identifier_span.str();
|
||||||
let alternatives = functions
|
let alternatives = functions
|
||||||
|
|
|
@ -4,14 +4,11 @@
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use error::{
|
use error::{IncompatibleFunctionAnnotation, InvalidNamespaceName};
|
||||||
IncompatibleFunctionAnnotation, InvalidNamespaceName, MissingFunctionDeclaration,
|
|
||||||
UnresolvedMacroUsage,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{self, source_file::SourceElement as _, Handler},
|
base::{self, source_file::SourceElement as _, Handler},
|
||||||
lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
|
lexical::token::MacroStringLiteral,
|
||||||
syntax::syntax_tree::{
|
syntax::syntax_tree::{
|
||||||
declaration::{Declaration, Function, ImportItems},
|
declaration::{Declaration, Function, ImportItems},
|
||||||
expression::{Expression, FunctionCall, Parenthesized, Primary},
|
expression::{Expression, FunctionCall, Parenthesized, Primary},
|
||||||
|
@ -387,28 +384,29 @@ impl MacroStringLiteral {
|
||||||
/// Analyzes the semantics of the macro string literal.
|
/// Analyzes the semantics of the macro string literal.
|
||||||
pub fn analyze_semantics(
|
pub fn analyze_semantics(
|
||||||
&self,
|
&self,
|
||||||
macro_names: &HashSet<String>,
|
_macro_names: &HashSet<String>,
|
||||||
handler: &impl Handler<base::Error>,
|
_handler: &impl Handler<base::Error>,
|
||||||
) -> Result<(), error::Error> {
|
) -> Result<(), error::Error> {
|
||||||
let mut errors = Vec::new();
|
// let mut errors = Vec::new();
|
||||||
for part in self.parts() {
|
// TODO: allow macro string literals to also contain other variables
|
||||||
if let MacroStringLiteralPart::MacroUsage { identifier, .. } = part {
|
// for part in self.parts() {
|
||||||
if !macro_names.contains(identifier.span.str()) {
|
// if let MacroStringLiteralPart::MacroUsage { identifier, .. } = part {
|
||||||
let err = error::Error::UnresolvedMacroUsage(UnresolvedMacroUsage {
|
// if !macro_names.contains(identifier.span.str()) {
|
||||||
span: identifier.span(),
|
// let err = error::Error::UnresolvedMacroUsage(UnresolvedMacroUsage {
|
||||||
});
|
// span: identifier.span(),
|
||||||
handler.receive(err.clone());
|
// });
|
||||||
errors.push(err);
|
// handler.receive(err.clone());
|
||||||
}
|
// errors.push(err);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
#[expect(clippy::option_if_let_else)]
|
// #[expect(clippy::option_if_let_else)]
|
||||||
if let Some(err) = errors.first() {
|
// if let Some(err) = errors.first() {
|
||||||
Err(err.clone())
|
// Err(err.clone())
|
||||||
} else {
|
// } else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,13 +460,14 @@ impl FunctionCall {
|
||||||
) -> Result<(), error::Error> {
|
) -> Result<(), error::Error> {
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
if !function_names.contains(self.identifier().span.str()) {
|
// TODO: also check for internal functions
|
||||||
let err = error::Error::MissingFunctionDeclaration(
|
// if !function_names.contains(self.identifier().span.str()) {
|
||||||
MissingFunctionDeclaration::from_context(self.identifier().span(), function_names),
|
// let err = error::Error::MissingFunctionDeclaration(
|
||||||
);
|
// MissingFunctionDeclaration::from_context(self.identifier().span(), function_names),
|
||||||
handler.receive(err.clone());
|
// );
|
||||||
errors.push(err);
|
// handler.receive(err.clone());
|
||||||
}
|
// errors.push(err);
|
||||||
|
// }
|
||||||
|
|
||||||
for expression in self
|
for expression in self
|
||||||
.arguments()
|
.arguments()
|
||||||
|
|
|
@ -42,7 +42,7 @@ where
|
||||||
// hold guard so no other can serialize at the same time in same thread
|
// hold guard so no other can serialize at the same time in same thread
|
||||||
let s = DEDUPLICATE_SOURCE_FILES.with(|d| {
|
let s = DEDUPLICATE_SOURCE_FILES.with(|d| {
|
||||||
let guard = d.read().unwrap();
|
let guard = d.read().unwrap();
|
||||||
let mut serialized_data = flexbuffers::FlexbufferSerializer::new();
|
let mut serialized_data = serde_json::Serializer::new(Vec::new());
|
||||||
self.0
|
self.0
|
||||||
.serialize(&mut serialized_data)
|
.serialize(&mut serialized_data)
|
||||||
.map_err(|_| serde::ser::Error::custom("could not buffer serialization"))?;
|
.map_err(|_| serde::ser::Error::custom("could not buffer serialization"))?;
|
||||||
|
|
|
@ -545,6 +545,20 @@ impl<'a> Parser<'a> {
|
||||||
arguments: token_tree.list,
|
arguments: token_tree.list,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
Reading::IntoDelimited(punc) if punc.punctuation == '[' => {
|
||||||
|
let token_tree = self.step_into(
|
||||||
|
Delimiter::Bracket,
|
||||||
|
|p| p.parse_expression(handler),
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Primary::Indexed(Indexed {
|
||||||
|
object: Box::new(Primary::Identifier(identifier)),
|
||||||
|
left_bracket: token_tree.open,
|
||||||
|
index: Box::new(token_tree.tree?),
|
||||||
|
right_bracket: token_tree.close,
|
||||||
|
}))
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// regular identifier
|
// regular identifier
|
||||||
Ok(Primary::Identifier(identifier))
|
Ok(Primary::Identifier(identifier))
|
||||||
|
|
|
@ -572,7 +572,7 @@ pub struct ScoreVariableDeclaration {
|
||||||
close_bracket: Punctuation,
|
close_bracket: Punctuation,
|
||||||
/// The optional assignment of the variable.
|
/// The optional assignment of the variable.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
target_assignment: Option<(AnyStringLiteral, VariableDeclarationAssignment)>,
|
assignment: Option<VariableDeclarationAssignment>,
|
||||||
/// The annotations of the variable declaration.
|
/// The annotations of the variable declaration.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
annotations: VecDeque<Annotation>,
|
annotations: VecDeque<Annotation>,
|
||||||
|
@ -582,17 +582,18 @@ impl SourceElement for ScoreVariableDeclaration {
|
||||||
fn span(&self) -> Span {
|
fn span(&self) -> Span {
|
||||||
self.int_keyword
|
self.int_keyword
|
||||||
.span()
|
.span()
|
||||||
.join(&self.target_assignment.as_ref().map_or_else(
|
.join(
|
||||||
|| self.close_bracket.span(),
|
&self
|
||||||
|(_, assignment)| assignment.span(),
|
.assignment
|
||||||
))
|
.as_ref()
|
||||||
|
.map_or_else(|| self.close_bracket.span(), SourceElement::span),
|
||||||
|
)
|
||||||
.expect("The span of the score variable declaration is invalid.")
|
.expect("The span of the score variable declaration is invalid.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScoreVariableDeclaration {
|
impl ScoreVariableDeclaration {
|
||||||
/// Dissolves the [`ScoreVariableDeclaration`] into its components.
|
/// Dissolves the [`ScoreVariableDeclaration`] into its components.
|
||||||
#[expect(clippy::type_complexity)]
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(
|
pub fn dissolve(
|
||||||
self,
|
self,
|
||||||
|
@ -602,7 +603,7 @@ impl ScoreVariableDeclaration {
|
||||||
Identifier,
|
Identifier,
|
||||||
Punctuation,
|
Punctuation,
|
||||||
Punctuation,
|
Punctuation,
|
||||||
Option<(AnyStringLiteral, VariableDeclarationAssignment)>,
|
Option<VariableDeclarationAssignment>,
|
||||||
) {
|
) {
|
||||||
(
|
(
|
||||||
self.int_keyword,
|
self.int_keyword,
|
||||||
|
@ -610,7 +611,7 @@ impl ScoreVariableDeclaration {
|
||||||
self.identifier,
|
self.identifier,
|
||||||
self.open_bracket,
|
self.open_bracket,
|
||||||
self.close_bracket,
|
self.close_bracket,
|
||||||
self.target_assignment,
|
self.assignment,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -939,9 +940,9 @@ impl<'a> Parser<'a> {
|
||||||
&mut self,
|
&mut self,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> ParseResult<VariableDeclaration> {
|
) -> ParseResult<VariableDeclaration> {
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
enum IndexingType {
|
enum IndexingType {
|
||||||
IntegerSize(Integer),
|
IntegerSize(Integer),
|
||||||
AnyString(AnyStringLiteral),
|
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -992,27 +993,13 @@ impl<'a> Parser<'a> {
|
||||||
IndexingType::IntegerSize(int)
|
IndexingType::IntegerSize(int)
|
||||||
}
|
}
|
||||||
|
|
||||||
Reading::Atomic(Token::StringLiteral(s)) => {
|
|
||||||
let selector = AnyStringLiteral::from(s);
|
|
||||||
p.forward();
|
|
||||||
IndexingType::AnyString(selector)
|
|
||||||
}
|
|
||||||
Reading::Atomic(Token::MacroStringLiteral(s)) => {
|
|
||||||
let selector = AnyStringLiteral::from(s);
|
|
||||||
p.forward();
|
|
||||||
IndexingType::AnyString(selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
Reading::DelimitedEnd(punc) if punc.punctuation == ']' => {
|
Reading::DelimitedEnd(punc) if punc.punctuation == ']' => {
|
||||||
IndexingType::None
|
IndexingType::None
|
||||||
}
|
}
|
||||||
|
|
||||||
unexpected => {
|
unexpected => {
|
||||||
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
||||||
expected: SyntaxKind::Either(&[
|
expected: SyntaxKind::Integer,
|
||||||
SyntaxKind::Integer,
|
|
||||||
SyntaxKind::AnyStringLiteral,
|
|
||||||
]),
|
|
||||||
found: unexpected.into_token(),
|
found: unexpected.into_token(),
|
||||||
});
|
});
|
||||||
handler.receive(err.clone());
|
handler.receive(err.clone());
|
||||||
|
@ -1034,10 +1021,10 @@ impl<'a> Parser<'a> {
|
||||||
let assignment = self
|
let assignment = self
|
||||||
.try_parse(|p| {
|
.try_parse(|p| {
|
||||||
// read equals sign
|
// read equals sign
|
||||||
let equals = p.parse_punctuation('=', true, handler)?;
|
let equals = p.parse_punctuation('=', true, &VoidHandler)?;
|
||||||
|
|
||||||
// read expression
|
// read expression
|
||||||
let expression = p.parse_expression(handler)?;
|
let expression = p.parse_expression(&VoidHandler)?;
|
||||||
|
|
||||||
Ok(VariableDeclarationAssignment { equals, expression })
|
Ok(VariableDeclarationAssignment { equals, expression })
|
||||||
})
|
})
|
||||||
|
@ -1053,37 +1040,6 @@ impl<'a> Parser<'a> {
|
||||||
annotations: VecDeque::new(),
|
annotations: VecDeque::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
IndexingType::AnyString(selector) => {
|
|
||||||
let equals = self.parse_punctuation('=', true, handler)?;
|
|
||||||
let expression = self.parse_expression(handler)?;
|
|
||||||
|
|
||||||
let assignment = VariableDeclarationAssignment { equals, expression };
|
|
||||||
|
|
||||||
match variable_type.keyword {
|
|
||||||
KeywordKind::Int => {
|
|
||||||
Ok(VariableDeclaration::Score(ScoreVariableDeclaration {
|
|
||||||
int_keyword: variable_type,
|
|
||||||
criteria: criteria_selection,
|
|
||||||
identifier,
|
|
||||||
open_bracket,
|
|
||||||
close_bracket,
|
|
||||||
target_assignment: Some((selector, assignment)),
|
|
||||||
annotations: VecDeque::new(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
KeywordKind::Bool => {
|
|
||||||
Ok(VariableDeclaration::Tag(TagVariableDeclaration {
|
|
||||||
bool_keyword: variable_type,
|
|
||||||
identifier,
|
|
||||||
open_bracket,
|
|
||||||
close_bracket,
|
|
||||||
target_assignment: Some((selector, assignment)),
|
|
||||||
annotations: VecDeque::new(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IndexingType::None => match variable_type.keyword {
|
IndexingType::None => match variable_type.keyword {
|
||||||
KeywordKind::Int => {
|
KeywordKind::Int => {
|
||||||
Ok(VariableDeclaration::Score(ScoreVariableDeclaration {
|
Ok(VariableDeclaration::Score(ScoreVariableDeclaration {
|
||||||
|
@ -1092,7 +1048,7 @@ impl<'a> Parser<'a> {
|
||||||
identifier,
|
identifier,
|
||||||
open_bracket,
|
open_bracket,
|
||||||
close_bracket,
|
close_bracket,
|
||||||
target_assignment: None,
|
assignment: None,
|
||||||
annotations: VecDeque::new(),
|
annotations: VecDeque::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,8 @@ pub enum TranspileError {
|
||||||
UnknownIdentifier(#[from] UnknownIdentifier),
|
UnknownIdentifier(#[from] UnknownIdentifier),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
MissingValue(#[from] MissingValue),
|
MissingValue(#[from] MissingValue),
|
||||||
|
#[error(transparent)]
|
||||||
|
IllegalIndexing(#[from] IllegalIndexing),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a transpilation operation.
|
/// The result of a transpilation operation.
|
||||||
|
@ -309,3 +311,54 @@ impl Display for MissingValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for MissingValue {}
|
impl std::error::Error for MissingValue {}
|
||||||
|
|
||||||
|
/// An error that occurs when an indexing operation is not permitted.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct IllegalIndexing {
|
||||||
|
pub reason: IllegalIndexingReason,
|
||||||
|
pub expression: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for IllegalIndexing {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", Message::new(Severity::Error, &self.reason))?;
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.expression, Option::<u8>::None)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for IllegalIndexing {}
|
||||||
|
|
||||||
|
/// The reason why an indexing operation is not permitted.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum IllegalIndexingReason {
|
||||||
|
NotIdentifier,
|
||||||
|
InvalidComptimeType { expected: ExpectedType },
|
||||||
|
IndexOutOfBounds { index: usize, length: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for IllegalIndexingReason {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NotIdentifier => {
|
||||||
|
write!(f, "The expression is not an identifier.")
|
||||||
|
}
|
||||||
|
Self::InvalidComptimeType { expected } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"The expression can only be indexed with type {expected} that can be evaluated at compile time."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Self::IndexOutOfBounds { index, length } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"The index {index} is out of bounds for the expression with length {length}."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use shulkerbox::prelude::{Command, Condition, Execute};
|
||||||
|
|
||||||
#[cfg(feature = "shulkerbox")]
|
#[cfg(feature = "shulkerbox")]
|
||||||
use super::{
|
use super::{
|
||||||
error::{MismatchedTypes, UnknownIdentifier},
|
error::{IllegalIndexing, IllegalIndexingReason, MismatchedTypes, UnknownIdentifier},
|
||||||
TranspileResult, Transpiler,
|
TranspileResult, Transpiler,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "shulkerbox")]
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
@ -31,7 +31,6 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: fix this leading to compile errors without 'shulkerbox' feature
|
|
||||||
/// Compile-time evaluated value
|
/// Compile-time evaluated value
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
@ -42,17 +41,6 @@ pub enum ComptimeValue {
|
||||||
MacroString(MacroString),
|
MacroString(MacroString),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ComptimeValue {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Boolean(boolean) => write!(f, "{boolean}"),
|
|
||||||
Self::Integer(int) => write!(f, "{int}"),
|
|
||||||
Self::String(string) => write!(f, "{string}"),
|
|
||||||
Self::MacroString(macro_string) => write!(f, "{macro_string}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComptimeValue {
|
impl ComptimeValue {
|
||||||
/// Returns the value as a string not containing a macro.
|
/// Returns the value as a string not containing a macro.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -64,6 +52,17 @@ impl ComptimeValue {
|
||||||
Self::MacroString(_) => None,
|
Self::MacroString(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the value as a [`MacroString`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn to_macro_string(&self) -> MacroString {
|
||||||
|
match self {
|
||||||
|
Self::Boolean(boolean) => MacroString::String(boolean.to_string()),
|
||||||
|
Self::Integer(int) => MacroString::String(int.to_string()),
|
||||||
|
Self::String(string) => MacroString::String(string.clone()),
|
||||||
|
Self::MacroString(macro_string) => macro_string.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of an expression.
|
/// The type of an expression.
|
||||||
|
@ -287,16 +286,15 @@ impl Primary {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Self::Indexed(indexed) => {
|
Self::Indexed(indexed) => {
|
||||||
let Self::Identifier(ident) = indexed.object().as_ref() else {
|
if let Self::Identifier(ident) = indexed.object().as_ref() {
|
||||||
todo!("throw error: cannot index anything except identifiers")
|
|
||||||
};
|
|
||||||
scope
|
scope
|
||||||
.get_variable(ident.span.str())
|
.get_variable(ident.span.str())
|
||||||
.map_or(false, |variable| match r#type {
|
.map_or(false, |variable| match r#type {
|
||||||
ValueType::Boolean => {
|
ValueType::Boolean => {
|
||||||
matches!(
|
matches!(
|
||||||
variable.as_ref(),
|
variable.as_ref(),
|
||||||
VariableData::Tag { .. } | VariableData::BooleanStorageArray { .. }
|
VariableData::Tag { .. }
|
||||||
|
| VariableData::BooleanStorageArray { .. }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ValueType::Integer => {
|
ValueType::Integer => {
|
||||||
|
@ -308,6 +306,9 @@ impl Primary {
|
||||||
}
|
}
|
||||||
ValueType::String => false,
|
ValueType::String => false,
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg_attr(not(feature = "lua"), expect(unused_variables))]
|
#[cfg_attr(not(feature = "lua"), expect(unused_variables))]
|
||||||
Self::Lua(lua) => {
|
Self::Lua(lua) => {
|
||||||
|
@ -785,9 +786,16 @@ impl Transpiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Primary::Indexed(indexed) => {
|
Primary::Indexed(indexed) => {
|
||||||
let Primary::Identifier(ident) = indexed.object().as_ref() else {
|
let ident = if let Primary::Identifier(ident) = indexed.object().as_ref() {
|
||||||
todo!("can only index identifier")
|
Ok(ident)
|
||||||
};
|
} else {
|
||||||
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::NotIdentifier,
|
||||||
|
expression: indexed.object().span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
|
}?;
|
||||||
let variable = scope.get_variable(ident.span.str());
|
let variable = scope.get_variable(ident.span.str());
|
||||||
#[expect(clippy::option_if_let_else)]
|
#[expect(clippy::option_if_let_else)]
|
||||||
if let Some(variable) = variable.as_deref() {
|
if let Some(variable) = variable.as_deref() {
|
||||||
|
@ -801,7 +809,14 @@ impl Transpiler {
|
||||||
target,
|
target,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
todo!("can only index scoreboard with comptime string")
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::String,
|
||||||
|
},
|
||||||
|
expression: indexed.index().span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VariableData::ScoreboardArray { objective, targets } => {
|
VariableData::ScoreboardArray { objective, targets } => {
|
||||||
|
@ -818,10 +833,25 @@ impl Transpiler {
|
||||||
target,
|
target,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
todo!("index out of bounds")
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::IndexOutOfBounds {
|
||||||
|
length: targets.len(),
|
||||||
|
index: usize::try_from(index).unwrap_or(usize::MAX),
|
||||||
|
},
|
||||||
|
expression: indexed.index().span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
todo!("can only index array with comptime integer")
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::Integer,
|
||||||
|
},
|
||||||
|
expression: indexed.index().span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VariableData::BooleanStorageArray {
|
VariableData::BooleanStorageArray {
|
||||||
|
@ -842,10 +872,25 @@ impl Transpiler {
|
||||||
r#type: StorageType::Boolean,
|
r#type: StorageType::Boolean,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
todo!("index out of bounds")
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::IndexOutOfBounds {
|
||||||
|
length: paths.len(),
|
||||||
|
index: usize::try_from(index).unwrap_or(usize::MAX),
|
||||||
|
},
|
||||||
|
expression: indexed.index().span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
todo!("can only index array with comptime integer")
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::Integer,
|
||||||
|
},
|
||||||
|
expression: indexed.index().span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -1022,9 +1067,16 @@ impl Transpiler {
|
||||||
self.transpile_expression_as_condition(parenthesized.expression(), scope, handler)
|
self.transpile_expression_as_condition(parenthesized.expression(), scope, handler)
|
||||||
}
|
}
|
||||||
Primary::Indexed(indexed) => {
|
Primary::Indexed(indexed) => {
|
||||||
let Primary::Identifier(ident) = indexed.object().as_ref() else {
|
let ident = if let Primary::Identifier(ident) = indexed.object().as_ref() {
|
||||||
todo!("can only index identifier")
|
Ok(ident)
|
||||||
};
|
} else {
|
||||||
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::NotIdentifier,
|
||||||
|
expression: indexed.object().span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
|
}?;
|
||||||
#[expect(clippy::option_if_let_else)]
|
#[expect(clippy::option_if_let_else)]
|
||||||
if let Some(variable) = scope.get_variable(ident.span.str()).as_deref() {
|
if let Some(variable) = scope.get_variable(ident.span.str()).as_deref() {
|
||||||
#[expect(clippy::single_match_else)]
|
#[expect(clippy::single_match_else)]
|
||||||
|
@ -1049,10 +1101,25 @@ impl Transpiler {
|
||||||
)),
|
)),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
todo!("index out of bounds")
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::IndexOutOfBounds {
|
||||||
|
length: paths.len(),
|
||||||
|
index: usize::try_from(index).unwrap_or(usize::MAX),
|
||||||
|
},
|
||||||
|
expression: indexed.index().span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
todo!("can only index array with comptime integer")
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::Integer,
|
||||||
|
},
|
||||||
|
expression: indexed.index().span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
@ -0,0 +1,317 @@
|
||||||
|
//! Functions provided by the language itself.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
ops::{Bound, Deref, RangeBounds},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use shulkerbox::prelude::Command;
|
||||||
|
|
||||||
|
use serde_json::{json, Value as JsonValue};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
base::{source_file::SourceElement as _, VoidHandler},
|
||||||
|
lexical::token::{Identifier, MacroStringLiteralPart},
|
||||||
|
semantic::error::InvalidFunctionArguments,
|
||||||
|
syntax::syntax_tree::expression::{Expression, FunctionCall, Primary},
|
||||||
|
transpile::{
|
||||||
|
error::{IllegalIndexing, IllegalIndexingReason, LuaRuntimeError, UnknownIdentifier},
|
||||||
|
expression::{ComptimeValue, DataLocation, StorageType},
|
||||||
|
util::MacroString,
|
||||||
|
TranspileError,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Scope, TranspileResult, Transpiler, VariableData};
|
||||||
|
|
||||||
|
/// A function that can be called from the language.
|
||||||
|
pub type InternalFunction =
|
||||||
|
fn(&mut Transpiler, &Arc<Scope>, &FunctionCall) -> TranspileResult<Vec<Command>>;
|
||||||
|
|
||||||
|
/// Adds all internal functions to the scope.
|
||||||
|
pub fn add_all_to_scope(scope: &Arc<Scope>) {
|
||||||
|
scope.set_variable(
|
||||||
|
"print",
|
||||||
|
VariableData::InternalFunction {
|
||||||
|
implementation: print_function,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_args_assert_in_range(
|
||||||
|
call: &FunctionCall,
|
||||||
|
range: impl RangeBounds<usize>,
|
||||||
|
) -> TranspileResult<Vec<&Expression>> {
|
||||||
|
let args = call
|
||||||
|
.arguments()
|
||||||
|
.as_ref()
|
||||||
|
.map(|args| args.elements().map(Deref::deref).collect::<Vec<_>>())
|
||||||
|
.unwrap_or_default();
|
||||||
|
if range.contains(&args.len()) {
|
||||||
|
Ok(args)
|
||||||
|
} else {
|
||||||
|
let span = args
|
||||||
|
.first()
|
||||||
|
.and_then(|first| {
|
||||||
|
args.last()
|
||||||
|
.map(|last| first.span().join(&last.span()).expect("invalid span"))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
call.left_parenthesis()
|
||||||
|
.span()
|
||||||
|
.join(&call.right_parenthesis().span())
|
||||||
|
.expect("invalid span")
|
||||||
|
});
|
||||||
|
|
||||||
|
let actual = args.len();
|
||||||
|
let expected = match range.start_bound() {
|
||||||
|
Bound::Excluded(excluded) => (excluded + 1 > actual).then_some(excluded + 1),
|
||||||
|
Bound::Included(&included) => (included > actual).then_some(included),
|
||||||
|
Bound::Unbounded => None,
|
||||||
|
}
|
||||||
|
.or_else(|| match range.end_bound() {
|
||||||
|
Bound::Excluded(&excluded) => (excluded <= actual).then_some(excluded.wrapping_sub(1)),
|
||||||
|
Bound::Included(&included) => (included < actual).then_some(included),
|
||||||
|
Bound::Unbounded => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Err(TranspileError::InvalidFunctionArguments(
|
||||||
|
InvalidFunctionArguments {
|
||||||
|
expected,
|
||||||
|
actual: args.len(),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
|
fn print_function(
|
||||||
|
transpiler: &mut Transpiler,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
call: &FunctionCall,
|
||||||
|
) -> TranspileResult<Vec<Command>> {
|
||||||
|
const PARAM_COLOR: &str = "gray";
|
||||||
|
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
fn get_identifier_part(
|
||||||
|
ident: &Identifier,
|
||||||
|
_transpiler: &mut Transpiler,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
) -> TranspileResult<(bool, Vec<Command>, JsonValue)> {
|
||||||
|
if let Some(var) = scope.get_variable(ident.span.str()).as_deref() {
|
||||||
|
match var {
|
||||||
|
VariableData::MacroParameter { macro_name, .. } => Ok((
|
||||||
|
true,
|
||||||
|
Vec::new(),
|
||||||
|
json!({"text": format!("$({macro_name})"), "color": PARAM_COLOR}),
|
||||||
|
)),
|
||||||
|
VariableData::ScoreboardValue { objective, target } => Ok((
|
||||||
|
false,
|
||||||
|
Vec::new(),
|
||||||
|
get_data_location(&DataLocation::ScoreboardValue {
|
||||||
|
objective: objective.to_string(),
|
||||||
|
target: target.to_string(),
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
VariableData::BooleanStorage { storage_name, path } => Ok((
|
||||||
|
false,
|
||||||
|
Vec::new(),
|
||||||
|
get_data_location(&DataLocation::Storage {
|
||||||
|
storage_name: storage_name.to_string(),
|
||||||
|
path: path.to_string(),
|
||||||
|
r#type: StorageType::Boolean,
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
_ => todo!("get_identifier_part"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(TranspileError::UnknownIdentifier(UnknownIdentifier {
|
||||||
|
identifier: ident.span(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data_location(location: &DataLocation) -> JsonValue {
|
||||||
|
match location {
|
||||||
|
DataLocation::ScoreboardValue { objective, target } => {
|
||||||
|
json!({"score": {"name": target, "objective": objective}, "color": PARAM_COLOR})
|
||||||
|
}
|
||||||
|
DataLocation::Storage {
|
||||||
|
storage_name, path, ..
|
||||||
|
} => json!({"nbt": path, "storage": storage_name, "color": PARAM_COLOR}),
|
||||||
|
DataLocation::Tag { .. } => todo!("implement tag"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = get_args_assert_in_range(call, 1..=2)?;
|
||||||
|
let first = args.first().expect("checked range");
|
||||||
|
let (target, message_expression) = args.get(1).map_or_else(
|
||||||
|
|| ("@a".into(), first),
|
||||||
|
|second| {
|
||||||
|
(
|
||||||
|
first
|
||||||
|
.comptime_eval(scope, &VoidHandler)
|
||||||
|
.map_or_else(|| "@a".into(), |val| val.to_macro_string()),
|
||||||
|
second,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut contains_macro = matches!(target, MacroString::MacroString(_));
|
||||||
|
|
||||||
|
let (mut cmds, parts) = match message_expression {
|
||||||
|
Expression::Primary(primary) => match primary {
|
||||||
|
Primary::Boolean(boolean) => Ok((
|
||||||
|
Vec::new(),
|
||||||
|
vec![JsonValue::String(boolean.value().to_string())],
|
||||||
|
)),
|
||||||
|
Primary::Integer(integer) => Ok((
|
||||||
|
Vec::new(),
|
||||||
|
vec![JsonValue::String(integer.as_i64().to_string())],
|
||||||
|
)),
|
||||||
|
Primary::StringLiteral(string) => Ok((
|
||||||
|
Vec::new(),
|
||||||
|
vec![JsonValue::String(string.str_content().to_string())],
|
||||||
|
)),
|
||||||
|
Primary::Lua(lua) => {
|
||||||
|
let (ret, _lua) = lua.eval(scope, &VoidHandler)?;
|
||||||
|
Ok((
|
||||||
|
Vec::new(),
|
||||||
|
vec![JsonValue::String(ret.to_string().map_err(|err| {
|
||||||
|
TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err(
|
||||||
|
&err,
|
||||||
|
lua.span(),
|
||||||
|
))
|
||||||
|
})?)],
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Primary::Identifier(ident) => {
|
||||||
|
// TODO: get_identifier_part
|
||||||
|
let (cur_contains_macro, cmds, part) =
|
||||||
|
get_identifier_part(ident, transpiler, scope).expect("failed");
|
||||||
|
contains_macro |= cur_contains_macro;
|
||||||
|
Ok((cmds, vec![part]))
|
||||||
|
}
|
||||||
|
Primary::Indexed(indexed) => match indexed.object().as_ref() {
|
||||||
|
Primary::Identifier(ident) => {
|
||||||
|
match scope.get_variable(ident.span.str()).as_deref() {
|
||||||
|
Some(VariableData::Scoreboard { objective }) => {
|
||||||
|
if let Some(ComptimeValue::String(index)) =
|
||||||
|
indexed.index().comptime_eval(scope, &VoidHandler)
|
||||||
|
{
|
||||||
|
Ok((
|
||||||
|
Vec::new(),
|
||||||
|
vec![get_data_location(&DataLocation::ScoreboardValue {
|
||||||
|
objective: objective.to_string(),
|
||||||
|
target: index,
|
||||||
|
})],
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
todo!("allow macro string, but throw error when index is not constant string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(VariableData::ScoreboardArray { objective, targets }) => {
|
||||||
|
if let Some(ComptimeValue::Integer(index)) =
|
||||||
|
indexed.index().comptime_eval(scope, &VoidHandler)
|
||||||
|
{
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
if let Some(target) = usize::try_from(index)
|
||||||
|
.ok()
|
||||||
|
.and_then(|index| targets.get(index))
|
||||||
|
{
|
||||||
|
Ok((
|
||||||
|
Vec::new(),
|
||||||
|
vec![get_data_location(&DataLocation::ScoreboardValue {
|
||||||
|
objective: objective.to_string(),
|
||||||
|
target: target.to_string(),
|
||||||
|
})],
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
todo!("throw error when index is out of bounds")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!("throw error when index is not constant integer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(VariableData::BooleanStorageArray {
|
||||||
|
storage_name,
|
||||||
|
paths,
|
||||||
|
}) => {
|
||||||
|
if let Some(ComptimeValue::Integer(index)) =
|
||||||
|
indexed.index().comptime_eval(scope, &VoidHandler)
|
||||||
|
{
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
if let Some(path) = usize::try_from(index)
|
||||||
|
.ok()
|
||||||
|
.and_then(|index| paths.get(index))
|
||||||
|
{
|
||||||
|
Ok((
|
||||||
|
Vec::new(),
|
||||||
|
vec![get_data_location(&DataLocation::Storage {
|
||||||
|
storage_name: storage_name.to_string(),
|
||||||
|
path: path.to_string(),
|
||||||
|
r#type: StorageType::Boolean,
|
||||||
|
})],
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
todo!("throw error when index is out of bounds")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!("throw error when index is not constant integer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
expression: indexed.object().span(),
|
||||||
|
reason: IllegalIndexingReason::NotIdentifier,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
Primary::MacroStringLiteral(macro_string) => {
|
||||||
|
let mut cmds = Vec::new();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
for part in macro_string.parts() {
|
||||||
|
match part {
|
||||||
|
MacroStringLiteralPart::Text(text) => {
|
||||||
|
parts.push(JsonValue::String(text.str().to_string()));
|
||||||
|
}
|
||||||
|
MacroStringLiteralPart::MacroUsage { identifier, .. } => {
|
||||||
|
let (cur_contains_macro, cur_cmds, part) =
|
||||||
|
get_identifier_part(identifier, transpiler, scope)?;
|
||||||
|
contains_macro |= cur_contains_macro;
|
||||||
|
cmds.extend(cur_cmds);
|
||||||
|
parts.push(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((cmds, parts))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => todo!("print_function Primary"),
|
||||||
|
},
|
||||||
|
Expression::Binary(_) => todo!("print_function Binary"),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// TODO: prepend prefix with datapack name to parts and remove following
|
||||||
|
let print_args = if parts.len() == 1 {
|
||||||
|
serde_json::to_string(&parts[0]).expect("json serialization failed")
|
||||||
|
} else {
|
||||||
|
serde_json::to_string(&parts).expect("json serialization failed")
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: throw correct error
|
||||||
|
let cmd = format!("tellraw {target} {print_args}");
|
||||||
|
|
||||||
|
let cmd = if contains_macro {
|
||||||
|
Command::UsesMacro(cmd.parse::<MacroString>().expect("cannot fail").into())
|
||||||
|
} else {
|
||||||
|
Command::Raw(cmd)
|
||||||
|
};
|
||||||
|
|
||||||
|
cmds.push(cmd);
|
||||||
|
|
||||||
|
Ok(cmds)
|
||||||
|
}
|
|
@ -4,13 +4,17 @@
|
||||||
mod enabled {
|
mod enabled {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use mlua::{Lua, Value};
|
use mlua::{Lua, Table, Value};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{self, source_file::SourceElement, Handler},
|
base::{self, source_file::SourceElement, Handler},
|
||||||
|
lexical::token::Identifier,
|
||||||
syntax::syntax_tree::expression::LuaCode,
|
syntax::syntax_tree::expression::LuaCode,
|
||||||
transpile::{
|
transpile::{
|
||||||
error::{LuaRuntimeError, MismatchedTypes, TranspileError, TranspileResult},
|
error::{
|
||||||
|
LuaRuntimeError, MismatchedTypes, TranspileError, TranspileResult,
|
||||||
|
UnknownIdentifier,
|
||||||
|
},
|
||||||
expression::{ComptimeValue, ExpectedType},
|
expression::{ComptimeValue, ExpectedType},
|
||||||
Scope, VariableData,
|
Scope, VariableData,
|
||||||
},
|
},
|
||||||
|
@ -53,14 +57,8 @@ mod enabled {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = self.add_globals(&lua, scope) {
|
self.add_globals(&lua, scope)
|
||||||
let err = TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err(
|
.inspect_err(|err| handler.receive(err.clone()))?;
|
||||||
&err,
|
|
||||||
self.span(),
|
|
||||||
));
|
|
||||||
handler.receive(crate::Error::from(err.clone()));
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = lua
|
let res = lua
|
||||||
.load(self.code())
|
.load(self.code())
|
||||||
|
@ -95,10 +93,30 @@ mod enabled {
|
||||||
self.handle_lua_result(lua_result, handler)
|
self.handle_lua_result(lua_result, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_globals(&self, lua: &Lua, scope: &Arc<Scope>) -> mlua::Result<()> {
|
fn add_globals(&self, lua: &Lua, scope: &Arc<Scope>) -> TranspileResult<()> {
|
||||||
let globals = lua.globals();
|
let globals = lua.globals();
|
||||||
|
|
||||||
let shulkerscript_globals = {
|
let shulkerscript_globals = self
|
||||||
|
.get_std_library(lua)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
|
||||||
|
if let Some(inputs) = self.inputs() {
|
||||||
|
for identifier in inputs.elements() {
|
||||||
|
let (name, value) = self.add_input_to_globals(identifier, lua, scope)?;
|
||||||
|
globals
|
||||||
|
.set(name, value)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globals
|
||||||
|
.set("shulkerscript", shulkerscript_globals)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_std_library(&self, lua: &Lua) -> mlua::Result<Table> {
|
||||||
let table = lua.create_table()?;
|
let table = lua.create_table()?;
|
||||||
|
|
||||||
let (location_path, location_start, location_end) = {
|
let (location_path, location_start, location_end) = {
|
||||||
|
@ -127,77 +145,134 @@ mod enabled {
|
||||||
|
|
||||||
table.set("version", crate::VERSION)?;
|
table.set("version", crate::VERSION)?;
|
||||||
|
|
||||||
table
|
Ok(table)
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(inputs) = self.inputs() {
|
|
||||||
for x in inputs.elements() {
|
|
||||||
let name = x.span.str();
|
|
||||||
let value = match scope.get_variable(name).as_deref() {
|
|
||||||
Some(VariableData::MacroParameter { macro_name, .. }) => {
|
|
||||||
Value::String(lua.create_string(format!("$({macro_name})"))?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
|
fn add_input_to_globals<'a>(
|
||||||
|
&self,
|
||||||
|
identifier: &'a Identifier,
|
||||||
|
lua: &Lua,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
) -> TranspileResult<(&'a str, Value)> {
|
||||||
|
let name = identifier.span.str();
|
||||||
|
let value = match scope.get_variable(name).as_deref() {
|
||||||
|
Some(VariableData::MacroParameter { macro_name, .. }) => Value::String(
|
||||||
|
lua.create_string(format!("$({macro_name})"))
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?,
|
||||||
|
),
|
||||||
Some(VariableData::Scoreboard { objective }) => {
|
Some(VariableData::Scoreboard { objective }) => {
|
||||||
let table = lua.create_table()?;
|
let table = lua
|
||||||
table.set("objective", objective.as_str())?;
|
.create_table()
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
table
|
||||||
|
.set("objective", objective.as_str())
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
Value::Table(table)
|
Value::Table(table)
|
||||||
}
|
}
|
||||||
Some(VariableData::ScoreboardValue { objective, target }) => {
|
Some(VariableData::ScoreboardValue { objective, target }) => {
|
||||||
let table = lua.create_table()?;
|
let table = lua
|
||||||
table.set("objective", lua.create_string(objective)?)?;
|
.create_table()
|
||||||
table.set("target", lua.create_string(target)?)?;
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
table
|
||||||
|
.set(
|
||||||
|
"objective",
|
||||||
|
lua.create_string(objective)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?,
|
||||||
|
)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
table
|
||||||
|
.set(
|
||||||
|
"target",
|
||||||
|
lua.create_string(target)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?,
|
||||||
|
)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
Value::Table(table)
|
Value::Table(table)
|
||||||
}
|
}
|
||||||
Some(VariableData::ScoreboardArray { objective, targets }) => {
|
Some(VariableData::ScoreboardArray { objective, targets }) => {
|
||||||
let table = lua.create_table()?;
|
let table = lua
|
||||||
table.set("objective", objective.as_str())?;
|
.create_table()
|
||||||
let values = lua.create_table_from(
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
table
|
||||||
|
.set("objective", objective.as_str())
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
let values = lua
|
||||||
|
.create_table_from(
|
||||||
targets
|
targets
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, target)| (i + 1, target.as_str())),
|
.map(|(i, target)| (i + 1, target.as_str())),
|
||||||
)?;
|
)
|
||||||
table.set("targets", values)?;
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
table
|
||||||
|
.set("targets", values)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
Value::Table(table)
|
Value::Table(table)
|
||||||
}
|
}
|
||||||
Some(VariableData::BooleanStorage { storage_name, path }) => {
|
Some(VariableData::BooleanStorage { storage_name, path }) => {
|
||||||
let table = lua.create_table()?;
|
let table = lua
|
||||||
table.set("storage", lua.create_string(storage_name)?)?;
|
.create_table()
|
||||||
table.set("path", lua.create_string(path)?)?;
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
table
|
||||||
|
.set(
|
||||||
|
"storage",
|
||||||
|
lua.create_string(storage_name)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?,
|
||||||
|
)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
table
|
||||||
|
.set(
|
||||||
|
"path",
|
||||||
|
lua.create_string(path)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?,
|
||||||
|
)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
Value::Table(table)
|
Value::Table(table)
|
||||||
}
|
}
|
||||||
Some(VariableData::BooleanStorageArray {
|
Some(VariableData::BooleanStorageArray {
|
||||||
storage_name,
|
storage_name,
|
||||||
paths,
|
paths,
|
||||||
}) => {
|
}) => {
|
||||||
let table = lua.create_table()?;
|
let table = lua
|
||||||
table.set("storage", storage_name.as_str())?;
|
.create_table()
|
||||||
let values = lua.create_table_from(
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
table
|
||||||
|
.set("storage", storage_name.as_str())
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
let values = lua
|
||||||
|
.create_table_from(
|
||||||
paths
|
paths
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, path)| (i + 1, path.as_str())),
|
.map(|(i, path)| (i + 1, path.as_str())),
|
||||||
)?;
|
)
|
||||||
table.set("paths", values)?;
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
table
|
||||||
|
.set("paths", values)
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
Value::Table(table)
|
Value::Table(table)
|
||||||
}
|
}
|
||||||
Some(VariableData::Tag { tag_name }) => {
|
Some(VariableData::Tag { tag_name }) => {
|
||||||
let table = lua.create_table()?;
|
let table = lua
|
||||||
table.set("name", tag_name.as_str())?;
|
.create_table()
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
|
table
|
||||||
|
.set("name", tag_name.as_str())
|
||||||
|
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||||
Value::Table(table)
|
Value::Table(table)
|
||||||
}
|
}
|
||||||
Some(VariableData::Function { .. }) => {
|
Some(VariableData::Function { .. } | VariableData::InternalFunction { .. }) => {
|
||||||
todo!("allow function variable type")
|
todo!("(internal) functions are not supported yet");
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(TranspileError::UnknownIdentifier(UnknownIdentifier {
|
||||||
|
identifier: identifier.span(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
None => todo!("throw correct error"),
|
|
||||||
};
|
};
|
||||||
globals.set(name, value)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
globals.set("shulkerscript", shulkerscript_globals)?;
|
Ok((name, value))
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_lua_result(
|
fn handle_lua_result(
|
||||||
|
|
|
@ -29,6 +29,9 @@ use strum::EnumIs;
|
||||||
#[cfg_attr(feature = "shulkerbox", doc(inline))]
|
#[cfg_attr(feature = "shulkerbox", doc(inline))]
|
||||||
pub use transpiler::Transpiler;
|
pub use transpiler::Transpiler;
|
||||||
|
|
||||||
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
pub mod internal_functions;
|
||||||
|
|
||||||
mod variables;
|
mod variables;
|
||||||
pub use variables::{Scope, VariableData};
|
pub use variables::{Scope, VariableData};
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ impl Transpiler {
|
||||||
let scope = self
|
let scope = self
|
||||||
.scopes
|
.scopes
|
||||||
.entry(program_identifier)
|
.entry(program_identifier)
|
||||||
.or_default()
|
.or_insert_with(Scope::with_internal_functions)
|
||||||
.to_owned();
|
.to_owned();
|
||||||
self.transpile_program_declarations(program, &scope, handler);
|
self.transpile_program_declarations(program, &scope, handler);
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ impl Transpiler {
|
||||||
let scope = self
|
let scope = self
|
||||||
.scopes
|
.scopes
|
||||||
.entry(identifier_span.source_file().identifier().to_owned())
|
.entry(identifier_span.source_file().identifier().to_owned())
|
||||||
.or_default()
|
.or_insert_with(Scope::with_internal_functions)
|
||||||
.to_owned();
|
.to_owned();
|
||||||
self.get_or_transpile_function(&identifier_span, None, &scope, handler)?;
|
self.get_or_transpile_function(&identifier_span, None, &scope, handler)?;
|
||||||
}
|
}
|
||||||
|
@ -441,7 +441,7 @@ impl Transpiler {
|
||||||
Expression::Primary(Primary::Lua(lua)) => {
|
Expression::Primary(Primary::Lua(lua)) => {
|
||||||
lua.eval_comptime(scope, handler).and_then(|val| match val {
|
lua.eval_comptime(scope, handler).and_then(|val| match val {
|
||||||
Some(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)),
|
Some(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)),
|
||||||
Some(val) => Ok(Parameter::Static(val.to_string().into())),
|
Some(val) => Ok(Parameter::Static(val.to_macro_string())),
|
||||||
None => {
|
None => {
|
||||||
let err = TranspileError::MismatchedTypes(MismatchedTypes {
|
let err = TranspileError::MismatchedTypes(MismatchedTypes {
|
||||||
expression: expression.span(),
|
expression: expression.span(),
|
||||||
|
@ -831,6 +831,13 @@ impl Transpiler {
|
||||||
.arguments()
|
.arguments()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|l| l.elements().map(Deref::deref).collect::<Vec<_>>());
|
.map(|l| l.elements().map(Deref::deref).collect::<Vec<_>>());
|
||||||
|
if let Some(VariableData::InternalFunction { implementation }) =
|
||||||
|
scope.get_variable(func.identifier().span.str()).as_deref()
|
||||||
|
{
|
||||||
|
implementation(self, scope, func).inspect_err(|err| {
|
||||||
|
handler.receive(err.clone());
|
||||||
|
})
|
||||||
|
} else {
|
||||||
let (location, arguments) = self.get_or_transpile_function(
|
let (location, arguments) = self.get_or_transpile_function(
|
||||||
&func.identifier().span,
|
&func.identifier().span,
|
||||||
arguments.as_deref(),
|
arguments.as_deref(),
|
||||||
|
@ -887,6 +894,7 @@ impl Transpiler {
|
||||||
TranspiledFunctionArguments::None => Ok(vec![Command::Raw(function_call)]),
|
TranspiledFunctionArguments::None => Ok(vec![Command::Raw(function_call)]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn transpile_execute_block(
|
fn transpile_execute_block(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#![expect(unused)]
|
#![expect(unused)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::{HashMap, VecDeque},
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
sync::{Arc, OnceLock, RwLock},
|
sync::{Arc, OnceLock, RwLock},
|
||||||
|
@ -20,15 +20,20 @@ use crate::{
|
||||||
syntax::syntax_tree::{
|
syntax::syntax_tree::{
|
||||||
expression::{Expression, Primary},
|
expression::{Expression, Primary},
|
||||||
statement::{
|
statement::{
|
||||||
AssignmentDestination, ScoreVariableDeclaration, SingleVariableDeclaration,
|
ArrayVariableDeclaration, AssignmentDestination, ScoreVariableDeclaration,
|
||||||
VariableDeclaration,
|
SingleVariableDeclaration, TagVariableDeclaration, VariableDeclaration,
|
||||||
},
|
},
|
||||||
|
Annotation,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
error::{AssignmentError, IllegalAnnotationContent, MismatchedTypes},
|
error::{
|
||||||
|
AssignmentError, IllegalAnnotationContent, IllegalIndexing, IllegalIndexingReason,
|
||||||
|
MismatchedTypes,
|
||||||
|
},
|
||||||
expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
|
expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
|
||||||
|
internal_functions::InternalFunction,
|
||||||
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult,
|
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -90,6 +95,11 @@ pub enum VariableData {
|
||||||
/// The paths to the booleans.
|
/// The paths to the booleans.
|
||||||
paths: Vec<String>,
|
paths: Vec<String>,
|
||||||
},
|
},
|
||||||
|
/// Compiler internal function.
|
||||||
|
InternalFunction {
|
||||||
|
/// The implementation
|
||||||
|
implementation: InternalFunction,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, EnumAsInner)]
|
#[derive(Debug, Clone, Copy, EnumAsInner)]
|
||||||
|
@ -129,6 +139,19 @@ impl<'a> Scope<'a> {
|
||||||
Arc::new(Self::default())
|
Arc::new(Self::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new scope with internal functions.
|
||||||
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_internal_functions() -> Arc<Self> {
|
||||||
|
use super::internal_functions;
|
||||||
|
|
||||||
|
let scope = Self::new();
|
||||||
|
|
||||||
|
internal_functions::add_all_to_scope(&scope);
|
||||||
|
|
||||||
|
scope
|
||||||
|
}
|
||||||
|
|
||||||
/// 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: &'a Arc<Self>) -> Arc<Self> {
|
||||||
|
@ -244,7 +267,18 @@ impl Transpiler {
|
||||||
scope,
|
scope,
|
||||||
handler,
|
handler,
|
||||||
),
|
),
|
||||||
_ => todo!("declarations other than single not supported yet: {declaration:?}"),
|
VariableDeclaration::Array(declaration) => self.transpile_array_variable_declaration(
|
||||||
|
declaration,
|
||||||
|
program_identifier,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
),
|
||||||
|
VariableDeclaration::Tag(declaration) => self.transpile_tag_variable_declaration(
|
||||||
|
declaration,
|
||||||
|
program_identifier,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,23 +345,14 @@ impl Transpiler {
|
||||||
scope: &Arc<Scope>,
|
scope: &Arc<Scope>,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> TranspileResult<Vec<Command>> {
|
) -> TranspileResult<Vec<Command>> {
|
||||||
let mut deobfuscate_annotations = declaration
|
let name = self.get_data_location_identifier(
|
||||||
.annotations()
|
declaration.int_keyword().keyword,
|
||||||
.iter()
|
declaration.identifier(),
|
||||||
.filter(|a| a.has_identifier("deobfuscate"));
|
declaration.annotations(),
|
||||||
|
program_identifier,
|
||||||
let deobfuscate_annotation = deobfuscate_annotations.next();
|
scope,
|
||||||
|
handler,
|
||||||
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);
|
|
||||||
}
|
|
||||||
let name =
|
|
||||||
self.get_data_location_identifier(declaration, program_identifier, scope, handler)?;
|
|
||||||
|
|
||||||
let criteria = declaration
|
let criteria = declaration
|
||||||
.criteria()
|
.criteria()
|
||||||
|
@ -350,6 +375,84 @@ impl Transpiler {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transpile_array_variable_declaration(
|
||||||
|
&mut self,
|
||||||
|
declaration: &ArrayVariableDeclaration,
|
||||||
|
program_identifier: &str,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<Vec<Command>> {
|
||||||
|
let variable_type = declaration.variable_type().keyword;
|
||||||
|
|
||||||
|
let (name, targets) =
|
||||||
|
self.get_array_data_location_pair(declaration, program_identifier, scope, handler)?;
|
||||||
|
|
||||||
|
match variable_type {
|
||||||
|
KeywordKind::Int => {
|
||||||
|
if !self.datapack.scoreboards().contains_key(&name) {
|
||||||
|
self.datapack
|
||||||
|
.register_scoreboard(&name, None::<&str>, None::<&str>);
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.set_variable(
|
||||||
|
declaration.identifier().span.str(),
|
||||||
|
VariableData::ScoreboardArray {
|
||||||
|
objective: name,
|
||||||
|
targets,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
KeywordKind::Bool => {
|
||||||
|
scope.set_variable(
|
||||||
|
declaration.identifier().span.str(),
|
||||||
|
VariableData::BooleanStorageArray {
|
||||||
|
storage_name: name,
|
||||||
|
paths: targets,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!("no other variable types"),
|
||||||
|
}
|
||||||
|
|
||||||
|
declaration.assignment().as_ref().map_or_else(
|
||||||
|
|| Ok(Vec::new()),
|
||||||
|
|assignment| {
|
||||||
|
self.transpile_assignment(
|
||||||
|
TranspileAssignmentTarget::Identifier(declaration.identifier()),
|
||||||
|
assignment.expression(),
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transpile_tag_variable_declaration(
|
||||||
|
&mut self,
|
||||||
|
declaration: &TagVariableDeclaration,
|
||||||
|
program_identifier: &str,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<Vec<Command>> {
|
||||||
|
let name = self.get_data_location_identifier(
|
||||||
|
declaration.bool_keyword().keyword,
|
||||||
|
declaration.identifier(),
|
||||||
|
declaration.annotations(),
|
||||||
|
program_identifier,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
scope.set_variable(
|
||||||
|
declaration.identifier().span.str(),
|
||||||
|
VariableData::Tag { tag_name: name },
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: implement assignment when map literal is implemented
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
pub(super) fn transpile_assignment(
|
pub(super) fn transpile_assignment(
|
||||||
&mut self,
|
&mut self,
|
||||||
destination: TranspileAssignmentTarget,
|
destination: TranspileAssignmentTarget,
|
||||||
|
@ -388,27 +491,176 @@ impl Transpiler {
|
||||||
Some(ComptimeValue::MacroString(s)) => {
|
Some(ComptimeValue::MacroString(s)) => {
|
||||||
todo!("indexing scoreboard with macro string: {s}")
|
todo!("indexing scoreboard with macro string: {s}")
|
||||||
}
|
}
|
||||||
Some(_) => todo!("invalid indexing value"),
|
Some(_) => {
|
||||||
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
expression: expression.span(),
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::String,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
todo!("cannot assign to scoreboard without indexing")
|
// TODO: allow when map literals are implemented
|
||||||
|
let err = TranspileError::AssignmentError(AssignmentError {
|
||||||
|
identifier: identifier.span(),
|
||||||
|
message: "Cannot assign to a scoreboard without indexing".to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
VariableData::Function { .. } | VariableData::MacroParameter { .. } => {
|
VariableData::ScoreboardArray { objective, targets } => {
|
||||||
|
match indexing_value {
|
||||||
|
Some(ComptimeValue::Integer(index)) => {
|
||||||
|
if let Some(target) = usize::try_from(index)
|
||||||
|
.ok()
|
||||||
|
.and_then(|index| targets.get(index))
|
||||||
|
{
|
||||||
|
Ok(DataLocation::ScoreboardValue {
|
||||||
|
objective: objective.to_string(),
|
||||||
|
target: target.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let index_span = match destination {
|
||||||
|
TranspileAssignmentTarget::Indexed(_, expr) => expr.span(),
|
||||||
|
TranspileAssignmentTarget::Identifier(_) => unreachable!(
|
||||||
|
"indexing value must be present (checked before)"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
expression: index_span,
|
||||||
|
reason: IllegalIndexingReason::IndexOutOfBounds {
|
||||||
|
index: usize::try_from(index).unwrap_or(usize::MAX),
|
||||||
|
length: targets.len(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
expression: expression.span(),
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::Integer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// TODO: implement when array literals are implemented
|
||||||
|
let err = TranspileError::AssignmentError(AssignmentError {
|
||||||
|
identifier: identifier.span(),
|
||||||
|
message: "Cannot assign to an array without indexing".to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VariableData::BooleanStorageArray {
|
||||||
|
storage_name,
|
||||||
|
paths,
|
||||||
|
} => {
|
||||||
|
match indexing_value {
|
||||||
|
Some(ComptimeValue::Integer(index)) => {
|
||||||
|
if let Some(path) = usize::try_from(index)
|
||||||
|
.ok()
|
||||||
|
.and_then(|index| paths.get(index))
|
||||||
|
{
|
||||||
|
Ok(DataLocation::Storage {
|
||||||
|
storage_name: storage_name.to_string(),
|
||||||
|
path: path.to_string(),
|
||||||
|
r#type: StorageType::Boolean,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let index_span = match destination {
|
||||||
|
TranspileAssignmentTarget::Indexed(_, expr) => expr.span(),
|
||||||
|
TranspileAssignmentTarget::Identifier(_) => unreachable!(
|
||||||
|
"indexing value must be present (checked before)"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
expression: index_span,
|
||||||
|
reason: IllegalIndexingReason::IndexOutOfBounds {
|
||||||
|
index: usize::try_from(index).unwrap_or(usize::MAX),
|
||||||
|
length: paths.len(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
expression: expression.span(),
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::Integer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// TODO: implement when array literals are implemented
|
||||||
|
let err = TranspileError::AssignmentError(AssignmentError {
|
||||||
|
identifier: identifier.span(),
|
||||||
|
message: "Cannot assign to an array without indexing".to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VariableData::Tag { tag_name } => match indexing_value {
|
||||||
|
Some(ComptimeValue::String(s)) => Ok(DataLocation::Tag {
|
||||||
|
tag_name: tag_name.to_string(),
|
||||||
|
entity: s,
|
||||||
|
}),
|
||||||
|
Some(ComptimeValue::MacroString(s)) => {
|
||||||
|
todo!("indexing tag with macro string: {s}")
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
let err = TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
expression: expression.span(),
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::String,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// TODO: allow when map literals are implemented
|
||||||
|
let err = TranspileError::AssignmentError(AssignmentError {
|
||||||
|
identifier: identifier.span(),
|
||||||
|
message: "Cannot assign to a tag without indexing".to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
VariableData::Function { .. }
|
||||||
|
| VariableData::MacroParameter { .. }
|
||||||
|
| VariableData::InternalFunction { .. } => {
|
||||||
let err = TranspileError::AssignmentError(AssignmentError {
|
let err = TranspileError::AssignmentError(AssignmentError {
|
||||||
identifier: identifier.span(),
|
identifier: identifier.span(),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Cannot assign to a {}.",
|
"Cannot assign to a {}.",
|
||||||
if matches!(target.as_ref(), VariableData::Function { .. }) {
|
match target.as_ref() {
|
||||||
"function"
|
VariableData::Function { .. } => "function",
|
||||||
} else {
|
VariableData::MacroParameter { .. } => "macro parameter",
|
||||||
"function argument"
|
VariableData::InternalFunction { .. } => "internal function",
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
handler.receive(err.clone());
|
handler.receive(err.clone());
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
_ => todo!("implement other variable types"),
|
|
||||||
}?;
|
}?;
|
||||||
self.transpile_expression(expression, &data_location, scope, handler)
|
self.transpile_expression(expression, &data_location, scope, handler)
|
||||||
} else {
|
} else {
|
||||||
|
@ -423,13 +675,14 @@ impl Transpiler {
|
||||||
|
|
||||||
fn get_data_location_identifier(
|
fn get_data_location_identifier(
|
||||||
&mut self,
|
&mut self,
|
||||||
declaration: &ScoreVariableDeclaration,
|
variable_type: KeywordKind,
|
||||||
|
identifier: &Identifier,
|
||||||
|
annotations: &VecDeque<Annotation>,
|
||||||
program_identifier: &str,
|
program_identifier: &str,
|
||||||
scope: &Arc<Scope>,
|
scope: &Arc<Scope>,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> TranspileResult<String> {
|
) -> TranspileResult<String> {
|
||||||
let mut deobfuscate_annotations = declaration
|
let mut deobfuscate_annotations = annotations
|
||||||
.annotations()
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|a| a.has_identifier("deobfuscate"));
|
.filter(|a| a.has_identifier("deobfuscate"));
|
||||||
|
|
||||||
|
@ -453,6 +706,7 @@ impl Transpiler {
|
||||||
.comptime_eval(scope, handler)
|
.comptime_eval(scope, handler)
|
||||||
.and_then(|val| val.to_string_no_macro())
|
.and_then(|val| val.to_string_no_macro())
|
||||||
{
|
{
|
||||||
|
// TODO: change invalid criteria if boolean
|
||||||
if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
|
if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
|
||||||
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
annotation: deobfuscate_annotation.span(),
|
annotation: deobfuscate_annotation.span(),
|
||||||
|
@ -471,9 +725,7 @@ impl Transpiler {
|
||||||
Err(error)
|
Err(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TranspileAnnotationValue::None => {
|
TranspileAnnotationValue::None => Ok(identifier.span.str().to_string()),
|
||||||
Ok(declaration.identifier().span.str().to_string())
|
|
||||||
}
|
|
||||||
TranspileAnnotationValue::Map(_) => {
|
TranspileAnnotationValue::Map(_) => {
|
||||||
let error =
|
let error =
|
||||||
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
@ -488,7 +740,7 @@ impl Transpiler {
|
||||||
} else {
|
} else {
|
||||||
let hashed = md5::hash(program_identifier).to_hex_lowercase();
|
let hashed = md5::hash(program_identifier).to_hex_lowercase();
|
||||||
let name = "shu_values_".to_string() + &hashed;
|
let name = "shu_values_".to_string() + &hashed;
|
||||||
let identifier_name = declaration.identifier().span.str();
|
let identifier_name = identifier.span.str();
|
||||||
let scope_ident = self.temp_counter;
|
let scope_ident = self.temp_counter;
|
||||||
self.temp_counter = self.temp_counter.wrapping_add(1);
|
self.temp_counter = self.temp_counter.wrapping_add(1);
|
||||||
let mut target = md5::hash(format!(
|
let mut target = md5::hash(format!(
|
||||||
|
@ -634,6 +886,97 @@ impl Transpiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_array_data_location_pair(
|
||||||
|
&mut self,
|
||||||
|
declaration: &ArrayVariableDeclaration,
|
||||||
|
program_identifier: &str,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<(String, Vec<String>)> {
|
||||||
|
let mut deobfuscate_annotations = declaration
|
||||||
|
.annotations()
|
||||||
|
.iter()
|
||||||
|
.filter(|a| a.has_identifier("deobfuscate"));
|
||||||
|
|
||||||
|
let variable_type = declaration.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());
|
||||||
|
|
||||||
|
match deobfuscate_annotation_value {
|
||||||
|
TranspileAnnotationValue::None => {
|
||||||
|
let ident_str = declaration.identifier().span.str();
|
||||||
|
let name = if matches!(variable_type, KeywordKind::Int) {
|
||||||
|
ident_str.to_string()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{namespace}:{ident_str}",
|
||||||
|
namespace = self.main_namespace_name
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let len = declaration.size().as_i64();
|
||||||
|
let targets = (0..len).map(|i| i.to_string()).collect();
|
||||||
|
Ok((name, targets))
|
||||||
|
}
|
||||||
|
TranspileAnnotationValue::Map(map) => {
|
||||||
|
todo!("allow map deobfuscate annotation for array variables")
|
||||||
|
}
|
||||||
|
TranspileAnnotationValue::Expression(_) => {
|
||||||
|
let error =
|
||||||
|
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: deobfuscate_annotation.span(),
|
||||||
|
message: "Deobfuscate annotation value must be a map or none."
|
||||||
|
.to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(error.clone());
|
||||||
|
Err(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let hashed = md5::hash(program_identifier).to_hex_lowercase();
|
||||||
|
let name = if matches!(variable_type, KeywordKind::Int) {
|
||||||
|
"shu_values_"
|
||||||
|
} else {
|
||||||
|
"shulkerbox:values_"
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
+ &hashed;
|
||||||
|
let identifier_name = declaration.identifier().span.str();
|
||||||
|
let scope_ident = self.temp_counter;
|
||||||
|
self.temp_counter = self.temp_counter.wrapping_add(1);
|
||||||
|
let len = declaration.size().as_i64();
|
||||||
|
let targets = (0..len)
|
||||||
|
.map(|i| {
|
||||||
|
let mut target = md5::hash(format!(
|
||||||
|
"{scope_ident}\0{identifier_name}\0{i}\0{shadowed}",
|
||||||
|
shadowed = scope.get_variable_shadow_count(identifier_name)
|
||||||
|
))
|
||||||
|
.to_hex_lowercase();
|
||||||
|
|
||||||
|
if matches!(variable_type, KeywordKind::Int) {
|
||||||
|
target.split_off(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
target
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok((name, targets))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Move data from location `from` to location `to`.
|
/// Move data from location `from` to location `to`.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
|
Loading…
Reference in New Issue