app
AI Flow: Routing
Public Made by Adomby adom
The routing stage of AI Flow: routes a placed KiCad board connection by connection and reports what it made, what it could not and why. A guide, never a gate.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
//! A small S-expression reader for KiCad files. Lists keep their byte span so a writer can splice
//! the original text instead of re-serialising it.
#[derive(Debug, Clone)]
pub enum Node {
Atom(String),
List(Vec<Node>, (usize, usize)),
}
impl Node {
pub fn items(&self) -> &[Node] {
match self {
Node::List(v, _) => v,
_ => &[],
}
}
pub fn head(&self) -> &str {
match self {
Node::List(v, _) => match v.first() {
Some(Node::Atom(a)) => a,
_ => "",
},
_ => "",
}
}
pub fn span(&self) -> (usize, usize) {
match self {
Node::List(_, s) => *s,
_ => (0, 0),
}
}
pub fn child(&self, name: &str) -> Option<&Node> {
self.items().iter().find(|n| n.head() == name)
}
pub fn children<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Node> + 'a {
self.items().iter().filter(move |n| n.head() == name)
}
pub fn atom(&self, i: usize) -> Option<&str> {
match self.items().get(i) {
Some(Node::Atom(a)) => Some(a),
_ => None,
}
}
pub fn num(&self, i: usize) -> Option<f64> {
self.atom(i).and_then(|a| a.parse().ok())
}
pub fn atoms(&self) -> Vec<&str> {
self.items()
.iter()
.filter_map(|n| if let Node::Atom(a) = n { Some(a.as_str()) } else { None })
.collect()
}
}
pub fn parse(text: &str) -> Result<Node, String> {
let b = text.as_bytes();
let mut stack: Vec<(Vec<Node>, usize)> = Vec::new();
let mut i = 0;
let mut root = None;
while i < b.len() {
match b[i] {
b' ' | b'\t' | b'\r' | b'\n' => i += 1,
b'(' => {
stack.push((Vec::new(), i));
i += 1;
}
b')' => {
let (items, start) = stack.pop().ok_or("unbalanced ')'")?;
let n = Node::List(items, (start, i + 1));
i += 1;
match stack.last_mut() {
Some((p, _)) => p.push(n),
None => {
root = Some(n);
break;
}
}
}
b'"' => {
i += 1;
let mut s = Vec::new();
while i < b.len() && b[i] != b'"' {
if b[i] == b'\\' && i + 1 < b.len() {
i += 1;
s.push(match b[i] {
b'n' => b'\n',
b't' => b'\t',
c => c,
});
} else {
s.push(b[i]);
}
i += 1;
}
i += 1;
let a = Node::Atom(String::from_utf8_lossy(&s).into_owned());
stack.last_mut().ok_or("atom outside list")?.0.push(a);
}
_ => {
let st = i;
while i < b.len() && !matches!(b[i], b' ' | b'\t' | b'\r' | b'\n' | b'(' | b')') {
i += 1;
}
let a = Node::Atom(text[st..i].to_string());
stack.last_mut().ok_or("atom outside list")?.0.push(a);
}
}
}
root.ok_or_else(|| "no root list".to_string())
}