//! 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, 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 { 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, #[get = "pub"] annotations: VecDeque, #[get = "pub"] function_keyword: Keyword, #[get = "pub"] identifier: Identifier, #[get = "pub"] open_paren: Punctuation, #[get = "pub"] parameters: Option>, #[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, VecDeque, Keyword, Identifier, Punctuation, Option>, 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' | 'val' /// ; /// ``` #[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), Value(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), } 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, #[get = "pub"] entries: DelimitedList, } impl Tag { #[must_use] #[allow(clippy::type_complexity)] pub fn dissolve( self, ) -> ( Keyword, Option<(Punctuation, StringLiteral, Punctuation)>, StringLiteral, Option, DelimitedList, ) { ( 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, ) -> ParseResult { 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) -> ParseResult { 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, ) -> ParseResult { 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, }) } Reading::Atomic(Token::Keyword(keyword)) if keyword.keyword == KeywordKind::Val => { let variable_type = FunctionVariableType::Value(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), SyntaxKind::Keyword(KeywordKind::Val), ]), found: unexpected.into_token(), }); handler.receive(Box::new(err.clone())); Err(err) } } } }