Implement conditional operators

This commit is contained in:
Moritz Hölting 2024-04-03 13:46:57 +02:00
parent 9159c8222e
commit 4b52985992
6 changed files with 358 additions and 19 deletions

View File

@ -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]
}

View File

@ -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`].
///

View File

@ -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<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
@ -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<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 {
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<Error>) -> Option<Condition> {
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(&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: 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<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

@ -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<Condition> for DpCondition {
fn from(value: Condition) -> Self {
match value {
Condition::Primary(primary) => primary.into(),
Condition::Binary(binary) => binary.into(),
}
}
}
impl From<PrimaryCondition> 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<BinaryCondition> 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()))
}
}
}
}

View File

@ -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;

View File

@ -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<String, Option<String>>;
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<Command> {
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<Command> {
};
Some(Command::Execute(Execute::If(
cond.value().string_content().into(),
datapack::Condition::from(cond),
Box::new(run),
el,
)))