netlist_db/parser/
mod.rs

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