lisette-emit 0.2.8

Little language inspired by Rust that compiles to Go
Documentation
use syntax::ast::Expression;
use syntax::types::peel_to_range_type;

use crate::Emitter;
use crate::expressions::context::ExpressionContext;
use crate::expressions::emission::EmittedExpression;
use crate::types::native::NativeGoType;

impl Emitter<'_> {
    pub(crate) fn emit_index_access(
        &mut self,
        output: &mut String,
        expression: &Expression,
        index: &Expression,
    ) -> String {
        if let Expression::Range {
            start,
            end,
            inclusive,
            ..
        } = index
        {
            return self.emit_range_slice(
                output,
                expression,
                start.as_deref(),
                end.as_deref(),
                *inclusive,
            );
        }

        let base_staged = self.stage_base_with_deref(expression);

        // Range-typed variable as index (e.g. `items[r]` where `r: Range<int>`,
        // or `r: Prefix` where `type Prefix = RangeTo<int>`).
        let index_ty = index.get_type();
        if let Some(range_kind) = peel_to_range_type(&index_ty).and_then(|t| t.get_name()) {
            let needs_cap = self.is_native_shape(&expression.get_type(), NativeGoType::Slice);
            let index_staged = self.stage_or_capture(index, "range");
            let values = self.sequence(output, vec![base_staged, index_staged], "_base");
            return self.emit_range_var_slice(&values[0], &values[1], range_kind, needs_cap);
        }

        let index_staged = self.stage_composite(index, ExpressionContext::value());
        let values = self.sequence(output, vec![base_staged, index_staged], "_base");
        format!("{}[{}]", values[0], values[1])
    }

    fn stage_base_with_deref(&mut self, expression: &Expression) -> EmittedExpression {
        let Some(inner) = expression.deref_inner() else {
            return self.stage_operand(expression, ExpressionContext::value());
        };
        let s = self.stage_operand(inner, ExpressionContext::value());
        EmittedExpression {
            value: format!("(*{})", s.value),
            setup: s.setup,
            capture: s.capture,
        }
    }

    /// Emit `base[start:end]` (or the three-index form for slices to prevent
    /// append-through-alias corruption). Strings use two-index slicing because
    /// immutability makes the backing array safe to share.
    fn emit_range_slice(
        &mut self,
        output: &mut String,
        expression: &Expression,
        start: Option<&Expression>,
        end: Option<&Expression>,
        inclusive: bool,
    ) -> String {
        let needs_cap = self.is_native_shape(&expression.get_type(), NativeGoType::Slice);
        let base_staged = self.stage_base_with_deref(expression);

        let mut all_stages = vec![base_staged];
        if let Some(s) = start {
            all_stages.push(self.stage_operand(s, ExpressionContext::value()));
        }
        if let Some(e) = end {
            all_stages.push(self.stage_operand(e, ExpressionContext::value()));
        }
        let values = self.sequence(output, all_stages, "_base");
        let base_str = &values[0];

        let (start_str, end_expression) = if start.is_some() {
            (values[1].as_str(), values.get(2).map(|s| s.as_str()))
        } else {
            ("", values.get(1).map(|s| s.as_str()))
        };

        let end_str = match (end_expression, inclusive) {
            (None, _) => String::new(),
            (Some(e), false) => e.to_string(),
            (Some(e), true) => format!("{}+1", e),
        };

        if !needs_cap {
            return format!("{}[{}:{}]", base_str, start_str, end_str);
        }

        if end_str.is_empty() {
            let len_var = self.hoist_tmp_value(output, "len", &format!("len({})", base_str));
            return format!("{}[{}:{}:{}]", base_str, start_str, len_var, len_var);
        }

        if end_str.contains('(') {
            let end_var = self.hoist_tmp_value(output, "end", &end_str);
            return format!("{}[{}:{}:{}]", base_str, start_str, end_var, end_var);
        }

        format!("{}[{}:{}:{}]", base_str, start_str, end_str, end_str)
    }

    /// Emit a Go slice expression from a range-typed variable index.
    ///
    /// When `needs_cap` is true, appends a third index to cap capacity at
    /// length, preventing append-through-alias corruption on shared backing
    /// arrays. Range field accesses (e.g. `.End`) are pure, so repeating
    /// them in the cap position is safe.
    fn emit_range_var_slice(
        &self,
        base: &str,
        range: &str,
        range_kind: &str,
        needs_cap: bool,
    ) -> String {
        let (start, end) = range_var_bounds(range, range_kind);
        let start_str = start.as_deref().unwrap_or("");
        let end_str = end.as_deref().unwrap_or("");

        if !needs_cap {
            return format!("{}[{}:{}]", base, start_str, end_str);
        }

        // For open-ended ranges, cap at len(base).
        let cap = if end_str.is_empty() {
            format!("len({})", base)
        } else {
            end_str.to_string()
        };

        format!("{}[{}:{}:{}]", base, start_str, end_str, cap)
    }
}

pub(crate) fn range_var_bounds(
    range_var: &str,
    range_kind: &str,
) -> (Option<String>, Option<String>) {
    match range_kind {
        "Range" => (
            Some(format!("{}.Start", range_var)),
            Some(format!("{}.End", range_var)),
        ),
        "RangeInclusive" => (
            Some(format!("{}.Start", range_var)),
            Some(format!("{}.End+1", range_var)),
        ),
        "RangeFrom" => (Some(format!("{}.Start", range_var)), None),
        "RangeTo" => (None, Some(format!("{}.End", range_var))),
        "RangeToInclusive" => (None, Some(format!("{}.End+1", range_var))),
        _ => unreachable!("unexpected range kind: {}", range_kind),
    }
}