1#![allow(clippy::disallowed_methods)] mod generator;
4
5use proc_macro::TokenStream;
6use std::env;
7use std::fs;
8use std::path::PathBuf;
9use syn::LitStr;
10use veryl_analyzer::ir::Ir;
11use veryl_analyzer::{Analyzer, Context};
12use veryl_metadata::Metadata;
13use veryl_parser::Parser;
14
15#[proc_macro]
16pub fn veryl_test(input: TokenStream) -> TokenStream {
17 let project_path_lit = match syn::parse::<LitStr>(input.clone()) {
18 Ok(lit) => lit,
19 Err(err) => return err.to_compile_error().into(),
20 };
21 let project_path_str = project_path_lit.value();
22 let span = project_path_lit.span();
23
24 macro_rules! exit_with_error {
25 ($msg:expr) => {{
26 let err_msg = $msg;
27 return quote::quote_spanned! { span => compile_error!(#err_msg); }.into();
28 }};
29 }
30
31 let manifest_dir = match env::var("CARGO_MANIFEST_DIR") {
33 Ok(dir) => dir,
34 Err(_) => exit_with_error!("CARGO_MANIFEST_DIR is not set"),
35 };
36 let base_dir = PathBuf::from(manifest_dir);
37 let target_dir = base_dir.join(project_path_str);
38
39 let metadata_path = match Metadata::search_from(&target_dir) {
40 Ok(path) => path,
41 Err(_) => exit_with_error!(&format!("Failed to find Veryl.toml from {:?}", target_dir)),
42 };
43
44 let mut metadata = match Metadata::load(&metadata_path) {
45 Ok(meta) => meta,
46 Err(e) => exit_with_error!(&format!("Failed to load Veryl.toml: {}", e)),
47 };
48
49 veryl_analyzer::symbol_table::clear();
51 veryl_analyzer::attribute_table::clear();
52 let mut ir = Ir::default();
53 let mut context = Context::default();
54 let analyzer = Analyzer::new(&metadata);
55 analyzer.clear();
56
57 let paths = match metadata.paths::<PathBuf>(&[], false, true) {
58 Ok(p) => p
59 .into_iter()
60 .filter(|path| !path.example)
61 .collect::<Vec<_>>(),
62 Err(e) => exit_with_error!(&format!("Failed to gather paths: {}", e)),
63 };
64
65 let mut table = fxhash::FxHashMap::default();
67 for path in &paths {
68 table.insert(path.src.clone(), path);
69 }
70
71 let mut prj_namespace = veryl_analyzer::namespace::Namespace::new();
72 prj_namespace.push(veryl_parser::resource_table::insert_str(
73 &metadata.project.name,
74 ));
75
76 let candidate_symbols: Vec<_> = veryl_analyzer::type_dag::connected_components()
77 .into_iter()
78 .filter(|symbols| symbols[0].namespace.included(&prj_namespace))
79 .flatten()
80 .collect();
81
82 let mut used_paths = fxhash::FxHashMap::default();
83 for symbol in &candidate_symbols {
84 if let veryl_parser::veryl_token::TokenSource::File { path, .. } = symbol.token.source {
85 let path = PathBuf::from(format!("{path}"));
86 if let Some(x) = table.remove(&path) {
87 used_paths.insert(path, x);
88 }
89 }
90 }
91
92 let mut sorted_paths = vec![];
93 let sorted_symbols = veryl_analyzer::type_dag::toposort();
94 for symbol in sorted_symbols {
95 if matches!(
96 symbol.kind,
97 veryl_analyzer::symbol::SymbolKind::Module(_)
98 | veryl_analyzer::symbol::SymbolKind::Interface(_)
99 | veryl_analyzer::symbol::SymbolKind::Package(_)
100 ) && let veryl_parser::veryl_token::TokenSource::File { path, .. } = symbol.token.source
101 {
102 let path = PathBuf::from(format!("{path}"));
103 if let Some(x) = used_paths.remove(&path) {
104 sorted_paths.push(x.clone());
105 }
106 }
107 }
108
109 for path in used_paths.into_values() {
110 sorted_paths.push(path.clone());
111 }
112
113 let mut parsed_files = Vec::new();
118 for path_set in paths {
119 let code = match fs::read_to_string(&path_set.src) {
120 Ok(c) => c,
121 Err(e) => {
122 exit_with_error!(&format!("Failed to read {}: {}", path_set.src.display(), e))
123 }
124 };
125 parsed_files.push((path_set, code));
126 }
127
128 let mut parsers = Vec::new();
129 for (path_set, code) in &parsed_files {
130 let parser = match Parser::parse(code, &path_set.src) {
131 Ok(p) => p,
132 Err(_) => exit_with_error!(&format!("Failed to parse {}", path_set.src.display())),
133 };
134 let errors = analyzer.analyze_pass1(&path_set.prj, &parser.veryl);
135 if !errors.is_empty() {
136 let msgs: Vec<String> = errors.iter().map(|e| format!("{e}")).collect();
137 exit_with_error!(&format!("Analysis pass1 errors: {}", msgs.join("; ")));
138 }
139 parsers.push(parser);
140 }
141 let errors = Analyzer::analyze_post_pass1();
142 if !errors.is_empty() {
143 let msgs: Vec<String> = errors.iter().map(|e| format!("{e}")).collect();
144 exit_with_error!(&format!("Analysis post-pass1 errors: {}", msgs.join("; ")));
145 }
146
147 for (i, (_path_set, _code)) in parsed_files.iter().enumerate() {
148 let parser = &parsers[i];
149 let errors = analyzer.analyze_pass2(&parser.veryl, &mut context, Some(&mut ir));
150 if !errors.is_empty() {
151 let msgs: Vec<String> = errors.iter().map(|e| format!("{e}")).collect();
152 exit_with_error!(&format!("Analysis pass2 errors: {}", msgs.join("; ")));
153 }
154 }
155 let errors = Analyzer::analyze_post_pass2(&ir);
156 if !errors.is_empty() {
157 let msgs: Vec<String> = errors.iter().map(|e| format!("{e}")).collect();
158 exit_with_error!(&format!("Analysis post-pass2 errors: {}", msgs.join("; ")));
159 }
160
161 let expanded = generator::generate_project(&ir);
163
164 TokenStream::from(expanded)
165}