knave 0.14.11

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use super::local_borrows::LocalBorrows;
use crate::core::{Division, Literal, Operand, ParsedTemplate, PrimitiveType, TemplateOperation};
use crate::reader::NumberValue;
use crate::types::{
    Type, TypedArgument, TypedControl, TypedExpr, TypedExprKind, TypedLeaf, TypedProgram,
};

#[derive(Clone, Copy)]
pub(super) struct PromotedConstants<'p> {
    program: &'p TypedProgram,
}

impl<'p> PromotedConstants<'p> {
    const ZERO: i128 = 0;
    const MINUS_ONE: i128 = -1;

    pub fn new(program: &'p TypedProgram) -> Self {
        Self { program }
    }

    pub fn is_temporary(&self, borrowed: &TypedExpr) -> bool {
        matches!(
            borrowed.kind,
            TypedExprKind::Call(..) | TypedExprKind::Control(_)
        ) && !self.promotes(borrowed)
    }

    fn promotes(&self, value: &TypedExpr) -> bool {
        match &value.kind {
            TypedExprKind::Call(callee, arguments) => self.promotes_operation(callee, arguments),
            TypedExprKind::Leaf(TypedLeaf::Rust(template)) => {
                ParsedTemplate::parse(&template.template)
                    .is_ok_and(|parsed| self.promotes_template(&parsed, &[]))
            }
            TypedExprKind::Control(TypedControl::Block(block)) => {
                LocalBorrows::tail_of(block).is_none_or(|tail| self.promotes(tail))
            }
            TypedExprKind::Leaf(TypedLeaf::Local(_)) | TypedExprKind::Control(_) => false,
            _ => value
                .children()
                .into_iter()
                .all(|child| self.promotes(child)),
        }
    }

    fn promotes_operation(&self, callee: &TypedExpr, arguments: &[TypedArgument]) -> bool {
        arguments
            .iter()
            .all(|argument| self.promotes(&argument.value))
            && self
                .program
                .template_borrows
                .read_template(callee, false, |template| {
                    self.promotes_template(template, arguments)
                })
    }

    fn promotes_template(&self, template: &ParsedTemplate, arguments: &[TypedArgument]) -> bool {
        let operation = template.operation();
        !operation.calls()
            && self.operates_on_builtins(&operation, arguments)
            && self.divides_by_constants(&operation, arguments)
    }

    fn operates_on_builtins(
        &self,
        operation: &TemplateOperation,
        arguments: &[TypedArgument],
    ) -> bool {
        operation.operator_arguments().all(|position| {
            arguments
                .get(position)
                .is_some_and(|argument| self.is_builtin_operand(&argument.value.ty))
        })
    }

    fn is_builtin_operand(&self, ty: &Type) -> bool {
        self.primitive(ty).is_some_and(|primitive| {
            primitive.is_integer()
                || primitive.is_float()
                || matches!(primitive, PrimitiveType::Bool | PrimitiveType::Char)
        })
    }

    fn primitive(&self, ty: &Type) -> Option<PrimitiveType> {
        match ty {
            Type::Primitive(primitive) => Some(*primitive),
            Type::Generic(generic) => self
                .program
                .literal_generics
                .get(generic)
                .map(|literal| literal.default_type()),
            _ => None,
        }
    }

    fn divides_by_constants(
        &self,
        operation: &TemplateOperation,
        arguments: &[TypedArgument],
    ) -> bool {
        operation
            .divisions()
            .iter()
            .all(|division| self.promotes_division(division, arguments))
    }

    fn promotes_division(&self, division: &Division, arguments: &[TypedArgument]) -> bool {
        match division.divisor {
            Operand::Argument(position) => arguments.get(position).is_some_and(|divisor| {
                self.promotes_argument_division(division.dividend, &divisor.value, arguments)
            }),
            Operand::Constant(value) => {
                self.promotes_constant_division(division.dividend, value, arguments)
            }
        }
    }

    fn promotes_argument_division(
        &self,
        dividend: Option<Operand>,
        divisor: &TypedExpr,
        arguments: &[TypedArgument],
    ) -> bool {
        !self.is_integer(&divisor.ty)
            || self.promotes_integer_division(dividend, divisor, arguments)
    }

    fn promotes_constant_division(
        &self,
        dividend: Option<Operand>,
        divisor: Option<i128>,
        arguments: &[TypedArgument],
    ) -> bool {
        match divisor {
            Some(Self::ZERO) => false,
            Some(Self::MINUS_ONE) => self.is_constant_dividend(dividend, arguments),
            _ => true,
        }
    }

    fn is_integer(&self, ty: &Type) -> bool {
        self.primitive(ty)
            .is_some_and(|primitive| primitive.is_integer())
    }

    fn promotes_integer_division(
        &self,
        dividend: Option<Operand>,
        divisor: &TypedExpr,
        arguments: &[TypedArgument],
    ) -> bool {
        let cannot_divide_by_zero =
            self.is_constant_operand(divisor) && !self.has_value(divisor, Self::ZERO);
        let cannot_overflow = !self.has_value(divisor, Self::MINUS_ONE)
            || self.is_constant_dividend(dividend, arguments);
        cannot_divide_by_zero && cannot_overflow
    }

    fn is_constant_dividend(&self, dividend: Option<Operand>, arguments: &[TypedArgument]) -> bool {
        match dividend {
            Some(Operand::Argument(position)) => arguments
                .get(position)
                .is_some_and(|argument| self.is_constant_operand(&argument.value)),
            Some(Operand::Constant(_)) | None => true,
        }
    }

    fn is_constant_operand(&self, operand: &TypedExpr) -> bool {
        match &operand.kind {
            TypedExprKind::Leaf(TypedLeaf::Literal(_) | TypedLeaf::Global(_)) => true,
            _ => self.negated(operand).is_some_and(Self::is_literal),
        }
    }

    fn has_value(&self, operand: &TypedExpr, value: i128) -> bool {
        Self::is_literal_of(operand, value)
            || self
                .negated(operand)
                .is_some_and(|negated| Self::is_literal_of(negated, -value))
    }

    fn negated<'e>(&self, operand: &'e TypedExpr) -> Option<&'e TypedExpr> {
        let TypedExprKind::Call(callee, arguments) = &operand.kind else {
            return None;
        };
        let negates = self
            .program
            .template_borrows
            .read_template(callee, false, |template| template.operation().is_negation());
        arguments
            .first()
            .filter(|_| negates)
            .map(|argument| &argument.value)
    }

    fn is_literal(operand: &TypedExpr) -> bool {
        matches!(operand.kind, TypedExprKind::Leaf(TypedLeaf::Literal(_)))
    }

    fn is_literal_of(operand: &TypedExpr, value: i128) -> bool {
        matches!(&operand.kind, TypedExprKind::Leaf(TypedLeaf::Literal(Literal::Number(number)))
            if *number.value() == NumberValue::Integer(value))
    }
}