netlist_db/parser/
mod.rs

1mod cmds;
2mod data_mc;
3pub use data_mc::*;
4mod data_meas;
5pub use data_meas::*;
6mod data_sweep;
7pub use data_sweep::*;
8mod instance;
9mod manager;
10mod measure;
11mod utils;
12
13use alloc::sync::Arc;
14use indexmap::IndexMap;
15use manager::ParseManager;
16use nom::IResult;
17use regex::Regex;
18use std::{collections::HashMap, mem, path::PathBuf, sync::OnceLock};
19use utils::local_ast;
20
21use super::{
22    Files, Parsed,
23    err::{ParseError, ParseErrorInner},
24};
25use crate::{
26    ast::{ASTBuilder, LocalAST, Segment},
27    span::{EndReason, FileId, FileStorage, LocatedSpan, ParsedId, Pos, span},
28};
29
30const BEGIN_TITLE: &str = ".TITLE";
31
32#[inline]
33pub fn ast(
34    manager: Arc<ParseManager>,
35    loaded: IndexMap<FileId, Option<Pos>>,
36    work_dir: PathBuf,
37    mut i: LocatedSpan,
38) -> IResult<LocatedSpan, ASTBuilder> {
39    let mut ast = ASTBuilder::new();
40    loop {
41        let _ast;
42        let reason;
43        (i, (_ast, reason)) = local_ast(i, &loaded, &manager, &work_dir)?;
44        if !_ast.is_empty() {
45            ast.segments.push(Segment::Local(Box::new(_ast)));
46        }
47        match reason {
48            EndReason::Include { file_name, section } => {
49                let res = Arc::new(OnceLock::new());
50                ast.segments.push(Segment::Include(res.clone()));
51                let manager_clone = manager.clone();
52                let file_path = if file_name.is_absolute() {
53                    file_name.to_path_buf()
54                } else {
55                    work_dir.join(file_name)
56                };
57                let include_pos = Pos::new(i);
58                let mut loaded = loaded.clone();
59                if let Some((_, _include_pos)) = loaded.last_mut() {
60                    *_include_pos = include_pos
61                }
62                manager.spawn_parse(async move {
63                    include(manager_clone, loaded, file_path, section, include_pos, res).await;
64                });
65            }
66            EndReason::End => return Ok((i, ast)),
67        }
68    }
69}
70
71async fn _include(
72    manager: Arc<ParseManager>,
73    mut loaded: IndexMap<FileId, Option<Pos>>,
74    file_id: FileId,
75    include_pos: Option<Pos>,
76) -> Result<ParsedId, (FileId, ParseError)> {
77    let mut file_path = file_id.path().to_path_buf();
78    if let Some(idx) = loaded.get_index_of(&file_id) {
79        return Err((
80            file_id,
81            ParseErrorInner::CircularDefinition(loaded, idx).with(include_pos),
82        ));
83    } else {
84        loaded.insert(file_id.clone(), include_pos);
85    }
86    if let Some(parsed_id) = manager.file_storage.lock().await.existed(&file_id) {
87        return Ok(parsed_id);
88    }
89    let contents = match tokio::fs::read_to_string(&file_path).await {
90        Ok(contents) => contents,
91        Err(e) => return Err((file_id, ParseErrorInner::with(e.into(), include_pos))),
92    };
93    let (line_off_set, file_ctx) = if let FileId::Section { path: _, section } = file_id.clone() {
94        if let Some((file_ctx, line_off_set)) = match_lib(&contents, &section) {
95            (line_off_set, file_ctx)
96        } else {
97            return Err((
98                file_id,
99                ParseErrorInner::NoLibSection {
100                    path: file_path,
101                    section,
102                }
103                .with(include_pos),
104            ));
105        }
106    } else {
107        (0, contents)
108    };
109    let parsed_id = manager.file_storage.lock().await.new_file(file_id);
110    let i = span(&file_ctx, line_off_set);
111    // get dir
112    file_path.pop();
113    #[cfg(test)]
114    assert!(file_path.is_dir());
115
116    let res = match ast(manager.clone(), loaded, file_path, i) {
117        Ok((_, res)) => res,
118        Err(e) => error2ast(e.into()),
119    };
120    manager
121        .file_storage
122        .lock()
123        .await
124        .update_ctx(&parsed_id, file_ctx, res);
125    Ok(parsed_id)
126}
127#[inline]
128async fn include(
129    manager: Arc<ParseManager>,
130    loaded: IndexMap<FileId, Option<Pos>>,
131    file_path: PathBuf,
132    section: Option<String>,
133    include_pos: Option<Pos>,
134    res: Arc<OnceLock<Result<ParsedId, ParseError>>>,
135) {
136    let file_id = if let Some(section) = section {
137        FileId::Section {
138            path: file_path.clone(),
139            section,
140        }
141    } else {
142        FileId::Include {
143            path: file_path.clone(),
144        }
145    };
146    let _res = _include(manager, loaded, file_id, include_pos).await;
147    res.set(_res.map_err(|(_, e)| e)).unwrap();
148}
149
150/// When ParseError come form the included file,
151/// also return the ASTBuilder, to recover the error
152fn error2ast(err: ParseError) -> ASTBuilder {
153    let mut ast = ASTBuilder::new();
154    let mut local = LocalAST::default();
155    local.errors.push(err);
156    ast.segments.push(Segment::Local(Box::new(local)));
157    ast
158}
159
160fn error2parsed(file_id: FileId, err: ParseError) -> (Parsed, Files) {
161    let ast = error2ast(err);
162    let parsed_id = ParsedId(0);
163    let mut id2idx = HashMap::new();
164    id2idx.insert(file_id.clone(), parsed_id);
165    (
166        Parsed {
167            top_ids: vec![parsed_id],
168            id2idx,
169            inner: vec![(file_id, ast)],
170        },
171        Files {
172            inner: vec![String::new()],
173        },
174    )
175}
176
177pub async fn parse_top_multi<I: Iterator<Item = FileId>>(file_ids: I) -> (Parsed, Files) {
178    let (manager, done_rx) = ParseManager::new();
179    let handles: Vec<_> = file_ids
180        .into_iter()
181        .map(|file_id| {
182            crate::spawn({
183                let manager = manager.clone();
184                async move { _include(manager, IndexMap::with_capacity(1), file_id, None).await }
185            })
186        })
187        .collect();
188    let mut top_ids = Vec::with_capacity(handles.len());
189    for handle in handles {
190        match handle.await.unwrap() {
191            Ok(parsed_id) => top_ids.push(parsed_id),
192            Err((file_id, e)) => return error2parsed(file_id, e),
193        }
194    }
195    manager.wait(done_rx).await;
196    let mut guard = manager.file_storage.lock().await;
197    let file_storage: FileStorage<ASTBuilder> = mem::take(&mut *guard);
198    (
199        Parsed {
200            top_ids,
201            id2idx: file_storage.id2idx,
202            inner: file_storage.parsed,
203        },
204        Files {
205            inner: file_storage.file,
206        },
207    )
208}
209
210fn match_lib(text: &str, section: &str) -> Option<(String, u32)> {
211    let section_escaped = regex::escape(section);
212    let re = Regex::new(&format!(
213        r"(?ims)^\s*\.lib\s+{section_escaped}\b(.*?)^\s*\.endl(?:\s+{section_escaped}\b)?"
214    ))
215    .unwrap();
216    re.captures_iter(text).last().map(|caps| {
217        let start_offset = caps.get(0).unwrap().start();
218        let line_num_offset = (text[..start_offset].matches('\n').count()) as u32;
219        (caps[1].to_owned(), line_num_offset)
220    })
221}
222#[tokio::test]
223async fn test_top() {
224    #[cfg(not(feature = "tracing"))]
225    {
226        _ = simple_logger::SimpleLogger::new().init();
227    }
228    #[cfg(feature = "tracing")]
229    {
230        let subscriber = tracing_subscriber::FmtSubscriber::builder()
231            // .with_ansi(colored::control::SHOULD_COLORIZE.should_colorize())
232            .with_max_level(tracing::Level::DEBUG)
233            .with_target(false)
234            .with_file(true)
235            .with_line_number(true)
236            .with_timer(tracing_subscriber::fmt::time::ChronoLocal::new(
237                "%FT%T".to_owned(),
238            ))
239            .finish();
240        _ = tracing::subscriber::set_global_default(subscriber);
241    }
242    let (parsed, files) = parse_top_multi(
243        [FileId::Include {
244            path: PathBuf::from("tests/top.sp"),
245        }]
246        .into_iter(),
247    )
248    .await;
249    println!("{parsed:?}");
250    println!("{files:?}");
251}
252#[test]
253fn test_match_lib() {
254    let text = r#"
255        .LIB TT
256        * Some lines here
257        M1 d g s b NMOS
258        .MEAS ...
259        .ENDL TT
260
261        .lib tt
262        R1 in out 10k
263        .endl
264        .lib ttg
265        it is wrong
266        .endl"#;
267    assert_eq!(
268        Some(("\n        R1 in out 10k\n".to_owned(), 6)),
269        match_lib(text, "tt")
270    );
271}