knave-expand 0.14.21

The reading, scoping and expansion stages of knave
Documentation
use std::rc::Rc;

use super::Expander;
use super::problems::ExpandProblems;
use crate::core::{
    Binder, BindingInfo, BindingKind, Block, BodyItem, Expr, ExprKind, FieldLabel, FieldSpelling,
    Keyword, Lambda, Leaf, StructExpr,
};
use crate::diagnostic::Diagnostic;
use crate::reader::Form;
use crate::source::Span;

impl Expander<'_> {
    const FIELD_READER_PARAMETER: &'static str = "value";

    pub(super) fn lower_field_keyword_call(
        &mut self,
        form: &Form,
        field_name: &str,
    ) -> Result<Expr, Diagnostic> {
        let items = form.as_list().unwrap_or_default();
        let [keyword, value] = items else {
            return Err(Self::field_keyword_arity(form, items));
        };
        let value = self.lower_recovered(value)?;
        let label = Self::keyword_label(field_name, keyword.span());
        Ok(Self::field_read(value, label, form.span()))
    }

    pub(super) fn lower_field_reader(&mut self, field_name: &str, span: Span) -> ExprKind {
        let parameter = self.tables.bindings.add(BindingInfo::new(
            Self::FIELD_READER_PARAMETER.into(),
            BindingKind::Local,
            Some(span),
        ));
        let value = Expr::new(ExprKind::Leaf(Leaf::Var(parameter)), span);
        let read = Self::field_read(value, Self::keyword_label(field_name, span), span);
        ExprKind::Fn(Rc::new(Lambda::new(
            vec![Binder {
                binding: parameter,
                annotation: None,
                span,
            }],
            Block {
                items: vec![BodyItem::Expr(read)],
            },
        )))
    }

    pub(super) fn reject_keyword_binder(name: &str, span: Span) -> Result<(), Diagnostic> {
        match Keyword::field_name(name) {
            Some(_) => Err(ExpandProblems::keyword_binder(span)),
            None => Ok(()),
        }
    }

    pub(super) fn written_field_keyword(form: &Form) -> Option<&str> {
        Keyword::field_name(form.as_symbol()?.name())
    }

    fn field_keyword_arity(form: &Form, items: &[Form]) -> Diagnostic {
        let keyword = items[0].to_string();
        match items.get(2) {
            Some(extra) => ExpandProblems::extra_field_keyword_value(&keyword, extra.span()),
            None => ExpandProblems::missing_field_keyword_value(&keyword, form.span()),
        }
    }

    pub(super) fn keyword_label(field_name: &str, span: Span) -> FieldLabel {
        FieldLabel {
            name: field_name.into(),
            span,
            spelling: FieldSpelling::Keyword,
        }
    }

    fn field_read(value: Expr, label: FieldLabel, span: Span) -> Expr {
        Expr::new(
            ExprKind::Struct(StructExpr::Field(Box::new(value), label)),
            span,
        )
    }
}