1pub mod config;
5pub(crate) mod filters;
7
8pub mod c;
10mod cpp;
11mod dart;
12mod demo_gen;
13mod js;
14mod kotlin;
15mod nanobind;
16
17use colored::*;
18use config::toml_value_from_str;
19use config::{find_top_level_attr, Config};
20use core::mem;
21use core::panic;
22use diplomat_core::hir;
23use std::borrow::Cow;
24use std::cell::RefCell;
25use std::collections::HashMap;
26use std::fmt;
27use std::path::Path;
28
29pub use hir::DocsUrlGenerator;
30
31pub fn get_supported(target_language: &str) -> hir::BackendAttrSupport {
32 match target_language {
33 "c" => c::attr_support(),
34 "cpp" => cpp::attr_support(),
35 "dart" => dart::attr_support(),
36 "js" => js::attr_support(),
37 "demo_gen" => demo_gen::attr_support(),
38 "kotlin" => kotlin::attr_support(),
39 "py-nanobind" | "nanobind" => nanobind::attr_support(),
40 o => panic!("Unknown target: {}", o),
41 }
42}
43
44pub fn gen(
45 entry: &Path,
46 target_language: &str,
47 out_folder: &Path,
48 docs_url_gen: &DocsUrlGenerator,
49 mut config: Config,
50 silent: bool,
51) -> std::io::Result<()> {
52 if !entry.exists() {
53 eprintln!(
54 "{}{}\n{}",
55 "Error: ".red().bold(),
56 if entry.file_name().map(|e| e == "lib.rs").unwrap_or_default() {
57 "Could not find the lib.rs file to process."
58 } else {
59 "The entry file does not exist."
60 },
61 format!("{}", std::env::current_dir().unwrap().join(entry).display()).red()
62 );
63 std::process::exit(1);
64 }
65
66 if config
68 .shared_config
69 .custom_extra_code_location
70 .as_os_str()
71 .is_empty()
72 {
73 config.shared_config.custom_extra_code_location = entry
74 .parent()
75 .expect("Could not get parent of lib.rs")
76 .to_path_buf();
77 }
78
79 let target_language = target_language.strip_suffix('2').unwrap_or(target_language);
81 let mut attr_validator = hir::BasicAttributeValidator::new(target_language);
82 attr_validator.support = get_supported(target_language);
83 if matches!(target_language, "demo_gen") {
84 attr_validator.other_backend_names = vec!["js".to_string()];
86 }
87
88 let module = syn_inline_mod::parse_and_inline_modules(entry);
89
90 let cfg = find_top_level_attr(module.items.clone());
93 for attr in cfg {
94 for kvp in attr.key_value_pairs {
95 config.set(&kvp.key, toml_value_from_str(&kvp.value));
96 }
97 }
98
99 let config = config.get_overridden(target_language);
100
101 let lowering_config = config.shared_config.lowering_config();
102
103 attr_validator.features_enabled = config.shared_config.features_enabled.clone();
104
105 let tcx =
106 hir::TypeContext::from_syn(&module, lowering_config, attr_validator).unwrap_or_else(|e| {
107 for (ctx, err) in e {
108 eprintln!("Lowering error in {ctx}: {err}");
109 }
110 std::process::exit(1);
111 });
112
113 let (files, errors) = match target_language {
114 "c" => c::run(&tcx, &config, docs_url_gen),
115 "cpp" => cpp::run(&tcx, &config, docs_url_gen),
116 "dart" => dart::run(&tcx, docs_url_gen),
117 "js" => js::run(&tcx, config, docs_url_gen),
118 "py-nanobind" | "nanobind" => nanobind::run(&tcx, config, docs_url_gen),
119 "demo_gen" => {
120 if !(config.demo_gen_config.module_name.is_some()
122 || config.demo_gen_config.relative_js_path.is_some())
123 {
124 gen(
125 entry,
126 "js",
127 &out_folder.join("js"),
128 docs_url_gen,
129 config.clone(),
130 silent,
131 )?;
132 }
133 demo_gen::run(entry, &tcx, docs_url_gen, config.clone())
134 }
135 "kotlin" => kotlin::run(&tcx, config.clone(), docs_url_gen),
136 o => panic!("Unknown target: {}", o),
137 };
138
139 let errors = errors.take_all();
140 if !errors.is_empty() {
141 eprintln!("Found errors whilst generating {target_language}:");
142 for error in errors {
143 eprintln!("\t{}: {}", error.0, error.1);
144 }
145 eprintln!("Not generating files due to errors");
146 std::process::exit(1);
148 }
149
150 if !silent {
151 println!(
152 "{}",
153 format!("Generating {target_language} bindings:")
154 .green()
155 .bold()
156 );
157 }
158 for (subpath, text) in files.take_files() {
159 let out_path = out_folder.join(subpath);
160 if !silent {
161 println!("{}", format!(" {}", out_path.display()).dimmed());
162 }
163 std::fs::create_dir_all(out_path.parent().unwrap()).unwrap();
164 std::fs::write(&out_path, text)?;
165 }
166
167 Ok(())
168}
169
170#[derive(Default, Debug)]
172pub struct FileMap {
173 files: RefCell<HashMap<String, String>>,
177}
178
179impl FileMap {
180 pub fn take_files(self) -> HashMap<String, String> {
181 mem::take(&mut *self.files.borrow_mut())
182 }
183
184 pub fn add_file(&self, name: String, contents: String) {
185 if self.files.borrow().get(&name).is_some() {
186 panic!("File map already contains {}", name)
187 }
188 self.files.borrow_mut().insert(name, contents);
189 }
190}
191
192#[derive(Default)]
200pub struct ErrorStore<'tcx, E> {
201 context: RefCell<ErrorContext<'tcx>>,
203 errors: RefCell<Vec<(ErrorContext<'tcx>, E)>>,
204}
205
206impl<'tcx, E> ErrorStore<'tcx, E> {
207 pub fn set_context_ty<'a>(&'a self, ty: Cow<'tcx, str>) -> ErrorContextGuard<'a, 'tcx, E> {
210 let new = ErrorContext { ty, method: None };
211 let old = mem::replace(&mut *self.context.borrow_mut(), new);
212 ErrorContextGuard(self, old)
213 }
214
215 pub fn set_context_method<'a>(
218 &'a self,
219 method: Cow<'tcx, str>,
220 ) -> ErrorContextGuard<'a, 'tcx, E> {
221 let new = ErrorContext {
222 ty: self.context.borrow().ty.clone(),
223 method: Some(method),
224 };
225
226 let old = mem::replace(&mut *self.context.borrow_mut(), new);
227 ErrorContextGuard(self, old)
228 }
229
230 pub fn push_error(&self, error: E) {
231 self.errors
232 .borrow_mut()
233 .push((self.context.borrow().clone(), error));
234 }
235
236 pub fn take_all(&self) -> Vec<(impl fmt::Display + 'tcx, E)> {
237 mem::take(&mut self.errors.borrow_mut())
238 }
239}
240
241#[derive(Default, Clone)]
243struct ErrorContext<'tcx> {
244 ty: Cow<'tcx, str>,
245 method: Option<Cow<'tcx, str>>,
246}
247
248impl fmt::Display for ErrorContext<'_> {
249 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
250 let ty = &self.ty;
251 if let Some(ref method) = self.method {
252 write!(f, "{ty}::{method}")
253 } else {
254 ty.fmt(f)
255 }
256 }
257}
258
259#[must_use]
261pub struct ErrorContextGuard<'a, 'tcx, E>(&'a ErrorStore<'tcx, E>, ErrorContext<'tcx>);
262
263impl<E> Drop for ErrorContextGuard<'_, '_, E> {
264 fn drop(&mut self) {
265 let _ = mem::replace(&mut *self.0.context.borrow_mut(), mem::take(&mut self.1));
266 }
267}
268
269pub(crate) fn read_custom_binding<'a, 'b>(
270 source: &hir::IncludeSource,
271 config: &Config,
272 errors: &'b ErrorStore<'a, String>,
273) -> Result<String, ()> {
274 match source {
275 hir::IncludeSource::File(path) => {
276 let path = config.shared_config.custom_extra_code_location.join(path);
277 std::fs::read_to_string(&path).map_err(|e| {
278 errors.push_error(format!("Cannot find file {}: {e}", path.display()));
279 })
280 }
281 hir::IncludeSource::Source(s) => Ok(s.clone()),
282 _ => panic!("Unrecognized IncludeSource: {:?}", source),
283 }
284}