Compare commits

...

5 Commits

Author SHA1 Message Date
Moritz Hölting a07f16f283 prepare transpiling variables 2025-02-25 22:52:39 +01:00
Moritz Hölting 38f90e6491 fix variable declaration parsing 2025-02-25 20:45:27 +01:00
Moritz Hölting 0d0df920ee implement variable declaration parsing 2025-02-25 20:45:27 +01:00
Moritz Hölting b8303689db make serialization of source file thread local 2025-02-19 09:32:47 +01:00
Moritz Hölting a6a5e42b6b
Change development branch name in github action workflow 2025-02-18 10:18:16 +01:00
12 changed files with 1124 additions and 153 deletions

View File

@ -3,7 +3,7 @@ on:
push: push:
branches: branches:
- main - main
- development - develop
- 'releases/**' - 'releases/**'
pull_request: pull_request:
@ -16,4 +16,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: cargo test --verbose - run: cargo test --verbose --all-features

View File

@ -1,6 +1,12 @@
//! Contains the [`Token`] struct and its related types. //! Contains the [`Token`] struct and its related types.
use std::{borrow::Cow, collections::HashMap, fmt::Display, str::FromStr, sync::OnceLock}; use std::{
borrow::Cow,
collections::HashMap,
fmt::{Debug, Display},
str::FromStr,
sync::OnceLock,
};
use crate::base::{ use crate::base::{
self, self,
@ -44,6 +50,8 @@ pub enum KeywordKind {
Tag, Tag,
Of, Of,
Replace, Replace,
Int,
Bool,
} }
impl Display for KeywordKind { impl Display for KeywordKind {
@ -107,6 +115,8 @@ impl KeywordKind {
Self::Tag => "tag", Self::Tag => "tag",
Self::Of => "of", Self::Of => "of",
Self::Replace => "replace", Self::Replace => "replace",
Self::Int => "int",
Self::Bool => "bool",
} }
} }
@ -141,7 +151,8 @@ pub enum Token {
Identifier(Identifier), Identifier(Identifier),
Keyword(Keyword), Keyword(Keyword),
Punctuation(Punctuation), Punctuation(Punctuation),
Numeric(Numeric), Integer(Integer),
Boolean(Boolean),
Comment(Comment), Comment(Comment),
DocComment(DocComment), DocComment(DocComment),
CommandLiteral(CommandLiteral), CommandLiteral(CommandLiteral),
@ -156,7 +167,8 @@ impl SourceElement for Token {
Self::Identifier(token) => token.span(), Self::Identifier(token) => token.span(),
Self::Keyword(token) => token.span(), Self::Keyword(token) => token.span(),
Self::Punctuation(token) => token.span(), Self::Punctuation(token) => token.span(),
Self::Numeric(token) => token.span(), Self::Integer(token) => token.span(),
Self::Boolean(token) => token.span(),
Self::Comment(token) => token.span(), Self::Comment(token) => token.span(),
Self::DocComment(token) => token.span(), Self::DocComment(token) => token.span(),
Self::CommandLiteral(token) => token.span(), Self::CommandLiteral(token) => token.span(),
@ -227,20 +239,72 @@ impl SourceElement for Punctuation {
} }
} }
/// Represents a hardcoded numeric literal value in the source code. /// Represents a hardcoded numeric integer literal value in the source code.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Numeric { pub struct Integer {
/// Is the span that makes up the token. /// Is the span that makes up the token.
pub span: Span, pub span: Span,
} }
impl SourceElement for Numeric { impl SourceElement for Integer {
fn span(&self) -> Span { fn span(&self) -> Span {
self.span.clone() self.span.clone()
} }
} }
impl Integer {
/// Returns the integer value of the token.
#[must_use]
pub fn as_i64(&self) -> i64 {
self.span.str().parse().unwrap()
}
}
impl Debug for Integer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("Integer");
s.field("value", &self.as_i64());
s.field("span", &self.span);
s.finish()
}
}
/// Represents a hardcoded boolean literal value in the source code.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Boolean {
/// Is the span that makes up the token.
pub span: Span,
}
impl SourceElement for Boolean {
fn span(&self) -> Span {
self.span.clone()
}
}
impl Boolean {
/// Returns the boolean value of the token.
#[must_use]
pub fn value(&self) -> bool {
match self.span.str() {
"true" => true,
"false" => false,
_ => unreachable!("Invalid boolean literal"),
}
}
}
impl Debug for Boolean {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("Boolean");
s.field("value", &self.value());
s.field("span", &self.span);
s.finish()
}
}
/// Represents a hardcoded string literal value in the source code. /// Represents a hardcoded string literal value in the source code.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -590,16 +654,13 @@ impl Token {
let word = span.str(); let word = span.str();
// Checks if the word is a keyword // Checks if the word is a keyword
KeywordKind::from_str(word).ok().map_or_else( if let Ok(kw) = KeywordKind::from_str(word) {
|| Identifier { span: span.clone() }.into(), Keyword { span, keyword: kw }.into()
|kw| { } else if bool::from_str(word).is_ok() {
Keyword { Boolean { span }.into()
span: span.clone(), } else {
keyword: kw, Identifier { span }.into()
} }
.into()
},
)
} }
/// Handles a sequence starting with a slash /// Handles a sequence starting with a slash
@ -684,11 +745,11 @@ impl Token {
} }
/// Handles a sequence of digits /// Handles a sequence of digits
fn handle_numeric_literal(iter: &mut SourceIterator, start: usize) -> Self { fn handle_integer_literal(iter: &mut SourceIterator, start: usize) -> Self {
// Tokenizes the whole number part // Tokenizes the whole number part
Self::walk_iter(iter, |character| character.is_ascii_digit()); Self::walk_iter(iter, |character| character.is_ascii_digit());
Numeric { Integer {
span: Self::create_span(start, iter), span: Self::create_span(start, iter),
} }
.into() .into()
@ -871,9 +932,9 @@ impl Token {
else if character == '`' { else if character == '`' {
Self::handle_macro_string_literal(iter, start) Self::handle_macro_string_literal(iter, start)
} }
// Found numeric literal // Found integer literal
else if character.is_ascii_digit() { else if character.is_ascii_digit() {
Ok(Self::handle_numeric_literal(iter, start)) Ok(Self::handle_integer_literal(iter, start))
} }
// Found a punctuation // Found a punctuation
else if character.is_ascii_punctuation() { else if character.is_ascii_punctuation() {

View File

@ -11,7 +11,7 @@ use error::{
use crate::{ use crate::{
base::{self, source_file::SourceElement as _, Handler}, base::{self, source_file::SourceElement as _, Handler},
lexical::token::{MacroStringLiteral, MacroStringLiteralPart}, lexical::token::{KeywordKind, MacroStringLiteral, MacroStringLiteralPart},
syntax::syntax_tree::{ syntax::syntax_tree::{
condition::{ condition::{
BinaryCondition, Condition, ParenthesizedCondition, PrimaryCondition, UnaryCondition, BinaryCondition, Condition, ParenthesizedCondition, PrimaryCondition, UnaryCondition,
@ -24,7 +24,7 @@ use crate::{
Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockHeadItem as _, Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockHeadItem as _,
ExecuteBlockTail, ExecuteBlockTail,
}, },
Block, Grouping, Run, Semicolon, Statement, Block, Grouping, Run, Semicolon, SemicolonStatement, Statement, VariableDeclaration,
}, },
AnyStringLiteral, AnyStringLiteral,
}, },
@ -291,16 +291,21 @@ impl Semicolon {
macro_names: &HashSet<String>, macro_names: &HashSet<String>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> Result<(), error::Error> { ) -> Result<(), error::Error> {
match self.expression() { match self.statement() {
Expression::Primary(Primary::FunctionCall(func)) => { SemicolonStatement::Expression(expr) => match expr {
func.analyze_semantics(function_names, macro_names, handler) Expression::Primary(Primary::FunctionCall(func)) => {
} func.analyze_semantics(function_names, macro_names, handler)
Expression::Primary(unexpected) => { }
let error = error::Error::UnexpectedExpression(UnexpectedExpression( Expression::Primary(unexpected) => {
Expression::Primary(unexpected.clone()), let error = error::Error::UnexpectedExpression(UnexpectedExpression(
)); Expression::Primary(unexpected.clone()),
handler.receive(error.clone()); ));
Err(error) handler.receive(error.clone());
Err(error)
}
},
SemicolonStatement::VariableDeclaration(decl) => {
decl.analyze_semantics(function_names, macro_names, handler)
} }
} }
} }
@ -456,7 +461,7 @@ impl Primary {
Self::FunctionCall(func) => { Self::FunctionCall(func) => {
func.analyze_semantics(function_names, macro_names, handler) func.analyze_semantics(function_names, macro_names, handler)
} }
Self::Lua(_) | Self::StringLiteral(_) => Ok(()), Self::Lua(_) | Self::StringLiteral(_) | Self::Integer(_) | Self::Boolean(_) => Ok(()),
Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler), Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler),
} }
} }
@ -514,6 +519,95 @@ impl AnyStringLiteral {
} }
} }
impl VariableDeclaration {
/// Analyzes the semantics of a variable declaration.
pub fn analyze_semantics(
&self,
function_names: &HashSet<String>,
macro_names: &HashSet<String>,
handler: &impl Handler<base::Error>,
) -> Result<(), error::Error> {
match self {
Self::Array(array) => array.assignment().as_ref().map_or(Ok(()), |assignment| {
assignment
.expression()
.analyze_semantics(function_names, macro_names, handler)
}),
Self::Single(single) => {
if let Some(assignment) = single.assignment() {
let err = match single.variable_type().keyword {
KeywordKind::Int => !matches!(
assignment.expression(),
// TODO: also allow macro identifier but not macro string literal
Expression::Primary(
Primary::Integer(_) | Primary::Lua(_) | Primary::FunctionCall(_)
)
),
KeywordKind::Bool => !matches!(
assignment.expression(),
Expression::Primary(Primary::Boolean(_) | Primary::Lua(_))
),
_ => false,
};
if err {
let err = error::Error::UnexpectedExpression(UnexpectedExpression(
assignment.expression().clone(),
));
handler.receive(err.clone());
return Err(err);
}
assignment
.expression()
.analyze_semantics(function_names, macro_names, handler)
} else {
Ok(())
}
}
Self::Score(score) => {
if let Some((_, assignment)) = score.target_assignment() {
// TODO: also allow macro identifier but not macro string literal
if !matches!(
assignment.expression(),
Expression::Primary(
Primary::Integer(_) | Primary::Lua(_) | Primary::FunctionCall(_)
)
) {
let err = error::Error::UnexpectedExpression(UnexpectedExpression(
assignment.expression().clone(),
));
handler.receive(err.clone());
return Err(err);
}
assignment
.expression()
.analyze_semantics(function_names, macro_names, handler)
} else {
Ok(())
}
}
Self::Tag(tag) => {
if let Some((_, assignment)) = tag.target_assignment() {
if !matches!(
assignment.expression(),
Expression::Primary(Primary::Boolean(_) | Primary::Lua(_))
) {
let err = error::Error::UnexpectedExpression(UnexpectedExpression(
assignment.expression().clone(),
));
handler.receive(err.clone());
return Err(err);
}
assignment
.expression()
.analyze_semantics(function_names, macro_names, handler)
} else {
Ok(())
}
}
}
}
}
impl PrimaryCondition { impl PrimaryCondition {
/// Analyzes the semantics of a primary condition. /// Analyzes the semantics of a primary condition.
pub fn analyze_semantics( pub fn analyze_semantics(

View File

@ -14,13 +14,15 @@ use serde::{
use crate::base::source_file::SourceFile; use crate::base::source_file::SourceFile;
static DEDUPLICATE_SOURCE_FILES: LazyLock<RwLock<bool>> = LazyLock::new(|| RwLock::new(false)); thread_local! {
static DEDUPLICATE_SOURCE_FILES: LazyLock<RwLock<bool>> = LazyLock::new(|| RwLock::new(false));
static SERIALIZE_DATA: LazyLock<Mutex<SerializeData>> = static SERIALIZE_DATA: LazyLock<Mutex<SerializeData>> =
LazyLock::new(|| Mutex::new(SerializeData::default())); LazyLock::new(|| Mutex::new(SerializeData::default()));
static DESERIALIZE_DATA: LazyLock<RwLock<Option<DeserializeData>>> = static DESERIALIZE_DATA: LazyLock<RwLock<Option<DeserializeData>>> =
LazyLock::new(|| RwLock::new(None)); LazyLock::new(|| RwLock::new(None));
}
/// Wrapper to remove duplicate source file data during (de-)serialization /// Wrapper to remove duplicate source file data during (de-)serialization
#[expect(clippy::module_name_repetitions)] #[expect(clippy::module_name_repetitions)]
@ -35,20 +37,27 @@ where
where where
S: serde::Serializer, S: serde::Serializer,
{ {
*DEDUPLICATE_SOURCE_FILES.write().unwrap() = true; DEDUPLICATE_SOURCE_FILES.with(|d| *d.write().unwrap() = true);
SERIALIZE_DATA.lock().unwrap().clear(); SERIALIZE_DATA.with(|d| d.lock().unwrap().clear());
let mut serialized_data = flexbuffers::FlexbufferSerializer::new(); // hold guard so no other can serialize at the same time in same thread
self.0 let s = DEDUPLICATE_SOURCE_FILES.with(|d| {
.serialize(&mut serialized_data) let guard = d.read().unwrap();
.map_err(|_| serde::ser::Error::custom("could not buffer serialization"))?; let mut serialized_data = flexbuffers::FlexbufferSerializer::new();
drop(serialized_data); self.0
let mut s = serializer.serialize_struct("SerdeWrapper", 3)?; .serialize(&mut serialized_data)
s.serialize_field( .map_err(|_| serde::ser::Error::custom("could not buffer serialization"))?;
"source_files", drop(serialized_data);
&SERIALIZE_DATA.lock().unwrap().id_to_source_file, let mut s = serializer.serialize_struct("SerdeWrapper", 3)?;
)?;
s.serialize_field("data", &self.0)?; SERIALIZE_DATA.with(|d| {
*DEDUPLICATE_SOURCE_FILES.write().unwrap() = false; s.serialize_field("source_files", &d.lock().unwrap().id_to_source_file)
})?;
s.serialize_field("data", &self.0)?;
drop(guard);
Ok(s)
})?;
DEDUPLICATE_SOURCE_FILES.with(|d| *d.write().unwrap() = false);
s.end() s.end()
} }
} }
@ -87,11 +96,13 @@ where
let source_files: BTreeMap<u64, SourceFile> = seq let source_files: BTreeMap<u64, SourceFile> = seq
.next_element()? .next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?; .ok_or_else(|| de::Error::invalid_length(0, &self))?;
*DESERIALIZE_DATA.write().unwrap() = Some(DeserializeData { DESERIALIZE_DATA.with(|d| {
id_to_source_file: source_files *d.write().unwrap() = Some(DeserializeData {
.into_iter() id_to_source_file: source_files
.map(|(k, v)| (k, Arc::new(v))) .into_iter()
.collect(), .map(|(k, v)| (k, Arc::new(v)))
.collect(),
})
}); });
let data = seq let data = seq
.next_element()? .next_element()?
@ -113,13 +124,15 @@ where
if data.is_some() { if data.is_some() {
return Err(de::Error::duplicate_field("data")); return Err(de::Error::duplicate_field("data"));
} }
*DESERIALIZE_DATA.write().unwrap() = DESERIALIZE_DATA.with(|d| {
source_files.as_ref().map(|source_files| DeserializeData { *d.write().unwrap() =
id_to_source_file: source_files source_files.as_ref().map(|source_files| DeserializeData {
.iter() id_to_source_file: source_files
.map(|(&k, v)| (k, Arc::new(v.clone()))) .iter()
.collect(), .map(|(&k, v)| (k, Arc::new(v.clone())))
}); .collect(),
})
});
data = Some(map.next_value()?); data = Some(map.next_value()?);
} }
Field::SourceFiles => { Field::SourceFiles => {
@ -136,14 +149,14 @@ where
} }
} }
*DEDUPLICATE_SOURCE_FILES.write().unwrap() = true; DEDUPLICATE_SOURCE_FILES.with(|d| *d.write().unwrap() = true);
*DESERIALIZE_DATA.write().unwrap() = None; DESERIALIZE_DATA.with(|d| *d.write().unwrap() = None);
let res = deserializer.deserialize_struct( let res = deserializer.deserialize_struct(
"SerdeWrapper", "SerdeWrapper",
&["source_files", "data"], &["source_files", "data"],
WrapperVisitor(PhantomData::<T>), WrapperVisitor(PhantomData::<T>),
); );
*DEDUPLICATE_SOURCE_FILES.write().unwrap() = false; DEDUPLICATE_SOURCE_FILES.with(|d| *d.write().unwrap() = false);
res res
} }
@ -200,9 +213,11 @@ pub mod source_file {
where where
S: serde::Serializer, S: serde::Serializer,
{ {
if *DEDUPLICATE_SOURCE_FILES.read().unwrap() { if DEDUPLICATE_SOURCE_FILES.with(|d| *d.read().unwrap()) {
let mut data = SERIALIZE_DATA.lock().unwrap(); SERIALIZE_DATA.with(|d| {
serializer.serialize_u64(data.get_id_of(this)) let mut data = d.lock().unwrap();
serializer.serialize_u64(data.get_id_of(this))
})
} else { } else {
this.as_ref().serialize(serializer) this.as_ref().serialize(serializer)
} }
@ -212,17 +227,18 @@ pub mod source_file {
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
if *DEDUPLICATE_SOURCE_FILES.read().unwrap() { if DEDUPLICATE_SOURCE_FILES.with(|d| *d.read().unwrap()) {
let id = u64::deserialize(deserializer)?; let id = u64::deserialize(deserializer)?;
Ok(DESERIALIZE_DATA Ok(DESERIALIZE_DATA.with(|d| {
.read() d.read()
.unwrap() .unwrap()
.as_ref() .as_ref()
.ok_or_else(|| de::Error::custom("SourceFiles do not have been loaded yet"))? .ok_or_else(|| de::Error::custom("SourceFiles do not have been loaded yet"))?
.id_to_source_file .id_to_source_file
.get(&id) .get(&id)
.map(Arc::clone) .map(Arc::clone)
.ok_or_else(|| serde::de::Error::custom("invalid source_file id"))?) .ok_or_else(|| serde::de::Error::custom("invalid source_file id"))
}))?
} else { } else {
Ok(Arc::new(SourceFile::deserialize(deserializer)?)) Ok(Arc::new(SourceFile::deserialize(deserializer)?))
} }

View File

@ -32,7 +32,8 @@ pub enum SyntaxKind {
Keyword(KeywordKind), Keyword(KeywordKind),
Identifier, Identifier,
Declaration, Declaration,
Numeric, Integer,
Boolean,
StringLiteral, StringLiteral,
MacroStringLiteral, MacroStringLiteral,
AnyStringLiteral, AnyStringLiteral,
@ -69,7 +70,8 @@ impl SyntaxKind {
Self::Punctuation(char) => format!("a punctuation token `{char}`"), Self::Punctuation(char) => format!("a punctuation token `{char}`"),
Self::Keyword(keyword) => format!("a keyword token `{}`", keyword.as_str()), Self::Keyword(keyword) => format!("a keyword token `{}`", keyword.as_str()),
Self::Declaration => "a declaration token".to_string(), Self::Declaration => "a declaration token".to_string(),
Self::Numeric => "a numeric token".to_string(), Self::Integer => "an integer token".to_string(),
Self::Boolean => "a boolean token".to_string(),
Self::StringLiteral => "a string literal".to_string(), Self::StringLiteral => "a string literal".to_string(),
Self::MacroStringLiteral => "a macro string literal".to_string(), Self::MacroStringLiteral => "a macro string literal".to_string(),
Self::AnyStringLiteral => "a (macro) string literal".to_string(), Self::AnyStringLiteral => "a (macro) string literal".to_string(),
@ -106,7 +108,8 @@ impl Display for UnexpectedSyntax {
Some(Token::Punctuation(punctuation)) => { Some(Token::Punctuation(punctuation)) => {
format!("a punctuation token `{}`", punctuation.punctuation) format!("a punctuation token `{}`", punctuation.punctuation)
} }
Some(Token::Numeric(..)) => "a numeric token".to_string(), Some(Token::Integer(..)) => "an integer token".to_string(),
Some(Token::Boolean(..)) => "a boolean token".to_string(),
Some(Token::CommandLiteral(..)) => "a literal command token".to_string(), Some(Token::CommandLiteral(..)) => "a literal command token".to_string(),
Some(Token::StringLiteral(..)) => "a string literal token".to_string(), Some(Token::StringLiteral(..)) => "a string literal token".to_string(),
Some(Token::MacroStringLiteral(..)) => "a macro string literal token".to_string(), Some(Token::MacroStringLiteral(..)) => "a macro string literal token".to_string(),

View File

@ -7,7 +7,7 @@ use crate::{
base::{self, Handler}, base::{self, Handler},
lexical::{ lexical::{
token::{ token::{
Identifier, Keyword, KeywordKind, MacroStringLiteral, Numeric, Punctuation, Identifier, Integer, Keyword, KeywordKind, MacroStringLiteral, Punctuation,
StringLiteral, Token, StringLiteral, Token,
}, },
token_stream::{Delimited, Delimiter, TokenStream, TokenTree}, token_stream::{Delimited, Delimiter, TokenStream, TokenTree},
@ -399,16 +399,16 @@ impl<'a> Frame<'a> {
} }
} }
/// Expects the next [`Token`] to be an [`Numeric`], and returns it. /// Expects the next [`Token`] to be an [`Integer`], and returns it.
/// ///
/// # Errors /// # Errors
/// If the next [`Token`] is not an [`Identifier`]. /// If the next [`Token`] is not an [`Identifier`].
pub fn parse_numeric(&mut self, handler: &impl Handler<Error>) -> ParseResult<Numeric> { pub fn parse_integer(&mut self, handler: &impl Handler<Error>) -> ParseResult<Integer> {
match self.next_significant_token() { match self.next_significant_token() {
Reading::Atomic(Token::Numeric(ident)) => Ok(ident), Reading::Atomic(Token::Integer(ident)) => Ok(ident),
found => { found => {
let err = Error::UnexpectedSyntax(UnexpectedSyntax { let err = Error::UnexpectedSyntax(UnexpectedSyntax {
expected: SyntaxKind::Numeric, expected: SyntaxKind::Integer,
found: found.into_token(), found: found.into_token(),
}); });
handler.receive(err.clone()); handler.receive(err.clone());

View File

@ -11,7 +11,8 @@ use crate::{
}, },
lexical::{ lexical::{
token::{ token::{
Identifier, Keyword, KeywordKind, MacroStringLiteral, Punctuation, StringLiteral, Token, Boolean, Identifier, Integer, Keyword, KeywordKind, MacroStringLiteral, Punctuation,
StringLiteral, Token,
}, },
token_stream::Delimiter, token_stream::Delimiter,
}, },
@ -53,8 +54,10 @@ impl SourceElement for Expression {
/// ///
/// ``` ebnf /// ``` ebnf
/// Primary: /// Primary:
/// FunctionCall /// Integer
/// | Boolean
/// | StringLiteral /// | StringLiteral
/// | FunctionCall
/// | MacroStringLiteral /// | MacroStringLiteral
/// | LuaCode /// | LuaCode
/// ``` /// ```
@ -62,8 +65,10 @@ impl SourceElement for Expression {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
pub enum Primary { pub enum Primary {
FunctionCall(FunctionCall), Integer(Integer),
Boolean(Boolean),
StringLiteral(StringLiteral), StringLiteral(StringLiteral),
FunctionCall(FunctionCall),
MacroStringLiteral(MacroStringLiteral), MacroStringLiteral(MacroStringLiteral),
Lua(Box<LuaCode>), Lua(Box<LuaCode>),
} }
@ -71,8 +76,10 @@ pub enum Primary {
impl SourceElement for Primary { impl SourceElement for Primary {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
Self::FunctionCall(function_call) => function_call.span(), Self::Integer(int) => int.span(),
Self::Boolean(bool) => bool.span(),
Self::StringLiteral(string_literal) => string_literal.span(), Self::StringLiteral(string_literal) => string_literal.span(),
Self::FunctionCall(function_call) => function_call.span(),
Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(), Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
Self::Lua(lua_code) => lua_code.span(), Self::Lua(lua_code) => lua_code.span(),
} }
@ -224,6 +231,22 @@ impl<'a> Parser<'a> {
} }
} }
// integer expression
Reading::Atomic(Token::Integer(int)) => {
// eat the int
self.forward();
Ok(Primary::Integer(int))
}
// boolean expression
Reading::Atomic(Token::Boolean(bool)) => {
// eat the bool
self.forward();
Ok(Primary::Boolean(bool))
}
// string literal expression // string literal expression
Reading::Atomic(Token::StringLiteral(literal)) => { Reading::Atomic(Token::StringLiteral(literal)) => {
// eat the string literal // eat the string literal

View File

@ -3,27 +3,31 @@
pub mod execute_block; pub mod execute_block;
use derive_more::From; use derive_more::From;
use enum_as_inner::EnumAsInner;
use getset::Getters; use getset::Getters;
use crate::{ use crate::{
base::{ base::{
self, self,
source_file::{SourceElement, Span}, source_file::{SourceElement, Span},
Handler, Handler, VoidHandler,
}, },
lexical::{ lexical::{
token::{CommandLiteral, DocComment, Keyword, KeywordKind, Punctuation, Token}, token::{
CommandLiteral, DocComment, Identifier, Integer, Keyword, KeywordKind, Punctuation,
Token,
},
token_stream::Delimiter, token_stream::Delimiter,
}, },
syntax::{ syntax::{
error::ParseResult, error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
parser::{Parser, Reading}, parser::{Parser, Reading},
}, },
}; };
use self::execute_block::ExecuteBlock; use self::execute_block::ExecuteBlock;
use super::expression::Expression; use super::{expression::Expression, AnyStringLiteral};
/// Represents a statement in the syntax tree. /// Represents a statement in the syntax tree.
/// ///
@ -189,7 +193,7 @@ impl SourceElement for Grouping {
/// Syntax Synopsis: /// Syntax Synopsis:
/// ``` ebnf /// ``` ebnf
/// Semicolon: /// Semicolon:
/// Expression ';' /// SemicolonStatement ';'
/// ; /// ;
/// ``` /// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -197,7 +201,7 @@ impl SourceElement for Grouping {
pub struct Semicolon { pub struct Semicolon {
/// The expression of the semicolon statement. /// The expression of the semicolon statement.
#[get = "pub"] #[get = "pub"]
expression: Expression, statement: SemicolonStatement,
/// The semicolon of the semicolon statement. /// The semicolon of the semicolon statement.
#[get = "pub"] #[get = "pub"]
semicolon: Punctuation, semicolon: Punctuation,
@ -205,7 +209,7 @@ pub struct Semicolon {
impl SourceElement for Semicolon { impl SourceElement for Semicolon {
fn span(&self) -> Span { fn span(&self) -> Span {
self.expression self.statement
.span() .span()
.join(&self.semicolon.span()) .join(&self.semicolon.span())
.expect("The span of the semicolon statement is invalid.") .expect("The span of the semicolon statement is invalid.")
@ -215,8 +219,373 @@ impl SourceElement for Semicolon {
impl Semicolon { impl Semicolon {
/// Dissolves the [`Semicolon`] into its components. /// Dissolves the [`Semicolon`] into its components.
#[must_use] #[must_use]
pub fn dissolve(self) -> (Expression, Punctuation) { pub fn dissolve(self) -> (SemicolonStatement, Punctuation) {
(self.expression, self.semicolon) (self.statement, self.semicolon)
}
}
/// Represents a statement that ends with a semicolon in the syntax tree.
///
/// Syntax Synopsis:
/// ``` ebnf
/// SemicolonStatement:
/// (Expression | VariableDeclaration)
/// ';'
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From, EnumAsInner)]
pub enum SemicolonStatement {
/// An expression that ends with a semicolon.
Expression(Expression),
/// A variable declaration.
VariableDeclaration(VariableDeclaration),
}
impl SourceElement for SemicolonStatement {
fn span(&self) -> Span {
match self {
Self::Expression(expression) => expression.span(),
Self::VariableDeclaration(declaration) => declaration.span(),
}
}
}
/// Represents a variable declaration in the syntax tree.
///
/// Syntax Synopsis:
///
/// ```ebnf
/// VariableDeclaration:
/// SingleVariableDeclaration
/// | ArrayVariableDeclaration
/// | ScoreVariableDeclaration
/// | TagVariableDeclaration
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From, EnumAsInner)]
#[allow(missing_docs)]
pub enum VariableDeclaration {
Single(SingleVariableDeclaration),
Array(ArrayVariableDeclaration),
Score(ScoreVariableDeclaration),
Tag(TagVariableDeclaration),
}
impl SourceElement for VariableDeclaration {
fn span(&self) -> Span {
match self {
Self::Single(declaration) => declaration.span(),
Self::Array(declaration) => declaration.span(),
Self::Score(declaration) => declaration.span(),
Self::Tag(declaration) => declaration.span(),
}
}
}
impl VariableDeclaration {
/// Get the identifier of the variable declaration
#[must_use]
pub fn identifier(&self) -> &Identifier {
match self {
Self::Single(declaration) => &declaration.identifier,
Self::Array(declaration) => &declaration.identifier,
Self::Score(declaration) => &declaration.identifier,
Self::Tag(declaration) => &declaration.identifier,
}
}
/// Get the type of the variable declaration
#[must_use]
pub fn variable_type(&self) -> &Keyword {
match self {
Self::Single(declaration) => &declaration.variable_type,
Self::Array(declaration) => &declaration.variable_type,
Self::Score(declaration) => &declaration.int_keyword,
Self::Tag(declaration) => &declaration.bool_keyword,
}
}
}
/// Represents a variable assignment.
///
/// Syntax Synopsis:
///
/// ```ebnf
/// VariableDeclarationAssignment:
/// '=' Expression
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct VariableDeclarationAssignment {
/// The equals sign of the variable declaration.
#[get = "pub"]
equals: Punctuation,
/// The expression of the variable declaration.
#[get = "pub"]
expression: Expression,
}
impl SourceElement for VariableDeclarationAssignment {
fn span(&self) -> Span {
self.equals
.span()
.join(&self.expression.span())
.expect("The span of the variable declaration assignment is invalid.")
}
}
impl VariableDeclarationAssignment {
/// Dissolves the [`VariableDeclarationAssignment`] into its components.
#[must_use]
pub fn dissolve(self) -> (Punctuation, Expression) {
(self.equals, self.expression)
}
}
/// Represents a single variable declaration in the syntax tree.
///
/// Syntax Synopsis:
///
/// ```ebnf
/// SingleVariableDeclaration:
/// ('int' | 'bool') identifier VariableDeclarationAssignment?
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct SingleVariableDeclaration {
/// The type of the variable.
#[get = "pub"]
variable_type: Keyword,
/// The identifier of the variable.
#[get = "pub"]
identifier: Identifier,
/// The optional assignment of the variable.
#[get = "pub"]
assignment: Option<VariableDeclarationAssignment>,
}
impl SourceElement for SingleVariableDeclaration {
fn span(&self) -> Span {
self.variable_type
.span()
.join(
&self
.assignment
.as_ref()
.map_or_else(|| self.identifier.span(), SourceElement::span),
)
.expect("The span of the single variable declaration is invalid.")
}
}
impl SingleVariableDeclaration {
/// Dissolves the [`SingleVariableDeclaration`] into its components.
#[must_use]
pub fn dissolve(self) -> (Keyword, Identifier, Option<VariableDeclarationAssignment>) {
(self.variable_type, self.identifier, self.assignment)
}
}
/// Represents an array variable declaration in the syntax tree.
///
/// Syntax Synopsis:
///
/// ```ebnf
/// ArrayVariableDeclaration:
/// ('int' | 'bool') identifier '[' integer ']' VariableDeclarationAssignment?
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct ArrayVariableDeclaration {
/// The type of the variable.
#[get = "pub"]
variable_type: Keyword,
/// The identifier of the variable.
#[get = "pub"]
identifier: Identifier,
/// The opening bracket of the array.
#[get = "pub"]
open_bracket: Punctuation,
/// The array size
#[get = "pub"]
size: Integer,
/// The closing bracket of the array.
#[get = "pub"]
close_bracket: Punctuation,
/// The optional assignment of the variable.
#[get = "pub"]
assignment: Option<VariableDeclarationAssignment>,
}
impl SourceElement for ArrayVariableDeclaration {
fn span(&self) -> Span {
self.variable_type
.span()
.join(
&self
.assignment
.as_ref()
.map_or_else(|| self.close_bracket.span(), SourceElement::span),
)
.expect("The span of the array variable declaration is invalid.")
}
}
impl ArrayVariableDeclaration {
/// Dissolves the [`ArrayVariableDeclaration`] into its components.
#[must_use]
pub fn dissolve(
self,
) -> (
Keyword,
Identifier,
Punctuation,
Integer,
Punctuation,
Option<VariableDeclarationAssignment>,
) {
(
self.variable_type,
self.identifier,
self.open_bracket,
self.size,
self.close_bracket,
self.assignment,
)
}
}
type CriteriaSelection = (Punctuation, AnyStringLiteral, Punctuation);
/// Represents a scoreboard variable declaration in the syntax tree.
///
/// Syntax Synopsis:
///
/// ```ebnf
/// ScoreVariableDeclaration:
/// 'int' ('<' AnyStringLiteral '>')? identifier '[' AnyStringLiteral? ']' VariableDeclarationAssignment?
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct ScoreVariableDeclaration {
/// `int` keyword
#[get = "pub"]
int_keyword: Keyword,
/// The scoreboard criteria
#[get = "pub"]
criteria: Option<CriteriaSelection>,
/// The identifier of the variable.
#[get = "pub"]
identifier: Identifier,
/// Opening bracket of the score variable
#[get = "pub"]
open_bracket: Punctuation,
/// Closing bracket of the score variable
#[get = "pub"]
close_bracket: Punctuation,
/// The optional assignment of the variable.
#[get = "pub"]
target_assignment: Option<(AnyStringLiteral, VariableDeclarationAssignment)>,
}
impl SourceElement for ScoreVariableDeclaration {
fn span(&self) -> Span {
self.int_keyword
.span()
.join(&self.target_assignment.as_ref().map_or_else(
|| self.close_bracket.span(),
|(_, assignment)| assignment.span(),
))
.expect("The span of the score variable declaration is invalid.")
}
}
impl ScoreVariableDeclaration {
/// Dissolves the [`ScoreVariableDeclaration`] into its components.
#[expect(clippy::type_complexity)]
#[must_use]
pub fn dissolve(
self,
) -> (
Keyword,
Option<CriteriaSelection>,
Identifier,
Punctuation,
Punctuation,
Option<(AnyStringLiteral, VariableDeclarationAssignment)>,
) {
(
self.int_keyword,
self.criteria,
self.identifier,
self.open_bracket,
self.close_bracket,
self.target_assignment,
)
}
}
/// Represents a tag variable declaration in the syntax tree.
///
/// Syntax Synopsis:
///
/// ```ebnf
/// TagVariableDeclaration:
/// 'bool' identifier '[' AnyStringLiteral? ']' VariableDeclarationAssignment?
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct TagVariableDeclaration {
/// `bool` keyword
#[get = "pub"]
bool_keyword: Keyword,
/// The identifier of the variable.
#[get = "pub"]
identifier: Identifier,
/// Opening bracket of the score variable
#[get = "pub"]
open_bracket: Punctuation,
/// Closing bracket of the score variable
#[get = "pub"]
close_bracket: Punctuation,
/// The optional assignment of the variable.
#[get = "pub"]
target_assignment: Option<(AnyStringLiteral, VariableDeclarationAssignment)>,
}
impl SourceElement for TagVariableDeclaration {
fn span(&self) -> Span {
self.bool_keyword
.span()
.join(&self.target_assignment.as_ref().map_or_else(
|| self.close_bracket.span(),
|(_, assignment)| assignment.span(),
))
.expect("The span of the tag variable declaration is invalid.")
}
}
impl TagVariableDeclaration {
/// Dissolves the [`TagVariableDeclaration`] into its components.
#[must_use]
pub fn dissolve(
self,
) -> (
Keyword,
Identifier,
Punctuation,
Punctuation,
Option<(AnyStringLiteral, VariableDeclarationAssignment)>,
) {
(
self.bool_keyword,
self.identifier,
self.open_bracket,
self.close_bracket,
self.target_assignment,
)
} }
} }
@ -333,17 +702,225 @@ impl<'a> Parser<'a> {
} }
// semicolon statement // semicolon statement
_ => { _ => self.parse_semicolon(handler).map(Statement::Semicolon),
}
}
/// Parses a [`Semicolon`].
#[tracing::instrument(level = "trace", skip_all)]
pub fn parse_semicolon(
&mut self,
handler: &impl Handler<base::Error>,
) -> ParseResult<Semicolon> {
let statement = match self.stop_at_significant() {
Reading::Atomic(Token::Keyword(keyword))
if matches!(keyword.keyword, KeywordKind::Int | KeywordKind::Bool) =>
{
self.parse_variable_declaration(handler)
.map(SemicolonStatement::VariableDeclaration)
}
_ => self
.parse_expression(handler)
.map(SemicolonStatement::Expression),
}?;
let semicolon = self.parse_punctuation(';', true, handler)?;
Ok(Semicolon {
statement,
semicolon,
})
}
/// Parses a [`VariableDeclaration`].
#[tracing::instrument(level = "trace", skip_all)]
pub fn parse_variable_declaration(
&mut self,
handler: &impl Handler<base::Error>,
) -> ParseResult<VariableDeclaration> {
enum IndexingType {
IntegerSize(Integer),
AnyString(AnyStringLiteral),
None,
}
let variable_type = match self.stop_at_significant() {
Reading::Atomic(Token::Keyword(keyword))
if matches!(keyword.keyword, KeywordKind::Int | KeywordKind::Bool) =>
{
self.forward();
keyword
}
unexpected => {
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
expected: SyntaxKind::Either(&[
SyntaxKind::Keyword(KeywordKind::Int),
SyntaxKind::Keyword(KeywordKind::Bool),
]),
found: unexpected.into_token(),
});
handler.receive(err.clone());
return Err(err);
}
};
let criteria_selection = if variable_type.keyword == KeywordKind::Int {
self.try_parse(|p| {
let open = p.parse_punctuation('<', true, &VoidHandler)?;
let criteria = p.parse_any_string_literal(&VoidHandler)?;
let close = p.parse_punctuation('>', true, &VoidHandler)?;
Ok((open, criteria, close))
})
.ok()
} else {
None
};
// read identifier
self.stop_at_significant();
let identifier = self.parse_identifier(handler)?;
match self.stop_at_significant() {
Reading::IntoDelimited(punc) if punc.punctuation == '[' => {
let tree = self.step_into(
Delimiter::Bracket,
|p| {
let res = match p.stop_at_significant() {
Reading::Atomic(Token::Integer(int)) => {
p.forward();
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 == ']' => {
IndexingType::None
}
unexpected => {
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
expected: SyntaxKind::Either(&[
SyntaxKind::Integer,
SyntaxKind::AnyStringLiteral,
]),
found: unexpected.into_token(),
});
handler.receive(err.clone());
return Err(err);
}
};
Ok(res)
},
handler,
)?;
let open_bracket = tree.open;
let close_bracket = tree.close;
let inner = tree.tree?;
match inner {
IndexingType::IntegerSize(size) => {
let assignment = self
.try_parse(|p| {
// read equals sign
let equals = p.parse_punctuation('=', true, handler)?;
// read expression
let expression = p.parse_expression(handler)?;
Ok(VariableDeclarationAssignment { equals, expression })
})
.ok();
Ok(VariableDeclaration::Array(ArrayVariableDeclaration {
variable_type,
identifier,
open_bracket,
size,
close_bracket,
assignment,
}))
}
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)),
}))
}
KeywordKind::Bool => {
Ok(VariableDeclaration::Tag(TagVariableDeclaration {
bool_keyword: variable_type,
identifier,
open_bracket,
close_bracket,
target_assignment: Some((selector, assignment)),
}))
}
_ => unreachable!(),
}
}
IndexingType::None => match variable_type.keyword {
KeywordKind::Int => {
Ok(VariableDeclaration::Score(ScoreVariableDeclaration {
int_keyword: variable_type,
criteria: criteria_selection,
identifier,
open_bracket,
close_bracket,
target_assignment: None,
}))
}
KeywordKind::Bool => Ok(VariableDeclaration::Tag(TagVariableDeclaration {
bool_keyword: variable_type,
identifier,
open_bracket,
close_bracket,
target_assignment: None,
})),
_ => unreachable!(),
},
}
}
// SingleVariableDeclaration with Assignment
Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '=' => {
self.forward();
let equals = punc;
let expression = self.parse_expression(handler)?; let expression = self.parse_expression(handler)?;
let semicolon = self.parse_punctuation(';', true, handler)?; let assignment = VariableDeclarationAssignment { equals, expression };
tracing::trace!("Parsed semicolon statement: {:?}", expression); Ok(VariableDeclaration::Single(SingleVariableDeclaration {
variable_type,
Ok(Statement::Semicolon(Semicolon { identifier,
expression, assignment: Some(assignment),
semicolon,
})) }))
} }
// SingleVariableDeclaration without Assignment
_ => Ok(VariableDeclaration::Single(SingleVariableDeclaration {
variable_type,
identifier,
assignment: None,
})),
} }
} }
} }

View File

@ -19,6 +19,8 @@ mod transpiler;
#[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;
#[cfg(feature = "shulkerbox")]
mod variables;
pub mod util; pub mod util;

View File

@ -21,7 +21,7 @@ use crate::{
program::{Namespace, ProgramFile}, program::{Namespace, ProgramFile},
statement::{ statement::{
execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail}, execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail},
Statement, SemicolonStatement, Statement,
}, },
}, },
transpile::error::MissingFunctionDeclaration, transpile::error::MissingFunctionDeclaration,
@ -29,6 +29,7 @@ use crate::{
use super::{ use super::{
error::{TranspileError, TranspileResult}, error::{TranspileError, TranspileResult},
variables::{Scope, VariableType},
FunctionData, FunctionData,
}; };
@ -74,8 +75,10 @@ impl Transpiler {
) -> Result<(), TranspileError> { ) -> Result<(), TranspileError> {
tracing::trace!("Transpiling program declarations"); tracing::trace!("Transpiling program declarations");
let scope = Scope::new();
for program in programs { for program in programs {
self.transpile_program_declarations(program, handler); self.transpile_program_declarations(program, &scope, handler);
} }
let mut always_transpile_functions = Vec::new(); let mut always_transpile_functions = Vec::new();
@ -98,7 +101,7 @@ impl Transpiler {
); );
for identifier_span in always_transpile_functions { for identifier_span in always_transpile_functions {
self.get_or_transpile_function(&identifier_span, None, handler)?; self.get_or_transpile_function(&identifier_span, None, &scope, handler)?;
} }
Ok(()) Ok(())
@ -108,12 +111,13 @@ impl Transpiler {
fn transpile_program_declarations( fn transpile_program_declarations(
&mut self, &mut self,
program: &ProgramFile, program: &ProgramFile,
scope: &Scope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) { ) {
let namespace = program.namespace(); let namespace = program.namespace();
for declaration in program.declarations() { for declaration in program.declarations() {
self.transpile_declaration(declaration, namespace, handler); self.transpile_declaration(declaration, namespace, scope, handler);
} }
} }
@ -123,6 +127,7 @@ impl Transpiler {
&mut self, &mut self,
declaration: &Declaration, declaration: &Declaration,
namespace: &Namespace, namespace: &Namespace,
_scope: &Scope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) { ) {
let program_identifier = declaration.span().source_file().identifier().clone(); let program_identifier = declaration.span().source_file().identifier().clone();
@ -143,7 +148,6 @@ impl Transpiler {
) )
}) })
.collect(); .collect();
#[allow(clippy::significant_drop_tightening)]
self.functions.insert( self.functions.insert(
(program_identifier, name), (program_identifier, name),
FunctionData { FunctionData {
@ -203,6 +207,7 @@ impl Transpiler {
if tag.replace().is_some() { if tag.replace().is_some() {
sb_tag.set_replace(true); sb_tag.set_replace(true);
} }
// TODO: handle global variables
} }
}; };
} }
@ -215,6 +220,7 @@ impl Transpiler {
&mut self, &mut self,
identifier_span: &Span, identifier_span: &Span,
arguments: Option<&[&Expression]>, arguments: Option<&[&Expression]>,
scope: &Scope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<(String, Option<BTreeMap<String, String>>)> { ) -> TranspileResult<(String, Option<BTreeMap<String, String>>)> {
let program_identifier = identifier_span.source_file().identifier(); let program_identifier = identifier_span.source_file().identifier();
@ -237,6 +243,8 @@ impl Transpiler {
if !already_transpiled { if !already_transpiled {
tracing::trace!("Function not transpiled yet, transpiling."); tracing::trace!("Function not transpiled yet, transpiling.");
let function_scope = scope.new_child();
let statements = { let statements = {
let functions = &self.functions; let functions = &self.functions;
let function_data = functions let function_data = functions
@ -257,9 +265,15 @@ impl Transpiler {
error error
})?; })?;
for (i, param) in function_data.parameters.iter().enumerate() {
function_scope.set_variable(param, VariableType::FunctionArgument { index: i });
}
function_data.statements.clone() function_data.statements.clone()
}; };
let commands = self.transpile_function(&statements, program_identifier, handler)?;
let commands =
self.transpile_function(&statements, program_identifier, &function_scope, handler)?;
let functions = &self.functions; let functions = &self.functions;
let function_data = functions let function_data = functions
@ -369,22 +383,22 @@ impl Transpiler {
}) })
.map(|(s, _)| s.to_owned())?; .map(|(s, _)| s.to_owned())?;
let arg_count = arguments.iter().flat_map(|x| x.iter()).count(); let arg_count = arguments.map(<[&Expression]>::len);
if arg_count != parameters.len() { if arg_count.is_some_and(|arg_count| arg_count != parameters.len()) {
let err = TranspileError::InvalidFunctionArguments(InvalidFunctionArguments { let err = TranspileError::InvalidFunctionArguments(InvalidFunctionArguments {
expected: parameters.len(), expected: parameters.len(),
actual: arg_count, actual: arg_count.expect("checked in if condition"),
span: identifier_span.clone(), span: identifier_span.clone(),
}); });
handler.receive(err.clone()); handler.receive(err.clone());
Err(err) Err(err)
} else if arg_count > 0 { } else if arg_count.is_some_and(|arg_count| arg_count > 0) {
let mut compiled_args = Vec::new(); let mut compiled_args = Vec::new();
let mut errs = Vec::new(); let mut errs = Vec::new();
for expression in arguments.iter().flat_map(|x| x.iter()) { for expression in arguments.iter().flat_map(|x| x.iter()) {
let value = match expression { let value = match expression {
Expression::Primary(Primary::FunctionCall(func)) => self Expression::Primary(Primary::FunctionCall(func)) => self
.transpile_function_call(func, handler) .transpile_function_call(func, scope, handler)
.map(|cmd| match cmd { .map(|cmd| match cmd {
Command::Raw(s) => s, Command::Raw(s) => s,
_ => unreachable!("Function call should always return a raw command"), _ => unreachable!("Function call should always return a raw command"),
@ -392,6 +406,8 @@ impl Transpiler {
Expression::Primary(Primary::Lua(lua)) => { Expression::Primary(Primary::Lua(lua)) => {
lua.eval_string(handler).map(Option::unwrap_or_default) lua.eval_string(handler).map(Option::unwrap_or_default)
} }
Expression::Primary(Primary::Integer(num)) => Ok(num.span.str().to_string()),
Expression::Primary(Primary::Boolean(bool)) => Ok(bool.span.str().to_string()),
Expression::Primary(Primary::StringLiteral(string)) => { Expression::Primary(Primary::StringLiteral(string)) => {
Ok(string.str_content().to_string()) Ok(string.str_content().to_string())
} }
@ -424,13 +440,14 @@ impl Transpiler {
&mut self, &mut self,
statements: &[Statement], statements: &[Statement],
program_identifier: &str, program_identifier: &str,
scope: &Scope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> { ) -> TranspileResult<Vec<Command>> {
let mut errors = Vec::new(); let mut errors = Vec::new();
let commands = statements let commands = statements
.iter() .iter()
.filter_map(|statement| { .filter_map(|statement| {
self.transpile_statement(statement, program_identifier, handler) self.transpile_statement(statement, program_identifier, scope, handler)
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
errors.push(err); errors.push(err);
None None
@ -449,6 +466,7 @@ impl Transpiler {
&mut self, &mut self,
statement: &Statement, statement: &Statement,
program_identifier: &str, program_identifier: &str,
scope: &Scope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Option<Command>> { ) -> TranspileResult<Option<Command>> {
match statement { match statement {
@ -457,7 +475,21 @@ impl Transpiler {
} }
Statement::Run(run) => match run.expression() { Statement::Run(run) => match run.expression() {
Expression::Primary(Primary::FunctionCall(func)) => { Expression::Primary(Primary::FunctionCall(func)) => {
self.transpile_function_call(func, handler).map(Some) self.transpile_function_call(func, scope, handler).map(Some)
}
Expression::Primary(Primary::Integer(num)) => {
let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
Expression::Primary(Primary::Integer(num.clone())),
));
handler.receive(error.clone());
Err(error)
}
Expression::Primary(Primary::Boolean(bool)) => {
let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
Expression::Primary(Primary::Boolean(bool.clone())),
));
handler.receive(error.clone());
Err(error)
} }
Expression::Primary(Primary::StringLiteral(string)) => { Expression::Primary(Primary::StringLiteral(string)) => {
Ok(Some(Command::Raw(string.str_content().to_string()))) Ok(Some(Command::Raw(string.str_content().to_string())))
@ -473,23 +505,30 @@ impl Transpiler {
unreachable!("Only literal commands are allowed in functions at this time.") unreachable!("Only literal commands are allowed in functions at this time.")
} }
Statement::ExecuteBlock(execute) => { Statement::ExecuteBlock(execute) => {
self.transpile_execute_block(execute, program_identifier, handler) let child_scope = scope.new_child();
self.transpile_execute_block(execute, program_identifier, &child_scope, handler)
} }
Statement::DocComment(doccomment) => { Statement::DocComment(doccomment) => {
let content = doccomment.content(); let content = doccomment.content();
Ok(Some(Command::Comment(content.to_string()))) Ok(Some(Command::Comment(content.to_string())))
} }
Statement::Grouping(group) => { Statement::Grouping(group) => {
let child_scope = scope.new_child();
let statements = group.block().statements(); let statements = group.block().statements();
let mut errors = Vec::new(); let mut errors = Vec::new();
let commands = statements let commands = statements
.iter() .iter()
.filter_map(|statement| { .filter_map(|statement| {
self.transpile_statement(statement, program_identifier, handler) self.transpile_statement(
.unwrap_or_else(|err| { statement,
errors.push(err); program_identifier,
None &child_scope,
}) handler,
)
.unwrap_or_else(|err| {
errors.push(err);
None
})
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !errors.is_empty() { if !errors.is_empty() {
@ -501,17 +540,38 @@ impl Transpiler {
Ok(Some(Command::Group(commands))) Ok(Some(Command::Group(commands)))
} }
} }
#[allow(clippy::match_wildcard_for_single_variants)] Statement::Semicolon(semi) => match semi.statement() {
Statement::Semicolon(semi) => match semi.expression() { #[expect(clippy::match_wildcard_for_single_variants)]
Expression::Primary(Primary::FunctionCall(func)) => { SemicolonStatement::Expression(expr) => match expr {
self.transpile_function_call(func, handler).map(Some) Expression::Primary(Primary::FunctionCall(func)) => {
} self.transpile_function_call(func, scope, handler).map(Some)
unexpected => { }
let error = TranspileError::UnexpectedExpression(UnexpectedExpression( unexpected => {
unexpected.clone(), let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
)); unexpected.clone(),
handler.receive(error.clone()); ));
Err(error) handler.receive(error.clone());
Err(error)
}
},
SemicolonStatement::VariableDeclaration(decl) => {
// let value = match decl {
// 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:?}")
} }
}, },
} }
@ -520,14 +580,19 @@ impl Transpiler {
fn transpile_function_call( fn transpile_function_call(
&mut self, &mut self,
func: &FunctionCall, func: &FunctionCall,
scope: &Scope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Command> { ) -> TranspileResult<Command> {
let arguments = func let arguments = func
.arguments() .arguments()
.as_ref() .as_ref()
.map(|l| l.elements().map(Deref::deref).collect::<Vec<_>>()); .map(|l| l.elements().map(Deref::deref).collect::<Vec<_>>());
let (location, arguments) = let (location, arguments) = self.get_or_transpile_function(
self.get_or_transpile_function(&func.identifier().span, arguments.as_deref(), handler)?; &func.identifier().span,
arguments.as_deref(),
scope,
handler,
)?;
let mut function_call = format!("function {location}"); let mut function_call = format!("function {location}");
if let Some(arguments) = arguments { if let Some(arguments) = arguments {
use std::fmt::Write; use std::fmt::Write;
@ -551,9 +616,10 @@ impl Transpiler {
&mut self, &mut self,
execute: &ExecuteBlock, execute: &ExecuteBlock,
program_identifier: &str, program_identifier: &str,
scope: &Scope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Option<Command>> { ) -> TranspileResult<Option<Command>> {
self.transpile_execute_block_internal(execute, program_identifier, handler) self.transpile_execute_block_internal(execute, program_identifier, scope, handler)
.map(|ex| ex.map(Command::Execute)) .map(|ex| ex.map(Command::Execute))
} }
@ -561,6 +627,7 @@ impl Transpiler {
&mut self, &mut self,
execute: &ExecuteBlock, execute: &ExecuteBlock,
program_identifier: &str, program_identifier: &str,
scope: &Scope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Option<Execute>> { ) -> TranspileResult<Option<Execute>> {
match execute { match execute {
@ -572,7 +639,7 @@ impl Transpiler {
.statements() .statements()
.iter() .iter()
.filter_map(|s| { .filter_map(|s| {
self.transpile_statement(s, program_identifier, handler) self.transpile_statement(s, program_identifier, scope, handler)
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
errors.push(err); errors.push(err);
None None
@ -593,11 +660,12 @@ impl Transpiler {
.transpile_execute_block_internal( .transpile_execute_block_internal(
execute_block, execute_block,
program_identifier, program_identifier,
scope,
handler, handler,
), ),
}?; }?;
self.combine_execute_head_tail(head, tail, program_identifier, handler) self.combine_execute_head_tail(head, tail, program_identifier, scope, handler)
} }
ExecuteBlock::IfElse(cond, block, el) => { ExecuteBlock::IfElse(cond, block, el) => {
let statements = block.statements(); let statements = block.statements();
@ -608,7 +676,7 @@ impl Transpiler {
let commands = statements let commands = statements
.iter() .iter()
.filter_map(|statement| { .filter_map(|statement| {
self.transpile_statement(statement, program_identifier, handler) self.transpile_statement(statement, program_identifier, scope, handler)
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
errors.push(err); errors.push(err);
None None
@ -620,7 +688,7 @@ impl Transpiler {
} }
Some(Execute::Runs(commands)) Some(Execute::Runs(commands))
} else { } else {
self.transpile_statement(&statements[0], program_identifier, handler)? self.transpile_statement(&statements[0], program_identifier, scope, handler)?
.map(|cmd| Execute::Run(Box::new(cmd))) .map(|cmd| Execute::Run(Box::new(cmd)))
}; };
@ -632,6 +700,7 @@ impl Transpiler {
then, then,
Some(el), Some(el),
program_identifier, program_identifier,
scope,
handler, handler,
) )
}, },
@ -646,6 +715,7 @@ impl Transpiler {
then: Execute, then: Execute,
el: Option<&Else>, el: Option<&Else>,
program_identifier: &str, program_identifier: &str,
scope: &Scope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Option<Execute>> { ) -> TranspileResult<Option<Execute>> {
let (_, cond) = cond.clone().dissolve(); let (_, cond) = cond.clone().dissolve();
@ -659,7 +729,7 @@ impl Transpiler {
if statements.is_empty() { if statements.is_empty() {
None None
} else if statements.len() == 1 { } else if statements.len() == 1 {
self.transpile_statement(&statements[0], program_identifier, handler) self.transpile_statement(&statements[0], program_identifier, scope, handler)
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
errors.push(err); errors.push(err);
None None
@ -669,7 +739,7 @@ impl Transpiler {
let commands = statements let commands = statements
.iter() .iter()
.filter_map(|statement| { .filter_map(|statement| {
self.transpile_statement(statement, program_identifier, handler) self.transpile_statement(statement, program_identifier, scope, handler)
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
errors.push(err); errors.push(err);
None None
@ -697,12 +767,20 @@ impl Transpiler {
head: &ExecuteBlockHead, head: &ExecuteBlockHead,
tail: Option<Execute>, tail: Option<Execute>,
program_identifier: &str, program_identifier: &str,
scope: &Scope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Option<Execute>> { ) -> TranspileResult<Option<Execute>> {
Ok(match head { Ok(match head {
ExecuteBlockHead::Conditional(cond) => { ExecuteBlockHead::Conditional(cond) => {
if let Some(tail) = tail { if let Some(tail) = tail {
self.transpile_conditional(cond, tail, None, program_identifier, handler)? self.transpile_conditional(
cond,
tail,
None,
program_identifier,
scope,
handler,
)?
} else { } else {
None None
} }

View File

@ -47,11 +47,11 @@ pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<str> {
if ident.contains("__") if ident.contains("__")
|| ident || ident
.chars() .chars()
.any(|c| !(c == '_' && c.is_ascii_alphanumeric())) .any(|c| c == '_' || !c.is_ascii_alphanumeric())
{ {
let new_ident = ident let new_ident = ident
.chars() .chars()
.filter(|c| *c == '_' || c.is_ascii_alphanumeric()) .filter(|c| *c != '_' && c.is_ascii_alphanumeric())
.collect::<String>(); .collect::<String>();
let chksum = md5::hash(ident).to_hex_lowercase(); let chksum = md5::hash(ident).to_hex_lowercase();

117
src/transpile/variables.rs Normal file
View File

@ -0,0 +1,117 @@
#![expect(unused)]
use std::{collections::HashMap, sync::RwLock};
use super::Transpiler;
#[derive(Debug, Clone)]
pub enum VariableType {
FunctionArgument {
index: usize,
},
Scoreboard {
objective: String,
},
ScoreboardValue {
objective: String,
name: String,
},
ScoreboardArray {
objective: String,
names: Vec<String>,
},
Tag {
tag_name: String,
},
BooleanStorage {
storage_name: String,
path: String,
},
BooleanStorageArray {
storage_name: String,
paths: Vec<String>,
},
}
#[derive(Debug, Default)]
pub struct Scope<'a> {
parent: Option<&'a Scope<'a>>,
variables: RwLock<HashMap<String, VariableType>>,
}
impl<'a> Scope<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn new_child(&'a self) -> Self {
Self {
parent: Some(self),
variables: RwLock::new(HashMap::new()),
}
}
pub fn get_variable(&self, name: &str) -> Option<VariableType> {
let var = self.variables.read().unwrap().get(name).cloned();
if var.is_some() {
var
} else {
self.parent.and_then(|parent| parent.get_variable(name))
}
}
pub fn set_variable(&self, name: &str, var: VariableType) {
self.variables
.write()
.unwrap()
.insert(name.to_string(), var);
}
pub fn get_parent(&self) -> Option<&'a Self> {
self.parent
}
}
impl Transpiler {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scope() {
let scope = Scope::new();
{
let mut variables = scope.variables.write().unwrap();
variables.insert(
"test".to_string(),
VariableType::Scoreboard {
objective: "test".to_string(),
},
);
}
match scope.get_variable("test") {
Some(VariableType::Scoreboard { objective }) => assert_eq!(objective, "test"),
_ => panic!("Incorrect Variable"),
}
}
#[test]
fn test_parent() {
let scope = Scope::new();
{
let mut variables = scope.variables.write().unwrap();
variables.insert(
"test".to_string(),
VariableType::Scoreboard {
objective: "test".to_string(),
},
);
}
let child = scope.new_child();
match child.get_variable("test") {
Some(VariableType::Scoreboard { objective }) => assert_eq!(objective, "test"),
_ => panic!("Incorrect Variable"),
}
}
}