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