orql 0.1.0

A toy SQL parser for a subset of the Oracle dialect.
Documentation
use std::fmt::Debug;

use super::{Keyword, Reserved, Text};

/// A token along with its location in the parsed source code
#[derive(Debug, PartialEq, Eq)]
pub struct Token<'s> {
    pub ttype: TokenType<'s>,
    pub loc: Location,
}

impl<'s> AsRef<Token<'s>> for Token<'s> {
    fn as_ref(&self) -> &Token<'s> {
        self
    }
}

/// Supported tokens
#[derive(Debug, PartialEq, Eq)]
pub enum TokenType<'s> {
    /// `(`
    LeftParen,
    /// `)`
    RightParen,
    /// `.`
    Dot,
    /// `,`
    Comma,
    /// `;`
    Semicolon,
    /// `+`
    Plus,
    /// `-`
    Minus,
    /// `*`
    Star,
    /// `/`
    Slash,
    /// `@`
    At,
    /// `||`
    PipePipe,
    /// `=`
    Equal,
    /// `=>`
    EqualGreater,
    /// `^=`
    CaretEqual,
    /// `<`
    Less,
    /// `<=`
    LessEqual,
    /// `<>`
    LessGreater,
    /// `>`
    Greater,
    /// `>=`
    GreaterEqual,
    /// `!=`
    BangEqual,
    /// `?`
    QuestionMark,

    /// `-- ...` or `/* ... */`
    Comment(Comment<'s>),

    /// e.g. `42` or `01234` but also `00001`
    Integer(&'s str),
    // XXX a finer grained exposure of the number's details
    Float(&'s str),
    /// Either a plain or quote delimited text / string literal, e.g. `'...'` or
    /// `Q'[...]'`. Note: the quotes themselves are _not_ part of the text.
    Text(Text<'s>, NationalStyle),

    /// A named placeholder like `:foo` or `:"foo"`; the identifier does
    /// neither include the colon `:` nor the quotes (if any)
    Placeholder(Ident<'s>),

    /// A single word identifier, e.g. `name`. Possibly a reserved word.
    Identifier(Ident<'s>, Option<Reserved>),

    /// A keyword
    Keyword(Keyword),
}

/// A single word identifier, i.e. not considered a keyword
#[derive(Debug, PartialEq, Eq)]
pub struct Ident<'s>(pub &'s str, pub QuoteStyle);

impl<'s> std::fmt::Display for Ident<'s> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        crate::fmt::Display::fmt(self, f)
    }
}

impl<'s> crate::fmt::Display for Ident<'s> {
    fn fmt(&self, f: &mut impl crate::fmt::Formatter) -> std::fmt::Result {
        match self.1 {
            QuoteStyle::None => f.write_str(self.0),
            QuoteStyle::Quoted => {
                f.write_char('"')?;
                f.write_str(self.0)?;
                f.write_char('"')
            }
        }
    }
}

/// Identifier quote style
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum QuoteStyle {
    /// non-quoted, `abc`
    None,
    /// quoted, e.g. `"abc"`
    Quoted,
}

/// String literal encoding; see [TokenType::Text].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum NationalStyle {
    /// plain string literal, `'asdf ...'`
    None,
    /// national string literal, e.g. `N'asdf ...'` or `NQ'{asdf...}'`
    National,
}

/// A SQL source code comment, either single- or multi-line.
///
/// Note: single line comments do _not_ contain the terminating newline.
#[derive(Debug, PartialEq, Eq)]
pub struct Comment<'s>(pub &'s str, pub CommentStyle);

/// Type of comment
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CommentStyle {
    /// a single line comment; e.g. `-- ...`
    Line,
    /// a block comment, possibly multi-line; e.g. `/* ... */`
    Block,
}

/// A location within a parsed SQL text as a coordinate of `(line, column)` with
/// both starting at `1`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Location {
    /// The line number, starting with `1`
    pub line: u32,
    /// The column number, starting with `1`
    pub col: u32,
}

impl Ord for Location {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.line
            .cmp(&other.line)
            .then_with(|| self.col.cmp(&other.col))
    }
}

impl PartialOrd for Location {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl From<(u32, u32)> for Location {
    fn from((line, col): (u32, u32)) -> Self {
        Self { line, col }
    }
}

impl Location {
    #[must_use]
    pub(crate) fn with_cols_removed(&self, n: u32) -> Self {
        Self {
            line: self.line,
            col: self.col.saturating_sub(n),
        }
    }
}

impl std::fmt::Display for Location {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "[line: {}, column: {}]", self.line, self.col)
    }
}