netlist_db/
lib.rs

1#![expect(clippy::upper_case_acronyms)]
2extern crate alloc;
3
4mod _impl_display;
5mod builder;
6mod err;
7pub mod instance;
8pub mod parser;
9
10use alloc::borrow::Cow;
11use builder::{
12    Builder as _,
13    span::{FileId, ParsedId},
14};
15use err::{ParseError, ParseErrorInner};
16use std::collections::HashMap;
17
18#[derive(Debug)]
19pub struct Parsed {
20    pub top_id: ParsedId,
21    pub id2idx: HashMap<FileId, ParsedId>,
22    pub inner: Vec<(FileId, builder::AST)>,
23}
24#[derive(Debug)]
25pub struct Files {
26    pub inner: Vec<String>,
27}
28
29#[derive(Debug, Clone)]
30pub enum Value<'s> {
31    Num(f64),
32    Expr(Cow<'s, str>),
33}
34
35#[derive(Debug, Clone)]
36pub struct KeyValue<'s> {
37    pub k: Cow<'s, str>,
38    pub v: Value<'s>,
39}
40
41#[derive(Debug, Clone)]
42pub enum Token<'s> {
43    KV(KeyValue<'s>),
44    Value(Value<'s>),
45    V(Cow<'s, str>),
46    I(Cow<'s, str>),
47}
48
49/// ``` spice
50/// .subckt pulvt11ll_ckt d g s b w=1e-6 l=1e-6 sa='sar'
51/// ...
52/// .ends pulvt11ll_ckt
53/// ```
54/// Do NOT support `.include` / `.lib` in `.subckt`
55#[derive(Debug, Clone)]
56pub struct Subckt<'s> {
57    pub name: Cow<'s, str>,
58    /// subckt/model name is the last arg
59    pub ports: Vec<Cow<'s, str>>,
60    pub params: Vec<KeyValue<'s>>,
61    pub ast: AST<'s>,
62}
63
64#[derive(Debug, Clone)]
65pub struct General<'s> {
66    pub cmd: builder::GeneralCmd,
67    pub tokens: Vec<Token<'s>>,
68}
69
70#[derive(Debug, Clone)]
71pub struct Unknwon<'s> {
72    pub cmd: Cow<'s, str>,
73    pub tokens: Vec<Token<'s>>,
74}
75
76#[derive(Debug, Clone)]
77pub struct Model<'s> {
78    pub name: Cow<'s, str>,
79    pub model_type: ModelType<'s>,
80    pub params: Vec<KeyValue<'s>>,
81}
82
83#[derive(Debug, Clone)]
84pub struct Data<'s> {
85    pub name: Cow<'s, str>,
86    pub values: DataValues<'s>,
87}
88
89#[derive(Debug, Clone)]
90pub enum DataValues<'s> {
91    InlineExpr {
92        params: Vec<Cow<'s, str>>,
93        values: Vec<Value<'s>>,
94    },
95    InlineNum {
96        params: Vec<Cow<'s, str>>,
97        values: Vec<f64>,
98    },
99    /// https://eda-cpu1.eias.junzhuo.site/~junzhuo/hspice/index.htm#page/hspice_14/data.htm
100    /// Concatenated (series merging) data files to use.
101    MER(DataFiles<'s>),
102    /// Column-laminated (parallel merging) data files to use.
103    LAM(DataFiles<'s>),
104}
105#[derive(Debug, Clone)]
106pub struct DataFile<'s> {
107    pub file: Cow<'s, str>,
108    pub pname_col_num: Vec<PnameColNum<'s>>,
109}
110
111#[derive(Debug, Clone)]
112pub struct PnameColNum<'s> {
113    pub pname: Cow<'s, str>,
114    pub col_num: usize,
115}
116#[derive(Debug, Clone)]
117pub struct DataFiles<'s> {
118    pub files: Vec<DataFile<'s>>,
119    pub out: Option<Cow<'s, str>>,
120}
121#[cfg(feature = "py")]
122use polars::{error::PolarsError, frame::DataFrame, prelude::Column};
123pub struct DataValuesCsv<'s, 'a>(pub(crate) &'a DataValues<'s>);
124impl<'s> DataValues<'s> {
125    pub fn csv(&self) -> DataValuesCsv<'s, '_> {
126        DataValuesCsv(self)
127    }
128    #[cfg(feature = "py")]
129    pub fn dataframe(&self) -> Result<DataFrame, PolarsError> {
130        if let Self::InlineNum { params, values } = self {
131            let ncols = params.len();
132            if ncols == 0 {
133                return Err(PolarsError::ComputeError("Header is empty".into()));
134            }
135            if values.len() % ncols != 0 {
136                return Err(PolarsError::ComputeError(
137                    "Data length is not a multiple of the number of columns".into(),
138                ));
139            }
140            let nrows = values.len() / ncols;
141            let columns = params
142                .into_iter()
143                .enumerate()
144                .map(|(col_idx, name)| {
145                    Column::new(
146                        name.as_ref().into(),
147                        (0..nrows)
148                            .into_iter()
149                            .map(|row| values[row * ncols + col_idx])
150                            .collect::<Vec<f64>>(),
151                    )
152                })
153                .collect();
154            DataFrame::new(columns)
155        } else {
156            Err(PolarsError::ComputeError("Is not inline data".into()))
157        }
158    }
159}
160
161#[derive(Debug, Clone)]
162pub enum ModelType<'s> {
163    /// operational amplifier model
164    AMP,
165    /// capacitor model
166    C,
167    /// magnetic core model
168    CORE,
169    /// diode model
170    D,
171    /// inductor model or magnetic core mutual inductor model
172    L,
173    /// n-channel JFET model
174    NJF,
175    /// n-channel MOSFET model
176    NMOS,
177    /// npn BJT model
178    NPN,
179    /// optimization model
180    OPT,
181    /// p-channel JFET model
182    PJF,
183    /// p-channel MOSFET model
184    PMOS,
185    /// pnp BJT model
186    PNP,
187    /// resistor model
188    R,
189    /// lossy transmission line model (lumped)
190    U,
191    /// lossy transmission line model
192    W,
193    /// S-parameter
194    S,
195    Unknown(Cow<'s, str>),
196}
197
198#[derive(Debug, Clone, Default)]
199pub struct AST<'s> {
200    pub subckt: Vec<Subckt<'s>>,
201    pub instance: Vec<instance::Instance<'s>>,
202    pub model: Vec<Model<'s>>,
203    pub param: Vec<KeyValue<'s>>,
204    pub option: Vec<(Cow<'s, str>, Option<Value<'s>>)>,
205    /// transient initial conditions
206    /// https://eda-cpu1.eias.junzhuo.site/~junzhuo/hspice/index.htm#page/hspice_14/ic.htm
207    ///
208    /// `node, val, [subckt]`
209    pub init_condition: Vec<(Cow<'s, str>, Value<'s>, Option<Cow<'s, str>>)>,
210    /// Initializes specified nodal voltages for DC operating point analysis and corrects convergence problems in DC analysis.
211    /// https://eda-cpu1.eias.junzhuo.site/~junzhuo/hspice/index.htm#page/hspice_14/nodeset.htm
212    ///
213    /// `node, val, [subckt]`
214    pub nodeset: Vec<(Cow<'s, str>, Value<'s>, Option<Cow<'s, str>>)>,
215    pub general: Vec<General<'s>>,
216    pub data: Vec<Data<'s>>,
217    pub unknwon: Vec<Unknwon<'s>>,
218}
219
220impl builder::AST {
221    #[expect(clippy::too_many_arguments)]
222    fn build<'s>(
223        &self,
224        ast: &mut AST<'s>,
225        has_err: &mut bool,
226        file_id: &FileId,
227        parsed_id: ParsedId,
228        files: &'s Files,
229        parsed: &Parsed,
230    ) {
231        fn build_local<'s>(
232            local_ast: &builder::LocalAST,
233            ast: &mut AST<'s>,
234            has_err: &mut bool,
235            file: &'s str,
236            file_id: &FileId,
237            parsed_id: ParsedId,
238            files: &'s Files,
239            parsed: &Parsed,
240        ) {
241            fn build_subckt<'s>(
242                s: &builder::Subckt,
243                has_err: &mut bool,
244                file: &'s str,
245                file_id: &FileId,
246                parsed_id: ParsedId,
247                files: &'s Files,
248                parsed: &Parsed,
249            ) -> Subckt<'s> {
250                let mut ast = AST::default();
251                s.ast
252                    .build(&mut ast, has_err, file_id, parsed_id, files, parsed);
253                Subckt {
254                    name: s.name.build(file),
255                    ports: s.ports.build(file),
256                    params: s.params.build(file),
257                    ast,
258                }
259            }
260            ast.subckt.extend(
261                local_ast
262                    .subckt
263                    .iter()
264                    .map(|s| build_subckt(s, has_err, file, file_id, parsed_id, files, parsed)),
265            );
266            ast.instance
267                .extend(local_ast.instance.iter().map(|b| b.build(file)));
268            ast.model
269                .extend(local_ast.model.iter().map(|b| b.build(file)));
270            ast.param
271                .extend(local_ast.param.iter().map(|b| b.build(file)));
272            ast.option
273                .extend(local_ast.option.iter().map(|b| b.build(file)));
274            ast.general
275                .extend(local_ast.general.iter().map(|b| b.build(file)));
276            ast.data
277                .extend(local_ast.data.iter().map(|b| b.build(file)));
278            ast.init_condition
279                .extend(local_ast.init_condition.iter().map(|b| b.build(file)));
280            ast.nodeset
281                .extend(local_ast.nodeset.iter().map(|b| b.build(file)));
282            ast.unknwon
283                .extend(local_ast.unknwon.iter().map(|b| b.build(file)));
284            for e in &local_ast.errors {
285                e.report(has_err, file_id, file);
286            }
287        }
288        let file = &files.inner[parsed_id.0];
289        for seg in &self.segments {
290            match seg {
291                builder::Segment::Local(local_ast) => {
292                    build_local(
293                        local_ast, ast, has_err, file, file_id, parsed_id, files, parsed,
294                    );
295                }
296                builder::Segment::Include(ast_res) => {
297                    let ast_res = ast_res.get().unwrap();
298                    match ast_res {
299                        Ok(parsed_id) => {
300                            let (file_id, _ast) = &parsed.inner[parsed_id.0];
301                            _ast.build(ast, has_err, file_id, *parsed_id, files, parsed);
302                        }
303                        Err(e) => {
304                            e.report(has_err, file_id, file);
305                        }
306                    }
307                }
308            }
309        }
310    }
311}
312
313impl Files {
314    #[inline]
315    pub fn build(&self, parsed: Parsed) -> (AST<'_>, bool) {
316        let mut ast = AST::default();
317        let mut has_err = false;
318        let (file_id, _ast) = &parsed.inner[parsed.top_id.0];
319        _ast.build(
320            &mut ast,
321            &mut has_err,
322            file_id,
323            parsed.top_id,
324            self,
325            &parsed,
326        );
327        (ast, has_err)
328    }
329}