lisette-emit 0.2.8

Little language inspired by Rust that compiles to Go
Documentation
use crate::Emitter;
use crate::expressions::context::ExpressionContext;
use syntax::ast::{BinaryOperator, Expression, Literal, UnaryOperator};
use syntax::types::Type;

struct NumericBinaryEmitInfo {
    cast_left_to: Option<Type>,
    cast_right_to: Option<Type>,
    cast_result_to: Option<Type>,
}

struct BinaryOperand<'a> {
    expression: &'a Expression,
    ty: Type,
}

impl Emitter<'_> {
    pub(crate) fn emit_binary_expression(
        &mut self,
        output: &mut String,
        operator: &BinaryOperator,
        left_expression: &Expression,
        right_expression: &Expression,
        ctx: ExpressionContext<'_>,
    ) -> String {
        if matches!(operator, BinaryOperator::Pipeline) {
            unreachable!("Pipeline operator should have been desugared by now")
        }

        let left = BinaryOperand {
            expression: left_expression,
            ty: left_expression.get_type(),
        };
        let right = BinaryOperand {
            expression: right_expression,
            ty: right_expression.get_type(),
        };

        if let Some(emit_info) = self.is_casting_needed(operator, &left, &right) {
            return self.emit_numeric_binary_with_casts(
                output,
                operator,
                left_expression,
                right_expression,
                emit_info,
                ctx,
            );
        }

        let left_ty = &left.ty;
        let right_ty = &right.ty;

        if matches!(operator, BinaryOperator::Multiplication) {
            if let Expression::Literal {
                literal: Literal::Imaginary(imag_coef),
                ..
            } = right_expression
                && left_ty.is_float()
                && !left_ty.is_complex()
            {
                let float_expression = self.emit_operand(output, left_expression, ctx);
                return format!("complex(0, {}*{})", float_expression, imag_coef);
            }
            if let Expression::Literal {
                literal: Literal::Imaginary(imag_coef),
                ..
            } = left_expression
                && right_ty.is_float()
                && !right_ty.is_complex()
            {
                let float_expression = self.emit_operand(output, right_expression, ctx);
                return format!("complex(0, {}*{})", float_expression, imag_coef);
            }
        }

        if matches!(operator, BinaryOperator::And | BinaryOperator::Or) {
            return self.emit_short_circuit_binary(
                output,
                operator,
                left_expression,
                right_expression,
                ctx,
            );
        }

        let stages = vec![
            self.stage_composite(left_expression, ctx),
            self.stage_composite(right_expression, ctx),
        ];
        let values = self.sequence(output, stages, "_left");
        let left_string = values[0].clone();
        let right_string = values[1].clone();

        format!("{} {} {}", left_string, operator, right_string)
    }

    fn emit_short_circuit_binary(
        &mut self,
        output: &mut String,
        operator: &BinaryOperator,
        left_expression: &Expression,
        right_expression: &Expression,
        ctx: ExpressionContext<'_>,
    ) -> String {
        let left_staged = self.stage_composite(left_expression, ctx);
        output.push_str(&left_staged.setup);

        // Wrap RHS setup in an IIFE so it runs only when control reaches the
        // RHS — hoisting it before the operator would defeat short-circuit.
        let right_staged = self.stage_composite(right_expression, ctx);
        let right_string = if right_staged.setup.is_empty() {
            right_staged.value
        } else {
            format!(
                "func() bool {{\n{}return {}\n}}()",
                right_staged.setup, right_staged.value
            )
        };

        format!("{} {} {}", left_staged.value, operator, right_string)
    }

    pub(crate) fn emit_unary_expression(
        &mut self,
        output: &mut String,
        operator: &UnaryOperator,
        expression: &Expression,
        ctx: ExpressionContext<'_>,
    ) -> String {
        // Special case: -9223372036854775808 cannot be written as a positive literal
        // because 9223372036854775808 overflows i64. Go handles this correctly
        // when written directly as -9223372036854775808.
        if matches!(operator, UnaryOperator::Negative)
            && let Expression::Literal {
                literal:
                    Literal::Integer {
                        value: 9223372036854775808,
                        ..
                    },
                ..
            } = expression
        {
            return "-9223372036854775808".to_string();
        }

        // Negate comparisons by flipping the operator instead of prepending `!`:
        // `!` binds tighter than `==` in Go, so `!(a == b)` must not emit as `!a == b`.
        if matches!(operator, UnaryOperator::Not) {
            let target = expression.unwrap_parens();
            let preserve_parens = matches!(expression, Expression::Paren { .. });
            let wrap = |s: String| {
                if preserve_parens {
                    format!("({})", s)
                } else {
                    s
                }
            };
            if let Expression::Binary {
                operator: cmp,
                left,
                right,
                ..
            } = target
                && let Some(flipped) = flip_comparison(cmp)
            {
                return wrap(self.emit_binary_expression(output, &flipped, left, right, ctx));
            }
            if matches!(target, Expression::Call { .. })
                && let Some(negated) = self.try_emit_negated_call(output, target)
            {
                return wrap(negated);
            }
        }

        let expression = self.emit_operand(output, expression, ctx);

        let op_str = match operator {
            UnaryOperator::Negative => "-",
            UnaryOperator::Not => "!",
            UnaryOperator::BitwiseNot => "^",
            UnaryOperator::Deref => "*",
        };
        format!("{}{}", op_str, expression)
    }

    /// Determines if casting is needed for a binary operation.
    ///
    /// Go requires explicit casts when mixing aliased numeric types with
    /// their underlying types. This function analyzes the operand types
    /// and determines what casts are needed.
    fn is_casting_needed(
        &self,
        operator: &BinaryOperator,
        left: &BinaryOperand<'_>,
        right: &BinaryOperand<'_>,
    ) -> Option<NumericBinaryEmitInfo> {
        if !is_numeric_binary_op(operator) {
            return None;
        }

        let left_underlying_ty = matching_underlying_numeric(&left.ty, &right.ty)?;

        let left_is_aliased = left.ty.is_aliased_numeric_type();
        let right_is_aliased = right.ty.is_aliased_numeric_type();

        if left.ty == right.ty {
            if left_is_aliased && matches!(operator, BinaryOperator::Division) {
                return Some(NumericBinaryEmitInfo {
                    cast_left_to: None,
                    cast_right_to: None,
                    cast_result_to: Some(left_underlying_ty),
                });
            }
            return None;
        }

        let left_is_literal = is_literal_expression(left.expression);
        let right_is_literal = is_literal_expression(right.expression);

        match (left_is_aliased, right_is_aliased) {
            (true, false) => Some(NumericBinaryEmitInfo {
                cast_left_to: None,
                cast_right_to: cast_unless_literal(right_is_literal, &left.ty),
                cast_result_to: None,
            }),
            (false, true) => Some(NumericBinaryEmitInfo {
                cast_left_to: cast_unless_literal(left_is_literal, &right.ty),
                cast_right_to: None,
                cast_result_to: None,
            }),
            _ => None,
        }
    }

    fn emit_numeric_binary_with_casts(
        &mut self,
        output: &mut String,
        operator: &BinaryOperator,
        left_expression: &Expression,
        right_expression: &Expression,
        info: NumericBinaryEmitInfo,
        ctx: ExpressionContext<'_>,
    ) -> String {
        let stages = vec![
            self.stage_operand(left_expression, ctx),
            self.stage_operand(right_expression, ctx),
        ];
        let values = self.sequence(output, stages, "_left");
        let left_string = values[0].clone();
        let right_string = values[1].clone();

        let left_string = match &info.cast_left_to {
            Some(ty) => format!("{}({})", self.go_type_as_string(ty), left_string),
            None => left_string,
        };

        let right_string = match &info.cast_right_to {
            Some(ty) => format!("{}({})", self.go_type_as_string(ty), right_string),
            None => right_string,
        };

        let result = format!("{} {} {}", left_string, operator, right_string);

        match &info.cast_result_to {
            Some(ty) => format!("{}({})", self.go_type_as_string(ty), result),
            None => result,
        }
    }
}

fn flip_comparison(operator: &BinaryOperator) -> Option<BinaryOperator> {
    match operator {
        BinaryOperator::Equal => Some(BinaryOperator::NotEqual),
        BinaryOperator::NotEqual => Some(BinaryOperator::Equal),
        BinaryOperator::LessThan => Some(BinaryOperator::GreaterThanOrEqual),
        BinaryOperator::LessThanOrEqual => Some(BinaryOperator::GreaterThan),
        BinaryOperator::GreaterThan => Some(BinaryOperator::LessThanOrEqual),
        BinaryOperator::GreaterThanOrEqual => Some(BinaryOperator::LessThan),
        _ => None,
    }
}

fn is_literal_expression(expression: &Expression) -> bool {
    match expression {
        Expression::Literal { .. } => true,
        Expression::Paren { expression, .. } => is_literal_expression(expression),
        Expression::Unary {
            operator: UnaryOperator::Negative,
            expression,
            ..
        } => is_literal_expression(expression),
        _ => false,
    }
}

fn is_numeric_binary_op(operator: &BinaryOperator) -> bool {
    use BinaryOperator::*;
    matches!(
        operator,
        Addition
            | Subtraction
            | Multiplication
            | Division
            | Remainder
            | BitwiseAnd
            | BitwiseOr
            | BitwiseXor
            | BitwiseAndNot
            | ShiftLeft
            | ShiftRight
            | LessThan
            | LessThanOrEqual
            | GreaterThan
            | GreaterThanOrEqual
            | Equal
            | NotEqual
    )
}

/// Common underlying numeric type when both operands lower to the same
/// numeric family; `None` if either operand is non-numeric or the two
/// numeric families differ.
fn matching_underlying_numeric(left: &Type, right: &Type) -> Option<Type> {
    let left_underlying = left.underlying_numeric_type()?;
    let right_underlying = right.underlying_numeric_type()?;
    if left_underlying.numeric_family()? != right_underlying.numeric_family()? {
        return None;
    }
    Some(left_underlying)
}

fn cast_unless_literal(is_literal: bool, target: &Type) -> Option<Type> {
    if is_literal {
        None
    } else {
        Some(target.clone())
    }
}