1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! Initialization machinery. It should be mainly used by the #[[`module`]] macro, not module code.
//!
//! [`module`]: attr.module.html

use std::{
    os, panic,
    collections::HashMap,
    sync::{Mutex, atomic::AtomicBool},
};

use once_cell::sync::Lazy;

use crate::{Env, Value, Result, ErrorKind};

#[doc(hidden)]
#[macro_export]
macro_rules! __module_init {
    ($init:ident) => {
        /// Entry point for Emacs's module loader.
        #[no_mangle]
        pub unsafe extern "C" fn emacs_module_init(
            runtime: *mut $crate::raw::emacs_runtime,
        ) -> ::std::os::raw::c_int {
            $crate::init::initialize(&$crate::Env::from_runtime(runtime), $init)
        }

        // TODO: Exclude this in release build.
        /// Entry point for live-reloading (by `rs-module`) during development.
        #[no_mangle]
        pub unsafe extern "C" fn emacs_rs_module_init(
            raw: *mut $crate::raw::emacs_env,
        ) -> ::std::os::raw::c_int {
            $crate::init::initialize(&$crate::Env::new(raw), $init)
        }
    };
}

type InitFn = Box<dyn Fn(&Env) -> Result<()> + Send + 'static>;

type FnMap = HashMap<String, InitFn>;

// TODO: How about defining these in user crate, and requiring #[module] to be at the crate's root?
// TODO: We probably don't need the mutexes.

/// Functions that will be called by [`emacs_module_init`] to initialize global references to
/// frequently used Lisp values.
///
/// They are called before loading module metadata, e.g. module name, function prefix.
///
/// This list is populated when the OS loads the dynamic library, before Emacs calls
/// [`emacs_module_init`].
///
/// [`emacs_module_init`]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Modules.html
pub static __GLOBAL_REFS__: Lazy<Mutex<Vec<InitFn>>> = Lazy::new(|| Mutex::new(vec![]));

/// Functions that will be called by [`emacs_module_init`] to define custom error signals.
///
/// They are called before loading module metadata, e.g. module name, function prefix.
///
/// This list is populated when the OS loads the dynamic library, before Emacs calls
/// [`emacs_module_init`].
///
/// [`emacs_module_init`]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Modules.html
pub static __CUSTOM_ERRORS__: Lazy<Mutex<Vec<InitFn>>> = Lazy::new(|| Mutex::new(vec![]));

/// Functions that will be called by [`emacs_module_init`] to define the module functions.
///
/// They are called after loading module metadata, e.g. module name, function prefix.
///
/// This map is populated when the OS loads the dynamic library, before Emacs calls
/// [`emacs_module_init`].
///
/// [`emacs_module_init`]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Modules.html
pub static __INIT_FNS__: Lazy<Mutex<FnMap>> = Lazy::new(|| Mutex::new(HashMap::new()));

/// Prefix to prepend to name of every Lisp function exposed by the dynamic module through the
/// attribute macro #[[`defun`]].
///
/// [`defun`]: attr.defun.html
pub static __PREFIX__: Lazy<Mutex<[String; 2]>> = Lazy::new(|| Mutex::new(["".to_owned(), "-".to_owned()]));

pub static __MOD_IN_NAME__: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(true));

fn debugging() -> bool {
    std::env::var("EMACS_MODULE_RS_DEBUG").unwrap_or_default() == "1"
}

fn check_gc_bug_31238(env: &Env) -> Result<()> {
    let version = env.call("default-value", [env.intern("emacs-version")?])?;
    let fixed = env.call("version<=", ("27", version))?.is_not_nil();
    if debugging() {
        env.call("set", (
            env.intern("module-rs-disable-gc-bug-31238-workaround")?, fixed
        ))?;
    }
    crate::env::HAS_FIXED_GC_BUG_31238.get_or_init(|| fixed);
    Ok(())
}

#[inline]
pub fn initialize<F>(env: &Env, init: F) -> os::raw::c_int
    where
        F: Fn(&Env) -> Result<Value<'_>> + panic::RefUnwindSafe,
{
    let env = panic::AssertUnwindSafe(env);
    let result = panic::catch_unwind(|| match (|| {
        for init_global_ref in __GLOBAL_REFS__.try_lock()
            .expect("Failed to acquire a read lock on the list of initializers for global-refs").iter() {
            init_global_ref(&env)?;
        }
        env.define_core_errors()?;
        check_gc_bug_31238(&env)?;
        for define_error in __CUSTOM_ERRORS__.try_lock()
            .expect("Failed to acquire a read lock on the list of initializers for custom error signals").iter() {
            define_error(&env)?;
        }
        init(&env)
    })() {
        Ok(_) => 0,
        Err(e) => {
            if let Some(ErrorKind::Signal { symbol, data }) = e.downcast_ref::<ErrorKind>() {
                env.call("message", (
                    "Error during initialization: symbol: %s data: %s",
                    unsafe { symbol.value(&env) },
                    unsafe { data.value(&env) },
                ))
            } else {
                env.message(format!("Error during initialization: {:#?}", e))
            }
                .expect("Failed to message Emacs about initialization error");
            1
        }
    });
    match result {
        Ok(v) => v,
        Err(e) => {
            env.message(format!("Panic during initialization: {:#?}", e))
                .expect("Failed to message Emacs about initialization panic");
            2
        }
    }
}

fn lisp_name(s: &str) -> String {
    s.replace("_", "-")
}

pub fn lisp_pkg(mod_path: &str) -> String {
    let crate_name = mod_path.split("::").nth(0).expect("mod_path is empty!");
    lisp_name(&crate_name)
}

pub fn lisp_path(mod_path: &str) -> String {
    let split = mod_path.split("::");
    let mut path =
        __PREFIX__.try_lock().expect("Failed to acquire read lock of module prefix").join("");
    for segment in split.skip(1) {
        path.push_str(segment);
        path.push('-');
    }
    lisp_name(&path)
}