orql 0.1.0

A toy SQL parser for a subset of the Oracle dialect.
Documentation
//! Routines to parse CASE expressions.

use super::{Error, ExprParser, Location, MetaTracker, ParseCaseIdent, Result};
use crate::{
    ast::{
        BinaryExpr, BinaryExprOp, CaseElse, CaseExpr, CaseWhenSearched, CaseWhenSimple, CaseWhens,
        Expr, Ident, Identifier, Node, UnaryExpr, UnaryExprOp,
    },
    parser::{Prec, expression::LeftOverIdent, precedence},
    scanner::{Keyword, Reserved, Token, TokenType},
};

impl<'p, 's, M> ExprParser<'p, 's, M>
where
    M: MetaTracker<'s>,
{
    pub(super) fn parse_case_ident_(
        &mut self,
        reserved_case_token: Node<Ident<'s>, M::NodeId>,
        // `true` to allow a "CASE foo" _not_ followed by WHEN;
        // `false` to abort parsing such a construct with an error
        min_bp: Prec,
    ) -> Result<ParseCaseIdent<'s, M::NodeId>> {
        // ~ lookahead and parse ...
        // * ... into identifier if followed by a dot or not binary or unary operator
        // * ... into case_expr if followed by WHEN
        // * ... into binary_expr if followed by *, /, ||
        // * ... into binary_expr if followed by unary-expr _not_ followed by WHEN
        // * ... into case_expr if followed by expression (including unary-exprs), followed by WHEN
        let maybe_expr = match self.inner.peek_token()? {
            None => {
                // ~ no need to parse a right-hand-side, we're already at the
                // end of the token stream
                return Ok(ParseCaseIdent::Expr(Expr::Identifier(Identifier::Simple(
                    reserved_case_token,
                ))));
            }
            Some(t) => match &t.ttype {
                // // XXX only if the the parens do contain a comma separated list, ie. more than one element
                // TokenType::LeftParen => {
                //     // ~ function call; yes, oracle 21 supports `select case(21) from dual` :-/
                // }
                TokenType::At
                | TokenType::Dot
                | TokenType::LeftParen
                | TokenType::RightParen
                | TokenType::Comma
                | TokenType::Semicolon
                | TokenType::Equal
                | TokenType::CaretEqual
                | TokenType::Less
                | TokenType::LessEqual
                | TokenType::LessGreater
                | TokenType::Greater
                | TokenType::GreaterEqual
                | TokenType::BangEqual
                | TokenType::Keyword(_) => return Ok(ParseCaseIdent::Ident(reserved_case_token)),
                TokenType::Star | TokenType::Slash | TokenType::PipePipe => {
                    ParseCaseIdent::Expr(Expr::Identifier(Identifier::Simple(reserved_case_token)))
                }
                tt @ TokenType::Plus | tt @ TokenType::Minus => {
                    // ~ parse a right hand side of the op and depending on
                    // whehter the WHEN reserved word following turn in into a
                    // binary_expr or case_expr
                    let op = {
                        let loc = t.loc;
                        let ops = if matches!(tt, TokenType::Plus) {
                            (UnaryExprOp::Add, BinaryExprOp::Add)
                        } else {
                            (UnaryExprOp::Sub, BinaryExprOp::Sub)
                        };
                        self.inner.consume_token()?;
                        Node(ops, self.inner.meta_tracker.on_node_start(loc))
                    };
                    let right =
                        self.parse_(precedence::unary(precedence::UnaryOp::Expr(op.0.0)).1)?;
                    // ~ if we've had left over, it cannot be a case_expr
                    if !matches!(
                        self.context.allow_left_over_ident,
                        LeftOverIdent::Allowed(Some(_))
                    ) && let Some(Token {
                        ttype: TokenType::Identifier(_, Some(Reserved::WHEN)),
                        ..
                    }) = self.inner.peek_token()?
                    {
                        let expr = Expr::Unary(
                            UnaryExpr {
                                op: Node(op.0.0, op.1),
                                expr: right,
                            }
                            .into(),
                        );
                        self.parse_case_expr_(reserved_case_token, Some(expr))?
                    } else {
                        ParseCaseIdent::Expr(Expr::Binary(
                            BinaryExpr {
                                left: Expr::Identifier(Identifier::Simple(reserved_case_token)),
                                op: Node(op.0.1, op.1),
                                right,
                            }
                            .into(),
                        ))
                    }
                }
                _ => self.parse_case_expr_(reserved_case_token, None)?,
            },
        };
        match maybe_expr {
            ident @ ParseCaseIdent::Ident(_) => Ok(ident),
            ParseCaseIdent::Expr(left) => {
                Ok(ParseCaseIdent::Expr(self.parse_right_(left, min_bp)?))
            }
        }
    }

    fn parse_case_expr_(
        &mut self,
        reserved_case_token: Node<Ident<'s>, M::NodeId>,
        case_expr: Option<Expr<'s, M::NodeId>>,
    ) -> Result<ParseCaseIdent<'s, M::NodeId>> {
        // ~ no left over idents allowed, the posible alias is tracked here
        // explicitly, which we'll assign ourselves (if there's one)
        let mut p = ExprParser {
            inner: &mut *self.inner,
            context: self.context.with_left_over_ident(LeftOverIdent::NotAllowed),
        };
        let case_token = Node((), reserved_case_token.1);
        let when_branches = if case_expr.is_none()
            && let Some(Token {
                ttype: TokenType::Identifier(_, Some(Reserved::WHEN)),
                loc,
            }) = p.inner.peek_token()?
        {
            let mut whens = Vec::new();
            let mut when_token = {
                let loc = *loc;
                p.inner.consume_token()?;
                Node((), p.inner.meta_tracker.on_node_start(loc))
            };
            loop {
                let condition = p
                    .inner
                    .condition_parser()
                    .with_context(p.context.condition_context)
                    .parse_condition()?;
                let then_token = expect_token!(|t = (p.inner).next_token()| "the THEN keyword" match {
                    TokenType::Keyword(Keyword::THEN) => Node((), p.inner.meta_tracker.on_node_start(t.loc)),
                });
                let return_expr = p.parse()?;
                whens.push(CaseWhenSearched {
                    when_token,
                    when_condition: condition,
                    then_token,
                    return_expr,
                });
                if let Some(Token {
                    ttype: TokenType::Identifier(_, Some(Reserved::WHEN)),
                    loc,
                }) = p.inner.peek_token()?
                {
                    let loc = *loc;
                    p.inner.consume_token()?;
                    when_token = Node((), p.inner.meta_tracker.on_node_start(loc));
                } else {
                    break;
                }
            }
            CaseWhens::Searched { whens }
        } else {
            let error_loc = p
                .inner
                .peek_token()?
                .map(|t| t.loc)
                .unwrap_or(Location { line: 0, col: 0 });
            let case_expr = match case_expr {
                Some(expr) => expr,
                None => p.parse()?,
            };
            // ~ if the next token is not `WHEN` then the parsed `case_expr`
            // could be an alias
            if !matches!(
                p.inner.peek_token()?,
                Some(Token {
                    ttype: TokenType::Identifier(_, Some(Reserved::WHEN)),
                    ..
                })
            ) && let LeftOverIdent::Allowed(node) = &mut self.context.allow_left_over_ident
            {
                return if let Expr::Identifier(Identifier::Simple(alias)) = case_expr {
                    *node = Some(alias);
                    Ok(ParseCaseIdent::Expr(Expr::Identifier(Identifier::Simple(
                        reserved_case_token,
                    ))))
                } else {
                    Err(Error::Unexpected {
                        unexpected: "expression".into(),
                        expected: "an alias",
                        loc: error_loc,
                    })
                };
            }

            let mut whens = Vec::new();
            let mut when_token = {
                let parser = &mut *p.inner;
                expect_reserved!(|t = parser.next_token()| "the WHEN keyword" match {
                    Reserved::WHEN => Node((), parser.meta_tracker.on_node_start(t.loc)),
                })
            };
            loop {
                let when_expr = p.parse()?;
                let then_token = {
                    let parser = &mut *p.inner;
                    expect_token!(|t = parser.next_token()| "the THEN keyword" match {
                        TokenType::Keyword(Keyword::THEN) => Node((), parser.meta_tracker.on_node_start(t.loc)),
                    })
                };
                let return_expr = p.parse()?;
                whens.push(CaseWhenSimple {
                    when_token,
                    when_expr,
                    then_token,
                    return_expr,
                });
                if let Some(Token {
                    ttype: TokenType::Identifier(_, Some(Reserved::WHEN)),
                    loc,
                }) = p.inner.peek_token()?
                {
                    let loc = *loc;
                    p.inner.consume_token()?;
                    when_token = Node((), p.inner.meta_tracker.on_node_start(loc));
                } else {
                    break;
                }
            }
            CaseWhens::Simple { case_expr, whens }
        };
        let (else_branch, end_token) = {
            let parser = &mut *p.inner;
            expect_token!(|t = parser.next_token()| "the ELSE or END keyword" match {
                TokenType::Keyword(Keyword::ELSE) => {
                    let else_token = Node((), p.inner.meta_tracker.on_node_start(t.loc));
                    let return_expr = p.parse()?;
                    let end_token = {
                        expect_reserved!(|t = (p.inner).next_token()| "the END keyword" match {
                            Reserved::END => Node((), p.inner.meta_tracker.on_node_start(t.loc)),
                        })
                    };
                    (Some(CaseElse { else_token, return_expr }), end_token)
                }
                TokenType::Identifier(_, Some(Reserved::END)) => {
                    (None, Node((), p.inner.meta_tracker.on_node_start(t.loc)))
                }
            })
        };
        Ok(ParseCaseIdent::Expr(Expr::Case(
            CaseExpr {
                case_token,
                when_branches,
                else_branch,
                end_token,
            }
            .into(),
        )))
    }
}