fusible 0.12.0

Electronic linter: a fuse that blows to block firmware code generation
//! Rust source rendering for generated pin bindings.

use std::collections::BTreeMap;

use crate::silicon::model::{parse_port_pin, SiliconTarget};
use crate::silicon::PeripheralSignal;

use super::groups::{categorize_pins, field_names};
use super::pins::{McuInfo, PinMapping};

/// Renders text for a `//` or `///` comment: control characters (newlines,
/// carriage returns, tabs) are escaped so the text stays on one line.
fn comment_text(s: &str) -> String {
    s.chars()
        .map(|c| {
            if c.is_control() {
                c.escape_default().to_string()
            } else {
                c.to_string()
            }
        })
        .collect()
}

/// Smallest unsigned integer type holding every pin number.
fn pin_type(pins: &[PinMapping]) -> &'static str {
    match pins.iter().map(|p| p.pin_number).max().unwrap_or(0) {
        0..=0xFF => "u8",
        0x100..=0xFFFF => "u16",
        _ => "u32",
    }
}

/// Generates typed Rust source code (`pins.rs`) from resolved MCU info.
pub fn generate_pins_rs(mcu: &McuInfo, board_name: &str) -> String {
    let mut buf = String::new();
    let ty = pin_type(&mcu.pins);
    render_header(&mut buf, mcu, board_name);
    render_gpio(&mut buf, mcu, ty);
    let groups = categorize_pins(&mcu.pins);
    for (group_name, pins) in &groups {
        render_group(&mut buf, group_name, pins, ty);
    }
    render_board(&mut buf, &groups);
    buf
}

fn render_header(buf: &mut String, mcu: &McuInfo, board_name: &str) {
    buf.push_str("//! Generated by Fusible — Electronic Linter & Hardware Bridge.\n");
    buf.push_str("//! DO NOT EDIT MANUALLY. Changes will be overwritten on next build.\n");
    buf.push_str("//!\n");
    buf.push_str(&format!("//! Board: {}\n", comment_text(board_name)));
    buf.push_str(&format!(
        "//! Microcontroller: {} ({})\n",
        comment_text(&mcu.chip),
        comment_text(&mcu.name)
    ));
    buf.push_str("\n#![allow(dead_code)]\n\n");
}

fn render_gpio(buf: &mut String, mcu: &McuInfo, ty: &str) {
    buf.push_str("/// Hardware GPIO pin constants extracted from the circuit netlist.\n");
    buf.push_str("pub mod gpio {\n");
    let target = crate::silicon::find_target(&mcu.chip);
    for pin in &mcu.pins {
        let af_doc = target.and_then(|t| af_doc(t, pin)).unwrap_or_default();
        let port_doc = if parse_port_pin(&pin.pin_label).is_some() {
            format!(" = `{}` (port * 16 + pin)", comment_text(&pin.pin_label))
        } else {
            String::new()
        };

        buf.push_str(&format!(
            "    /// Pin {}{} (hardware net `{}`){}.\n",
            pin.pin_number,
            port_doc,
            comment_text(&pin.net_name),
            af_doc
        ));
        buf.push_str(&format!(
            "    pub const {}: {ty} = {};\n",
            pin.signal_name, pin.pin_number
        ));
    }
    buf.push_str("}\n\n");
}

/// ` [AF_NAME: AFn]` when the pin's signal maps to an alternate function.
fn af_doc(target: &SiliconTarget, pin: &PinMapping) -> Option<String> {
    let spec = target.find_pin(&pin.pin_label)?;
    let sig = PeripheralSignal::parse(&pin.signal_name)
        .or_else(|| PeripheralSignal::parse(&pin.net_name))?;
    let af = spec.get_af(&sig)?;
    Some(format!(" [{}: AF{}]", af.af_name, af.af_number))
}

fn render_group(buf: &mut String, group_name: &str, pins: &[&PinMapping], ty: &str) {
    let struct_name = format!("{group_name}Pins");
    let const_name = format!("{}_PINS", group_name.to_ascii_uppercase());
    let fields = field_names(pins, group_name);

    buf.push_str(&format!("/// Peripheral pin mapping for `{group_name}`.\n"));
    buf.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n");
    buf.push_str(&format!("pub struct {struct_name} {{\n"));
    for field_name in &fields {
        buf.push_str(&format!("    pub {field_name}: {ty},\n"));
    }
    buf.push_str("}\n\n");

    buf.push_str(&format!(
        "/// Default `{group_name}` hardware configuration.\n"
    ));
    buf.push_str(&format!(
        "pub const {const_name}: {struct_name} = {struct_name} {{\n"
    ));
    for (pin, field_name) in pins.iter().zip(&fields) {
        buf.push_str(&format!("    {field_name}: gpio::{},\n", pin.signal_name));
    }
    buf.push_str("};\n\n");
}

fn render_board(buf: &mut String, groups: &BTreeMap<String, Vec<&PinMapping>>) {
    buf.push_str("/// Complete board pin assignment container.\n");
    buf.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n");
    buf.push_str("pub struct BoardPins {\n");
    for group_name in groups.keys() {
        let field_name = group_name.to_ascii_lowercase();
        let struct_name = format!("{group_name}Pins");
        buf.push_str(&format!("    pub {field_name}: {struct_name},\n"));
    }
    buf.push_str("}\n\n");

    buf.push_str("/// Verified hardware board pin assignments.\n");
    buf.push_str("pub const BOARD_PINS: BoardPins = BoardPins {\n");
    for group_name in groups.keys() {
        let field_name = group_name.to_ascii_lowercase();
        let const_name = format!("{}_PINS", group_name.to_ascii_uppercase());
        buf.push_str(&format!("    {field_name}: {const_name},\n"));
    }
    buf.push_str("};\n");
}