Compare commits
5 Commits
4bfae0c960
...
a07f16f283
Author | SHA1 | Date |
---|---|---|
|
a07f16f283 | |
|
38f90e6491 | |
|
0d0df920ee | |
|
b8303689db | |
|
a6a5e42b6b |
|
@ -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
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
110
src/serde.rs
110
src/serde.rs
|
@ -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)?))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue