Implement annotations for functions
This commit is contained in:
		
							parent
							
								
									d4305b3629
								
							
						
					
					
						commit
						b9bc5438e5
					
				| 
						 | 
				
			
			@ -15,13 +15,18 @@ Declaration: FunctionDeclaration;
 | 
			
		|||
### FunctionDeclaration
 | 
			
		||||
```ebnf
 | 
			
		||||
Function:
 | 
			
		||||
    'fn' Identifier '(' ParameterList? ')' Block
 | 
			
		||||
    Annotation* 'fn' Identifier '(' ParameterList? ')' Block
 | 
			
		||||
    ;
 | 
			
		||||
ParameterList:
 | 
			
		||||
    Identifier (',' Identifier)* ','?  
 | 
			
		||||
    ;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Annotation
 | 
			
		||||
```ebnf
 | 
			
		||||
Annotation: '#[' Identifier ('=' StringLiteral)? ']';
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Statement
 | 
			
		||||
```ebnf
 | 
			
		||||
Statement:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ use std::{
 | 
			
		|||
    fs,
 | 
			
		||||
    iter::{Iterator, Peekable},
 | 
			
		||||
    ops::Range,
 | 
			
		||||
    path::PathBuf,
 | 
			
		||||
    path::{Path, PathBuf},
 | 
			
		||||
    str::CharIndices,
 | 
			
		||||
    sync::Arc,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -85,9 +85,9 @@ impl SourceFile {
 | 
			
		|||
    ///
 | 
			
		||||
    /// # Errors
 | 
			
		||||
    /// - [`Error::IoError`]: Error occurred when reading the file contents.
 | 
			
		||||
    pub fn load(path: PathBuf) -> Result<Arc<Self>, Error> {
 | 
			
		||||
        let source = fs::read_to_string(&path).map_err(Error::IoError)?;
 | 
			
		||||
        Ok(Self::new(path, source))
 | 
			
		||||
    pub fn load(path: &Path) -> Result<Arc<Self>, Error> {
 | 
			
		||||
        let source = fs::read_to_string(path).map_err(Error::IoError)?;
 | 
			
		||||
        Ok(Self::new(path.to_path_buf(), source))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the [`Location`] of a given byte index
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ use crate::base::{
 | 
			
		|||
    Handler,
 | 
			
		||||
};
 | 
			
		||||
use derive_more::From;
 | 
			
		||||
use enum_as_inner::EnumAsInner;
 | 
			
		||||
use strum::IntoEnumIterator;
 | 
			
		||||
use strum_macros::EnumIter;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -64,8 +65,8 @@ impl KeywordKind {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Is an enumeration containing all kinds of tokens in the Flux programming language.
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From)]
 | 
			
		||||
/// Is an enumeration containing all kinds of tokens in the Shulkerscript programming language.
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From, EnumAsInner)]
 | 
			
		||||
#[allow(missing_docs)]
 | 
			
		||||
pub enum Token {
 | 
			
		||||
    WhiteSpaces(WhiteSpaces),
 | 
			
		||||
| 
						 | 
				
			
			@ -202,7 +203,7 @@ impl SourceElement for StringLiteral {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Is an enumeration representing the two kinds of comments in the Flux programming language.
 | 
			
		||||
/// Is an enumeration representing the two kinds of comments in the Shulkerscript programming language.
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
			
		||||
pub enum CommentKind {
 | 
			
		||||
    /// A comment that starts with `//` and ends at the end of the line.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -173,8 +173,11 @@ pub enum TokenTree {
 | 
			
		|||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
			
		||||
#[allow(missing_docs)]
 | 
			
		||||
pub enum Delimiter {
 | 
			
		||||
    /// ()
 | 
			
		||||
    Parenthesis,
 | 
			
		||||
    /// {}
 | 
			
		||||
    Brace,
 | 
			
		||||
    /// []
 | 
			
		||||
    Bracket,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										43
									
								
								src/lib.rs
								
								
								
								
							
							
						
						
									
										43
									
								
								src/lib.rs
								
								
								
								
							| 
						 | 
				
			
			@ -17,19 +17,58 @@ pub mod compile;
 | 
			
		|||
pub mod lexical;
 | 
			
		||||
pub mod syntax;
 | 
			
		||||
 | 
			
		||||
use std::{cell::Cell, fmt::Display, path::PathBuf};
 | 
			
		||||
use std::{cell::Cell, fmt::Display, path::Path};
 | 
			
		||||
 | 
			
		||||
use base::{source_file::SourceFile, Handler, Result};
 | 
			
		||||
use compile::compiler::Compiler;
 | 
			
		||||
use shulkerbox::{util::compile::CompileOptions, virtual_fs::VFolder};
 | 
			
		||||
use syntax::syntax_tree::program::Program;
 | 
			
		||||
 | 
			
		||||
use crate::{base::Error, lexical::token_stream::TokenStream, syntax::parser::Parser};
 | 
			
		||||
 | 
			
		||||
/// Converts the given source code to tokens.
 | 
			
		||||
///
 | 
			
		||||
/// # Errors
 | 
			
		||||
/// - If an error occurs while reading the file.
 | 
			
		||||
pub fn tokenize(path: &Path) -> Result<TokenStream> {
 | 
			
		||||
    let source_file = SourceFile::load(path)?;
 | 
			
		||||
 | 
			
		||||
    let printer = Printer::new();
 | 
			
		||||
 | 
			
		||||
    Ok(TokenStream::tokenize(&source_file, &printer))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Parses the given source code.
 | 
			
		||||
///
 | 
			
		||||
/// # Errors
 | 
			
		||||
/// - If an error occurs while reading the file.
 | 
			
		||||
/// - If an error occurs while parsing the source code.
 | 
			
		||||
pub fn parse(path: &Path) -> Result<Program> {
 | 
			
		||||
    let source_file = SourceFile::load(path)?;
 | 
			
		||||
 | 
			
		||||
    let printer = Printer::new();
 | 
			
		||||
 | 
			
		||||
    let tokens = TokenStream::tokenize(&source_file, &printer);
 | 
			
		||||
 | 
			
		||||
    if printer.has_printed() {
 | 
			
		||||
        return Err(Error::Other(
 | 
			
		||||
            "An error occurred while tokenizing the source code.",
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut parser = Parser::new(&tokens);
 | 
			
		||||
    let program = parser.parse_program(&printer).ok_or(Error::Other(
 | 
			
		||||
        "An error occured while parsing the source code.",
 | 
			
		||||
    ))?;
 | 
			
		||||
 | 
			
		||||
    Ok(program)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Compiles the given source code.
 | 
			
		||||
///
 | 
			
		||||
/// # Errors
 | 
			
		||||
/// - If an error occurs while reading the file.
 | 
			
		||||
pub fn compile(path: PathBuf) -> Result<VFolder> {
 | 
			
		||||
pub fn compile(path: &Path) -> Result<VFolder> {
 | 
			
		||||
    let source_file = SourceFile::load(path)?;
 | 
			
		||||
 | 
			
		||||
    let printer = Printer::new();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -142,6 +142,20 @@ impl<'a> Parser<'a> {
 | 
			
		|||
            close: close_punctuation,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Tries to parse the given function, and if it fails, resets the current index to the
 | 
			
		||||
    /// `current_index` before the function call.
 | 
			
		||||
    pub fn try_parse<T>(&mut self, f: impl FnOnce(&mut Self) -> Option<T>) -> Option<T> {
 | 
			
		||||
        let current_index = self.current_frame.current_index;
 | 
			
		||||
 | 
			
		||||
        let result = f(self);
 | 
			
		||||
 | 
			
		||||
        if result.is_none() {
 | 
			
		||||
            self.current_frame.current_index = current_index;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        result
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a result of [`Parser::step_into()`] function.
 | 
			
		||||
| 
						 | 
				
			
			@ -409,20 +423,6 @@ impl<'a> Frame<'a> {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Tries to parse the given function, and if it fails, resets the current index to the
 | 
			
		||||
    /// `current_index` before the function call.
 | 
			
		||||
    pub fn try_parse<T>(&mut self, f: impl FnOnce(&mut Self) -> Option<T>) -> Option<T> {
 | 
			
		||||
        let current_index = self.current_index;
 | 
			
		||||
 | 
			
		||||
        let result = f(self);
 | 
			
		||||
 | 
			
		||||
        if result.is_none() {
 | 
			
		||||
            self.current_index = current_index;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        result
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents the read value of the [`Frame`].
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@ use crate::{
 | 
			
		|||
        Handler,
 | 
			
		||||
    },
 | 
			
		||||
    lexical::{
 | 
			
		||||
        token::{Identifier, Keyword, KeywordKind, Punctuation, Token},
 | 
			
		||||
        token::{Identifier, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
 | 
			
		||||
        token_stream::Delimiter,
 | 
			
		||||
    },
 | 
			
		||||
    syntax::{
 | 
			
		||||
| 
						 | 
				
			
			@ -33,12 +33,62 @@ impl SourceElement for Declaration {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
/// Syntax Synopsis:
 | 
			
		||||
///
 | 
			
		||||
/// ``` ebnf
 | 
			
		||||
/// Annotation:
 | 
			
		||||
///     '#[' Identifier ('=' StringLiteral)? ']'
 | 
			
		||||
///     ;
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
 | 
			
		||||
pub struct Annotation {
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    pound_sign: Punctuation,
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    open_bracket: Punctuation,
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    identifier: Identifier,
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    value: Option<(Punctuation, StringLiteral)>,
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    close_bracket: Punctuation,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Annotation {
 | 
			
		||||
    /// Dissolves the [`Annotation`] into its components.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn dissolve(
 | 
			
		||||
        self,
 | 
			
		||||
    ) -> (
 | 
			
		||||
        Punctuation,
 | 
			
		||||
        Punctuation,
 | 
			
		||||
        Identifier,
 | 
			
		||||
        Option<(Punctuation, StringLiteral)>,
 | 
			
		||||
        Punctuation,
 | 
			
		||||
    ) {
 | 
			
		||||
        (
 | 
			
		||||
            self.pound_sign,
 | 
			
		||||
            self.open_bracket,
 | 
			
		||||
            self.identifier,
 | 
			
		||||
            self.value,
 | 
			
		||||
            self.close_bracket,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
impl SourceElement for Annotation {
 | 
			
		||||
    fn span(&self) -> Span {
 | 
			
		||||
        self.pound_sign
 | 
			
		||||
            .span
 | 
			
		||||
            .join(&self.close_bracket.span())
 | 
			
		||||
            .unwrap()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Syntax Synopsis:
 | 
			
		||||
///
 | 
			
		||||
/// ``` ebnf
 | 
			
		||||
/// Function:
 | 
			
		||||
///     'fn' Identifier '(' ParameterList? ')' Block
 | 
			
		||||
///     Annotation* 'fn' Identifier '(' ParameterList? ')' Block
 | 
			
		||||
///     ;
 | 
			
		||||
///
 | 
			
		||||
/// ParameterList:
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +97,8 @@ impl SourceElement for Declaration {
 | 
			
		|||
/// ```
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
 | 
			
		||||
pub struct Function {
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    annotations: Vec<Annotation>,
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
    function_keyword: Keyword,
 | 
			
		||||
    #[get = "pub"]
 | 
			
		||||
| 
						 | 
				
			
			@ -64,9 +116,11 @@ pub struct Function {
 | 
			
		|||
impl Function {
 | 
			
		||||
    /// Dissolves the [`Function`] into its components.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    #[allow(clippy::type_complexity)]
 | 
			
		||||
    pub fn dissolve(
 | 
			
		||||
        self,
 | 
			
		||||
    ) -> (
 | 
			
		||||
        Vec<Annotation>,
 | 
			
		||||
        Keyword,
 | 
			
		||||
        Identifier,
 | 
			
		||||
        Punctuation,
 | 
			
		||||
| 
						 | 
				
			
			@ -75,6 +129,7 @@ impl Function {
 | 
			
		|||
        Block,
 | 
			
		||||
    ) {
 | 
			
		||||
        (
 | 
			
		||||
            self.annotations,
 | 
			
		||||
            self.function_keyword,
 | 
			
		||||
            self.identifier,
 | 
			
		||||
            self.open_paren,
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +147,59 @@ impl SourceElement for Function {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Parser<'a> {
 | 
			
		||||
    pub fn parse_annotation(&mut self, handler: &impl Handler<Error>) -> Option<Annotation> {
 | 
			
		||||
        match self.stop_at_significant() {
 | 
			
		||||
            Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => {
 | 
			
		||||
                // eat the pound sign
 | 
			
		||||
                self.forward();
 | 
			
		||||
 | 
			
		||||
                // step into the brackets
 | 
			
		||||
                let content = self.step_into(
 | 
			
		||||
                    Delimiter::Bracket,
 | 
			
		||||
                    |parser| {
 | 
			
		||||
                        let identifier = parser.parse_identifier(handler)?;
 | 
			
		||||
 | 
			
		||||
                        let value = if let Reading::Atomic(Token::Punctuation(punctuation)) =
 | 
			
		||||
                            parser.stop_at_significant()
 | 
			
		||||
                        {
 | 
			
		||||
                            if punctuation.punctuation == '=' {
 | 
			
		||||
                                // eat the equals sign
 | 
			
		||||
                                parser.forward();
 | 
			
		||||
 | 
			
		||||
                                // parse the string literal
 | 
			
		||||
                                let string_literal = parser
 | 
			
		||||
                                    .next_significant_token()
 | 
			
		||||
                                    .into_token()?
 | 
			
		||||
                                    .into_string_literal()
 | 
			
		||||
                                    .ok()?;
 | 
			
		||||
 | 
			
		||||
                                Some((punctuation, string_literal))
 | 
			
		||||
                            } else {
 | 
			
		||||
                                None
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            None
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        Some((identifier, value))
 | 
			
		||||
                    },
 | 
			
		||||
                    handler,
 | 
			
		||||
                )?;
 | 
			
		||||
 | 
			
		||||
                let (identifier, value) = content.tree?;
 | 
			
		||||
 | 
			
		||||
                Some(Annotation {
 | 
			
		||||
                    pound_sign: punctuation,
 | 
			
		||||
                    open_bracket: content.open,
 | 
			
		||||
                    identifier,
 | 
			
		||||
                    value,
 | 
			
		||||
                    close_bracket: content.close,
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn parse_declaration(&mut self, handler: &impl Handler<Error>) -> Option<Declaration> {
 | 
			
		||||
        match self.stop_at_significant() {
 | 
			
		||||
            Reading::Atomic(Token::Keyword(function_keyword))
 | 
			
		||||
| 
						 | 
				
			
			@ -113,6 +221,7 @@ impl<'a> Parser<'a> {
 | 
			
		|||
                let block = self.parse_block(handler)?;
 | 
			
		||||
 | 
			
		||||
                Some(Declaration::Function(Function {
 | 
			
		||||
                    annotations: Vec::new(),
 | 
			
		||||
                    function_keyword,
 | 
			
		||||
                    identifier,
 | 
			
		||||
                    open_paren: delimited_tree.open,
 | 
			
		||||
| 
						 | 
				
			
			@ -122,6 +231,27 @@ impl<'a> Parser<'a> {
 | 
			
		|||
                }))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // parse annotations
 | 
			
		||||
            Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => {
 | 
			
		||||
                // parse the annotation
 | 
			
		||||
                let mut annotations = Vec::new();
 | 
			
		||||
 | 
			
		||||
                while let Some(annotation) =
 | 
			
		||||
                    self.try_parse(|parser| parser.parse_annotation(handler))
 | 
			
		||||
                {
 | 
			
		||||
                    annotations.push(annotation);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // parse the function
 | 
			
		||||
                self.parse_declaration(handler)
 | 
			
		||||
                    .map(|declaration| match declaration {
 | 
			
		||||
                        Declaration::Function(mut function) => {
 | 
			
		||||
                            function.annotations.extend(annotations);
 | 
			
		||||
                            Declaration::Function(function)
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            unexpected => {
 | 
			
		||||
                // make progress
 | 
			
		||||
                self.forward();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue