implement function calls

This commit is contained in:
Moritz Hölting 2024-04-05 16:16:12 +02:00
parent 2ed6e56ef1
commit 62dd12cab3
7 changed files with 672 additions and 418 deletions

View File

@ -33,6 +33,8 @@ Statement:
Block
| LiteralCommand
| Conditional
| Grouping
| DocComment
;
```
@ -59,4 +61,30 @@ ParenthizedCondition:
```ebnf
Condition:
StringLiteral
```
### Grouping
``` ebnf
Grouping:
'group' Block
;
```
### Expression
```ebnf
Expression:
Primary
```
### Primary
``` ebnf
Primary:
FunctionCall
```
### FunctionCall
``` ebnf
FunctionCall:
Identifier '(' (Expression (',' Expression)*)? ')'
;
```

View File

@ -0,0 +1,378 @@
//! Syntax tree nodes for conditions.
use std::{cmp::Ordering, collections::VecDeque};
use enum_as_inner::EnumAsInner;
use getset::Getters;
use crate::{
base::{
source_file::{SourceElement, Span},
Dummy, Handler,
},
lexical::{
token::{Punctuation, StringLiteral, Token},
token_stream::Delimiter,
},
syntax::{
error::{Error, SyntaxKind, UnexpectedSyntax},
parser::{Parser, Reading},
},
};
/// Syntax Synopsis:
///
/// ``` ebnf
/// Expression:
/// Prefix
/// | Parenthesized
/// | StringLiteral
/// ```
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
pub enum PrimaryCondition {
Prefix(ConditionalPrefix),
Parenthesized(ParenthesizedCondition),
StringLiteral(StringLiteral),
}
impl SourceElement for PrimaryCondition {
fn span(&self) -> Span {
match self {
Self::Prefix(prefix) => prefix.span(),
Self::Parenthesized(parenthesized) => parenthesized.span(),
Self::StringLiteral(literal) => literal.span(),
}
}
}
/// Syntax Synopsis:
///
/// ``` ebnf
/// BinaryCondition:
/// Condition ConditionalBinaryOperator Condition
/// ;
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct BinaryCondition {
/// The left operand of the binary condition.
#[get = "pub"]
left_operand: Box<Condition>,
/// The operator of the binary condition.
#[get = "pub"]
operator: ConditionalBinaryOperator,
/// The right operand of the binary condition.
#[get = "pub"]
right_operand: Box<Condition>,
}
impl SourceElement for BinaryCondition {
fn span(&self) -> Span {
self.left_operand
.span()
.join(&self.right_operand.span())
.unwrap()
}
}
impl BinaryCondition {
/// Dissolves the binary condition into its components
#[must_use]
pub fn dissolve(self) -> (Condition, ConditionalBinaryOperator, Condition) {
(*self.left_operand, self.operator, *self.right_operand)
}
}
/// Syntax Synopsis:
///
/// ``` ebnf
/// BinaryOperator:
/// '&&'
/// | '||'
/// ;
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
#[allow(missing_docs)]
pub enum ConditionalBinaryOperator {
LogicalAnd(Punctuation, Punctuation),
LogicalOr(Punctuation, Punctuation),
}
impl ConditionalBinaryOperator {
/// Gets the precedence of the operator (the higher the number, the first it will be evaluated)
///
/// The least operator has precedence 1.
#[must_use]
pub fn get_precedence(&self) -> u8 {
match self {
Self::LogicalOr(..) => 1,
Self::LogicalAnd(..) => 2,
}
}
}
impl SourceElement for ConditionalBinaryOperator {
fn span(&self) -> Span {
match self {
Self::LogicalAnd(a, b) | Self::LogicalOr(a, b) => a
.span
.join(&b.span)
.expect("Invalid tokens for ConditionalBinaryOperator"),
}
}
}
/// 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
/// PrefixOperator: '!';
/// ```
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
pub enum ConditionalPrefixOperator {
LogicalNot(Punctuation),
}
impl SourceElement for ConditionalPrefixOperator {
fn span(&self) -> Span {
match self {
Self::LogicalNot(token) => token.span.clone(),
}
}
}
/// Syntax Synopsis:
///
/// ```ebnf
/// Prefix:
/// ConditionalPrefixOperator StringLiteral
/// ;
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct ConditionalPrefix {
/// The operator of the prefix.
#[get = "pub"]
operator: ConditionalPrefixOperator,
/// The operand of the prefix.
#[get = "pub"]
operand: Box<PrimaryCondition>,
}
impl SourceElement for ConditionalPrefix {
fn span(&self) -> Span {
self.operator.span().join(&self.operand.span()).unwrap()
}
}
impl ConditionalPrefix {
/// Dissolves the conditional prefix into its components
#[must_use]
pub fn dissolve(self) -> (ConditionalPrefixOperator, PrimaryCondition) {
(self.operator, *self.operand)
}
}
/// Syntax Synopsis:
///
/// ``` ebnf
/// Condition: PrimaryCondition;
/// ```
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
pub enum Condition {
Primary(PrimaryCondition),
Binary(BinaryCondition),
}
impl SourceElement for Condition {
fn span(&self) -> Span {
match self {
Self::Primary(primary) => primary.span(),
Self::Binary(binary) => binary.span(),
}
}
}
impl<'a> Parser<'a> {
/// Parses a [`Condition`].
pub fn parse_condition(&mut self, handler: &impl Handler<Error>) -> Option<Condition> {
let mut lhs = Condition::Primary(self.parse_primary_condition(handler)?);
let mut expressions = VecDeque::new();
// Parses a list of binary operators and expressions
while let Some(binary_operator) = self.try_parse_conditional_binary_operator() {
expressions.push_back((
binary_operator,
Some(Condition::Primary(self.parse_primary_condition(handler)?)),
));
}
let mut candidate_index = 0;
let mut current_precedence;
while !expressions.is_empty() {
// reset precedence
current_precedence = 0;
for (index, (binary_op, _)) in expressions.iter().enumerate() {
let new_precedence = binary_op.get_precedence();
match new_precedence.cmp(&current_precedence) {
// Clear the candidate indices and set the current precedence to the
// precedence of the current binary operator.
Ordering::Greater => {
current_precedence = new_precedence;
candidate_index = index;
}
Ordering::Less | Ordering::Equal => (),
}
}
// ASSUMPTION: The assignments have 1 precedence and are right associative.
assert!(current_precedence > 0);
if candidate_index == 0 {
let (binary_op, rhs) = expressions.pop_front().expect("No binary operator found");
// fold the first expression
lhs = Condition::Binary(BinaryCondition {
left_operand: Box::new(lhs),
operator: binary_op,
right_operand: Box::new(rhs.unwrap()),
});
} else {
let (binary_op, rhs) = expressions
.remove(candidate_index)
.expect("No binary operator found");
// fold the expression at candidate_index
expressions[candidate_index - 1].1 = Some(Condition::Binary(BinaryCondition {
left_operand: Box::new(expressions[candidate_index - 1].1.take().unwrap()),
operator: binary_op,
right_operand: Box::new(rhs.unwrap()),
}));
}
}
Some(lhs)
}
/// Parses a [`PrimaryCondition`].
pub fn parse_primary_condition(
&mut self,
handler: &impl Handler<Error>,
) -> Option<PrimaryCondition> {
match self.stop_at_significant() {
// prefixed expression
Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '!' => {
// eat prefix operator
self.forward();
let operator = match punc.punctuation {
'!' => ConditionalPrefixOperator::LogicalNot(punc),
_ => unreachable!(),
};
let operand = Box::new(self.parse_primary_condition(handler)?);
Some(PrimaryCondition::Prefix(ConditionalPrefix {
operator,
operand,
}))
}
// string literal
Reading::Atomic(Token::StringLiteral(literal)) => {
self.forward();
Some(PrimaryCondition::StringLiteral(literal))
}
// parenthesized condition
Reading::IntoDelimited(punc) if punc.punctuation == '(' => self
.parse_parenthesized_condition(handler)
.map(PrimaryCondition::Parenthesized),
unexpected => {
// make progress
self.forward();
handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax {
expected: 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,
})
}
fn try_parse_conditional_binary_operator(&mut self) -> Option<ConditionalBinaryOperator> {
self.try_parse(|parser| match parser.next_significant_token() {
Reading::Atomic(Token::Punctuation(punc)) => match punc.punctuation {
'&' => {
let b = parser.parse_punctuation('&', false, &Dummy)?;
Some(ConditionalBinaryOperator::LogicalAnd(punc, b))
}
'|' => {
let b = parser.parse_punctuation('|', false, &Dummy)?;
Some(ConditionalBinaryOperator::LogicalOr(punc, b))
}
_ => None,
},
_ => None,
})
}
}

View File

@ -1,338 +1,139 @@
//! Syntax tree nodes for expressions.
use std::{cmp::Ordering, collections::VecDeque};
use enum_as_inner::EnumAsInner;
use getset::Getters;
use crate::{
base::{
source_file::{SourceElement, Span},
Dummy, Handler,
Handler,
},
lexical::{
token::{Punctuation, StringLiteral, Token},
token::{Identifier, Punctuation, Token},
token_stream::Delimiter,
},
syntax::{
error::{Error, SyntaxKind, UnexpectedSyntax},
error::{Error, UnexpectedSyntax},
parser::{Parser, Reading},
},
};
/// Syntax Synopsis:
///
/// ``` ebnf
/// Expression:
/// Prefix
/// | Parenthesized
/// | StringLiteral
/// ```
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
pub enum PrimaryCondition {
Prefix(ConditionalPrefix),
Parenthesized(ParenthesizedCondition),
StringLiteral(StringLiteral),
}
impl SourceElement for PrimaryCondition {
fn span(&self) -> Span {
match self {
Self::Prefix(prefix) => prefix.span(),
Self::Parenthesized(parenthesized) => parenthesized.span(),
Self::StringLiteral(literal) => literal.span(),
}
}
}
/// Syntax Synopsis:
///
/// ``` ebnf
/// BinaryCondition:
/// Condition ConditionalBinaryOperator Condition
/// ;
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct BinaryCondition {
/// The left operand of the binary condition.
#[get = "pub"]
left_operand: Box<Condition>,
/// The operator of the binary condition.
#[get = "pub"]
operator: ConditionalBinaryOperator,
/// The right operand of the binary condition.
#[get = "pub"]
right_operand: Box<Condition>,
}
impl SourceElement for BinaryCondition {
fn span(&self) -> Span {
self.left_operand
.span()
.join(&self.right_operand.span())
.unwrap()
}
}
impl BinaryCondition {
/// Dissolves the binary condition into its components
#[must_use]
pub fn dissolve(self) -> (Condition, ConditionalBinaryOperator, Condition) {
(*self.left_operand, self.operator, *self.right_operand)
}
}
/// Syntax Synopsis:
///
/// ``` ebnf
/// BinaryOperator:
/// '&&'
/// | '||'
/// ;
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
#[allow(missing_docs)]
pub enum ConditionalBinaryOperator {
LogicalAnd(Punctuation, Punctuation),
LogicalOr(Punctuation, Punctuation),
}
impl ConditionalBinaryOperator {
/// Gets the precedence of the operator (the higher the number, the first it will be evaluated)
///
/// The least operator has precedence 1.
#[must_use]
pub fn get_precedence(&self) -> u8 {
match self {
Self::LogicalOr(..) => 1,
Self::LogicalAnd(..) => 2,
}
}
}
impl SourceElement for ConditionalBinaryOperator {
fn span(&self) -> Span {
match self {
Self::LogicalAnd(a, b) | Self::LogicalOr(a, b) => a
.span
.join(&b.span)
.expect("Invalid tokens for ConditionalBinaryOperator"),
}
}
}
/// 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
/// PrefixOperator: '!';
/// ```
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
pub enum ConditionalPrefixOperator {
LogicalNot(Punctuation),
}
impl SourceElement for ConditionalPrefixOperator {
fn span(&self) -> Span {
match self {
Self::LogicalNot(token) => token.span.clone(),
}
}
}
use super::ConnectedList;
/// Syntax Synopsis:
///
/// ```ebnf
/// Prefix:
/// ConditionalPrefixOperator StringLiteral
/// ;
/// Expression:
/// Primary
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct ConditionalPrefix {
/// The operator of the prefix.
#[get = "pub"]
operator: ConditionalPrefixOperator,
/// The operand of the prefix.
#[get = "pub"]
operand: Box<PrimaryCondition>,
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
#[allow(missing_docs)]
pub enum Expression {
Primary(Primary),
}
impl SourceElement for ConditionalPrefix {
impl SourceElement for Expression {
fn span(&self) -> Span {
self.operator.span().join(&self.operand.span()).unwrap()
}
}
impl ConditionalPrefix {
/// Dissolves the conditional prefix into its components
#[must_use]
pub fn dissolve(self) -> (ConditionalPrefixOperator, PrimaryCondition) {
(self.operator, *self.operand)
match self {
Self::Primary(primary) => primary.span(),
}
}
}
/// Syntax Synopsis:
///
/// ``` ebnf
/// Condition: PrimaryCondition;
/// Primary:
/// FunctionCall
/// ```
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
pub enum Condition {
Primary(PrimaryCondition),
Binary(BinaryCondition),
#[allow(missing_docs)]
pub enum Primary {
FunctionCall(FunctionCall),
}
impl SourceElement for Condition {
impl SourceElement for Primary {
fn span(&self) -> Span {
match self {
Self::Primary(primary) => primary.span(),
Self::Binary(binary) => binary.span(),
Self::FunctionCall(function_call) => function_call.span(),
}
}
}
/// Syntax Synopsis:
///
/// ``` ebnf
/// FunctionCall:
/// Identifier '(' (Expression (',' Expression)*)? ')'
/// ;
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct FunctionCall {
/// The identifier of the function.
#[get = "pub"]
identifier: Identifier,
/// The left parenthesis of the function call.
#[get = "pub"]
left_parenthesis: Punctuation,
/// The arguments of the function call.
#[get = "pub"]
arguments: Option<ConnectedList<Box<Expression>, Punctuation>>,
/// The right parenthesis of the function call.
#[get = "pub"]
right_parenthesis: Punctuation,
}
impl SourceElement for FunctionCall {
fn span(&self) -> Span {
self.identifier
.span()
.join(&self.right_parenthesis.span)
.unwrap()
}
}
impl<'a> Parser<'a> {
/// Parses a [`Condition`].
pub fn parse_condition(&mut self, handler: &impl Handler<Error>) -> Option<Condition> {
let mut lhs = Condition::Primary(self.parse_primary_condition(handler)?);
let mut expressions = VecDeque::new();
// Parses a list of binary operators and expressions
while let Some(binary_operator) = self.try_parse_conditional_binary_operator() {
expressions.push_back((
binary_operator,
Some(Condition::Primary(self.parse_primary_condition(handler)?)),
));
}
let mut candidate_index = 0;
let mut current_precedence;
while !expressions.is_empty() {
// reset precedence
current_precedence = 0;
for (index, (binary_op, _)) in expressions.iter().enumerate() {
let new_precedence = binary_op.get_precedence();
match new_precedence.cmp(&current_precedence) {
// Clear the candidate indices and set the current precedence to the
// precedence of the current binary operator.
Ordering::Greater => {
current_precedence = new_precedence;
candidate_index = index;
}
Ordering::Less | Ordering::Equal => (),
}
}
// ASSUMPTION: The assignments have 1 precedence and are right associative.
assert!(current_precedence > 0);
if candidate_index == 0 {
let (binary_op, rhs) = expressions.pop_front().expect("No binary operator found");
// fold the first expression
lhs = Condition::Binary(BinaryCondition {
left_operand: Box::new(lhs),
operator: binary_op,
right_operand: Box::new(rhs.unwrap()),
});
} else {
let (binary_op, rhs) = expressions
.remove(candidate_index)
.expect("No binary operator found");
// fold the expression at candidate_index
expressions[candidate_index - 1].1 = Some(Condition::Binary(BinaryCondition {
left_operand: Box::new(expressions[candidate_index - 1].1.take().unwrap()),
operator: binary_op,
right_operand: Box::new(rhs.unwrap()),
}));
}
}
Some(lhs)
/// Parses an [`Expression`]
pub fn parse_expression(&mut self, handler: &impl Handler<Error>) -> Option<Expression> {
Some(Expression::Primary(self.parse_primary(handler)?))
}
/// Parses a [`PrimaryCondition`].
pub fn parse_primary_condition(
&mut self,
handler: &impl Handler<Error>,
) -> Option<PrimaryCondition> {
/// Parses an [`Primary`]
pub fn parse_primary(&mut self, handler: &impl Handler<Error>) -> Option<Primary> {
match self.stop_at_significant() {
// prefixed expression
Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '!' => {
// eat prefix operator
// identifier expression
Reading::Atomic(Token::Identifier(identifier)) => {
// eat the identifier
self.forward();
let operator = match punc.punctuation {
'!' => ConditionalPrefixOperator::LogicalNot(punc),
_ => unreachable!(),
};
// function call
if matches!(self.stop_at_significant(), Reading::IntoDelimited(punc) if punc.punctuation == '(')
{
let token_tree = self.parse_enclosed_list(
Delimiter::Parenthesis,
',',
|parser| parser.parse_expression(handler).map(Box::new),
handler,
)?;
let operand = Box::new(self.parse_primary_condition(handler)?);
Some(PrimaryCondition::Prefix(ConditionalPrefix {
operator,
operand,
}))
Some(Primary::FunctionCall(FunctionCall {
identifier,
left_parenthesis: token_tree.open,
right_parenthesis: token_tree.close,
arguments: token_tree.list,
}))
} else {
// insert parser for regular identifier here
None
}
}
// string literal
Reading::Atomic(Token::StringLiteral(literal)) => {
self.forward();
Some(PrimaryCondition::StringLiteral(literal))
}
// parenthesized condition
Reading::IntoDelimited(punc) if punc.punctuation == '(' => self
.parse_parenthesized_condition(handler)
.map(PrimaryCondition::Parenthesized),
unexpected => {
// make progress
self.forward();
handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax {
expected: SyntaxKind::Expression,
expected: crate::syntax::error::SyntaxKind::Expression,
found: unexpected.into_token(),
}));
@ -340,39 +141,4 @@ impl<'a> Parser<'a> {
}
}
}
/// 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,
})
}
fn try_parse_conditional_binary_operator(&mut self) -> Option<ConditionalBinaryOperator> {
self.try_parse(|parser| match parser.next_significant_token() {
Reading::Atomic(Token::Punctuation(punc)) => match punc.punctuation {
'&' => {
let b = parser.parse_punctuation('&', false, &Dummy)?;
Some(ConditionalBinaryOperator::LogicalAnd(punc, b))
}
'|' => {
let b = parser.parse_punctuation('|', false, &Dummy)?;
Some(ConditionalBinaryOperator::LogicalOr(punc, b))
}
_ => None,
},
_ => None,
})
}
}

View File

@ -16,6 +16,7 @@ use crate::{
use super::{error::Error, parser::Parser};
pub mod condition;
pub mod declaration;
pub mod expression;
pub mod program;

View File

@ -12,12 +12,12 @@ use crate::{
token_stream::Delimiter,
},
syntax::{
error::{Error, SyntaxKind, UnexpectedSyntax},
error::Error,
parser::{Parser, Reading},
},
};
use super::expression::ParenthesizedCondition;
use super::{condition::ParenthesizedCondition, expression::Expression};
/// Syntax Synopsis:
///
@ -38,6 +38,7 @@ pub enum Statement {
Conditional(Conditional),
Grouping(Grouping),
DocComment(DocComment),
Semicolon(Semicolon),
}
impl SourceElement for Statement {
@ -48,6 +49,7 @@ impl SourceElement for Statement {
Self::Conditional(conditional) => conditional.span(),
Self::Grouping(grouping) => grouping.span(),
Self::DocComment(doc_comment) => doc_comment.span(),
Self::Semicolon(semi) => semi.span(),
}
}
}
@ -139,6 +141,37 @@ impl SourceElement for Conditional {
}
}
/// 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()
}
}
/// Syntax Synopsis:
///
/// ``` ebnf
@ -174,33 +207,35 @@ impl SourceElement for Grouping {
}
/// Syntax Synopsis:
///
/// ``` ebnf
/// Else:
/// 'else' Block
/// ;
/// Semicolon:
/// Expression ';'
/// ;
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Else {
/// The `else` keyword.
pub struct Semicolon {
/// The expression of the semicolon statement.
#[get = "pub"]
else_keyword: Keyword,
/// The block of the else statement.
expression: Expression,
/// The semicolon of the semicolon statement.
#[get = "pub"]
block: Box<Block>,
semicolon: Punctuation,
}
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 Semicolon {
fn span(&self) -> Span {
self.expression
.span()
.join(&self.semicolon.span())
.expect("The span of the semicolon statement is invalid.")
}
}
impl SourceElement for Else {
fn span(&self) -> Span {
self.else_keyword.span().join(&self.block.span()).unwrap()
impl Semicolon {
/// Dissolves the [`Semicolon`] into its components.
#[must_use]
pub fn dissolve(self) -> (Expression, Punctuation) {
(self.expression, self.semicolon)
}
}
@ -320,14 +355,15 @@ impl<'a> Parser<'a> {
}))
}
// other
unexpected => {
handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax {
expected: SyntaxKind::Statement,
found: unexpected.into_token(),
}));
// semicolon statement
_ => {
let expression = self.parse_expression(handler)?;
let semicolon = self.parse_punctuation(';', true, handler)?;
None
Some(Statement::Semicolon(Semicolon {
expression,
semicolon,
}))
}
}
}

View File

@ -2,7 +2,7 @@
use shulkerbox::datapack::Condition as DpCondition;
use crate::syntax::syntax_tree::expression::{
use crate::syntax::syntax_tree::condition::{
BinaryCondition, Condition, ConditionalBinaryOperator, ConditionalPrefixOperator,
PrimaryCondition,
};

View File

@ -1,22 +1,27 @@
//! Compiler for `ShulkerScript`
use std::collections::HashMap;
use std::{collections::HashMap, sync::RwLock};
use shulkerbox::datapack::{self, Command, Datapack, Execute};
use crate::{
base::{source_file::SourceElement, Handler},
syntax::syntax_tree::{declaration::Declaration, program::Program, statement::Statement},
syntax::syntax_tree::{
declaration::Declaration,
expression::{Expression, Primary},
program::Program,
statement::{Conditional, Statement},
},
};
use super::error::{self, TranspileError};
/// A transpiler for `ShulkerScript`.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Transpiler {
datapack: shulkerbox::datapack::Datapack,
functions: HashMap<String, FunctionData>,
function_locations: HashMap<String, String>,
functions: RwLock<HashMap<String, FunctionData>>,
function_locations: RwLock<HashMap<String, String>>,
}
#[derive(Debug, Clone)]
@ -32,8 +37,8 @@ impl Transpiler {
pub fn new(pack_name: &str, pack_format: u8) -> Self {
Self {
datapack: shulkerbox::datapack::Datapack::new(pack_name, pack_format),
functions: HashMap::new(),
function_locations: HashMap::new(),
functions: RwLock::new(HashMap::new()),
function_locations: RwLock::new(HashMap::new()),
}
}
@ -82,7 +87,7 @@ impl Transpiler {
)
})
.collect();
self.functions.insert(
self.functions.write().unwrap().insert(
name,
FunctionData {
namespace: "shulkerscript".to_string(),
@ -95,11 +100,23 @@ impl Transpiler {
}
/// Gets the function at the given path, or transpiles it if it hasn't been transpiled yet.
fn get_or_transpile_function(&mut self, path: &str) -> Option<&str> {
let already_transpiled = self.function_locations.get(path);
if already_transpiled.is_none() {
let function_data = self.functions.get(path)?;
let commands = compile_function(&function_data.statements);
/// Returns the location of the function or None if the function does not exist.
#[allow(clippy::significant_drop_tightening)]
fn get_or_transpile_function(&mut self, path: &str) -> Option<String> {
let already_transpiled = {
let locations = self.function_locations.read().unwrap();
locations.get(path).is_some()
};
if !already_transpiled {
let statements = {
let functions = self.functions.read().unwrap();
let function_data = functions.get(path)?;
function_data.statements.clone()
};
let commands = self.transpile_function(&statements);
let functions = self.functions.read().unwrap();
let function_data = functions.get(path)?;
let function = self
.datapack
@ -118,86 +135,114 @@ impl Transpiler {
}
self.function_locations
.write()
.unwrap()
.insert(path.to_string(), function_location);
}
self.function_locations.get(path).map(String::as_str)
let locations = self.function_locations.read().unwrap();
locations.get(path).map(String::to_owned)
}
}
fn compile_function(statements: &[Statement]) -> Vec<Command> {
let mut commands = Vec::new();
for statement in statements {
commands.extend(compile_statement(statement));
}
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.")
fn transpile_function(&mut self, statements: &[Statement]) -> Vec<Command> {
let mut commands = Vec::new();
for statement in statements {
commands.extend(self.transpile_statement(statement));
}
Statement::Conditional(cond) => {
let (_, cond, block, el) = cond.clone().dissolve();
let (_, cond, _) = cond.dissolve();
let statements = block.statements();
commands
}
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() {
fn transpile_statement(&mut self, 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) => self.transpile_conditional(cond),
Statement::DocComment(doccomment) => {
let content = doccomment.content();
Some(Command::Comment(content.to_string()))
}
Statement::Grouping(group) => {
let statements = group.block().statements();
let commands = statements
.iter()
.filter_map(|statement| self.transpile_statement(statement))
.collect::<Vec<_>>();
if commands.is_empty() {
None
} else {
Some(Command::Execute(Execute::If(
datapack::Condition::from(cond),
Box::new(Execute::Runs(Vec::new())),
el,
)))
Some(Command::Group(commands))
}
} 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])?))
};
}
Statement::Semicolon(semi) => match semi.expression() {
Expression::Primary(primary) => self.transpile_primary_expression(primary),
},
}
}
fn transpile_conditional(&mut self, cond: &Conditional) -> Option<Command> {
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 {
self.transpile_statement(&statements[0])
.map(|cmd| Execute::Run(Box::new(cmd)))
} else {
let commands = statements
.iter()
.filter_map(|statement| self.transpile_statement(statement))
.collect();
Some(Execute::Runs(commands))
}
})
.map(Box::new);
if statements.is_empty() {
if el.is_none() {
None
} else {
Some(Command::Execute(Execute::If(
datapack::Condition::from(cond),
Box::new(run),
Box::new(Execute::Runs(Vec::new())),
el,
)))
}
}
Statement::DocComment(doccomment) => {
let content = doccomment.content();
Some(Command::Comment(content.to_string()))
}
Statement::Grouping(group) => {
let statements = group.block().statements();
let commands = statements
.iter()
.filter_map(compile_statement)
.collect::<Vec<_>>();
if commands.is_empty() {
None
} else {
let run = if statements.len() > 1 {
let commands = statements
.iter()
.filter_map(|statement| self.transpile_statement(statement))
.collect();
Execute::Runs(commands)
} else {
Some(Command::Group(commands))
Execute::Run(Box::new(self.transpile_statement(&statements[0])?))
};
Some(Command::Execute(Execute::If(
datapack::Condition::from(cond),
Box::new(run),
el,
)))
}
}
fn transpile_primary_expression(&mut self, primary: &Primary) -> Option<Command> {
match primary {
Primary::FunctionCall(func) => {
let identifier = func.identifier().span();
let identifier = identifier.str();
let location = self.get_or_transpile_function(identifier)?;
Some(Command::Raw(format!("function {location}")))
}
}
}