netlist_db/parser/
mod.rs

1use std::{
2    collections::HashMap,
3    mem,
4    path::PathBuf,
5    sync::{Arc, OnceLock},
6};
7
8use indexmap::IndexMap;
9use manager::ParseManager;
10use nom::IResult;
11use regex::Regex;
12use utils::local_ast;
13
14use super::{
15    Files, ParseError, ParseErrorInner, Parsed,
16    builder::{
17        AST, LocalAST, Segment,
18        span::{EndReason, FileId, FileStorage, LocatedSpan, ParsedId, Pos, span},
19    },
20};
21
22mod cmds;
23mod instance;
24mod manager;
25mod utils;
26
27#[inline]
28pub fn ast(
29    manager: Arc<ParseManager>,
30    loaded: IndexMap<FileId, Option<Pos>>,
31    work_dir: PathBuf,
32    mut i: LocatedSpan,
33) -> IResult<LocatedSpan, AST> {
34    let mut ast = AST::new();
35    loop {
36        log::trace!("\n{:?}", i.fragment());
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 pos = Pos::new(i);
54                let mut loaded = loaded.clone();
55                if let Some((_, _pos)) = loaded.last_mut() {
56                    *_pos = pos
57                }
58                manager.spawn_parse(async move {
59                    include(manager_clone, loaded, file_path, pos, section, 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    mut file_path: PathBuf,
71    pos: Option<Pos>,
72    section: Option<String>,
73) -> Result<ParsedId, ParseError> {
74    let file_id = if let Some(section) = section {
75        FileId::Section {
76            path: file_path.clone(),
77            section,
78        }
79    } else {
80        FileId::Include {
81            path: file_path.clone(),
82        }
83    };
84    if let Some(idx) = loaded.get_index_of(&file_id) {
85        return Err(ParseErrorInner::CircularDefinition(loaded, idx).with(pos));
86    } else {
87        loaded.insert(file_id.clone(), pos);
88    }
89    if let Some(parsed_id) = manager.file_storage.lock().await.existed(&file_id) {
90        return Ok(parsed_id);
91    }
92    let contents = tokio::fs::read_to_string(&file_path)
93        .await
94        .map_err(|e| ParseErrorInner::with(e.into(), pos))?;
95    let (line_off_set, file_ctx) = if let FileId::Section { path: _, section } = &file_id {
96        if let Some((file_ctx, line_off_set)) = match_lib(&contents, section) {
97            (line_off_set, file_ctx)
98        } else {
99            return Err(ParseErrorInner::NoLibSection {
100                path: file_path,
101                section: section.clone(),
102            }
103            .with(pos));
104        }
105    } else {
106        (0, contents)
107    };
108    let parsed_id = manager.file_storage.lock().await.new_file(file_id);
109    let i = span(&file_ctx, line_off_set);
110    // get dir
111    file_path.pop();
112    #[cfg(test)]
113    assert!(file_path.is_dir());
114
115    let res = match ast(manager.clone(), loaded, file_path, i) {
116        Ok((_, res)) => res,
117        Err(e) => error2ast(e.into()),
118    };
119    manager
120        .file_storage
121        .lock()
122        .await
123        .update_ctx(&parsed_id, file_ctx, res);
124    Ok(parsed_id)
125}
126#[inline]
127async fn include(
128    manager: Arc<ParseManager>,
129    loaded: IndexMap<FileId, Option<Pos>>,
130    file_path: PathBuf,
131    pos: Option<Pos>,
132    section: Option<String>,
133    res: Arc<OnceLock<Result<ParsedId, ParseError>>>,
134) {
135    let _res = _include(manager, loaded, file_path, pos, section).await;
136    res.set(_res).unwrap();
137}
138
139/// When ParseError come form the included file,
140/// also return the AST, to recover the error
141fn error2ast(err: ParseError) -> AST {
142    let mut ast = AST::new();
143    let mut local = LocalAST::default();
144    local.errors.push(err);
145    ast.segments.push(Segment::Local(Box::new(local)));
146    ast
147}
148
149fn error2parsed(file_id: FileId, err: ParseError) -> (Parsed, Files) {
150    let ast = error2ast(err);
151    let parsed_id = ParsedId(0);
152    let mut id2idx = HashMap::new();
153    id2idx.insert(file_id.clone(), parsed_id);
154    (
155        Parsed {
156            top_id: parsed_id,
157            id2idx,
158            inner: vec![(file_id, ast)],
159        },
160        Files {
161            inner: vec![String::new()],
162        },
163    )
164}
165
166pub async fn top(mut path: PathBuf) -> (Parsed, Files) {
167    let (manager, done_rx) = ParseManager::new();
168    let file_id = FileId::Include { path: path.clone() };
169    let file_ctx = match tokio::fs::read_to_string(&path).await {
170        Ok(s) => s,
171        Err(e) => return error2parsed(file_id, ParseErrorInner::with(e.into(), None)),
172    };
173    let mut loaded = IndexMap::with_capacity(1);
174    loaded.insert(file_id.clone(), None);
175    let parsed_id = manager.file_storage.lock().await.new_file(file_id);
176    path.pop();
177    let input = span(&file_ctx, 0);
178    let res = match ast(manager.clone(), loaded, path, input) {
179        Ok((_, res)) => res,
180        Err(e) => error2ast(e.into()),
181    };
182    manager.wait(done_rx).await;
183    let mut guard = manager.file_storage.lock().await;
184    let mut file_storage: FileStorage<AST> = mem::take(&mut *guard);
185    file_storage.update_ctx(&parsed_id, file_ctx, res);
186    (
187        Parsed {
188            top_id: parsed_id,
189            id2idx: file_storage.id2idx,
190            inner: file_storage.parsed,
191        },
192        Files {
193            inner: file_storage.file,
194        },
195    )
196}
197
198fn match_lib(text: &str, section: &str) -> Option<(String, u32)> {
199    let section_escaped = regex::escape(section);
200    let re = Regex::new(&format!(
201        r"(?ims)^\s*\.lib\s+{section_escaped}\b(.*?)^\s*\.endl(?:\s+{section_escaped}\b)?"
202    ))
203    .unwrap();
204    re.captures_iter(text).last().map(|caps| {
205        let start_offset = caps.get(0).unwrap().start();
206        let line_num_offset = (text[..start_offset].matches('\n').count()) as u32;
207        (caps[1].to_owned(), line_num_offset)
208    })
209}
210#[tokio::test]
211async fn test_top() {
212    _ = simple_logger::SimpleLogger::new().init();
213    let (parsed, files) = top(PathBuf::from("tests/top.sp")).await;
214    println!("{parsed:?}");
215    println!("{files:?}");
216}
217#[test]
218fn test_match_lib() {
219    let text = r#"
220        .LIB TT
221        * Some lines here
222        M1 d g s b NMOS
223        .MEAS ...
224        .ENDL TT
225
226        .lib tt
227        R1 in out 10k
228        .endl
229        .lib ttg
230        it is wrong
231        .endl"#;
232    assert_eq!(
233        Some(("\n        R1 in out 10k\n".to_owned(), 6)),
234        match_lib(text, "tt")
235    );
236}