diff --git a/src/bin/topola-sdl2-demo/main.rs b/src/bin/topola-sdl2-demo/main.rs index 746fbf6..4a571f8 100644 --- a/src/bin/topola-sdl2-demo/main.rs +++ b/src/bin/topola-sdl2-demo/main.rs @@ -48,6 +48,9 @@ use topola::tracer::{Trace, Tracer}; use topola::math::Circle; use topola::router::Router; +use topola::dsn::de::from_str; +use topola::dsn::structure::Pcb; + struct SimpleRules { net_clearances: HashMap<(i64, i64), f64>, } @@ -245,331 +248,104 @@ fn main() -> Result<(), anyhow::Error> { ]), }); - let component1_1 = router.layout.add_component(1); - let component1_2 = router.layout.add_component(1); - let component2 = router.layout.add_component(2); - let component3_1 = router.layout.add_component(3); - let component3_2 = router.layout.add_component(3); - let component4_1 = router.layout.add_component(4); - let component4_2 = router.layout.add_component(4); + let contents = std::fs::read_to_string("tests/data/test.dsn")?; + let pcb = from_str::(&contents)?; + //dbg!(&pcb); - let dot_start = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component1_1, - circle: Circle { - pos: (100.5, 400.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let dot_start2 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component3_1, - circle: Circle { - pos: (100.5, 500.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let dot_start3 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component4_1, - circle: Circle { - pos: (160.5, 430.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let dot_end = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component1_2, - circle: Circle { - pos: (470.5, 350.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let dot_end2 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component3_2, - circle: Circle { - pos: (500.5, 150.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let dot_end3 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component4_2, - circle: Circle { - pos: (350.5, 200.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let dot1_1 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component2, - circle: Circle { - pos: (200.5, 200.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let dot2_1 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component2, - circle: Circle { - pos: (200.5, 500.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let _ = router.layout.add_fixed_seg( - dot1_1, - dot2_1, - FixedSegWeight { - component: component2, - width: 3.0, - }, + // this holds the mapping of net names to numerical IDs (here for now) + let net_ids: HashMap = HashMap::from_iter( + pcb.network.classes[0].nets + .iter() + .enumerate() + .map(|(id, net)| (net.clone(), id)) ); - let dot2_2 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component2, - circle: Circle { - pos: (600.5, 500.5).into(), - r: 8.0, - }, + // add vias to layout and save indices of dots in the order they appear in the file + let dot_indices: Vec<_> = pcb.wiring.vias + .iter() + .map(|via| { + let net_id = net_ids.get(&via.net.0).unwrap(); + let component = router.layout.add_component(*net_id as i64); + + // no way to resolve the name or layer support yet + // pick the first layer of the first object found + let circle = &pcb.library.padstacks[0].shapes[0].0; + let circle = Circle { + pos: ( + via.x as f64 / 100.0, + -via.y as f64 / 100.0, + ).into(), + r: circle.radius as f64 / 100.0, + }; + + router.layout.add_fixed_dot(FixedDotWeight { + component, + circle, + }).unwrap() }) - .unwrap(); + .collect(); - let _ = router.layout.add_fixed_seg( - dot2_1, - dot2_2, - FixedSegWeight { - component: component2, - width: 3.0, - }, - ); + for wire in pcb.wiring.wires.iter() { + let net_id = net_ids.get(&wire.net.0).unwrap(); + let component = router.layout.add_component(*net_id as i64); - let dot3 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component2, + // add the first coordinate in the wire path as a dot and save its index + let mut prev_index = router.layout.add_fixed_dot(FixedDotWeight { + component, circle: Circle { - pos: (400.5, 200.5).into(), - r: 8.0, - }, - }) - .unwrap(); + pos: ( + wire.path.coords[0].x as f64 / 100.0, + -wire.path.coords[0].y as f64 / 100.0, + ).into(), + r: wire.path.width as f64 / 100.0, + } + }).unwrap(); - let dot4 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component2, - circle: Circle { - pos: (400.5, 400.5).into(), - r: 8.0, - }, - }) - .unwrap(); + // iterate through path coords starting from the second + for coord in wire.path.coords.iter().skip(1) { + let index = router.layout.add_fixed_dot(FixedDotWeight { + component, + circle: Circle { + pos: ( + coord.x as f64 / 100.0, + -coord.y as f64 / 100.0, + ).into(), + r: wire.path.width as f64 / 100.0, + } + }).unwrap(); - let _ = router.layout.add_fixed_seg( - dot3, - dot4, - FixedSegWeight { - component: component2, - width: 3.0, - }, - ); + // add a seg between the current and previous coords + let _ = router.layout.add_fixed_seg( + prev_index, + index, + FixedSegWeight { + component, + width: wire.path.width as f64 / 100.0, + }, + ).unwrap(); - let dot5 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component2, - circle: Circle { - pos: (530.5, 400.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let _ = router.layout.add_fixed_seg( - dot4, - dot5, - FixedSegWeight { - component: component2, - width: 3.0, - }, - ); - - let dot1_2 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component2, - circle: Circle { - pos: (600.5, 200.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let _ = router.layout.add_fixed_seg( - dot3, - dot1_2, - FixedSegWeight { - component: component2, - width: 3.0, - }, - ); - - let _ = router.layout.add_fixed_seg( - dot1_2, - dot2_2, - FixedSegWeight { - component: component2, - width: 3.0, - }, - ); - - let dot6 = router - .layout - .add_fixed_dot(FixedDotWeight { - component: component2, - circle: Circle { - pos: (530.5, 300.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let _ = router.layout.add_fixed_seg( - dot5, - dot6, - FixedSegWeight { - component: component2, - width: 3.0, - }, - ); - - /*let dot7 = router - .layout - .add_dot(FixedDotWeight { - net: 2, - circle: Circle { - pos: (400.5, 440.5).into(), - r: 8.0, - }, - }) - .unwrap(); - - let _ = router.layout.add_seg( - dot4, - dot7, - FixedSegWeight { - net: 20, - width: 3.0, - }, - );*/ - - /*render_times( - &mut event_pump, - &mut canvas, - RouterOrLayout::Layout(&router.layout), - None, - None, - None, - &[], - -1, - ); + prev_index = index; + } + } render_times( &mut event_pump, - &mut canvas, - RouterOrLayout::Router(&mut router), - Some(dot_start), - Some(dot_end), + &window, + &mut renderer, + &font_context, + RouterOrLayout::Layout(&router.layout), + None, None, &[], + &[], + &[], -1, - );*/ + ); + // these are both on net 1 in the test file let _ = router.route_band( - dot_start, - dot_end, - 3.0, - &mut EmptyRouterObserver, - //&mut DebugRouterObserver::new(&mut event_pump, &window, &mut renderer, &font_context), - )?; - - render_times( - &mut event_pump, - &window, - &mut renderer, - &font_context, - RouterOrLayout::Layout(&router.layout), - None, - None, - &[], - &[], - &[], - -1, - ); - - /*render_times( - &mut event_pump, - &mut canvas, - RouterOrLayout::Router(&mut router), - Some(dot_start2), - Some(dot_end), - None, - &[], - -1, - );*/ - - let band2 = router.route_band( - dot_start2, - dot_end2, - 3.0, - &mut EmptyRouterObserver, - //&mut DebugRouterObserver::new(&mut event_pump, &window, &mut renderer, &font_context), - )?; - - render_times( - &mut event_pump, - &window, - &mut renderer, - &font_context, - RouterOrLayout::Layout(&router.layout), - None, - None, - &[], - &[], - &[], - -1, - ); - - let band3 = router.route_band( - dot_start3, - dot_end3, + dot_indices[1], + dot_indices[2], 3.0, //&mut EmptyRouterObserver, &mut DebugRouterObserver::new(&mut event_pump, &window, &mut renderer, &font_context), @@ -589,50 +365,6 @@ fn main() -> Result<(), anyhow::Error> { -1, ); - router.layout.remove_band(band3); - - render_times( - &mut event_pump, - &window, - &mut renderer, - &font_context, - RouterOrLayout::Layout(&router.layout), - None, - None, - &[], - &[], - &[], - -1, - ); - - render_times( - &mut event_pump, - &window, - &mut renderer, - &font_context, - RouterOrLayout::Router(&mut router), - Some(band2), - None, - &[], - &[], - &[], - -1, - ); - - render_times( - &mut event_pump, - &window, - &mut renderer, - &font_context, - RouterOrLayout::Layout(&router.layout), - None, - None, - &[], - &[], - &[], - -1, - ); - Ok(()) } diff --git a/src/dsn/de.rs b/src/dsn/de.rs new file mode 100644 index 0000000..e5c0ca6 --- /dev/null +++ b/src/dsn/de.rs @@ -0,0 +1,632 @@ +use serde::Deserialize; +use serde::de::{self, Visitor, SeqAccess, DeserializeSeed}; +use thiserror::Error; + +type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Message(String), + #[error("unexpected EOF")] + Eof, + #[error("expected boolean value")] + ExpectedBool, + #[error("expected quoted string")] + ExpectedQuoted, + #[error("spaces in quoted strings weren't declared")] + SpaceInQuoted, + #[error("expected unquoted string")] + ExpectedUnquoted, + #[error("expected opening parenthesis")] + ExpectedOpeningParen, + #[error("expected closing parenthesis")] + ExpectedClosingParen, + #[error("wrong struct, expected {0}")] + ExpectedStruct(&'static str), +} + +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error::Message(msg.to_string()) + } +} + +struct Deserializer<'de> +{ + input: &'de str, + line: usize, + column: usize, + + string_quote: Option, + space_in_quoted_tokens: bool, + reconfig_incoming: Option, + + next_option_empty_hint: bool, + last_deserialized_type: Option<&'static str>, +} + +#[derive(PartialEq, Debug, Copy, Clone)] +enum ReconfigIncoming { + StringQuote, + SpaceAllowed, +} + +impl<'de> Deserializer<'de> { + fn from_str(input: &'de str) -> Self { + Self { + input, + line: 0, + column: 0, + + string_quote: None, + space_in_quoted_tokens: false, + reconfig_incoming: None, + + next_option_empty_hint: false, + last_deserialized_type: None, + } + } + + fn next_name_lookahead(&self) -> Option { + let mut iter = self.input.chars(); + if iter.next() != Some('(') { + None + } + else { + Some(iter.take_while(|c| c != &' ' && c != &'\r' && c != &'\n').collect::()) + } + } + + fn peek(&mut self) -> Result { + self.input.chars().next().ok_or(Error::Eof) + } + + fn next(&mut self) -> Result { + let chr = self.peek()?; + self.input = &self.input[1..]; + if chr == '\n' { + self.line += 1; + self.column = 0; + } + else { + self.column += 1; + } + Ok(chr) + } + + fn skip_ws(&mut self) { + while let Ok(chr) = self.peek() { + if chr != ' ' && chr != '\r' && chr != '\n' { + return; + } + else { + self.next().unwrap(); + } + } + return; + } + + fn parse_bool(&mut self) -> Result { + match &self.parse_identifier() { + Ok(string) => match string.as_str() { + "on" => Ok(true), + "off" => Ok(false), + _ => Err(Error::ExpectedBool), + } + Err(_) => Err(Error::ExpectedBool), + } + } + + fn parse_identifier(&mut self) -> Result { + self.parse_unquoted() + } + + fn parse_string(&mut self) -> Result { + let chr = self.peek()?; + if self.string_quote == Some(chr) { + self.parse_quoted() + } + else { + self.parse_unquoted() + } + } + + fn parse_unquoted(&mut self) -> Result { + let mut string = String::new(); + loop { + let chr = self.peek()?; + if chr != ' ' && chr != '\r' && chr != '\n' && chr != '(' && chr != ')' { + string.push(self.next()?); // can't fail because of earlier peek + } + else { + if string.len() > 0 { + return Ok(string); + } + else { + dbg!(self.line, self.column); + return Err(Error::ExpectedUnquoted); + } + } + } + } + + // method only called if parse_string sees a valid string quote + fn parse_quoted(&mut self) -> Result { + assert!(self.next().unwrap() == self.string_quote.unwrap()); + + let mut string = String::new(); + loop { + let chr = self.peek()?; + + // XXX this is silly + // not declaring that spaces are allowed in qyoted strings downgrades the format + // but there's no reason we shouldn't try to parse the file anyway, no ambiguity arises + // maybe this should log a warning and proceed? + if self.space_in_quoted_tokens != true && chr == ' ' { + return Err(Error::SpaceInQuoted); + } + + if Some(chr) == self.string_quote { + self.next().unwrap(); + return Ok(string); + } + else { + string.push(self.next()?); // can't fail because of earlier peek + } + } + } +} + +pub fn from_str<'a, T>(input: &'a str) -> Result +where + T: Deserialize<'a>, +{ + let mut deserializer = Deserializer::from_str(input); + let t = T::deserialize(&mut deserializer)?; + if !deserializer.input.is_empty() { + println!("remaining input"); + dbg!(deserializer.input); + } + Ok(t) +} + +impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { + type Error = Error; + + fn deserialize_any(self, _visitor: V) -> Result + where V: + Visitor<'de>, + { + todo!(); + } + + fn deserialize_bool(self, visitor: V) -> Result + where V: + Visitor<'de>, + { + let value = self.parse_bool()?; + self.skip_ws(); + + // if the struct deserializer set a variable saying the incoming value should reconfigure a specific variable in the parser + // we do so and clear the flag + if self.reconfig_incoming == Some(ReconfigIncoming::SpaceAllowed) { + self.space_in_quoted_tokens = value; + self.reconfig_incoming = None; + } + + self.last_deserialized_type = Some(""); + visitor.visit_bool(value) + } + + fn deserialize_i8(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + fn deserialize_i16(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + fn deserialize_i32(self, visitor: V) -> Result + where V: Visitor<'de>, + { + let value = self.parse_unquoted()?; + self.skip_ws(); + + self.last_deserialized_type = Some(""); + visitor.visit_i32(value.parse().unwrap()) + } + fn deserialize_i64(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + fn deserialize_u8(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + fn deserialize_u16(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + fn deserialize_u32(self, visitor: V) -> Result + where V: Visitor<'de>, + { + let value = self.parse_unquoted()?; + self.skip_ws(); + + self.last_deserialized_type = Some(""); + visitor.visit_u32(value.parse().unwrap()) + } + fn deserialize_u64(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + fn deserialize_f32(self, visitor: V) -> Result + where V: Visitor<'de>, + { + let value = self.parse_unquoted()?; + self.skip_ws(); + + self.last_deserialized_type = Some(""); + visitor.visit_f32(value.parse().unwrap()) + } + fn deserialize_f64(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + fn deserialize_char(self, visitor: V) -> Result + where V: Visitor<'de>, + { + let chr = self.next()?; + self.skip_ws(); + + // if the struct deserializer set a variable saying the incoming value should reconfigure a specific variable in the parser + // we do so and clear the flag + if self.reconfig_incoming == Some(ReconfigIncoming::StringQuote) { + self.string_quote = Some(chr); + self.reconfig_incoming = None; + } + + self.last_deserialized_type = Some(""); + visitor.visit_char(chr) + } + + fn deserialize_str(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + + fn deserialize_string(self, visitor: V) -> Result + where V: + Visitor<'de>, + { + let string = self.parse_string()?; + self.skip_ws(); + + self.last_deserialized_type = Some(""); + visitor.visit_string(string) + } + + fn deserialize_bytes(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + + fn deserialize_byte_buf( + self, + _visitor: V + ) -> Result + where V: Visitor<'de>, + { + todo!(); + } + + fn deserialize_option(self, visitor: V) -> Result + where V: Visitor<'de>, + { + if self.next_option_empty_hint { + visitor.visit_none() + } + else { + visitor.visit_some(self) + } + } + + fn deserialize_unit(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + + fn deserialize_unit_struct( + self, + name: &'static str, + visitor: V + ) -> Result + where V: Visitor<'de>, + { + if self.next()? != '(' { + return Err(Error::ExpectedOpeningParen); + } + self.skip_ws(); + + if &self.parse_identifier()? != name { + return Err(Error::ExpectedStruct(name)); + } + self.skip_ws(); + + if self.next()? != ')' { + return Err(Error::ExpectedClosingParen); + } + self.skip_ws(); + + self.last_deserialized_type = Some(name); + + visitor.visit_unit() + } + + fn deserialize_newtype_struct( + self, + name: &'static str, + visitor: V + ) -> Result + where V: Visitor<'de>, + { + if self.next()? != '(' { + return Err(Error::ExpectedOpeningParen); + } + self.skip_ws(); + + if &self.parse_identifier()? != name { + return Err(Error::ExpectedStruct(name)); + } + self.skip_ws(); + + // if what we're deserializing is a directive to update parser configuration + // set a variable so the deserializer for the following value can update the relevant config + // (the variable is reset to None by the bool/char deserializer when it updates the config) + self.reconfig_incoming = match name { + "string_quote" => Some(ReconfigIncoming::StringQuote), + "space_in_quoted_tokens" => Some(ReconfigIncoming::SpaceAllowed), + _ => None, + }; + + let value = visitor.visit_seq(NewtypeStructFields::new(self))?; + + if self.next()? != ')' { + return Err(Error::ExpectedClosingParen); + } + self.skip_ws(); + + self.last_deserialized_type = Some(name); + + Ok(value) + } + + fn deserialize_seq(self, visitor: V) -> Result + where V: Visitor<'de>, + { + self.last_deserialized_type = None; + visitor.visit_seq(ArrayIndices::new(self)) + } + + fn deserialize_tuple( + self, + _len: usize, + _visitor: V + ) -> Result + where V: Visitor<'de>, + { + todo!(); + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + _visitor: V + ) -> Result + where V: Visitor<'de>, + { + todo!(); + } + + fn deserialize_map(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + todo!(); + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V + ) -> Result + where V: Visitor<'de>, + { + if self.next()? != '(' { + return Err(Error::ExpectedOpeningParen); + } + self.skip_ws(); + + if &self.parse_identifier()? != name { + return Err(Error::ExpectedStruct(name)); + } + self.skip_ws(); + + let value = visitor.visit_seq(StructFields::new(self, fields))?; + + if self.next()? != ')' { + return Err(Error::ExpectedClosingParen); + } + self.skip_ws(); + + // a hint for the array deserializer + self.last_deserialized_type = Some(name); + + Ok(value) + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + _visitor: V + ) -> Result + where V: Visitor<'de>, + { + todo!(); + } + + fn deserialize_identifier( + self, + visitor: V + ) -> Result + where V: Visitor<'de> + { + visitor.visit_string(self.parse_string()?) + } + + fn deserialize_ignored_any( + self, + _visitor: V + ) -> Result + where V: Visitor<'de> + { + todo!(); + } +} + +struct NewtypeStructFields<'a, 'de: 'a> { + de: &'a mut Deserializer<'de>, +} + +impl<'a, 'de> NewtypeStructFields<'a, 'de> { + fn new(de: &'a mut Deserializer<'de>) -> Self { + Self { + de, + } + } +} + +impl<'de, 'a> SeqAccess<'de> for NewtypeStructFields<'a, 'de> { + type Error = Error; + + fn next_element_seed(&mut self, seed: S) -> Result> + where S: DeserializeSeed<'de>, + { + if self.de.peek()? == ')' { + return Ok(None); + } + + seed.deserialize(&mut *self.de).map(Some) + } +} + +struct ArrayIndices<'a, 'de: 'a> { + de: &'a mut Deserializer<'de>, +} + +impl<'a, 'de> ArrayIndices<'a, 'de> { + fn new(de: &'a mut Deserializer<'de>) -> Self { + Self { + de, + } + } +} + +impl<'de, 'a> SeqAccess<'de> for ArrayIndices<'a, 'de> { + type Error = Error; + + fn next_element_seed(&mut self, seed: S) -> Result> + where S: DeserializeSeed<'de>, + { + if self.de.peek()? == ')' { + return Ok(None); + } + + if let Some(prev) = self.de.last_deserialized_type { + if let Some(lookahead) = self.de.next_name_lookahead() { + if prev != lookahead { + // the next struct is of different type from the array contents + // that means the array implicitly ended + // and we're looking at a field following the array instead + return Ok(None); + } + } + } + + seed.deserialize(&mut *self.de).map(Some) + } +} + + +struct StructFields<'a, 'de: 'a> { + de: &'a mut Deserializer<'de>, + current_field: usize, + fields: &'static [&'static str], +} + +impl<'a, 'de> StructFields<'a, 'de> { + fn new(de: &'a mut Deserializer<'de>, fields: &'static [&'static str]) -> Self { + Self { + de, + current_field: 0, + fields, + } + } +} + +impl<'de, 'a> SeqAccess<'de> for StructFields<'a, 'de> { + type Error = Error; + + fn next_element_seed(&mut self, seed: S) -> Result> + where S: DeserializeSeed<'de>, + { + if self.de.peek()? == ')' { + if self.current_field < self.fields.len() { + // We're short a field (or multiple), + // but the trailing field(s) might be optional and implicitly absent. + // In that case we prepare a hint for deserialize_option to emit None: + self.de.next_option_empty_hint = true; + // and we tell serde to deserialize a field that may or may not be there: + self.current_field += 1; + return seed.deserialize(&mut *self.de).map(Some); + // If it was a non-optional that was missing for real, + // then even though our bet here was wrong (and we just lied to serde) + // the deserializer we handed off to will see the same closing paren + // (that we reacted to just now) and still return a sensible error. + } + else { + return Ok(None); + } + } + + // TODO explain this part of empty option detection + if let Some(lookahead) = self.de.next_name_lookahead() { + if lookahead != self.fields[self.current_field] { + self.de.next_option_empty_hint = true; + } + else { + self.de.next_option_empty_hint = false; + } + } + else { + self.de.next_option_empty_hint = false; + } + + self.current_field += 1; + seed.deserialize(&mut *self.de).map(Some) + } +} + diff --git a/src/dsn/mod.rs b/src/dsn/mod.rs new file mode 100644 index 0000000..ede6337 --- /dev/null +++ b/src/dsn/mod.rs @@ -0,0 +1,2 @@ +pub mod de; +pub mod structure; diff --git a/src/dsn/structure.rs b/src/dsn/structure.rs new file mode 100644 index 0000000..954b7e0 --- /dev/null +++ b/src/dsn/structure.rs @@ -0,0 +1,239 @@ +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +#[serde(rename = "pcb")] +pub struct Pcb { + pub name: String, + pub parser: Parser, + pub resolution: Resolution, + pub unit: Option, + pub structure: Structure, + pub placement: Placement, + pub library: Library, + pub network: Network, + pub wiring: Wiring, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "parser")] +pub struct Parser { + pub string_quote: Option, + pub space_in_quoted_tokens: SpaceAllowed, + pub host_cad: Option, + pub host_version: Option, +} + +#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] +#[serde(rename = "string_quote")] +pub struct StringQuote(pub char); + +#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] +#[serde(rename = "space_in_quoted_tokens")] +pub struct SpaceAllowed(pub bool); + +#[derive(Deserialize, Debug)] +#[serde(rename = "host_cad")] +pub struct HostCad(pub String); + +#[derive(Deserialize, Debug)] +#[serde(rename = "host_version")] +pub struct HostVersion(pub String); + +#[derive(Deserialize, Debug)] +#[serde(rename = "resolution")] +pub struct Resolution { + pub unit: String, + pub value: u32, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "unit")] +pub struct Unit(pub String); + +#[derive(Deserialize, Debug)] +#[serde(rename = "structure")] +pub struct Structure { + pub layers: Vec, + pub boundary: Boundary, + pub vias: Vias, + pub rule: Rule, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "layer")] +pub struct Layer { + pub name: String, + pub r#type: Type, + pub property: Property, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "property")] +pub struct Property(Index); + +#[derive(Deserialize, Debug)] +#[serde(rename = "index")] +pub struct Index(pub u32); + +#[derive(Deserialize, Debug)] +#[serde(rename = "boundary")] +pub struct Boundary(pub Path); + +#[derive(Deserialize, Debug)] +#[serde(rename = "via")] +pub struct Vias { + vias: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "placement")] +pub struct Placement; + +#[derive(Deserialize, Debug)] +#[serde(rename = "library")] +pub struct Library { + pub padstacks: Vec +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "padstack")] +pub struct Padstack { + pub name: String, + pub shapes: Vec, + pub attach: Attach, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "shape")] +pub struct Shape(pub Circle); + +#[derive(Deserialize, Debug)] +#[serde(rename = "circle")] +pub struct Circle { + pub layer: String, + pub radius: u32, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "attach")] +pub struct Attach(pub bool); + +#[derive(Deserialize, Debug)] +#[serde(rename = "network")] +pub struct Network { + pub classes: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "class")] +pub struct Class { + pub name: String, + pub nets: Vec, + pub circuit: Circuit, + pub rule: Rule, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "circuit")] +pub struct Circuit(pub UseVia); + +#[derive(Deserialize, Debug)] +#[serde(rename = "use_via")] +pub struct UseVia { + pub name: String, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "wiring")] +pub struct Wiring { + pub wires: Vec, + pub vias: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "wire")] +pub struct Wire { + pub path: Path, + pub net: Net, + pub r#type: Type, +} + +// structs that appear in multiple places + +#[derive(Deserialize, Debug)] +#[serde(rename = "type")] +pub struct Type(pub String); + +#[derive(Deserialize, Debug)] +#[serde(rename = "unit")] +pub struct Point { + pub x: i32, + pub y: i32, +} + +#[derive(Deserialize, Debug)] +#[serde(from = "FlatPath")] +pub struct Path { + pub layer: String, + pub width: u32, + pub coords: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "path")] +struct FlatPath { + pub layer: String, + pub width: u32, + pub coords: Vec, +} + +impl From for Path { + fn from(flat: FlatPath) -> Path { + Path { + layer: flat.layer, + width: flat.width, + coords: flat.coords.chunks(2) + .map(|pair| Point { + x: pair[0], + // it's possible to return an error instead of panicking if this From were TryFrom, + // but I don't think serde will let us grab and inspect it elsewhere + // so annotating this with line/column information later might be difficult? + y: *pair.get(1).expect("unpaired coordinate in path"), + }) + .collect(), + } + } +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "via")] +pub struct Via { + pub name: String, + pub x: i32, + pub y: i32, + pub net: Net, + pub r#type: Type, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "rule")] +pub struct Rule { + pub width: Width, + pub clearances: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename = "net")] +pub struct Net(pub String); + +#[derive(Deserialize, Debug)] +#[serde(rename = "width")] +pub struct Width(pub f32); + +#[derive(Deserialize, Debug)] +#[serde(rename = "clearance")] +pub struct Clearance { + pub value: f32, + pub r#type: Option, +} + diff --git a/src/lib.rs b/src/lib.rs index 9ef767c..a1a6161 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,3 +11,4 @@ pub mod router; pub mod tracer; pub mod triangulation; pub mod wraparoundable; +pub mod dsn; diff --git a/tests/data/test.dsn b/tests/data/test.dsn new file mode 100644 index 0000000..8faaeae --- /dev/null +++ b/tests/data/test.dsn @@ -0,0 +1,77 @@ +(pcb test.dsn + (parser + (string_quote ") + (space_in_quoted_tokens on) + (host_cad "KiCad's Pcbnew") + (host_version "7.0.9") + ) + (resolution um 10) + (unit um) + (structure + (layer F.Cu + (type signal) + (property + (index 0) + ) + ) + (layer B.Cu + (type signal) + (property + (index 1) + ) + ) + (boundary + (path pcb 0 60400 -50400 9600 -50400 9600 -14600 60400 -14600 + 60400 -50400) + ) + (via "Via[0-1]_800:400_um") + (rule + (width 300) + (clearance 200.1) + (clearance 200.1 (type default_smd)) + (clearance 50 (type smd_smd)) + ) + ) + (placement + ) + (library + (padstack "Via[0-1]_800:400_um" + (shape (circle F.Cu 800)) + (shape (circle B.Cu 800)) + (attach off) + ) + ) + (network + (class kicad_default "" 1 2 3 5 + (circuit + (use_via Via[0-1]_800:400_um) + ) + (rule + (width 300) + (clearance 200.1) + ) + ) + ) + (wiring + (wire (path F.Cu 300 20000 -50000 60000 -50000)(net 5)(type route)) + (wire (path F.Cu 300 60000 -20000 40000 -20000 40000 -40000)(net 5)(type route)) + (wire (path F.Cu 300 50000 -40000 50000 -30000)(net 5)(type route)) + (wire (path F.Cu 300 60000 -50000 60000 -20000)(net 5)(type route)) + (wire (path F.Cu 300 20000 -20000 20000 -50000)(net 5)(type route)) + (wire (path F.Cu 300 40000 -40000 50000 -40000)(net 5)(type route)) + (via "Via[0-1]_800:400_um" 47000 -35000 (net 2)(type route)) + (via "Via[0-1]_800:400_um" 10000 -40000 (net 1)(type route)) + (via "Via[0-1]_800:400_um" 50000 -15000 (net 1)(type route)) + (via "Via[0-1]_800:400_um" 10000 -50000 (net 2)(type route)) + (via "Via[0-1]_800:400_um" 13000 -47000 (net 3)(type route)) + (via "Via[0-1]_800:400_um" 35000 -20000 (net 3)(type route)) + (via "Via[0-1]_800:400_um" 20000 -50000 (net 5)(type route)) + (via "Via[0-1]_800:400_um" 20000 -20000 (net 5)(type route)) + (via "Via[0-1]_800:400_um" 40000 -20000 (net 5)(type route)) + (via "Via[0-1]_800:400_um" 60000 -50000 (net 5)(type route)) + (via "Via[0-1]_800:400_um" 50000 -30000 (net 5)(type route)) + (via "Via[0-1]_800:400_um" 40000 -40000 (net 5)(type route)) + (via "Via[0-1]_800:400_um" 60000 -20000 (net 5)(type route)) + (via "Via[0-1]_800:400_um" 50000 -40000 (net 5)(type route)) + ) +)