rework annotations & transpile single int declarations without assignment
This commit is contained in:
parent
9279e52c00
commit
68da1f4e12
14
Cargo.toml
14
Cargo.toml
|
@ -38,7 +38,7 @@ path-absolutize = "3.1.1"
|
||||||
pathdiff = "0.2.3"
|
pathdiff = "0.2.3"
|
||||||
serde = { version = "1.0.217", features = ["derive"], optional = true }
|
serde = { version = "1.0.217", features = ["derive"], optional = true }
|
||||||
# shulkerbox = { version = "0.1.0", default-features = false, optional = true }
|
# shulkerbox = { version = "0.1.0", default-features = false, optional = true }
|
||||||
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "76d58c0766518fe5ab2635de60ba40972565a3e0", default-features = false, optional = true }
|
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "811d71508208f8415d881f7c4d73429f1a73f36a", default-features = false, optional = true }
|
||||||
strsim = "0.11.1"
|
strsim = "0.11.1"
|
||||||
strum = { version = "0.27.0", features = ["derive"] }
|
strum = { version = "0.27.0", features = ["derive"] }
|
||||||
thiserror = "2.0.11"
|
thiserror = "2.0.11"
|
||||||
|
@ -46,3 +46,15 @@ tracing = "0.1.41"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1.0.138"
|
serde_json = "1.0.138"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "compiler"
|
||||||
|
required-features = ["fs_access", "shulkerbox"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "parsing"
|
||||||
|
required-features = ["shulkerbox"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "transpiling"
|
||||||
|
required-features = ["shulkerbox"]
|
||||||
|
|
|
@ -8,19 +8,19 @@ use shulkerscript::{
|
||||||
compile,
|
compile,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "shulkerbox"))]
|
|
||||||
compile_error!("Need feature 'shulkerbox' to compile this example");
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut args = std::env::args();
|
let mut args = std::env::args();
|
||||||
let _ = args.next().unwrap();
|
let _ = args.next().unwrap();
|
||||||
let input = args.next().expect("Expect path to shulkerscript file");
|
let input = args.next().expect("Expect path to shulkerscript file");
|
||||||
|
|
||||||
|
let main_namespace = args.next().expect("Expect main namespace name");
|
||||||
|
|
||||||
let output = args.next().expect("Expect path to output directory");
|
let output = args.next().expect("Expect path to output directory");
|
||||||
|
|
||||||
let code = compile(
|
let code = compile(
|
||||||
&PrintHandler::new(),
|
&PrintHandler::new(),
|
||||||
&FsProvider::default(),
|
&FsProvider::default(),
|
||||||
|
main_namespace,
|
||||||
shulkerbox::datapack::Datapack::LATEST_FORMAT,
|
shulkerbox::datapack::Datapack::LATEST_FORMAT,
|
||||||
&[("main".to_string(), &input)],
|
&[("main".to_string(), &input)],
|
||||||
)
|
)
|
||||||
|
|
|
@ -353,7 +353,6 @@ pub struct MacroStringLiteral {
|
||||||
|
|
||||||
impl MacroStringLiteral {
|
impl MacroStringLiteral {
|
||||||
/// Returns the string content without escapement characters, leading and trailing double quotes.
|
/// Returns the string content without escapement characters, leading and trailing double quotes.
|
||||||
#[cfg(feature = "shulkerbox")]
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn str_content(&self) -> String {
|
pub fn str_content(&self) -> String {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
@ -369,7 +368,7 @@ impl MacroStringLiteral {
|
||||||
write!(
|
write!(
|
||||||
content,
|
content,
|
||||||
"$({})",
|
"$({})",
|
||||||
crate::transpile::util::identifier_to_macro(identifier.span.str())
|
crate::util::identifier_to_macro(identifier.span.str())
|
||||||
)
|
)
|
||||||
.expect("can always write to string");
|
.expect("can always write to string");
|
||||||
}
|
}
|
||||||
|
@ -949,7 +948,7 @@ impl Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(all(test, feature = "shulkerbox"))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::base::source_file::SourceFile;
|
use crate::base::source_file::SourceFile;
|
||||||
use shulkerbox::virtual_fs::{VFile, VFolder};
|
use shulkerbox::virtual_fs::{VFile, VFolder};
|
||||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -130,6 +130,7 @@ pub fn parse(
|
||||||
/// let datapack = transpile(
|
/// let datapack = transpile(
|
||||||
/// &PrintHandler::new(),
|
/// &PrintHandler::new(),
|
||||||
/// &FsProvider::default(),
|
/// &FsProvider::default(),
|
||||||
|
/// "main",
|
||||||
/// 48,
|
/// 48,
|
||||||
/// &[
|
/// &[
|
||||||
/// (String::from("fileA"), Path::new("path/to/fileA.shu")),
|
/// (String::from("fileA"), Path::new("path/to/fileA.shu")),
|
||||||
|
@ -141,6 +142,7 @@ pub fn parse(
|
||||||
pub fn transpile<F, P>(
|
pub fn transpile<F, P>(
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
file_provider: &F,
|
file_provider: &F,
|
||||||
|
main_namespace_name: impl Into<String>,
|
||||||
pack_format: u8,
|
pack_format: u8,
|
||||||
script_paths: &[(String, P)],
|
script_paths: &[(String, P)],
|
||||||
) -> Result<Datapack>
|
) -> Result<Datapack>
|
||||||
|
@ -174,7 +176,7 @@ where
|
||||||
|
|
||||||
tracing::info!("Transpiling the source code.");
|
tracing::info!("Transpiling the source code.");
|
||||||
|
|
||||||
let mut transpiler = Transpiler::new(pack_format);
|
let mut transpiler = Transpiler::new(main_namespace_name, pack_format);
|
||||||
transpiler.transpile(&programs, handler)?;
|
transpiler.transpile(&programs, handler)?;
|
||||||
let datapack = transpiler.into_datapack();
|
let datapack = transpiler.into_datapack();
|
||||||
|
|
||||||
|
@ -203,6 +205,7 @@ where
|
||||||
/// let vfolder = compile(
|
/// let vfolder = compile(
|
||||||
/// &PrintHandler::new(),
|
/// &PrintHandler::new(),
|
||||||
/// &FsProvider::default(),
|
/// &FsProvider::default(),
|
||||||
|
/// "main",
|
||||||
/// 48,
|
/// 48,
|
||||||
/// &[
|
/// &[
|
||||||
/// (String::from("fileA"), Path::new("path/to/fileA.shu")),
|
/// (String::from("fileA"), Path::new("path/to/fileA.shu")),
|
||||||
|
@ -214,6 +217,7 @@ where
|
||||||
pub fn compile<F, P>(
|
pub fn compile<F, P>(
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
file_provider: &F,
|
file_provider: &F,
|
||||||
|
main_namespace_name: impl Into<String>,
|
||||||
pack_format: u8,
|
pack_format: u8,
|
||||||
script_paths: &[(String, P)],
|
script_paths: &[(String, P)],
|
||||||
) -> Result<VFolder>
|
) -> Result<VFolder>
|
||||||
|
@ -223,7 +227,13 @@ where
|
||||||
{
|
{
|
||||||
use shulkerbox::prelude::CompileOptions;
|
use shulkerbox::prelude::CompileOptions;
|
||||||
|
|
||||||
let datapack = transpile(handler, file_provider, pack_format, script_paths)?;
|
let datapack = transpile(
|
||||||
|
handler,
|
||||||
|
file_provider,
|
||||||
|
main_namespace_name,
|
||||||
|
pack_format,
|
||||||
|
script_paths,
|
||||||
|
)?;
|
||||||
|
|
||||||
tracing::info!("Compiling the source code.");
|
tracing::info!("Compiling the source code.");
|
||||||
|
|
||||||
|
|
|
@ -156,11 +156,11 @@ impl Function {
|
||||||
if let Some(incompatible) = self
|
if let Some(incompatible) = self
|
||||||
.annotations()
|
.annotations()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|a| ["tick", "load"].contains(&a.identifier().span.str()))
|
.find(|a| ["tick", "load"].contains(&a.assignment().identifier.span.str()))
|
||||||
{
|
{
|
||||||
let err =
|
let err =
|
||||||
error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
|
error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
|
||||||
span: incompatible.identifier().span(),
|
span: incompatible.assignment().identifier.span(),
|
||||||
reason:
|
reason:
|
||||||
"functions with the `tick` or `load` annotation cannot have parameters"
|
"functions with the `tick` or `load` annotation cannot have parameters"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
|
|
@ -102,7 +102,7 @@ where
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| (k, Arc::new(v)))
|
.map(|(k, v)| (k, Arc::new(v)))
|
||||||
.collect(),
|
.collect(),
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
let data = seq
|
let data = seq
|
||||||
.next_element()?
|
.next_element()?
|
||||||
|
@ -131,7 +131,7 @@ where
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(&k, v)| (k, Arc::new(v.clone())))
|
.map(|(&k, v)| (k, Arc::new(v.clone())))
|
||||||
.collect(),
|
.collect(),
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
data = Some(map.next_value()?);
|
data = Some(map.next_value()?);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ pub enum Error {
|
||||||
UnexpectedSyntax(#[from] UnexpectedSyntax),
|
UnexpectedSyntax(#[from] UnexpectedSyntax),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidArgument(#[from] InvalidArgument),
|
InvalidArgument(#[from] InvalidArgument),
|
||||||
|
#[error(transparent)]
|
||||||
|
InvalidAnnotation(#[from] InvalidAnnotation),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumeration containing all kinds of syntax that can be failed to parse.
|
/// Enumeration containing all kinds of syntax that can be failed to parse.
|
||||||
|
@ -154,3 +156,36 @@ impl Display for InvalidArgument {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for InvalidArgument {}
|
impl std::error::Error for InvalidArgument {}
|
||||||
|
|
||||||
|
/// An error that occurred due to an invalid annotation.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct InvalidAnnotation {
|
||||||
|
/// The invalid annotation identifier.
|
||||||
|
pub annotation: Span,
|
||||||
|
/// The target of the annotation.
|
||||||
|
pub target: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for InvalidAnnotation {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
Message::new(
|
||||||
|
Severity::Error,
|
||||||
|
format!(
|
||||||
|
"Annotation '{}' cannot be applied to {}",
|
||||||
|
self.annotation.str(),
|
||||||
|
self.target
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.annotation, Option::<u8>::None)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for InvalidAnnotation {}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -15,12 +17,12 @@ use crate::{
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::{
|
syntax::{
|
||||||
error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
|
error::{Error, InvalidAnnotation, ParseResult, SyntaxKind, UnexpectedSyntax},
|
||||||
parser::{Parser, Reading},
|
parser::{Parser, Reading},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{statement::Block, ConnectedList, DelimitedList};
|
use super::{statement::Block, Annotation, ConnectedList, DelimitedList};
|
||||||
|
|
||||||
/// Represents a declaration in the syntax tree.
|
/// Represents a declaration in the syntax tree.
|
||||||
///
|
///
|
||||||
|
@ -49,57 +51,29 @@ impl SourceElement for Declaration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Represents an Annotation with optional value.
|
|
||||||
///
|
|
||||||
/// Syntax Synopsis:
|
|
||||||
///
|
|
||||||
/// ``` ebnf
|
|
||||||
/// Annotation:
|
|
||||||
/// '#[' Identifier ('=' StringLiteral)? ']'
|
|
||||||
/// ;
|
|
||||||
/// ```
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
|
||||||
pub struct Annotation {
|
|
||||||
#[get = "pub"]
|
|
||||||
pound_sign: Punctuation,
|
|
||||||
#[get = "pub"]
|
|
||||||
open_bracket: Punctuation,
|
|
||||||
#[get = "pub"]
|
|
||||||
identifier: Identifier,
|
|
||||||
#[get = "pub"]
|
|
||||||
value: Option<(Punctuation, StringLiteral)>,
|
|
||||||
#[get = "pub"]
|
|
||||||
close_bracket: Punctuation,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Annotation {
|
impl Declaration {
|
||||||
/// Dissolves the [`Annotation`] into its components.
|
/// Adds an annotation to the declaration.
|
||||||
#[must_use]
|
///
|
||||||
pub fn dissolve(
|
/// # Errors
|
||||||
self,
|
/// - if the annotation is invalid for the target declaration.
|
||||||
) -> (
|
pub fn with_annotation(self, annotation: Annotation) -> ParseResult<Self> {
|
||||||
Punctuation,
|
#[expect(clippy::single_match_else)]
|
||||||
Punctuation,
|
match self {
|
||||||
Identifier,
|
Self::Function(mut function) => {
|
||||||
Option<(Punctuation, StringLiteral)>,
|
function.annotations.push_front(annotation);
|
||||||
Punctuation,
|
|
||||||
) {
|
Ok(Self::Function(function))
|
||||||
(
|
}
|
||||||
self.pound_sign,
|
_ => {
|
||||||
self.open_bracket,
|
let err = Error::InvalidAnnotation(InvalidAnnotation {
|
||||||
self.identifier,
|
annotation: annotation.assignment.identifier.span,
|
||||||
self.value,
|
target: "declarations except functions".to_string(),
|
||||||
self.close_bracket,
|
});
|
||||||
)
|
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl SourceElement for Annotation {
|
|
||||||
fn span(&self) -> Span {
|
|
||||||
self.pound_sign
|
|
||||||
.span
|
|
||||||
.join(&self.close_bracket.span())
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +96,7 @@ pub struct Function {
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
public_keyword: Option<Keyword>,
|
public_keyword: Option<Keyword>,
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
annotations: Vec<Annotation>,
|
annotations: VecDeque<Annotation>,
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
function_keyword: Keyword,
|
function_keyword: Keyword,
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
|
@ -145,7 +119,7 @@ impl Function {
|
||||||
self,
|
self,
|
||||||
) -> (
|
) -> (
|
||||||
Option<Keyword>,
|
Option<Keyword>,
|
||||||
Vec<Annotation>,
|
VecDeque<Annotation>,
|
||||||
Keyword,
|
Keyword,
|
||||||
Identifier,
|
Identifier,
|
||||||
Punctuation,
|
Punctuation,
|
||||||
|
@ -313,67 +287,6 @@ impl SourceElement for Tag {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
/// Parses an annotation.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// - if the parser position is not at an annotation.
|
|
||||||
/// - if the parsing of the annotation fails
|
|
||||||
pub fn parse_annotation(
|
|
||||||
&mut self,
|
|
||||||
handler: &impl Handler<base::Error>,
|
|
||||||
) -> ParseResult<Annotation> {
|
|
||||||
match self.stop_at_significant() {
|
|
||||||
Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => {
|
|
||||||
// eat the pound sign
|
|
||||||
self.forward();
|
|
||||||
|
|
||||||
// step into the brackets
|
|
||||||
let content = self.step_into(
|
|
||||||
Delimiter::Bracket,
|
|
||||||
|parser| {
|
|
||||||
let identifier = parser.parse_identifier(handler)?;
|
|
||||||
|
|
||||||
let value = match parser.stop_at_significant() {
|
|
||||||
Reading::Atomic(Token::Punctuation(punc))
|
|
||||||
if punc.punctuation == '=' =>
|
|
||||||
{
|
|
||||||
// eat the equals sign
|
|
||||||
parser.forward();
|
|
||||||
|
|
||||||
// parse the string literal
|
|
||||||
let string_literal = parser.parse_string_literal(handler)?;
|
|
||||||
|
|
||||||
Some((punc, string_literal))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((identifier, value))
|
|
||||||
},
|
|
||||||
handler,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let (identifier, value) = content.tree?;
|
|
||||||
|
|
||||||
Ok(Annotation {
|
|
||||||
pound_sign: punctuation,
|
|
||||||
open_bracket: content.open,
|
|
||||||
identifier,
|
|
||||||
value,
|
|
||||||
close_bracket: content.close,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
unexpected => {
|
|
||||||
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
|
||||||
expected: SyntaxKind::Punctuation('#'),
|
|
||||||
found: unexpected.into_token(),
|
|
||||||
});
|
|
||||||
handler.receive(err.clone());
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[tracing::instrument(level = "trace", skip_all)]
|
||||||
pub fn parse_declaration(
|
pub fn parse_declaration(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -403,18 +316,12 @@ impl<'a> Parser<'a> {
|
||||||
// parse annotations
|
// parse annotations
|
||||||
Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => {
|
Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => {
|
||||||
// parse the annotation
|
// parse the annotation
|
||||||
let mut annotations = Vec::new();
|
let annotation = self.parse_annotation(handler)?;
|
||||||
|
let declaration = self.parse_declaration(handler)?;
|
||||||
|
|
||||||
while let Ok(annotation) =
|
declaration
|
||||||
self.try_parse(|parser| parser.parse_annotation(&VoidHandler))
|
.with_annotation(annotation)
|
||||||
{
|
.inspect_err(|err| handler.receive(err.clone()))
|
||||||
annotations.push(annotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.parse_function(handler).map(|mut function| {
|
|
||||||
function.annotations.extend(annotations);
|
|
||||||
Declaration::Function(function)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Reading::Atomic(Token::Keyword(from_keyword))
|
Reading::Atomic(Token::Keyword(from_keyword))
|
||||||
|
@ -553,7 +460,7 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
Ok(Function {
|
Ok(Function {
|
||||||
public_keyword: pub_keyword.ok(),
|
public_keyword: pub_keyword.ok(),
|
||||||
annotations: Vec::new(),
|
annotations: VecDeque::new(),
|
||||||
function_keyword,
|
function_keyword,
|
||||||
identifier,
|
identifier,
|
||||||
open_paren: delimited_tree.open,
|
open_paren: delimited_tree.open,
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
base::{
|
base::{
|
||||||
self,
|
self,
|
||||||
source_file::{SourceElement, Span},
|
source_file::{SourceElement, Span},
|
||||||
Handler,
|
Handler, VoidHandler,
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{
|
token::{
|
||||||
|
@ -48,6 +48,24 @@ impl SourceElement for Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Expression {
|
||||||
|
/// Checks if the expression is compile-time.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_comptime(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Primary(primary) => primary.is_comptime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate at compile-time to a string.
|
||||||
|
#[must_use]
|
||||||
|
pub fn comptime_eval(&self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Self::Primary(primary) => primary.comptime_eval(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a primary expression in the syntax tree.
|
/// Represents a primary expression in the syntax tree.
|
||||||
///
|
///
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
|
@ -86,6 +104,38 @@ impl SourceElement for Primary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Primary {
|
||||||
|
/// Checks if the primary expression is compile-time.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_comptime(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Boolean(_)
|
||||||
|
| Self::Integer(_)
|
||||||
|
| Self::StringLiteral(_)
|
||||||
|
| Self::MacroStringLiteral(_)
|
||||||
|
| Self::Lua(_) => true,
|
||||||
|
Self::FunctionCall(func) => func.is_comptime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate at compile-time to a string.
|
||||||
|
#[must_use]
|
||||||
|
pub fn comptime_eval(&self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Self::Boolean(boolean) => Some(boolean.span.str().to_string()),
|
||||||
|
Self::Integer(int) => Some(int.span.str().to_string()),
|
||||||
|
Self::StringLiteral(string_literal) => Some(string_literal.str_content().to_string()),
|
||||||
|
// TODO: correctly evaluate lua code
|
||||||
|
Self::Lua(lua) => lua.eval_string(&VoidHandler).ok().flatten(),
|
||||||
|
Self::MacroStringLiteral(macro_string_literal) => {
|
||||||
|
Some(macro_string_literal.str_content())
|
||||||
|
}
|
||||||
|
// TODO: correctly evaluate function calls
|
||||||
|
Self::FunctionCall(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a function call in the syntax tree.
|
/// Represents a function call in the syntax tree.
|
||||||
///
|
///
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
|
@ -121,6 +171,16 @@ impl SourceElement for FunctionCall {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FunctionCall {
|
||||||
|
/// Checks if the function call is compile-time.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_comptime(&self) -> bool {
|
||||||
|
self.arguments
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |args| args.elements().all(|elem| elem.is_comptime()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a lua code block in the syntax tree.
|
/// Represents a lua code block in the syntax tree.
|
||||||
///
|
///
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
//! Contains the syntax tree nodes that represent the structure of the source code.
|
//! Contains the syntax tree nodes that represent the structure of the source code.
|
||||||
|
|
||||||
use derive_more::derive::From;
|
use derive_more::derive::From;
|
||||||
|
use expression::Expression;
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
|
use strum::EnumIs;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{
|
base::{
|
||||||
|
@ -10,13 +12,16 @@ use crate::{
|
||||||
Handler, VoidHandler,
|
Handler, VoidHandler,
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{MacroStringLiteral, Punctuation, StringLiteral, Token},
|
token::{Identifier, MacroStringLiteral, Punctuation, StringLiteral, Token},
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::parser::Reading,
|
syntax::parser::Reading,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{error::ParseResult, parser::Parser};
|
use super::{
|
||||||
|
error::{ParseResult, SyntaxKind, UnexpectedSyntax},
|
||||||
|
parser::Parser,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod condition;
|
pub mod condition;
|
||||||
pub mod declaration;
|
pub mod declaration;
|
||||||
|
@ -88,6 +93,142 @@ impl SourceElement for AnyStringLiteral {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents an Annotation with optional value.
|
||||||
|
///
|
||||||
|
/// Syntax Synopsis:
|
||||||
|
///
|
||||||
|
/// ``` ebnf
|
||||||
|
/// Annotation:
|
||||||
|
/// '#[' AnnotationAssignment ']'
|
||||||
|
/// ;
|
||||||
|
/// ```
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
||||||
|
pub struct Annotation {
|
||||||
|
/// The pound sign of the annotation.
|
||||||
|
#[get = "pub"]
|
||||||
|
pound_sign: Punctuation,
|
||||||
|
/// The open bracket of the annotation.
|
||||||
|
#[get = "pub"]
|
||||||
|
open_bracket: Punctuation,
|
||||||
|
/// The assignment inside the annotation.
|
||||||
|
#[get = "pub"]
|
||||||
|
assignment: AnnotationAssignment,
|
||||||
|
/// The close bracket of the annotation.
|
||||||
|
#[get = "pub"]
|
||||||
|
close_bracket: Punctuation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Annotation {
|
||||||
|
/// Dissolves the [`Annotation`] into its components.
|
||||||
|
#[must_use]
|
||||||
|
pub fn dissolve(self) -> (Punctuation, Punctuation, AnnotationAssignment, Punctuation) {
|
||||||
|
(
|
||||||
|
self.pound_sign,
|
||||||
|
self.open_bracket,
|
||||||
|
self.assignment,
|
||||||
|
self.close_bracket,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the annotation has the given identifier.
|
||||||
|
#[must_use]
|
||||||
|
pub fn has_identifier(&self, identifier: &str) -> bool {
|
||||||
|
self.assignment.identifier.span().str() == identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SourceElement for Annotation {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.pound_sign
|
||||||
|
.span
|
||||||
|
.join(&self.close_bracket.span())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a value of an annotation.
|
||||||
|
///
|
||||||
|
/// Syntax Synopsis:
|
||||||
|
///
|
||||||
|
/// ``` ebnf
|
||||||
|
/// AnnotationValue:
|
||||||
|
/// '=' Expression
|
||||||
|
/// | '(' AnnotationAssignment ( ',' AnnotationAssignment )* ')'
|
||||||
|
/// ;
|
||||||
|
/// ```
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumIs)]
|
||||||
|
pub enum AnnotationValue {
|
||||||
|
/// A single value assignment.
|
||||||
|
///
|
||||||
|
/// '=' Expression
|
||||||
|
Single {
|
||||||
|
/// The equal sign of the assignment.
|
||||||
|
equal_sign: Punctuation,
|
||||||
|
/// The value of the assignment.
|
||||||
|
value: Expression,
|
||||||
|
},
|
||||||
|
/// A multiple value assignment.
|
||||||
|
///
|
||||||
|
/// '(' [`AnnotationAssignment`] ( ',' [`AnnotationAssignment`] )* ')'
|
||||||
|
Multiple {
|
||||||
|
/// The opening parenthesis of the assignment.
|
||||||
|
opening_parenthesis: Punctuation,
|
||||||
|
/// The list of assignments.
|
||||||
|
list: Box<ConnectedList<AnnotationAssignment, Punctuation>>,
|
||||||
|
/// The closing parenthesis of the assignment.
|
||||||
|
closing_parenthesis: Punctuation,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceElement for AnnotationValue {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Self::Single { equal_sign, value } => equal_sign.span().join(&value.span()).unwrap(),
|
||||||
|
Self::Multiple {
|
||||||
|
opening_parenthesis,
|
||||||
|
closing_parenthesis,
|
||||||
|
..
|
||||||
|
} => opening_parenthesis
|
||||||
|
.span()
|
||||||
|
.join(&closing_parenthesis.span())
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an assignment inside an annotation.
|
||||||
|
///
|
||||||
|
/// Syntax Synopsis:
|
||||||
|
///
|
||||||
|
/// ``` ebnf
|
||||||
|
/// AnnotationAssignment:
|
||||||
|
/// Identifier AnnotationValue
|
||||||
|
/// ;
|
||||||
|
/// ```
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
||||||
|
pub struct AnnotationAssignment {
|
||||||
|
/// The identifier of the assignment.
|
||||||
|
pub identifier: Identifier,
|
||||||
|
/// The value of the assignment.
|
||||||
|
pub value: Option<AnnotationValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceElement for AnnotationAssignment {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.identifier
|
||||||
|
.span()
|
||||||
|
.join(
|
||||||
|
&self
|
||||||
|
.value
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| self.identifier.span(), AnnotationValue::span),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
/// Parses a list of elements enclosed by a pair of delimiters, separated by a separator.
|
/// Parses a list of elements enclosed by a pair of delimiters, separated by a separator.
|
||||||
///
|
///
|
||||||
|
@ -214,6 +355,95 @@ impl<'a> Parser<'a> {
|
||||||
trailing_separator: None,
|
trailing_separator: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses an annotation.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - if the parser position is not at an annotation.
|
||||||
|
/// - if the parsing of the annotation fails
|
||||||
|
pub fn parse_annotation(
|
||||||
|
&mut self,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> ParseResult<Annotation> {
|
||||||
|
match self.stop_at_significant() {
|
||||||
|
Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => {
|
||||||
|
// eat the pound sign
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
// step into the brackets
|
||||||
|
let content = self.step_into(
|
||||||
|
Delimiter::Bracket,
|
||||||
|
|parser| parser.parse_annotation_assignment(handler),
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Annotation {
|
||||||
|
pound_sign: punctuation,
|
||||||
|
open_bracket: content.open,
|
||||||
|
assignment: content.tree?,
|
||||||
|
close_bracket: content.close,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
unexpected => {
|
||||||
|
let err = super::error::Error::UnexpectedSyntax(UnexpectedSyntax {
|
||||||
|
expected: SyntaxKind::Punctuation('#'),
|
||||||
|
found: unexpected.into_token(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_annotation_assignment(
|
||||||
|
&mut self,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> ParseResult<AnnotationAssignment> {
|
||||||
|
let identifier = self.parse_identifier(handler)?;
|
||||||
|
|
||||||
|
match self.stop_at_significant() {
|
||||||
|
Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '=' => {
|
||||||
|
// eat the equals sign
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
let value = self.parse_expression(handler)?;
|
||||||
|
|
||||||
|
Ok(AnnotationAssignment {
|
||||||
|
identifier,
|
||||||
|
value: Some(AnnotationValue::Single {
|
||||||
|
equal_sign: punc,
|
||||||
|
value,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Reading::IntoDelimited(delim) if delim.punctuation == '(' => {
|
||||||
|
let tree = self.step_into(
|
||||||
|
Delimiter::Parenthesis,
|
||||||
|
|p| {
|
||||||
|
p.parse_connected_list(
|
||||||
|
',',
|
||||||
|
|pp| pp.parse_annotation_assignment(handler),
|
||||||
|
handler,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(AnnotationAssignment {
|
||||||
|
identifier,
|
||||||
|
value: Some(AnnotationValue::Multiple {
|
||||||
|
opening_parenthesis: tree.open,
|
||||||
|
list: Box::new(tree.tree?),
|
||||||
|
closing_parenthesis: tree.close,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => Ok(AnnotationAssignment {
|
||||||
|
identifier,
|
||||||
|
value: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Element: SourceElement, Separator: SourceElement> SourceElement
|
impl<Element: SourceElement, Separator: SourceElement> SourceElement
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
pub mod execute_block;
|
pub mod execute_block;
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use derive_more::From;
|
use derive_more::From;
|
||||||
use enum_as_inner::EnumAsInner;
|
use enum_as_inner::EnumAsInner;
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
|
@ -20,14 +22,14 @@ use crate::{
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::{
|
syntax::{
|
||||||
error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
|
error::{Error, InvalidAnnotation, ParseResult, SyntaxKind, UnexpectedSyntax},
|
||||||
parser::{Parser, Reading},
|
parser::{Parser, Reading},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::execute_block::ExecuteBlock;
|
use self::execute_block::ExecuteBlock;
|
||||||
|
|
||||||
use super::{expression::Expression, AnyStringLiteral};
|
use super::{expression::Expression, Annotation, AnyStringLiteral};
|
||||||
|
|
||||||
/// Represents a statement in the syntax tree.
|
/// Represents a statement in the syntax tree.
|
||||||
///
|
///
|
||||||
|
@ -71,6 +73,47 @@ impl SourceElement for Statement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Statement {
|
||||||
|
/// Adds an annotation to the statement.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - if the annotation is invalid for the statement.
|
||||||
|
pub fn with_annotation(self, annotation: Annotation) -> ParseResult<Self> {
|
||||||
|
#[expect(clippy::single_match_else)]
|
||||||
|
match self {
|
||||||
|
Self::Semicolon(Semicolon {
|
||||||
|
statement,
|
||||||
|
semicolon,
|
||||||
|
}) => match statement {
|
||||||
|
SemicolonStatement::VariableDeclaration(decl) => {
|
||||||
|
decl.with_annotation(annotation).map(|decl| {
|
||||||
|
Self::Semicolon(Semicolon {
|
||||||
|
statement: SemicolonStatement::VariableDeclaration(decl),
|
||||||
|
semicolon,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
SemicolonStatement::Expression(_) => {
|
||||||
|
let err = Error::InvalidAnnotation(InvalidAnnotation {
|
||||||
|
annotation: annotation.assignment.identifier.span,
|
||||||
|
target: "expressions".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let err = Error::InvalidAnnotation(InvalidAnnotation {
|
||||||
|
annotation: annotation.assignment.identifier.span,
|
||||||
|
target: "statements except variable declarations".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a block in the syntax tree.
|
/// Represents a block in the syntax tree.
|
||||||
///
|
///
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
|
@ -306,6 +349,31 @@ impl VariableDeclaration {
|
||||||
Self::Tag(declaration) => &declaration.bool_keyword,
|
Self::Tag(declaration) => &declaration.bool_keyword,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds an annotation to the variable declaration.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - if the annotation is invalid for the variable declaration.
|
||||||
|
pub fn with_annotation(self, annotation: Annotation) -> ParseResult<Self> {
|
||||||
|
match self {
|
||||||
|
Self::Single(mut declaration) => {
|
||||||
|
declaration.annotations.push_front(annotation);
|
||||||
|
Ok(Self::Single(declaration))
|
||||||
|
}
|
||||||
|
Self::Array(mut declaration) => {
|
||||||
|
declaration.annotations.push_front(annotation);
|
||||||
|
Ok(Self::Array(declaration))
|
||||||
|
}
|
||||||
|
Self::Score(mut declaration) => {
|
||||||
|
declaration.annotations.push_front(annotation);
|
||||||
|
Ok(Self::Score(declaration))
|
||||||
|
}
|
||||||
|
Self::Tag(mut declaration) => {
|
||||||
|
declaration.annotations.push_front(annotation);
|
||||||
|
Ok(Self::Tag(declaration))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a variable assignment.
|
/// Represents a variable assignment.
|
||||||
|
@ -364,6 +432,9 @@ pub struct SingleVariableDeclaration {
|
||||||
/// The optional assignment of the variable.
|
/// The optional assignment of the variable.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
assignment: Option<VariableDeclarationAssignment>,
|
assignment: Option<VariableDeclarationAssignment>,
|
||||||
|
/// The annotations of the variable declaration.
|
||||||
|
#[get = "pub"]
|
||||||
|
annotations: VecDeque<Annotation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for SingleVariableDeclaration {
|
impl SourceElement for SingleVariableDeclaration {
|
||||||
|
@ -417,6 +488,9 @@ pub struct ArrayVariableDeclaration {
|
||||||
/// The optional assignment of the variable.
|
/// The optional assignment of the variable.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
assignment: Option<VariableDeclarationAssignment>,
|
assignment: Option<VariableDeclarationAssignment>,
|
||||||
|
/// The annotations of the variable declaration.
|
||||||
|
#[get = "pub"]
|
||||||
|
annotations: VecDeque<Annotation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for ArrayVariableDeclaration {
|
impl SourceElement for ArrayVariableDeclaration {
|
||||||
|
@ -488,6 +562,9 @@ pub struct ScoreVariableDeclaration {
|
||||||
/// The optional assignment of the variable.
|
/// The optional assignment of the variable.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
target_assignment: Option<(AnyStringLiteral, VariableDeclarationAssignment)>,
|
target_assignment: Option<(AnyStringLiteral, VariableDeclarationAssignment)>,
|
||||||
|
/// The annotations of the variable declaration.
|
||||||
|
#[get = "pub"]
|
||||||
|
annotations: VecDeque<Annotation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for ScoreVariableDeclaration {
|
impl SourceElement for ScoreVariableDeclaration {
|
||||||
|
@ -553,6 +630,9 @@ pub struct TagVariableDeclaration {
|
||||||
/// The optional assignment of the variable.
|
/// The optional assignment of the variable.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
target_assignment: Option<(AnyStringLiteral, VariableDeclarationAssignment)>,
|
target_assignment: Option<(AnyStringLiteral, VariableDeclarationAssignment)>,
|
||||||
|
/// The annotations of the variable declaration.
|
||||||
|
#[get = "pub"]
|
||||||
|
annotations: VecDeque<Annotation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for TagVariableDeclaration {
|
impl SourceElement for TagVariableDeclaration {
|
||||||
|
@ -638,6 +718,15 @@ impl<'a> Parser<'a> {
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> ParseResult<Statement> {
|
) -> ParseResult<Statement> {
|
||||||
match self.stop_at_significant() {
|
match self.stop_at_significant() {
|
||||||
|
// annotations
|
||||||
|
Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '#' => {
|
||||||
|
let annotation = self.parse_annotation(handler)?;
|
||||||
|
let statement = self.parse_statement(handler)?;
|
||||||
|
|
||||||
|
statement
|
||||||
|
.with_annotation(annotation)
|
||||||
|
.inspect_err(|err| handler.receive(err.clone()))
|
||||||
|
}
|
||||||
// variable declaration
|
// variable declaration
|
||||||
Reading::Atomic(Token::CommandLiteral(command)) => {
|
Reading::Atomic(Token::CommandLiteral(command)) => {
|
||||||
self.forward();
|
self.forward();
|
||||||
|
@ -849,6 +938,7 @@ impl<'a> Parser<'a> {
|
||||||
size,
|
size,
|
||||||
close_bracket,
|
close_bracket,
|
||||||
assignment,
|
assignment,
|
||||||
|
annotations: VecDeque::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
IndexingType::AnyString(selector) => {
|
IndexingType::AnyString(selector) => {
|
||||||
|
@ -866,6 +956,7 @@ impl<'a> Parser<'a> {
|
||||||
open_bracket,
|
open_bracket,
|
||||||
close_bracket,
|
close_bracket,
|
||||||
target_assignment: Some((selector, assignment)),
|
target_assignment: Some((selector, assignment)),
|
||||||
|
annotations: VecDeque::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
KeywordKind::Bool => {
|
KeywordKind::Bool => {
|
||||||
|
@ -875,6 +966,7 @@ impl<'a> Parser<'a> {
|
||||||
open_bracket,
|
open_bracket,
|
||||||
close_bracket,
|
close_bracket,
|
||||||
target_assignment: Some((selector, assignment)),
|
target_assignment: Some((selector, assignment)),
|
||||||
|
annotations: VecDeque::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -889,6 +981,7 @@ impl<'a> Parser<'a> {
|
||||||
open_bracket,
|
open_bracket,
|
||||||
close_bracket,
|
close_bracket,
|
||||||
target_assignment: None,
|
target_assignment: None,
|
||||||
|
annotations: VecDeque::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
KeywordKind::Bool => Ok(VariableDeclaration::Tag(TagVariableDeclaration {
|
KeywordKind::Bool => Ok(VariableDeclaration::Tag(TagVariableDeclaration {
|
||||||
|
@ -897,6 +990,7 @@ impl<'a> Parser<'a> {
|
||||||
open_bracket,
|
open_bracket,
|
||||||
close_bracket,
|
close_bracket,
|
||||||
target_assignment: None,
|
target_assignment: None,
|
||||||
|
annotations: VecDeque::new(),
|
||||||
})),
|
})),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
|
@ -913,6 +1007,7 @@ impl<'a> Parser<'a> {
|
||||||
variable_type,
|
variable_type,
|
||||||
identifier,
|
identifier,
|
||||||
assignment: Some(assignment),
|
assignment: Some(assignment),
|
||||||
|
annotations: VecDeque::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
// SingleVariableDeclaration without Assignment
|
// SingleVariableDeclaration without Assignment
|
||||||
|
@ -920,6 +1015,7 @@ impl<'a> Parser<'a> {
|
||||||
variable_type,
|
variable_type,
|
||||||
identifier,
|
identifier,
|
||||||
assignment: None,
|
assignment: None,
|
||||||
|
annotations: VecDeque::new(),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ impl From<&MacroStringLiteral> for MacroString {
|
||||||
),
|
),
|
||||||
MacroStringLiteralPart::MacroUsage { identifier, .. } => {
|
MacroStringLiteralPart::MacroUsage { identifier, .. } => {
|
||||||
MacroStringPart::MacroUsage(
|
MacroStringPart::MacroUsage(
|
||||||
super::util::identifier_to_macro(identifier.span.str()).to_string(),
|
crate::util::identifier_to_macro(identifier.span.str()).to_string(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
//! Errors that can occur during transpilation.
|
//! Errors that can occur during transpilation.
|
||||||
|
|
||||||
use std::{fmt::Display, sync::Arc};
|
use std::fmt::Display;
|
||||||
|
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{
|
base::{
|
||||||
|
@ -13,10 +12,7 @@ use crate::{
|
||||||
semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
|
semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::FunctionData;
|
||||||
variables::{Scope, VariableType},
|
|
||||||
FunctionData,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Errors that can occur during transpilation.
|
/// Errors that can occur during transpilation.
|
||||||
#[allow(clippy::module_name_repetitions, missing_docs)]
|
#[allow(clippy::module_name_repetitions, missing_docs)]
|
||||||
|
@ -34,6 +30,8 @@ pub enum TranspileError {
|
||||||
ConflictingFunctionNames(#[from] ConflictingFunctionNames),
|
ConflictingFunctionNames(#[from] ConflictingFunctionNames),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidFunctionArguments(#[from] InvalidFunctionArguments),
|
InvalidFunctionArguments(#[from] InvalidFunctionArguments),
|
||||||
|
#[error(transparent)]
|
||||||
|
IllegalAnnotationContent(#[from] IllegalAnnotationContent),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a transpilation operation.
|
/// The result of a transpilation operation.
|
||||||
|
@ -49,8 +47,13 @@ pub struct MissingFunctionDeclaration {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MissingFunctionDeclaration {
|
impl MissingFunctionDeclaration {
|
||||||
#[cfg_attr(not(feature = "shulkerbox"), expect(unused))]
|
#[cfg(feature = "shulkerbox")]
|
||||||
pub(super) fn from_scope(identifier_span: Span, scope: &Arc<Scope>) -> Self {
|
pub(super) fn from_scope(
|
||||||
|
identifier_span: Span,
|
||||||
|
scope: &std::sync::Arc<super::variables::Scope>,
|
||||||
|
) -> Self {
|
||||||
|
use itertools::Itertools as _;
|
||||||
|
|
||||||
let own_name = identifier_span.str();
|
let own_name = identifier_span.str();
|
||||||
let alternatives = scope
|
let alternatives = scope
|
||||||
.get_variables()
|
.get_variables()
|
||||||
|
@ -58,9 +61,12 @@ impl MissingFunctionDeclaration {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(name, value)| {
|
.filter_map(|(name, value)| {
|
||||||
let data = match value.as_ref() {
|
let super::variables::VariableType::Function {
|
||||||
VariableType::Function { function_data, .. } => function_data,
|
function_data: data,
|
||||||
_ => return None,
|
..
|
||||||
|
} = value.as_ref()
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let normalized_distance = strsim::normalized_damerau_levenshtein(own_name, name);
|
let normalized_distance = strsim::normalized_damerau_levenshtein(own_name, name);
|
||||||
|
@ -150,3 +156,29 @@ impl LuaRuntimeError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error that occurs when an annotation has an illegal content.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct IllegalAnnotationContent {
|
||||||
|
pub annotation: Span,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for IllegalAnnotationContent {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let message = format!(
|
||||||
|
"illegal content in annotation `{}`: {}",
|
||||||
|
self.annotation.str(),
|
||||||
|
self.message
|
||||||
|
);
|
||||||
|
write!(f, "{}", Message::new(Severity::Error, message))?;
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.annotation, Option::<u8>::None)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for IllegalAnnotationContent {}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
//! The transpile module is responsible for transpiling the abstract syntax tree into a data pack.
|
//! The transpile module is responsible for transpiling the abstract syntax tree into a data pack.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
use crate::{base::source_file::Span, syntax::syntax_tree::statement::Statement};
|
use crate::{
|
||||||
|
base::source_file::Span,
|
||||||
|
syntax::syntax_tree::{expression::Expression, statement::Statement, AnnotationValue},
|
||||||
|
};
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[cfg(feature = "shulkerbox")]
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
@ -16,6 +19,7 @@ pub use error::{TranspileError, TranspileResult};
|
||||||
pub mod lua;
|
pub mod lua;
|
||||||
#[cfg(feature = "shulkerbox")]
|
#[cfg(feature = "shulkerbox")]
|
||||||
mod transpiler;
|
mod transpiler;
|
||||||
|
use strum::EnumIs;
|
||||||
#[cfg(feature = "shulkerbox")]
|
#[cfg(feature = "shulkerbox")]
|
||||||
#[cfg_attr(feature = "shulkerbox", doc(inline))]
|
#[cfg_attr(feature = "shulkerbox", doc(inline))]
|
||||||
pub use transpiler::Transpiler;
|
pub use transpiler::Transpiler;
|
||||||
|
@ -31,5 +35,37 @@ pub(super) struct FunctionData {
|
||||||
pub(super) parameters: Vec<String>,
|
pub(super) parameters: Vec<String>,
|
||||||
pub(super) statements: Vec<Statement>,
|
pub(super) statements: Vec<Statement>,
|
||||||
pub(super) public: bool,
|
pub(super) public: bool,
|
||||||
pub(super) annotations: HashMap<String, Option<String>>,
|
pub(super) annotations: HashMap<String, TranspileAnnotationValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Possible values for an annotation.
|
||||||
|
#[expect(clippy::module_name_repetitions)]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, EnumIs)]
|
||||||
|
pub enum TranspileAnnotationValue {
|
||||||
|
/// No value.
|
||||||
|
None,
|
||||||
|
/// A single expression.
|
||||||
|
Expression(Expression),
|
||||||
|
/// A map of key-value pairs.
|
||||||
|
Map(BTreeMap<String, TranspileAnnotationValue>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Option<AnnotationValue>> for TranspileAnnotationValue {
|
||||||
|
fn from(value: Option<AnnotationValue>) -> Self {
|
||||||
|
match value {
|
||||||
|
None => Self::None,
|
||||||
|
Some(AnnotationValue::Single { value, .. }) => Self::Expression(value),
|
||||||
|
Some(AnnotationValue::Multiple { list, .. }) => {
|
||||||
|
let map = list
|
||||||
|
.into_elements()
|
||||||
|
.map(|elem| {
|
||||||
|
let key = elem.identifier.span.str().to_string();
|
||||||
|
let value = Self::from(elem.value);
|
||||||
|
(key, value)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Self::Map(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ use crate::{
|
||||||
source_file::{SourceElement, Span},
|
source_file::{SourceElement, Span},
|
||||||
Handler,
|
Handler,
|
||||||
},
|
},
|
||||||
|
lexical::token::KeywordKind,
|
||||||
semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
|
semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
|
||||||
syntax::syntax_tree::{
|
syntax::syntax_tree::{
|
||||||
declaration::{Declaration, ImportItems},
|
declaration::{Declaration, ImportItems},
|
||||||
|
@ -22,16 +23,17 @@ use crate::{
|
||||||
program::{Namespace, ProgramFile},
|
program::{Namespace, ProgramFile},
|
||||||
statement::{
|
statement::{
|
||||||
execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail},
|
execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail},
|
||||||
SemicolonStatement, Statement,
|
SemicolonStatement, SingleVariableDeclaration, Statement, VariableDeclaration,
|
||||||
},
|
},
|
||||||
|
AnnotationAssignment,
|
||||||
},
|
},
|
||||||
transpile::error::MissingFunctionDeclaration,
|
transpile::error::{IllegalAnnotationContent, MissingFunctionDeclaration},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
error::{TranspileError, TranspileResult},
|
error::{TranspileError, TranspileResult},
|
||||||
variables::{Scope, VariableType},
|
variables::{Scope, VariableType},
|
||||||
FunctionData,
|
FunctionData, TranspileAnnotationValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A transpiler for `Shulkerscript`.
|
/// A transpiler for `Shulkerscript`.
|
||||||
|
@ -49,9 +51,9 @@ pub struct Transpiler {
|
||||||
impl Transpiler {
|
impl Transpiler {
|
||||||
/// Creates a new transpiler.
|
/// Creates a new transpiler.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(pack_format: u8) -> Self {
|
pub fn new(main_namespace_name: impl Into<String>, pack_format: u8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
datapack: shulkerbox::datapack::Datapack::new(pack_format),
|
datapack: shulkerbox::datapack::Datapack::new(main_namespace_name, pack_format),
|
||||||
scopes: BTreeMap::new(),
|
scopes: BTreeMap::new(),
|
||||||
functions: BTreeMap::new(),
|
functions: BTreeMap::new(),
|
||||||
aliases: HashMap::new(),
|
aliases: HashMap::new(),
|
||||||
|
@ -85,7 +87,7 @@ impl Transpiler {
|
||||||
let scope = self
|
let scope = self
|
||||||
.scopes
|
.scopes
|
||||||
.entry(program_identifier)
|
.entry(program_identifier)
|
||||||
.or_insert_with(Scope::new)
|
.or_default()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
self.transpile_program_declarations(program, &scope, handler);
|
self.transpile_program_declarations(program, &scope, handler);
|
||||||
}
|
}
|
||||||
|
@ -113,7 +115,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_insert_with(Scope::new)
|
.or_default()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
self.get_or_transpile_function(&identifier_span, None, &scope, handler)?;
|
self.get_or_transpile_function(&identifier_span, None, &scope, handler)?;
|
||||||
}
|
}
|
||||||
|
@ -154,11 +156,13 @@ impl Transpiler {
|
||||||
.annotations()
|
.annotations()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|annotation| {
|
.map(|annotation| {
|
||||||
let key = annotation.identifier();
|
let AnnotationAssignment {
|
||||||
let value = annotation.value();
|
identifier: key,
|
||||||
|
value,
|
||||||
|
} = annotation.assignment();
|
||||||
(
|
(
|
||||||
key.span().str().to_string(),
|
key.span().str().to_string(),
|
||||||
value.as_ref().map(|(_, ref v)| v.str_content().to_string()),
|
TranspileAnnotationValue::from(value.clone()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -278,12 +282,12 @@ impl Transpiler {
|
||||||
error
|
error
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (function_data, function_path) = match function_data.as_ref() {
|
let VariableType::Function {
|
||||||
VariableType::Function {
|
|
||||||
function_data,
|
function_data,
|
||||||
path,
|
path: function_path,
|
||||||
} => (function_data, path),
|
} = function_data.as_ref()
|
||||||
_ => todo!("correctly throw error on wrong type"),
|
else {
|
||||||
|
unreachable!("must be of correct type, otherwise errored out before");
|
||||||
};
|
};
|
||||||
|
|
||||||
if !already_transpiled {
|
if !already_transpiled {
|
||||||
|
@ -300,18 +304,37 @@ impl Transpiler {
|
||||||
let commands =
|
let commands =
|
||||||
self.transpile_function(&statements, program_identifier, &function_scope, handler)?;
|
self.transpile_function(&statements, program_identifier, &function_scope, handler)?;
|
||||||
|
|
||||||
let modified_name = function_data
|
let modified_name = function_data.annotations.get("deobfuscate").map_or_else(
|
||||||
.annotations
|
|
||||||
.get("deobfuscate")
|
|
||||||
.map_or_else(
|
|
||||||
|| {
|
|| {
|
||||||
let hash_data =
|
let hash_data = program_identifier.to_string() + "\0" + identifier_span.str();
|
||||||
program_identifier.to_string() + "\0" + identifier_span.str();
|
Ok("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase())
|
||||||
Some("shu/".to_string() + &md5::hash(hash_data).to_hex_lowercase())
|
|
||||||
},
|
},
|
||||||
Clone::clone,
|
|val| match val {
|
||||||
)
|
TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()),
|
||||||
.unwrap_or_else(|| identifier_span.str().to_string());
|
TranspileAnnotationValue::Expression(expr) => {
|
||||||
|
expr.comptime_eval().ok_or_else(|| {
|
||||||
|
let err = TranspileError::IllegalAnnotationContent(
|
||||||
|
IllegalAnnotationContent {
|
||||||
|
annotation: identifier_span.clone(),
|
||||||
|
message: "Cannot evaluate annotation at compile time"
|
||||||
|
.to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
handler.receive(err.clone());
|
||||||
|
err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
TranspileAnnotationValue::Map(_) => {
|
||||||
|
let err =
|
||||||
|
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: identifier_span.clone(),
|
||||||
|
message: "Deobfuscate annotation cannot be a map.".to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
let namespace = self.datapack.namespace_mut(&function_data.namespace);
|
let namespace = self.datapack.namespace_mut(&function_data.namespace);
|
||||||
|
|
||||||
|
@ -339,16 +362,7 @@ impl Transpiler {
|
||||||
self.datapack.add_load(&function_location);
|
self.datapack.add_load(&function_location);
|
||||||
}
|
}
|
||||||
|
|
||||||
match scope
|
function_path.set(function_location.clone()).unwrap();
|
||||||
.get_variable(identifier_span.str())
|
|
||||||
.expect("variable should be in scope if called")
|
|
||||||
.as_ref()
|
|
||||||
{
|
|
||||||
VariableType::Function { path, .. } => {
|
|
||||||
path.set(function_location.clone()).unwrap();
|
|
||||||
}
|
|
||||||
_ => todo!("implement error handling")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let parameters = function_data.parameters.clone();
|
let parameters = function_data.parameters.clone();
|
||||||
|
@ -362,7 +376,7 @@ impl Transpiler {
|
||||||
handler.receive(error.clone());
|
handler.receive(error.clone());
|
||||||
error
|
error
|
||||||
})
|
})
|
||||||
.map(|s| s.to_owned())?;
|
.map(String::to_owned)?;
|
||||||
|
|
||||||
let arg_count = arguments.map(<[&Expression]>::len);
|
let arg_count = arguments.map(<[&Expression]>::len);
|
||||||
if arg_count.is_some_and(|arg_count| arg_count != parameters.len()) {
|
if arg_count.is_some_and(|arg_count| arg_count != parameters.len()) {
|
||||||
|
@ -536,23 +550,7 @@ impl Transpiler {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SemicolonStatement::VariableDeclaration(decl) => {
|
SemicolonStatement::VariableDeclaration(decl) => {
|
||||||
// let value = match decl {
|
self.transpile_variable_declaration(decl, program_identifier, scope, handler)
|
||||||
// VariableDeclaration::Single(single) => {
|
|
||||||
// match single.variable_type().keyword {
|
|
||||||
// KeywordKind::Int => {
|
|
||||||
// VariableType::ScoreboardValue { objective: (), name: () }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// TODO: only for demonstration
|
|
||||||
// scope.set_variable(
|
|
||||||
// decl.identifier().span.str(),
|
|
||||||
// VariableType::Tag {
|
|
||||||
// tag_name: "TODO".to_string(),
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
todo!("Variable declarations are not yet supported: {decl:?}")
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -582,7 +580,7 @@ impl Transpiler {
|
||||||
.map(|(ident, v)| {
|
.map(|(ident, v)| {
|
||||||
format!(
|
format!(
|
||||||
r#"{macro_name}:"{escaped}""#,
|
r#"{macro_name}:"{escaped}""#,
|
||||||
macro_name = super::util::identifier_to_macro(ident),
|
macro_name = crate::util::identifier_to_macro(ident),
|
||||||
escaped = crate::util::escape_str(v)
|
escaped = crate::util::escape_str(v)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -593,6 +591,158 @@ impl Transpiler {
|
||||||
Ok(Command::Raw(function_call))
|
Ok(Command::Raw(function_call))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transpile_variable_declaration(
|
||||||
|
&mut self,
|
||||||
|
declaration: &VariableDeclaration,
|
||||||
|
program_identifier: &str,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<Option<Command>> {
|
||||||
|
match declaration {
|
||||||
|
VariableDeclaration::Single(single) => self.transpile_single_variable_declaration(
|
||||||
|
single,
|
||||||
|
program_identifier,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
),
|
||||||
|
_ => todo!("declarations not supported yet: {declaration:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
|
fn transpile_single_variable_declaration(
|
||||||
|
&mut self,
|
||||||
|
single: &SingleVariableDeclaration,
|
||||||
|
program_identifier: &str,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<Option<Command>> {
|
||||||
|
let mut deobfuscate_annotations = single
|
||||||
|
.annotations()
|
||||||
|
.iter()
|
||||||
|
.filter(|a| a.has_identifier("deobfuscate"));
|
||||||
|
|
||||||
|
let variable_type = single.variable_type().keyword;
|
||||||
|
|
||||||
|
let deobfuscate_annotation = deobfuscate_annotations.next();
|
||||||
|
|
||||||
|
if let Some(duplicate) = deobfuscate_annotations.next() {
|
||||||
|
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: duplicate.span(),
|
||||||
|
message: "Multiple deobfuscate annotations are not allowed.".to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(error.clone());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
let (name, target) = if let Some(deobfuscate_annotation) = deobfuscate_annotation {
|
||||||
|
let deobfuscate_annotation_value =
|
||||||
|
TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone());
|
||||||
|
|
||||||
|
if let TranspileAnnotationValue::Map(map) = deobfuscate_annotation_value {
|
||||||
|
if map.len() > 2 {
|
||||||
|
let error =
|
||||||
|
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: deobfuscate_annotation.span(),
|
||||||
|
message: "Deobfuscate annotation must have at most 2 key-value pairs."
|
||||||
|
.to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(error.clone());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
if let (Some(name), Some(target)) = (map.get("name"), map.get("target")) {
|
||||||
|
if let (
|
||||||
|
TranspileAnnotationValue::Expression(objective),
|
||||||
|
TranspileAnnotationValue::Expression(target),
|
||||||
|
) = (name, target)
|
||||||
|
{
|
||||||
|
if let (Some(name_eval), Some(target_eval)) =
|
||||||
|
(objective.comptime_eval(), target.comptime_eval())
|
||||||
|
{
|
||||||
|
// TODO: change invalid criteria if boolean
|
||||||
|
if !crate::util::is_valid_scoreboard_name(&name_eval) {
|
||||||
|
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: deobfuscate_annotation.span(),
|
||||||
|
message: "Deobfuscate annotation 'name' must be a valid scoreboard name.".to_string()
|
||||||
|
});
|
||||||
|
handler.receive(error.clone());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
if !crate::util::is_valid_player_name(&target_eval) {
|
||||||
|
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: deobfuscate_annotation.span(),
|
||||||
|
message: "Deobfuscate annotation 'target' must be a valid player name.".to_string()
|
||||||
|
});
|
||||||
|
handler.receive(error.clone());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
(name_eval, target_eval)
|
||||||
|
} else {
|
||||||
|
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: deobfuscate_annotation.span(),
|
||||||
|
message: "Deobfuscate annotation 'name' or 'target' could not have been evaluated at compile time.".to_string()
|
||||||
|
});
|
||||||
|
handler.receive(error.clone());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: deobfuscate_annotation.span(),
|
||||||
|
message: "Deobfuscate annotation 'name' and 'target' must be compile time expressions.".to_string()
|
||||||
|
});
|
||||||
|
handler.receive(error.clone());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let error =
|
||||||
|
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: deobfuscate_annotation.span(),
|
||||||
|
message:
|
||||||
|
"Deobfuscate annotation must have both 'name' and 'target' keys."
|
||||||
|
.to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(error.clone());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
|
||||||
|
annotation: deobfuscate_annotation.span(),
|
||||||
|
message: "Deobfuscate annotation must be a map.".to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(error.clone());
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let name =
|
||||||
|
"shu_values_".to_string() + &md5::hash(program_identifier).to_hex_lowercase();
|
||||||
|
let target = md5::hash((Arc::as_ptr(scope) as usize).to_le_bytes())
|
||||||
|
.to_hex_lowercase()
|
||||||
|
.split_off(16);
|
||||||
|
|
||||||
|
(name, target)
|
||||||
|
};
|
||||||
|
|
||||||
|
if variable_type == KeywordKind::Int {
|
||||||
|
if !self.datapack.scoreboards().contains_key(&name) {
|
||||||
|
self.datapack.register_scoreboard(&name, None, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.set_variable(
|
||||||
|
&name,
|
||||||
|
VariableType::ScoreboardValue {
|
||||||
|
objective: name.clone(),
|
||||||
|
target,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
todo!("implement other variable types")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(single
|
||||||
|
.assignment()
|
||||||
|
.is_some()
|
||||||
|
.then(|| todo!("transpile assignment")))
|
||||||
|
}
|
||||||
|
|
||||||
fn transpile_execute_block(
|
fn transpile_execute_block(
|
||||||
&mut self,
|
&mut self,
|
||||||
execute: &ExecuteBlock,
|
execute: &ExecuteBlock,
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
//! Utility methods for transpiling
|
//! Utility methods for transpiling
|
||||||
|
|
||||||
#[cfg(feature = "shulkerbox")]
|
|
||||||
use chksum_md5 as md5;
|
|
||||||
|
|
||||||
fn normalize_program_identifier<S>(identifier: S) -> String
|
fn normalize_program_identifier<S>(identifier: S) -> String
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
@ -39,25 +36,3 @@ where
|
||||||
normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref())
|
normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transforms an identifier to a macro name that only contains `a-zA-Z0-9_`.
|
|
||||||
#[cfg(feature = "shulkerbox")]
|
|
||||||
#[must_use]
|
|
||||||
pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<str> {
|
|
||||||
if ident.contains("__")
|
|
||||||
|| ident
|
|
||||||
.chars()
|
|
||||||
.any(|c| c == '_' || !c.is_ascii_alphanumeric())
|
|
||||||
{
|
|
||||||
let new_ident = ident
|
|
||||||
.chars()
|
|
||||||
.filter(|c| *c != '_' && c.is_ascii_alphanumeric())
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
let chksum = md5::hash(ident).to_hex_lowercase();
|
|
||||||
|
|
||||||
std::borrow::Cow::Owned(new_ident + "__" + &chksum[..8])
|
|
||||||
} else {
|
|
||||||
std::borrow::Cow::Borrowed(ident)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -23,11 +23,11 @@ pub enum VariableType {
|
||||||
},
|
},
|
||||||
ScoreboardValue {
|
ScoreboardValue {
|
||||||
objective: String,
|
objective: String,
|
||||||
name: String,
|
target: String,
|
||||||
},
|
},
|
||||||
ScoreboardArray {
|
ScoreboardArray {
|
||||||
objective: String,
|
objective: String,
|
||||||
names: Vec<String>,
|
targets: Vec<String>,
|
||||||
},
|
},
|
||||||
Tag {
|
Tag {
|
||||||
tag_name: String,
|
tag_name: String,
|
||||||
|
@ -83,7 +83,7 @@ impl<'a> Scope<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_parent(&self) -> Option<Arc<Self>> {
|
pub fn get_parent(&self) -> Option<Arc<Self>> {
|
||||||
self.parent.map(|v| v.clone())
|
self.parent.cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
56
src/util.rs
56
src/util.rs
|
@ -36,6 +36,62 @@ pub fn unescape_macro_string(s: &str) -> Cow<str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transforms an identifier to a macro name that only contains `a-zA-Z0-9_`.
|
||||||
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
#[must_use]
|
||||||
|
pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<str> {
|
||||||
|
if ident.contains("__")
|
||||||
|
|| ident
|
||||||
|
.chars()
|
||||||
|
.any(|c| c == '_' || !c.is_ascii_alphanumeric())
|
||||||
|
{
|
||||||
|
let new_ident = ident
|
||||||
|
.chars()
|
||||||
|
.filter(|c| *c != '_' && c.is_ascii_alphanumeric())
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let chksum = chksum_md5::hash(ident).to_hex_lowercase();
|
||||||
|
|
||||||
|
std::borrow::Cow::Owned(new_ident + "__" + &chksum[..8])
|
||||||
|
} else {
|
||||||
|
std::borrow::Cow::Borrowed(ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms an identifier to a macro name that only contains `a-zA-Z0-9_`.
|
||||||
|
/// Does only strip invalid characters if the `shulkerbox` feature is not enabled.
|
||||||
|
#[cfg(not(feature = "shulkerbox"))]
|
||||||
|
#[must_use]
|
||||||
|
pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<str> {
|
||||||
|
if ident.contains("__")
|
||||||
|
|| ident
|
||||||
|
.chars()
|
||||||
|
.any(|c| c == '_' || !c.is_ascii_alphanumeric())
|
||||||
|
{
|
||||||
|
let new_ident = ident
|
||||||
|
.chars()
|
||||||
|
.filter(|c| *c != '_' && c.is_ascii_alphanumeric())
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
std::borrow::Cow::Owned(new_ident)
|
||||||
|
} else {
|
||||||
|
std::borrow::Cow::Borrowed(ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether a string is a valid scoreboard name.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_valid_scoreboard_name(name: &str) -> bool {
|
||||||
|
name.chars()
|
||||||
|
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '+' | '.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether a string is a valid player name.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_valid_player_name(name: &str) -> bool {
|
||||||
|
(3..=16).contains(&name.len()) && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -13,13 +13,14 @@ fn transpile_test1() {
|
||||||
let transpiled = shulkerscript::transpile(
|
let transpiled = shulkerscript::transpile(
|
||||||
&PrintHandler::default(),
|
&PrintHandler::default(),
|
||||||
&dir,
|
&dir,
|
||||||
|
"main",
|
||||||
48,
|
48,
|
||||||
&[("test1".to_string(), "./test1.shu")],
|
&[("test1".to_string(), "./test1.shu")],
|
||||||
)
|
)
|
||||||
.expect("Failed to transpile");
|
.expect("Failed to transpile");
|
||||||
|
|
||||||
let expected = {
|
let expected = {
|
||||||
let mut dp = Datapack::new(48);
|
let mut dp = Datapack::new("main", 48);
|
||||||
|
|
||||||
let namespace = dp.namespace_mut("transpiling-test");
|
let namespace = dp.namespace_mut("transpiling-test");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue