diff --git a/Cargo.toml b/Cargo.toml index 5929391..8ea7461 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,10 @@ itertools = "0.8.2" contracts = "0.6.3" bimap = "0.6.3" log = "0.4" +utf8-chars = "3.0.2" + +[dependencies.dsn_derive] +path = "macro/dsn_derive" [dependencies.geo] version = "0.25.1" diff --git a/macro/dsn_derive/Cargo.toml b/macro/dsn_derive/Cargo.toml new file mode 100644 index 0000000..0ee0688 --- /dev/null +++ b/macro/dsn_derive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dsn_derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.79" +quote = "1.0.35" +syn = "2.0.55" diff --git a/macro/dsn_derive/src/lib.rs b/macro/dsn_derive/src/lib.rs new file mode 100644 index 0000000..14915b2 --- /dev/null +++ b/macro/dsn_derive/src/lib.rs @@ -0,0 +1,40 @@ +use proc_macro::TokenStream; +use syn::{DeriveInput, Attribute, LitStr}; + +mod read; +mod write; + +#[proc_macro_derive(ReadDsn, attributes(opt, anon, vec, anon_vec))] +pub fn derive_read(input: TokenStream) + -> TokenStream +{ + let input = syn::parse_macro_input!(input as DeriveInput); + read::impl_read(&input).into() +} + +#[proc_macro_derive(WriteDsn, attributes(anon))] +pub fn derive_write(input: TokenStream) + -> TokenStream +{ + let input = syn::parse_macro_input!(input as DeriveInput); + write::impl_write(&input).into() +} + +fn attr_present(attrs: &Vec, name: &str) -> bool { + attrs + .iter() + .find(|attr| attr.path().is_ident(name)) + .is_some() +} + +fn attr_content(attrs: &Vec, name: &str) -> Option { + attrs + .iter() + .find(|attr| attr.path().is_ident(name)) + .and_then(|attr| Some(attr + .parse_args::() + .expect("string literal") + .value() + )) +} + diff --git a/macro/dsn_derive/src/read.rs b/macro/dsn_derive/src/read.rs new file mode 100644 index 0000000..cd44a81 --- /dev/null +++ b/macro/dsn_derive/src/read.rs @@ -0,0 +1,84 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, Fields, Field}; +use syn::Type::Path; +use syn::ext::IdentExt; + +use crate::attr_present; +use crate::attr_content; + +pub fn impl_read(input: &DeriveInput) -> TokenStream { + let name = &input.ident; + let body = impl_body(&input.data); + + quote! { + impl ReadDsn for #name { + fn read_dsn(tokenizer: &mut ListTokenizer) + -> Result + { + #body + } + } + } +} + +fn impl_body(data: &Data) -> TokenStream { + match data { + Data::Struct(data) => { + match &data.fields { + Fields::Named(fields) => { + let fields = fields.named.iter().map(|field| { + impl_field(field) + }); + + quote! { + Ok(Self { + #(#fields)* + }) + } + } + _ => unimplemented!() + } + } + Data::Enum(_data) => { + todo!(); + } + _ => unimplemented!() + } +} + +fn impl_field(field: &Field) -> TokenStream { + let name = &field.ident; + let name_str = name.as_ref().expect("field name").unraw(); + + if attr_present(&field.attrs, "anon") { + quote! { + #name: tokenizer.read_value()?, + } + } else if let Some(dsn_name) = attr_content(&field.attrs, "vec") { + quote! { + #name: tokenizer.read_named_array(#dsn_name)?, + } + } else if attr_present(&field.attrs, "anon_vec") { + quote! { + #name: tokenizer.read_array()?, + } + } else { + if let Path(type_path) = &field.ty { + let segments = &type_path.path.segments; + if segments.len() == 1 { + let ident = &segments.first().unwrap().ident; + if ident == "Option" { + return quote! { + #name: tokenizer.read_optional(stringify!(#name_str))?, + } + } + } + } + + quote! { + #name: tokenizer.read_named(stringify!(#name_str))?, + } + } +} + diff --git a/macro/dsn_derive/src/write.rs b/macro/dsn_derive/src/write.rs new file mode 100644 index 0000000..6e9cf24 --- /dev/null +++ b/macro/dsn_derive/src/write.rs @@ -0,0 +1,84 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, Fields, Field}; +use syn::Type::Path; +use syn::ext::IdentExt; + +use crate::attr_present; +use crate::attr_content; + +pub fn impl_write(input: &DeriveInput) -> TokenStream { + let name = &input.ident; + + let body = impl_body(&input.data); + + quote! { + impl WriteDsn for #name { + fn write_dsn(&self, writer: &mut ListWriter) + -> std::io::Result<()> + { + #body + } + } + } +} + +fn impl_body(data: &Data) -> TokenStream { + match data { + Data::Struct(data) => { + match &data.fields { + Fields::Named(fields) => { + let fields = fields.named.iter().map(|field| { + impl_field(field) + }); + + quote! { + #(#fields)* + + Ok(()) + } + } + _ => unimplemented!() + } + } + _ => unimplemented!() + } +} + +fn impl_field(field: &Field) -> TokenStream { + let name = &field.ident; + let name_str = name.as_ref().expect("field name").unraw(); + + if attr_present(&field.attrs, "anon") { + quote! { + writer.write_value(&self.#name)?; + } + } else if let Some(dsn_name) = attr_content(&field.attrs, "vec") { + quote! { + writer.write_named_array(#dsn_name, &self.#name)?; + } + } else if attr_present(&field.attrs, "anon_vec") { + quote! { + writer.write_array(&self.#name)?; + } + + } else { + if let Path(type_path) = &field.ty { + let segments = &type_path.path.segments; + if segments.len() == 1 { + let ident = &segments.first().unwrap().ident; + if ident == "Option" { + return quote! { + writer.write_optional(stringify!(#name_str), &self.#name)?; + } + } + } + } + + quote! { + writer.write_named(stringify!(#name_str), &self.#name)?; + } + } +} + + diff --git a/src/dsn/common.rs b/src/dsn/common.rs new file mode 100644 index 0000000..6540f86 --- /dev/null +++ b/src/dsn/common.rs @@ -0,0 +1,58 @@ +use dsn_derive::ReadDsn; + +use super::structure2::*; +use super::read::{ListTokenizer, ReadDsn, ParseError}; +use super::write::{ListWriter, WriteDsn}; + +pub enum ListToken { + Start { name: String }, + Leaf { value: String }, + End, +} + +impl ListToken { + pub fn expect_start(self, name: &'static str) -> Result<(), ParseError> { + if let Self::Start { name: actual_name } = self { + if name == actual_name { + Ok(()) + } else { + Err(ParseError::ExpectedStartOfList(name)) + } + } else { + Err(ParseError::ExpectedStartOfList(name)) + } + } + + pub fn expect_any_start(self) -> Result { + if let Self::Start { name } = self { + Ok(name) + } else { + Err(ParseError::ExpectedStartOfList("")) + } + } + + pub fn expect_leaf(self) -> Result { + if let Self::Leaf { value } = self { + Ok(value) + } else { + Err(ParseError::Expected("leaf value")) + } + } + + pub fn expect_end(self) -> Result<(), ParseError> { + if let Self::End = self { + Ok(()) + } else { + Err(ParseError::Expected("end of list")) + } + } + + pub fn len(&self) -> usize { + match &self { + Self::Start { name } => 1 + name.len(), + Self::Leaf { value } => value.len(), + Self::End => 1, + } + } +} + diff --git a/src/dsn/design.rs b/src/dsn/design.rs index db3049f..6eeccd9 100644 --- a/src/dsn/design.rs +++ b/src/dsn/design.rs @@ -32,7 +32,83 @@ pub struct DsnDesign { impl DsnDesign { pub fn load_from_file(filename: &str) -> Result { + + let file = std::fs::File::open(filename)?; + let reader = std::io::BufReader::new(file); + let mut list_reader = super::read::ListTokenizer::new(reader); + + let mut dsn = list_reader.read_value::(); + + // TODO: make_board() still uses the old version of structure.rs + // so we can't pass the data to topola for real + + if let Ok(dsn) = dsn { + use super::structure2::*; + + // (this entire if let block does not belong here) + + let ses_name = filename.replace(".dsn", ".ses"); + let file2 = std::fs::File::create(ses_name).unwrap(); + let writer = std::io::BufWriter::new(file2); + let mut list_writer = super::write::ListWriter::new(writer); + + let mut net_outs = HashMap::::new(); + for mut wire in dsn.pcb.wiring.wires { + // move wires to double check that importing the resulting file into KiCad does something + for point in &mut wire.path.coords { + point.x += 1000.0; + } + + if let Some(net) = net_outs.get_mut(&wire.net) { + net.wire.push(wire); + } else { + net_outs.insert(wire.net.clone(), NetOut { + name: wire.net.clone(), + wire: vec!(wire), + via: Vec::new() + }); + } + } + for via in dsn.pcb.wiring.vias { + if let Some(net) = net_outs.get_mut(&via.net) { + net.via.push(via); + } else { + net_outs.insert(via.net.clone(), NetOut { + name: via.net.clone(), + wire: Vec::new(), + via: vec!(via) + }); + } + } + + // build a basic .ses file from what was loaded + let ses = SesFile { + session: Session { + id: "ID".to_string(), + routes: Routes { + resolution: Resolution { + unit: "um".into(), + // TODO: why does resolution need to be adjusted from what was imported? + value: 1.0 + }, + library_out: Library { + images: Vec::new(), + padstacks: dsn.pcb.library.padstacks, + }, + network_out: NetworkOut { + net: net_outs.into_values().collect(), + }, + } + } + }; + + println!("{:?}", list_writer.write_value(&ses)); + } else { + dbg!(dsn); + } + let contents = std::fs::read_to_string(filename)?; + Self::load_from_string(contents) } diff --git a/src/dsn/mod.rs b/src/dsn/mod.rs index f1de5cf..0093e31 100644 --- a/src/dsn/mod.rs +++ b/src/dsn/mod.rs @@ -2,3 +2,7 @@ mod de; pub mod design; pub mod mesadata; mod structure; +mod common; +mod structure2; +mod read; +mod write; diff --git a/src/dsn/read.rs b/src/dsn/read.rs new file mode 100644 index 0000000..efdd463 --- /dev/null +++ b/src/dsn/read.rs @@ -0,0 +1,312 @@ +use thiserror::Error; +use utf8_chars::BufReadCharsExt; +use super::common::ListToken; +use super::structure2::Parser; + +#[derive(Error, Debug)] +pub enum ParseError { + #[error("unexpected end of file")] + Eof, + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("expected {0}")] + Expected(&'static str), + #[error("expected ({0}")] + ExpectedStartOfList(&'static str), +} + +pub trait ReadDsn: Sized { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result; +} +// custom impl feeding the read values back into the tokenizer +impl ReadDsn for Parser { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + Ok(Self { + string_quote: tokenizer + .read_optional("string_quote")? + .inspect(|v| tokenizer.quote_char = Some(*v)), + space_in_quoted_tokens: tokenizer + .read_optional("space_in_quoted_tokens")? + .inspect(|v| tokenizer.space_in_quoted = *v), + host_cad: tokenizer.read_optional("host_cad")?, + host_version: tokenizer.read_optional("host_version")?, + }) + } +} + + +impl ReadDsn for String { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + Ok(tokenizer.consume_token()?.expect_leaf()?) + } +} + +impl ReadDsn for char { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + let string = tokenizer.consume_token()?.expect_leaf()?; + if string.chars().count() == 1 { + Ok(string.chars().next().unwrap()) + } else { + Err(ParseError::Expected("a single character")) + } + } +} + +impl ReadDsn for bool { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + match tokenizer.consume_token()?.expect_leaf()?.as_str() { + "on" => Ok(true), + "off" => Ok(false), + _ => Err(ParseError::Expected("boolean")), + } + } +} + +impl ReadDsn for i32 { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + Ok(tokenizer + .consume_token()? + .expect_leaf()? + .parse() + .map_err(|_| ParseError::Expected("i32"))?) + } +} + +impl ReadDsn for u32 { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + Ok(tokenizer + .consume_token()? + .expect_leaf()? + .parse() + .map_err(|_| ParseError::Expected("u32"))?) + } +} +impl ReadDsn for usize { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + Ok(tokenizer + .consume_token()? + .expect_leaf()? + .parse() + .map_err(|_| ParseError::Expected("usize"))?) + } +} +impl ReadDsn for f32 { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + Ok(tokenizer + .consume_token()? + .expect_leaf()? + .parse() + .map_err(|_| ParseError::Expected("f32"))?) + } +} + +pub struct ListTokenizer { + reader: R, + peeked_char: Option, + cached_token: Option, + space_in_quoted: bool, + quote_char: Option, + line: usize, + column: usize, +} + +impl ListTokenizer { + pub fn new(reader: R) -> Self { + Self { + reader: reader, + peeked_char: None, + cached_token: None, + space_in_quoted: false, + quote_char: None, + line: 1, + column: 0, + } + } + + fn next_char(&mut self) -> Result { + let return_chr = if let Some(chr) = self.peeked_char { + self.peeked_char = None; + chr + } else { + self.reader.chars().next().ok_or(ParseError::Eof)?? + }; + + if return_chr == '\n' { + self.line += 1; + self.column = 0; + } else { + self.column += 1; + } + + Ok(return_chr) + } + + fn peek_char(&mut self) -> Result { + if let Some(chr) = self.peeked_char { + Ok(chr) + } else { + let chr = self.reader.chars().next().ok_or(ParseError::Eof)??; + self.peeked_char = Some(chr); + Ok(chr) + } + } + + fn skip_whitespace(&mut self) -> Result<(), ParseError> { + loop { + let chr = self.peek_char()?; + if chr == ' ' || chr == '\r' || chr == '\n' { + self.next_char().unwrap(); + } else { + return Ok(()); + } + } + } + + fn read_string(&mut self) -> Result { + if let Some(chr) = self.quote_char { + if chr == self.peek_char()? { + return self.read_quoted(); + } + } + self.read_unquoted() + } + + fn read_unquoted(&mut self) -> Result { + let mut string = String::new(); + + loop { + let chr = self.peek_char()?; + if chr == ' ' || chr == '(' || chr == ')' || chr == '\r' || chr == '\n' { + break; + } + string.push(self.next_char().unwrap()); + } + + if string.is_empty() { + Err(ParseError::Expected("string (unquoted)")) + } else { + Ok(string) + } + } + + fn read_quoted(&mut self) -> Result { + let mut string = String::new(); + + if self.next_char().unwrap() != self.quote_char.unwrap() { + panic!(); + } + + loop { + let chr = self.peek_char()?; + if !self.space_in_quoted && chr == ' ' { + panic!("found a space inside a quoted string, but file didn't declare this possibility"); + } + if chr == self.quote_char.unwrap() { + self.next_char().unwrap(); + break; + } + string.push(self.next_char().unwrap()); + } + + Ok(string) + } + + // the following two methods effectively allow 1 token of lookahead + + // returns next token, either a cached one returned earlier or a newly read one + pub fn consume_token(&mut self) -> Result { + // move out of cache if not empty, otherwise consume input + // always leaves cache empty + if let Some(token) = self.cached_token.take() { + Ok(token) + } else { + let token = self.read_token()?; + Ok(token) + } + } + + // puts a token back into cache, to be consumed by something else + pub fn return_token(&mut self, token: ListToken) { + self.cached_token = Some(token); + } + + fn read_token(&mut self) -> Result { + self.skip_whitespace()?; + let chr = self.peek_char()?; + Ok(if chr == '(' { + self.next_char().unwrap(); + self.skip_whitespace()?; + ListToken::Start { name: self.read_string()? } + } else if chr == ')' { + self.next_char().unwrap(); + ListToken::End + } else { + ListToken::Leaf { value: self.read_string()? } + }) + } + + pub fn read_value>(&mut self) -> Result { + T::read_dsn(self) + } + + pub fn read_named>(&mut self, name: &'static str) -> Result { + self.consume_token()?.expect_start(name)?; + let value = self.read_value::()?; + self.consume_token()?.expect_end()?; + Ok(value) + } + + pub fn read_optional>(&mut self, name: &'static str) -> Result, ParseError> { + let token = self.consume_token()?; + if let ListToken::Start { name: ref actual_name } = token { + if actual_name == name { + let value = self.read_value::()?; + self.consume_token()?.expect_end()?; + Ok(Some(value)) + } else { + self.return_token(token); + Ok(None) + } + } else { + self.return_token(token); + Ok(None) + } + } + + pub fn read_array>(&mut self) -> Result, ParseError> { + let mut array = Vec::::new(); + loop { + let token = self.consume_token()?; + if let ListToken::Leaf { .. } = token { + self.return_token(token); + array.push(self.read_value::()?); + } else { + self.return_token(token); + break + } + } + Ok(array) + } + + pub fn read_named_array>(&mut self, name: &'static str) -> Result, ParseError> { + let mut array = Vec::::new(); + loop { + let token = self.consume_token()?; + if let ListToken::Start { name: ref actual_name } = token { + if actual_name == name { + let value = self.read_value::()?; + self.consume_token()?.expect_end()?; + array.push(value); + } else { + self.return_token(token); + break + } + } else { + self.return_token(token); + break + } + } + Ok(array) + } +} + diff --git a/src/dsn/structure2.rs b/src/dsn/structure2.rs new file mode 100644 index 0000000..52f3569 --- /dev/null +++ b/src/dsn/structure2.rs @@ -0,0 +1,399 @@ +use super::read::ReadDsn; +use super::write::WriteDsn; +use dsn_derive::ReadDsn; +use dsn_derive::WriteDsn; +use super::common::ListToken; +use super::read::{ListTokenizer, ParseError}; +use super::write::ListWriter; + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Dummy {} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct SesFile { + pub session: Session, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Session { + #[anon] pub id: String, + pub routes: Routes, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Routes { + pub resolution: Resolution, + pub library_out: Library, + pub network_out: NetworkOut, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct NetworkOut { + #[vec("net")] + pub net: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct NetOut { + #[anon] pub name: String, + #[vec("wire")] + pub wire: Vec, + #[vec("via")] + pub via: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct DsnFile { + pub pcb: Pcb, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Pcb { + #[anon] pub name: String, + pub parser: Parser, + pub resolution: Resolution, + pub unit: String, + pub structure: Structure, + pub placement: Placement, + pub library: Library, + pub network: Network, + pub wiring: Wiring, +} + +#[derive(WriteDsn, Debug)] +pub struct Parser { + pub string_quote: Option, + pub space_in_quoted_tokens: Option, + pub host_cad: Option, + pub host_version: Option, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Resolution { + #[anon] pub unit: String, + #[anon] pub value: f32, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Structure { + #[vec("layer")] + pub layers: Vec, + pub boundary: Boundary, + #[vec("plane")] + pub planes: Vec, + pub via: ViaNames, + pub rule: Rule, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Layer { + #[anon] pub name: String, + pub r#type: String, + pub property: Property, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Property { + pub index: usize, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Boundary { + pub path: Path, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Plane { + #[anon] pub net: String, + pub polygon: Polygon, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct ViaNames { + #[anon_vec] + pub names: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Placement { + #[vec("component")] + pub components: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Component { + #[anon] pub name: String, + #[vec("place")] + pub places: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +#[allow(non_snake_case)] +pub struct Place { + #[anon] pub name: String, + #[anon] pub x: f32, + #[anon] pub y: f32, + #[anon] pub side: String, + #[anon] pub rotation: f32, + pub PN: Option, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Library { + #[vec("image")] + pub images: Vec, + #[vec("padstack")] + pub padstacks: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Image { + #[anon] pub name: String, + #[vec("outline")] + pub outlines: Vec, + #[vec("pin")] + pub pins: Vec, + #[vec("keepout")] + pub keepouts: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Outline { + pub path: Path, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Pin { + #[anon] pub name: String, + pub rotate: Option, + #[anon] pub id: String, + #[anon] pub x: f32, + #[anon] pub y: f32, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Rotate { + pub angle: f32, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Keepout { + #[anon] pub idk: String, + #[anon] pub shape: Shape, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Padstack { + #[anon] pub name: String, + #[vec("shape")] + pub shapes: Vec, + pub attach: bool, +} + +// TODO: derive for enums if more than this single one is needed +#[derive(Debug)] +pub enum Shape { + Circle(Circle), + Rect(Rect), + Path(Path), + Polygon(Polygon), +} + +impl ReadDsn for Shape { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + let name = tokenizer.consume_token()?.expect_any_start()?; + let value = match name.as_str() { + "circle" => Ok(Shape::Circle(tokenizer.read_value()?)), + "rect" => Ok(Shape::Rect(tokenizer.read_value()?)), + "path" => Ok(Shape::Path(tokenizer.read_value()?)), + "polygon" => Ok(Shape::Polygon(tokenizer.read_value()?)), + _ => Err(ParseError::Expected("a different keyword")), + }; + tokenizer.consume_token()?.expect_end()?; + value + } +} + +impl WriteDsn for Shape { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), std::io::Error> { + match self { + Self::Circle(inner) => writer.write_named("circle", inner), + Self::Rect(inner) => writer.write_named("rect", inner), + Self::Path(inner) => writer.write_named("path", inner), + Self::Polygon(inner) => writer.write_named("polygon", inner), + } + } +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Circle { + #[anon] pub layer: String, + #[anon] pub diameter: u32, + #[anon] pub offset: Option, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Network { + #[vec("net")] + pub nets: Vec, + #[vec("class")] + pub classes: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +// dsn names this "net", but it's a structure unrelated to "net" in wiring or elsewhere +pub struct NetPinAssignments { + #[anon] pub name: String, + pub pins: Pins, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Pins { + #[anon_vec] + pub names: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Class { + #[anon] + pub name: String, + #[anon_vec] + pub nets: Vec, + pub circuit: Circuit, + pub rule: Rule, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Circuit { + pub use_via: String, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Wiring { + #[vec("wire")] + pub wires: Vec, + #[vec("via")] + pub vias: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Wire { + pub path: Path, + pub net: String, + pub r#type: String, +} + +//////////////////////////////////////////// +// structs that appear in multiple places // +//////////////////////////////////////////// + +// This type isn't meant to be deserialized as is (single points are +// more conveniently represented as fields on the enclosing struct) +// It exists to give a way to read arrays of coordinates +// (and enforce that such an array actually contains a whole number of points) +#[derive(Debug)] +pub struct Point { + pub x: f32, + pub y: f32, +} + +// Custom impl for the case described above +impl ReadDsn for Vec { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + let mut array = Vec::::new(); + loop { + let token = tokenizer.consume_token()?; + if let ListToken::Leaf { value: ref x } = token { + let x = x.parse::().unwrap(); + let y = tokenizer.read_value::()?; + array.push(Point { x, y }); + } else { + tokenizer.return_token(token); + break + } + } + Ok(array) + } +} + +impl ReadDsn for Option { + fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { + let token = tokenizer.consume_token()?; + if let ListToken::Leaf { value: ref x } = token { + let x = x.parse::().unwrap(); + let y = tokenizer.read_value::()?; + Ok(Some(Point { x, y })) + } else { + tokenizer.return_token(token); + Ok(None) + } + } +} + +impl WriteDsn for Vec { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), std::io::Error> { + for elem in self { + writer.write_value(&elem.x)?; + writer.write_value(&elem.y)?; + } + Ok(()) + } +} + +impl WriteDsn for Option { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), std::io::Error> { + if let Some(value) = self { + writer.write_value(&value.x)?; + writer.write_value(&value.y)?; + } + Ok(()) + } +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Polygon { + #[anon] pub layer: String, + #[anon] pub width: f32, + #[anon] pub coords: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Path { + #[anon] pub layer: String, + #[anon] pub width: f32, + #[anon] pub coords: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Rect { + #[anon] pub layer: String, + #[anon] pub x1: f32, + #[anon] pub y1: f32, + #[anon] pub x2: f32, + #[anon] pub y2: f32, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Via { + #[anon] + pub name: String, + #[anon] + pub x: i32, + #[anon] + pub y: i32, + pub net: String, + pub r#type: String, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Rule { + pub width: f32, + #[vec("clearance")] + pub clearances: Vec, +} + +#[derive(ReadDsn, WriteDsn, Debug)] +pub struct Clearance { + #[anon] pub value: f32, + pub r#type: Option, +} diff --git a/src/dsn/write.rs b/src/dsn/write.rs new file mode 100644 index 0000000..71ae500 --- /dev/null +++ b/src/dsn/write.rs @@ -0,0 +1,182 @@ +use super::common::ListToken; +use std::io; + +pub trait WriteDsn { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error>; +} + +impl WriteDsn for char { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { + writer.write_leaf(self.to_string()) + } +} + +impl WriteDsn for String { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { + let string = if self.len() == 0 { + "\"\"".to_string() + } else if self.contains(" ") + || self.contains("(") + || self.contains(")") + || self.contains("\n") + { + format!("\"{}\"", self) + } else { + self.to_string() + }; + writer.write_leaf(string) + } +} + +impl WriteDsn for bool { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { + writer.write_leaf(match self { + true => "on".to_string(), + false => "off".to_string(), + }) + } +} + +impl WriteDsn for i32 { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { + writer.write_leaf(self.to_string()) + } +} + +impl WriteDsn for u32 { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { + writer.write_leaf(self.to_string()) + } +} + +impl WriteDsn for usize { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { + writer.write_leaf(self.to_string()) + } +} + +impl WriteDsn for f32 { + fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { + writer.write_leaf(self.to_string()) + } +} + +pub struct ListWriter { + writable: W, + indent_level: usize, + multiline_level: usize, + pub line_len: usize, +} + +impl ListWriter { + pub fn new(writable: W) -> Self { + Self { + writable, + indent_level: 0, + multiline_level: 0, + line_len: 0, + } + } + + pub fn write_token(&mut self, token: ListToken) -> Result<(), io::Error> { + let len = token.len(); + + match token { + ListToken::Start { name } => { + write!(self.writable, + "\n{}({}", + " ".repeat(self.indent_level), + name + )?; + self.multiline_level = self.indent_level; + self.line_len = 2 * self.indent_level + len; + self.indent_level += 1; + Ok(()) + } + ListToken::Leaf { value } => { + self.line_len += 1 + len; + write!(self.writable, " {}", value) + } + ListToken::End => { + if self.indent_level <= self.multiline_level { + self.indent_level -= 1; + self.line_len = 2 * self.indent_level + len; + write!(self.writable, + "\n{})", + " ".repeat(self.indent_level) + ) + } else { + self.indent_level -= 1; + self.line_len += len; + write!(self.writable, ")") + } + } + } + } + + pub fn write_leaf(&mut self, value: String) -> Result<(), io::Error> { + self.write_token(ListToken::Leaf { value }) + } + + pub fn write_value>( + &mut self, + value: &T, + ) -> Result<(), io::Error> { + value.write_dsn(self) + } + + pub fn write_named>( + &mut self, + name: &'static str, + value: &T, + ) + -> Result<(), io::Error> + { + self.write_token(ListToken::Start { name: name.to_string() } )?; + self.write_value(value)?; + self.write_token(ListToken::End)?; + + Ok(()) + } + + pub fn write_optional>( + &mut self, + name: &'static str, + optional: &Option, + ) + -> Result<(), io::Error> + { + if let Some(value) = optional { + self.write_named(name, value)?; + } + + Ok(()) + } + + pub fn write_array>( + &mut self, + array: &Vec, + ) + -> Result<(), io::Error> + { + for elem in array { + self.write_value(elem)?; + } + + Ok(()) + } + + pub fn write_named_array>( + &mut self, + name: &'static str, + array: &Vec, + ) + -> Result<(), io::Error> + { + for elem in array { + self.write_named(name, elem)?; + } + + Ok(()) + } +}