vox-lang 0.4.0

A systems level compiler for Vox (sentence based code)
use super::*;

impl CodeGenerator {
    /// Resolve a `{name}` format part: emit code leaving the runtime value
    /// (or pointer) in rax, and classify what was found. This is THE single
    /// name-resolution path shared by every format-string sink - Print, the
    /// buffer set/copy/append writers, and the expression materializer that
    /// write payloads, paths, and text initializers go through. Special
    /// names, variable/global lookup, and the constant fallback must never
    /// be re-implemented per sink: that duplication is exactly how the
    /// buffer sinks shipped without `{current time's hour}` support while
    /// Print had it.
    pub(crate) fn resolve_format_variable(&mut self, name: &str) -> FormatPartValue {
        match name {
            "current time's hour" => {
                self.emit_indent("TIME_GET");
                self.emit_indent("TIME_GET_HOUR rax");
                self.uses_time = true;
                FormatPartValue::Loaded(Some(VarType::Integer))
            }
            "current time's minute" => {
                self.emit_indent("TIME_GET");
                self.emit_indent("TIME_GET_MINUTE rax");
                self.uses_time = true;
                FormatPartValue::Loaded(Some(VarType::Integer))
            }
            "current time's second" => {
                self.emit_indent("TIME_GET");
                self.emit_indent("TIME_GET_SECOND rax");
                self.uses_time = true;
                FormatPartValue::Loaded(Some(VarType::Integer))
            }
            "arguments's count" | "argument's count" => {
                self.generate_expr(&Expr::ArgumentCount);
                FormatPartValue::Loaded(Some(VarType::Integer))
            }
            "arguments's name" | "argument's name" => {
                self.generate_expr(&Expr::ArgumentName);
                FormatPartValue::Loaded(Some(VarType::String))
            }
            "arguments's first" | "argument's first" => {
                self.generate_expr(&Expr::ArgumentFirst);
                FormatPartValue::Loaded(Some(VarType::String))
            }
            "arguments's last" | "argument's last" => {
                self.generate_expr(&Expr::ArgumentLast);
                FormatPartValue::Loaded(Some(VarType::String))
            }
            _ => {
                if let Some(offset) = self.get_var(name) {
                    self.emit_indent(&format!("mov rax, [rbp-{}]", offset));
                    FormatPartValue::Loaded(self.variable_types.get(name).cloned())
                } else if let Some(label) = self.global_var_label(name).cloned() {
                    self.emit_indent(&format!("mov rax, [rel {}]", label));
                    FormatPartValue::Loaded(self.variable_types.get(name).cloned())
                } else if let Some(expr) = self.global_constants.get(name).cloned() {
                    match expr {
                        Expr::StringLit(s) => FormatPartValue::Literal(s),
                        Expr::IntegerLit(n) => {
                            self.emit_indent(&format!("mov rax, {}", n));
                            FormatPartValue::Loaded(Some(VarType::Integer))
                        }
                        Expr::BoolLit(b) => {
                            self.emit_indent(&format!("mov rax, {}", if b { 1 } else { 0 }));
                            FormatPartValue::Loaded(Some(VarType::Integer))
                        }
                        _ => FormatPartValue::Unknown,
                    }
                } else {
                    FormatPartValue::Unknown
                }
            }
        }
    }

    pub(crate) fn emit_format_parts_into_buffer_slot(&mut self, offset: i64, parts: &[FormatPart], clear_first: bool) {
        if clear_first {
            self.emit_clear_buffer_slot(offset);
        }

        for part in parts {
            match part {
                FormatPart::Literal(s) => self.emit_append_literal_to_buffer_slot(offset, s),
                FormatPart::Variable { name, format } => {
                    match self.resolve_format_variable(name) {
                        FormatPartValue::Loaded(value_type) => {
                            let fmt_spec = self.parse_format_spec(format.as_deref());
                            self.emit_append_runtime_value_to_buffer_slot(offset, value_type, fmt_spec);
                        }
                        FormatPartValue::Literal(s) => {
                            self.emit_append_literal_to_buffer_slot(offset, &s);
                        }
                        FormatPartValue::Unknown => {
                            // Same placeholder Print renders for unknown names
                            let placeholder = format!("{{{}}}", name);
                            self.emit_append_literal_to_buffer_slot(offset, &placeholder);
                        }
                    }
                }
                FormatPart::Expression { expr, format } => {
                    self.generate_expr(expr);
                    let expr_type = self.infer_expr_type(expr);
                    let fmt_spec = self.parse_format_spec(format.as_deref());
                    self.emit_append_runtime_value_to_buffer_slot(offset, expr_type, fmt_spec);
                }
            }
        }
    }

    pub(crate) fn emit_format_parts_into_buffer(
        &mut self,
        dst_local: Option<i64>,
        dst_global: Option<&str>,
        parts: &[FormatPart],
    ) {
        let load_dst = |this: &mut Self| {
            if let Some(offset) = dst_local {
                this.emit_indent(&format!("mov rdi, [rbp-{}]", offset));
            } else if let Some(label) = dst_global {
                this.emit_indent(&format!("mov rdi, [rel {}]", label));
            }
        };

        for part in parts {
            load_dst(self);
            self.emit_indent("push rdi  ; save destination buffer pointer");
            match part {
                FormatPart::Literal(s) => {
                    let label = self.add_string(s);
                    self.emit_indent(&format!("lea rsi, [rel {}]", label));
                    self.emit_indent(&format!("mov rdx, {}_len", label));
                    self.emit_indent("call _buffer_append_bytes");
                }
                FormatPart::Variable { name, format } => {
                    match self.resolve_format_variable(name) {
                        FormatPartValue::Loaded(value_type) => {
                            let fmt_spec = self.parse_format_spec(format.as_deref());
                            self.emit_append_runtime_value_to_buffer_ptr(value_type, fmt_spec);
                        }
                        FormatPartValue::Literal(s) => {
                            let label = self.add_string(&s);
                            self.emit_indent(&format!("lea rsi, [rel {}]", label));
                            self.emit_indent(&format!("mov rdx, {}_len", label));
                            self.emit_indent("call _buffer_append_bytes");
                        }
                        FormatPartValue::Unknown => {
                            let placeholder = format!("{{{}}}", name);
                            let label = self.add_string(&placeholder);
                            self.emit_indent(&format!("lea rsi, [rel {}]", label));
                            self.emit_indent(&format!("mov rdx, {}_len", label));
                            self.emit_indent("call _buffer_append_bytes");
                        }
                    }
                }
                FormatPart::Expression { expr, format } => {
                    self.generate_expr(expr);
                    let expr_type = self.infer_expr_type(expr);
                    let fmt_spec = self.parse_format_spec(format.as_deref());
                    self.emit_append_runtime_value_to_buffer_ptr(expr_type, fmt_spec);
                }
            }
            if let Some(offset) = dst_local {
                self.emit_indent(&format!("mov [rbp-{}], rax", offset));
            } else if let Some(label) = dst_global {
                self.emit_indent(&format!("mov [rel {}], rax", label));
            }
            self.emit_indent("pop rsi  ; discard saved pointer copy");
        }
    }

    pub(crate) fn parse_format_spec(&self, fmt: Option<&str>) -> FormatSpec {
        match fmt {
            None => FormatSpec {
                width: None,
                zero_pad: false,
                base: IntegerBase::Decimal,
                precision: None,
            },
            Some(fmt_str) => {
                let mut spec = FormatSpec {
                    width: None,
                    zero_pad: false,
                    base: IntegerBase::Decimal,
                    precision: None,
                };
                
                // Check for precision format first (starts with '.')
                if fmt_str.starts_with('.') {
                    // Float precision format like .2, .4, etc.
                    if let Some(precision) = fmt_str.strip_prefix('.').and_then(|s| s.parse::<i32>().ok()) {
                        spec.precision = Some(precision);
                    }
                    return spec;
                }
                
                // Parse width and zero padding
                let mut remaining = fmt_str;
                let mut has_width = false;
                
                // Check if it starts with digit or '0' for width/padding
                if remaining.chars().next().map(|c| c.is_ascii_digit() || c == '0').unwrap_or(false) {
                    let zero_pad = remaining.starts_with('0');
                    let width_str = if zero_pad {
                        remaining.trim_start_matches('0')
                    } else {
                        remaining
                    };
                    
                    // Extract digits for width
                    let width_end = width_str.chars().take_while(|c| c.is_ascii_digit()).count();
                    if width_end > 0 {
                        let width_digits = &width_str[..width_end];
                        if let Ok(width) = width_digits.parse::<i32>() {
                            spec.width = Some(width);
                            spec.zero_pad = zero_pad;
                            has_width = true;
                            remaining = &fmt_str[if zero_pad { 1 + width_end } else { width_end }..];
                        }
                    }
                }
                
                // Parse base specifier from remaining characters
                if !remaining.is_empty() {
                    match remaining {
                        "x" => spec.base = IntegerBase::HexLower,
                        "X" => spec.base = IntegerBase::HexUpper,
                        "b" => spec.base = IntegerBase::Binary,
                        "o" => spec.base = IntegerBase::Octal,
                        _ => {
                            // If we parsed a width but no base, treat as decimal
                            if has_width {
                                spec.base = IntegerBase::Decimal;
                            }
                        }
                    }
                }
                
                spec
            }
        }
    }

    pub(crate) fn emit_formatted_value(&mut self, value_type: Option<VarType>, fmt: FormatSpec) {
        // Handle precision format for floats
        if let Some(precision) = fmt.precision {
            self.emit_indent("movq xmm0, rdi");
            self.emit_indent(&format!("mov rdi, {}", precision));
            self.emit_indent("call _print_float_precision");
            self.uses_floats = true;
            self.uses_format = true;
            return;
        }
        
        // If no specific format (default case), handle by type
        if fmt.width.is_none() && matches!(fmt.base, IntegerBase::Decimal) {
            match value_type {
                Some(VarType::Float) => {
                    self.emit_indent("movq xmm0, rdi");
                    self.emit_indent("PRINT_FLOAT");
                    self.uses_floats = true;
                }
                Some(VarType::Buffer) => {
                    // rdi must be the struct pointer (not data area) here.
                    // The fixed call sites guarantee this; it's documented on
                    // each one. Kept separate from VarType::String to make the
                    // contract explicit and catch any future callers that get
                    // it wrong (PRINT_BUF on a data pointer would print garbage).
                    self.emit_indent("PRINT_BUF rdi");
                }
                Some(VarType::String) => {
                    self.emit_indent("PRINT_CSTR rdi");
                }
                _ => {
                    self.emit_indent("PRINT_INT rdi");
                }
            }
            return;
        }
        
        // Handle integer formatting with width and base
        match fmt.base {
            IntegerBase::Decimal => {
                match (fmt.width, fmt.zero_pad) {
                    (Some(width), true) => {
                        self.emit_indent(&format!("PRINT_INT_ZEROPAD rdi, {}", width));
                    }
                    (Some(width), false) => {
                        self.emit_indent(&format!("PRINT_INT_PADDED rdi, {}", width));
                    }
                    _ => {
                        self.emit_indent("PRINT_INT rdi");
                    }
                }
                self.uses_format = true;
            }
            IntegerBase::HexLower => {
                if fmt.width.is_some() {
                    match (fmt.width, fmt.zero_pad) {
                        (Some(width), true) => {
                            self.emit_indent(&format!("PRINT_HEX_LOWER_ZEROPAD rdi, {}", width));
                        }
                        (Some(width), false) => {
                            self.emit_indent(&format!("PRINT_HEX_LOWER_PADDED rdi, {}", width));
                        }
                        _ => {
                            self.emit_indent("PRINT_HEX_LOWER rdi");
                        }
                    }
                } else {
                    self.emit_indent("PRINT_HEX_LOWER rdi");
                }
                self.uses_format = true;
            }
            IntegerBase::HexUpper => {
                if fmt.width.is_some() {
                    match (fmt.width, fmt.zero_pad) {
                        (Some(width), true) => {
                            self.emit_indent(&format!("PRINT_HEX_UPPER_ZEROPAD rdi, {}", width));
                        }
                        (Some(width), false) => {
                            self.emit_indent(&format!("PRINT_HEX_UPPER_PADDED rdi, {}", width));
                        }
                        _ => {
                            self.emit_indent("PRINT_HEX_UPPER rdi");
                        }
                    }
                } else {
                    self.emit_indent("PRINT_HEX_UPPER rdi");
                }
                self.uses_format = true;
            }
            IntegerBase::Binary => {
                if fmt.width.is_some() {
                    match (fmt.width, fmt.zero_pad) {
                        (Some(width), true) => {
                            self.emit_indent(&format!("PRINT_BINARY_ZEROPAD rdi, {}", width));
                        }
                        (Some(width), false) => {
                            self.emit_indent(&format!("PRINT_BINARY_PADDED rdi, {}", width));
                        }
                        _ => {
                            self.emit_indent("PRINT_BINARY rdi");
                        }
                    }
                } else {
                    self.emit_indent("PRINT_BINARY rdi");
                }
                self.uses_format = true;
            }
            IntegerBase::Octal => {
                if fmt.width.is_some() {
                    match (fmt.width, fmt.zero_pad) {
                        (Some(width), true) => {
                            self.emit_indent(&format!("PRINT_OCTAL_ZEROPAD rdi, {}", width));
                        }
                        (Some(width), false) => {
                            self.emit_indent(&format!("PRINT_OCTAL_PADDED rdi, {}", width));
                        }
                        _ => {
                            self.emit_indent("PRINT_OCTAL rdi");
                        }
                    }
                } else {
                    self.emit_indent("PRINT_OCTAL rdi");
                }
                self.uses_format = true;
            }
        }
    }

}