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