app
Adom Fields
Public Made by Adomby adom
Adom Fields: where the current and the heat go on your board.
main
John Lauer
0.10.3: KiCad 8 numbered nets (issue 21); same-net copper pieces kept apart (issue 22)
5ed6b7b
//! A small s-expression reader for .kicad_pcb files.
#[derive(Debug, Clone)]
pub enum Sx { Atom(String), Num(f64), List(Vec<Sx>) }
impl Sx {
pub fn list(&self) -> &[Sx] { if let Sx::List(v) = self { v } else { &[] } }
pub fn head(&self) -> &str { match self.list().first() { Some(Sx::Atom(a)) => a, _ => "" } }
pub fn kids(&self, key: &str) -> Vec<&Sx> { self.list().iter().filter(|k| matches!(k, Sx::List(_)) && k.head() == key).collect() }
pub fn kid(&self, key: &str) -> Option<&Sx> { self.list().iter().find(|k| matches!(k, Sx::List(_)) && k.head() == key) }
pub fn num(&self, i: usize) -> Option<f64> { match self.list().get(i) { Some(Sx::Num(n)) => Some(*n), Some(Sx::Atom(a)) => a.parse().ok(), _ => None } }
/// the i-th element as text, a number or a word alike (a KiCad 8 net is `(net 41)`, its table entry `(net 41 "GND")`)
pub fn tok(&self, i: usize) -> Option<String> { match self.list().get(i) { Some(Sx::Atom(a)) => Some(a.clone()), Some(Sx::Num(n)) => Some(if n.fract() == 0.0 { format!("{}", *n as i64) } else { format!("{n}") }), _ => None } }
pub fn atom(&self, i: usize) -> Option<&str> { match self.list().get(i) { Some(Sx::Atom(a)) => Some(a), _ => None } }
/// the last atom of the list (a net entry is `(net 3 "+VBAT")` or `(net "+VBAT")`)
pub fn last_atom(&self) -> Option<&str> { self.list().iter().rev().find_map(|k| if let Sx::Atom(a) = k { Some(a.as_str()) } else { None }) }
}
pub fn parse(text: &str) -> Sx {
let b = text.as_bytes();
let mut stack: Vec<Vec<Sx>> = vec![Vec::new()];
let mut i = 0;
while i < b.len() {
let c = b[i];
if c == b'(' { stack.push(Vec::new()); i += 1; }
else if c == b')' { let l = stack.pop().unwrap_or_default(); if let Some(top) = stack.last_mut() { top.push(Sx::List(l)); } i += 1; }
else if c == b'"' {
let mut j = i + 1; let mut s = String::new();
while j < b.len() && b[j] != b'"' { if b[j] == b'\\' && j + 1 < b.len() { j += 1; } s.push(b[j] as char); j += 1; }
stack.last_mut().unwrap().push(Sx::Atom(s)); i = j + 1;
}
else if c.is_ascii_whitespace() { i += 1; }
else {
let mut j = i; while j < b.len() && !b[j].is_ascii_whitespace() && b[j] != b'(' && b[j] != b')' { j += 1; }
let t = &text[i..j];
stack.last_mut().unwrap().push(match t.parse::<f64>() { Ok(n) if !t.is_empty() && t.bytes().next().map(|c| c.is_ascii_digit() || c == b'-' || c == b'.').unwrap_or(false) => Sx::Num(n), _ => Sx::Atom(t.to_string()) });
i = j;
}
}
stack.pop().and_then(|mut v| v.pop()).unwrap_or(Sx::List(vec![]))
}