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;
14pub mod utlis;
15
16pub mod _impl_display;
17mod builder;
18mod err;
19mod span;
20
21use alloc::borrow::Cow;
22use ast::ASTBuilder;
23use indexmap::IndexMap;
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: IndexMap<String, 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                let subckt = 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                (subckt.name.to_lowercase(), subckt)
178            }));
179            ast.instance
180                .extend(local_ast.instance.iter().map(|b| b.build(file)));
181            ast.model
182                .extend(local_ast.model.iter().map(|b| b.build(file)));
183            ast.param
184                .extend(local_ast.param.iter().map(|b| b.build(file)));
185            ast.option
186                .extend(local_ast.option.iter().map(|b| b.build(file)));
187            ast.general
188                .extend(local_ast.general.iter().map(|b| b.build(file)));
189            ast.data
190                .extend(local_ast.data.iter().map(|b| b.build(file)));
191            ast.init_condition
192                .extend(local_ast.init_condition.iter().map(|b| b.build(file)));
193            ast.nodeset
194                .extend(local_ast.nodeset.iter().map(|b| b.build(file)));
195            ast.unknwon
196                .extend(local_ast.unknwon.iter().map(|b| b.build(file)));
197            for e in &local_ast.errors {
198                e.report(has_err, file_id, file);
199            }
200        }
201        let file = &files.inner[parsed_id.0];
202        for seg in &self.segments {
203            match seg {
204                ast::Segment::Local(local_ast) => {
205                    build_local(
206                        local_ast,
207                        ast,
208                        has_err,
209                        file,
210                        file_id,
211                        parsed_id,
212                        files,
213                        parsed_id2idx,
214                        parsed_inner,
215                    );
216                }
217                ast::Segment::Include(ast_res) => {
218                    let ast_res = ast_res.get().unwrap();
219                    match ast_res {
220                        Ok(parsed_id) => {
221                            let (file_id, _ast) = &parsed_inner[parsed_id.0];
222                            _ast.build(
223                                ast,
224                                has_err,
225                                file_id,
226                                *parsed_id,
227                                files,
228                                parsed_id2idx,
229                                parsed_inner,
230                            );
231                        }
232                        Err(e) => {
233                            e.report(has_err, file_id, file);
234                        }
235                    }
236                }
237            }
238        }
239    }
240}
241
242impl Files {
243    #[inline]
244    pub fn build(&self, parsed: Parsed) -> (AST<'_>, bool) {
245        let mut ast = AST::default();
246        let mut has_err = false;
247        for top_id in parsed.top_ids {
248            let (file_id, _ast) = &parsed.inner[top_id.0];
249            _ast.build(
250                &mut ast,
251                &mut has_err,
252                file_id,
253                top_id,
254                self,
255                &parsed.id2idx,
256                &parsed.inner,
257            );
258        }
259        (ast, has_err)
260    }
261}