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, §ion) {
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 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
143fn 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_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}