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 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
139fn 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}