Implement annotations for functions

This commit is contained in:
Moritz Hölting 2024-04-01 20:42:38 +02:00
parent d4305b3629
commit b9bc5438e5
7 changed files with 204 additions and 26 deletions

View File

@ -15,13 +15,18 @@ Declaration: FunctionDeclaration;
### FunctionDeclaration ### FunctionDeclaration
```ebnf ```ebnf
Function: Function:
'fn' Identifier '(' ParameterList? ')' Block Annotation* 'fn' Identifier '(' ParameterList? ')' Block
; ;
ParameterList: ParameterList:
Identifier (',' Identifier)* ','? Identifier (',' Identifier)* ','?
; ;
``` ```
### Annotation
```ebnf
Annotation: '#[' Identifier ('=' StringLiteral)? ']';
```
### Statement ### Statement
```ebnf ```ebnf
Statement: Statement:

View File

@ -6,7 +6,7 @@ use std::{
fs, fs,
iter::{Iterator, Peekable}, iter::{Iterator, Peekable},
ops::Range, ops::Range,
path::PathBuf, path::{Path, PathBuf},
str::CharIndices, str::CharIndices,
sync::Arc, sync::Arc,
}; };
@ -85,9 +85,9 @@ impl SourceFile {
/// ///
/// # Errors /// # Errors
/// - [`Error::IoError`]: Error occurred when reading the file contents. /// - [`Error::IoError`]: Error occurred when reading the file contents.
pub fn load(path: PathBuf) -> Result<Arc<Self>, Error> { pub fn load(path: &Path) -> Result<Arc<Self>, Error> {
let source = fs::read_to_string(&path).map_err(Error::IoError)?; let source = fs::read_to_string(path).map_err(Error::IoError)?;
Ok(Self::new(path, source)) Ok(Self::new(path.to_path_buf(), source))
} }
/// Get the [`Location`] of a given byte index /// Get the [`Location`] of a given byte index

View File

@ -7,6 +7,7 @@ use crate::base::{
Handler, Handler,
}; };
use derive_more::From; use derive_more::From;
use enum_as_inner::EnumAsInner;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use strum_macros::EnumIter; use strum_macros::EnumIter;
@ -64,8 +65,8 @@ impl KeywordKind {
} }
} }
/// Is an enumeration containing all kinds of tokens in the Flux programming language. /// Is an enumeration containing all kinds of tokens in the Shulkerscript programming language.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From, EnumAsInner)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum Token { pub enum Token {
WhiteSpaces(WhiteSpaces), 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CommentKind { pub enum CommentKind {
/// A comment that starts with `//` and ends at the end of the line. /// A comment that starts with `//` and ends at the end of the line.

View File

@ -173,8 +173,11 @@ pub enum TokenTree {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum Delimiter { pub enum Delimiter {
/// ()
Parenthesis, Parenthesis,
/// {}
Brace, Brace,
/// []
Bracket, Bracket,
} }

View File

@ -17,19 +17,58 @@ pub mod compile;
pub mod lexical; pub mod lexical;
pub mod syntax; 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 base::{source_file::SourceFile, Handler, Result};
use compile::compiler::Compiler; use compile::compiler::Compiler;
use shulkerbox::{util::compile::CompileOptions, virtual_fs::VFolder}; 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}; 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. /// Compiles the given source code.
/// ///
/// # Errors /// # Errors
/// - If an error occurs while reading the file. /// - 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 source_file = SourceFile::load(path)?;
let printer = Printer::new(); let printer = Printer::new();

View File

@ -142,6 +142,20 @@ impl<'a> Parser<'a> {
close: close_punctuation, 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. /// 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`]. /// Represents the read value of the [`Frame`].

View File

@ -10,7 +10,7 @@ use crate::{
Handler, Handler,
}, },
lexical::{ lexical::{
token::{Identifier, Keyword, KeywordKind, Punctuation, Token}, token::{Identifier, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
token_stream::Delimiter, token_stream::Delimiter,
}, },
syntax::{ 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: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
/// Function: /// Function:
/// 'fn' Identifier '(' ParameterList? ')' Block /// Annotation* 'fn' Identifier '(' ParameterList? ')' Block
/// ; /// ;
/// ///
/// ParameterList: /// ParameterList:
@ -47,6 +97,8 @@ impl SourceElement for Declaration {
/// ``` /// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Function { pub struct Function {
#[get = "pub"]
annotations: Vec<Annotation>,
#[get = "pub"] #[get = "pub"]
function_keyword: Keyword, function_keyword: Keyword,
#[get = "pub"] #[get = "pub"]
@ -64,9 +116,11 @@ pub struct Function {
impl Function { impl Function {
/// Dissolves the [`Function`] into its components. /// Dissolves the [`Function`] into its components.
#[must_use] #[must_use]
#[allow(clippy::type_complexity)]
pub fn dissolve( pub fn dissolve(
self, self,
) -> ( ) -> (
Vec<Annotation>,
Keyword, Keyword,
Identifier, Identifier,
Punctuation, Punctuation,
@ -75,6 +129,7 @@ impl Function {
Block, Block,
) { ) {
( (
self.annotations,
self.function_keyword, self.function_keyword,
self.identifier, self.identifier,
self.open_paren, self.open_paren,
@ -92,6 +147,59 @@ impl SourceElement for Function {
} }
impl<'a> Parser<'a> { 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> { pub fn parse_declaration(&mut self, handler: &impl Handler<Error>) -> Option<Declaration> {
match self.stop_at_significant() { match self.stop_at_significant() {
Reading::Atomic(Token::Keyword(function_keyword)) Reading::Atomic(Token::Keyword(function_keyword))
@ -113,6 +221,7 @@ impl<'a> Parser<'a> {
let block = self.parse_block(handler)?; let block = self.parse_block(handler)?;
Some(Declaration::Function(Function { Some(Declaration::Function(Function {
annotations: Vec::new(),
function_keyword, function_keyword,
identifier, identifier,
open_paren: delimited_tree.open, 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 => { unexpected => {
// make progress // make progress
self.forward(); self.forward();