Skip to main content

diplomat_tool/
lib.rs

1// Enable once https://github.com/rust-lang/rust/issues/89554 is stable
2// #![deny(non_exhaustive_omitted_patterns)] // diplomat_core uses non_exhaustive a lot; we should never miss its patterns
3
4pub mod config;
5// Custom askama filters
6pub(crate) mod filters;
7
8// Backends
9pub 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    // Set the default binding location:
67    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    // The HIR backends used to be named "c2", "js2", etc
80    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        // So renames and disables are carried across.
85        attr_validator.other_backend_names = vec!["js".to_string()];
86    }
87
88    let module = syn_inline_mod::parse_and_inline_modules(entry);
89
90    // Config:
91    // Just search the top-level lib.rs for the Config attributes for now. We can re-configure this to use AST to search ALL modules if need be.
92    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 we don't already have an import path set up, generate our own imports:
121            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        // Eventually this should use eyre or something
147        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/// This type abstracts over files being written to.
171#[derive(Default, Debug)]
172pub struct FileMap {
173    // The context types exist as a way to avoid passing around a billion different
174    // parameters. However, passing them around as &mut self restricts the amount of
175    // borrowing that can be done. We instead use a RefCell to guard the specifically mutable bits.
176    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/// This type acts as a "store" for errors, which can be appended to.
193/// Keeps track of the context in which an error was generated.
194///
195/// You can use [`set_context_ty()`] and [`set_context_method()`] to set the context
196/// as a type or method. They will return scope guards that will automatically pop the stack
197/// once they go out of scope, so you don't have to worry about errors originating from code
198/// that does not set a context.
199#[derive(Default)]
200pub struct ErrorStore<'tcx, E> {
201    /// The stack of contexts reached so far
202    context: RefCell<ErrorContext<'tcx>>,
203    errors: RefCell<Vec<(ErrorContext<'tcx>, E)>>,
204}
205
206impl<'tcx, E> ErrorStore<'tcx, E> {
207    /// Set the context to a named type. Will return a scope guard that will automatically
208    /// clear the context on drop.
209    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    /// Set the context to a named method. Will return a scope guard that will automatically
216    /// clear the context on drop.
217    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/// The context in which an error was discovered
242#[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/// Scope guard terminating the context created `set_context_*` method on [`ErrorStore`]
260#[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}