1mod base;
2mod components;
3mod source;
4mod simulate;
5mod measures;
6mod error;
7mod subckt;
8use std::path::Path;
9
10use nom::branch::alt;
11
12pub use base::*;
13pub use components::*;
14pub use source::*;
15pub use simulate::*;
16pub use measures::*;
17pub use error::*;
18pub use subckt::*;
19
20use crate::model::{Component, Instance, MeasureCommand, Model, SimCommand, Source, Spice, Subckt};
21use nom::{error::convert_error, Err};
22use nom::combinator::map;
23
24pub fn load_spice<P: AsRef<Path>>(path: P) -> Result<Spice, ParseError> {
25 let input = std::fs::read_to_string(path.as_ref())?;
26 read_spice(&input)
27}
28
29pub fn read_spice(full_input: &str) -> Result<Spice, ParseError> {
30 let mut spice = Spice::default();
31 let mut input = full_input;
32
33 while !input.trim_start().is_empty() {
34 input = skip_blank_or_comment_lines(input);
35 if input.is_empty() {
36 break;
37 }
38
39 match statement(input) {
40 Ok((rest, stmt)) => {
41 match stmt {
42 ParsedStatement::Component(c) => spice.components.push(c),
43 ParsedStatement::Source(s) => spice.sources.push(s),
44 ParsedStatement::SimCommand(s) => spice.simulation.push(s),
45 ParsedStatement::Measure(m) => spice.measures.push(m),
46 ParsedStatement::Instance(i) => spice.instances.push(i),
47 ParsedStatement::Subckt(s) => spice.subckts.push(s),
48 ParsedStatement::Model(m) => spice.model.push(m),
49 }
50 input = rest;
51 }
52 Err(Err::Failure(e)) => {
53 let first_error_input = e.errors.get(0).map(|(slice, _)| *slice).unwrap_or(input);
54 let err_text = convert_error(full_input, e);
55 let line_num = get_error_line(full_input, first_error_input);
56 return Err(ParseError::Parse(format!("Error at line {}:\n{}", line_num, err_text)));
57 }
58 Err(Err::Error(_)) => {
59 let line_num = get_error_line(full_input, input);
60 return Err(ParseError::Parse(format!(
61 "At line {}: Unknown statement: {}",
62 line_num,
63 preview_line(input)
64 )));
65 }
66 Err(Err::Incomplete(e)) => {
67 return Err(ParseError::Parse(format!("Incomplete: {:?}", e)));
68 }
69 }
70 }
71
72 Ok(spice)
73}
74
75fn get_error_line(full_input: &str, error_input: &str) -> usize {
76 let err_pos = error_input.as_ptr() as usize - full_input.as_ptr() as usize;
77 let line_num = full_input[..err_pos].chars().filter(|&c| c == '\n').count() + 1;
78 line_num
79}
80pub enum ParsedStatement {
81 Component(Component),
82 Source(Source),
83 SimCommand(SimCommand),
84 Measure(MeasureCommand),
85 Instance(Instance),
86 Subckt(Subckt),
87 Model(Model),
88}
89
90fn statement(input: &str) -> NomResult<ParsedStatement> {
91 alt((
92 map(component, ParsedStatement::Component),
93 map(source, ParsedStatement::Source),
94 map(sim_command, ParsedStatement::SimCommand),
95 map(measure_command, ParsedStatement::Measure),
96 map(instance, ParsedStatement::Instance),
97 map(subckt, ParsedStatement::Subckt),
98 map(model, ParsedStatement::Model),
99 ))(input)
100}
101
102pub fn skip_blank_or_comment_lines(mut input: &str) -> &str {
103 loop {
104 input = input.trim_start();
105 if input.is_empty() {
106 return "";
107 }
108
109 if let Ok((rest, _)) = comment(input) {
110 input = rest;
111 continue;
112 }
113
114 if input.starts_with('\n') {
115 input = &input[1..];
116 continue;
117 }
118
119 return input;
120 }
121}
122
123fn preview_line(input: &str) -> &str {
124 input.lines().next().unwrap_or(input).trim()
125}
126
127#[cfg(test)]
128mod tests {
129 use crate::model::{Component, MeasureCommand, SimCommand, SourceValue};
130 use reda_unit::u;
131
132 use super::*;
133
134 #[test]
135 fn test_read_spice_with_subckt_and_instance() {
136 let input = r#"
137 * Test circuit
138 .SUBCKT inv in out vdd gnd
139 M1 out in vdd vdd pmos L=1u W=2u
140 M2 out in gnd gnd nmos L=1u W=1u
141 .ENDS
142
143 X1 a b vdd gnd inv
144 Vdd vdd 0 DC 5
145 .TRAN 1n 10n
146 "#;
147
148 let spice = read_spice(input).map_err(|e| {
149 println!("{}", e);
150 }).unwrap();
151
152 assert_eq!(spice.subckts.len(), 1);
153 assert_eq!(spice.instances.len(), 1);
154 assert_eq!(spice.sources.len(), 1);
155 assert_eq!(spice.simulation.len(), 1);
156 }
157
158 #[test]
159 fn test_read_spice_basic() {
160 let input = r#"
161 * Simple resistor circuit
162 R1 in out 10k
163 C1 out 0 1u
164 V1 in 0 DC 5
165 .TRAN 1n 10n
166 .MEAS TRAN rise_time TRIG V(out) VAL=0.2 RISE=1 TARG V(out) VAL=0.8 RISE=1
167 "#;
168
169 let spice = read_spice(input).map_err(|e| {
170 println!("{}", e);
171 }).unwrap();
172
173 assert_eq!(spice.components.len(), 2); assert_eq!(spice.sources.len(), 1); assert_eq!(spice.simulation.len(), 1); assert_eq!(spice.measures.len(), 1); if let Component::R(r) = &spice.components[0] {
181 assert_eq!(r.name, "1");
182 assert_eq!(r.node_pos, "in");
183 assert_eq!(r.node_neg, "out");
184 assert_eq!(r.resistance, u!(10. kΩ));
185 } else {
186 panic!("Expected resistor");
187 }
188
189 assert_eq!(&spice.sources[0].name, "1");
191 assert_eq!(&spice.sources[0].node_pos, "in");
192 assert_eq!(&spice.sources[0].node_neg, "0");
193 if let SourceValue::DcVoltage(dc) = &spice.sources[0].value {
194 assert_eq!(*dc, u!(5. V));
195 } else {
196 panic!("Expected DC voltage source");
197 }
198
199 if let SimCommand::Tran(tran) = &spice.simulation[0] {
201 assert_eq!(tran.t_step, u!(1. ns));
202 assert_eq!(tran.t_stop, u!(10. ns));
203 } else {
204 panic!("Expected .TRAN command");
205 }
206
207 if let MeasureCommand::Rise(rise) = &spice.measures[0] {
209 assert_eq!(rise.name, "rise_time");
210 } else {
211 panic!("Expected .MEAS rise command");
212 }
213 }
214
215 #[test]
216 fn test_unknown_statement_error() {
217 let input = "
218 R1 in out 1k
219 .DC V1 0 10 1
220 ??? this line is invalid
221 .TRAN 1n 10n
222 ";
223 let result = read_spice(input);
224 assert!(matches!(result, Err(ParseError::Parse(_))));
225 println!("{:?}", result.unwrap_err());
226 }
227
228 #[test]
229 fn test_subckt_parse_failure() {
230 let input = "
231 .SUBCKT
232 R1 a b 1k
233 .ENDS
234 ";
235 let result = read_spice(input);
236 assert!(matches!(result, Err(ParseError::Parse(_))));
237 }
238
239 #[test]
240 fn test_read_spice_basic_components() {
241 let input = r#"
242 R1 1 0 1k
243 C1 1 0 1u
244 L1 1 0 10u
245 D1 1 0 Dmodel
246 Q1 3 2 0 NPN
247 M1 3 2 1 0 nmos L=0.18u W=1u
248 "#;
249
250 let spice = read_spice(input).unwrap();
251 assert_eq!(spice.components.len(), 6);
252 }
253
254 #[test]
255 fn test_read_spice_sources_dc() {
256 let input = r#"
257 V1 1 0 DC 5
258 I1 2 0 0.001
259 "#;
260
261 let spice = read_spice(input).unwrap();
262 assert_eq!(spice.sources.len(), 2);
263 }
264
265 #[test]
266 fn test_read_spice_sources_sin_pwl() {
267 let input = r#"
268 Vsin 1 0 SIN(0 5 1k)
269 Vpwl 2 0 PWL(0 0 1u 5 2u 0)
270 "#;
271
272 let spice = read_spice(input).unwrap();
273 assert_eq!(spice.sources.len(), 2);
274 }
275
276 #[test]
277 fn test_read_spice_sim_commands() {
278 let input = r#"
279 .DC Vin 0 5 0.1
280 .AC LIN 10 1 1k
281 .TRAN 1n 10n 0n 1n
282 "#;
283
284 let spice = read_spice(input).unwrap();
285 assert_eq!(spice.simulation.len(), 3);
286 }
287
288 #[test]
289 fn test_read_spice_measures() {
290 let input = r#"
291 .model NMOS1 NMOS (LEVEL=1 VTO=0.7 KP=20u LAMBDA=0.02)
292 .model PMOS1 PMOS (LEVEL=1 VTO=-0.7 KP=10u LAMBDA=0.02)
293 .MEAS TRAN t_rise TRIG V(1) VAL=0.2 RISE=1 TARG V(1) VAL=0.8 RISE=1
294 .MEAS TRAN avgval AVG V(1) FROM=1n TO=10n
295 .MEAS TRAN when FIND I(V1) WHEN V(2)=1.0
296 "#;
297
298 let spice = read_spice(input).unwrap();
299 assert_eq!(spice.measures.len(), 3);
300 }
301
302 #[test]
303 fn test_read_spice_subckt_and_instance() {
304 let input = r#"
305.SUBCKT inverter in out vdd gnd
306M1 out in vdd vdd pmos
307+ L=1u W=2u
308M2 out in gnd gnd nmos L=1u W=1u
309.ENDS
310
311Xinv a b vdd gnd inverter
312 "#;
313
314 let spice = read_spice(input).unwrap();
315 assert_eq!(spice.subckts.len(), 1);
316 assert_eq!(spice.instances.len(), 1);
317 assert_eq!(spice.subckts[0].ports, ["in", "out", "vdd", "gnd"]);
318 assert_eq!(spice.subckts[0].components.len(), 2);
319 }
320
321 #[test]
322 fn test_read_spice_line_continuation() {
323 let input = r#"
324V1 1 0
325+ DC 5
326 "#;
327
328 let spice = read_spice(input).unwrap();
329 assert_eq!(spice.sources.len(), 1);
330 }
331
332 #[test]
333 fn test_read_spice_comment_and_blank_lines() {
334 let input = r#"
335* This is a comment
336R1 1 0 1k
337
338* Another comment
339C1 1 0 1u
340 "#;
341
342 let spice = read_spice(input).unwrap();
343 assert_eq!(spice.components.len(), 2);
344 }
345
346 #[test]
347 fn test_read_spice_failure_invalid_line() {
348 let input = r#"
349R1 1 0 1k
350THIS_IS_INVALID
351 "#;
352
353 let res = read_spice(input);
354 assert!(res.is_err());
355 if let Err(ParseError::Parse(msg)) = res {
356 assert!(msg.contains("Unknown statement"));
357 } else {
358 panic!("Expected ParseError::Parse");
359 }
360 }
361}
362