knave 0.12.6

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use super::{ArgumentRole, ExpressionEmitter, TypeContext};
use crate::core::{ReferenceKind, RustTemplate};
use crate::emit::fragment::Code;
use crate::source::Span;
use crate::types::{
    Borrow, Type, TypedArgument, TypedExpr, TypedExprKind, TypedLeaf, TypedStructExpr,
};

pub(super) struct CompoundAssignment {
    pub template: String,
    pub arguments: Vec<TypedArgument>,
}

struct Operands<'e> {
    value: &'e TypedArgument,
    place_first: bool,
}

impl ExpressionEmitter<'_> {
    const COMMUTING_OPERATORS: [&'static str; 2] = ["+", "*"];
    const ELEMENT_READ: &'static str = "&{0}[{1}]";
    const READ_PLACE: &'static str = "{!current}";
    const COMPUTED_VALUE: &'static str = "{!value}";

    pub(super) fn compound_assignment(
        &mut self,
        template: &RustTemplate,
        arguments: &[TypedArgument],
        types: &[(String, Code)],
        span: Span,
    ) -> Option<CompoundAssignment> {
        let (value, places) = arguments.split_last()?;
        let assigned = format!(" = {{{}}}", places.len());
        let target = template.template.strip_suffix(assigned.as_str())?;
        let TypedExprKind::Call(callee, operands) = &value.value.kind else {
            return None;
        };
        let [left, right] = operands.as_slice() else {
            return None;
        };
        let operator = self.compound_operator(callee, &value.value.ty)?;
        if !places.iter().all(|place| self.is_steady(&place.value)) {
            return None;
        }
        let target_text = self.target_text(template, target, places, types, span);
        let operands = self.operands_beside(&target_text, operator, [left, right])?;
        let value = format!("{{{}}}", places.len());
        let template = if Self::leaves_places_alone(&operands.value.value) {
            format!("{target} {operator}= {value}")
        } else {
            let assigned = Self::in_written_order(&operands, &target_text, operator, &value);
            format!("{target} = {assigned}")
        };
        Some(CompoundAssignment {
            template,
            arguments: places.iter().chain([operands.value]).cloned().collect(),
        })
    }

    fn in_written_order(
        operands: &Operands,
        target_text: &str,
        operator: &str,
        value: &str,
    ) -> String {
        let place = target_text.replace('{', "{{").replace('}', "}}");
        let (current, computed) = (Self::READ_PLACE, Self::COMPUTED_VALUE);
        let statements = if operands.place_first {
            format!(
                "let {current} = {place}; let {computed} = {value}; {current} {operator} {computed}"
            )
        } else {
            format!("let {computed} = {value}; {computed} {operator} {place}")
        };
        format!("{{{{ {statements} }}}}")
    }

    fn target_text(
        &mut self,
        template: &RustTemplate,
        target: &str,
        places: &[TypedArgument],
        types: &[(String, Code)],
        span: Span,
    ) -> String {
        let target_template = RustTemplate {
            template: target.into(),
            ..template.clone()
        };
        self.template_call(&target_template, places, types.to_vec(), span)
            .text()
    }

    fn operands_beside<'e>(
        &mut self,
        target_text: &str,
        operator: &str,
        [left, right]: [&'e TypedArgument; 2],
    ) -> Option<Operands<'e>> {
        if self.names_place(left, target_text) {
            return Some(Operands {
                value: right,
                place_first: true,
            });
        }
        let commutes = Self::COMMUTING_OPERATORS.contains(&operator);
        (commutes && self.names_place(right, target_text)).then_some(Operands {
            value: left,
            place_first: false,
        })
    }

    fn names_place(&mut self, operand: &TypedArgument, target_text: &str) -> bool {
        self.argument(operand, TypeContext::Known, ArgumentRole::Ordinary)
            .text()
            == target_text
    }

    fn is_steady(&self, expr: &TypedExpr) -> bool {
        match &expr.kind {
            TypedExprKind::Leaf(TypedLeaf::Local(_) | TypedLeaf::Literal(_)) => true,
            TypedExprKind::Deref(value)
            | TypedExprKind::Borrow(ReferenceKind::Shared, value)
            | TypedExprKind::Struct(TypedStructExpr::Field(value, _)) => self.is_steady(value),
            TypedExprKind::Call(callee, arguments) => {
                self.is_pure_read(callee, &expr.ty)
                    && arguments
                        .iter()
                        .all(|argument| self.is_steady(&argument.value))
            }
            _ => false,
        }
    }

    fn is_pure_read(&self, callee: &TypedExpr, ty: &Type) -> bool {
        self.reads_element(callee) || self.compound_operator(callee, ty).is_some()
    }

    fn reads_element(&self, callee: &TypedExpr) -> bool {
        let TypedExprKind::Leaf(TypedLeaf::Global(binding)) = callee.kind else {
            return false;
        };
        self.definition_template(binding, &callee.ty)
            .is_some_and(|template| *template.template == *Self::ELEMENT_READ)
    }

    fn leaves_places_alone(expr: &TypedExpr) -> bool {
        let changes = match &expr.kind {
            TypedExprKind::Call(callee, arguments) => {
                !matches!(
                    callee.kind,
                    TypedExprKind::Leaf(TypedLeaf::Global(_) | TypedLeaf::Rust(_))
                ) || !arguments.iter().all(Self::passes_unchanged)
            }
            _ => false,
        };
        !changes && expr.children().into_iter().all(Self::leaves_places_alone)
    }

    fn passes_unchanged(argument: &TypedArgument) -> bool {
        !matches!(argument.borrow, Borrow::Mutable | Borrow::Reborrow)
            && !matches!(argument.value.ty, Type::Mut(..))
    }
}