reda_sp/parse/
subckt.rs

1use nom::{bytes::complete::tag_no_case, error::{context, VerboseError, VerboseErrorKind}, multi::many0, Err};
2
3use crate::model::{Instance, Subckt};
4use super::{comment, component, hws, identifier, node, NomResult, ToFailure};
5
6/// Parse a complete .SUBCKT ... .ENDS block from &str
7pub fn subckt<'a>(input: &'a str) -> NomResult<'a, Subckt> {
8    context("subckt", |mut input: &'a str| {
9        // Parse header line
10        let (i, _) = context("keyword", hws(tag_no_case(".SUBCKT")))(input)?;
11        let (i, (name, ports)) = context("declaration", hws(subckt_decl))(i).to_failure()?;
12        input = i;
13
14        let mut components = vec![];
15        let mut instances = vec![];
16
17        // line-by-line
18        loop {
19            input = input.trim_start();
20            if let Ok((rest, _)) = comment(input) {
21                input = rest;
22                continue;
23            }
24
25            // Check for .ENDS
26            if input.to_ascii_lowercase().starts_with(".ends") {
27                // consume this line and break
28                if let Some(pos) = input.find('\n') {
29                    input = &input[pos + 1..];
30                } else {
31                    input = "";
32                }
33                break;
34            }
35            
36            // Try component
37            match hws(component)(input) {
38                Ok((rest, c)) => {
39                    components.push(c);
40                    input = rest;
41                    continue;
42                }
43                Err(Err::Error(_)) => {
44                    // Try instance
45                    match hws(instance)(input) {
46                        Ok((rest, inst)) => {
47                            instances.push(inst);
48                            input = rest;
49                            continue;
50                        }
51                        Err(Err::Error(_)) => {
52                            return Err(Err::Failure(VerboseError {
53                                errors: [(input, VerboseErrorKind::Context("unknown line in subckt"))].into(),
54                            }));
55                        }
56                        Err(e @ Err::Failure(_)) | Err(e @ Err::Incomplete(_)) => return Err(e),
57                    }
58                }
59                Err(e @ Err::Failure(_)) | Err(e @ Err::Incomplete(_)) => return Err(e),
60            }
61        }
62
63        Ok((
64            input,
65            Subckt {
66                name,
67                ports,
68                components,
69                instances,
70            },
71        ))
72    })(input)
73}
74
75fn subckt_decl(input: &str) -> NomResult<(String, Vec<String>)> {
76    context("subckt_decl", |input| {
77        let (input, name) = context("name", hws(identifier))(input)?;
78        let (input, ports) = context("ports", many0(hws(node)))(input)?;
79        Ok((
80            input,
81            (
82                name.to_string(),
83                ports.iter().map(|s| s.to_string()).collect(),
84            ),
85        ))
86    })(input)
87}
88
89/// Xname node1 node2 ... subckt_name
90pub fn instance(input: &str) -> NomResult<Instance> {
91    context("instance", |input| {
92        let (input, name) = context("name", hws(identifier))(input)?;
93
94        if !name.starts_with('X') && !name.starts_with('x') {
95            return Err(Err::Error(VerboseError {
96                errors: [(input, VerboseErrorKind::Context("should begin with X"))].into(),
97            }));
98        }
99
100        let (input, args) = context("args", many0(hws(node)))(input)?;
101
102        if args.is_empty() {
103            return Err(Err::Failure(VerboseError {
104                errors: [(input, VerboseErrorKind::Context("missing subckt name"))].into(),
105            }));
106        }
107
108        let subckt_name = args.last().unwrap().to_string();
109        let pins = args[..args.len() - 1]
110            .iter()
111            .map(|s| s.to_string())
112            .collect();
113
114        Ok((
115            input,
116            Instance {
117                name: name.to_string(),
118                pins,
119                subckt_name,
120            },
121        ))
122    })(input)
123}
124
125#[allow(unused)]
126#[cfg(test)]
127mod test {
128    use super::*;
129    use nom::{Err, error::convert_error};
130
131    #[test]
132    fn test_parse_subckt_and_instance() {
133        let input = 
134        r#".SUBCKT inverter in out vdd gnd
135        M1 out in vdd vdd pmos L=1u W=2u
136        M2 out in gnd gnd nmos L=1u W=1u
137        .ENDS
138        Xinv a b vdd gnd inverter
139        "#;
140
141        let (rest, subckt) = subckt(input).unwrap();
142
143        assert_eq!(subckt.name, "inverter");
144        assert_eq!(subckt.ports, ["in", "out", "vdd", "gnd"]);
145        assert_eq!(subckt.components.len(), 2);
146
147        let (_, inst) = instance(rest.trim()).unwrap();
148        assert_eq!(inst.name, "Xinv");
149        assert_eq!(inst.pins, ["a", "b", "vdd", "gnd"]);
150        assert_eq!(inst.subckt_name, "inverter");
151    }
152
153    #[test]
154    fn test_instance_bad_prefix_error() {
155        let input = "Y1 in out myblk";
156        let res = instance(input);
157        assert!(matches!(res, Err(Err::Error(_))));
158    }
159
160    #[test]
161    fn test_subckt_unknown_line_failure() {
162        let input = r#".SUBCKT foo in out
163            ??? bad line
164            .ENDS
165        "#;
166
167        let res = subckt(input);
168        assert!(matches!(res, Err(Err::Failure(_))));
169    }
170}