Compare commits
No commits in common. "96fe865ac1dcc72db7e98c197c21bb7e002ebf0b" and "f962f5c36b13c36f0380dc63e9bf177a0e2d126d" have entirely different histories.
96fe865ac1
...
f962f5c36b
|
@ -9,9 +9,6 @@ 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,8 +37,7 @@ 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,21 +12,6 @@ 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;
|
||||||
|
@ -102,7 +87,7 @@ Condition:
|
||||||
PrimaryCondition:
|
PrimaryCondition:
|
||||||
ConditionalPrefix
|
ConditionalPrefix
|
||||||
| ParenthesizedCondition
|
| ParenthesizedCondition
|
||||||
| AnyStringLiteral
|
| StringLiteral
|
||||||
;
|
;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -159,8 +144,6 @@ Expression:
|
||||||
```ebnf
|
```ebnf
|
||||||
Primary:
|
Primary:
|
||||||
FunctionCall
|
FunctionCall
|
||||||
| AnyStringLiteral
|
|
||||||
| LuaCode
|
|
||||||
;
|
;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,6 @@ 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,26 +254,6 @@ 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,7 +4,6 @@ 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,
|
||||||
};
|
};
|
||||||
|
@ -146,7 +145,24 @@ 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 {
|
||||||
|
@ -161,7 +177,6 @@ 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(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,76 +290,6 @@ 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)]
|
||||||
|
@ -417,7 +362,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, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error, From)]
|
||||||
#[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.")]
|
||||||
|
@ -425,95 +370,8 @@ 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) {
|
||||||
|
@ -527,7 +385,6 @@ 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(),
|
||||||
|
@ -535,26 +392,6 @@ 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 == '_'
|
||||||
|
@ -714,113 +551,6 @@ 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()));
|
||||||
|
@ -862,15 +592,9 @@ 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,13 +5,10 @@ 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::{
|
use crate::base::{
|
||||||
base::{
|
self,
|
||||||
self,
|
source_file::{SourceElement, SourceFile, Span},
|
||||||
source_file::{SourceElement, SourceFile, Span},
|
Handler,
|
||||||
Handler,
|
|
||||||
},
|
|
||||||
lexical::Error,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -65,17 +62,6 @@ 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)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +184,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(),
|
Self::Token(token) => token.span().to_owned(),
|
||||||
Self::Delimited(delimited) => delimited
|
Self::Delimited(delimited) => delimited
|
||||||
.open
|
.open
|
||||||
.span()
|
.span()
|
||||||
|
|
|
@ -17,10 +17,8 @@ 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;
|
||||||
|
@ -108,8 +106,6 @@ pub fn parse(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
program.analyze_semantics(handler)?;
|
|
||||||
|
|
||||||
Ok(program)
|
Ok(program)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,286 +0,0 @@
|
||||||
//! 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 {}
|
|
|
@ -1,574 +0,0 @@
|
||||||
//! 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::{SourceElement as _, Span},
|
source_file::Span,
|
||||||
},
|
},
|
||||||
lexical::token::{KeywordKind, Token},
|
lexical::token::{KeywordKind, Token},
|
||||||
};
|
};
|
||||||
|
@ -34,8 +34,6 @@ pub enum SyntaxKind {
|
||||||
Declaration,
|
Declaration,
|
||||||
Numeric,
|
Numeric,
|
||||||
StringLiteral,
|
StringLiteral,
|
||||||
MacroStringLiteral,
|
|
||||||
AnyStringLiteral,
|
|
||||||
Statement,
|
Statement,
|
||||||
Expression,
|
Expression,
|
||||||
Type,
|
Type,
|
||||||
|
@ -71,8 +69,6 @@ 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(),
|
||||||
|
@ -109,7 +105,6 @@ 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(),
|
||||||
};
|
};
|
||||||
|
@ -122,7 +117,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,18 +6,12 @@ use enum_as_inner::EnumAsInner;
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{self, Handler},
|
base::{self, Handler},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{
|
token::{Identifier, Keyword, KeywordKind, Numeric, Punctuation, StringLiteral, Token},
|
||||||
Identifier, Keyword, KeywordKind, MacroStringLiteral, Numeric, Punctuation,
|
|
||||||
StringLiteral, Token,
|
|
||||||
},
|
|
||||||
token_stream::{Delimited, Delimiter, TokenStream, TokenTree},
|
token_stream::{Delimited, Delimiter, TokenStream, TokenTree},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax};
|
||||||
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)]
|
||||||
|
@ -438,49 +432,6 @@ 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, Token},
|
token::{Punctuation, StringLiteral, Token},
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::{
|
syntax::{
|
||||||
|
@ -23,8 +23,6 @@ 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:
|
||||||
|
@ -33,7 +31,7 @@ use super::AnyStringLiteral;
|
||||||
/// PrimaryCondition:
|
/// PrimaryCondition:
|
||||||
/// UnaryCondition
|
/// UnaryCondition
|
||||||
/// | ParenthesizedCondition
|
/// | ParenthesizedCondition
|
||||||
/// | AnyStringLiteral
|
/// | StringLiteral
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -41,7 +39,7 @@ use super::AnyStringLiteral;
|
||||||
pub enum PrimaryCondition {
|
pub enum PrimaryCondition {
|
||||||
Unary(UnaryCondition),
|
Unary(UnaryCondition),
|
||||||
Parenthesized(ParenthesizedCondition),
|
Parenthesized(ParenthesizedCondition),
|
||||||
StringLiteral(AnyStringLiteral),
|
StringLiteral(StringLiteral),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for PrimaryCondition {
|
impl SourceElement for PrimaryCondition {
|
||||||
|
@ -356,13 +354,7 @@ 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.into()))
|
Ok(PrimaryCondition::StringLiteral(literal))
|
||||||
}
|
|
||||||
|
|
||||||
// macro string literal
|
|
||||||
Reading::Atomic(Token::MacroStringLiteral(literal)) => {
|
|
||||||
self.forward();
|
|
||||||
Ok(PrimaryCondition::StringLiteral(literal.into()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parenthesized condition
|
// parenthesized condition
|
||||||
|
|
|
@ -10,9 +10,7 @@ use crate::{
|
||||||
Handler,
|
Handler,
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{
|
token::{Identifier, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
|
||||||
Identifier, Keyword, KeywordKind, MacroStringLiteral, Punctuation, StringLiteral, Token,
|
|
||||||
},
|
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::{
|
syntax::{
|
||||||
|
@ -54,9 +52,6 @@ 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))]
|
||||||
|
@ -64,7 +59,6 @@ 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>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +67,6 @@ 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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,7 +180,6 @@ 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
|
||||||
|
@ -232,14 +224,6 @@ 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 =>
|
||||||
|
@ -283,11 +267,10 @@ impl<'a> Parser<'a> {
|
||||||
let combined = first
|
let combined = first
|
||||||
.into_token()
|
.into_token()
|
||||||
.and_then(|first| {
|
.and_then(|first| {
|
||||||
first.span().join(
|
first.span().join(&last.into_token().map_or_else(
|
||||||
&last
|
|| first.span().to_owned(),
|
||||||
.into_token()
|
|last| last.span().to_owned(),
|
||||||
.map_or_else(|| first.span(), |last| last.span()),
|
))
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.expect("Invalid lua code span");
|
.expect("Invalid lua code span");
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
//! 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::{
|
||||||
|
@ -10,7 +9,7 @@ use crate::{
|
||||||
Handler, VoidHandler,
|
Handler, VoidHandler,
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{MacroStringLiteral, Punctuation, StringLiteral, Token},
|
token::{Punctuation, Token},
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::parser::Reading,
|
syntax::parser::Reading,
|
||||||
|
@ -65,29 +64,6 @@ 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::{ParseResult, SyntaxKind, UnexpectedSyntax},
|
error::{InvalidArgument, ParseResult, SyntaxKind, UnexpectedSyntax},
|
||||||
parser::{Parser, Reading},
|
parser::{Parser, Reading},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -108,7 +108,22 @@ impl<'a> Parser<'a> {
|
||||||
// eat the keyword
|
// eat the keyword
|
||||||
self.forward();
|
self.forward();
|
||||||
|
|
||||||
let namespace_name = self.parse_string_literal(handler)?;
|
let namespace_name = self.parse_string_literal(handler).and_then(|name| {
|
||||||
|
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,7 +1,5 @@
|
||||||
//! 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;
|
||||||
|
@ -13,13 +11,13 @@ use crate::{
|
||||||
Handler, VoidHandler,
|
Handler, VoidHandler,
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{Keyword, KeywordKind, Punctuation, Token},
|
token::{Keyword, KeywordKind, Punctuation, StringLiteral, 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, AnyStringLiteral},
|
syntax_tree::condition::ParenthesizedCondition,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -219,7 +217,7 @@ impl SourceElement for Else {
|
||||||
///
|
///
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// As:
|
/// As:
|
||||||
/// 'as' '(' AnyStringLiteral ')'
|
/// 'as' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -233,7 +231,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: AnyStringLiteral,
|
as_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -250,7 +248,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.as_keyword,
|
self.as_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -265,7 +263,7 @@ impl As {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Align:
|
/// Align:
|
||||||
/// 'align' '(' AnyStringLiteral ')'
|
/// 'align' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
#[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)]
|
||||||
|
@ -278,7 +276,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: AnyStringLiteral,
|
align_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -296,7 +294,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.align_keyword,
|
self.align_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -311,7 +309,7 @@ impl Align {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Anchored:
|
/// Anchored:
|
||||||
/// 'anchored' '(' AnyStringLiteral ')'
|
/// 'anchored' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -325,7 +323,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: AnyStringLiteral,
|
anchored_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -341,7 +339,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.anchored_keyword,
|
self.anchored_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -356,7 +354,7 @@ impl Anchored {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// AsAt:
|
/// AsAt:
|
||||||
/// 'asat' '(' AnyStringLiteral ')'
|
/// 'asat' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -370,7 +368,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: AnyStringLiteral,
|
asat_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -386,7 +384,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.asat_keyword,
|
self.asat_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -401,7 +399,7 @@ impl AsAt {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// At:
|
/// At:
|
||||||
/// 'at' '(' AnyStringLiteral ')'
|
/// 'at' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -415,7 +413,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: AnyStringLiteral,
|
at_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -431,7 +429,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.at_keyword,
|
self.at_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -446,7 +444,7 @@ impl At {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Facing:
|
/// Facing:
|
||||||
/// 'facing' '(' AnyStringLiteral ')'
|
/// 'facing' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -460,7 +458,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: AnyStringLiteral,
|
facing_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -476,7 +474,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.facing_keyword,
|
self.facing_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -491,7 +489,7 @@ impl Facing {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// In:
|
/// In:
|
||||||
/// 'in' '(' AnyStringLiteral ')'
|
/// 'in' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -505,7 +503,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: AnyStringLiteral,
|
in_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -521,7 +519,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.in_keyword,
|
self.in_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -536,7 +534,7 @@ impl In {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// On:
|
/// On:
|
||||||
/// 'on' '(' AnyStringLiteral ')'
|
/// 'on' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -550,7 +548,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: AnyStringLiteral,
|
on_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -566,7 +564,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.on_keyword,
|
self.on_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -581,7 +579,7 @@ impl On {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Positioned:
|
/// Positioned:
|
||||||
/// 'positioned' '(' AnyStringLiteral ')'
|
/// 'positioned' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -595,7 +593,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: AnyStringLiteral,
|
positioned_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -611,7 +609,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.positioned_keyword,
|
self.positioned_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -626,7 +624,7 @@ impl Positioned {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Rotated:
|
/// Rotated:
|
||||||
/// 'rotated' '(' AnyStringLiteral ')'
|
/// 'rotated' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -640,7 +638,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: AnyStringLiteral,
|
rotated_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -656,7 +654,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.rotated_keyword,
|
self.rotated_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -671,7 +669,7 @@ impl Rotated {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Store:
|
/// Store:
|
||||||
/// 'store' '(' AnyStringLiteral ')'
|
/// 'store' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -685,7 +683,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: AnyStringLiteral,
|
store_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -701,7 +699,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.store_keyword,
|
self.store_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -716,7 +714,7 @@ impl Store {
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// Summon:
|
/// Summon:
|
||||||
/// 'summon' '(' AnyStringLiteral ')'
|
/// 'summon' '(' StringLiteral ')'
|
||||||
/// ;
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
@ -730,7 +728,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: AnyStringLiteral,
|
summon_selector: StringLiteral,
|
||||||
/// The close parenthesis.
|
/// The close parenthesis.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
close_paren: Punctuation,
|
close_paren: Punctuation,
|
||||||
|
@ -746,7 +744,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, AnyStringLiteral, Punctuation) {
|
pub fn dissolve(self) -> (Keyword, Punctuation, StringLiteral, Punctuation) {
|
||||||
(
|
(
|
||||||
self.summon_keyword,
|
self.summon_keyword,
|
||||||
self.open_paren,
|
self.open_paren,
|
||||||
|
@ -827,7 +825,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_any_string_literal(handler),
|
|parser| parser.parse_string_literal(handler),
|
||||||
handler,
|
handler,
|
||||||
),
|
),
|
||||||
unexpected => {
|
unexpected => {
|
||||||
|
@ -898,7 +896,7 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
fn head_from_keyword(
|
fn head_from_keyword(
|
||||||
keyword: Keyword,
|
keyword: Keyword,
|
||||||
argument: DelimitedTree<AnyStringLiteral>,
|
argument: DelimitedTree<StringLiteral>,
|
||||||
) -> ParseResult<ExecuteBlockHead> {
|
) -> ParseResult<ExecuteBlockHead> {
|
||||||
Ok(match keyword.keyword {
|
Ok(match keyword.keyword {
|
||||||
KeywordKind::Align => Align {
|
KeywordKind::Align => Align {
|
||||||
|
@ -988,91 +986,3 @@ 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,19 +1,10 @@
|
||||||
//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
|
//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
|
||||||
|
|
||||||
use shulkerbox::{
|
use shulkerbox::datapack::Condition as DpCondition;
|
||||||
datapack::Condition as DpCondition,
|
|
||||||
util::{MacroString, MacroStringPart},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::syntax::syntax_tree::condition::{
|
||||||
lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
|
BinaryCondition, Condition, ConditionalBinaryOperator, ConditionalPrefixOperator,
|
||||||
syntax::syntax_tree::{
|
PrimaryCondition,
|
||||||
condition::{
|
|
||||||
BinaryCondition, Condition, ConditionalBinaryOperator, ConditionalPrefixOperator,
|
|
||||||
PrimaryCondition,
|
|
||||||
},
|
|
||||||
AnyStringLiteral,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl From<Condition> for DpCondition {
|
impl From<Condition> for DpCondition {
|
||||||
|
@ -28,7 +19,9 @@ 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) => Self::Atom(literal.into()),
|
PrimaryCondition::StringLiteral(literal) => {
|
||||||
|
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(_) => {
|
||||||
|
@ -39,56 +32,6 @@ 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::Span,
|
source_file::{SourceElement, Span},
|
||||||
},
|
},
|
||||||
semantic::error::{ConflictingFunctionNames, InvalidFunctionArguments, UnexpectedExpression},
|
syntax::syntax_tree::expression::Expression,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::FunctionData;
|
use super::FunctionData;
|
||||||
|
@ -29,8 +29,6 @@ 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.
|
||||||
|
@ -146,3 +144,52 @@ 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;
|
||||||
|
|
||||||
pub mod util;
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
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},
|
||||||
ops::Deref,
|
iter,
|
||||||
};
|
};
|
||||||
|
|
||||||
use shulkerbox::datapack::{self, Command, Datapack, Execute};
|
use shulkerbox::datapack::{self, Command, Datapack, Execute};
|
||||||
|
@ -14,7 +14,6 @@ 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},
|
||||||
|
@ -24,11 +23,11 @@ use crate::{
|
||||||
Statement,
|
Statement,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
transpile::error::MissingFunctionDeclaration,
|
transpile::error::{ConflictingFunctionNames, MissingFunctionDeclaration},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
error::{TranspileError, TranspileResult},
|
error::{TranspileError, TranspileResult, UnexpectedExpression},
|
||||||
FunctionData,
|
FunctionData,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,7 +97,7 @@ impl Transpiler {
|
||||||
);
|
);
|
||||||
|
|
||||||
for identifier_span in always_transpile_functions {
|
for identifier_span in always_transpile_functions {
|
||||||
self.get_or_transpile_function(&identifier_span, None, handler)?;
|
self.get_or_transpile_function(&identifier_span, handler)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -123,7 +122,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 {
|
||||||
|
@ -149,15 +148,6 @@ 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,
|
||||||
|
@ -172,13 +162,12 @@ impl Transpiler {
|
||||||
let aliases = &mut self.aliases;
|
let aliases = &mut self.aliases;
|
||||||
|
|
||||||
match import.items() {
|
match import.items() {
|
||||||
ImportItems::All(_) => {
|
ImportItems::All(_) => todo!("Importing all items is not yet supported."),
|
||||||
handler.receive(base::Error::Other(
|
|
||||||
"Importing all items is not yet supported.".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
ImportItems::Named(list) => {
|
ImportItems::Named(list) => {
|
||||||
for item in list.elements() {
|
let items = iter::once(list.first())
|
||||||
|
.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()),
|
||||||
|
@ -214,9 +203,8 @@ 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, Option<BTreeMap<String, String>>)> {
|
) -> TranspileResult<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(),
|
||||||
|
@ -256,7 +244,6 @@ 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)?;
|
||||||
|
@ -328,35 +315,10 @@ impl Transpiler {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let parameters = {
|
let locations = &self.function_locations;
|
||||||
let function_data = self
|
locations
|
||||||
.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(|| {
|
.or_else(|| alias_query.and_then(|q| locations.get(&q).filter(|(_, p)| *p)))
|
||||||
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(
|
||||||
|
@ -367,57 +329,7 @@ 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(
|
||||||
|
@ -462,9 +374,6 @@ 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))
|
||||||
}
|
}
|
||||||
|
@ -522,29 +431,8 @@ impl Transpiler {
|
||||||
func: &FunctionCall,
|
func: &FunctionCall,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> TranspileResult<Command> {
|
) -> TranspileResult<Command> {
|
||||||
let arguments = func
|
let location = self.get_or_transpile_function(&func.identifier().span, handler)?;
|
||||||
.arguments()
|
Ok(Command::Raw(format!("function {location}")))
|
||||||
.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(
|
||||||
|
@ -707,53 +595,53 @@ impl Transpiler {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::As(r#as) => {
|
ExecuteBlockHead::As(as_) => {
|
||||||
let selector = r#as.as_selector();
|
let selector = as_.as_selector().str_content();
|
||||||
tail.map(|tail| Execute::As(selector.into(), Box::new(tail)))
|
tail.map(|tail| Execute::As(selector.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::At(at) => {
|
ExecuteBlockHead::At(at) => {
|
||||||
let selector = at.at_selector();
|
let selector = at.at_selector().str_content();
|
||||||
tail.map(|tail| Execute::At(selector.into(), Box::new(tail)))
|
tail.map(|tail| Execute::At(selector.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Align(align) => {
|
ExecuteBlockHead::Align(align) => {
|
||||||
let align = align.align_selector();
|
let align = align.align_selector().str_content();
|
||||||
tail.map(|tail| Execute::Align(align.into(), Box::new(tail)))
|
tail.map(|tail| Execute::Align(align.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Anchored(anchored) => {
|
ExecuteBlockHead::Anchored(anchored) => {
|
||||||
let anchor = anchored.anchored_selector();
|
let anchor = anchored.anchored_selector().str_content();
|
||||||
tail.map(|tail| Execute::Anchored(anchor.into(), Box::new(tail)))
|
tail.map(|tail| Execute::Anchored(anchor.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::In(r#in) => {
|
ExecuteBlockHead::In(in_) => {
|
||||||
let dimension = r#in.in_selector();
|
let dimension = in_.in_selector().str_content();
|
||||||
tail.map(|tail| Execute::In(dimension.into(), Box::new(tail)))
|
tail.map(|tail| Execute::In(dimension.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Positioned(positioned) => {
|
ExecuteBlockHead::Positioned(positioned) => {
|
||||||
let position = positioned.positioned_selector();
|
let position = positioned.positioned_selector().str_content();
|
||||||
tail.map(|tail| Execute::Positioned(position.into(), Box::new(tail)))
|
tail.map(|tail| Execute::Positioned(position.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Rotated(rotated) => {
|
ExecuteBlockHead::Rotated(rotated) => {
|
||||||
let rotation = rotated.rotated_selector();
|
let rotation = rotated.rotated_selector().str_content();
|
||||||
tail.map(|tail| Execute::Rotated(rotation.into(), Box::new(tail)))
|
tail.map(|tail| Execute::Rotated(rotation.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Facing(facing) => {
|
ExecuteBlockHead::Facing(facing) => {
|
||||||
let facing = facing.facing_selector();
|
let facing = facing.facing_selector().str_content();
|
||||||
tail.map(|tail| Execute::Facing(facing.into(), Box::new(tail)))
|
tail.map(|tail| Execute::Facing(facing.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::AsAt(as_at) => {
|
ExecuteBlockHead::AsAt(as_at) => {
|
||||||
let selector = as_at.asat_selector();
|
let selector = as_at.asat_selector().str_content();
|
||||||
tail.map(|tail| Execute::AsAt(selector.into(), Box::new(tail)))
|
tail.map(|tail| Execute::AsAt(selector.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::On(on) => {
|
ExecuteBlockHead::On(on) => {
|
||||||
let dimension = on.on_selector();
|
let dimension = on.on_selector().str_content();
|
||||||
tail.map(|tail| Execute::On(dimension.into(), Box::new(tail)))
|
tail.map(|tail| Execute::On(dimension.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Store(store) => {
|
ExecuteBlockHead::Store(store) => {
|
||||||
let store = store.store_selector();
|
let store = store.store_selector().str_content();
|
||||||
tail.map(|tail| Execute::Store(store.into(), Box::new(tail)))
|
tail.map(|tail| Execute::Store(store.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Summon(summon) => {
|
ExecuteBlockHead::Summon(summon) => {
|
||||||
let entity = summon.summon_selector();
|
let entity = summon.summon_selector().str_content();
|
||||||
tail.map(|tail| Execute::Summon(entity.into(), Box::new(tail)))
|
tail.map(|tail| Execute::Summon(entity.to_string(), Box::new(tail)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
//! 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>,
|
||||||
|
@ -24,8 +19,6 @@ 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>,
|
||||||
|
@ -39,25 +32,3 @@ where
|
||||||
normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref())
|
normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transforms an identifier to a macro name that only contains `a-zA-Z0-9_`.
|
|
||||||
#[cfg(feature = "shulkerbox")]
|
|
||||||
#[must_use]
|
|
||||||
pub fn identifier_to_macro(ident: &str) -> std::borrow::Cow<str> {
|
|
||||||
if ident.contains("__")
|
|
||||||
|| ident
|
|
||||||
.chars()
|
|
||||||
.any(|c| !(c == '_' && c.is_ascii_alphanumeric()))
|
|
||||||
{
|
|
||||||
let new_ident = ident
|
|
||||||
.chars()
|
|
||||||
.filter(|c| *c == '_' || c.is_ascii_alphanumeric())
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
let chksum = md5::hash(ident).to_hex_lowercase();
|
|
||||||
|
|
||||||
std::borrow::Cow::Owned(new_ident + "__" + &chksum[..8])
|
|
||||||
} else {
|
|
||||||
std::borrow::Cow::Borrowed(ident)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
82
src/util.rs
82
src/util.rs
|
@ -1,82 +0,0 @@
|
||||||
//! 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().into(),
|
"@a".to_string(),
|
||||||
Box::new(Execute::If(
|
Box::new(Execute::If(
|
||||||
Condition::Atom("entity @p[distance=..5]".to_string().into()),
|
Condition::Atom("entity @p[distance=..5]".to_string()),
|
||||||
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