1use crate::layer::LayerInfo;
28use smol_str::SmolStr;
29use std::collections::HashMap;
30
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct LayerMapEntry {
33 pub name: SmolStr,
34 pub purpose: SmolStr,
35 pub layer: u16,
36 pub datatype: u16,
37}
38
39#[derive(Default, Clone, Debug)]
40pub struct LayerMapping {
41 pub entries: Vec<LayerMapEntry>,
42 by_key: HashMap<(SmolStr, SmolStr), usize>,
43 by_gds: HashMap<(u16, u16), usize>,
44}
45
46impl LayerMapping {
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn insert(&mut self, entry: LayerMapEntry) {
52 let idx = self.entries.len();
53 self.by_key.insert((entry.name.clone(), entry.purpose.clone()), idx);
54 self.by_gds.insert((entry.layer, entry.datatype), idx);
55 self.entries.push(entry);
56 }
57
58 pub fn lookup_name(&self, name: &str, purpose: &str) -> Option<&LayerMapEntry> {
59 let key = (SmolStr::from(name), SmolStr::from(purpose));
60 self.by_key.get(&key).map(|&i| &self.entries[i])
61 }
62
63 pub fn lookup_gds(&self, layer: u16, datatype: u16) -> Option<&LayerMapEntry> {
64 self.by_gds.get(&(layer, datatype)).map(|&i| &self.entries[i])
65 }
66
67 pub fn to_layer_info(entry: &LayerMapEntry) -> LayerInfo {
71 LayerInfo::named(entry.name.clone(), entry.layer, entry.datatype)
72 }
73}
74
75#[derive(Debug, thiserror::Error)]
76pub enum LayerMapError {
77 #[error("layermap parse error on line {line}: {msg}")]
78 Parse { line: usize, msg: String },
79}
80
81pub fn parse_layermap(text: &str) -> Result<LayerMapping, LayerMapError> {
82 let mut map = LayerMapping::new();
83 for (line_no, line) in text.lines().enumerate() {
84 let line_no = line_no + 1;
85 let trimmed = line.trim();
86 if trimmed.is_empty() || trimmed.starts_with('#') || trimmed.starts_with("//") {
87 continue;
88 }
89 let parts: Vec<&str> = trimmed.split_whitespace().collect();
91 if parts.len() < 4 {
92 return Err(LayerMapError::Parse {
93 line: line_no,
94 msg: format!("expected 4 fields, got {}", parts.len()),
95 });
96 }
97 let name = SmolStr::from(parts[0]);
98 let purpose = SmolStr::from(parts[1]);
99 let layer = parts[2].parse::<u16>().map_err(|_| LayerMapError::Parse {
100 line: line_no,
101 msg: format!("invalid layer number `{}`", parts[2]),
102 })?;
103 let datatype = parts[3].parse::<u16>().map_err(|_| LayerMapError::Parse {
104 line: line_no,
105 msg: format!("invalid datatype `{}`", parts[3]),
106 })?;
107 map.insert(LayerMapEntry {
108 name,
109 purpose,
110 layer,
111 datatype,
112 });
113 }
114 Ok(map)
115}
116
117pub fn write_layermap(map: &LayerMapping) -> String {
118 use std::fmt::Write as _;
119 let mut out = String::new();
120 let _ = writeln!(out, "# name purpose layer datatype");
121 let name_w = map
123 .entries
124 .iter()
125 .map(|e| e.name.len())
126 .max()
127 .unwrap_or(0)
128 .max(4);
129 let purp_w = map
130 .entries
131 .iter()
132 .map(|e| e.purpose.len())
133 .max()
134 .unwrap_or(0)
135 .max(7);
136 for e in &map.entries {
137 let _ = writeln!(
138 out,
139 "{:<name_w$} {:<purp_w$} {:>3} {:>3}",
140 e.name.as_str(),
141 e.purpose.as_str(),
142 e.layer,
143 e.datatype,
144 name_w = name_w,
145 purp_w = purp_w,
146 );
147 }
148 out
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 const SAMPLE: &str = r#"
156# Foundry XYZ map
157METAL1 drawing 10 0
158METAL1 pin 10 1
159METAL1 label 10 2
160VIA12 drawing 68 0
161POLY drawing 7 0
162"#;
163
164 #[test]
165 fn parses_basic_map() {
166 let map = parse_layermap(SAMPLE).unwrap();
167 assert_eq!(map.entries.len(), 5);
168 let m1 = map.lookup_name("METAL1", "drawing").unwrap();
169 assert_eq!(m1.layer, 10);
170 assert_eq!(m1.datatype, 0);
171 }
172
173 #[test]
174 fn lookup_by_gds_pair() {
175 let map = parse_layermap(SAMPLE).unwrap();
176 let v = map.lookup_gds(68, 0).unwrap();
177 assert_eq!(v.name.as_str(), "VIA12");
178 }
179
180 #[test]
181 fn comments_and_blank_lines_skipped() {
182 let txt = "# header\n\nA drawing 1 0\n# end\n";
183 let map = parse_layermap(txt).unwrap();
184 assert_eq!(map.entries.len(), 1);
185 }
186
187 #[test]
188 fn malformed_rejected() {
189 let bad = "BAD line\n";
190 assert!(parse_layermap(bad).is_err());
191 let bad2 = "X drawing notanumber 0\n";
192 assert!(parse_layermap(bad2).is_err());
193 }
194
195 #[test]
196 fn round_trip_via_string() {
197 let map1 = parse_layermap(SAMPLE).unwrap();
198 let text = write_layermap(&map1);
199 let map2 = parse_layermap(&text).unwrap();
200 assert_eq!(map2.entries.len(), map1.entries.len());
201 for e1 in &map1.entries {
202 let e2 = map2.lookup_name(&e1.name, &e1.purpose).unwrap();
203 assert_eq!(e1.layer, e2.layer);
204 assert_eq!(e1.datatype, e2.datatype);
205 }
206 }
207
208 #[test]
209 fn entry_to_layer_info() {
210 let entry = LayerMapEntry {
211 name: "METAL1".into(),
212 purpose: "drawing".into(),
213 layer: 10,
214 datatype: 0,
215 };
216 let info = LayerMapping::to_layer_info(&entry);
217 assert_eq!(info.name.as_str(), "METAL1");
218 assert_eq!(info.layer, 10);
219 }
220}