diff --git a/src/lexical/token.rs b/src/lexical/token.rs index 124ba7c..6326cba 100644 --- a/src/lexical/token.rs +++ b/src/lexical/token.rs @@ -196,7 +196,7 @@ pub struct StringLiteral { impl StringLiteral { /// Returns the string without the leading and trailing double quotes. #[must_use] - pub fn string_content(&self) -> &str { + pub fn str_content(&self) -> &str { let string = self.span.str(); &string[1..string.len() - 1] } diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index e9e5fd7..9f3ef93 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -247,6 +247,30 @@ impl<'a> Frame<'a> { self.get_reading(self.token_provider.token_stream().get(self.current_index)) } + /// Returns the next significant [`Token`] after the `current_index` of the [`Frame`]. + #[must_use] + pub fn peek_significant(&self) -> Reading { + let mut index = self.current_index; + let token_stream = self.token_provider.token_stream(); + while index < self.token_provider.token_stream().len() { + let token = self.get_reading(token_stream.get(index)); + + if !matches!( + token, + Reading::Atomic(Token::WhiteSpaces(..) | Token::Comment(..)) + ) { + return token; + } + + index += 1; + } + + match self.token_provider { + TokenProvider::TokenStream(..) => Reading::Eof, + TokenProvider::Delimited(delimited) => Reading::DelimitedEnd(delimited.close.clone()), + } + } + /// Returns a [`Token`] pointing by the `current_index` with the given index offset of the /// [`Frame`]. /// diff --git a/src/syntax/syntax_tree/expression.rs b/src/syntax/syntax_tree/expression.rs index 266ee86..f3900b3 100644 --- a/src/syntax/syntax_tree/expression.rs +++ b/src/syntax/syntax_tree/expression.rs @@ -1,22 +1,127 @@ //! 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}, - Handler, + Dummy, Handler, }, lexical::{ token::{Punctuation, StringLiteral, Token}, token_stream::Delimiter, }, syntax::{ - error::{Error, UnexpectedSyntax}, + 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, + /// The operator of the binary condition. + #[get = "pub"] + operator: ConditionalBinaryOperator, + /// The right operand of the binary condition. + #[get = "pub"] + right_operand: Box, +} + +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 @@ -56,32 +161,178 @@ impl SourceElement for ParenthesizedCondition { /// Syntax Synopsis: /// /// ``` ebnf -/// Condition: StringLiteral; +/// 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 Condition { - /// The value of the condition. +pub struct ConditionalPrefix { + /// The operator of the prefix. #[get = "pub"] - pub value: StringLiteral, + operator: ConditionalPrefixOperator, + /// The operand of the prefix. + #[get = "pub"] + operand: Box, +} + +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 { - self.value.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) -> Option { - match self.next_significant_token() { - Reading::Atomic(Token::StringLiteral(s)) => Some(Condition { value: s }), + 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(¤t_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, + ) -> Option { + 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: crate::syntax::error::SyntaxKind::Expression, + expected: SyntaxKind::Expression, found: unexpected.into_token(), })); @@ -107,4 +358,21 @@ impl<'a> Parser<'a> { close_paren: token_tree.close, }) } + + fn try_parse_conditional_binary_operator(&mut self) -> Option { + 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, + }) + } } diff --git a/src/transpile/conversions.rs b/src/transpile/conversions.rs new file mode 100644 index 0000000..2360e0f --- /dev/null +++ b/src/transpile/conversions.rs @@ -0,0 +1,47 @@ +//! Conversion functions for converting between tokens/ast-nodes and [`shulkerbox`] types + +use shulkerbox::datapack::Condition as DpCondition; + +use crate::syntax::syntax_tree::expression::{ + BinaryCondition, Condition, ConditionalBinaryOperator, ConditionalPrefixOperator, + PrimaryCondition, +}; + +impl From for DpCondition { + fn from(value: Condition) -> Self { + match value { + Condition::Primary(primary) => primary.into(), + Condition::Binary(binary) => binary.into(), + } + } +} + +impl From for DpCondition { + fn from(value: PrimaryCondition) -> Self { + match value { + PrimaryCondition::StringLiteral(literal) => { + Self::Atom(literal.str_content().to_string()) + } + PrimaryCondition::Parenthesized(cond) => cond.dissolve().1.into(), + PrimaryCondition::Prefix(prefix) => match prefix.operator() { + ConditionalPrefixOperator::LogicalNot(_) => { + Self::Not(Box::new(prefix.dissolve().1.into())) + } + }, + } + } +} + +impl From for DpCondition { + fn from(value: BinaryCondition) -> Self { + let (lhs, op, rhs) = value.dissolve(); + match op { + ConditionalBinaryOperator::LogicalAnd(_, _) => { + Self::And(Box::new(lhs.into()), Box::new(rhs.into())) + } + ConditionalBinaryOperator::LogicalOr(_, _) => { + Self::Or(Box::new(lhs.into()), Box::new(rhs.into())) + } + } + } +} diff --git a/src/transpile/mod.rs b/src/transpile/mod.rs index 129a19b..d2ba5e0 100644 --- a/src/transpile/mod.rs +++ b/src/transpile/mod.rs @@ -1,4 +1,6 @@ //! The transpile module is responsible for transpiling the abstract syntax tree into a data pack. +#[doc(hidden)] +pub mod conversions; pub mod error; pub mod transpiler; diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 65f8506..2c5b992 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; -use shulkerbox::datapack::{Command, Datapack, Execute}; +use shulkerbox::datapack::{self, Command, Datapack, Execute}; use crate::{ base::{source_file::SourceElement, Handler}, @@ -19,7 +19,7 @@ pub struct Transpiler { type AnnotationMap = HashMap>; impl Transpiler { - /// Creates a new compiler. + /// Creates a new transpiler. #[must_use] pub fn new() -> Self { Self { @@ -27,7 +27,7 @@ impl Transpiler { } } - /// Compiles the given program. + /// Transpiles the given program. /// /// # Errors /// - [`TranspileError::MissingMainFunction`] If the main function is missing. @@ -49,9 +49,7 @@ impl Transpiler { let value = annotation.value(); ( key.span().str().to_string(), - value - .as_ref() - .map(|(_, ref v)| v.string_content().to_string()), + value.as_ref().map(|(_, ref v)| v.str_content().to_string()), ) }) .collect(); @@ -122,7 +120,7 @@ fn compile_statement(statement: &Statement) -> Option { None } else { Some(Command::Execute(Execute::If( - cond.value().string_content().into(), + datapack::Condition::from(cond), Box::new(Execute::Runs(Vec::new())), el, ))) @@ -136,7 +134,7 @@ fn compile_statement(statement: &Statement) -> Option { }; Some(Command::Execute(Execute::If( - cond.value().string_content().into(), + datapack::Condition::from(cond), Box::new(run), el, )))