///|
// Always quote CSV cells. An apostrophe prevents user-controlled names from
// becoming spreadsheet formulas, including after leading whitespace.
fn csv_cell(text : String, user_value? : Bool = false) -> String {
let mut escaped = ""
for unit in text.iter() {
escaped = escaped + (if unit == '"' { "\"\"" } else { unit.to_string() })
}
if user_value {
// Prefix every nonempty user value: no spreadsheet-specific whitespace
// interpretation can expose a formula. The prefix is part of the export.
if text.length() > 0 {
escaped = "'" + escaped
}
}
"\"" + escaped + "\""
}
///|
/// Verified signal endpoints, not an electrical safety certificate.
/// User ID/bus cells carry a literal apostrophe prefix for spreadsheet safety.
pub fn export_wiring_csv(
assignments : Array[Assignment],
reserved_pins? : Array[Int] = [],
repair_optimal? : Bool? = None,
) -> String? {
if !check_assignments(assignments, reserved_pins~).is_empty() {
return None
}
let lines : Array[String] = [
"device_index,device,signal,gpio,header_pin,resource,bus,i2c_address,reserved_pins,repair_optimal",
]
let reservations = reserved_pins.map(fn(pin) { pin.to_string() }).join(" ")
for index = 0; index < assignments.length(); index = index + 1 {
let a = assignments[index]
let address = match a.i2c_address {
Some(n) => n.to_string()
None => ""
}
for row in wiring_rows([a], reserved_pins~) {
lines.push(
[
index.to_string(),
csv_cell(a.id, user_value=true),
csv_cell(row.signal),
row.gpio.to_string(),
row.header_pin.to_string(),
csv_cell(row.resource),
csv_cell(
match a.bus {
Some(b) => b
None => ""
},
user_value=true,
),
address,
csv_cell(reservations),
match repair_optimal {
Some(b) => b.to_string()
None => ""
},
].join(","),
)
}
}
Some(lines.join("\r\n") + "\r\n")
}
///|
/// Numeric constants for Pico SDK projects. No initialization or drivers.
/// Device indices match the JSON assignments array; names never enter C code.
pub fn export_pico_header(
assignments : Array[Assignment],
reserved_pins? : Array[Int] = [],
repair_optimal? : Bool? = None,
) -> String? {
if !check_assignments(assignments, reserved_pins~).is_empty() {
return None
}
let lines = [
"/* PinPlan: original Pico signal constants; no hardware initialization.",
" * Feasible mapping only. Consult JSON for repair optimality and IDs.",
" * I2C_ADDRESS_KNOWN=0 means address collisions could not be fully checked. */",
"#ifndef PINPLAN_CONFIG_H",
"#define PINPLAN_CONFIG_H",
"#define PINPLAN_DEVICE_COUNT \{assignments.length()}",
"#define PINPLAN_RESERVED_COUNT \{reserved_pins.length()}",
]
match repair_optimal {
Some(optimal) => {
lines.push("#define PINPLAN_REPAIR_OPTIMAL_KNOWN 1")
lines.push(
"#define PINPLAN_REPAIR_OPTIMAL \{if optimal { 1 } else { 0 }}",
)
if !optimal {
lines.push(
"/* REPAIR_LIMIT: feasible repair; minimum changes not proven. */",
)
}
}
None => lines.push("#define PINPLAN_REPAIR_OPTIMAL_KNOWN 0")
}
for i = 0; i < reserved_pins.length(); i = i + 1 {
lines.push("#define PINPLAN_RESERVED_\{i}_GPIO \{reserved_pins[i]}")
}
for i = 0; i < assignments.length(); i = i + 1 {
let a = assignments[i]
let prefix = "PINPLAN_DEVICE_\{i}"
lines.push("#define \{prefix}_INSTANCE \{a.instance}")
lines.push("#define \{prefix}_FREQUENCY_HZ \{a.frequency_hz}")
if a.kind == I2c {
match a.i2c_address {
Some(n) => {
lines.push("#define \{prefix}_I2C_ADDRESS_KNOWN 1")
lines.push("#define \{prefix}_I2C_ADDRESS \{n}")
}
None => lines.push("#define \{prefix}_I2C_ADDRESS_KNOWN 0")
}
}
for row in wiring_rows([a], reserved_pins~) {
lines.push("#define \{prefix}_\{row.signal}_GPIO \{row.gpio}")
lines.push("#define \{prefix}_\{row.signal}_HEADER_PIN \{row.header_pin}")
if a.kind == Pwm {
lines.push("#define \{prefix}_PWM_SLICE \{a.instance}")
lines.push("#define \{prefix}_PWM_CHANNEL \{pwm_channel(row.gpio)}")
}
}
}
lines.push("#endif /* PINPLAN_CONFIG_H */")
Some(lines.join("\n") + "\n")
}