knave-expand 0.13.2

The reading, scoping and expansion stages of knave
Documentation
use super::Expander;
use crate::core::{BindingId, BindingKind, Namespace};
use crate::reader::{Form, FormKind, Symbol};
use crate::scope::Resolution;
use crate::source::Span;

impl Expander<'_> {
    const TRIAL_LEFT: &'static str = "suggestion-left";
    const TRIAL_RIGHT: &'static str = "suggestion-right";

    pub(super) fn macro_function(
        &mut self,
        binding: BindingId,
        reference: &Symbol,
        span: Span,
    ) -> Option<BindingId> {
        let (trial, left, right) = Self::trial_form(reference, span);
        let output = self.discarded_expansion(binding, trial).ok()?;
        let function = self.output_function(&output, &left, &right)?;
        self.resolves_to(function, reference)
    }

    fn trial_form(reference: &Symbol, span: Span) -> (Form, Symbol, Symbol) {
        let left = Symbol::with_scopes(Self::TRIAL_LEFT, reference.scopes().clone());
        let right = Symbol::with_scopes(Self::TRIAL_RIGHT, reference.scopes().clone());
        let form = |symbol: &Symbol| Form::new(FormKind::Symbol(symbol.clone()), span);
        let items = vec![form(reference), form(&left), form(&right)];
        (Form::new(FormKind::List(items), span), left, right)
    }

    fn output_function(&self, output: &Form, left: &Symbol, right: &Symbol) -> Option<BindingId> {
        let [head, first, second] = output.as_list()? else {
            return None;
        };
        if first.as_symbol()?.name() != left.name() || second.as_symbol()?.name() != right.name() {
            return None;
        }
        let binding = self.try_resolve(head.as_symbol()?, Namespace::Value)?;
        matches!(self.kind(binding), BindingKind::Global(_)).then_some(binding)
    }

    fn resolves_to(&self, function: BindingId, reference: &Symbol) -> Option<BindingId> {
        let name = self.tables.bindings.get(function).name();
        let candidate = Symbol::with_scopes(name, reference.scopes().clone());
        match self
            .tables
            .table
            .resolve(&candidate, Namespace::Value, self.phase)
        {
            Resolution::Bound(resolved) if resolved == function => Some(function),
            _ => None,
        }
    }
}