hamelin_legacy 0.4.3

Legacy AST translation code for Hamelin (to be deprecated)
Documentation
use std::{cell::RefCell, rc::Rc};

use antlr_rust::tree::ParseTree;

use hamelin_lib::{
    antlr::{
        completion_interval,
        hamelinparser::{
            AssignmentClauseContextAll, AssignmentClauseContextAttrs, AssignmentContextAttrs,
            ExpressionContextAll, ExpressionEOFContextAttrs, TsTruncContextAttrs,
        },
        interval,
    },
    completion::Completion,
    err::TranslationErrors,
    parse_identifier,
    parser::make_hamelin_parser_from_input,
    sql::expression::{
        identifier::{HamelinIdentifier, Identifier, SimpleIdentifier},
        SQLExpression,
    },
    translation::ExpressionTranslation,
};

use super::{expression::HamelinExpression, ExpressionTranslationContext};

pub struct HamelinAssignmentClause {
    pub ctx: Rc<AssignmentClauseContextAll<'static>>,
    pub translation_context: Rc<ExpressionTranslationContext>,
}

impl HamelinAssignmentClause {
    pub fn new(
        ctx: Rc<AssignmentClauseContextAll<'static>>,
        expression_translation_context: Rc<ExpressionTranslationContext>,
    ) -> Self {
        Self {
            ctx,
            translation_context: expression_translation_context,
        }
    }

    pub fn to_sql(&self) -> Result<(Identifier, ExpressionTranslation), TranslationErrors> {
        if self.ctx.expression().is_none() && self.ctx.assignment().is_none() {
            self.append_completions();
        }

        if let Some(expression_tree) = self.ctx.expression() {
            let expression =
                HamelinExpression::new(expression_tree.clone(), self.translation_context.clone());
            let translation = expression.translate()?;

            // Re-parse the expression to see if it works as an identifier.
            // We have to re-parse because the ANTLR grammar would have applied the #deref expression alt in this context.
            let maybe_identifier = parse_identifier(self.ctx.get_text())
                .ok()
                .and_then(|i| HamelinIdentifier::new(i).to_sql().ok());

            // If the expression works as an identifier, we roll with that
            let identifier = maybe_identifier
                .or_else(|| match &translation.sql {
                    // If the expression is a function call with no arguments, roll with the function name
                    SQLExpression::FunctionCallApply(fca)
                        if fca.arguments.is_empty() && fca.named_arguments.is_empty() =>
                    {
                        fca.function_name.clone().parse().ok()
                    }
                    _ => None,
                })
                // If the expression is a tstrunc of an identifier, roll with the identifier.
                .or_else(|| match expression_tree.as_ref() {
                    ExpressionContextAll::TsTruncContext(ctx) => ctx
                        .expression()
                        .and_then(|exp| parse_identifier(exp.get_text()).ok())
                        .and_then(|i| HamelinIdentifier::new(i).to_sql().ok()),
                    _ => None,
                })
                // Don't know what else to do. Use the backquote that I love and everyone else hates.
                .unwrap_or_else(|| {
                    SimpleIdentifier::new(&expression_tree.get_text().replace("`", "")).into()
                });

            Ok((identifier.into(), translation))
        } else {
            let assignment_tree =
                TranslationErrors::expect(self.ctx.as_ref(), self.ctx.assignment())?;
            let identifier_tree =
                TranslationErrors::expect(assignment_tree.as_ref(), assignment_tree.identifier())?;
            let expression_tree =
                TranslationErrors::expect(assignment_tree.as_ref(), assignment_tree.expression())?;
            let identifier = HamelinIdentifier::new(identifier_tree);
            let expression =
                HamelinExpression::new(expression_tree.clone(), self.translation_context.clone());

            let translation = expression.translate()?;

            Ok((identifier.to_sql()?, translation))
        }
    }

    // Append all expression completions.
    //
    // We only complete on an assignment clause when its children are broken. If the children
    // aren't broken, there will be hooks on the children for doing better completion.
    fn append_completions(&self) {
        let range = completion_interval(self.ctx.as_ref());

        match self.translation_context.at {
            Some(at) if range.contains(&at) => {
                let text = self.ctx.get_text();

                if text.is_empty() {
                    // If the error node is literally empty text, this just means that we are free
                    // to dump the "empty completion" plan to the user. They haven't typed anything
                    // yet, so we just give them the "any expression" full list of options.
                    if let Ok(mut guard) = self.translation_context.completions.try_borrow_mut() {
                        if guard.is_none() {
                            let insert_interval = interval(self.ctx.as_ref());
                            let mut completion = Completion::new(insert_interval);
                            completion.add_items(
                                self.translation_context
                                    .bindings
                                    .autocomplete_suggestions(false),
                            );
                            completion.add_items(
                                self.translation_context.registry.autocomplete_suggestions(),
                            );
                            *guard = Some(completion);
                        }
                    }
                } else {
                    // If the error node has text in it, brace yourself. Fuck my life.
                    //
                    // ANTLR's error recovery seems to always predict that a broken assignment
                    // clause is actually an assignment, rather than an expression.
                    //
                    //   When the user types "<ident> = " then ANTLR can correctly figure out
                    //   that the next thing is an expression, and normal completion works just fine.
                    //
                    //   When the user just starts typing an expression, like "client.", it seems
                    //   to think that the dot is actually just an incorrect =, and it predicts
                    //   this as a broken assignment. Chaos ensues.
                    //
                    // So, if there is any text at all in the broken assignment clause, we just
                    // FORCE the issue -- re-parse the text as an expression and do completion of that.
                    // If that thing yields any items, we translate those sub-items and yeet them up.
                    //
                    // Christ on a bicycle.
                    let expression_tree = make_hamelin_parser_from_input(self.ctx.get_text())
                        .0
                        .expressionEOF();
                    if let Some(tree) = expression_tree.ok().and_then(|t| t.expression()) {
                        // Set up a brand new context, with a brand new completion spot.
                        // This new set of completions is entirely distinct from the current ones.
                        let sub_context = ExpressionTranslationContext::new(
                            self.translation_context.bindings.clone(),
                            self.translation_context.registry.clone(),
                            self.translation_context.translation_registry.clone(),
                            self.translation_context.fctx.clone(),
                            Some(at - range.start()),
                            Rc::new(RefCell::new(None)),
                        );

                        // Will have the side effect of completions on the above context.
                        // Don't love this. Should probably be able to .append_completions() from outside.
                        let _ignore = HamelinExpression::new(tree, sub_context.clone()).translate();

                        // Translate the nested completions into the current context. Ugh.
                        let top_guard = self.translation_context.completions.try_borrow_mut();
                        let sub_guard = sub_context.completions.borrow();
                        match (top_guard, &*sub_guard) {
                            (Ok(mut tg), Some(sg)) if tg.is_none() => {
                                let mut completion = Completion::new(
                                    sg.at.start() + range.start()..=sg.at.end() + range.end(),
                                );
                                completion.filter(false);
                                completion.add_items(sg.items.clone());
                                *tg = Some(completion);
                            }
                            _ => {}
                        }
                    }
                }
            }
            _ => {}
        }
    }
}