Merge branch 'feature/macro-functions' into develop
This commit is contained in:
commit
96fe865ac1
|
@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Macro strings
|
||||||
|
- Function parameters/arguments
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Option to deduplicate source files during serialization when using `SerdeWrapper`
|
- Option to deduplicate source files during serialization when using `SerdeWrapper`
|
||||||
|
|
|
@ -37,7 +37,8 @@ mlua = { version = "0.10.2", features = ["lua54", "vendored"], optional = true }
|
||||||
path-absolutize = "3.1.1"
|
path-absolutize = "3.1.1"
|
||||||
pathdiff = "0.2.3"
|
pathdiff = "0.2.3"
|
||||||
serde = { version = "1.0.217", features = ["derive"], optional = true }
|
serde = { version = "1.0.217", features = ["derive"], optional = true }
|
||||||
shulkerbox = { version = "0.1.0", default-features = false, optional = true }
|
# shulkerbox = { version = "0.1.0", default-features = false, optional = true }
|
||||||
|
shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "76d58c0766518fe5ab2635de60ba40972565a3e0", default-features = false, optional = true }
|
||||||
strsim = "0.11.1"
|
strsim = "0.11.1"
|
||||||
strum = { version = "0.27.0", features = ["derive"] }
|
strum = { version = "0.27.0", features = ["derive"] }
|
||||||
thiserror = "2.0.11"
|
thiserror = "2.0.11"
|
||||||
|
|
19
grammar.md
19
grammar.md
|
@ -12,6 +12,21 @@ Program: Namespace Declaration*;
|
||||||
Namespace: 'namespace' StringLiteral;
|
Namespace: 'namespace' StringLiteral;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### StringLiteral
|
||||||
|
```ebnf
|
||||||
|
StringLiteral: '"' TEXT '"';
|
||||||
|
```
|
||||||
|
|
||||||
|
### MacroStringLiteral
|
||||||
|
```ebnf
|
||||||
|
MacroStringLiteral: '`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
|
||||||
|
```
|
||||||
|
|
||||||
|
### AnyStringLiteral
|
||||||
|
```ebnf
|
||||||
|
AnyStringLiteral: StringLiteral | MacroStringLiteral;
|
||||||
|
```
|
||||||
|
|
||||||
### Declaration
|
### Declaration
|
||||||
```ebnf
|
```ebnf
|
||||||
Declaration: FunctionDeclaration | Import | TagDeclaration;
|
Declaration: FunctionDeclaration | Import | TagDeclaration;
|
||||||
|
@ -87,7 +102,7 @@ Condition:
|
||||||
PrimaryCondition:
|
PrimaryCondition:
|
||||||
ConditionalPrefix
|
ConditionalPrefix
|
||||||
| ParenthesizedCondition
|
| ParenthesizedCondition
|
||||||
| StringLiteral
|
| AnyStringLiteral
|
||||||
;
|
;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -144,6 +159,8 @@ Expression:
|
||||||
```ebnf
|
```ebnf
|
||||||
Primary:
|
Primary:
|
||||||
FunctionCall
|
FunctionCall
|
||||||
|
| AnyStringLiteral
|
||||||
|
| LuaCode
|
||||||
;
|
;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ParseError(#[from] crate::syntax::error::Error),
|
ParseError(#[from] crate::syntax::error::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
SemanticError(#[from] crate::semantic::error::Error),
|
||||||
|
#[error(transparent)]
|
||||||
TranspileError(#[from] crate::transpile::TranspileError),
|
TranspileError(#[from] crate::transpile::TranspileError),
|
||||||
#[error("An error occurred: {0}")]
|
#[error("An error occurred: {0}")]
|
||||||
Other(String),
|
Other(String),
|
||||||
|
|
|
@ -254,6 +254,26 @@ impl Span {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a span from the given start byte index to the end of the source file with an offset.
|
||||||
|
#[must_use]
|
||||||
|
pub fn to_end_with_offset(
|
||||||
|
source_file: Arc<SourceFile>,
|
||||||
|
start: usize,
|
||||||
|
end_offset: isize,
|
||||||
|
) -> Option<Self> {
|
||||||
|
if !source_file.content().is_char_boundary(start) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Self {
|
||||||
|
start,
|
||||||
|
end: source_file
|
||||||
|
.content()
|
||||||
|
.len()
|
||||||
|
.saturating_add_signed(end_offset),
|
||||||
|
source_file,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the string slice of the source code that the span represents.
|
/// Get the string slice of the source code that the span represents.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn str(&self) -> &str {
|
pub fn str(&self) -> &str {
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::{borrow::Cow, collections::HashMap, fmt::Display, str::FromStr, sync::O
|
||||||
|
|
||||||
use crate::base::{
|
use crate::base::{
|
||||||
self,
|
self,
|
||||||
|
log::SourceCodeDisplay,
|
||||||
source_file::{SourceElement, SourceIterator, Span},
|
source_file::{SourceElement, SourceIterator, Span},
|
||||||
Handler,
|
Handler,
|
||||||
};
|
};
|
||||||
|
@ -145,24 +146,7 @@ pub enum Token {
|
||||||
DocComment(DocComment),
|
DocComment(DocComment),
|
||||||
CommandLiteral(CommandLiteral),
|
CommandLiteral(CommandLiteral),
|
||||||
StringLiteral(StringLiteral),
|
StringLiteral(StringLiteral),
|
||||||
}
|
MacroStringLiteral(MacroStringLiteral),
|
||||||
|
|
||||||
impl Token {
|
|
||||||
/// Returns the span of the token.
|
|
||||||
#[must_use]
|
|
||||||
pub fn span(&self) -> &Span {
|
|
||||||
match self {
|
|
||||||
Self::WhiteSpaces(token) => &token.span,
|
|
||||||
Self::Identifier(token) => &token.span,
|
|
||||||
Self::Keyword(token) => &token.span,
|
|
||||||
Self::Punctuation(token) => &token.span,
|
|
||||||
Self::Numeric(token) => &token.span,
|
|
||||||
Self::Comment(token) => &token.span,
|
|
||||||
Self::DocComment(token) => &token.span,
|
|
||||||
Self::CommandLiteral(token) => &token.span,
|
|
||||||
Self::StringLiteral(token) => &token.span,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for Token {
|
impl SourceElement for Token {
|
||||||
|
@ -177,6 +161,7 @@ impl SourceElement for Token {
|
||||||
Self::DocComment(token) => token.span(),
|
Self::DocComment(token) => token.span(),
|
||||||
Self::CommandLiteral(token) => token.span(),
|
Self::CommandLiteral(token) => token.span(),
|
||||||
Self::StringLiteral(token) => token.span(),
|
Self::StringLiteral(token) => token.span(),
|
||||||
|
Self::MacroStringLiteral(token) => token.span(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,6 +275,76 @@ impl SourceElement for StringLiteral {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a hardcoded macro string literal value in the source code.
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct MacroStringLiteral {
|
||||||
|
/// The backtick that starts the macro string literal.
|
||||||
|
starting_backtick: Punctuation,
|
||||||
|
/// The parts that make up the macro string literal.
|
||||||
|
parts: Vec<MacroStringLiteralPart>,
|
||||||
|
/// The backtick that ends the macro string literal.
|
||||||
|
ending_backtick: Punctuation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MacroStringLiteral {
|
||||||
|
/// Returns the string content without escapement characters, leading and trailing double quotes.
|
||||||
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
#[must_use]
|
||||||
|
pub fn str_content(&self) -> String {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
let mut content = String::new();
|
||||||
|
|
||||||
|
for part in &self.parts {
|
||||||
|
match part {
|
||||||
|
MacroStringLiteralPart::Text(span) => {
|
||||||
|
content += &crate::util::unescape_macro_string(span.str());
|
||||||
|
}
|
||||||
|
MacroStringLiteralPart::MacroUsage { identifier, .. } => {
|
||||||
|
write!(
|
||||||
|
content,
|
||||||
|
"$({})",
|
||||||
|
crate::transpile::util::identifier_to_macro(identifier.span.str())
|
||||||
|
)
|
||||||
|
.expect("can always write to string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the parts that make up the macro string literal.
|
||||||
|
#[must_use]
|
||||||
|
pub fn parts(&self) -> &[MacroStringLiteralPart] {
|
||||||
|
&self.parts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceElement for MacroStringLiteral {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.starting_backtick
|
||||||
|
.span
|
||||||
|
.join(&self.ending_backtick.span)
|
||||||
|
.expect("Invalid macro string literal span")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a part of a macro string literal value in the source code.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum MacroStringLiteralPart {
|
||||||
|
Text(Span),
|
||||||
|
MacroUsage {
|
||||||
|
dollar: Punctuation,
|
||||||
|
open_brace: Punctuation,
|
||||||
|
identifier: Identifier,
|
||||||
|
close_brace: Punctuation,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// Is an enumeration representing the two kinds of comments in the Shulkerscript programming language.
|
/// Is an enumeration representing the two kinds of comments in the Shulkerscript programming language.
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
@ -362,7 +417,7 @@ impl CommandLiteral {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is an error that can occur when invoking the [`Token::tokenize`] method.
|
/// Is an error that can occur when invoking the [`Token::tokenize`] method.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error, From)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum TokenizeError {
|
pub enum TokenizeError {
|
||||||
#[error("encountered a fatal lexical error that causes the process to stop.")]
|
#[error("encountered a fatal lexical error that causes the process to stop.")]
|
||||||
|
@ -370,8 +425,95 @@ pub enum TokenizeError {
|
||||||
|
|
||||||
#[error("the iterator argument is at the end of the source code.")]
|
#[error("the iterator argument is at the end of the source code.")]
|
||||||
EndOfSourceCodeIteratorArgument,
|
EndOfSourceCodeIteratorArgument,
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
InvalidMacroNameCharacter(#[from] InvalidMacroNameCharacter),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
UnclosedMacroUsage(#[from] UnclosedMacroUsage),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
EmptyMacroUsage(#[from] EmptyMacroUsage),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is an error that can occur when the macro name contains invalid characters.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct InvalidMacroNameCharacter {
|
||||||
|
/// The span of the invalid characters.
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for InvalidMacroNameCharacter {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
base::log::Message::new(base::log::Severity::Error, format!("The macro name contains invalid characters: `{}`. Only alphanumeric characters and underscores are allowed.", self.span.str()))
|
||||||
|
)?;
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.span, Option::<u8>::None)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for InvalidMacroNameCharacter {}
|
||||||
|
|
||||||
|
/// Is an error that can occur when the macro usage is not closed.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct UnclosedMacroUsage {
|
||||||
|
/// The span of the unclosed macro usage.
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UnclosedMacroUsage {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
base::log::Message::new(
|
||||||
|
base::log::Severity::Error,
|
||||||
|
"A macro usage was opened with `$(` but never closed."
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.span, Option::<u8>::None)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for UnclosedMacroUsage {}
|
||||||
|
|
||||||
|
/// Is an error that can occur when the macro usage is not closed.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct EmptyMacroUsage {
|
||||||
|
/// The span of the unclosed macro usage.
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for EmptyMacroUsage {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
base::log::Message::new(
|
||||||
|
base::log::Severity::Error,
|
||||||
|
"A macro usage was opened with `$(` but closed immediately with `)`."
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.span, Option::<u8>::None)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for EmptyMacroUsage {}
|
||||||
|
|
||||||
impl Token {
|
impl Token {
|
||||||
/// Increments the iterator while the predicate returns true.
|
/// Increments the iterator while the predicate returns true.
|
||||||
pub fn walk_iter(iter: &mut SourceIterator, predicate: impl Fn(char) -> bool) {
|
pub fn walk_iter(iter: &mut SourceIterator, predicate: impl Fn(char) -> bool) {
|
||||||
|
@ -385,6 +527,7 @@ impl Token {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a span from the given start location to the current location of the iterator.
|
/// Creates a span from the given start location to the current location of the iterator.
|
||||||
|
#[must_use]
|
||||||
fn create_span(start: usize, iter: &mut SourceIterator) -> Span {
|
fn create_span(start: usize, iter: &mut SourceIterator) -> Span {
|
||||||
iter.peek().map_or_else(
|
iter.peek().map_or_else(
|
||||||
|| Span::to_end(iter.source_file().clone(), start).unwrap(),
|
|| Span::to_end(iter.source_file().clone(), start).unwrap(),
|
||||||
|
@ -392,6 +535,26 @@ impl Token {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a span from the given start location to the current location of the iterator with the given offset.
|
||||||
|
#[must_use]
|
||||||
|
fn create_span_with_end_offset(
|
||||||
|
start: usize,
|
||||||
|
iter: &mut SourceIterator,
|
||||||
|
end_offset: isize,
|
||||||
|
) -> Span {
|
||||||
|
iter.peek().map_or_else(
|
||||||
|
|| Span::to_end_with_offset(iter.source_file().clone(), start, end_offset).unwrap(),
|
||||||
|
|(index, _)| {
|
||||||
|
Span::new(
|
||||||
|
iter.source_file().clone(),
|
||||||
|
start,
|
||||||
|
index.saturating_add_signed(end_offset),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if the given character is a valid first character of an identifier.
|
/// Checks if the given character is a valid first character of an identifier.
|
||||||
fn is_first_identifier_character(character: char) -> bool {
|
fn is_first_identifier_character(character: char) -> bool {
|
||||||
character == '_'
|
character == '_'
|
||||||
|
@ -551,6 +714,113 @@ impl Token {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles a sequence of characters that are enclosed in backticks and contain macro usages
|
||||||
|
fn handle_macro_string_literal(
|
||||||
|
iter: &mut SourceIterator,
|
||||||
|
mut start: usize,
|
||||||
|
) -> Result<Self, TokenizeError> {
|
||||||
|
let mut is_escaped = false;
|
||||||
|
let mut is_inside_macro = false;
|
||||||
|
let mut encountered_open_parenthesis = false;
|
||||||
|
let starting_backtick = Punctuation {
|
||||||
|
span: Self::create_span(start, iter),
|
||||||
|
punctuation: '`',
|
||||||
|
};
|
||||||
|
start += 1;
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
while iter.peek().is_some() {
|
||||||
|
let (index, character) = iter.next().unwrap();
|
||||||
|
|
||||||
|
#[expect(clippy::collapsible_else_if)]
|
||||||
|
if is_inside_macro {
|
||||||
|
if character == ')' {
|
||||||
|
// Check if the macro usage is empty
|
||||||
|
if start + 2 == index {
|
||||||
|
return Err(EmptyMacroUsage {
|
||||||
|
span: Span::new(iter.source_file().clone(), start, index + 1).unwrap(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
parts.push(MacroStringLiteralPart::MacroUsage {
|
||||||
|
dollar: Punctuation {
|
||||||
|
span: Span::new(iter.source_file().clone(), start, start + 1).unwrap(),
|
||||||
|
punctuation: '$',
|
||||||
|
},
|
||||||
|
open_brace: Punctuation {
|
||||||
|
span: Span::new(iter.source_file().clone(), start + 1, start + 2)
|
||||||
|
.unwrap(),
|
||||||
|
punctuation: '(',
|
||||||
|
},
|
||||||
|
identifier: Identifier {
|
||||||
|
span: Self::create_span_with_end_offset(start + 2, iter, -1),
|
||||||
|
},
|
||||||
|
close_brace: Punctuation {
|
||||||
|
span: Span::new(iter.source_file().clone(), index, index + 1).unwrap(),
|
||||||
|
punctuation: ')',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
start = index + 1;
|
||||||
|
is_inside_macro = false;
|
||||||
|
} else if !encountered_open_parenthesis && character == '(' {
|
||||||
|
encountered_open_parenthesis = true;
|
||||||
|
} else if encountered_open_parenthesis && !Self::is_identifier_character(character)
|
||||||
|
{
|
||||||
|
if character == '`' {
|
||||||
|
return Err(UnclosedMacroUsage {
|
||||||
|
span: Span::new(iter.source_file().clone(), start, start + 2).unwrap(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::walk_iter(iter, |c| c != ')' && !Self::is_identifier_character(c));
|
||||||
|
return Err(InvalidMacroNameCharacter {
|
||||||
|
span: Self::create_span(index, iter),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if character == '$' && iter.peek().is_some_and(|(_, c)| c == '(') {
|
||||||
|
parts.push(MacroStringLiteralPart::Text(
|
||||||
|
Self::create_span_with_end_offset(start, iter, -1),
|
||||||
|
));
|
||||||
|
start = index;
|
||||||
|
is_inside_macro = true;
|
||||||
|
encountered_open_parenthesis = false;
|
||||||
|
} else if character == '\\' {
|
||||||
|
is_escaped = !is_escaped;
|
||||||
|
} else if character == '`' && !is_escaped {
|
||||||
|
if start != index {
|
||||||
|
parts.push(MacroStringLiteralPart::Text(
|
||||||
|
Self::create_span_with_end_offset(start, iter, -1),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
start = index;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
is_escaped = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_inside_macro {
|
||||||
|
Err(UnclosedMacroUsage {
|
||||||
|
span: Span::new(iter.source_file().clone(), start, start + 2).unwrap(),
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
} else {
|
||||||
|
Ok(MacroStringLiteral {
|
||||||
|
starting_backtick,
|
||||||
|
parts,
|
||||||
|
ending_backtick: Punctuation {
|
||||||
|
span: Self::create_span(start, iter),
|
||||||
|
punctuation: '`',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles a command that is preceeded by a slash
|
/// Handles a command that is preceeded by a slash
|
||||||
fn handle_command_literal(iter: &mut SourceIterator, start: usize) -> Self {
|
fn handle_command_literal(iter: &mut SourceIterator, start: usize) -> Self {
|
||||||
Self::walk_iter(iter, |c| !(c.is_whitespace() && c.is_ascii_control()));
|
Self::walk_iter(iter, |c| !(c.is_whitespace() && c.is_ascii_control()));
|
||||||
|
@ -592,9 +862,15 @@ impl Token {
|
||||||
// Found comment/single slash punctuation
|
// Found comment/single slash punctuation
|
||||||
else if character == '/' {
|
else if character == '/' {
|
||||||
Self::handle_comment(iter, start, character, prev_token, handler)
|
Self::handle_comment(iter, start, character, prev_token, handler)
|
||||||
} else if character == '"' {
|
}
|
||||||
|
// Found string literal
|
||||||
|
else if character == '"' {
|
||||||
Ok(Self::handle_string_literal(iter, start))
|
Ok(Self::handle_string_literal(iter, start))
|
||||||
}
|
}
|
||||||
|
// Found macro string literal
|
||||||
|
else if character == '`' {
|
||||||
|
Self::handle_macro_string_literal(iter, start)
|
||||||
|
}
|
||||||
// Found numeric literal
|
// Found numeric literal
|
||||||
else if character.is_ascii_digit() {
|
else if character.is_ascii_digit() {
|
||||||
Ok(Self::handle_numeric_literal(iter, start))
|
Ok(Self::handle_numeric_literal(iter, start))
|
||||||
|
|
|
@ -5,10 +5,13 @@ use std::{fmt::Debug, sync::Arc};
|
||||||
use derive_more::{Deref, From};
|
use derive_more::{Deref, From};
|
||||||
use enum_as_inner::EnumAsInner;
|
use enum_as_inner::EnumAsInner;
|
||||||
|
|
||||||
use crate::base::{
|
use crate::{
|
||||||
self,
|
base::{
|
||||||
source_file::{SourceElement, SourceFile, Span},
|
self,
|
||||||
Handler,
|
source_file::{SourceElement, SourceFile, Span},
|
||||||
|
Handler,
|
||||||
|
},
|
||||||
|
lexical::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -62,6 +65,17 @@ impl TokenStream {
|
||||||
Err(TokenizeError::FatalLexicalError) => {
|
Err(TokenizeError::FatalLexicalError) => {
|
||||||
tracing::error!("Fatal lexical error encountered while tokenizing source code");
|
tracing::error!("Fatal lexical error encountered while tokenizing source code");
|
||||||
}
|
}
|
||||||
|
Err(TokenizeError::InvalidMacroNameCharacter(err)) => {
|
||||||
|
handler.receive(Error::TokenizeError(
|
||||||
|
TokenizeError::InvalidMacroNameCharacter(err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(TokenizeError::UnclosedMacroUsage(err)) => {
|
||||||
|
handler.receive(Error::TokenizeError(TokenizeError::UnclosedMacroUsage(err)));
|
||||||
|
}
|
||||||
|
Err(TokenizeError::EmptyMacroUsage(err)) => {
|
||||||
|
handler.receive(Error::TokenizeError(TokenizeError::EmptyMacroUsage(err)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +198,7 @@ pub enum TokenTree {
|
||||||
impl SourceElement for TokenTree {
|
impl SourceElement for TokenTree {
|
||||||
fn span(&self) -> Span {
|
fn span(&self) -> Span {
|
||||||
match self {
|
match self {
|
||||||
Self::Token(token) => token.span().to_owned(),
|
Self::Token(token) => token.span(),
|
||||||
Self::Delimited(delimited) => delimited
|
Self::Delimited(delimited) => delimited
|
||||||
.open
|
.open
|
||||||
.span()
|
.span()
|
||||||
|
|
|
@ -17,8 +17,10 @@ pub use shulkerbox;
|
||||||
|
|
||||||
pub mod base;
|
pub mod base;
|
||||||
pub mod lexical;
|
pub mod lexical;
|
||||||
|
pub mod semantic;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
pub mod transpile;
|
pub mod transpile;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
pub(crate) mod serde;
|
pub(crate) mod serde;
|
||||||
|
@ -106,6 +108,8 @@ pub fn parse(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
program.analyze_semantics(handler)?;
|
||||||
|
|
||||||
Ok(program)
|
Ok(program)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,286 @@
|
||||||
|
//! Error types for the semantic analysis phase of the compiler.
|
||||||
|
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
use std::{collections::HashSet, fmt::Display};
|
||||||
|
|
||||||
|
use getset::Getters;
|
||||||
|
use itertools::Itertools as _;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
base::{
|
||||||
|
log::{Message, Severity, SourceCodeDisplay},
|
||||||
|
source_file::{SourceElement as _, Span},
|
||||||
|
},
|
||||||
|
lexical::token::StringLiteral,
|
||||||
|
syntax::syntax_tree::expression::Expression,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
MissingFunctionDeclaration(#[from] MissingFunctionDeclaration),
|
||||||
|
#[error(transparent)]
|
||||||
|
UnexpectedExpression(#[from] UnexpectedExpression),
|
||||||
|
#[error(transparent)]
|
||||||
|
ConflictingFunctionNames(#[from] ConflictingFunctionNames),
|
||||||
|
#[error(transparent)]
|
||||||
|
InvalidNamespaceName(#[from] InvalidNamespaceName),
|
||||||
|
#[error(transparent)]
|
||||||
|
UnresolvedMacroUsage(#[from] UnresolvedMacroUsage),
|
||||||
|
#[error(transparent)]
|
||||||
|
IncompatibleFunctionAnnotation(#[from] IncompatibleFunctionAnnotation),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error that occurs when a function declaration is missing.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)]
|
||||||
|
pub struct MissingFunctionDeclaration {
|
||||||
|
#[get = "pub"]
|
||||||
|
span: Span,
|
||||||
|
#[get = "pub"]
|
||||||
|
alternatives: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MissingFunctionDeclaration {
|
||||||
|
pub(super) fn from_context(identifier_span: Span, functions: &HashSet<String>) -> Self {
|
||||||
|
let own_name = identifier_span.str();
|
||||||
|
let alternatives = functions
|
||||||
|
.iter()
|
||||||
|
.filter_map(|function_name| {
|
||||||
|
let normalized_distance =
|
||||||
|
strsim::normalized_damerau_levenshtein(own_name, function_name);
|
||||||
|
(normalized_distance > 0.8
|
||||||
|
|| strsim::damerau_levenshtein(own_name, function_name) < 3)
|
||||||
|
.then_some((normalized_distance, function_name))
|
||||||
|
})
|
||||||
|
.sorted_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal))
|
||||||
|
.map(|(_, data)| data)
|
||||||
|
.take(8)
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
alternatives,
|
||||||
|
span: identifier_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for MissingFunctionDeclaration {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
let message = format!(
|
||||||
|
"no matching function declaration found for invocation of function `{}`",
|
||||||
|
self.span.str()
|
||||||
|
);
|
||||||
|
write!(f, "{}", Message::new(Severity::Error, message))?;
|
||||||
|
|
||||||
|
let help_message = if self.alternatives.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let mut message = String::from("did you mean ");
|
||||||
|
for (i, alternative) in self.alternatives.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
message.push_str(", ");
|
||||||
|
}
|
||||||
|
write!(message, "`{alternative}`")?;
|
||||||
|
}
|
||||||
|
Some(message + "?")
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.span, help_message.as_ref())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for MissingFunctionDeclaration {}
|
||||||
|
|
||||||
|
/// An error that occurs when a function declaration is missing.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct UnexpectedExpression(pub Expression);
|
||||||
|
|
||||||
|
impl Display for UnexpectedExpression {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
Message::new(Severity::Error, "encountered unexpected expression")
|
||||||
|
)?;
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.0.span(), Option::<u8>::None)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for UnexpectedExpression {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ConflictingFunctionNames {
|
||||||
|
pub definition: Span,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ConflictingFunctionNames {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
Message::new(
|
||||||
|
Severity::Error,
|
||||||
|
format!("the following function declaration conflicts with an existing function with name `{}`", self.name)
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.definition, Option::<u8>::None)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ConflictingFunctionNames {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct InvalidNamespaceName {
|
||||||
|
pub name: StringLiteral,
|
||||||
|
pub invalid_chars: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for InvalidNamespaceName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
Message::new(
|
||||||
|
Severity::Error,
|
||||||
|
format!(
|
||||||
|
"Invalid characters in namespace `{}`. The following characters are not allowed in namespace definitions: `{}`",
|
||||||
|
self.name.str_content(),
|
||||||
|
self.invalid_chars
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.name.span, Option::<u8>::None)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for InvalidNamespaceName {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct UnresolvedMacroUsage {
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UnresolvedMacroUsage {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
Message::new(
|
||||||
|
Severity::Error,
|
||||||
|
format!(
|
||||||
|
"Macro `{}` was used, but could not be resolved.",
|
||||||
|
self.span.str(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(
|
||||||
|
&self.span,
|
||||||
|
Some(format!(
|
||||||
|
"You might want to add `{}` to the function parameters.",
|
||||||
|
self.span.str()
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for UnresolvedMacroUsage {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct IncompatibleFunctionAnnotation {
|
||||||
|
pub span: Span,
|
||||||
|
pub reason: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for IncompatibleFunctionAnnotation {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
Message::new(
|
||||||
|
Severity::Error,
|
||||||
|
format!(
|
||||||
|
"Annotation `{}` cannot be used here, because {}.",
|
||||||
|
self.span.str(),
|
||||||
|
self.reason
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
write!(f, "\n{}", SourceCodeDisplay::new(&self.span, None::<u8>))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for IncompatibleFunctionAnnotation {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct InvalidFunctionArguments {
|
||||||
|
pub span: Span,
|
||||||
|
pub expected: usize,
|
||||||
|
pub actual: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for InvalidFunctionArguments {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
Message::new(
|
||||||
|
Severity::Error,
|
||||||
|
format!(
|
||||||
|
"Expected {} arguments, but got {}.",
|
||||||
|
self.expected, self.actual
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let help_message = if self.expected > self.actual {
|
||||||
|
format!(
|
||||||
|
"You might want to add {} more arguments.",
|
||||||
|
self.expected - self.actual
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"You might want to remove {} arguments.",
|
||||||
|
self.actual - self.expected
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
SourceCodeDisplay::new(&self.span, Some(help_message))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for InvalidFunctionArguments {}
|
|
@ -0,0 +1,574 @@
|
||||||
|
//! This module contains the semantic analysis of the AST.
|
||||||
|
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use error::{
|
||||||
|
IncompatibleFunctionAnnotation, InvalidNamespaceName, MissingFunctionDeclaration,
|
||||||
|
UnexpectedExpression, UnresolvedMacroUsage,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
base::{self, source_file::SourceElement as _, Handler},
|
||||||
|
lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
|
||||||
|
syntax::syntax_tree::{
|
||||||
|
condition::{
|
||||||
|
BinaryCondition, Condition, ParenthesizedCondition, PrimaryCondition, UnaryCondition,
|
||||||
|
},
|
||||||
|
declaration::{Declaration, Function, ImportItems},
|
||||||
|
expression::{Expression, FunctionCall, Primary},
|
||||||
|
program::{Namespace, ProgramFile},
|
||||||
|
statement::{
|
||||||
|
execute_block::{
|
||||||
|
Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockHeadItem as _,
|
||||||
|
ExecuteBlockTail,
|
||||||
|
},
|
||||||
|
Block, Grouping, Run, Semicolon, Statement,
|
||||||
|
},
|
||||||
|
AnyStringLiteral,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
impl ProgramFile {
|
||||||
|
/// Analyzes the semantics of the program.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
self.namespace().analyze_semantics(handler)?;
|
||||||
|
|
||||||
|
let mut errs = Vec::new();
|
||||||
|
let function_names = extract_all_function_names(self.declarations(), handler)?;
|
||||||
|
|
||||||
|
for declaration in self.declarations() {
|
||||||
|
if let Err(err) = declaration.analyze_semantics(&function_names, handler) {
|
||||||
|
errs.push(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
if let Some(err) = errs.first() {
|
||||||
|
Err(err.clone())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_all_function_names(
|
||||||
|
declarations: &[Declaration],
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<HashSet<String>, error::Error> {
|
||||||
|
let mut function_names = HashSet::new();
|
||||||
|
let mut errs = Vec::new();
|
||||||
|
|
||||||
|
for declaration in declarations {
|
||||||
|
match declaration {
|
||||||
|
Declaration::Function(func) => {
|
||||||
|
let name = func.identifier();
|
||||||
|
if function_names.contains(name.span.str()) {
|
||||||
|
let err = error::Error::from(error::ConflictingFunctionNames {
|
||||||
|
name: name.span.str().to_string(),
|
||||||
|
definition: name.span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
errs.push(err);
|
||||||
|
}
|
||||||
|
function_names.insert(name.span.str().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Declaration::Import(imp) => match imp.items() {
|
||||||
|
ImportItems::All(_) => {
|
||||||
|
handler.receive(base::Error::Other(
|
||||||
|
"Importing all items is not yet supported.".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
ImportItems::Named(items) => {
|
||||||
|
for item in items.elements() {
|
||||||
|
if function_names.contains(item.span.str()) {
|
||||||
|
let err = error::Error::from(error::ConflictingFunctionNames {
|
||||||
|
name: item.span.str().to_string(),
|
||||||
|
definition: item.span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
errs.push(err);
|
||||||
|
}
|
||||||
|
function_names.insert(item.span.str().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Declaration::Tag(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
if let Some(err) = errs.first() {
|
||||||
|
Err(err.clone())
|
||||||
|
} else {
|
||||||
|
Ok(function_names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Namespace {
|
||||||
|
/// Analyzes the semantics of the namespace.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
let name = self.namespace_name();
|
||||||
|
Self::validate_str(name.str_content().as_ref()).map_err(|invalid_chars| {
|
||||||
|
let err = error::Error::from(InvalidNamespaceName {
|
||||||
|
name: name.clone(),
|
||||||
|
invalid_chars,
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Declaration {
|
||||||
|
/// Analyzes the semantics of the declaration.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Function(func) => func.analyze_semantics(function_names, handler),
|
||||||
|
Self::Import(_) | Self::Tag(_) => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Function {
|
||||||
|
/// Analyzes the semantics of the function.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
let macro_names = if let Some(parameters) = self.parameters() {
|
||||||
|
if let Some(incompatible) = self
|
||||||
|
.annotations()
|
||||||
|
.iter()
|
||||||
|
.find(|a| ["tick", "load"].contains(&a.identifier().span.str()))
|
||||||
|
{
|
||||||
|
let err =
|
||||||
|
error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
|
||||||
|
span: incompatible.identifier().span(),
|
||||||
|
reason:
|
||||||
|
"functions with the `tick` or `load` annotation cannot have parameters"
|
||||||
|
.to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters
|
||||||
|
.elements()
|
||||||
|
.map(|el| el.span.str().to_string())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
HashSet::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.block()
|
||||||
|
.analyze_semantics(function_names, ¯o_names, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Block {
|
||||||
|
/// Analyzes the semantics of a block.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
let mut errs = Vec::new();
|
||||||
|
for statement in &self.statements {
|
||||||
|
if let Err(err) = match statement {
|
||||||
|
Statement::Block(block) => {
|
||||||
|
block.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
Statement::DocComment(_) | Statement::LiteralCommand(_) => Ok(()),
|
||||||
|
Statement::ExecuteBlock(ex) => {
|
||||||
|
ex.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
Statement::Grouping(group) => {
|
||||||
|
group.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
Statement::Run(run) => run.analyze_semantics(function_names, macro_names, handler),
|
||||||
|
Statement::Semicolon(sem) => {
|
||||||
|
sem.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
errs.push(err);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
if let Some(err) = errs.first() {
|
||||||
|
Err(err.clone())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlock {
|
||||||
|
/// Analyzes the semantics of the execute block.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
match self {
|
||||||
|
Self::HeadTail(head, tail) => {
|
||||||
|
let head_res = head.analyze_semantics(function_names, macro_names, handler);
|
||||||
|
let tail_res = tail.analyze_semantics(function_names, macro_names, handler);
|
||||||
|
|
||||||
|
if head_res.is_err() {
|
||||||
|
head_res
|
||||||
|
} else {
|
||||||
|
tail_res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::IfElse(cond, then, el) => {
|
||||||
|
let cond_res = cond.analyze_semantics(function_names, macro_names, handler);
|
||||||
|
let then_res = then.analyze_semantics(function_names, macro_names, handler);
|
||||||
|
let else_res = el.analyze_semantics(function_names, macro_names, handler);
|
||||||
|
|
||||||
|
if cond_res.is_err() {
|
||||||
|
cond_res
|
||||||
|
} else if then_res.is_err() {
|
||||||
|
then_res
|
||||||
|
} else {
|
||||||
|
else_res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grouping {
|
||||||
|
/// Analyzes the semantics of the grouping.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
self.block()
|
||||||
|
.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Run {
|
||||||
|
/// Analyzes the semantics of the run statement.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
self.expression()
|
||||||
|
.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Semicolon {
|
||||||
|
/// Analyzes the semantics of the semicolon statement.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
match self.expression() {
|
||||||
|
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.clone()),
|
||||||
|
));
|
||||||
|
handler.receive(error.clone());
|
||||||
|
Err(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHead {
|
||||||
|
/// Analyzes the semantics of the execute block head.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Align(align) => align.analyze_semantics(macro_names, handler),
|
||||||
|
Self::Anchored(anchored) => anchored.analyze_semantics(macro_names, handler),
|
||||||
|
Self::As(r#as) => r#as.analyze_semantics(macro_names, handler),
|
||||||
|
Self::At(at) => at.analyze_semantics(macro_names, handler),
|
||||||
|
Self::AsAt(asat) => asat.analyze_semantics(macro_names, handler),
|
||||||
|
Self::Conditional(cond) => cond.analyze_semantics(function_names, macro_names, handler),
|
||||||
|
Self::Facing(facing) => facing.analyze_semantics(macro_names, handler),
|
||||||
|
Self::In(r#in) => r#in.analyze_semantics(macro_names, handler),
|
||||||
|
Self::On(on) => on.analyze_semantics(macro_names, handler),
|
||||||
|
Self::Positioned(pos) => pos.analyze_semantics(macro_names, handler),
|
||||||
|
Self::Rotated(rot) => rot.analyze_semantics(macro_names, handler),
|
||||||
|
Self::Store(store) => store.analyze_semantics(macro_names, handler),
|
||||||
|
Self::Summon(summon) => summon.analyze_semantics(macro_names, handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockTail {
|
||||||
|
/// Analyzes the semantics of the execute block tail.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Block(block) => block.analyze_semantics(function_names, macro_names, handler),
|
||||||
|
Self::ExecuteBlock(_, ex) => ex.analyze_semantics(function_names, macro_names, handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Conditional {
|
||||||
|
/// Analyzes the semantics of the conditional.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
self.condition()
|
||||||
|
.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParenthesizedCondition {
|
||||||
|
/// Analyzes the semantics of the parenthesized condition.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
self.condition
|
||||||
|
.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Condition {
|
||||||
|
/// Analyzes the semantics of the condition.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Primary(prim) => prim.analyze_semantics(function_names, macro_names, handler),
|
||||||
|
Self::Binary(bin) => bin.analyze_semantics(function_names, macro_names, handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Else {
|
||||||
|
/// Analyzes the semantics of the else block.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
self.block()
|
||||||
|
.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MacroStringLiteral {
|
||||||
|
/// Analyzes the semantics of the macro string literal.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
for part in self.parts() {
|
||||||
|
if let MacroStringLiteralPart::MacroUsage { identifier, .. } = part {
|
||||||
|
if !macro_names.contains(identifier.span.str()) {
|
||||||
|
let err = error::Error::UnresolvedMacroUsage(UnresolvedMacroUsage {
|
||||||
|
span: identifier.span(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
errors.push(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
if let Some(err) = errors.first() {
|
||||||
|
Err(err.clone())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Expression {
|
||||||
|
/// Analyzes the semantics of an expression.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Primary(prim) => prim.analyze_semantics(function_names, macro_names, handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Primary {
|
||||||
|
/// Analyzes the semantics of a primary expression.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
match self {
|
||||||
|
Self::FunctionCall(func) => {
|
||||||
|
func.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
Self::Lua(_) | Self::StringLiteral(_) => Ok(()),
|
||||||
|
Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionCall {
|
||||||
|
/// Analyzes the semantics of a function call.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
if !function_names.contains(self.identifier().span.str()) {
|
||||||
|
let err = error::Error::MissingFunctionDeclaration(
|
||||||
|
MissingFunctionDeclaration::from_context(self.identifier().span(), function_names),
|
||||||
|
);
|
||||||
|
handler.receive(err.clone());
|
||||||
|
errors.push(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
for expression in self
|
||||||
|
.arguments()
|
||||||
|
.iter()
|
||||||
|
.flat_map(super::syntax::syntax_tree::ConnectedList::elements)
|
||||||
|
{
|
||||||
|
if let Err(err) = expression.analyze_semantics(function_names, macro_names, handler) {
|
||||||
|
handler.receive(err.clone());
|
||||||
|
errors.push(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
if let Some(err) = errors.first() {
|
||||||
|
Err(err.clone())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnyStringLiteral {
|
||||||
|
/// Analyzes the semantics of any string literal.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
match self {
|
||||||
|
Self::StringLiteral(_) => Ok(()),
|
||||||
|
Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimaryCondition {
|
||||||
|
/// Analyzes the semantics of a primary condition.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Parenthesized(paren) => {
|
||||||
|
paren.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
Self::StringLiteral(_) => Ok(()),
|
||||||
|
Self::Unary(unary) => unary.analyze_semantics(function_names, macro_names, handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnaryCondition {
|
||||||
|
/// Analyzes the semantics of an unary condition.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
self.operand()
|
||||||
|
.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinaryCondition {
|
||||||
|
/// Analyzes the semantics of a binary condition.
|
||||||
|
pub fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
function_names: &HashSet<String>,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
let a = self
|
||||||
|
.left_operand()
|
||||||
|
.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
.inspect_err(|err| {
|
||||||
|
handler.receive(err.clone());
|
||||||
|
});
|
||||||
|
let b = self
|
||||||
|
.right_operand()
|
||||||
|
.analyze_semantics(function_names, macro_names, handler)
|
||||||
|
.inspect_err(|err| {
|
||||||
|
handler.receive(err.clone());
|
||||||
|
});
|
||||||
|
if a.is_err() {
|
||||||
|
a
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ use std::fmt::Display;
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{
|
base::{
|
||||||
log::{Message, Severity, SourceCodeDisplay},
|
log::{Message, Severity, SourceCodeDisplay},
|
||||||
source_file::Span,
|
source_file::{SourceElement as _, Span},
|
||||||
},
|
},
|
||||||
lexical::token::{KeywordKind, Token},
|
lexical::token::{KeywordKind, Token},
|
||||||
};
|
};
|
||||||
|
@ -34,6 +34,8 @@ pub enum SyntaxKind {
|
||||||
Declaration,
|
Declaration,
|
||||||
Numeric,
|
Numeric,
|
||||||
StringLiteral,
|
StringLiteral,
|
||||||
|
MacroStringLiteral,
|
||||||
|
AnyStringLiteral,
|
||||||
Statement,
|
Statement,
|
||||||
Expression,
|
Expression,
|
||||||
Type,
|
Type,
|
||||||
|
@ -69,6 +71,8 @@ impl SyntaxKind {
|
||||||
Self::Declaration => "a declaration token".to_string(),
|
Self::Declaration => "a declaration token".to_string(),
|
||||||
Self::Numeric => "a numeric token".to_string(),
|
Self::Numeric => "a numeric 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::AnyStringLiteral => "a (macro) string literal".to_string(),
|
||||||
Self::Statement => "a statement syntax".to_string(),
|
Self::Statement => "a statement syntax".to_string(),
|
||||||
Self::Expression => "an expression syntax".to_string(),
|
Self::Expression => "an expression syntax".to_string(),
|
||||||
Self::Type => "a type syntax".to_string(),
|
Self::Type => "a type syntax".to_string(),
|
||||||
|
@ -105,6 +109,7 @@ impl Display for UnexpectedSyntax {
|
||||||
Some(Token::Numeric(..)) => "a numeric token".to_string(),
|
Some(Token::Numeric(..)) => "a numeric 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(),
|
||||||
|
|
||||||
None => "EOF".to_string(),
|
None => "EOF".to_string(),
|
||||||
};
|
};
|
||||||
|
@ -117,7 +122,7 @@ impl Display for UnexpectedSyntax {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"\n{}",
|
"\n{}",
|
||||||
SourceCodeDisplay::new(span.span(), Option::<u8>::None)
|
SourceCodeDisplay::new(&span.span(), Option::<u8>::None)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,18 @@ use enum_as_inner::EnumAsInner;
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{self, Handler},
|
base::{self, Handler},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{Identifier, Keyword, KeywordKind, Numeric, Punctuation, StringLiteral, Token},
|
token::{
|
||||||
|
Identifier, Keyword, KeywordKind, MacroStringLiteral, Numeric, Punctuation,
|
||||||
|
StringLiteral, Token,
|
||||||
|
},
|
||||||
token_stream::{Delimited, Delimiter, TokenStream, TokenTree},
|
token_stream::{Delimited, Delimiter, TokenStream, TokenTree},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax};
|
use super::{
|
||||||
|
error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
|
||||||
|
syntax_tree::AnyStringLiteral,
|
||||||
|
};
|
||||||
|
|
||||||
/// Represents a parser that reads a token stream and constructs an abstract syntax tree.
|
/// Represents a parser that reads a token stream and constructs an abstract syntax tree.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut)]
|
||||||
|
@ -432,6 +438,49 @@ impl<'a> Frame<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expects the next [`Token`] to be an [`MacroStringLiteral`], and returns it.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// If the next [`Token`] is not an [`MacroStringLiteral`].
|
||||||
|
pub fn parse_macro_string_literal(
|
||||||
|
&mut self,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> ParseResult<MacroStringLiteral> {
|
||||||
|
match self.next_significant_token() {
|
||||||
|
Reading::Atomic(Token::MacroStringLiteral(literal)) => Ok(literal),
|
||||||
|
found => {
|
||||||
|
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
||||||
|
expected: SyntaxKind::MacroStringLiteral,
|
||||||
|
found: found.into_token(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expects the next [`Token`] to be an [`AnyStringLiteral`], and returns it.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// If the next [`Token`] is not an [`AnyStringLiteral`].
|
||||||
|
pub fn parse_any_string_literal(
|
||||||
|
&mut self,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> ParseResult<AnyStringLiteral> {
|
||||||
|
match self.next_significant_token() {
|
||||||
|
Reading::Atomic(Token::StringLiteral(literal)) => Ok(literal.into()),
|
||||||
|
Reading::Atomic(Token::MacroStringLiteral(literal)) => Ok(literal.into()),
|
||||||
|
found => {
|
||||||
|
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
||||||
|
expected: SyntaxKind::AnyStringLiteral,
|
||||||
|
found: found.into_token(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Expects the next [`Token`] to be a [`Keyword`] of specific kind, and returns it.
|
/// Expects the next [`Token`] to be a [`Keyword`] of specific kind, and returns it.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
Handler, VoidHandler,
|
Handler, VoidHandler,
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{Punctuation, StringLiteral, Token},
|
token::{Punctuation, Token},
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::{
|
syntax::{
|
||||||
|
@ -23,6 +23,8 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::AnyStringLiteral;
|
||||||
|
|
||||||
/// Condition that is viewed as a single entity during precedence parsing.
|
/// Condition that is viewed as a single entity during precedence parsing.
|
||||||
///
|
///
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
|
@ -31,7 +33,7 @@ use crate::{
|
||||||
/// PrimaryCondition:
|
/// PrimaryCondition:
|
||||||
/// UnaryCondition
|
/// UnaryCondition
|
||||||
/// | ParenthesizedCondition
|
/// | ParenthesizedCondition
|
||||||
/// | StringLiteral
|
/// | AnyStringLiteral
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -39,7 +41,7 @@ use crate::{
|
||||||
pub enum PrimaryCondition {
|
pub enum PrimaryCondition {
|
||||||
Unary(UnaryCondition),
|
Unary(UnaryCondition),
|
||||||
Parenthesized(ParenthesizedCondition),
|
Parenthesized(ParenthesizedCondition),
|
||||||
StringLiteral(StringLiteral),
|
StringLiteral(AnyStringLiteral),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for PrimaryCondition {
|
impl SourceElement for PrimaryCondition {
|
||||||
|
@ -354,7 +356,13 @@ impl<'a> Parser<'a> {
|
||||||
// string literal
|
// string literal
|
||||||
Reading::Atomic(Token::StringLiteral(literal)) => {
|
Reading::Atomic(Token::StringLiteral(literal)) => {
|
||||||
self.forward();
|
self.forward();
|
||||||
Ok(PrimaryCondition::StringLiteral(literal))
|
Ok(PrimaryCondition::StringLiteral(literal.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// macro string literal
|
||||||
|
Reading::Atomic(Token::MacroStringLiteral(literal)) => {
|
||||||
|
self.forward();
|
||||||
|
Ok(PrimaryCondition::StringLiteral(literal.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// parenthesized condition
|
// parenthesized condition
|
||||||
|
|
|
@ -10,7 +10,9 @@ use crate::{
|
||||||
Handler,
|
Handler,
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{Identifier, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
|
token::{
|
||||||
|
Identifier, Keyword, KeywordKind, MacroStringLiteral, Punctuation, StringLiteral, Token,
|
||||||
|
},
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::{
|
syntax::{
|
||||||
|
@ -52,6 +54,9 @@ impl SourceElement for Expression {
|
||||||
/// ``` ebnf
|
/// ``` ebnf
|
||||||
/// Primary:
|
/// Primary:
|
||||||
/// FunctionCall
|
/// FunctionCall
|
||||||
|
/// | StringLiteral
|
||||||
|
/// | MacroStringLiteral
|
||||||
|
/// | LuaCode
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -59,6 +64,7 @@ impl SourceElement for Expression {
|
||||||
pub enum Primary {
|
pub enum Primary {
|
||||||
FunctionCall(FunctionCall),
|
FunctionCall(FunctionCall),
|
||||||
StringLiteral(StringLiteral),
|
StringLiteral(StringLiteral),
|
||||||
|
MacroStringLiteral(MacroStringLiteral),
|
||||||
Lua(Box<LuaCode>),
|
Lua(Box<LuaCode>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +73,7 @@ impl SourceElement for Primary {
|
||||||
match self {
|
match self {
|
||||||
Self::FunctionCall(function_call) => function_call.span(),
|
Self::FunctionCall(function_call) => function_call.span(),
|
||||||
Self::StringLiteral(string_literal) => string_literal.span(),
|
Self::StringLiteral(string_literal) => 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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,6 +187,7 @@ impl<'a> Parser<'a> {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// - If the parser is not at a primary expression.
|
/// - If the parser is not at a primary expression.
|
||||||
/// - If the parser is not at a valid primary expression.
|
/// - If the parser is not at a valid primary expression.
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
pub fn parse_primary(&mut self, handler: &impl Handler<base::Error>) -> ParseResult<Primary> {
|
pub fn parse_primary(&mut self, handler: &impl Handler<base::Error>) -> ParseResult<Primary> {
|
||||||
match self.stop_at_significant() {
|
match self.stop_at_significant() {
|
||||||
// identifier expression
|
// identifier expression
|
||||||
|
@ -224,6 +232,14 @@ impl<'a> Parser<'a> {
|
||||||
Ok(Primary::StringLiteral(literal))
|
Ok(Primary::StringLiteral(literal))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// macro string literal expression
|
||||||
|
Reading::Atomic(Token::MacroStringLiteral(macro_string_literal)) => {
|
||||||
|
// eat the macro string literal
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
Ok(Primary::MacroStringLiteral(macro_string_literal))
|
||||||
|
}
|
||||||
|
|
||||||
// lua code expression
|
// lua code expression
|
||||||
Reading::Atomic(Token::Keyword(lua_keyword))
|
Reading::Atomic(Token::Keyword(lua_keyword))
|
||||||
if lua_keyword.keyword == KeywordKind::Lua =>
|
if lua_keyword.keyword == KeywordKind::Lua =>
|
||||||
|
@ -267,10 +283,11 @@ impl<'a> Parser<'a> {
|
||||||
let combined = first
|
let combined = first
|
||||||
.into_token()
|
.into_token()
|
||||||
.and_then(|first| {
|
.and_then(|first| {
|
||||||
first.span().join(&last.into_token().map_or_else(
|
first.span().join(
|
||||||
|| first.span().to_owned(),
|
&last
|
||||||
|last| last.span().to_owned(),
|
.into_token()
|
||||||
))
|
.map_or_else(|| first.span(), |last| last.span()),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.expect("Invalid lua code span");
|
.expect("Invalid lua code span");
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! Contains the syntax tree nodes that represent the structure of the source code.
|
//! Contains the syntax tree nodes that represent the structure of the source code.
|
||||||
|
|
||||||
|
use derive_more::derive::From;
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -9,7 +10,7 @@ use crate::{
|
||||||
Handler, VoidHandler,
|
Handler, VoidHandler,
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{Punctuation, Token},
|
token::{MacroStringLiteral, Punctuation, StringLiteral, Token},
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::parser::Reading,
|
syntax::parser::Reading,
|
||||||
|
@ -64,6 +65,29 @@ pub struct DelimitedList<T> {
|
||||||
pub close: Punctuation,
|
pub close: Punctuation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a syntax tree node that can be either a string literal or a macro string literal.
|
||||||
|
///
|
||||||
|
/// Syntax Synopsis:
|
||||||
|
/// ```ebnf
|
||||||
|
/// AnyStringLiteral: StringLiteral | MacroStringLiteral ;
|
||||||
|
/// ```
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From)]
|
||||||
|
pub enum AnyStringLiteral {
|
||||||
|
StringLiteral(StringLiteral),
|
||||||
|
MacroStringLiteral(MacroStringLiteral),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceElement for AnyStringLiteral {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Self::StringLiteral(string_literal) => string_literal.span(),
|
||||||
|
Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
/// Parses a list of elements enclosed by a pair of delimiters, separated by a separator.
|
/// Parses a list of elements enclosed by a pair of delimiters, separated by a separator.
|
||||||
///
|
///
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
lexical::token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token},
|
lexical::token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token},
|
||||||
syntax::{
|
syntax::{
|
||||||
self,
|
self,
|
||||||
error::{InvalidArgument, ParseResult, SyntaxKind, UnexpectedSyntax},
|
error::{ParseResult, SyntaxKind, UnexpectedSyntax},
|
||||||
parser::{Parser, Reading},
|
parser::{Parser, Reading},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -108,22 +108,7 @@ impl<'a> Parser<'a> {
|
||||||
// eat the keyword
|
// eat the keyword
|
||||||
self.forward();
|
self.forward();
|
||||||
|
|
||||||
let namespace_name = self.parse_string_literal(handler).and_then(|name| {
|
let namespace_name = self.parse_string_literal(handler)?;
|
||||||
Namespace::validate_str(name.str_content().as_ref())
|
|
||||||
.map(|()| name.clone())
|
|
||||||
.map_err(|invalid| {
|
|
||||||
let err = syntax::error::Error::InvalidArgument(InvalidArgument {
|
|
||||||
message: format!(
|
|
||||||
"Invalid characters in namespace '{}'. The following characters are not allowed in namespace definitions: '{}'",
|
|
||||||
name.str_content(),
|
|
||||||
invalid
|
|
||||||
),
|
|
||||||
span: name.span(),
|
|
||||||
});
|
|
||||||
handler.receive(err.clone());
|
|
||||||
err
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let semicolon = self.parse_punctuation(';', true, handler)?;
|
let semicolon = self.parse_punctuation(';', true, handler)?;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
//! Execute block statement syntax tree.
|
//! Execute block statement syntax tree.
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use derive_more::From;
|
use derive_more::From;
|
||||||
use enum_as_inner::EnumAsInner;
|
use enum_as_inner::EnumAsInner;
|
||||||
use getset::Getters;
|
use getset::Getters;
|
||||||
|
@ -11,13 +13,13 @@ use crate::{
|
||||||
Handler, VoidHandler,
|
Handler, VoidHandler,
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token},
|
token::{Keyword, KeywordKind, Punctuation, Token},
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::{
|
syntax::{
|
||||||
error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
|
error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
|
||||||
parser::{DelimitedTree, Parser, Reading},
|
parser::{DelimitedTree, Parser, Reading},
|
||||||
syntax_tree::condition::ParenthesizedCondition,
|
syntax_tree::{condition::ParenthesizedCondition, AnyStringLiteral},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -217,7 +219,7 @@ impl SourceElement for Else {
|
||||||
///
|
///
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// As:
|
/// As:
|
||||||
/// 'as' '(' StringLiteral ')'
|
/// 'as' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -231,7 +233,7 @@ pub struct As {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the as statement.
|
/// The selector of the as statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
as_selector: StringLiteral,
|
as_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -248,7 +250,7 @@ impl SourceElement for As {
|
||||||
impl As {
|
impl As {
|
||||||
/// Dissolves the [`As`] into its components.
|
/// Dissolves the [`As`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.as_keyword,
|
self.as_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -263,7 +265,7 @@ impl As {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Align:
|
/// Align:
|
||||||
/// 'align' '(' StringLiteral ')'
|
/// 'align' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
#[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, Getters)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
||||||
|
@ -276,7 +278,7 @@ pub struct Align {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the align statement.
|
/// The selector of the align statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
align_selector: StringLiteral,
|
align_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -294,7 +296,7 @@ impl SourceElement for Align {
|
||||||
impl Align {
|
impl Align {
|
||||||
/// Dissolves the [`Align`] into its components.
|
/// Dissolves the [`Align`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.align_keyword,
|
self.align_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -309,7 +311,7 @@ impl Align {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Anchored:
|
/// Anchored:
|
||||||
/// 'anchored' '(' StringLiteral ')'
|
/// 'anchored' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -323,7 +325,7 @@ pub struct Anchored {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the anchored statement.
|
/// The selector of the anchored statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
anchored_selector: StringLiteral,
|
anchored_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -339,7 +341,7 @@ impl SourceElement for Anchored {
|
||||||
impl Anchored {
|
impl Anchored {
|
||||||
/// Dissolves the [`Anchored`] into its components.
|
/// Dissolves the [`Anchored`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.anchored_keyword,
|
self.anchored_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -354,7 +356,7 @@ impl Anchored {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// AsAt:
|
/// AsAt:
|
||||||
/// 'asat' '(' StringLiteral ')'
|
/// 'asat' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -368,7 +370,7 @@ pub struct AsAt {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the asat statement.
|
/// The selector of the asat statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
asat_selector: StringLiteral,
|
asat_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -384,7 +386,7 @@ impl SourceElement for AsAt {
|
||||||
impl AsAt {
|
impl AsAt {
|
||||||
/// Dissolves the [`AsAt`] into its components.
|
/// Dissolves the [`AsAt`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.asat_keyword,
|
self.asat_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -399,7 +401,7 @@ impl AsAt {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// At:
|
/// At:
|
||||||
/// 'at' '(' StringLiteral ')'
|
/// 'at' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -413,7 +415,7 @@ pub struct At {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the at statement.
|
/// The selector of the at statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
at_selector: StringLiteral,
|
at_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -429,7 +431,7 @@ impl SourceElement for At {
|
||||||
impl At {
|
impl At {
|
||||||
/// Dissolves the [`At`] into its components.
|
/// Dissolves the [`At`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.at_keyword,
|
self.at_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -444,7 +446,7 @@ impl At {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Facing:
|
/// Facing:
|
||||||
/// 'facing' '(' StringLiteral ')'
|
/// 'facing' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -458,7 +460,7 @@ pub struct Facing {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the facing statement.
|
/// The selector of the facing statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
facing_selector: StringLiteral,
|
facing_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -474,7 +476,7 @@ impl SourceElement for Facing {
|
||||||
impl Facing {
|
impl Facing {
|
||||||
/// Dissolves the [`Facing`] into its components.
|
/// Dissolves the [`Facing`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.facing_keyword,
|
self.facing_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -489,7 +491,7 @@ impl Facing {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// In:
|
/// In:
|
||||||
/// 'in' '(' StringLiteral ')'
|
/// 'in' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -503,7 +505,7 @@ pub struct In {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the in statement.
|
/// The selector of the in statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
in_selector: StringLiteral,
|
in_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -519,7 +521,7 @@ impl SourceElement for In {
|
||||||
impl In {
|
impl In {
|
||||||
/// Dissolves the [`In`] into its components.
|
/// Dissolves the [`In`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.in_keyword,
|
self.in_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -534,7 +536,7 @@ impl In {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// On:
|
/// On:
|
||||||
/// 'on' '(' StringLiteral ')'
|
/// 'on' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -548,7 +550,7 @@ pub struct On {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the on statement.
|
/// The selector of the on statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
on_selector: StringLiteral,
|
on_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -564,7 +566,7 @@ impl SourceElement for On {
|
||||||
impl On {
|
impl On {
|
||||||
/// Dissolves the [`On`] into its components.
|
/// Dissolves the [`On`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.on_keyword,
|
self.on_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -579,7 +581,7 @@ impl On {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Positioned:
|
/// Positioned:
|
||||||
/// 'positioned' '(' StringLiteral ')'
|
/// 'positioned' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -593,7 +595,7 @@ pub struct Positioned {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the positioned statement.
|
/// The selector of the positioned statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
positioned_selector: StringLiteral,
|
positioned_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -609,7 +611,7 @@ impl SourceElement for Positioned {
|
||||||
impl Positioned {
|
impl Positioned {
|
||||||
/// Dissolves the [`Positioned`] into its components.
|
/// Dissolves the [`Positioned`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.positioned_keyword,
|
self.positioned_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -624,7 +626,7 @@ impl Positioned {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Rotated:
|
/// Rotated:
|
||||||
/// 'rotated' '(' StringLiteral ')'
|
/// 'rotated' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -638,7 +640,7 @@ pub struct Rotated {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the rotated statement.
|
/// The selector of the rotated statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
rotated_selector: StringLiteral,
|
rotated_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -654,7 +656,7 @@ impl SourceElement for Rotated {
|
||||||
impl Rotated {
|
impl Rotated {
|
||||||
/// Dissolves the [`Rotated`] into its components.
|
/// Dissolves the [`Rotated`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.rotated_keyword,
|
self.rotated_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -669,7 +671,7 @@ impl Rotated {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Store:
|
/// Store:
|
||||||
/// 'store' '(' StringLiteral ')'
|
/// 'store' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -683,7 +685,7 @@ pub struct Store {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the store statement.
|
/// The selector of the store statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
store_selector: StringLiteral,
|
store_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -699,7 +701,7 @@ impl SourceElement for Store {
|
||||||
impl Store {
|
impl Store {
|
||||||
/// Dissolves the [`Store`] into its components.
|
/// Dissolves the [`Store`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.store_keyword,
|
self.store_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -714,7 +716,7 @@ impl Store {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Summon:
|
/// Summon:
|
||||||
/// 'summon' '(' StringLiteral ')'
|
/// 'summon' '(' AnyStringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -728,7 +730,7 @@ pub struct Summon {
|
||||||
open_paren: Punctuation,
|
open_paren: Punctuation,
|
||||||
/// The selector of the summon statement.
|
/// The selector of the summon statement.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
summon_selector: StringLiteral,
|
summon_selector: AnyStringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -744,7 +746,7 @@ impl SourceElement for Summon {
|
||||||
impl Summon {
|
impl Summon {
|
||||||
/// Dissolves the [`Summon`] into its components.
|
/// Dissolves the [`Summon`] into its components.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, AnyStringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.summon_keyword,
|
self.summon_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -825,7 +827,7 @@ impl<'a> Parser<'a> {
|
||||||
let argument = match self.stop_at_significant() {
|
let argument = match self.stop_at_significant() {
|
||||||
Reading::IntoDelimited(punc) if punc.punctuation == '(' => self.step_into(
|
Reading::IntoDelimited(punc) if punc.punctuation == '(' => self.step_into(
|
||||||
Delimiter::Parenthesis,
|
Delimiter::Parenthesis,
|
||||||
|parser| parser.parse_string_literal(handler),
|
|parser| parser.parse_any_string_literal(handler),
|
||||||
handler,
|
handler,
|
||||||
),
|
),
|
||||||
unexpected => {
|
unexpected => {
|
||||||
|
@ -896,7 +898,7 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
fn head_from_keyword(
|
fn head_from_keyword(
|
||||||
keyword: Keyword,
|
keyword: Keyword,
|
||||||
argument: DelimitedTree<StringLiteral>,
|
argument: DelimitedTree<AnyStringLiteral>,
|
||||||
) -> ParseResult<ExecuteBlockHead> {
|
) -> ParseResult<ExecuteBlockHead> {
|
||||||
Ok(match keyword.keyword {
|
Ok(match keyword.keyword {
|
||||||
KeywordKind::Align => Align {
|
KeywordKind::Align => Align {
|
||||||
|
@ -986,3 +988,91 @@ fn head_from_keyword(
|
||||||
_ => unreachable!("The keyword is not a valid execute block head."),
|
_ => unreachable!("The keyword is not a valid execute block head."),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for the execute block head items with a [`AnyStringLiteral`] as their selector.
|
||||||
|
pub trait ExecuteBlockHeadItem {
|
||||||
|
/// Returns a reference to the selector of the execute block head item.
|
||||||
|
fn selector(&self) -> &AnyStringLiteral;
|
||||||
|
|
||||||
|
/// Analyzes the semantics of the execute block head item.
|
||||||
|
#[expect(clippy::missing_errors_doc)]
|
||||||
|
fn analyze_semantics(
|
||||||
|
&self,
|
||||||
|
macro_names: &HashSet<String>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> Result<(), crate::semantic::error::Error> {
|
||||||
|
self.selector().analyze_semantics(macro_names, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for Align {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.align_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for Anchored {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.anchored_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for As {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.as_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for At {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.at_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for AsAt {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.asat_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for Facing {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.facing_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for In {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.in_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for On {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.on_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for Positioned {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.positioned_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for Rotated {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.rotated_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for Store {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.store_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlockHeadItem for Summon {
|
||||||
|
fn selector(&self) -> &AnyStringLiteral {
|
||||||
|
&self.summon_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
|
//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
|
||||||
|
|
||||||
use shulkerbox::datapack::Condition as DpCondition;
|
use shulkerbox::{
|
||||||
|
datapack::Condition as DpCondition,
|
||||||
|
util::{MacroString, MacroStringPart},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::syntax::syntax_tree::condition::{
|
use crate::{
|
||||||
BinaryCondition, Condition, ConditionalBinaryOperator, ConditionalPrefixOperator,
|
lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
|
||||||
PrimaryCondition,
|
syntax::syntax_tree::{
|
||||||
|
condition::{
|
||||||
|
BinaryCondition, Condition, ConditionalBinaryOperator, ConditionalPrefixOperator,
|
||||||
|
PrimaryCondition,
|
||||||
|
},
|
||||||
|
AnyStringLiteral,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl From<Condition> for DpCondition {
|
impl From<Condition> for DpCondition {
|
||||||
|
@ -19,9 +28,7 @@ impl From<Condition> for DpCondition {
|
||||||
impl From<PrimaryCondition> for DpCondition {
|
impl From<PrimaryCondition> for DpCondition {
|
||||||
fn from(value: PrimaryCondition) -> Self {
|
fn from(value: PrimaryCondition) -> Self {
|
||||||
match value {
|
match value {
|
||||||
PrimaryCondition::StringLiteral(literal) => {
|
PrimaryCondition::StringLiteral(literal) => Self::Atom(literal.into()),
|
||||||
Self::Atom(literal.str_content().to_string())
|
|
||||||
}
|
|
||||||
PrimaryCondition::Parenthesized(cond) => cond.dissolve().1.into(),
|
PrimaryCondition::Parenthesized(cond) => cond.dissolve().1.into(),
|
||||||
PrimaryCondition::Unary(prefix) => match prefix.operator() {
|
PrimaryCondition::Unary(prefix) => match prefix.operator() {
|
||||||
ConditionalPrefixOperator::LogicalNot(_) => {
|
ConditionalPrefixOperator::LogicalNot(_) => {
|
||||||
|
@ -32,6 +39,56 @@ impl From<PrimaryCondition> for DpCondition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&AnyStringLiteral> for MacroString {
|
||||||
|
fn from(value: &AnyStringLiteral) -> Self {
|
||||||
|
match value {
|
||||||
|
AnyStringLiteral::StringLiteral(literal) => Self::from(literal.str_content().as_ref()),
|
||||||
|
AnyStringLiteral::MacroStringLiteral(literal) => Self::from(literal),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AnyStringLiteral> for MacroString {
|
||||||
|
fn from(value: AnyStringLiteral) -> Self {
|
||||||
|
Self::from(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&MacroStringLiteral> for MacroString {
|
||||||
|
fn from(value: &MacroStringLiteral) -> Self {
|
||||||
|
if value
|
||||||
|
.parts()
|
||||||
|
.iter()
|
||||||
|
.any(|p| matches!(p, MacroStringLiteralPart::MacroUsage { .. }))
|
||||||
|
{
|
||||||
|
Self::MacroString(
|
||||||
|
value
|
||||||
|
.parts()
|
||||||
|
.iter()
|
||||||
|
.map(|part| match part {
|
||||||
|
MacroStringLiteralPart::Text(span) => MacroStringPart::String(
|
||||||
|
crate::util::unescape_macro_string(span.str()).to_string(),
|
||||||
|
),
|
||||||
|
MacroStringLiteralPart::MacroUsage { identifier, .. } => {
|
||||||
|
MacroStringPart::MacroUsage(
|
||||||
|
super::util::identifier_to_macro(identifier.span.str()).to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Self::String(value.str_content())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MacroStringLiteral> for MacroString {
|
||||||
|
fn from(value: MacroStringLiteral) -> Self {
|
||||||
|
Self::from(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<BinaryCondition> for DpCondition {
|
impl From<BinaryCondition> for DpCondition {
|
||||||
fn from(value: BinaryCondition) -> Self {
|
fn from(value: BinaryCondition) -> Self {
|
||||||
let (lhs, op, rhs) = value.dissolve();
|
let (lhs, op, rhs) = value.dissolve();
|
||||||
|
|
|
@ -8,9 +8,9 @@ use itertools::Itertools;
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{
|
base::{
|
||||||
log::{Message, Severity, SourceCodeDisplay},
|
log::{Message, Severity, SourceCodeDisplay},
|
||||||
source_file::{SourceElement, Span},
|
source_file::Span,
|
||||||
},
|
},
|
||||||
syntax::syntax_tree::expression::Expression,
|
semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::FunctionData;
|
use super::FunctionData;
|
||||||
|
@ -29,6 +29,8 @@ pub enum TranspileError {
|
||||||
LuaRuntimeError(#[from] LuaRuntimeError),
|
LuaRuntimeError(#[from] LuaRuntimeError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ConflictingFunctionNames(#[from] ConflictingFunctionNames),
|
ConflictingFunctionNames(#[from] ConflictingFunctionNames),
|
||||||
|
#[error(transparent)]
|
||||||
|
InvalidFunctionArguments(#[from] InvalidFunctionArguments),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a transpilation operation.
|
/// The result of a transpilation operation.
|
||||||
|
@ -144,52 +146,3 @@ impl LuaRuntimeError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error that occurs when a function declaration is missing.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct UnexpectedExpression(pub Expression);
|
|
||||||
|
|
||||||
impl Display for UnexpectedExpression {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
Message::new(Severity::Error, "encountered unexpected expression")
|
|
||||||
)?;
|
|
||||||
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"\n{}",
|
|
||||||
SourceCodeDisplay::new(&self.0.span(), Option::<u8>::None)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for UnexpectedExpression {}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct ConflictingFunctionNames {
|
|
||||||
pub definition: Span,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ConflictingFunctionNames {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
Message::new(
|
|
||||||
Severity::Error,
|
|
||||||
format!("the following function declaration conflicts with an existing function with name `{}`", self.name)
|
|
||||||
)
|
|
||||||
)?;
|
|
||||||
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"\n{}",
|
|
||||||
SourceCodeDisplay::new(&self.definition, Option::<u8>::None)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for ConflictingFunctionNames {}
|
|
||||||
|
|
|
@ -20,13 +20,13 @@ mod transpiler;
|
||||||
#[cfg_attr(feature = "shulkerbox", doc(inline))]
|
#[cfg_attr(feature = "shulkerbox", doc(inline))]
|
||||||
pub use transpiler::Transpiler;
|
pub use transpiler::Transpiler;
|
||||||
|
|
||||||
#[cfg(feature = "shulkerbox")]
|
pub mod util;
|
||||||
mod util;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub(super) struct FunctionData {
|
pub(super) struct FunctionData {
|
||||||
pub(super) namespace: String,
|
pub(super) namespace: String,
|
||||||
pub(super) identifier_span: Span,
|
pub(super) identifier_span: Span,
|
||||||
|
pub(super) parameters: Vec<String>,
|
||||||
pub(super) statements: Vec<Statement>,
|
pub(super) statements: Vec<Statement>,
|
||||||
pub(super) public: bool,
|
pub(super) public: bool,
|
||||||
pub(super) annotations: HashMap<String, Option<String>>,
|
pub(super) annotations: HashMap<String, Option<String>>,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use chksum_md5 as md5;
|
use chksum_md5 as md5;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
iter,
|
ops::Deref,
|
||||||
};
|
};
|
||||||
|
|
||||||
use shulkerbox::datapack::{self, Command, Datapack, Execute};
|
use shulkerbox::datapack::{self, Command, Datapack, Execute};
|
||||||
|
@ -14,6 +14,7 @@ use crate::{
|
||||||
source_file::{SourceElement, Span},
|
source_file::{SourceElement, Span},
|
||||||
Handler,
|
Handler,
|
||||||
},
|
},
|
||||||
|
semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
|
||||||
syntax::syntax_tree::{
|
syntax::syntax_tree::{
|
||||||
declaration::{Declaration, ImportItems},
|
declaration::{Declaration, ImportItems},
|
||||||
expression::{Expression, FunctionCall, Primary},
|
expression::{Expression, FunctionCall, Primary},
|
||||||
|
@ -23,11 +24,11 @@ use crate::{
|
||||||
Statement,
|
Statement,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
transpile::error::{ConflictingFunctionNames, MissingFunctionDeclaration},
|
transpile::error::MissingFunctionDeclaration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
error::{TranspileError, TranspileResult, UnexpectedExpression},
|
error::{TranspileError, TranspileResult},
|
||||||
FunctionData,
|
FunctionData,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -97,7 +98,7 @@ impl Transpiler {
|
||||||
);
|
);
|
||||||
|
|
||||||
for identifier_span in always_transpile_functions {
|
for identifier_span in always_transpile_functions {
|
||||||
self.get_or_transpile_function(&identifier_span, handler)?;
|
self.get_or_transpile_function(&identifier_span, None, handler)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -122,7 +123,7 @@ impl Transpiler {
|
||||||
&mut self,
|
&mut self,
|
||||||
declaration: &Declaration,
|
declaration: &Declaration,
|
||||||
namespace: &Namespace,
|
namespace: &Namespace,
|
||||||
_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();
|
||||||
match declaration {
|
match declaration {
|
||||||
|
@ -148,6 +149,15 @@ impl Transpiler {
|
||||||
FunctionData {
|
FunctionData {
|
||||||
namespace: namespace.namespace_name().str_content().to_string(),
|
namespace: namespace.namespace_name().str_content().to_string(),
|
||||||
identifier_span: identifier_span.clone(),
|
identifier_span: identifier_span.clone(),
|
||||||
|
parameters: function
|
||||||
|
.parameters()
|
||||||
|
.as_ref()
|
||||||
|
.map(|l| {
|
||||||
|
l.elements()
|
||||||
|
.map(|i| i.span.str().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
statements,
|
statements,
|
||||||
public: function.is_public(),
|
public: function.is_public(),
|
||||||
annotations,
|
annotations,
|
||||||
|
@ -162,12 +172,13 @@ impl Transpiler {
|
||||||
let aliases = &mut self.aliases;
|
let aliases = &mut self.aliases;
|
||||||
|
|
||||||
match import.items() {
|
match import.items() {
|
||||||
ImportItems::All(_) => todo!("Importing all items is not yet supported."),
|
ImportItems::All(_) => {
|
||||||
|
handler.receive(base::Error::Other(
|
||||||
|
"Importing all items is not yet supported.".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
ImportItems::Named(list) => {
|
ImportItems::Named(list) => {
|
||||||
let items = iter::once(list.first())
|
for item in list.elements() {
|
||||||
.chain(list.rest().iter().map(|(_, ident)| ident));
|
|
||||||
|
|
||||||
for item in items {
|
|
||||||
let name = item.span.str();
|
let name = item.span.str();
|
||||||
aliases.insert(
|
aliases.insert(
|
||||||
(program_identifier.clone(), name.to_string()),
|
(program_identifier.clone(), name.to_string()),
|
||||||
|
@ -203,8 +214,9 @@ impl Transpiler {
|
||||||
fn get_or_transpile_function(
|
fn get_or_transpile_function(
|
||||||
&mut self,
|
&mut self,
|
||||||
identifier_span: &Span,
|
identifier_span: &Span,
|
||||||
|
arguments: Option<&[&Expression]>,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> TranspileResult<String> {
|
) -> TranspileResult<(String, Option<BTreeMap<String, String>>)> {
|
||||||
let program_identifier = identifier_span.source_file().identifier();
|
let program_identifier = identifier_span.source_file().identifier();
|
||||||
let program_query = (
|
let program_query = (
|
||||||
program_identifier.to_string(),
|
program_identifier.to_string(),
|
||||||
|
@ -244,6 +256,7 @@ impl Transpiler {
|
||||||
handler.receive(error.clone());
|
handler.receive(error.clone());
|
||||||
error
|
error
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
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, handler)?;
|
||||||
|
@ -315,10 +328,35 @@ impl Transpiler {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let locations = &self.function_locations;
|
let parameters = {
|
||||||
locations
|
let function_data = self
|
||||||
|
.functions
|
||||||
|
.get(&program_query)
|
||||||
|
.or_else(|| {
|
||||||
|
alias_query
|
||||||
|
.clone()
|
||||||
|
.and_then(|q| self.functions.get(&q).filter(|f| f.public))
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
let error = TranspileError::MissingFunctionDeclaration(
|
||||||
|
MissingFunctionDeclaration::from_context(
|
||||||
|
identifier_span.clone(),
|
||||||
|
&self.functions,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
handler.receive(error.clone());
|
||||||
|
error
|
||||||
|
})?;
|
||||||
|
|
||||||
|
function_data.parameters.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let function_location = self
|
||||||
|
.function_locations
|
||||||
.get(&program_query)
|
.get(&program_query)
|
||||||
.or_else(|| alias_query.and_then(|q| locations.get(&q).filter(|(_, p)| *p)))
|
.or_else(|| {
|
||||||
|
alias_query.and_then(|q| self.function_locations.get(&q).filter(|(_, p)| *p))
|
||||||
|
})
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
let error = TranspileError::MissingFunctionDeclaration(
|
let error = TranspileError::MissingFunctionDeclaration(
|
||||||
MissingFunctionDeclaration::from_context(
|
MissingFunctionDeclaration::from_context(
|
||||||
|
@ -329,7 +367,57 @@ impl Transpiler {
|
||||||
handler.receive(error.clone());
|
handler.receive(error.clone());
|
||||||
error
|
error
|
||||||
})
|
})
|
||||||
.map(|(s, _)| s.to_owned())
|
.map(|(s, _)| s.to_owned())?;
|
||||||
|
|
||||||
|
let arg_count = arguments.iter().flat_map(|x| x.iter()).count();
|
||||||
|
if arg_count != parameters.len() {
|
||||||
|
let err = TranspileError::InvalidFunctionArguments(InvalidFunctionArguments {
|
||||||
|
expected: parameters.len(),
|
||||||
|
actual: arg_count,
|
||||||
|
span: identifier_span.clone(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
Err(err)
|
||||||
|
} else if arg_count > 0 {
|
||||||
|
let mut compiled_args = Vec::new();
|
||||||
|
let mut errs = Vec::new();
|
||||||
|
for expression in arguments.iter().flat_map(|x| x.iter()) {
|
||||||
|
let value = match expression {
|
||||||
|
Expression::Primary(Primary::FunctionCall(func)) => self
|
||||||
|
.transpile_function_call(func, handler)
|
||||||
|
.map(|cmd| match cmd {
|
||||||
|
Command::Raw(s) => s,
|
||||||
|
_ => unreachable!("Function call should always return a raw command"),
|
||||||
|
}),
|
||||||
|
Expression::Primary(Primary::Lua(lua)) => {
|
||||||
|
lua.eval_string(handler).map(Option::unwrap_or_default)
|
||||||
|
}
|
||||||
|
Expression::Primary(Primary::StringLiteral(string)) => {
|
||||||
|
Ok(string.str_content().to_string())
|
||||||
|
}
|
||||||
|
Expression::Primary(Primary::MacroStringLiteral(literal)) => {
|
||||||
|
Ok(literal.str_content())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Ok(value) => {
|
||||||
|
compiled_args.push(value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
compiled_args.push(String::new());
|
||||||
|
errs.push(err.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(err) = errs.first() {
|
||||||
|
return Err(err.clone());
|
||||||
|
}
|
||||||
|
let function_args = parameters.into_iter().zip(compiled_args).collect();
|
||||||
|
Ok((function_location, Some(function_args)))
|
||||||
|
} else {
|
||||||
|
Ok((function_location, None))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transpile_function(
|
fn transpile_function(
|
||||||
|
@ -374,6 +462,9 @@ impl Transpiler {
|
||||||
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())))
|
||||||
}
|
}
|
||||||
|
Expression::Primary(Primary::MacroStringLiteral(string)) => {
|
||||||
|
Ok(Some(Command::UsesMacro(string.into())))
|
||||||
|
}
|
||||||
Expression::Primary(Primary::Lua(code)) => {
|
Expression::Primary(Primary::Lua(code)) => {
|
||||||
Ok(code.eval_string(handler)?.map(Command::Raw))
|
Ok(code.eval_string(handler)?.map(Command::Raw))
|
||||||
}
|
}
|
||||||
|
@ -431,8 +522,29 @@ impl Transpiler {
|
||||||
func: &FunctionCall,
|
func: &FunctionCall,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> TranspileResult<Command> {
|
) -> TranspileResult<Command> {
|
||||||
let location = self.get_or_transpile_function(&func.identifier().span, handler)?;
|
let arguments = func
|
||||||
Ok(Command::Raw(format!("function {location}")))
|
.arguments()
|
||||||
|
.as_ref()
|
||||||
|
.map(|l| l.elements().map(Deref::deref).collect::<Vec<_>>());
|
||||||
|
let (location, arguments) =
|
||||||
|
self.get_or_transpile_function(&func.identifier().span, arguments.as_deref(), handler)?;
|
||||||
|
let mut function_call = format!("function {location}");
|
||||||
|
if let Some(arguments) = arguments {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let arguments = arguments
|
||||||
|
.iter()
|
||||||
|
.map(|(ident, v)| {
|
||||||
|
format!(
|
||||||
|
r#"{macro_name}:"{escaped}""#,
|
||||||
|
macro_name = super::util::identifier_to_macro(ident),
|
||||||
|
escaped = crate::util::escape_str(v)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",");
|
||||||
|
write!(function_call, " {{{arguments}}}").unwrap();
|
||||||
|
}
|
||||||
|
Ok(Command::Raw(function_call))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transpile_execute_block(
|
fn transpile_execute_block(
|
||||||
|
@ -595,53 +707,53 @@ impl Transpiler {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::As(as_) => {
|
ExecuteBlockHead::As(r#as) => {
|
||||||
let selector = as_.as_selector().str_content();
|
let selector = r#as.as_selector();
|
||||||
tail.map(|tail| Execute::As(selector.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::As(selector.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::At(at) => {
|
ExecuteBlockHead::At(at) => {
|
||||||
let selector = at.at_selector().str_content();
|
let selector = at.at_selector();
|
||||||
tail.map(|tail| Execute::At(selector.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::At(selector.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Align(align) => {
|
ExecuteBlockHead::Align(align) => {
|
||||||
let align = align.align_selector().str_content();
|
let align = align.align_selector();
|
||||||
tail.map(|tail| Execute::Align(align.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::Align(align.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Anchored(anchored) => {
|
ExecuteBlockHead::Anchored(anchored) => {
|
||||||
let anchor = anchored.anchored_selector().str_content();
|
let anchor = anchored.anchored_selector();
|
||||||
tail.map(|tail| Execute::Anchored(anchor.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::Anchored(anchor.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::In(in_) => {
|
ExecuteBlockHead::In(r#in) => {
|
||||||
let dimension = in_.in_selector().str_content();
|
let dimension = r#in.in_selector();
|
||||||
tail.map(|tail| Execute::In(dimension.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::In(dimension.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Positioned(positioned) => {
|
ExecuteBlockHead::Positioned(positioned) => {
|
||||||
let position = positioned.positioned_selector().str_content();
|
let position = positioned.positioned_selector();
|
||||||
tail.map(|tail| Execute::Positioned(position.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::Positioned(position.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Rotated(rotated) => {
|
ExecuteBlockHead::Rotated(rotated) => {
|
||||||
let rotation = rotated.rotated_selector().str_content();
|
let rotation = rotated.rotated_selector();
|
||||||
tail.map(|tail| Execute::Rotated(rotation.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::Rotated(rotation.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Facing(facing) => {
|
ExecuteBlockHead::Facing(facing) => {
|
||||||
let facing = facing.facing_selector().str_content();
|
let facing = facing.facing_selector();
|
||||||
tail.map(|tail| Execute::Facing(facing.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::Facing(facing.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::AsAt(as_at) => {
|
ExecuteBlockHead::AsAt(as_at) => {
|
||||||
let selector = as_at.asat_selector().str_content();
|
let selector = as_at.asat_selector();
|
||||||
tail.map(|tail| Execute::AsAt(selector.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::AsAt(selector.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::On(on) => {
|
ExecuteBlockHead::On(on) => {
|
||||||
let dimension = on.on_selector().str_content();
|
let dimension = on.on_selector();
|
||||||
tail.map(|tail| Execute::On(dimension.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::On(dimension.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Store(store) => {
|
ExecuteBlockHead::Store(store) => {
|
||||||
let store = store.store_selector().str_content();
|
let store = store.store_selector();
|
||||||
tail.map(|tail| Execute::Store(store.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::Store(store.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Summon(summon) => {
|
ExecuteBlockHead::Summon(summon) => {
|
||||||
let entity = summon.summon_selector().str_content();
|
let entity = summon.summon_selector();
|
||||||
tail.map(|tail| Execute::Summon(entity.to_string(), Box::new(tail)))
|
tail.map(|tail| Execute::Summon(entity.into(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
//! Utility methods for transpiling
|
||||||
|
|
||||||
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
use chksum_md5 as md5;
|
||||||
|
|
||||||
fn normalize_program_identifier<S>(identifier: S) -> String
|
fn normalize_program_identifier<S>(identifier: S) -> String
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
@ -19,6 +24,8 @@ where
|
||||||
.join("/")
|
.join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate the identifier to import the function based on the current identifier and the import path
|
||||||
|
#[must_use]
|
||||||
pub fn calculate_import_identifier<S, T>(current_identifier: S, import_path: T) -> String
|
pub fn calculate_import_identifier<S, T>(current_identifier: S, import_path: T) -> String
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
@ -32,3 +39,25 @@ where
|
||||||
normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref())
|
normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transforms an identifier to a macro name that only contains `a-zA-Z0-9_`.
|
||||||
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
#[must_use]
|
||||||
|
pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<str> {
|
||||||
|
if ident.contains("__")
|
||||||
|
|| ident
|
||||||
|
.chars()
|
||||||
|
.any(|c| !(c == '_' && c.is_ascii_alphanumeric()))
|
||||||
|
{
|
||||||
|
let new_ident = ident
|
||||||
|
.chars()
|
||||||
|
.filter(|c| *c == '_' || c.is_ascii_alphanumeric())
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let chksum = md5::hash(ident).to_hex_lowercase();
|
||||||
|
|
||||||
|
std::borrow::Cow::Owned(new_ident + "__" + &chksum[..8])
|
||||||
|
} else {
|
||||||
|
std::borrow::Cow::Borrowed(ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
//! Utility functions for the `Shulkerscript` language.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
/// Escapes `"` and `\` in a string.
|
||||||
|
#[must_use]
|
||||||
|
pub fn escape_str(s: &str) -> Cow<str> {
|
||||||
|
if s.contains('"') || s.contains('\\') {
|
||||||
|
let mut escaped = String::with_capacity(s.len());
|
||||||
|
for c in s.chars() {
|
||||||
|
match c {
|
||||||
|
'"' => escaped.push_str("\\\""),
|
||||||
|
'\\' => escaped.push_str("\\\\"),
|
||||||
|
_ => escaped.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Cow::Owned(escaped)
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unescapes '\`', `\`, `\n`, `\r` and `\t` in a string.
|
||||||
|
#[must_use]
|
||||||
|
pub fn unescape_macro_string(s: &str) -> Cow<str> {
|
||||||
|
if s.contains('\\') || s.contains('`') {
|
||||||
|
Cow::Owned(
|
||||||
|
s.replace("\\n", "\n")
|
||||||
|
.replace("\\r", "\r")
|
||||||
|
.replace("\\t", "\t")
|
||||||
|
.replace("\\`", "`")
|
||||||
|
.replace("\\\\", "\\"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_escape_str() {
|
||||||
|
assert_eq!(escape_str("Hello, world!"), "Hello, world!");
|
||||||
|
assert_eq!(escape_str(r#"Hello, "world"!"#), r#"Hello, \"world\"!"#);
|
||||||
|
assert_eq!(escape_str(r"Hello, \world\!"), r"Hello, \\world\\!");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unescape_macro_string() {
|
||||||
|
assert_eq!(unescape_macro_string("Hello, world!"), "Hello, world!");
|
||||||
|
assert_eq!(
|
||||||
|
unescape_macro_string(r#"Hello, "world"!"#),
|
||||||
|
r#"Hello, "world"!"#
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unescape_macro_string(r"Hello, \world\!"),
|
||||||
|
r"Hello, \world\!"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unescape_macro_string(r"Hello, \nworld\!"),
|
||||||
|
"Hello, \nworld\\!"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unescape_macro_string(r"Hello, \rworld\!"),
|
||||||
|
"Hello, \rworld\\!"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unescape_macro_string(r"Hello, \tworld\!"),
|
||||||
|
"Hello, \tworld\\!"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unescape_macro_string(r"Hello, \`world\!"),
|
||||||
|
r"Hello, `world\!"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unescape_macro_string(r"Hello, \\world\!"),
|
||||||
|
r"Hello, \world\!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,9 +28,9 @@ fn transpile_test1() {
|
||||||
main_fn.add_command(Command::Raw("say Hello, World!".to_string()));
|
main_fn.add_command(Command::Raw("say Hello, World!".to_string()));
|
||||||
|
|
||||||
let exec_cmd = Command::Execute(Execute::As(
|
let exec_cmd = Command::Execute(Execute::As(
|
||||||
"@a".to_string(),
|
"@a".to_string().into(),
|
||||||
Box::new(Execute::If(
|
Box::new(Execute::If(
|
||||||
Condition::Atom("entity @p[distance=..5]".to_string()),
|
Condition::Atom("entity @p[distance=..5]".to_string().into()),
|
||||||
Box::new(Execute::Run(Box::new(Command::Raw(
|
Box::new(Execute::Run(Box::new(Command::Raw(
|
||||||
"say You are close to me!".to_string(),
|
"say You are close to me!".to_string(),
|
||||||
)))),
|
)))),
|
||||||
|
|
Loading…
Reference in New Issue