netlist_db/
lib.rs

1#[cfg(feature = "py")]
2mod py;
3#[expect(unused_imports)]
4#[cfg(not(feature = "tracing"))]
5use log::{debug, error, info, trace, warn};
6#[expect(unused_imports)]
7#[cfg(feature = "tracing")]
8use tracing::{debug, error, info, trace, warn};
9
10extern crate alloc;
11pub mod ast;
12pub mod instance;
13pub mod parser;
14
15pub mod _impl_display;
16mod _impl_hash;
17mod builder;
18mod err;
19mod span;
20
21use alloc::borrow::Cow;
22use ast::ASTBuilder;
23use indexmap::IndexSet;
24pub use span::{FileId, ParsedId};
25use std::collections::HashMap;
26
27#[derive(Debug)]
28pub struct Parsed {
29    pub top_ids: Vec<span::ParsedId>,
30    pub id2idx: HashMap<span::FileId, span::ParsedId>,
31    pub inner: Vec<(span::FileId, ast::ASTBuilder)>,
32}
33#[derive(Debug)]
34pub struct Files {
35    pub inner: Vec<String>,
36}
37
38/// ``` spice
39/// .subckt pulvt11ll_ckt d g s b w=1e-6 l=1e-6 sa='sar'
40/// ...
41/// .ends pulvt11ll_ckt
42/// ```
43/// Do NOT support `.include` / `.lib` in `.subckt`
44#[derive(Debug, Clone)]
45pub struct Subckt<'s> {
46    pub name: Cow<'s, str>,
47    /// subckt/model name is the last arg
48    pub ports: Vec<Cow<'s, str>>,
49    pub params: Vec<ast::KeyValue<'s>>,
50    pub ast: AST<'s>,
51}
52
53#[derive(Debug, Clone, Default)]
54pub struct AST<'s> {
55    pub subckt: IndexSet<Subckt<'s>>,
56    pub instance: Vec<instance::Instance<'s>>,
57    pub model: Vec<ast::Model<'s>>,
58    pub param: Vec<ast::KeyValue<'s>>,
59    pub option: Vec<(Cow<'s, str>, Option<ast::Value<'s>>)>,
60    /// transient initial conditions
61    /// https://eda-cpu1.eias.junzhuo.site/~junzhuo/hspice/index.htm#page/hspice_14/ic.htm
62    ///
63    /// `node, val, [subckt]`
64    pub init_condition: Vec<(Cow<'s, str>, ast::Value<'s>, Option<Cow<'s, str>>)>,
65    /// Initializes specified nodal voltages for DC operating point analysis and corrects convergence problems in DC analysis.
66    /// https://eda-cpu1.eias.junzhuo.site/~junzhuo/hspice/index.htm#page/hspice_14/nodeset.htm
67    ///
68    /// `node, val, [subckt]`
69    pub nodeset: Vec<(Cow<'s, str>, ast::Value<'s>, Option<Cow<'s, str>>)>,
70    pub general: Vec<ast::General<'s>>,
71    pub data: Vec<ast::Data<'s>>,
72    pub unknwon: Vec<ast::Unknwon<'s>>,
73}
74
75// #[cfg(feature = "py")]
76// use polars::{error::PolarsError, frame::DataFrame, prelude::Column};
77// pub struct DataValuesCsv<'s, 'a>(pub(crate) &'a ast::DataValues<'s>);
78// impl<'s> ast::DataValues<'s> {
79//     pub fn csv(&self) -> DataValuesCsv<'s, '_> {
80//         DataValuesCsv(self)
81//     }
82//     #[cfg(feature = "py")]
83//     pub fn dataframe(&self) -> Result<DataFrame, PolarsError> {
84//         if let Self::InlineNum { params, values } = self {
85//             let ncols = params.len();
86//             if ncols == 0 {
87//                 return Err(PolarsError::ComputeError("Header is empty".into()));
88//             }
89//             if values.len() % ncols != 0 {
90//                 return Err(PolarsError::ComputeError(
91//                     "Data length is not a multiple of the number of columns".into(),
92//                 ));
93//             }
94//             let nrows = values.len() / ncols;
95//             let columns = params
96//                 .into_iter()
97//                 .enumerate()
98//                 .map(|(col_idx, name)| {
99//                     Column::new(
100//                         name.as_ref().into(),
101//                         (0..nrows)
102//                             .into_iter()
103//                             .map(|row| values[row * ncols + col_idx])
104//                             .collect::<Vec<f64>>(),
105//                     )
106//                 })
107//                 .collect();
108//             DataFrame::new(columns)
109//         } else {
110//             Err(PolarsError::ComputeError("Is not inline data".into()))
111//         }
112//     }
113// }
114
115impl ast::ASTBuilder {
116    #[expect(clippy::too_many_arguments)]
117    fn build<'s>(
118        &self,
119        ast: &mut AST<'s>,
120        has_err: &mut bool,
121        file_id: &span::FileId,
122        parsed_id: span::ParsedId,
123        files: &'s Files,
124        parsed_id2idx: &HashMap<FileId, ParsedId>,
125        parsed_inner: &Vec<(FileId, ASTBuilder)>,
126    ) {
127        use builder::Builder as _;
128        fn build_local<'s>(
129            local_ast: &ast::LocalAST,
130            ast: &mut AST<'s>,
131            has_err: &mut bool,
132            file: &'s str,
133            file_id: &span::FileId,
134            parsed_id: span::ParsedId,
135            files: &'s Files,
136            parsed_id2idx: &HashMap<FileId, ParsedId>,
137            parsed_inner: &Vec<(FileId, ASTBuilder)>,
138        ) {
139            fn build_subckt<'s>(
140                s: &ast::SubcktBuilder,
141                has_err: &mut bool,
142                file: &'s str,
143                file_id: &span::FileId,
144                parsed_id: span::ParsedId,
145                files: &'s Files,
146                parsed_id2idx: &HashMap<FileId, ParsedId>,
147                parsed_inner: &Vec<(FileId, ASTBuilder)>,
148            ) -> Subckt<'s> {
149                let mut ast = AST::default();
150                s.ast.build(
151                    &mut ast,
152                    has_err,
153                    file_id,
154                    parsed_id,
155                    files,
156                    parsed_id2idx,
157                    parsed_inner,
158                );
159                Subckt {
160                    name: s.name.build(file),
161                    ports: s.ports.build(file),
162                    params: s.params.build(file),
163                    ast,
164                }
165            }
166            ast.subckt.extend(local_ast.subckt.iter().map(|s| {
167                build_subckt(
168                    s,
169                    has_err,
170                    file,
171                    file_id,
172                    parsed_id,
173                    files,
174                    parsed_id2idx,
175                    parsed_inner,
176                )
177            }));
178            ast.instance
179                .extend(local_ast.instance.iter().map(|b| b.build(file)));
180            ast.model
181                .extend(local_ast.model.iter().map(|b| b.build(file)));
182            ast.param
183                .extend(local_ast.param.iter().map(|b| b.build(file)));
184            ast.option
185                .extend(local_ast.option.iter().map(|b| b.build(file)));
186            ast.general
187                .extend(local_ast.general.iter().map(|b| b.build(file)));
188            ast.data
189                .extend(local_ast.data.iter().map(|b| b.build(file)));
190            ast.init_condition
191                .extend(local_ast.init_condition.iter().map(|b| b.build(file)));
192            ast.nodeset
193                .extend(local_ast.nodeset.iter().map(|b| b.build(file)));
194            ast.unknwon
195                .extend(local_ast.unknwon.iter().map(|b| b.build(file)));
196            for e in &local_ast.errors {
197                e.report(has_err, file_id, file);
198            }
199        }
200        let file = &files.inner[parsed_id.0];
201        for seg in &self.segments {
202            match seg {
203                ast::Segment::Local(local_ast) => {
204                    build_local(
205                        local_ast,
206                        ast,
207                        has_err,
208                        file,
209                        file_id,
210                        parsed_id,
211                        files,
212                        parsed_id2idx,
213                        parsed_inner,
214                    );
215                }
216                ast::Segment::Include(ast_res) => {
217                    let ast_res = ast_res.get().unwrap();
218                    match ast_res {
219                        Ok(parsed_id) => {
220                            let (file_id, _ast) = &parsed_inner[parsed_id.0];
221                            _ast.build(
222                                ast,
223                                has_err,
224                                file_id,
225                                *parsed_id,
226                                files,
227                                parsed_id2idx,
228                                parsed_inner,
229                            );
230                        }
231                        Err(e) => {
232                            e.report(has_err, file_id, file);
233                        }
234                    }
235                }
236            }
237        }
238    }
239}
240
241impl Files {
242    #[inline]
243    pub fn build(&self, parsed: Parsed) -> (AST<'_>, bool) {
244        let mut ast = AST::default();
245        let mut has_err = false;
246        for top_id in parsed.top_ids {
247            let (file_id, _ast) = &parsed.inner[top_id.0];
248            _ast.build(
249                &mut ast,
250                &mut has_err,
251                file_id,
252                top_id,
253                self,
254                &parsed.id2idx,
255                &parsed.inner,
256            );
257        }
258        (ast, has_err)
259    }
260}