625 lines
19 KiB
Rust
625 lines
19 KiB
Rust
//! Syntax tree nodes for declarations.
|
|
|
|
#![expect(missing_docs)]
|
|
#![expect(clippy::struct_field_names)]
|
|
|
|
use std::collections::VecDeque;
|
|
|
|
use enum_as_inner::EnumAsInner;
|
|
use getset::Getters;
|
|
|
|
use crate::{
|
|
base::{
|
|
self,
|
|
source_file::{SourceElement, Span},
|
|
Handler, VoidHandler,
|
|
},
|
|
lexical::{
|
|
token::{Identifier, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
|
|
token_stream::Delimiter,
|
|
},
|
|
syntax::{
|
|
error::{Error, InvalidAnnotation, ParseResult, SyntaxKind, UnexpectedSyntax},
|
|
parser::{Parser, Reading},
|
|
},
|
|
};
|
|
|
|
use super::{
|
|
statement::{Block, VariableDeclaration},
|
|
Annotation, ConnectedList, DelimitedList,
|
|
};
|
|
|
|
/// Represents a declaration in the syntax tree.
|
|
///
|
|
/// Syntax Synopsis:
|
|
///
|
|
/// ```ebnf
|
|
/// Declaration:
|
|
/// Function
|
|
/// | Import
|
|
/// | TagDeclaration
|
|
/// | ('pub'? VariableDeclaration ';')
|
|
/// ;
|
|
/// ```
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum Declaration {
|
|
Function(Function),
|
|
Import(Import),
|
|
Tag(Tag),
|
|
GlobalVariable((Option<Keyword>, VariableDeclaration, Punctuation)),
|
|
}
|
|
|
|
impl SourceElement for Declaration {
|
|
fn span(&self) -> Span {
|
|
match self {
|
|
Self::Function(function) => function.span(),
|
|
Self::Import(import) => import.span(),
|
|
Self::Tag(tag) => tag.span(),
|
|
Self::GlobalVariable((pub_kw, variable, semicolon)) => pub_kw
|
|
.as_ref()
|
|
.map_or_else(|| variable.span(), SourceElement::span)
|
|
.join(&semicolon.span)
|
|
.expect("invalid declaration span"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Declaration {
|
|
/// Adds an annotation to the declaration.
|
|
///
|
|
/// # Errors
|
|
/// - if the annotation is invalid for the target declaration.
|
|
pub fn with_annotation(self, annotation: Annotation) -> ParseResult<Self> {
|
|
match self {
|
|
Self::Function(mut function) => {
|
|
function.annotations.push_front(annotation);
|
|
|
|
Ok(Self::Function(function))
|
|
}
|
|
Self::GlobalVariable((pub_kw, var, semi)) => {
|
|
let var_with_annotation = var.with_annotation(annotation)?;
|
|
|
|
Ok(Self::GlobalVariable((pub_kw, var_with_annotation, semi)))
|
|
}
|
|
_ => {
|
|
let err = Error::InvalidAnnotation(InvalidAnnotation {
|
|
annotation: annotation.assignment.identifier.span,
|
|
target: "declarations except functions".to_string(),
|
|
});
|
|
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents a function declaration in the syntax tree.
|
|
///
|
|
/// Syntax Synopsis:
|
|
///
|
|
/// ```ebnf
|
|
/// Function:
|
|
/// Annotation* 'pub'? 'fn' Identifier '(' FunctionParameterList? ')' Block
|
|
/// ;
|
|
///
|
|
/// FunctionParameterList:
|
|
/// FunctionArgument (',' FunctionArgument)* ','?
|
|
/// ;
|
|
/// ```
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
|
pub struct Function {
|
|
#[get = "pub"]
|
|
public_keyword: Option<Keyword>,
|
|
#[get = "pub"]
|
|
annotations: VecDeque<Annotation>,
|
|
#[get = "pub"]
|
|
function_keyword: Keyword,
|
|
#[get = "pub"]
|
|
identifier: Identifier,
|
|
#[get = "pub"]
|
|
open_paren: Punctuation,
|
|
#[get = "pub"]
|
|
parameters: Option<ConnectedList<FunctionParameter, Punctuation>>,
|
|
#[get = "pub"]
|
|
close_paren: Punctuation,
|
|
#[get = "pub"]
|
|
block: Block,
|
|
}
|
|
|
|
impl Function {
|
|
/// Dissolves the [`Function`] into its components.
|
|
#[must_use]
|
|
#[allow(clippy::type_complexity)]
|
|
pub fn dissolve(
|
|
self,
|
|
) -> (
|
|
Option<Keyword>,
|
|
VecDeque<Annotation>,
|
|
Keyword,
|
|
Identifier,
|
|
Punctuation,
|
|
Option<ConnectedList<FunctionParameter, Punctuation>>,
|
|
Punctuation,
|
|
Block,
|
|
) {
|
|
(
|
|
self.public_keyword,
|
|
self.annotations,
|
|
self.function_keyword,
|
|
self.identifier,
|
|
self.open_paren,
|
|
self.parameters,
|
|
self.close_paren,
|
|
self.block,
|
|
)
|
|
}
|
|
|
|
/// Returns `true` if the function is public.
|
|
#[must_use]
|
|
pub fn is_public(&self) -> bool {
|
|
self.public_keyword.is_some()
|
|
}
|
|
}
|
|
|
|
impl SourceElement for Function {
|
|
fn span(&self) -> Span {
|
|
self.public_keyword
|
|
.as_ref()
|
|
.map_or_else(|| self.function_keyword.span(), SourceElement::span)
|
|
.join(&self.block.span())
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
// Represents a variable type keyword for function arguments.
|
|
///
|
|
/// Syntax Synopsis:
|
|
///
|
|
/// ```ebnf
|
|
/// FunctionVariableType:
|
|
/// 'macro' | 'int' | 'bool'
|
|
/// ;
|
|
/// ```
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
|
|
pub enum FunctionVariableType {
|
|
Macro(Keyword),
|
|
Integer(Keyword),
|
|
Boolean(Keyword),
|
|
}
|
|
|
|
/// Represents a function argument in the syntax tree.
|
|
///
|
|
/// Syntax Synopsis:
|
|
///
|
|
/// ```ebnf
|
|
/// FunctionArgument:
|
|
/// FunctionVariableType Identifier
|
|
/// ;
|
|
/// ```
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
|
pub struct FunctionParameter {
|
|
#[get = "pub"]
|
|
variable_type: FunctionVariableType,
|
|
#[get = "pub"]
|
|
identifier: Identifier,
|
|
}
|
|
|
|
/// Represents an import declaration in the syntax tree.
|
|
///
|
|
/// Syntax Synopsis:
|
|
///
|
|
/// ```ebnf
|
|
/// Import:
|
|
/// 'from' StringLiteral 'import' ('*' | Identifier (',' Identifier)*) ';'
|
|
/// ;
|
|
/// ```
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
|
pub struct Import {
|
|
#[get = "pub"]
|
|
from_keyword: Keyword,
|
|
#[get = "pub"]
|
|
module: StringLiteral,
|
|
#[get = "pub"]
|
|
import_keyword: Keyword,
|
|
#[get = "pub"]
|
|
items: ImportItems,
|
|
#[get = "pub"]
|
|
semicolon: Punctuation,
|
|
}
|
|
|
|
/// Items to import.
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum ImportItems {
|
|
All(Punctuation),
|
|
Named(ConnectedList<Identifier, Punctuation>),
|
|
}
|
|
|
|
impl Import {
|
|
/// Dissolves the [`Import`] into its components.
|
|
#[must_use]
|
|
pub fn dissolve(self) -> (Keyword, StringLiteral, Keyword, ImportItems, Punctuation) {
|
|
(
|
|
self.from_keyword,
|
|
self.module,
|
|
self.import_keyword,
|
|
self.items,
|
|
self.semicolon,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl SourceElement for Import {
|
|
fn span(&self) -> Span {
|
|
self.from_keyword
|
|
.span()
|
|
.join(&self.semicolon.span())
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
/// Represents a tag declaration in the syntax tree.
|
|
///
|
|
/// Syntax Synopsis:
|
|
///
|
|
/// ```ebnf
|
|
/// TagDeclaration:
|
|
/// 'tag' ('<' StringLiteral '>')? StringLiteral 'replace'? '[' (StringLiteral (',' StringLiteral)*)? ']'
|
|
/// ;
|
|
/// ```
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
|
pub struct Tag {
|
|
#[get = "pub"]
|
|
tag_keyword: Keyword,
|
|
#[get = "pub"]
|
|
of_type: Option<(Punctuation, StringLiteral, Punctuation)>,
|
|
#[get = "pub"]
|
|
name: StringLiteral,
|
|
#[get = "pub"]
|
|
replace: Option<Keyword>,
|
|
#[get = "pub"]
|
|
entries: DelimitedList<StringLiteral>,
|
|
}
|
|
|
|
impl Tag {
|
|
#[must_use]
|
|
#[allow(clippy::type_complexity)]
|
|
pub fn dissolve(
|
|
self,
|
|
) -> (
|
|
Keyword,
|
|
Option<(Punctuation, StringLiteral, Punctuation)>,
|
|
StringLiteral,
|
|
Option<Keyword>,
|
|
DelimitedList<StringLiteral>,
|
|
) {
|
|
(
|
|
self.tag_keyword,
|
|
self.of_type,
|
|
self.name,
|
|
self.replace,
|
|
self.entries,
|
|
)
|
|
}
|
|
|
|
#[cfg(feature = "shulkerbox")]
|
|
#[must_use]
|
|
pub fn tag_type(&self) -> shulkerbox::datapack::tag::TagType {
|
|
use shulkerbox::datapack::tag::TagType;
|
|
|
|
self.of_type
|
|
.as_ref()
|
|
.map_or(TagType::Function, |(_, tag_type, _)| {
|
|
match tag_type.str_content().as_ref() {
|
|
"function" => TagType::Function,
|
|
"block" => TagType::Block,
|
|
"entity_type" => TagType::Entity,
|
|
"fluid" => TagType::Fluid,
|
|
"game_event" => TagType::GameEvent,
|
|
"item" => TagType::Item,
|
|
other => TagType::Other(other.to_string()),
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl SourceElement for Tag {
|
|
fn span(&self) -> Span {
|
|
self.tag_keyword
|
|
.span()
|
|
.join(&self.entries.close.span)
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
impl Parser<'_> {
|
|
/// Parses a declaration
|
|
///
|
|
/// # Errors
|
|
/// - cannot parse declaration from current position
|
|
#[expect(clippy::too_many_lines)]
|
|
#[tracing::instrument(level = "trace", skip_all)]
|
|
pub fn parse_declaration(
|
|
&mut self,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> ParseResult<Declaration> {
|
|
match self.stop_at_significant() {
|
|
Reading::Atomic(Token::Keyword(function_keyword))
|
|
if function_keyword.keyword == KeywordKind::Function =>
|
|
{
|
|
let function = self.parse_function(handler)?;
|
|
|
|
tracing::trace!("Parsed function '{:?}'", function.identifier.span.str());
|
|
|
|
Ok(Declaration::Function(function))
|
|
}
|
|
|
|
Reading::Atomic(Token::Keyword(pub_keyword))
|
|
if pub_keyword.keyword == KeywordKind::Pub =>
|
|
{
|
|
match self.peek_offset(2) {
|
|
Some(Reading::Atomic(Token::Keyword(function_keyword)))
|
|
if function_keyword.keyword == KeywordKind::Function =>
|
|
{
|
|
let function = self.parse_function(handler)?;
|
|
tracing::trace!("Parsed function '{:?}'", function.identifier.span.str());
|
|
|
|
Ok(Declaration::Function(function))
|
|
}
|
|
_ => {
|
|
// eat the pub keyword
|
|
self.forward();
|
|
|
|
let var = self.parse_variable_declaration(handler)?;
|
|
let semi = self.parse_punctuation(';', true, handler)?;
|
|
|
|
Ok(Declaration::GlobalVariable((Some(pub_keyword), var, semi)))
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse annotations
|
|
Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => {
|
|
// parse the annotation
|
|
let annotation = self.parse_annotation(handler)?;
|
|
let declaration = self.parse_declaration(handler)?;
|
|
|
|
declaration
|
|
.with_annotation(annotation)
|
|
.inspect_err(|err| handler.receive(Box::new(err.clone())))
|
|
}
|
|
|
|
Reading::Atomic(Token::Keyword(from_keyword))
|
|
if from_keyword.keyword == KeywordKind::From =>
|
|
{
|
|
// eat the from keyword
|
|
self.forward();
|
|
|
|
// parse the module
|
|
let module = self.parse_string_literal(handler)?;
|
|
|
|
let import_keyword = self.parse_keyword(KeywordKind::Import, handler)?;
|
|
|
|
// TODO: re-enable when the asterisk is supported
|
|
let items = // match self.stop_at_significant() {
|
|
// Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '*' => {
|
|
// eat the asterisk
|
|
// self.forward();
|
|
|
|
// ImportItems::All(punc)
|
|
// }
|
|
// _ =>
|
|
self.try_parse(|parser| parser
|
|
.parse_connected_list(
|
|
',',
|
|
|parser| parser.parse_identifier(&VoidHandler),
|
|
handler,
|
|
)
|
|
.map(ImportItems::Named)) // ,
|
|
// }
|
|
;
|
|
|
|
if let Ok(items) = items {
|
|
let semicolon = self.parse_punctuation(';', true, handler)?;
|
|
|
|
tracing::trace!("Parsed import from '{:?}'", module.str_content());
|
|
|
|
Ok(Declaration::Import(Import {
|
|
from_keyword,
|
|
module,
|
|
import_keyword,
|
|
items,
|
|
semicolon,
|
|
}))
|
|
} else {
|
|
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
|
expected: SyntaxKind::Punctuation('*'),
|
|
found: self.stop_at_significant().into_token(),
|
|
});
|
|
handler.receive(Box::new(err.clone()));
|
|
|
|
Err(err)
|
|
}
|
|
}
|
|
|
|
Reading::Atomic(Token::Keyword(tag_keyword))
|
|
if tag_keyword.keyword == KeywordKind::Tag =>
|
|
{
|
|
// eat the tag keyword
|
|
self.forward();
|
|
|
|
let of_type = match self.stop_at_significant() {
|
|
Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '<' => {
|
|
// eat the open bracket
|
|
self.forward();
|
|
|
|
let of_type = self.parse_string_literal(handler)?;
|
|
|
|
// eat the close bracket
|
|
let closing = self.parse_punctuation('>', true, handler)?;
|
|
|
|
Some((punc, of_type, closing))
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
// parse the name
|
|
let name = self.parse_string_literal(handler)?;
|
|
|
|
let replace = self
|
|
.try_parse(|parser| parser.parse_keyword(KeywordKind::Replace, &VoidHandler))
|
|
.ok();
|
|
|
|
let entries = self.parse_enclosed_list(
|
|
Delimiter::Bracket,
|
|
',',
|
|
|parser| parser.parse_string_literal(handler),
|
|
handler,
|
|
)?;
|
|
|
|
Ok(Declaration::Tag(Tag {
|
|
tag_keyword,
|
|
of_type,
|
|
name,
|
|
replace,
|
|
entries,
|
|
}))
|
|
}
|
|
|
|
Reading::Atomic(Token::Keyword(keyword))
|
|
if matches!(
|
|
keyword.keyword,
|
|
KeywordKind::Int | KeywordKind::Bool | KeywordKind::Val
|
|
) =>
|
|
{
|
|
let var = self.parse_variable_declaration(handler)?;
|
|
let semi = self.parse_punctuation(';', true, handler)?;
|
|
|
|
Ok(Declaration::GlobalVariable((None, var, semi)))
|
|
}
|
|
|
|
unexpected => {
|
|
// make progress
|
|
self.forward();
|
|
|
|
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
|
expected: SyntaxKind::Declaration,
|
|
found: unexpected.into_token(),
|
|
});
|
|
handler.receive(Box::new(err.clone()));
|
|
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parses a function.
|
|
///
|
|
/// # Errors
|
|
/// - if the parser is not at a function (not at annotation).
|
|
/// - if the parsing of the function fails.
|
|
pub fn parse_function(&mut self, handler: &impl Handler<base::Error>) -> ParseResult<Function> {
|
|
let pub_keyword =
|
|
self.try_parse(|parser| parser.parse_keyword(KeywordKind::Pub, &VoidHandler));
|
|
|
|
match self.stop_at_significant() {
|
|
Reading::Atomic(Token::Keyword(function_keyword))
|
|
if function_keyword.keyword == KeywordKind::Function =>
|
|
{
|
|
// eat the function keyword
|
|
self.forward();
|
|
|
|
// parse the identifier
|
|
let identifier = self.parse_identifier(handler)?;
|
|
let delimited_tree = self.parse_enclosed_list(
|
|
Delimiter::Parenthesis,
|
|
',',
|
|
|parser: &mut Parser<'_>| parser.parse_function_parameter(handler),
|
|
handler,
|
|
)?;
|
|
|
|
// parse the block
|
|
let block = self.parse_block(handler)?;
|
|
|
|
Ok(Function {
|
|
public_keyword: pub_keyword.ok(),
|
|
annotations: VecDeque::new(),
|
|
function_keyword,
|
|
identifier,
|
|
open_paren: delimited_tree.open,
|
|
parameters: delimited_tree.list,
|
|
close_paren: delimited_tree.close,
|
|
block,
|
|
})
|
|
}
|
|
unexpected => {
|
|
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
|
expected: SyntaxKind::Keyword(KeywordKind::Function),
|
|
found: unexpected.into_token(),
|
|
});
|
|
handler.receive(Box::new(err.clone()));
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_function_parameter(
|
|
&mut self,
|
|
handler: &impl Handler<base::Error>,
|
|
) -> ParseResult<FunctionParameter> {
|
|
match self.stop_at_significant() {
|
|
Reading::Atomic(Token::Keyword(keyword)) if keyword.keyword == KeywordKind::Int => {
|
|
let variable_type = FunctionVariableType::Integer(keyword);
|
|
self.forward();
|
|
|
|
let identifier = self.parse_identifier(handler)?;
|
|
|
|
Ok(FunctionParameter {
|
|
variable_type,
|
|
identifier,
|
|
})
|
|
}
|
|
Reading::Atomic(Token::Keyword(keyword)) if keyword.keyword == KeywordKind::Bool => {
|
|
let variable_type = FunctionVariableType::Boolean(keyword);
|
|
self.forward();
|
|
|
|
let identifier = self.parse_identifier(handler)?;
|
|
|
|
Ok(FunctionParameter {
|
|
variable_type,
|
|
identifier,
|
|
})
|
|
}
|
|
Reading::Atomic(Token::Keyword(keyword)) if keyword.keyword == KeywordKind::Macro => {
|
|
let variable_type = FunctionVariableType::Macro(keyword);
|
|
self.forward();
|
|
|
|
let identifier = self.parse_identifier(handler)?;
|
|
|
|
Ok(FunctionParameter {
|
|
variable_type,
|
|
identifier,
|
|
})
|
|
}
|
|
unexpected => {
|
|
let err = Error::UnexpectedSyntax(UnexpectedSyntax {
|
|
expected: SyntaxKind::Either(&[
|
|
SyntaxKind::Keyword(KeywordKind::Int),
|
|
SyntaxKind::Keyword(KeywordKind::Bool),
|
|
SyntaxKind::Keyword(KeywordKind::Macro),
|
|
]),
|
|
found: unexpected.into_token(),
|
|
});
|
|
handler.receive(Box::new(err.clone()));
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
}
|