netlist_db/parser/
mod.rs

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