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