Compare commits
3 Commits
3bc3ca180f
...
b7d50f8222
Author | SHA1 | Date |
---|---|---|
|
b7d50f8222 | |
|
183d3e85c6 | |
|
08ed56b673 |
17
grammar.md
17
grammar.md
|
@ -170,7 +170,7 @@ VariableDeclarationAssignment:
|
||||||
## AnyStringLiteral
|
## AnyStringLiteral
|
||||||
|
|
||||||
```ebnf
|
```ebnf
|
||||||
AnyStringLiteral: StringLiteral | MacroStringLiteral ;
|
AnyStringLiteral: StringLiteral | TemplateStringLiteral ;
|
||||||
```
|
```
|
||||||
|
|
||||||
## AnnotationValue
|
## AnnotationValue
|
||||||
|
@ -230,11 +230,11 @@ Expression:
|
||||||
Primary | Binary ;
|
Primary | Binary ;
|
||||||
```
|
```
|
||||||
|
|
||||||
## MacroStringLiteral
|
## TemplateStringLiteral
|
||||||
|
|
||||||
```ebnf
|
```ebnf
|
||||||
MacroStringLiteral:
|
TemplateStringLiteral:
|
||||||
'`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
|
'`' ( TemplateStringLiteralText | '$(' Expression ')' )* '`';
|
||||||
```
|
```
|
||||||
|
|
||||||
## Else
|
## Else
|
||||||
|
@ -304,10 +304,17 @@ Primary:
|
||||||
| StringLiteral
|
| StringLiteral
|
||||||
| FunctionCall
|
| FunctionCall
|
||||||
| MemberAccess
|
| MemberAccess
|
||||||
| MacroStringLiteral
|
| TemplateStringLiteral
|
||||||
| LuaCode
|
| LuaCode
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## TemplateStringLiteralText
|
||||||
|
|
||||||
|
```ebnf
|
||||||
|
TemplateStringLiteralText:
|
||||||
|
TEXT ;
|
||||||
|
```
|
||||||
|
|
||||||
## Align
|
## Align
|
||||||
|
|
||||||
```ebnf
|
```ebnf
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
iter::{Iterator, Peekable},
|
iter::Iterator,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::CharIndices,
|
str::CharIndices,
|
||||||
|
@ -11,6 +11,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use getset::{CopyGetters, Getters};
|
use getset::{CopyGetters, Getters};
|
||||||
|
use itertools::{structs::MultiPeek, Itertools as _};
|
||||||
|
|
||||||
use super::{file_provider::FileProvider, Error};
|
use super::{file_provider::FileProvider, Error};
|
||||||
|
|
||||||
|
@ -72,8 +73,9 @@ impl SourceFile {
|
||||||
pub fn iter(self: &Arc<Self>) -> SourceIterator<'_> {
|
pub fn iter(self: &Arc<Self>) -> SourceIterator<'_> {
|
||||||
SourceIterator {
|
SourceIterator {
|
||||||
source_file: self,
|
source_file: self,
|
||||||
iterator: self.content().char_indices().peekable(),
|
iterator: self.content().char_indices().multipeek(),
|
||||||
prev: None,
|
prev: None,
|
||||||
|
in_template_string_expression_open_count: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,16 +339,69 @@ pub struct SourceIterator<'a> {
|
||||||
/// Get the source file that the iterator is iterating over.
|
/// Get the source file that the iterator is iterating over.
|
||||||
#[get_copy = "pub"]
|
#[get_copy = "pub"]
|
||||||
source_file: &'a Arc<SourceFile>,
|
source_file: &'a Arc<SourceFile>,
|
||||||
iterator: Peekable<CharIndices<'a>>,
|
iterator: MultiPeek<CharIndices<'a>>,
|
||||||
/// Get the previous character that was iterated over.
|
/// Get the previous character that was iterated over.
|
||||||
#[get_copy = "pub"]
|
#[get_copy = "pub"]
|
||||||
prev: Option<(usize, char)>,
|
prev: Option<(usize, char)>,
|
||||||
|
/// Current state for parsing template strings.
|
||||||
|
in_template_string_expression_open_count: Vec<u32>,
|
||||||
}
|
}
|
||||||
impl SourceIterator<'_> {
|
impl SourceIterator<'_> {
|
||||||
/// Peek at the next character in the source file.
|
/// Peek at the next character in the source file.
|
||||||
pub fn peek(&mut self) -> Option<(usize, char)> {
|
pub fn peek(&mut self) -> Option<(usize, char)> {
|
||||||
|
self.iterator.reset_peek();
|
||||||
self.iterator.peek().copied()
|
self.iterator.peek().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Peek at the next character in the source file.
|
||||||
|
pub fn multipeek(&mut self) -> Option<(usize, char)> {
|
||||||
|
self.iterator.peek().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the multipeek state of the iterator.
|
||||||
|
pub fn reset_multipeek(&mut self) {
|
||||||
|
self.iterator.reset_peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increase the count of open parentheses in the current template string expression.
|
||||||
|
pub fn increase_template_string_expression_open_paren_count(&mut self) {
|
||||||
|
if let Some(count) = self.in_template_string_expression_open_count.last_mut() {
|
||||||
|
*count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrease the count of open parentheses in the current template string expression.
|
||||||
|
pub fn decrease_template_string_expression_open_paren_count(&mut self) {
|
||||||
|
if let Some(count) = self.in_template_string_expression_open_count.last_mut() {
|
||||||
|
*count = count.saturating_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enter a template string expression.
|
||||||
|
pub fn enter_template_string(&mut self) {
|
||||||
|
self.in_template_string_expression_open_count.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit a template string expression.
|
||||||
|
pub fn exit_template_string(&mut self) {
|
||||||
|
self.in_template_string_expression_open_count.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the iterator is currently in a template string expression.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_in_template_string_expression(&self) -> Option<bool> {
|
||||||
|
self.in_template_string_expression_open_count
|
||||||
|
.last()
|
||||||
|
.map(|&count| count > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of open parentheses in the current template string expression.
|
||||||
|
#[must_use]
|
||||||
|
pub fn template_string_expression_open_paren_count(&self) -> Option<u32> {
|
||||||
|
self.in_template_string_expression_open_count
|
||||||
|
.last()
|
||||||
|
.copied()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl Iterator for SourceIterator<'_> {
|
impl Iterator for SourceIterator<'_> {
|
||||||
type Item = (usize, char);
|
type Item = (usize, char);
|
||||||
|
|
|
@ -10,7 +10,6 @@ use std::{
|
||||||
|
|
||||||
use crate::base::{
|
use crate::base::{
|
||||||
self,
|
self,
|
||||||
log::SourceCodeDisplay,
|
|
||||||
source_file::{SourceElement, SourceIterator, Span},
|
source_file::{SourceElement, SourceIterator, Span},
|
||||||
Handler,
|
Handler,
|
||||||
};
|
};
|
||||||
|
@ -165,7 +164,7 @@ pub enum Token {
|
||||||
DocComment(DocComment),
|
DocComment(DocComment),
|
||||||
CommandLiteral(CommandLiteral),
|
CommandLiteral(CommandLiteral),
|
||||||
StringLiteral(StringLiteral),
|
StringLiteral(StringLiteral),
|
||||||
MacroStringLiteral(Box<MacroStringLiteral>),
|
TemplateStringText(TemplateStringLiteralText),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for Token {
|
impl SourceElement for Token {
|
||||||
|
@ -181,7 +180,7 @@ impl SourceElement for Token {
|
||||||
Self::DocComment(token) => token.span(),
|
Self::DocComment(token) => token.span(),
|
||||||
Self::CommandLiteral(token) => token.span(),
|
Self::CommandLiteral(token) => token.span(),
|
||||||
Self::StringLiteral(token) => token.span(),
|
Self::StringLiteral(token) => token.span(),
|
||||||
Self::MacroStringLiteral(token) => token.span(),
|
Self::TemplateStringText(token) => token.span(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,80 +351,25 @@ impl SourceElement for StringLiteral {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a hardcoded macro string literal value in the source code.
|
/// Represents a hardcoded template string text value in the source code.
|
||||||
///
|
///
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// MacroStringLiteral:
|
/// TemplateStringLiteralText:
|
||||||
/// '`' ( TEXT | '$(' [a-zA-Z0-9_]+ ')' )* '`';
|
/// TEXT ;
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct MacroStringLiteral {
|
pub struct TemplateStringLiteralText {
|
||||||
/// The backtick that starts the macro string literal.
|
/// Is the span that makes up the token.
|
||||||
starting_backtick: Punctuation,
|
pub span: Span,
|
||||||
/// 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 {
|
impl SourceElement for TemplateStringLiteralText {
|
||||||
/// Returns the string content without escapement characters, leading and trailing double quotes.
|
|
||||||
#[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::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 {
|
fn span(&self) -> Span {
|
||||||
self.starting_backtick
|
self.span.clone()
|
||||||
.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)]
|
||||||
|
@ -500,101 +444,15 @@ 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, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
#[expect(missing_copy_implementations)]
|
||||||
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.")]
|
||||||
FatalLexicalError,
|
FatalLexicalError,
|
||||||
|
|
||||||
#[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) {
|
||||||
|
@ -616,26 +474,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 == '_'
|
||||||
|
@ -792,110 +630,84 @@ impl Token {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a sequence of characters that are enclosed in backticks and contain macro usages
|
/// Handles a backticks for opening and closing template strings
|
||||||
fn handle_macro_string_literal(
|
fn handle_template_string_quotes(iter: &mut SourceIterator, start: usize) -> Self {
|
||||||
iter: &mut SourceIterator,
|
if iter
|
||||||
mut start: usize,
|
.is_in_template_string_expression()
|
||||||
) -> Result<Self, TokenizeError> {
|
.is_some_and(|last| !last)
|
||||||
let mut is_escaped = false;
|
{
|
||||||
let mut is_inside_macro = false;
|
// in template string text
|
||||||
let mut encountered_open_parenthesis = false;
|
iter.exit_template_string();
|
||||||
let starting_backtick = Punctuation {
|
} else {
|
||||||
span: Self::create_span(start, iter),
|
// outside template string or in expression
|
||||||
punctuation: '`',
|
iter.enter_template_string();
|
||||||
};
|
|
||||||
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 {
|
Punctuation {
|
||||||
Err(UnclosedMacroUsage {
|
span: Self::create_span(start, iter),
|
||||||
span: Span::new(iter.source_file().clone(), start, start + 2).unwrap(),
|
punctuation: '`',
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_template_string_inner(
|
||||||
|
iter: &mut SourceIterator,
|
||||||
|
start: usize,
|
||||||
|
character: char,
|
||||||
|
prev_token: Option<&Self>,
|
||||||
|
) -> Self {
|
||||||
|
let prev_was_backslash = iter.prev().is_some_and(|(_, c)| c == '\\');
|
||||||
|
|
||||||
|
if !prev_was_backslash && character == '$' && iter.peek().is_some_and(|(_, c)| c == '(') {
|
||||||
|
// starts immediately with expression, return punctuation
|
||||||
|
return Punctuation {
|
||||||
|
span: Self::create_span(start, iter),
|
||||||
|
punctuation: '$',
|
||||||
}
|
}
|
||||||
.into())
|
.into();
|
||||||
} else {
|
}
|
||||||
Ok(Box::new(MacroStringLiteral {
|
|
||||||
starting_backtick,
|
match (character, prev_token) {
|
||||||
parts,
|
('(', Some(Self::Punctuation(punc)))
|
||||||
ending_backtick: Punctuation {
|
if !prev_was_backslash && punc.punctuation == '$' =>
|
||||||
|
{
|
||||||
|
// Found expression opening parenthesis
|
||||||
|
iter.increase_template_string_expression_open_paren_count();
|
||||||
|
|
||||||
|
return Punctuation {
|
||||||
span: Self::create_span(start, iter),
|
span: Self::create_span(start, iter),
|
||||||
punctuation: '`',
|
punctuation: '(',
|
||||||
},
|
}
|
||||||
})
|
.into();
|
||||||
.into())
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if character != '`' {
|
||||||
|
iter.reset_multipeek();
|
||||||
|
Self::walk_iter(iter, |c| !matches!(c, '$' | '`' | '\\'));
|
||||||
|
}
|
||||||
|
|
||||||
|
iter.reset_multipeek();
|
||||||
|
let first_peek = iter.multipeek().map(|(_, c)| c);
|
||||||
|
let second_peek_open_paren = iter.multipeek().is_some_and(|(_, c)| c == '(');
|
||||||
|
|
||||||
|
if first_peek.is_some_and(|c| c == '\\') {
|
||||||
|
iter.next();
|
||||||
|
iter.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if character == '`' || first_peek.is_none_or(|c| c == '`') || second_peek_open_paren {
|
||||||
|
// Found expression start, end of text
|
||||||
|
|
||||||
|
break TemplateStringLiteralText {
|
||||||
|
span: Self::create_span(start, iter),
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
iter.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,8 +741,13 @@ impl Token {
|
||||||
.next()
|
.next()
|
||||||
.ok_or(TokenizeError::EndOfSourceCodeIteratorArgument)?;
|
.ok_or(TokenizeError::EndOfSourceCodeIteratorArgument)?;
|
||||||
|
|
||||||
|
if iter.is_in_template_string_expression().is_some_and(|b| !b) && character != '`' {
|
||||||
|
Ok(Self::handle_template_string_inner(
|
||||||
|
iter, start, character, prev_token,
|
||||||
|
))
|
||||||
|
}
|
||||||
// Found white spaces
|
// Found white spaces
|
||||||
if character.is_whitespace() {
|
else if character.is_whitespace() {
|
||||||
Ok(Self::handle_whitespace(iter, start))
|
Ok(Self::handle_whitespace(iter, start))
|
||||||
}
|
}
|
||||||
// Found identifier/keyword
|
// Found identifier/keyword
|
||||||
|
@ -947,7 +764,7 @@ impl Token {
|
||||||
}
|
}
|
||||||
// Found macro string literal
|
// Found macro string literal
|
||||||
else if character == '`' {
|
else if character == '`' {
|
||||||
Self::handle_macro_string_literal(iter, start)
|
Ok(Self::handle_template_string_quotes(iter, start))
|
||||||
}
|
}
|
||||||
// Found integer literal
|
// Found integer literal
|
||||||
else if character.is_ascii_digit() {
|
else if character.is_ascii_digit() {
|
||||||
|
@ -955,6 +772,12 @@ impl Token {
|
||||||
}
|
}
|
||||||
// Found a punctuation
|
// Found a punctuation
|
||||||
else if character.is_ascii_punctuation() {
|
else if character.is_ascii_punctuation() {
|
||||||
|
if character == '(' {
|
||||||
|
iter.increase_template_string_expression_open_paren_count();
|
||||||
|
} else if character == ')' {
|
||||||
|
iter.decrease_template_string_expression_open_paren_count();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Punctuation {
|
Ok(Punctuation {
|
||||||
span: Self::create_span(start, iter),
|
span: Self::create_span(start, iter),
|
||||||
punctuation: character,
|
punctuation: character,
|
||||||
|
|
|
@ -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)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,13 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{self, source_file::SourceElement as _, Handler},
|
base::{self, source_file::SourceElement as _, Handler},
|
||||||
lexical::token::{KeywordKind, MacroStringLiteral, MacroStringLiteralPart},
|
lexical::token::KeywordKind,
|
||||||
syntax::syntax_tree::{
|
syntax::syntax_tree::{
|
||||||
declaration::{Declaration, Function, FunctionVariableType, ImportItems},
|
declaration::{Declaration, Function, FunctionVariableType, ImportItems},
|
||||||
expression::{Binary, BinaryOperator, Expression, LuaCode, PrefixOperator, Primary},
|
expression::{
|
||||||
|
Binary, BinaryOperator, Expression, LuaCode, PrefixOperator, Primary,
|
||||||
|
TemplateStringLiteral, TemplateStringLiteralPart,
|
||||||
|
},
|
||||||
program::{Namespace, ProgramFile},
|
program::{Namespace, ProgramFile},
|
||||||
statement::{
|
statement::{
|
||||||
execute_block::{
|
execute_block::{
|
||||||
|
@ -581,7 +584,7 @@ impl Primary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Boolean(_) | Self::Integer(_) | Self::StringLiteral(_) => Ok(()),
|
Self::Boolean(_) | Self::Integer(_) | Self::StringLiteral(_) => Ok(()),
|
||||||
Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler),
|
Self::TemplateStringLiteral(lit) => lit.analyze_semantics(scope, handler),
|
||||||
Self::FunctionCall(call) => {
|
Self::FunctionCall(call) => {
|
||||||
let var = scope.get_variable(call.identifier().span.str());
|
let var = scope.get_variable(call.identifier().span.str());
|
||||||
var.map_or_else(
|
var.map_or_else(
|
||||||
|
@ -719,7 +722,7 @@ impl Primary {
|
||||||
match self {
|
match self {
|
||||||
Self::Boolean(_) => expected == ValueType::Boolean,
|
Self::Boolean(_) => expected == ValueType::Boolean,
|
||||||
Self::Integer(_) => expected == ValueType::Integer,
|
Self::Integer(_) => expected == ValueType::Integer,
|
||||||
Self::StringLiteral(_) | Self::MacroStringLiteral(_) => {
|
Self::StringLiteral(_) | Self::TemplateStringLiteral(_) => {
|
||||||
matches!(expected, ValueType::String | ValueType::Boolean)
|
matches!(expected, ValueType::String | ValueType::Boolean)
|
||||||
}
|
}
|
||||||
Self::FunctionCall(_) => matches!(expected, ValueType::Boolean | ValueType::Integer),
|
Self::FunctionCall(_) => matches!(expected, ValueType::Boolean | ValueType::Integer),
|
||||||
|
@ -948,12 +951,12 @@ impl AnyStringLiteral {
|
||||||
) -> Result<(), error::Error> {
|
) -> Result<(), error::Error> {
|
||||||
match self {
|
match self {
|
||||||
Self::StringLiteral(_) => Ok(()),
|
Self::StringLiteral(_) => Ok(()),
|
||||||
Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler),
|
Self::TemplateStringLiteral(lit) => lit.analyze_semantics(scope, handler),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacroStringLiteral {
|
impl TemplateStringLiteral {
|
||||||
fn analyze_semantics(
|
fn analyze_semantics(
|
||||||
&self,
|
&self,
|
||||||
scope: &SemanticScope,
|
scope: &SemanticScope,
|
||||||
|
@ -963,25 +966,76 @@ impl MacroStringLiteral {
|
||||||
|
|
||||||
for part in self.parts() {
|
for part in self.parts() {
|
||||||
match part {
|
match part {
|
||||||
MacroStringLiteralPart::MacroUsage { identifier, .. } => {
|
TemplateStringLiteralPart::Expression { expression, .. } => {
|
||||||
if let Some(variable_type) = scope.get_variable(identifier.span.str()) {
|
match expression.as_ref() {
|
||||||
if variable_type != VariableType::MacroParameter {
|
Expression::Primary(Primary::Identifier(identifier)) => {
|
||||||
let err =
|
if let Some(variable_type) = scope.get_variable(identifier.span.str()) {
|
||||||
error::Error::UnexpectedExpression(UnexpectedExpression(Box::new(
|
// TODO: template string correct checks
|
||||||
Expression::Primary(Primary::Identifier(identifier.clone())),
|
// if variable_type != VariableType::MacroParameter {
|
||||||
)));
|
// let err = error::Error::UnexpectedExpression(UnexpectedExpression(
|
||||||
|
// Box::new(Expression::Primary(Primary::Identifier(
|
||||||
|
// identifier.clone(),
|
||||||
|
// ))),
|
||||||
|
// ));
|
||||||
|
// handler.receive(err.clone());
|
||||||
|
// errs.push(err);
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
|
||||||
|
identifier: identifier.span.clone(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
errs.push(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::Primary(Primary::Indexed(indexed)) => {
|
||||||
|
if let Primary::Identifier(identifier) = indexed.object().as_ref() {
|
||||||
|
if let Some(variable_type) =
|
||||||
|
scope.get_variable(identifier.span.str())
|
||||||
|
{
|
||||||
|
match variable_type {
|
||||||
|
VariableType::BooleanStorageArray
|
||||||
|
| VariableType::ScoreboardArray
|
||||||
|
| VariableType::Tag
|
||||||
|
| VariableType::Scoreboard => {
|
||||||
|
// Valid types
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let err = error::Error::UnexpectedExpression(
|
||||||
|
UnexpectedExpression(expression.clone()),
|
||||||
|
);
|
||||||
|
handler.receive(err.clone());
|
||||||
|
errs.push(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
|
||||||
|
identifier: identifier.span.clone(),
|
||||||
|
});
|
||||||
|
handler.receive(err.clone());
|
||||||
|
errs.push(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let err = error::Error::UnexpectedExpression(UnexpectedExpression(
|
||||||
|
expression.clone(),
|
||||||
|
));
|
||||||
|
handler.receive(err.clone());
|
||||||
|
errs.push(err);
|
||||||
|
}
|
||||||
|
if let Err(err) = indexed.index().analyze_semantics(scope, handler) {
|
||||||
|
errs.push(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let err = error::Error::UnexpectedExpression(UnexpectedExpression(
|
||||||
|
expression.clone(),
|
||||||
|
));
|
||||||
handler.receive(err.clone());
|
handler.receive(err.clone());
|
||||||
errs.push(err);
|
errs.push(err);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
|
|
||||||
identifier: identifier.span.clone(),
|
|
||||||
});
|
|
||||||
handler.receive(err.clone());
|
|
||||||
errs.push(err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MacroStringLiteralPart::Text(_) => {}
|
TemplateStringLiteralPart::Text(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
src/serde.rs
10
src/serde.rs
|
@ -275,14 +275,8 @@ mod tests {
|
||||||
serde_json::from_str::<SerdeWrapper<ProgramFile>>(&serialized).unwrap();
|
serde_json::from_str::<SerdeWrapper<ProgramFile>>(&serialized).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Arc::as_ptr(
|
Arc::as_ptr(deserialized.namespace().keyword().span.source_file()),
|
||||||
deserialized
|
Arc::as_ptr(deserialized.namespace().name().span.source_file())
|
||||||
.namespace()
|
|
||||||
.namespace_keyword()
|
|
||||||
.span
|
|
||||||
.source_file()
|
|
||||||
),
|
|
||||||
Arc::as_ptr(deserialized.namespace().namespace_name().span.source_file())
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ pub enum SyntaxKind {
|
||||||
Integer,
|
Integer,
|
||||||
Boolean,
|
Boolean,
|
||||||
StringLiteral,
|
StringLiteral,
|
||||||
MacroStringLiteral,
|
TemplateStringLiteralPart,
|
||||||
AnyStringLiteral,
|
AnyStringLiteral,
|
||||||
Statement,
|
Statement,
|
||||||
Expression,
|
Expression,
|
||||||
|
@ -76,8 +76,8 @@ impl SyntaxKind {
|
||||||
Self::Integer => "an integer token".to_string(),
|
Self::Integer => "an integer token".to_string(),
|
||||||
Self::Boolean => "a boolean token".to_string(),
|
Self::Boolean => "a boolean token".to_string(),
|
||||||
Self::StringLiteral => "a string literal".to_string(),
|
Self::StringLiteral => "a string literal".to_string(),
|
||||||
Self::MacroStringLiteral => "a macro string literal".to_string(),
|
Self::TemplateStringLiteralPart => "part of a template string literal".to_string(),
|
||||||
Self::AnyStringLiteral => "a (macro) string literal".to_string(),
|
Self::AnyStringLiteral => "a (template) 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::Operator => "an operator".to_string(),
|
Self::Operator => "an operator".to_string(),
|
||||||
|
@ -116,7 +116,7 @@ impl Display for UnexpectedSyntax {
|
||||||
Some(Token::Boolean(..)) => "a boolean token".to_string(),
|
Some(Token::Boolean(..)) => "a boolean token".to_string(),
|
||||||
Some(Token::CommandLiteral(..)) => "a literal command token".to_string(),
|
Some(Token::CommandLiteral(..)) => "a literal command token".to_string(),
|
||||||
Some(Token::StringLiteral(..)) => "a string literal token".to_string(),
|
Some(Token::StringLiteral(..)) => "a string literal token".to_string(),
|
||||||
Some(Token::MacroStringLiteral(..)) => "a macro string literal token".to_string(),
|
Some(Token::TemplateStringText(..)) => "a template string text token".to_string(),
|
||||||
|
|
||||||
None => "EOF".to_string(),
|
None => "EOF".to_string(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,18 +6,12 @@ use enum_as_inner::EnumAsInner;
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{self, Handler},
|
base::{self, Handler},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{
|
token::{Identifier, Integer, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
|
||||||
Identifier, Integer, Keyword, KeywordKind, MacroStringLiteral, 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 Frame<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(Box::new(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(Box::new(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
|
||||||
|
|
|
@ -344,7 +344,6 @@ impl Parser<'_> {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// - cannot parse declaration from current position
|
/// - cannot parse declaration from current position
|
||||||
#[expect(clippy::too_many_lines)]
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[tracing::instrument(level = "trace", skip_all)]
|
||||||
pub fn parse_declaration(
|
pub fn parse_declaration(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -13,15 +13,16 @@ use crate::{
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{
|
token::{
|
||||||
Boolean, Identifier, Integer, Keyword, KeywordKind, MacroStringLiteral, Punctuation,
|
Boolean, Identifier, Integer, Keyword, KeywordKind, Punctuation, StringLiteral,
|
||||||
StringLiteral, Token,
|
TemplateStringLiteralText, Token,
|
||||||
},
|
},
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::{
|
syntax::{
|
||||||
self,
|
self,
|
||||||
error::{Error, ParseResult, UnexpectedSyntax},
|
error::{Error, ParseResult, SyntaxKind, UnexpectedSyntax},
|
||||||
parser::{Parser, Reading},
|
parser::{Parser, Reading},
|
||||||
|
syntax_tree::AnyStringLiteral,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -177,7 +178,7 @@ impl SourceElement for Expression {
|
||||||
/// | StringLiteral
|
/// | StringLiteral
|
||||||
/// | FunctionCall
|
/// | FunctionCall
|
||||||
/// | MemberAccess
|
/// | MemberAccess
|
||||||
/// | MacroStringLiteral
|
/// | TemplateStringLiteral
|
||||||
/// | LuaCode
|
/// | LuaCode
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
@ -193,7 +194,7 @@ pub enum Primary {
|
||||||
StringLiteral(StringLiteral),
|
StringLiteral(StringLiteral),
|
||||||
FunctionCall(FunctionCall),
|
FunctionCall(FunctionCall),
|
||||||
MemberAccess(MemberAccess),
|
MemberAccess(MemberAccess),
|
||||||
MacroStringLiteral(MacroStringLiteral),
|
TemplateStringLiteral(TemplateStringLiteral),
|
||||||
Lua(Box<LuaCode>),
|
Lua(Box<LuaCode>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +210,7 @@ impl SourceElement for Primary {
|
||||||
Self::StringLiteral(string_literal) => string_literal.span(),
|
Self::StringLiteral(string_literal) => string_literal.span(),
|
||||||
Self::FunctionCall(function_call) => function_call.span(),
|
Self::FunctionCall(function_call) => function_call.span(),
|
||||||
Self::MemberAccess(member_access) => member_access.span(),
|
Self::MemberAccess(member_access) => member_access.span(),
|
||||||
Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
|
Self::TemplateStringLiteral(template_string_literal) => template_string_literal.span(),
|
||||||
Self::Lua(lua_code) => lua_code.span(),
|
Self::Lua(lua_code) => lua_code.span(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -391,6 +392,70 @@ impl SourceElement for FunctionCall {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a hardcoded template string literal value in the source code.
|
||||||
|
///
|
||||||
|
/// ```ebnf
|
||||||
|
/// TemplateStringLiteral:
|
||||||
|
/// '`' ( TemplateStringLiteralText | '$(' Expression ')' )* '`';
|
||||||
|
/// ```
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct TemplateStringLiteral {
|
||||||
|
/// The backtick that starts the template string literal.
|
||||||
|
pub(crate) starting_backtick: Punctuation,
|
||||||
|
/// The parts that make up the template string literal.
|
||||||
|
pub(crate) parts: Vec<TemplateStringLiteralPart>,
|
||||||
|
/// The backtick that ends the template string literal.
|
||||||
|
pub(crate) ending_backtick: Punctuation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TemplateStringLiteral {
|
||||||
|
/// Returns the parts that make up the template string literal.
|
||||||
|
#[must_use]
|
||||||
|
pub fn parts(&self) -> &[TemplateStringLiteralPart] {
|
||||||
|
&self.parts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceElement for TemplateStringLiteral {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
self.starting_backtick
|
||||||
|
.span
|
||||||
|
.join(&self.ending_backtick.span)
|
||||||
|
.expect("Invalid template string literal span")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a part of a template 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 TemplateStringLiteralPart {
|
||||||
|
Text(TemplateStringLiteralText),
|
||||||
|
Expression {
|
||||||
|
dollar: Punctuation,
|
||||||
|
open_brace: Punctuation,
|
||||||
|
expression: Box<Expression>,
|
||||||
|
close_brace: Punctuation,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceElement for TemplateStringLiteralPart {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Self::Text(text) => text.span(),
|
||||||
|
Self::Expression {
|
||||||
|
dollar,
|
||||||
|
close_brace,
|
||||||
|
..
|
||||||
|
} => dollar
|
||||||
|
.span()
|
||||||
|
.join(&close_brace.span())
|
||||||
|
.expect("Invalid template string literal part span"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a lua code block in the syntax tree.
|
/// Represents a lua code block in the syntax tree.
|
||||||
///
|
///
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
|
@ -589,6 +654,13 @@ impl Parser<'_> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// template string literal
|
||||||
|
Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '`' => {
|
||||||
|
let template_string_literal = self.parse_template_string_literal(handler)?;
|
||||||
|
|
||||||
|
Ok(Primary::TemplateStringLiteral(template_string_literal))
|
||||||
|
}
|
||||||
|
|
||||||
// parenthesized expression
|
// parenthesized expression
|
||||||
Reading::IntoDelimited(left_parenthesis) if left_parenthesis.punctuation == '(' => self
|
Reading::IntoDelimited(left_parenthesis) if left_parenthesis.punctuation == '(' => self
|
||||||
.parse_parenthesized(handler)
|
.parse_parenthesized(handler)
|
||||||
|
@ -661,14 +733,6 @@ impl Parser<'_> {
|
||||||
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 =>
|
||||||
|
@ -843,4 +907,88 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expects the next [`Token`] to be an [`TemplateStringLiteral`], and returns it.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// If the next [`Token`] is not an [`TemplateStringLiteral`].
|
||||||
|
pub fn parse_template_string_literal(
|
||||||
|
&mut self,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> ParseResult<TemplateStringLiteral> {
|
||||||
|
let starting_backtick = self.parse_punctuation('`', true, handler)?;
|
||||||
|
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.stop_at_significant() {
|
||||||
|
Reading::Atomic(Token::Punctuation(ending_backtick))
|
||||||
|
if ending_backtick.punctuation == '`' =>
|
||||||
|
{
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
// closing tick
|
||||||
|
return Ok(TemplateStringLiteral {
|
||||||
|
starting_backtick,
|
||||||
|
parts,
|
||||||
|
ending_backtick,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Reading::Atomic(Token::Punctuation(dollar)) if dollar.punctuation == '$' => {
|
||||||
|
self.forward();
|
||||||
|
|
||||||
|
let delimited_expression = self.step_into(
|
||||||
|
Delimiter::Parenthesis,
|
||||||
|
|parser| parser.parse_expression(handler),
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
parts.push(TemplateStringLiteralPart::Expression {
|
||||||
|
dollar,
|
||||||
|
open_brace: delimited_expression.open,
|
||||||
|
expression: Box::new(delimited_expression.tree?),
|
||||||
|
close_brace: delimited_expression.close,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Reading::Atomic(Token::TemplateStringText(text)) => {
|
||||||
|
self.forward();
|
||||||
|
parts.push(TemplateStringLiteralPart::Text(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
unexpected => {
|
||||||
|
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
||||||
|
expected: syntax::error::SyntaxKind::TemplateStringLiteralPart,
|
||||||
|
found: unexpected.into_token(),
|
||||||
|
});
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
return 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::Punctuation(punc)) if punc.punctuation == '`' => self
|
||||||
|
.parse_template_string_literal(handler)
|
||||||
|
.map(AnyStringLiteral::TemplateStringLiteral),
|
||||||
|
found => {
|
||||||
|
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
||||||
|
expected: SyntaxKind::AnyStringLiteral,
|
||||||
|
found: found.into_token(),
|
||||||
|
});
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,10 @@ use crate::{
|
||||||
Handler, VoidHandler,
|
Handler, VoidHandler,
|
||||||
},
|
},
|
||||||
lexical::{
|
lexical::{
|
||||||
token::{Identifier, MacroStringLiteral, Punctuation, StringLiteral, Token},
|
token::{Identifier, Punctuation, StringLiteral, Token},
|
||||||
token_stream::Delimiter,
|
token_stream::Delimiter,
|
||||||
},
|
},
|
||||||
syntax::parser::Reading,
|
syntax::{parser::Reading, syntax_tree::expression::TemplateStringLiteral},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -69,25 +69,25 @@ 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.
|
/// Represents a syntax tree node that can be either a string literal or a template string literal.
|
||||||
///
|
///
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// AnyStringLiteral: StringLiteral | MacroStringLiteral ;
|
/// AnyStringLiteral: StringLiteral | TemplateStringLiteral ;
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[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, From)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From)]
|
||||||
pub enum AnyStringLiteral {
|
pub enum AnyStringLiteral {
|
||||||
StringLiteral(StringLiteral),
|
StringLiteral(StringLiteral),
|
||||||
MacroStringLiteral(MacroStringLiteral),
|
TemplateStringLiteral(TemplateStringLiteral),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for AnyStringLiteral {
|
impl SourceElement for AnyStringLiteral {
|
||||||
fn span(&self) -> Span {
|
fn span(&self) -> Span {
|
||||||
match self {
|
match self {
|
||||||
Self::StringLiteral(string_literal) => string_literal.span(),
|
Self::StringLiteral(string_literal) => string_literal.span(),
|
||||||
Self::MacroStringLiteral(macro_string_literal) => macro_string_literal.span(),
|
Self::TemplateStringLiteral(template_string_literal) => template_string_literal.span(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1010,7 +1010,6 @@ impl Parser<'_> {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// - cannot parse variable declaration from current position
|
/// - cannot parse variable declaration from current position
|
||||||
#[expect(clippy::too_many_lines)]
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[tracing::instrument(level = "trace", skip_all)]
|
||||||
pub fn parse_variable_declaration(
|
pub fn parse_variable_declaration(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
|
//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types
|
||||||
|
|
||||||
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
use shulkerbox::util::{MacroString as ExtMacroString, MacroStringPart as ExtMacroStringPart};
|
use shulkerbox::util::{MacroString as ExtMacroString, MacroStringPart as ExtMacroStringPart};
|
||||||
|
|
||||||
use crate::{lexical::token::MacroStringLiteral, syntax::syntax_tree::AnyStringLiteral};
|
use crate::{
|
||||||
|
base::{self, Handler},
|
||||||
|
semantic::error::UnexpectedExpression,
|
||||||
|
syntax::syntax_tree::expression::{TemplateStringLiteral, TemplateStringLiteralPart},
|
||||||
|
transpile::{Scope, TranspileError, TranspileResult},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
use super::util::{MacroString, MacroStringPart};
|
use super::util::{MacroString, MacroStringPart};
|
||||||
|
|
||||||
|
@ -26,26 +34,38 @@ impl From<MacroStringPart> for ExtMacroStringPart {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&AnyStringLiteral> for ExtMacroString {
|
impl TemplateStringLiteral {
|
||||||
fn from(value: &AnyStringLiteral) -> Self {
|
pub fn as_str(
|
||||||
Self::from(MacroString::from(value))
|
&self,
|
||||||
}
|
scope: &Arc<Scope>,
|
||||||
}
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<Cow<'_, str>> {
|
||||||
|
let mut res = Cow::Borrowed("");
|
||||||
|
|
||||||
impl From<AnyStringLiteral> for ExtMacroString {
|
for part in &self.parts {
|
||||||
fn from(value: AnyStringLiteral) -> Self {
|
match part {
|
||||||
Self::from(&value)
|
TemplateStringLiteralPart::Text(s) => {
|
||||||
}
|
let s = util::unescape_template_string(s.span.str());
|
||||||
}
|
if res.is_empty() {
|
||||||
|
res = s;
|
||||||
|
} else {
|
||||||
|
res.to_mut().push_str(&s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TemplateStringLiteralPart::Expression { expression, .. } => {
|
||||||
|
let compiled = expression.comptime_eval(scope, handler)?;
|
||||||
|
let s = compiled.to_string_no_macro().ok_or_else(|| {
|
||||||
|
let err = TranspileError::UnexpectedExpression(UnexpectedExpression(
|
||||||
|
expression.clone(),
|
||||||
|
));
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
err
|
||||||
|
})?;
|
||||||
|
res.to_mut().push_str(&s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&MacroStringLiteral> for ExtMacroString {
|
Ok(res)
|
||||||
fn from(value: &MacroStringLiteral) -> Self {
|
|
||||||
Self::from(MacroString::from(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MacroStringLiteral> for ExtMacroString {
|
|
||||||
fn from(value: MacroStringLiteral) -> Self {
|
|
||||||
Self::from(&value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ use super::{
|
||||||
#[cfg(feature = "shulkerbox")]
|
#[cfg(feature = "shulkerbox")]
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{self, source_file::SourceElement, Handler, VoidHandler},
|
base::{self, source_file::SourceElement, Handler, VoidHandler},
|
||||||
lexical::token::{Identifier, MacroStringLiteralPart, StringLiteral},
|
lexical::token::{Identifier, StringLiteral},
|
||||||
syntax::syntax_tree::expression::{
|
syntax::syntax_tree::expression::{
|
||||||
Binary, BinaryOperator, Expression, Indexed, MemberAccess, Parenthesized, PrefixOperator,
|
Binary, BinaryOperator, Expression, Indexed, MemberAccess, Parenthesized, PrefixOperator,
|
||||||
Primary,
|
Primary,
|
||||||
|
@ -337,7 +337,7 @@ impl Primary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::StringLiteral(_) | Self::MacroStringLiteral(_) => {
|
Self::StringLiteral(_) | Self::TemplateStringLiteral(_) => {
|
||||||
matches!(r#type, ValueType::String | ValueType::Boolean)
|
matches!(r#type, ValueType::String | ValueType::Boolean)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -412,17 +412,29 @@ impl Primary {
|
||||||
expression: lua.span(),
|
expression: lua.span(),
|
||||||
})
|
})
|
||||||
.and_then(|val| val),
|
.and_then(|val| val),
|
||||||
Self::MacroStringLiteral(macro_string_literal) => {
|
Self::TemplateStringLiteral(template_string_literal) => {
|
||||||
if macro_string_literal
|
use crate::syntax::syntax_tree::expression::TemplateStringLiteralPart;
|
||||||
|
|
||||||
|
if template_string_literal
|
||||||
.parts()
|
.parts()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|part| matches!(part, MacroStringLiteralPart::MacroUsage { .. }))
|
.any(|part| matches!(part, TemplateStringLiteralPart::Expression { .. }))
|
||||||
{
|
{
|
||||||
Ok(ComptimeValue::MacroString(
|
template_string_literal
|
||||||
macro_string_literal.clone().into(),
|
.to_macro_string(scope, handler)
|
||||||
))
|
.map(ComptimeValue::MacroString)
|
||||||
|
.map_err(|_| NotComptime {
|
||||||
|
expression: template_string_literal.span(),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(ComptimeValue::String(macro_string_literal.str_content()))
|
Ok(ComptimeValue::String(
|
||||||
|
template_string_literal
|
||||||
|
.as_str(scope, handler)
|
||||||
|
.map_err(|_| NotComptime {
|
||||||
|
expression: template_string_literal.span(),
|
||||||
|
})?
|
||||||
|
.into_owned(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -446,7 +458,7 @@ impl Primary {
|
||||||
| Self::FunctionCall(_)
|
| Self::FunctionCall(_)
|
||||||
| Self::Integer(_)
|
| Self::Integer(_)
|
||||||
| Self::Lua(_)
|
| Self::Lua(_)
|
||||||
| Self::MacroStringLiteral(_)
|
| Self::TemplateStringLiteral(_)
|
||||||
| Self::MemberAccess(_)
|
| Self::MemberAccess(_)
|
||||||
| Self::Prefix(_) => Err(NotComptime {
|
| Self::Prefix(_) => Err(NotComptime {
|
||||||
expression: self.span(),
|
expression: self.span(),
|
||||||
|
@ -476,7 +488,7 @@ impl Primary {
|
||||||
| Self::FunctionCall(_)
|
| Self::FunctionCall(_)
|
||||||
| Self::Integer(_)
|
| Self::Integer(_)
|
||||||
| Self::Lua(_)
|
| Self::Lua(_)
|
||||||
| Self::MacroStringLiteral(_)
|
| Self::TemplateStringLiteral(_)
|
||||||
| Self::MemberAccess(_)
|
| Self::MemberAccess(_)
|
||||||
| Self::Prefix(_) => false,
|
| Self::Prefix(_) => false,
|
||||||
}
|
}
|
||||||
|
@ -1260,7 +1272,7 @@ impl Transpiler {
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Primary::StringLiteral(_) | Primary::MacroStringLiteral(_) => {
|
Primary::StringLiteral(_) | Primary::TemplateStringLiteral(_) => {
|
||||||
if matches!(
|
if matches!(
|
||||||
target,
|
target,
|
||||||
DataLocation::Storage {
|
DataLocation::Storage {
|
||||||
|
@ -1662,9 +1674,11 @@ impl Transpiler {
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
ExtendedCondition::Runtime(Condition::Atom(s.str_content().to_string().into())),
|
ExtendedCondition::Runtime(Condition::Atom(s.str_content().to_string().into())),
|
||||||
)),
|
)),
|
||||||
Primary::MacroStringLiteral(macro_string) => Ok((
|
Primary::TemplateStringLiteral(template_string) => Ok((
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
ExtendedCondition::Runtime(Condition::Atom(macro_string.into())),
|
ExtendedCondition::Runtime(Condition::Atom(
|
||||||
|
template_string.to_macro_string(scope, handler)?.into(),
|
||||||
|
)),
|
||||||
)),
|
)),
|
||||||
Primary::FunctionCall(func) => {
|
Primary::FunctionCall(func) => {
|
||||||
if func
|
if func
|
||||||
|
|
|
@ -448,8 +448,8 @@ impl Transpiler {
|
||||||
Expression::Primary(Primary::StringLiteral(string)) => {
|
Expression::Primary(Primary::StringLiteral(string)) => {
|
||||||
Ok(Parameter::Static(string.str_content().to_string().into()))
|
Ok(Parameter::Static(string.str_content().to_string().into()))
|
||||||
}
|
}
|
||||||
Expression::Primary(Primary::MacroStringLiteral(literal)) => {
|
Expression::Primary(Primary::TemplateStringLiteral(literal)) => {
|
||||||
Ok(Parameter::Static(literal.into()))
|
Ok(Parameter::Static(literal.to_macro_string(scope, handler)?))
|
||||||
}
|
}
|
||||||
Expression::Primary(primary @ Primary::Identifier(ident)) => {
|
Expression::Primary(primary @ Primary::Identifier(ident)) => {
|
||||||
let var =
|
let var =
|
||||||
|
|
|
@ -12,11 +12,13 @@ use serde_json::{json, Value as JsonValue};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{source_file::SourceElement as _, VoidHandler},
|
base::{source_file::SourceElement as _, VoidHandler},
|
||||||
lexical::token::{Identifier, MacroStringLiteralPart},
|
lexical::token::Identifier,
|
||||||
semantic::error::{InvalidFunctionArguments, UnexpectedExpression},
|
semantic::error::{InvalidFunctionArguments, UnexpectedExpression},
|
||||||
syntax::syntax_tree::expression::{Expression, FunctionCall, Primary},
|
syntax::syntax_tree::expression::{
|
||||||
|
Expression, FunctionCall, Primary, TemplateStringLiteralPart,
|
||||||
|
},
|
||||||
transpile::{
|
transpile::{
|
||||||
error::{IllegalIndexing, IllegalIndexingReason, UnknownIdentifier},
|
error::{IllegalIndexing, IllegalIndexingReason, NotComptime, UnknownIdentifier},
|
||||||
expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
|
expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
|
||||||
util::MacroString,
|
util::MacroString,
|
||||||
TranspileError,
|
TranspileError,
|
||||||
|
@ -95,7 +97,6 @@ fn print_function(
|
||||||
) -> TranspileResult<Vec<Command>> {
|
) -> TranspileResult<Vec<Command>> {
|
||||||
const PARAM_COLOR: &str = "gray";
|
const PARAM_COLOR: &str = "gray";
|
||||||
|
|
||||||
#[expect(clippy::option_if_let_else)]
|
|
||||||
fn get_identifier_part(
|
fn get_identifier_part(
|
||||||
ident: &Identifier,
|
ident: &Identifier,
|
||||||
transpiler: &mut Transpiler,
|
transpiler: &mut Transpiler,
|
||||||
|
@ -131,6 +132,158 @@ fn print_function(
|
||||||
|
|
||||||
Ok((false, cmd, value))
|
Ok((false, cmd, value))
|
||||||
}
|
}
|
||||||
|
VariableData::ComptimeValue { value, .. } => {
|
||||||
|
let value = {
|
||||||
|
let guard = value.read().map_err(|_| {
|
||||||
|
TranspileError::NotComptime(NotComptime {
|
||||||
|
expression: ident.span(),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
guard.as_ref().map_or_else(
|
||||||
|
|| "null".into(),
|
||||||
|
super::expression::ComptimeValue::to_macro_string,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
value.contains_macros(),
|
||||||
|
None,
|
||||||
|
json!({"text": value.to_string(), "color": PARAM_COLOR}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => Err(TranspileError::UnexpectedExpression(UnexpectedExpression(
|
||||||
|
Box::new(Expression::Primary(Primary::Identifier(ident.to_owned()))),
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(TranspileError::UnknownIdentifier(UnknownIdentifier {
|
||||||
|
identifier: ident.span(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_indexed_part(
|
||||||
|
ident: &Identifier,
|
||||||
|
index: &Expression,
|
||||||
|
transpiler: &mut Transpiler,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
) -> TranspileResult<(bool, Option<Command>, JsonValue)> {
|
||||||
|
if let Some(var) = scope.get_variable(ident.span.str()).as_deref() {
|
||||||
|
match var {
|
||||||
|
VariableData::Scoreboard { objective } => {
|
||||||
|
let Ok(ComptimeValue::String(target)) =
|
||||||
|
index.comptime_eval(scope, &VoidHandler)
|
||||||
|
else {
|
||||||
|
return Err(TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::String,
|
||||||
|
},
|
||||||
|
expression: index.span(),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cmd, value) = get_data_location(
|
||||||
|
&DataLocation::ScoreboardValue {
|
||||||
|
objective: objective.to_string(),
|
||||||
|
target,
|
||||||
|
},
|
||||||
|
transpiler,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok((false, cmd, value))
|
||||||
|
}
|
||||||
|
VariableData::ScoreboardArray { objective, targets } => {
|
||||||
|
let Ok(ComptimeValue::Integer(idx)) = index.comptime_eval(scope, &VoidHandler)
|
||||||
|
else {
|
||||||
|
return Err(TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::Integer,
|
||||||
|
},
|
||||||
|
expression: index.span(),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
if let Some(target) = usize::try_from(idx)
|
||||||
|
.ok()
|
||||||
|
.and_then(|index| targets.get(index))
|
||||||
|
{
|
||||||
|
let (cmd, value) = get_data_location(
|
||||||
|
&DataLocation::ScoreboardValue {
|
||||||
|
objective: objective.to_string(),
|
||||||
|
target: target.to_string(),
|
||||||
|
},
|
||||||
|
transpiler,
|
||||||
|
);
|
||||||
|
Ok((false, cmd, value))
|
||||||
|
} else {
|
||||||
|
Err(TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::IndexOutOfBounds {
|
||||||
|
index: usize::try_from(idx).unwrap_or(usize::MAX),
|
||||||
|
length: targets.len(),
|
||||||
|
},
|
||||||
|
expression: index.span(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VariableData::BooleanStorageArray {
|
||||||
|
storage_name,
|
||||||
|
paths,
|
||||||
|
} => {
|
||||||
|
let Ok(ComptimeValue::Integer(idx)) = index.comptime_eval(scope, &VoidHandler)
|
||||||
|
else {
|
||||||
|
return Err(TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::Integer,
|
||||||
|
},
|
||||||
|
expression: index.span(),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
if let Some(path) = usize::try_from(idx).ok().and_then(|index| paths.get(index))
|
||||||
|
{
|
||||||
|
let (cmd, value) = get_data_location(
|
||||||
|
&DataLocation::Storage {
|
||||||
|
storage_name: storage_name.to_string(),
|
||||||
|
path: path.to_string(),
|
||||||
|
r#type: StorageType::Boolean,
|
||||||
|
},
|
||||||
|
transpiler,
|
||||||
|
);
|
||||||
|
Ok((false, cmd, value))
|
||||||
|
} else {
|
||||||
|
Err(TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::IndexOutOfBounds {
|
||||||
|
index: usize::try_from(idx).unwrap_or(usize::MAX),
|
||||||
|
length: paths.len(),
|
||||||
|
},
|
||||||
|
expression: index.span(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VariableData::Tag { tag_name } => {
|
||||||
|
let Ok(ComptimeValue::String(entity)) =
|
||||||
|
index.comptime_eval(scope, &VoidHandler)
|
||||||
|
else {
|
||||||
|
return Err(TranspileError::IllegalIndexing(IllegalIndexing {
|
||||||
|
reason: IllegalIndexingReason::InvalidComptimeType {
|
||||||
|
expected: ExpectedType::String,
|
||||||
|
},
|
||||||
|
expression: index.span(),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cmd, value) = get_data_location(
|
||||||
|
&DataLocation::Tag {
|
||||||
|
tag_name: tag_name.clone(),
|
||||||
|
entity,
|
||||||
|
},
|
||||||
|
transpiler,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok((false, cmd, value))
|
||||||
|
}
|
||||||
_ => Err(TranspileError::UnexpectedExpression(UnexpectedExpression(
|
_ => Err(TranspileError::UnexpectedExpression(UnexpectedExpression(
|
||||||
Box::new(Expression::Primary(Primary::Identifier(ident.to_owned()))),
|
Box::new(Expression::Primary(Primary::Identifier(ident.to_owned()))),
|
||||||
))),
|
))),
|
||||||
|
@ -192,7 +345,7 @@ fn print_function(
|
||||||
("@a".into(), first)
|
("@a".into(), first)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut contains_macro = matches!(target, MacroString::MacroString(_));
|
let mut contains_macro = target.contains_macros();
|
||||||
|
|
||||||
let (mut cmds, parts) = match message_expression {
|
let (mut cmds, parts) = match message_expression {
|
||||||
Expression::Primary(primary) => match primary {
|
Expression::Primary(primary) => match primary {
|
||||||
|
@ -343,20 +496,43 @@ fn print_function(
|
||||||
reason: IllegalIndexingReason::NotIdentifier,
|
reason: IllegalIndexingReason::NotIdentifier,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
Primary::MacroStringLiteral(macro_string) => {
|
Primary::TemplateStringLiteral(template_string) => {
|
||||||
let mut cmds = Vec::new();
|
let mut cmds = Vec::new();
|
||||||
let mut parts = Vec::new();
|
let mut parts = Vec::new();
|
||||||
for part in macro_string.parts() {
|
for part in template_string.parts() {
|
||||||
match part {
|
match part {
|
||||||
MacroStringLiteralPart::Text(text) => {
|
TemplateStringLiteralPart::Text(text) => {
|
||||||
parts.push(JsonValue::String(text.str().to_string()));
|
parts.push(JsonValue::String(text.span.str().to_string()));
|
||||||
}
|
}
|
||||||
MacroStringLiteralPart::MacroUsage { identifier, .. } => {
|
TemplateStringLiteralPart::Expression { expression, .. } => {
|
||||||
let (cur_contains_macro, cur_cmds, part) =
|
match expression.as_ref() {
|
||||||
get_identifier_part(identifier, transpiler, scope)?;
|
Expression::Primary(Primary::Identifier(identifier)) => {
|
||||||
contains_macro |= cur_contains_macro;
|
let (cur_contains_macro, cur_cmds, part) =
|
||||||
cmds.extend(cur_cmds);
|
get_identifier_part(identifier, transpiler, scope)?;
|
||||||
parts.push(part);
|
contains_macro |= cur_contains_macro;
|
||||||
|
cmds.extend(cur_cmds);
|
||||||
|
parts.push(part);
|
||||||
|
}
|
||||||
|
Expression::Primary(Primary::Indexed(indexed)) => {
|
||||||
|
match indexed.object().as_ref() {
|
||||||
|
Primary::Identifier(ident) => {
|
||||||
|
let (cur_contains_macro, cur_cmds, part) =
|
||||||
|
get_indexed_part(
|
||||||
|
ident,
|
||||||
|
indexed.index(),
|
||||||
|
transpiler,
|
||||||
|
scope,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
contains_macro |= cur_contains_macro;
|
||||||
|
cmds.extend(cur_cmds);
|
||||||
|
parts.push(part);
|
||||||
|
}
|
||||||
|
_ => todo!("other expression in indexed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => todo!("other expression in template string literal"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,8 @@ mod enabled {
|
||||||
|
|
||||||
table.set("version", crate::VERSION)?;
|
table.set("version", crate::VERSION)?;
|
||||||
|
|
||||||
|
// TODO: add functions for requesting data/scoreboard locations
|
||||||
|
|
||||||
Ok(table)
|
Ok(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,26 +325,41 @@ mod enabled {
|
||||||
handler,
|
handler,
|
||||||
),
|
),
|
||||||
Value::Boolean(boolean) => Ok(Some(ComptimeValue::Boolean(boolean))),
|
Value::Boolean(boolean) => Ok(Some(ComptimeValue::Boolean(boolean))),
|
||||||
Value::Table(table) => match table.get::<Value>("value") {
|
Value::Table(table) => {
|
||||||
Ok(Value::Nil) => {
|
// TODO: allow to return arrays when comptime arrays are implemented
|
||||||
let err = TranspileError::LuaRuntimeError(LuaRuntimeError {
|
match table.get::<Value>("value") {
|
||||||
code_block: self.span(),
|
Ok(Value::Nil) => {
|
||||||
error_message: "return table must contain non-nil 'value'".to_string(),
|
let err = TranspileError::LuaRuntimeError(LuaRuntimeError {
|
||||||
});
|
code_block: self.span(),
|
||||||
handler.receive(Box::new(err.clone()));
|
error_message: "return table must contain non-nil 'value'"
|
||||||
Err(err)
|
.to_string(),
|
||||||
}
|
});
|
||||||
Ok(value) => {
|
handler.receive(Box::new(err.clone()));
|
||||||
let value = match self.handle_lua_result(value, handler)? {
|
Err(err)
|
||||||
Some(ComptimeValue::String(s)) => {
|
}
|
||||||
let contains_macro = match table.get::<Value>("contains_macro") {
|
Ok(value) => {
|
||||||
Ok(Value::Boolean(boolean)) => Ok(boolean),
|
let value = match self.handle_lua_result(value, handler)? {
|
||||||
Ok(value) => {
|
Some(ComptimeValue::String(s)) => {
|
||||||
if let Some(ComptimeValue::Boolean(boolean)) =
|
let contains_macro = match table.get::<Value>("contains_macro")
|
||||||
self.handle_lua_result(value, handler)?
|
{
|
||||||
{
|
Ok(Value::Boolean(boolean)) => Ok(boolean),
|
||||||
Ok(boolean)
|
Ok(value) => {
|
||||||
} else {
|
if let Some(ComptimeValue::Boolean(boolean)) =
|
||||||
|
self.handle_lua_result(value, handler)?
|
||||||
|
{
|
||||||
|
Ok(boolean)
|
||||||
|
} else {
|
||||||
|
let err = TranspileError::MismatchedTypes(
|
||||||
|
MismatchedTypes {
|
||||||
|
expression: self.span(),
|
||||||
|
expected_type: ExpectedType::Boolean,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
let err =
|
let err =
|
||||||
TranspileError::MismatchedTypes(MismatchedTypes {
|
TranspileError::MismatchedTypes(MismatchedTypes {
|
||||||
expression: self.span(),
|
expression: self.span(),
|
||||||
|
@ -351,39 +368,29 @@ mod enabled {
|
||||||
handler.receive(Box::new(err.clone()));
|
handler.receive(Box::new(err.clone()));
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}?;
|
||||||
_ => {
|
|
||||||
let err =
|
|
||||||
TranspileError::MismatchedTypes(MismatchedTypes {
|
|
||||||
expression: self.span(),
|
|
||||||
expected_type: ExpectedType::Boolean,
|
|
||||||
});
|
|
||||||
handler.receive(Box::new(err.clone()));
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
|
|
||||||
if contains_macro {
|
if contains_macro {
|
||||||
Some(ComptimeValue::MacroString(
|
Some(ComptimeValue::MacroString(
|
||||||
s.parse().expect("parsing cannot fail"),
|
s.parse().expect("parsing cannot fail"),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Some(ComptimeValue::String(s))
|
Some(ComptimeValue::String(s))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
value => value,
|
||||||
value => value,
|
};
|
||||||
};
|
Ok(value)
|
||||||
Ok(value)
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let err = TranspileError::LuaRuntimeError(
|
||||||
|
LuaRuntimeError::from_lua_err(&err, self.span()),
|
||||||
|
);
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
}
|
||||||
let err = TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err(
|
|
||||||
&err,
|
|
||||||
self.span(),
|
|
||||||
));
|
|
||||||
handler.receive(Box::new(err.clone()));
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Value::Error(_)
|
Value::Error(_)
|
||||||
| Value::Thread(_)
|
| Value::Thread(_)
|
||||||
| Value::UserData(_)
|
| Value::UserData(_)
|
||||||
|
|
|
@ -69,7 +69,6 @@ impl Transpiler {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing
|
/// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing
|
||||||
#[expect(clippy::too_many_lines)]
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[tracing::instrument(level = "trace", skip_all)]
|
||||||
pub fn transpile(
|
pub fn transpile(
|
||||||
mut self,
|
mut self,
|
||||||
|
@ -645,7 +644,9 @@ impl Transpiler {
|
||||||
Primary::StringLiteral(string) => {
|
Primary::StringLiteral(string) => {
|
||||||
Ok(vec![Command::Raw(string.str_content().to_string())])
|
Ok(vec![Command::Raw(string.str_content().to_string())])
|
||||||
}
|
}
|
||||||
Primary::MacroStringLiteral(string) => Ok(vec![Command::UsesMacro(string.into())]),
|
Primary::TemplateStringLiteral(string) => Ok(vec![Command::UsesMacro(
|
||||||
|
string.to_macro_string(scope, handler)?.into(),
|
||||||
|
)]),
|
||||||
Primary::Lua(code) => match code.eval_comptime(scope, handler)? {
|
Primary::Lua(code) => match code.eval_comptime(scope, handler)? {
|
||||||
Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
|
Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
|
||||||
Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]),
|
Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]),
|
||||||
|
@ -987,37 +988,41 @@ impl Transpiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::As(r#as) => {
|
ExecuteBlockHead::As(r#as) => {
|
||||||
let selector = r#as.as_selector();
|
let selector = r#as.as_selector().to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::As(selector.into(), Box::new(tail)))
|
(pre_cmds, Execute::As(selector.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::At(at) => {
|
ExecuteBlockHead::At(at) => {
|
||||||
let selector = at.at_selector();
|
let selector = at.at_selector().to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::At(selector.into(), Box::new(tail)))
|
(pre_cmds, Execute::At(selector.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Align(align) => {
|
ExecuteBlockHead::Align(align) => {
|
||||||
let align = align.align_selector();
|
let align = align.align_selector().to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::Align(align.into(), Box::new(tail)))
|
(pre_cmds, Execute::Align(align.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Anchored(anchored) => {
|
ExecuteBlockHead::Anchored(anchored) => {
|
||||||
let anchor = anchored.anchored_selector();
|
let anchor = anchored
|
||||||
|
.anchored_selector()
|
||||||
|
.to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::Anchored(anchor.into(), Box::new(tail)))
|
(pre_cmds, Execute::Anchored(anchor.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::In(r#in) => {
|
ExecuteBlockHead::In(r#in) => {
|
||||||
let dimension = r#in.in_selector();
|
let dimension = r#in.in_selector().to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::In(dimension.into(), Box::new(tail)))
|
(pre_cmds, Execute::In(dimension.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Positioned(positioned) => {
|
ExecuteBlockHead::Positioned(positioned) => {
|
||||||
let position = positioned.positioned_selector();
|
let position = positioned
|
||||||
|
.positioned_selector()
|
||||||
|
.to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
|
@ -1026,37 +1031,37 @@ impl Transpiler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Rotated(rotated) => {
|
ExecuteBlockHead::Rotated(rotated) => {
|
||||||
let rotation = rotated.rotated_selector();
|
let rotation = rotated.rotated_selector().to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::Rotated(rotation.into(), Box::new(tail)))
|
(pre_cmds, Execute::Rotated(rotation.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Facing(facing) => {
|
ExecuteBlockHead::Facing(facing) => {
|
||||||
let facing = facing.facing_selector();
|
let facing = facing.facing_selector().to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::Facing(facing.into(), Box::new(tail)))
|
(pre_cmds, Execute::Facing(facing.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::AsAt(as_at) => {
|
ExecuteBlockHead::AsAt(as_at) => {
|
||||||
let selector = as_at.asat_selector();
|
let selector = as_at.asat_selector().to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::AsAt(selector.into(), Box::new(tail)))
|
(pre_cmds, Execute::AsAt(selector.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::On(on) => {
|
ExecuteBlockHead::On(on) => {
|
||||||
let dimension = on.on_selector();
|
let dimension = on.on_selector().to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::On(dimension.into(), Box::new(tail)))
|
(pre_cmds, Execute::On(dimension.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Store(store) => {
|
ExecuteBlockHead::Store(store) => {
|
||||||
let store = store.store_selector();
|
let store = store.store_selector().to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::Store(store.into(), Box::new(tail)))
|
(pre_cmds, Execute::Store(store.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Summon(summon) => {
|
ExecuteBlockHead::Summon(summon) => {
|
||||||
let entity = summon.summon_selector();
|
let entity = summon.summon_selector().to_macro_string(scope, handler)?;
|
||||||
tail.map(|(pre_cmds, tail)| {
|
tail.map(|(pre_cmds, tail)| {
|
||||||
(pre_cmds, Execute::Summon(entity.into(), Box::new(tail)))
|
(pre_cmds, Execute::Summon(entity.into(), Box::new(tail)))
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
//! Utility methods for transpiling
|
//! Utility methods for transpiling
|
||||||
|
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
lexical::token::{MacroStringLiteral, MacroStringLiteralPart},
|
base::{self, source_file::SourceElement as _, Handler},
|
||||||
syntax::syntax_tree::AnyStringLiteral,
|
syntax::syntax_tree::{
|
||||||
|
expression::{Expression, Primary, TemplateStringLiteral, TemplateStringLiteralPart},
|
||||||
|
AnyStringLiteral,
|
||||||
|
},
|
||||||
|
transpile::{
|
||||||
|
error::{TranspileError, UnknownIdentifier},
|
||||||
|
Scope, TranspileResult, VariableData,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::expression::ComptimeValue;
|
||||||
|
|
||||||
/// String that can contain macros
|
/// String that can contain macros
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
@ -247,53 +256,90 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&AnyStringLiteral> for MacroString {
|
impl AnyStringLiteral {
|
||||||
fn from(value: &AnyStringLiteral) -> Self {
|
/// Convert the any string literal to a macro string, using the provided scope to resolve variables
|
||||||
match value {
|
///
|
||||||
AnyStringLiteral::StringLiteral(literal) => Self::from(literal.str_content().as_ref()),
|
/// # Errors
|
||||||
AnyStringLiteral::MacroStringLiteral(literal) => Self::from(literal),
|
/// - If an identifier in a template string is not found in the scope
|
||||||
|
pub fn to_macro_string(
|
||||||
|
&self,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<MacroString> {
|
||||||
|
match self {
|
||||||
|
Self::StringLiteral(literal) => Ok(MacroString::from(literal.str_content().as_ref())),
|
||||||
|
Self::TemplateStringLiteral(literal) => literal.to_macro_string(scope, handler),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AnyStringLiteral> for MacroString {
|
impl TemplateStringLiteral {
|
||||||
fn from(value: AnyStringLiteral) -> Self {
|
/// Convert the template string literal to a macro string, using the provided scope to resolve variables
|
||||||
Self::from(&value)
|
///
|
||||||
}
|
/// # Errors
|
||||||
}
|
/// - If an identifier in a template string is not found in the scope
|
||||||
|
pub fn to_macro_string(
|
||||||
impl From<&MacroStringLiteral> for MacroString {
|
&self,
|
||||||
fn from(value: &MacroStringLiteral) -> Self {
|
scope: &Arc<Scope>,
|
||||||
if value
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<MacroString> {
|
||||||
|
if self
|
||||||
.parts()
|
.parts()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|p| matches!(p, MacroStringLiteralPart::MacroUsage { .. }))
|
.any(|p| matches!(p, TemplateStringLiteralPart::Expression { .. }))
|
||||||
{
|
{
|
||||||
Self::MacroString(
|
let macro_string = MacroString::MacroString(
|
||||||
value
|
self.parts()
|
||||||
.parts()
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
MacroStringLiteralPart::Text(span) => MacroStringPart::String(
|
TemplateStringLiteralPart::Text(text) => Ok(MacroStringPart::String(
|
||||||
crate::util::unescape_macro_string(span.str()).to_string(),
|
crate::util::unescape_template_string(text.span.str()).into_owned(),
|
||||||
),
|
)),
|
||||||
MacroStringLiteralPart::MacroUsage { identifier, .. } => {
|
TemplateStringLiteralPart::Expression { expression, .. } => {
|
||||||
MacroStringPart::MacroUsage(
|
match expression.as_ref() {
|
||||||
crate::util::identifier_to_macro(identifier.span.str()).to_string(),
|
Expression::Primary(Primary::Identifier(identifier)) =>
|
||||||
)
|
{
|
||||||
|
#[expect(clippy::option_if_let_else)]
|
||||||
|
if let Some(var_data) =
|
||||||
|
scope.get_variable(identifier.span.str())
|
||||||
|
{
|
||||||
|
match var_data.as_ref() {
|
||||||
|
VariableData::MacroParameter { macro_name, .. } => Ok(
|
||||||
|
MacroStringPart::MacroUsage(macro_name.to_owned()),
|
||||||
|
),
|
||||||
|
VariableData::ComptimeValue { value, .. } => {
|
||||||
|
let value = value.read().unwrap().as_ref().map_or_else(
|
||||||
|
|| "null".into(),
|
||||||
|
ComptimeValue::to_macro_string,
|
||||||
|
);
|
||||||
|
|
||||||
|
match value.as_str() {
|
||||||
|
Ok(s) => Ok(MacroStringPart::String(s.into_owned())),
|
||||||
|
Err(_) => todo!("comptime value resulting in macro string with macros")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => todo!("other identifiers in template strings"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let err =
|
||||||
|
TranspileError::UnknownIdentifier(UnknownIdentifier {
|
||||||
|
identifier: identifier.span(),
|
||||||
|
});
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => todo!("other expressions in template strings"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect::<TranspileResult<Vec<MacroStringPart>>>()?,
|
||||||
)
|
);
|
||||||
} else {
|
|
||||||
Self::String(value.str_content())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MacroStringLiteral> for MacroString {
|
Ok(macro_string)
|
||||||
fn from(value: MacroStringLiteral) -> Self {
|
} else {
|
||||||
Self::from(&value)
|
Ok(MacroString::String(self.as_str(scope, handler)?.into_owned()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
21
src/util.rs
21
src/util.rs
|
@ -20,15 +20,16 @@ pub fn escape_str(s: &str) -> Cow<'_, str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unescapes '\`', `\`, `\n`, `\r` and `\t` in a string.
|
/// Unescapes '\`', `\`, `\n`, `\r` and `\t`, `\$` in a string.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn unescape_macro_string(s: &str) -> Cow<'_, str> {
|
pub fn unescape_template_string(s: &str) -> Cow<'_, str> {
|
||||||
if s.contains('\\') || s.contains('`') {
|
if s.contains('\\') || s.contains('`') {
|
||||||
Cow::Owned(
|
Cow::Owned(
|
||||||
s.replace("\\n", "\n")
|
s.replace("\\n", "\n")
|
||||||
.replace("\\r", "\r")
|
.replace("\\r", "\r")
|
||||||
.replace("\\t", "\t")
|
.replace("\\t", "\t")
|
||||||
.replace("\\`", "`")
|
.replace("\\`", "`")
|
||||||
|
.replace("\\$", "$")
|
||||||
.replace("\\\\", "\\"),
|
.replace("\\\\", "\\"),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -147,33 +148,33 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unescape_macro_string() {
|
fn test_unescape_macro_string() {
|
||||||
assert_eq!(unescape_macro_string("Hello, world!"), "Hello, world!");
|
assert_eq!(unescape_template_string("Hello, world!"), "Hello, world!");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unescape_macro_string(r#"Hello, "world"!"#),
|
unescape_template_string(r#"Hello, "world"!"#),
|
||||||
r#"Hello, "world"!"#
|
r#"Hello, "world"!"#
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unescape_macro_string(r"Hello, \world\!"),
|
unescape_template_string(r"Hello, \world\!"),
|
||||||
r"Hello, \world\!"
|
r"Hello, \world\!"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unescape_macro_string(r"Hello, \nworld\!"),
|
unescape_template_string(r"Hello, \nworld\!"),
|
||||||
"Hello, \nworld\\!"
|
"Hello, \nworld\\!"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unescape_macro_string(r"Hello, \rworld\!"),
|
unescape_template_string(r"Hello, \rworld\!"),
|
||||||
"Hello, \rworld\\!"
|
"Hello, \rworld\\!"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unescape_macro_string(r"Hello, \tworld\!"),
|
unescape_template_string(r"Hello, \tworld\!"),
|
||||||
"Hello, \tworld\\!"
|
"Hello, \tworld\\!"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unescape_macro_string(r"Hello, \`world\!"),
|
unescape_template_string(r"Hello, \`world\!"),
|
||||||
r"Hello, `world\!"
|
r"Hello, `world\!"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unescape_macro_string(r"Hello, \\world\!"),
|
unescape_template_string(r"Hello, \\world\!"),
|
||||||
r"Hello, \world\!"
|
r"Hello, \world\!"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue