kosame_dsl 0.3.0

Macro-based Rust ORM focused on developer ergonomics
Documentation
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::parse::{Parse, ParseStream};

use crate::{
    clause::{self, Fields, FromChain, Limit, Offset, OrderBy},
    command::Command,
    keyword,
    parse_option::ParseOption,
    part::{SetOp, SetQuantifier},
    pretty::{BreakMode, Delim, PrettyPrint, Printer},
    quote_option::QuoteOption,
    scopes::{ScopeId, Scoped},
    visit::Visit,
};

pub struct Select {
    pub chain: SelectChain,
    pub order_by: Option<OrderBy>,
    pub limit: Option<Limit>,
    pub offset: Option<Offset>,
}

impl Select {
    #[must_use]
    pub fn fields(&self) -> &Fields {
        match &self.chain.start {
            SelectItem::Core(core) => &core.select.fields,
            SelectItem::Paren { command, .. } => {
                command.fields().expect("nested select must have fields")
            }
        }
    }

    #[must_use]
    #[allow(clippy::wrong_self_convention)]
    pub fn from_chain(&self) -> Option<&FromChain> {
        if !self.chain.combinators.is_empty() {
            return None;
        }
        match &self.chain.start {
            SelectItem::Core(core) => core.from.as_ref().map(|from| &from.chain),
            SelectItem::Paren { command, .. } => command.from_chain(),
        }
    }

    pub fn peek(input: ParseStream) -> bool {
        SelectItem::peek(input)
    }
}

pub fn visit_select_command<'a>(visit: &mut (impl Visit<'a> + ?Sized), select: &'a Select) {
    visit.visit_select_chain(&select.chain);
    if let Some(inner) = select.order_by.as_ref() {
        visit.visit_order_by(inner);
    }
    if let Some(inner) = select.limit.as_ref() {
        visit.visit_limit(inner);
    }
    if let Some(inner) = select.offset.as_ref() {
        visit.visit_offset(inner);
    }
}

impl Parse for Select {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let chain = input.parse()?;
        let order_by = input.call(OrderBy::parse_option)?;
        let limit = input.call(Limit::parse_option)?;
        let offset = input.call(Offset::parse_option)?;

        Ok(Self {
            chain,
            order_by,
            limit,
            offset,
        })
    }
}

impl ToTokens for Select {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let chain = &self.chain;
        let order_by = QuoteOption::from(&self.order_by);
        let limit = QuoteOption::from(&self.limit);
        let offset = QuoteOption::from(&self.offset);

        quote! {
            ::kosame::repr::command::Select::new(
                #chain,
                #order_by,
                #limit,
                #offset,
            )
        }
        .to_tokens(tokens);
    }
}

impl PrettyPrint for Select {
    fn pretty_print(&self, printer: &mut Printer<'_>) {
        self.chain.pretty_print(printer);
        self.order_by.pretty_print(printer);
        self.limit.pretty_print(printer);
        self.offset.pretty_print(printer);
    }
}

pub struct SelectChain {
    pub start: SelectItem,
    pub combinators: Vec<SelectCombinator>,
}

impl SelectChain {
    #[must_use]
    pub fn iter(&self) -> SelectIter<'_> {
        self.into_iter()
    }
}

pub fn visit_select_chain<'a>(
    visit: &mut (impl Visit<'a> + ?Sized),
    select_chain: &'a SelectChain,
) {
    visit.visit_select_item(&select_chain.start);
    for combinator in &select_chain.combinators {
        visit.visit_select_combinator(combinator);
    }
}

impl Parse for SelectChain {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let start = input.parse()?;
        let mut combinators = Vec::new();
        while let Some(combinator) = input.call(SelectCombinator::parse_option)? {
            combinators.push(combinator);
        }
        Ok(Self { start, combinators })
    }
}

impl ToTokens for SelectChain {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let start = &self.start;
        let combinators = &self.combinators;

        quote! {
            ::kosame::repr::command::SelectChain::new(
                #start,
                &[#(#combinators),*],
            )
        }
        .to_tokens(tokens);
    }
}

impl PrettyPrint for SelectChain {
    fn pretty_print(&self, printer: &mut Printer<'_>) {
        self.start.pretty_print(printer);
        self.combinators.pretty_print(printer);
    }
}

pub enum SelectItem {
    Core(Box<clause::SelectCore>),
    Paren {
        paren_token: syn::token::Paren,
        command: Box<Command>,
    },
}

impl SelectItem {
    #[must_use]
    pub fn scope_id(&self) -> ScopeId {
        match self {
            Self::Core(inner) => inner.scope_id,
            Self::Paren { command, .. } => command.scope_id,
        }
    }
}

pub fn visit_select_item<'a>(visit: &mut (impl Visit<'a> + ?Sized), select_item: &'a SelectItem) {
    match select_item {
        SelectItem::Core(core) => visit.visit_select_core(core),
        SelectItem::Paren { command, .. } => visit.visit_command(command),
    }
}

impl Parse for SelectItem {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        if input.peek(syn::token::Paren) {
            let content;
            Ok(Self::Paren {
                paren_token: syn::parenthesized!(content in input),
                command: content.parse()?,
            })
        } else {
            let core = input.parse()?;
            Ok(Self::Core(core))
        }
    }
}

impl ParseOption for SelectItem {
    fn peek(input: ParseStream) -> bool {
        clause::SelectCore::peek(input) || input.peek(syn::token::Paren)
    }
}

impl ToTokens for SelectItem {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::Core(core) => {
                quote! {
                    ::kosame::repr::command::SelectItem::Core(#core)
                }
                .to_tokens(tokens);
            }
            Self::Paren { command, .. } => {
                quote! {
                    ::kosame::repr::command::SelectItem::Paren(&#command)
                }
                .to_tokens(tokens);
            }
        }
    }
}

impl PrettyPrint for SelectItem {
    fn pretty_print(&self, printer: &mut Printer<'_>) {
        match self {
            Self::Core(core) => core.pretty_print(printer),
            Self::Paren {
                paren_token,
                command,
            } => {
                paren_token.pretty_print(printer, Some(BreakMode::Inconsistent), |printer| {
                    command.pretty_print(printer);
                });
            }
        }
    }
}

pub struct SelectCombinator {
    pub op: SetOp,
    pub quantifier: SetQuantifier,
    pub right: SelectItem,
}

pub fn visit_select_combinator<'a>(
    visit: &mut (impl Visit<'a> + ?Sized),
    select_combinator: &'a SelectCombinator,
) {
    visit.visit_select_item(&select_combinator.right);
}

impl Parse for SelectCombinator {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let op = input.parse()?;
        let quantifier = input.parse()?;
        let right = input.parse()?;

        Ok(Self {
            op,
            quantifier,
            right,
        })
    }
}

impl ParseOption for SelectCombinator {
    fn peek(input: ParseStream) -> bool {
        input.peek(keyword::union) || input.peek(keyword::intersect) || input.peek(keyword::except)
    }
}

impl ToTokens for SelectCombinator {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let op = &self.op;
        let quantifier = &self.quantifier;
        let right = &self.right;

        quote! {
            ::kosame::repr::command::SelectCombinator::new(
                #op,
                #quantifier,
                #right,
            )
        }
        .to_tokens(tokens);
    }
}

impl PrettyPrint for SelectCombinator {
    fn pretty_print(&self, printer: &mut Printer<'_>) {
        printer.scan_break();
        " ".pretty_print(printer);
        printer.scan_trivia(false, true);
        self.op.pretty_print(printer);
        self.quantifier.pretty_print(printer);
        printer.scan_indent(1);
        printer.scan_break();
        " ".pretty_print(printer);
        self.right.pretty_print(printer);
        printer.scan_indent(-1);
    }
}

pub struct SelectIter<'a> {
    chain: &'a SelectChain,
    index: i32,
}

impl<'a> Iterator for SelectIter<'a> {
    type Item = &'a SelectItem;

    fn next(&mut self) -> Option<Self::Item> {
        let item = match self.index {
            -1 => &self.chain.start,
            _ => {
                &self
                    .chain
                    .combinators
                    .get::<usize>(self.index.try_into().unwrap())?
                    .right
            }
        };
        self.index += 1;
        Some(item)
    }
}

impl<'a> IntoIterator for &'a SelectChain {
    type IntoIter = SelectIter<'a>;
    type Item = &'a SelectItem;

    fn into_iter(self) -> Self::IntoIter {
        SelectIter {
            index: -1,
            chain: self,
        }
    }
}