Skip to content

Commit

Permalink
Cherrypick altium work from the ecad-gui branch
Browse files Browse the repository at this point in the history
  • Loading branch information
tgross35 committed Sep 19, 2023
1 parent 55a969f commit a98d426
Show file tree
Hide file tree
Showing 19 changed files with 876 additions and 206 deletions.
211 changes: 158 additions & 53 deletions altium-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::BTreeMap;

use convert_case::{Case, Casing};
use proc_macro::TokenStream;
use proc_macro2::{Ident, Literal, Span, TokenStream as TokenStream2, TokenTree};
use proc_macro2::{Delimiter, Ident, Literal, Span, TokenStream as TokenStream2, TokenTree};
use quote::{quote, ToTokens};
use syn::{parse2, Attribute, Data, DeriveInput, Meta, Type};

Expand Down Expand Up @@ -45,13 +45,21 @@ fn inner(tokens: TokenStream2) -> syn::Result<TokenStream2> {
panic!("record id should be a literal");
};

// Handle cases where we want to box the struct
let use_box = match struct_attr_map.remove("use_box") {
Some(TokenTree::Ident(val)) if val == "true" => true,
Some(TokenTree::Ident(val)) if val == "false" => true,
Some(v) => panic!("Expected ident but got {v:?}"),
None => false,
};

// Handle cases where our struct doesn't have the same name as the enum variant
let record_variant = match struct_attr_map.remove("record_variant") {
Some(TokenTree::Ident(val)) => val,
Some(v) => panic!("Expected ident but got {v:?}"),
None => name.clone(),
};

error_if_map_not_empty(&struct_attr_map);

let mut match_stmts: Vec<TokenStream2> = Vec::new();
Expand All @@ -72,7 +80,11 @@ fn inner(tokens: TokenStream2) -> syn::Result<TokenStream2> {
.remove("count")
.expect("missing 'count' attribute");

process_array(&name, &field_name, count_ident, &mut match_stmts);
let arr_map = field_attr_map
.remove("map")
.expect("missing 'map' attribute");

process_array(&name, &field_name, count_ident, arr_map, &mut match_stmts);
error_if_map_not_empty(&field_attr_map);
continue;
} else if arr_val_str != "false" {
Expand Down Expand Up @@ -129,7 +141,9 @@ fn inner(tokens: TokenStream2) -> syn::Result<TokenStream2> {
let def_flag = quote! { let mut #flag_ident: bool = false; };
let check_flag = quote! {
if #flag_ident {
::log::debug!("skipping {} after finding utf8", #field_name_str);
::log::debug!(concat!(
"skipping ", #field_name_str, " after finding utf8 version"
));
continue;
}
};
Expand Down Expand Up @@ -164,9 +178,9 @@ fn inner(tokens: TokenStream2) -> syn::Result<TokenStream2> {
}

let ret_val = if use_box {
quote! { Ok(SchRecord::#name(Box::new(ret))) }
quote! { Ok(SchRecord::#record_variant(Box::new(ret))) }
} else {
quote! { Ok(SchRecord::#name(ret)) }
quote! { Ok(SchRecord::#record_variant(ret)) }
};

let ret = quote! {
Expand Down Expand Up @@ -198,7 +212,7 @@ fn inner(tokens: TokenStream2) -> syn::Result<TokenStream2> {

/// Next type of token we are expecting
#[derive(Clone, Debug, PartialEq)]
enum AttrState {
enum AttrParseState {
Key,
/// Contains the last key we had
Eq(String),
Expand All @@ -218,42 +232,107 @@ fn parse_attrs(attrs: Vec<Attribute>) -> Option<BTreeMap<String, TokenTree>> {
panic!("invalid usage; use `#[from_record(...=..., ...)]`");
};

let mut state = AttrState::Key;
let mut state = AttrParseState::Key;
let mut map = BTreeMap::new();

for token in list.tokens {
match state {
AttrState::Key => {
AttrParseState::Key => {
let TokenTree::Ident(idtoken) = token else {
panic!("expected an identifier at {token}");
};
state = AttrState::Eq(idtoken.to_string());
state = AttrParseState::Eq(idtoken.to_string());
}
AttrState::Eq(key) => {
match token {
TokenTree::Punct(v) if v.as_char() == '=' => (),
_ => panic!("expected `=` at {token}"),
AttrParseState::Eq(key) => {
if !matches!(&token, TokenTree::Punct(v) if v.as_char() == '=') {
panic!("expected `=` at {token}");
}

state = AttrState::Val(key);
state = AttrParseState::Val(key);
}
AttrState::Val(key) => {
AttrParseState::Val(key) => {
map.insert(key, token);
state = AttrState::Comma;
state = AttrParseState::Comma;
}
AttrState::Comma => {
match token {
TokenTree::Punct(v) if v.as_char() == ',' => (),
_ => panic!("expected `,` at {token}"),
};
state = AttrState::Key;
AttrParseState::Comma => {
if !matches!(&token, TokenTree::Punct(v) if v.as_char() == ',') {
panic!("expected `,` at {token}");
}
state = AttrParseState::Key;
}
}
}

Some(map)
}

/// Next type of token we are expecting
#[derive(Clone, Debug, PartialEq)]
enum MapParseState {
Key,
/// Contains the last key we had
Dash(Ident),
Gt(Ident),
Val(Ident),
Comma,
}

/// Parse a `(X -> x, Y -> y)` map that tells us how to set members based on
/// found items in an array.
///
/// E.g. with the above, `X1` will set `record[1].x`
fn parse_map(map: TokenTree) -> Vec<(Ident, Ident)> {
let mut ret = Vec::new();

let TokenTree::Group(group) = map else {
panic!("expected group but got {map:?}")
};

if group.delimiter() != Delimiter::Parenthesis {
panic!("expected parenthese but got {:?}", group.delimiter());
};

let mut state = MapParseState::Key;

for token in group.stream() {
match state {
MapParseState::Key => {
let TokenTree::Ident(idtoken) = token else {
panic!("expected an identifier at {token}");
};
state = MapParseState::Dash(idtoken);
}
MapParseState::Dash(key) => {
if !matches!(&token, TokenTree::Punct(v) if v.as_char() == '-') {
panic!("expected `->` at {token}");
}
state = MapParseState::Gt(key);
}
MapParseState::Gt(key) => {
if !matches!(&token, TokenTree::Punct(v) if v.as_char() == '>') {
panic!("expected `->` at {token}");
}
state = MapParseState::Val(key);
}
MapParseState::Val(key) => {
let TokenTree::Ident(ident) = token else {
panic!("expcected ident but got {token}");
};
ret.push((key, ident));
state = MapParseState::Comma;
}
MapParseState::Comma => {
if !matches!(&token, TokenTree::Punct(v) if v.as_char() == ',') {
panic!("expected `,` at {token}");
}

state = MapParseState::Key;
}
}
}

ret
}

fn error_if_map_not_empty(map: &BTreeMap<String, TokenTree>) {
assert!(map.is_empty(), "unexpected pairs {map:?}");
}
Expand All @@ -263,11 +342,13 @@ fn process_array(
name: &Ident,
field_name: &Ident,
count_ident_tt: TokenTree,
arr_map_tt: TokenTree,
match_stmts: &mut Vec<TokenStream2>,
) {
let TokenTree::Literal(match_pat) = count_ident_tt else {
panic!("expected a literal for `count`");
};
let arr_map = parse_map(arr_map_tt);

let field_name_str = field_name.to_string();
let match_pat_str = match_pat.to_string();
Expand All @@ -279,45 +360,69 @@ fn process_array(
stringify!(#name), "` (via proc macro array)"
))?;

ret.#field_name = vec![crate::common::Location::default(); count];
ret.#field_name = vec![Default::default(); count].into();
},
};

// Set an X value if given
xstr if crate::common::is_number_pattern(xstr, b'X') => {
let idx: usize = xstr.strip_prefix(b"X").unwrap()
.parse_as_utf8()
.or_context(|| format!(
"while extracting `{}` (`{}`) for `{}` (via proc macro array)",
String::from_utf8_lossy(xstr), #field_name_str, stringify!(#name)
))?;
match_stmts.push(count_match);

let x = val.parse_as_utf8().or_context(|| format!(
"while extracting `{}` (`{}`) for `{}` (via proc macro array)",
String::from_utf8_lossy(xstr), #field_name_str, stringify!(#name)
))?;
for (match_pfx, assign_value) in arr_map {
let match_pfx_bstr = Literal::byte_string(match_pfx.to_string().as_bytes());

ret.#field_name[idx - 1].x = x;
},
let item_match = quote! {
match_val if crate::common::is_number_pattern(match_val, #match_pfx_bstr) => {
let idx: usize = match_val.strip_prefix(#match_pfx_bstr).unwrap()
.parse_as_utf8()
.or_context(|| format!(
"while extracting `{}` (`{}`) for `{}` (via proc macro array)",
String::from_utf8_lossy(match_val), #field_name_str, stringify!(#name)
))?;

// Set a Y value if given
ystr if crate::common::is_number_pattern(ystr, b'Y') => {
let idx: usize = ystr.strip_prefix(b"Y").unwrap()
.parse_as_utf8()
.or_context(|| format!(
let parsed_val = val.parse_as_utf8().or_context(|| format!(
"while extracting `{}` (`{}`) for `{}` (via proc macro array)",
String::from_utf8_lossy(ystr), #field_name_str, stringify!(#name)
String::from_utf8_lossy(match_val), #field_name_str, stringify!(#name)
))?;

let y = val.parse_as_utf8().or_context(|| format!(
"while extracting `{}` (`{}`) for `{}` (via proc macro array)",
String::from_utf8_lossy(ystr), #field_name_str, stringify!(#name)
))?;

ret.#field_name[idx - 1].y = y;
},
};
ret.#field_name[idx - 1].#assign_value = parsed_val;
},
};
match_stmts.push(item_match);
}

match_stmts.push(count_match);
// // Set an X value if given
// xstr if crate::common::is_number_pattern(xstr, b'X') => {
// let idx: usize = xstr.strip_prefix(b"X").unwrap()
// .parse_as_utf8()
// .or_context(|| format!(
// "while extracting `{}` (`{}`) for `{}` (via proc macro array)",
// String::from_utf8_lossy(xstr), #field_name_str, stringify!(#name)
// ))?;

// let x = val.parse_as_utf8().or_context(|| format!(
// "while extracting `{}` (`{}`) for `{}` (via proc macro array)",
// String::from_utf8_lossy(xstr), #field_name_str, stringify!(#name)
// ))?;

// ret.#field_name[idx - 1].x = x;
// },

// // Set a Y value if given
// ystr if crate::common::is_number_pattern(ystr, b'Y') => {
// let idx: usize = ystr.strip_prefix(b"Y").unwrap()
// .parse_as_utf8()
// .or_context(|| format!(
// "while extracting `{}` (`{}`) for `{}` (via proc macro array)",
// String::from_utf8_lossy(ystr), #field_name_str, stringify!(#name)
// ))?;

// let y = val.parse_as_utf8().or_context(|| format!(
// "while extracting `{}` (`{}`) for `{}` (via proc macro array)",
// String::from_utf8_lossy(ystr), #field_name_str, stringify!(#name)
// ))?;

// ret.#field_name[idx - 1].y = y;
// },
// };
}

/// From a field in our struct, create the name we should match by
Expand Down
7 changes: 6 additions & 1 deletion altium/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ description = "A library for processing Altium file types"
[dependencies]
altium-macros = { path = "../altium-macros", version = "0.1.0" }
base64 = "0.21.2"
cfb = "0.8.1"
# Use custom rev so we get debug outputs
cfb = { git = "https://github.com/mdsteele/rust-cfb.git", rev = "5c5279d6" }
# cfb = "0.8.1"
flate2 = "1.0.26"
# image = "0.24.6"
image = { version = "0.24.6", default-features = false, features = ["png", "bmp", "jpeg"] }
Expand All @@ -25,6 +27,9 @@ svg = "0.13.1"
uuid = { version = "1.4.1", features = ["v1", "v4", "fast-rng"]}
xml-rs = "0.8.16"

[dev-dependencies]
env_logger = "0.10.0"

[package.metadata.release]
shared-version = true

Expand Down
27 changes: 24 additions & 3 deletions altium/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,28 @@ impl Location {
}
}

impl From<(i32, i32)> for Location {
fn from(value: (i32, i32)) -> Self {
Self {
x: value.0,
y: value.1,
}
}
}

#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum Visibility {
Hidden,
#[default]
Visible,
}

impl FromUtf8<'_> for Visibility {
fn from_utf8(buf: &[u8]) -> Result<Self, ErrorKind> {
todo!("{}", String::from_utf8_lossy(buf))
}
}

/// A unique ID
///
// TODO: figure out what file types use this exact format
Expand Down Expand Up @@ -202,6 +217,12 @@ impl Rotation {
}
}

impl FromUtf8<'_> for Rotation {
fn from_utf8(buf: &[u8]) -> Result<Self, ErrorKind> {
todo!("{}", String::from_utf8_lossy(buf))
}
}

#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum ReadOnlyState {
#[default]
Expand All @@ -216,7 +237,7 @@ impl TryFrom<u8> for ReadOnlyState {
let res = match value {
x if x == Self::ReadWrite as u8 => Self::ReadWrite,
x if x == Self::ReadOnly as u8 => Self::ReadOnly,
_ => return Err(ErrorKind::SheetStyle(value)),
_ => return Err(ErrorKind::ReadOnlyState(value)),
};

Ok(res)
Expand Down Expand Up @@ -249,9 +270,9 @@ pub enum PosVert {
}

/// Verify a number pattern matches, e.g. `X100`
pub fn is_number_pattern(s: &[u8], prefix: u8) -> bool {
pub fn is_number_pattern(s: &[u8], prefix: &[u8]) -> bool {
if let Some(stripped) = s
.strip_prefix(&[prefix])
.strip_prefix(prefix)
.map(|s| s.strip_prefix(&[b'-']).unwrap_or(s))
{
if stripped.iter().all(u8::is_ascii_digit) {
Expand Down
Loading

0 comments on commit a98d426

Please sign in to comment.