mirror of https://codeberg.org/topola/topola.git
dsn: add new parser/writer and .ses export prototype
This commit is contained in:
parent
90bc90350d
commit
7d9bf1346a
|
|
@ -32,6 +32,10 @@ itertools = "0.8.2"
|
||||||
contracts = "0.6.3"
|
contracts = "0.6.3"
|
||||||
bimap = "0.6.3"
|
bimap = "0.6.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
utf8-chars = "3.0.2"
|
||||||
|
|
||||||
|
[dependencies.dsn_derive]
|
||||||
|
path = "macro/dsn_derive"
|
||||||
|
|
||||||
[dependencies.geo]
|
[dependencies.geo]
|
||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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<Attribute>, name: &str) -> bool {
|
||||||
|
attrs
|
||||||
|
.iter()
|
||||||
|
.find(|attr| attr.path().is_ident(name))
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attr_content(attrs: &Vec<Attribute>, name: &str) -> Option<String> {
|
||||||
|
attrs
|
||||||
|
.iter()
|
||||||
|
.find(|attr| attr.path().is_ident(name))
|
||||||
|
.and_then(|attr| Some(attr
|
||||||
|
.parse_args::<LitStr>()
|
||||||
|
.expect("string literal")
|
||||||
|
.value()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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<R: std::io::BufRead> ReadDsn<R> for #name {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>)
|
||||||
|
-> Result<Self, ParseError>
|
||||||
|
{
|
||||||
|
#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))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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<W: std::io::Write> WriteDsn<W> for #name {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>)
|
||||||
|
-> 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)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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<String, ParseError> {
|
||||||
|
if let Self::Start { name } = self {
|
||||||
|
Ok(name)
|
||||||
|
} else {
|
||||||
|
Err(ParseError::ExpectedStartOfList(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expect_leaf(self) -> Result<String, ParseError> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -32,7 +32,83 @@ pub struct DsnDesign {
|
||||||
|
|
||||||
impl DsnDesign {
|
impl DsnDesign {
|
||||||
pub fn load_from_file(filename: &str) -> Result<Self, LoadingError> {
|
pub fn load_from_file(filename: &str) -> Result<Self, LoadingError> {
|
||||||
|
|
||||||
|
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::<super::structure2::DsnFile>();
|
||||||
|
|
||||||
|
// 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::<String, NetOut>::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)?;
|
let contents = std::fs::read_to_string(filename)?;
|
||||||
|
|
||||||
Self::load_from_string(contents)
|
Self::load_from_string(contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,7 @@ mod de;
|
||||||
pub mod design;
|
pub mod design;
|
||||||
pub mod mesadata;
|
pub mod mesadata;
|
||||||
mod structure;
|
mod structure;
|
||||||
|
mod common;
|
||||||
|
mod structure2;
|
||||||
|
mod read;
|
||||||
|
mod write;
|
||||||
|
|
|
||||||
|
|
@ -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<R: std::io::BufRead>: Sized {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError>;
|
||||||
|
}
|
||||||
|
// custom impl feeding the read values back into the tokenizer
|
||||||
|
impl<R: std::io::BufRead> ReadDsn<R> for Parser {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
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<R: std::io::BufRead> ReadDsn<R> for String {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
Ok(tokenizer.consume_token()?.expect_leaf()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: std::io::BufRead> ReadDsn<R> for char {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
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<R: std::io::BufRead> ReadDsn<R> for bool {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
match tokenizer.consume_token()?.expect_leaf()?.as_str() {
|
||||||
|
"on" => Ok(true),
|
||||||
|
"off" => Ok(false),
|
||||||
|
_ => Err(ParseError::Expected("boolean")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: std::io::BufRead> ReadDsn<R> for i32 {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
Ok(tokenizer
|
||||||
|
.consume_token()?
|
||||||
|
.expect_leaf()?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ParseError::Expected("i32"))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: std::io::BufRead> ReadDsn<R> for u32 {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
Ok(tokenizer
|
||||||
|
.consume_token()?
|
||||||
|
.expect_leaf()?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ParseError::Expected("u32"))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<R: std::io::BufRead> ReadDsn<R> for usize {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
Ok(tokenizer
|
||||||
|
.consume_token()?
|
||||||
|
.expect_leaf()?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ParseError::Expected("usize"))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<R: std::io::BufRead> ReadDsn<R> for f32 {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
Ok(tokenizer
|
||||||
|
.consume_token()?
|
||||||
|
.expect_leaf()?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ParseError::Expected("f32"))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListTokenizer<R: std::io::BufRead> {
|
||||||
|
reader: R,
|
||||||
|
peeked_char: Option<char>,
|
||||||
|
cached_token: Option<ListToken>,
|
||||||
|
space_in_quoted: bool,
|
||||||
|
quote_char: Option<char>,
|
||||||
|
line: usize,
|
||||||
|
column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: std::io::BufRead> ListTokenizer<R> {
|
||||||
|
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<char, ParseError> {
|
||||||
|
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<char, ParseError> {
|
||||||
|
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<String, ParseError> {
|
||||||
|
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<String, ParseError> {
|
||||||
|
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<String, ParseError> {
|
||||||
|
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<ListToken, ParseError> {
|
||||||
|
// 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<ListToken, ParseError> {
|
||||||
|
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<T: ReadDsn<R>>(&mut self) -> Result<T, ParseError> {
|
||||||
|
T::read_dsn(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_named<T: ReadDsn<R>>(&mut self, name: &'static str) -> Result<T, ParseError> {
|
||||||
|
self.consume_token()?.expect_start(name)?;
|
||||||
|
let value = self.read_value::<T>()?;
|
||||||
|
self.consume_token()?.expect_end()?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_optional<T: ReadDsn<R>>(&mut self, name: &'static str) -> Result<Option<T>, ParseError> {
|
||||||
|
let token = self.consume_token()?;
|
||||||
|
if let ListToken::Start { name: ref actual_name } = token {
|
||||||
|
if actual_name == name {
|
||||||
|
let value = self.read_value::<T>()?;
|
||||||
|
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<T: ReadDsn<R>>(&mut self) -> Result<Vec<T>, ParseError> {
|
||||||
|
let mut array = Vec::<T>::new();
|
||||||
|
loop {
|
||||||
|
let token = self.consume_token()?;
|
||||||
|
if let ListToken::Leaf { .. } = token {
|
||||||
|
self.return_token(token);
|
||||||
|
array.push(self.read_value::<T>()?);
|
||||||
|
} else {
|
||||||
|
self.return_token(token);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_named_array<T: ReadDsn<R>>(&mut self, name: &'static str) -> Result<Vec<T>, ParseError> {
|
||||||
|
let mut array = Vec::<T>::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::<T>()?;
|
||||||
|
self.consume_token()?.expect_end()?;
|
||||||
|
array.push(value);
|
||||||
|
} else {
|
||||||
|
self.return_token(token);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.return_token(token);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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<NetOut>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct NetOut {
|
||||||
|
#[anon] pub name: String,
|
||||||
|
#[vec("wire")]
|
||||||
|
pub wire: Vec<Wire>,
|
||||||
|
#[vec("via")]
|
||||||
|
pub via: Vec<Via>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<char>,
|
||||||
|
pub space_in_quoted_tokens: Option<bool>,
|
||||||
|
pub host_cad: Option<String>,
|
||||||
|
pub host_version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Layer>,
|
||||||
|
pub boundary: Boundary,
|
||||||
|
#[vec("plane")]
|
||||||
|
pub planes: Vec<Plane>,
|
||||||
|
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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct Placement {
|
||||||
|
#[vec("component")]
|
||||||
|
pub components: Vec<Component>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct Component {
|
||||||
|
#[anon] pub name: String,
|
||||||
|
#[vec("place")]
|
||||||
|
pub places: Vec<Place>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct Library {
|
||||||
|
#[vec("image")]
|
||||||
|
pub images: Vec<Image>,
|
||||||
|
#[vec("padstack")]
|
||||||
|
pub padstacks: Vec<Padstack>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct Image {
|
||||||
|
#[anon] pub name: String,
|
||||||
|
#[vec("outline")]
|
||||||
|
pub outlines: Vec<Outline>,
|
||||||
|
#[vec("pin")]
|
||||||
|
pub pins: Vec<Pin>,
|
||||||
|
#[vec("keepout")]
|
||||||
|
pub keepouts: Vec<Keepout>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct Outline {
|
||||||
|
pub path: Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct Pin {
|
||||||
|
#[anon] pub name: String,
|
||||||
|
pub rotate: Option<f32>,
|
||||||
|
#[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<Shape>,
|
||||||
|
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<R: std::io::BufRead> ReadDsn<R> for Shape {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
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<W: std::io::Write> WriteDsn<W> for Shape {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> 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<Point>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct Network {
|
||||||
|
#[vec("net")]
|
||||||
|
pub nets: Vec<NetPinAssignments>,
|
||||||
|
#[vec("class")]
|
||||||
|
pub classes: Vec<Class>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct Class {
|
||||||
|
#[anon]
|
||||||
|
pub name: String,
|
||||||
|
#[anon_vec]
|
||||||
|
pub nets: Vec<String>,
|
||||||
|
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<Wire>,
|
||||||
|
#[vec("via")]
|
||||||
|
pub vias: Vec<Via>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<R: std::io::BufRead> ReadDsn<R> for Vec<Point> {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
let mut array = Vec::<Point>::new();
|
||||||
|
loop {
|
||||||
|
let token = tokenizer.consume_token()?;
|
||||||
|
if let ListToken::Leaf { value: ref x } = token {
|
||||||
|
let x = x.parse::<f32>().unwrap();
|
||||||
|
let y = tokenizer.read_value::<f32>()?;
|
||||||
|
array.push(Point { x, y });
|
||||||
|
} else {
|
||||||
|
tokenizer.return_token(token);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: std::io::BufRead> ReadDsn<R> for Option<Point> {
|
||||||
|
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||||
|
let token = tokenizer.consume_token()?;
|
||||||
|
if let ListToken::Leaf { value: ref x } = token {
|
||||||
|
let x = x.parse::<f32>().unwrap();
|
||||||
|
let y = tokenizer.read_value::<f32>()?;
|
||||||
|
Ok(Some(Point { x, y }))
|
||||||
|
} else {
|
||||||
|
tokenizer.return_token(token);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: std::io::Write> WriteDsn<W> for Vec<Point> {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> Result<(), std::io::Error> {
|
||||||
|
for elem in self {
|
||||||
|
writer.write_value(&elem.x)?;
|
||||||
|
writer.write_value(&elem.y)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: std::io::Write> WriteDsn<W> for Option<Point> {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> 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<Point>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct Path {
|
||||||
|
#[anon] pub layer: String,
|
||||||
|
#[anon] pub width: f32,
|
||||||
|
#[anon] pub coords: Vec<Point>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Clearance>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ReadDsn, WriteDsn, Debug)]
|
||||||
|
pub struct Clearance {
|
||||||
|
#[anon] pub value: f32,
|
||||||
|
pub r#type: Option<String>,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
use super::common::ListToken;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
pub trait WriteDsn<W: io::Write> {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> Result<(), io::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: io::Write> WriteDsn<W> for char {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> Result<(), io::Error> {
|
||||||
|
writer.write_leaf(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: io::Write> WriteDsn<W> for String {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> 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<W: io::Write> WriteDsn<W> for bool {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> Result<(), io::Error> {
|
||||||
|
writer.write_leaf(match self {
|
||||||
|
true => "on".to_string(),
|
||||||
|
false => "off".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: io::Write> WriteDsn<W> for i32 {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> Result<(), io::Error> {
|
||||||
|
writer.write_leaf(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: io::Write> WriteDsn<W> for u32 {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> Result<(), io::Error> {
|
||||||
|
writer.write_leaf(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: io::Write> WriteDsn<W> for usize {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> Result<(), io::Error> {
|
||||||
|
writer.write_leaf(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: io::Write> WriteDsn<W> for f32 {
|
||||||
|
fn write_dsn(&self, writer: &mut ListWriter<W>) -> Result<(), io::Error> {
|
||||||
|
writer.write_leaf(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListWriter<W: io::Write> {
|
||||||
|
writable: W,
|
||||||
|
indent_level: usize,
|
||||||
|
multiline_level: usize,
|
||||||
|
pub line_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: io::Write> ListWriter<W> {
|
||||||
|
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<T: WriteDsn<W>>(
|
||||||
|
&mut self,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<(), io::Error> {
|
||||||
|
value.write_dsn(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_named<T: WriteDsn<W>>(
|
||||||
|
&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<T: WriteDsn<W>>(
|
||||||
|
&mut self,
|
||||||
|
name: &'static str,
|
||||||
|
optional: &Option<T>,
|
||||||
|
)
|
||||||
|
-> Result<(), io::Error>
|
||||||
|
{
|
||||||
|
if let Some(value) = optional {
|
||||||
|
self.write_named(name, value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_array<T: WriteDsn<W>>(
|
||||||
|
&mut self,
|
||||||
|
array: &Vec<T>,
|
||||||
|
)
|
||||||
|
-> Result<(), io::Error>
|
||||||
|
{
|
||||||
|
for elem in array {
|
||||||
|
self.write_value(elem)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_named_array<T: WriteDsn<W>>(
|
||||||
|
&mut self,
|
||||||
|
name: &'static str,
|
||||||
|
array: &Vec<T>,
|
||||||
|
)
|
||||||
|
-> Result<(), io::Error>
|
||||||
|
{
|
||||||
|
for elem in array {
|
||||||
|
self.write_named(name, elem)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue