matdb 0.1.0

An experimental embedded SQL-like DBMS
Documentation
pub mod lexer;

use std::iter::Peekable;

use anyhow::{Context, Result};
use create_table::CreateTableError;
use lexer::{Lexer, LexerError, Spanned, Token};
use thiserror::Error;

use crate::ast::{Column, Expr, Statement};

mod create_table;
mod delete;
mod expr;
mod insert;
mod primitive;
mod select;
mod update;

pub struct Parser<'a> {
    lexer: Peekable<Lexer<'a>>,
}

#[derive(Debug, Error)]
pub enum ParserError {
    #[error("unexpected token {0:?}")]
    Unexpected(Token),
    #[error("unexpected keyword {0}")]
    UnexpectedKeyword(String),
    #[error("unexpected end of input")]
    UnexpectedEOF,
    #[error("unexpected end of edge")]
    UnexpectedEndOfEdge,
    #[error("edge must have at least one expression in its list")]
    EdgeNoExpressions,
    #[error("lexer error: {0:?}")]
    LexerError(Spanned<LexerError>),
    #[error("create table error: {0}")]
    CreateTable(CreateTableError),
}

// https://forcedotcom.github.io/phoenix/index.html
impl<'a> Parser<'a> {
    pub fn make_parse_list<T, F: Fn(&mut Self) -> Result<T, ParserError>>(
        &mut self,
        f: F,
    ) -> Result<Vec<T>, ParserError> {
        let mut list = Vec::new();

        if let Ok(m) = f(self) {
            list.push(m);

            loop {
                if self.peek().map(|t| t == Token::Comma).unwrap_or_default() {
                    let _ = self.lexer.next().unwrap();
                } else {
                    break;
                }

                list.push(f(self)?)
            }
        }

        Ok(list)
    }

    pub fn new(lexer: Lexer<'a>) -> Parser<'a> {
        Parser {
            lexer: lexer.peekable(),
        }
    }

    fn parse_statement(&mut self) -> Result<Statement, ParserError> {
        match self.peek_symbol()?.as_str() {
            "CREATE" => Ok(Statement::CreateTable(self.parse_create_table_statement()?)),
            "DROP" => {
                self.try_consume_keyword("DROP")?;
                self.try_consume_keyword("TABLE")?;
                let table = self.try_consume_symbol()?;
                self.try_consume_semi()?;

                Ok(Statement::DropTable(table))
            }
            "SELECT" => self.parse_select_statement(),
            "DELETE" => self.parse_delete_statement(),
            "UPDATE" => self.parse_update_statement(),
            "INSERT" => self.parse_insert_statement(),

            token => Err(ParserError::UnexpectedKeyword(token.to_string())),
        }
    }

    fn parse_statement_or_eof(&mut self) -> Result<Option<Statement>, ParserError> {
        if self.lexer.peek().is_some() {
            Ok(Some(self.parse_statement()?))
        } else {
            Ok(None)
        }
    }

    pub fn parse_program(&mut self) -> Result<Vec<Statement>> {
        let mut program = Vec::new();

        // TODO: HACK: error lifetime badness
        while let Some(st) = self
            .parse_statement_or_eof()
            .context("failed parsing input")?
        {
            program.push(st);
        }

        Ok(program)
    }
}

fn rename_default_table(e: &Expr, default_table: &str) -> Expr {
    match e {
        Expr::Binding(_) => e.clone(),
        Expr::Literal(_) => e.clone(),
        Expr::Unary(bin_op, rhs) => {
            Expr::Unary(*bin_op, Box::new(rename_default_table(&rhs, default_table)))
        }
        Expr::Bin(lhs, bin_op, rhs) => Expr::Bin(
            Box::new(rename_default_table(&lhs, default_table)),
            *bin_op,
            Box::new(rename_default_table(&rhs, default_table)),
        ),
        Expr::Column(Column(fake_table, column)) => Expr::Column(Column(
            if fake_table == "" {
                default_table.to_string()
            } else {
                fake_table.to_string()
            },
            column.to_string(),
        )),
        Expr::Edge(lhs, mapping, rhs, e) => Expr::Edge(
            lhs.iter()
                .map(|e| rename_default_table(e, default_table))
                .collect(),
            mapping.clone(),
            rhs.as_ref().map(|rhs| {
                rhs.iter()
                    .map(|e| rename_default_table(e, default_table))
                    .collect()
            }),
            Box::new(rename_default_table(e, default_table)),
        ),
    }
}