Basic implementation of if/else without conditional operators

This commit is contained in:
Moritz Hölting 2024-03-29 18:26:43 +01:00
parent 0d93faf87f
commit d4305b3629
10 changed files with 424 additions and 28 deletions

57
grammar.md Normal file
View File

@ -0,0 +1,57 @@
# Grammar of the shulkerscript language
## Table of contents
### Program
```ebnf
Program: Declaration*;
```
### Declaration
```ebnf
Declaration: FunctionDeclaration;
```
### FunctionDeclaration
```ebnf
Function:
'fn' Identifier '(' ParameterList? ')' Block
;
ParameterList:
Identifier (',' Identifier)* ','?
;
```
### Statement
```ebnf
Statement:
Block
| LiteralCommand
| Conditional
;
```
### Block
```ebnf
Block: '{' Statement* '}';
```
### Conditional
```ebnf
Conditional:
'if' ParenthizedCondition Block ('else' Block)?
;
```
### ParenthizedCondition
```ebnf
ParenthizedCondition:
'(' Condition ')'
;
```
### Condition
```ebnf
Condition:
StringLiteral
```

View File

@ -3,3 +3,11 @@ pub trait Handler<T> {
/// Receive an error and handles it. /// Receive an error and handles it.
fn receive(&self, error: T); fn receive(&self, error: T);
} }
/// Is a struct that implements [`Handler`] trait by doing nothing with the errors.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Dummy;
impl<T> Handler<T> for Dummy {
fn receive(&self, _error: T) {}
}

View File

@ -7,6 +7,6 @@ mod error;
pub use error::{Error, Result}; pub use error::{Error, Result};
mod diagnostic; mod diagnostic;
pub use diagnostic::Handler; pub use diagnostic::{Dummy, Handler};
pub mod log; pub mod log;

View File

@ -2,7 +2,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use shulkerbox::datapack::{Command, Datapack}; use shulkerbox::datapack::{Command, Datapack, Execute};
use crate::{ use crate::{
base::{source_file::SourceElement, Handler}, base::{source_file::SourceElement, Handler},
@ -63,14 +63,61 @@ impl Compiler {
fn compile_function(statements: &[Statement]) -> Vec<Command> { fn compile_function(statements: &[Statement]) -> Vec<Command> {
let mut commands = Vec::new(); let mut commands = Vec::new();
for statement in statements { for statement in statements {
match statement { commands.extend(compile_statement(statement));
Statement::LiteralCommand(literal_command) => {
commands.push(literal_command.clean_command().into());
}
Statement::Block(_) => {
unreachable!("Only literal commands are allowed in functions at this time.")
}
}
} }
commands commands
} }
fn compile_statement(statement: &Statement) -> Option<Command> {
match statement {
Statement::LiteralCommand(literal_command) => Some(literal_command.clean_command().into()),
Statement::Block(_) => {
unreachable!("Only literal commands are allowed in functions at this time.")
}
Statement::Conditional(cond) => {
let (_, cond, block, el) = cond.clone().dissolve();
let (_, cond, _) = cond.dissolve();
let statements = block.statements();
let el = el
.and_then(|el| {
let (_, block) = el.dissolve();
let statements = block.statements();
if statements.is_empty() {
None
} else if statements.len() == 1 {
compile_statement(&statements[0]).map(|cmd| Execute::Run(Box::new(cmd)))
} else {
let commands = statements.iter().filter_map(compile_statement).collect();
Some(Execute::Runs(commands))
}
})
.map(Box::new);
if statements.is_empty() {
if el.is_none() {
None
} else {
Some(Command::Execute(Execute::If(
cond.value().string_content().into(),
Box::new(Execute::Runs(Vec::new())),
el,
)))
}
} else {
let run = if statements.len() > 1 {
let commands = statements.iter().filter_map(compile_statement).collect();
Execute::Runs(commands)
} else {
Execute::Run(Box::new(compile_statement(&statements[0])?))
};
Some(Command::Execute(Execute::If(
cond.value().string_content().into(),
Box::new(run),
el,
)))
}
}
}
}

View File

@ -74,7 +74,8 @@ pub enum Token {
Punctuation(Punctuation), Punctuation(Punctuation),
Numeric(Numeric), Numeric(Numeric),
Comment(Comment), Comment(Comment),
LiteralCommand(LiteralCommand), CommandLiteral(CommandLiteral),
StringLiteral(StringLiteral),
} }
impl Token { impl Token {
@ -88,7 +89,8 @@ impl Token {
Self::Punctuation(token) => &token.span, Self::Punctuation(token) => &token.span,
Self::Numeric(token) => &token.span, Self::Numeric(token) => &token.span,
Self::Comment(token) => &token.span, Self::Comment(token) => &token.span,
Self::LiteralCommand(token) => &token.span, Self::CommandLiteral(token) => &token.span,
Self::StringLiteral(token) => &token.span,
} }
} }
} }
@ -102,7 +104,8 @@ impl SourceElement for Token {
Self::Punctuation(token) => token.span(), Self::Punctuation(token) => token.span(),
Self::Numeric(token) => token.span(), Self::Numeric(token) => token.span(),
Self::Comment(token) => token.span(), Self::Comment(token) => token.span(),
Self::LiteralCommand(token) => token.span(), Self::CommandLiteral(token) => token.span(),
Self::StringLiteral(token) => token.span(),
} }
} }
} }
@ -177,6 +180,28 @@ impl SourceElement for Numeric {
} }
} }
/// Represents a hardcoded string literal value in the source code.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StringLiteral {
/// Is the span that makes up the token.
pub span: Span,
}
impl StringLiteral {
/// Returns the string without the leading and trailing double quotes.
#[must_use]
pub fn string_content(&self) -> &str {
let string = self.span.str();
&string[1..string.len() - 1]
}
}
impl SourceElement for StringLiteral {
fn span(&self) -> Span {
self.span.clone()
}
}
/// 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 Flux 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 {
@ -205,17 +230,17 @@ impl SourceElement for Comment {
/// Represents a hardcoded literal command in the source code. /// Represents a hardcoded literal command in the source code.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LiteralCommand { pub struct CommandLiteral {
/// Span that makes up the token. /// Span that makes up the token.
pub span: Span, pub span: Span,
} }
impl SourceElement for LiteralCommand { impl SourceElement for CommandLiteral {
fn span(&self) -> Span { fn span(&self) -> Span {
self.span.clone() self.span.clone()
} }
} }
impl LiteralCommand { impl CommandLiteral {
/// Returns the command without the leading slash. /// Returns the command without the leading slash.
#[must_use] #[must_use]
pub fn clean_command(&self) -> &str { pub fn clean_command(&self) -> &str {
@ -367,7 +392,7 @@ impl Token {
} }
// When there is no second slash and at the start of a line // When there is no second slash and at the start of a line
else if prev_token.map_or(true, |token| token.span().str().contains('\n')) { else if prev_token.map_or(true, |token| token.span().str().contains('\n')) {
Ok(Self::handle_literal_command(iter, start)) Ok(Self::handle_command_literal(iter, start))
} }
// Just a single slash punctuation // Just a single slash punctuation
else { else {
@ -390,11 +415,31 @@ impl Token {
.into() .into()
} }
/// Handles a sequence of characters that are enclosed in double quotes
fn handle_string_literal(iter: &mut SourceIterator, start: usize) -> Self {
let mut is_escaped = false;
for (_, character) in iter.by_ref() {
if character == '\\' {
is_escaped = !is_escaped;
} else if character == '"' && !is_escaped {
break;
} else {
is_escaped = false;
}
}
StringLiteral {
span: Self::create_span(start, iter),
}
.into()
}
/// Handles a command that is preceeded by a slash /// Handles a command that is preceeded by a slash
fn handle_literal_command(iter: &mut SourceIterator, start: usize) -> Self { fn handle_command_literal(iter: &mut SourceIterator, start: usize) -> Self {
Self::walk_iter(iter, |c| !(c.is_whitespace() && c.is_ascii_control())); Self::walk_iter(iter, |c| !(c.is_whitespace() && c.is_ascii_control()));
LiteralCommand { CommandLiteral {
span: Self::create_span(start, iter), span: Self::create_span(start, iter),
} }
.into() .into()
@ -431,6 +476,8 @@ impl Token {
// Found comment/single slash punctuation // Found comment/single slash punctuation
else if character == '/' { else if character == '/' {
Self::handle_comment(iter, start, character, prev_token, handler) Self::handle_comment(iter, start, character, prev_token, handler)
} else if character == '"' {
Ok(Self::handle_string_literal(iter, start))
} }
// Found numeric literal // Found numeric literal
else if character.is_ascii_digit() { else if character.is_ascii_digit() {

View File

@ -36,6 +36,8 @@ pub fn compile(path: PathBuf) -> Result<VFolder> {
let tokens = TokenStream::tokenize(&source_file, &printer); let tokens = TokenStream::tokenize(&source_file, &printer);
// println!("tokens: {tokens:#?}");
if printer.has_printed() { if printer.has_printed() {
return Err(Error::Other( return Err(Error::Other(
"An error occurred while tokenizing the source code.", "An error occurred while tokenizing the source code.",
@ -47,7 +49,7 @@ pub fn compile(path: PathBuf) -> Result<VFolder> {
"An error occured while parsing the source code.", "An error occured while parsing the source code.",
))?; ))?;
// println!("result: {result:#?}"); // println!("program: {program:#?}");
let mut compiler = Compiler::new(); let mut compiler = Compiler::new();
let datapack = compiler.compile(&program, &printer)?; let datapack = compiler.compile(&program, &printer)?;

View File

@ -16,6 +16,7 @@ pub enum SyntaxKind {
Identifier, Identifier,
Declaration, Declaration,
Numeric, Numeric,
StringLiteral,
Statement, Statement,
Expression, Expression,
Type, Type,
@ -39,6 +40,7 @@ impl Display for UnexpectedSyntax {
SyntaxKind::Keyword(keyword) => format!("a keyword token `{}`", keyword.as_str()), SyntaxKind::Keyword(keyword) => format!("a keyword token `{}`", keyword.as_str()),
SyntaxKind::Declaration => "a declaration token".to_string(), SyntaxKind::Declaration => "a declaration token".to_string(),
SyntaxKind::Numeric => "a numeric token".to_string(), SyntaxKind::Numeric => "a numeric token".to_string(),
SyntaxKind::StringLiteral => "a string literal".to_string(),
SyntaxKind::Statement => "a statement syntax".to_string(), SyntaxKind::Statement => "a statement syntax".to_string(),
SyntaxKind::Expression => "an expression syntax".to_string(), SyntaxKind::Expression => "an expression syntax".to_string(),
SyntaxKind::Type => "a type syntax".to_string(), SyntaxKind::Type => "a type syntax".to_string(),
@ -54,7 +56,8 @@ impl Display for UnexpectedSyntax {
format!("a punctuation token `{}`", punctuation.punctuation) format!("a punctuation token `{}`", punctuation.punctuation)
} }
Some(Token::Numeric(..)) => "a numeric token".to_string(), Some(Token::Numeric(..)) => "a numeric token".to_string(),
Some(Token::LiteralCommand(..)) => "a literal command token".to_string(), Some(Token::CommandLiteral(..)) => "a literal command token".to_string(),
Some(Token::StringLiteral(..)) => "a string literal token".to_string(),
None => "EOF".to_string(), None => "EOF".to_string(),
}; };

View File

@ -38,7 +38,7 @@ impl SourceElement for Declaration {
/// ///
/// ``` ebnf /// ``` ebnf
/// Function: /// Function:
/// 'function' Identifier '(' ParameterList? ')' Block /// 'fn' Identifier '(' ParameterList? ')' Block
/// ; /// ;
/// ///
/// ParameterList: /// ParameterList:

View File

@ -1 +1,110 @@
//! Syntax tree nodes for expressions. //! Syntax tree nodes for expressions.
use getset::Getters;
use crate::{
base::{
source_file::{SourceElement, Span},
Handler,
},
lexical::{
token::{Punctuation, StringLiteral, Token},
token_stream::Delimiter,
},
syntax::{
error::{Error, UnexpectedSyntax},
parser::{Parser, Reading},
},
};
/// Syntax Synopsis:
///
/// ``` ebnf
/// ParenthesizedCondition:
/// '(' Condition ')';
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct ParenthesizedCondition {
/// The opening parenthesis.
#[get = "pub"]
pub open_paren: Punctuation,
/// The condition within the parenthesis.
#[get = "pub"]
pub condition: Box<Condition>,
/// The closing parenthesis.
#[get = "pub"]
pub close_paren: Punctuation,
}
impl ParenthesizedCondition {
/// Dissolves the parenthesized condition into its components
#[must_use]
pub fn dissolve(self) -> (Punctuation, Condition, Punctuation) {
(self.open_paren, *self.condition, self.close_paren)
}
}
impl SourceElement for ParenthesizedCondition {
fn span(&self) -> Span {
self.open_paren
.span()
.join(&self.close_paren.span())
.expect("The span of the parenthesis is invalid.")
}
}
/// Syntax Synopsis:
///
/// ``` ebnf
/// Condition: StringLiteral;
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Condition {
/// The value of the condition.
#[get = "pub"]
pub value: StringLiteral,
}
impl SourceElement for Condition {
fn span(&self) -> Span {
self.value.span()
}
}
impl<'a> Parser<'a> {
/// Parses a [`Condition`].
pub fn parse_condition(&mut self, handler: &impl Handler<Error>) -> Option<Condition> {
match self.next_significant_token() {
Reading::Atomic(Token::StringLiteral(s)) => Some(Condition { value: s }),
unexpected => {
// make progress
self.forward();
handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax {
expected: crate::syntax::error::SyntaxKind::Expression,
found: unexpected.into_token(),
}));
None
}
}
}
/// Parses a [`ParenthesizedCondition`].
pub fn parse_parenthesized_condition(
&mut self,
handler: &impl Handler<Error>,
) -> Option<ParenthesizedCondition> {
let token_tree = self.step_into(
Delimiter::Parenthesis,
|parser| parser.parse_condition(handler),
handler,
)?;
Some(ParenthesizedCondition {
open_paren: token_tree.open,
condition: Box::new(token_tree.tree?),
close_paren: token_tree.close,
})
}
}

View File

@ -8,7 +8,7 @@ use crate::{
Handler, Handler,
}, },
lexical::{ lexical::{
token::{LiteralCommand, Punctuation, Token}, token::{CommandLiteral, Keyword, KeywordKind, Punctuation, Token},
token_stream::Delimiter, token_stream::Delimiter,
}, },
syntax::{ syntax::{
@ -17,20 +17,22 @@ use crate::{
}, },
}; };
use super::expression::ParenthesizedCondition;
/// Syntax Synopsis: /// Syntax Synopsis:
/// ///
/// ``` ebnf /// ``` ebnf
/// Statement: /// Statement:
/// Block /// Block
/// | Conditional /// | LiteralCommand
/// ; /// ;
/// ``` /// ```
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Statement { pub enum Statement {
Block(Block), Block(Block),
LiteralCommand(LiteralCommand), LiteralCommand(CommandLiteral),
// Conditional(Conditional), Conditional(Conditional),
} }
impl SourceElement for Statement { impl SourceElement for Statement {
@ -38,7 +40,7 @@ impl SourceElement for Statement {
match self { match self {
Self::Block(block) => block.span(), Self::Block(block) => block.span(),
Self::LiteralCommand(literal_command) => literal_command.span(), Self::LiteralCommand(literal_command) => literal_command.span(),
//Self::Conditional(conditional) => conditional.span(), Self::Conditional(conditional) => conditional.span(),
} }
} }
} }
@ -80,6 +82,87 @@ impl SourceElement for Block {
} }
} }
/// Syntax Synopsis:
///
/// ``` ebnf
/// Conditional:
/// 'if' ParenthizedCondition Block ('else' Block)?
/// ;
/// ````
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Conditional {
/// The `if` keyword.
#[get = "pub"]
if_keyword: Keyword,
/// The condition of the conditional.
#[get = "pub"]
condition: ParenthesizedCondition,
/// The block of the conditional.
#[get = "pub"]
block: Block,
/// The `else` statement.
#[get = "pub"]
r#else: Option<Else>,
}
impl Conditional {
/// Dissolves the [`Conditional`] into its components.
#[must_use]
pub fn dissolve(self) -> (Keyword, ParenthesizedCondition, Block, Option<Else>) {
(self.if_keyword, self.condition, self.block, self.r#else)
}
}
impl SourceElement for Conditional {
fn span(&self) -> Span {
self.r#else.as_ref().map_or_else(
|| {
self.if_keyword
.span()
.join(&self.block.span())
.expect("The span of the conditional is invalid.")
},
|r#else| {
self.if_keyword
.span()
.join(&r#else.span())
.expect("The span of the else conditional is invalid.")
},
)
}
}
/// Syntax Synopsis:
///
/// ``` ebnf
/// Else:
/// 'else' Block
/// ;
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Else {
/// The `else` keyword.
#[get = "pub"]
else_keyword: Keyword,
/// The block of the else statement.
#[get = "pub"]
block: Box<Block>,
}
impl Else {
/// Dissolves the [`Else`] into its components.
#[must_use]
pub fn dissolve(self) -> (Keyword, Box<Block>) {
(self.else_keyword, self.block)
}
}
impl SourceElement for Else {
fn span(&self) -> Span {
self.else_keyword.span().join(&self.block.span()).unwrap()
}
}
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
/// Parses a [`Block`]. /// Parses a [`Block`].
pub fn parse_block(&mut self, handler: &impl Handler<Error>) -> Option<Block> { pub fn parse_block(&mut self, handler: &impl Handler<Error>) -> Option<Block> {
@ -123,7 +206,7 @@ impl<'a> Parser<'a> {
pub fn parse_statement(&mut self, handler: &impl Handler<Error>) -> Option<Statement> { pub fn parse_statement(&mut self, handler: &impl Handler<Error>) -> Option<Statement> {
match self.stop_at_significant() { match self.stop_at_significant() {
// variable declaration // variable declaration
Reading::Atomic(Token::LiteralCommand(command)) => { Reading::Atomic(Token::CommandLiteral(command)) => {
self.forward(); self.forward();
Some(Statement::LiteralCommand(command)) Some(Statement::LiteralCommand(command))
} }
@ -134,6 +217,46 @@ impl<'a> Parser<'a> {
Some(Statement::Block(block)) Some(Statement::Block(block))
} }
// conditional statement
Reading::Atomic(Token::Keyword(if_keyword))
if if_keyword.keyword == KeywordKind::If =>
{
// eat the if keyword
self.forward();
let condition = self.parse_parenthesized_condition(handler)?;
let block = self.parse_block(handler)?;
match self.stop_at_significant() {
// else statement
Reading::Atomic(Token::Keyword(else_keyword))
if else_keyword.keyword == KeywordKind::Else =>
{
// eat the else keyword
self.forward();
let else_block = self.parse_block(handler)?;
Some(Statement::Conditional(Conditional {
if_keyword,
condition,
block,
r#else: Some(Else {
else_keyword,
block: Box::new(else_block),
}),
}))
}
// no else statement
_ => Some(Statement::Conditional(Conditional {
if_keyword,
condition,
block,
r#else: None,
})),
}
}
// other // other
unexpected => { unexpected => {
handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax {