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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
use std::ops::Range;
use darling::FromMeta;
use quote::{quote, quote_spanned, TokenStreamExt};
use syn::{
self,
export::{Span, TokenStream2},
spanned::Spanned,
AttributeArgs, FnArg, FnDecl, Ident, ItemFn,
};
use crate::util::{self, report};
#[derive(Debug)]
enum Arg {
Env { span: Span },
Val { span: Span, access: Access, nth: usize },
}
/// Kinds of argument.
#[derive(Copy, Clone, Debug)]
enum Access {
Owned,
Ref,
RefMut,
}
/// Kinds of `user-ptr` embedding.
#[derive(Debug)]
enum UserPtr {
/// Embedding through a [`RefCell`]. This is suitable for common use cases, where module
/// functions can borrow the underlying data back for read/write. It is safe because Lisp
/// threads are subjected to the GIL. [`BorrowError`]/[`BorrowMutError`] may be signaled at
/// runtime, depending on how module functions call back into the Lisp runtime.
///
/// [`RefCell`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html
/// [`BorrowError`]: https://doc.rust-lang.org/std/cell/struct.BorrowError.html
/// [`BorrowMutError`]: https://doc.rust-lang.org/std/cell/struct.BorrowMutError.html
RefCell,
/// Embedding through a [`RwLock`]. Suitable for sharing data between module functions (on Lisp
/// threads, with [`Env`] access) and pure Rust code (on background threads, without [`Env`]
/// access).
///
/// [`RwLock`]: https://doc.rust-lang.org/std/sync/struct.RwLock.html
RwLock,
/// Embedding through a [`Mutex`]. Suitable for sharing data between module functions (on Lisp
/// threads, with [`Env`] access) and pure Rust code (on background threads, without [`Env`]
/// access).
///
/// [`Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
Mutex,
/// Embedding a `Transfer` value directly. Suitable for immutable data that will only be read
/// back (not written to) by module functions (writing requires `unsafe` access, and is
/// discouraged).
Direct,
}
#[derive(Debug, FromMeta)]
struct FuncOpts {
/// Name of the function in Lisp, excluding prefix. `None` means sanitized Rust name is used.
#[darling(default)]
name: Option<String>,
/// Whether module path should be used to construct the full Lisp name. `None` means using
/// crate-wide config.
#[darling(default)]
mod_in_name: Option<bool>,
/// How the return value should be embedded in Lisp as a `user-ptr`. `None` means no embedding.
#[darling(default)]
user_ptr: Option<UserPtr>,
}
#[derive(Debug)]
pub struct LispFunc {
/// The original Rust definition.
def: ItemFn,
/// Relevant info about the arguments in Rust.
args: Vec<Arg>,
/// Function's arities in Lisp.
arities: Range<usize>,
/// Span of the return type. This helps with error reporting.
output_span: Span,
opts: FuncOpts,
}
/// We don't use the derived impl provided by darling, since we want a different syntax.
/// See https://github.com/TedDriggs/darling/issues/74.
impl FromMeta for UserPtr {
fn from_word() -> darling::Result<UserPtr> {
Ok(UserPtr::RefCell)
}
fn from_list(outer: &[syn::NestedMeta]) -> darling::Result<UserPtr> {
match outer.len() {
0 => Err(darling::Error::too_few_items(1)),
1 => {
let elem = &outer[0];
match elem {
syn::NestedMeta::Meta(syn::Meta::Word(ref ident)) => {
match ident.to_string().as_ref() {
"refcell" => Ok(UserPtr::RefCell),
"mutex" => Ok(UserPtr::Mutex),
"rwlock" => Ok(UserPtr::RwLock),
"direct" => Ok(UserPtr::Direct),
_ => Err(darling::Error::custom("Unknown kind of embedding")
.with_span(ident)),
}
}
_ => Err(darling::Error::custom("Expected an identifier").with_span(elem)),
}
}
_ => Err(darling::Error::too_many_items(1)),
}
}
}
impl LispFunc {
pub fn parse(attr_args: AttributeArgs, fn_item: ItemFn) -> Result<Self, TokenStream2> {
let opts: FuncOpts = match FuncOpts::from_list(&attr_args) {
Ok(v) => v,
Err(e) => return Err(e.write_errors()),
};
let (args, arities, output_span) = check_signature(&fn_item.decl)?;
let def = fn_item;
Ok(Self { def, args, arities, output_span, opts })
}
pub fn render(&self) -> TokenStream2 {
let define_exporter = self.gen_exporter();
let register_exporter = self.gen_registrator();
let define_func = &self.def;
quote! {
#define_func
#define_exporter
#register_exporter
}
}
/// Generates the wrapper function, which decodes input arguments and encodes return value.
pub fn gen_wrapper(&self) -> TokenStream2 {
let mut args = TokenStream2::new();
// Inlined references do not live long enough. We need bindings for them.
let mut bindings = TokenStream2::new();
for arg in &self.args {
match *arg {
Arg::Env { span } => {
// TODO: Find a way not to define inner function, somehow, otherwise the reported
// error is confusing (i.e expecting Env, found &Env).
args.append_all(quote_spanned!(span=> &**env,))
}
Arg::Val { span, access, nth } => {
let name = util::arg("arg", nth);
// TODO: Create a slice of `emacs_value` once and iterate through it, instead of
// using `get_arg`, which creates a slice each call.
bindings.append_all(match access {
Access::Owned => quote_spanned! {span=>
let #name = env.get_arg(#nth).into_rust()?;
},
// TODO: Support RwLock/Mutex (for the use case of sharing data with
// background Rust threads).
// TODO: Support direct access.
Access::Ref => quote_spanned! {span=>
let #name = &*env.get_arg(#nth).into_ref()?;
},
Access::RefMut => quote_spanned! {span=>
let #name = &mut *env.get_arg(#nth).into_ref_mut()?;
},
});
args.append_all(quote_spanned!(span=> #name,));
}
}
}
let maybe_embed = match &self.opts.user_ptr {
None => TokenStream2::new(),
Some(user_ptr) => match user_ptr {
UserPtr::RefCell => quote_spanned! {self.output_span=>
let result = ::std::boxed::Box::new(::std::cell::RefCell::new(result));
},
UserPtr::RwLock => quote_spanned! {self.output_span=>
let result = ::std::boxed::Box::new(::std::sync::RwLock::new(result));
},
UserPtr::Mutex => quote_spanned! {self.output_span=>
let result = ::std::boxed::Box::new(::std::sync::Mutex::new(result));
},
UserPtr::Direct => quote_spanned! {self.output_span=>
let result = ::std::boxed::Box::new(result);
},
},
};
// XXX: result can be (), but we can't easily know when.
let into_lisp = quote_spanned! {self.output_span=>
#[allow(clippy::unit_arg)]
::emacs::IntoLisp::into_lisp(result, env)
};
let inner = &self.def.ident;
let wrapper = self.wrapper_ident();
quote! {
fn #wrapper(env: &::emacs::CallEnv) -> ::emacs::Result<::emacs::Value<'_>> {
#bindings
let result = #inner(#args)?;
#maybe_embed
#into_lisp
}
}
}
/// Generates the exporter function. It will be called in `emacs_module_init` to bind the Lisp
/// symbol to the defined extern function.
pub fn gen_exporter(&self) -> TokenStream2 {
let define_wrapper = self.gen_wrapper();
let wrapper = self.wrapper_ident();
let exporter = self.exporter_ident();
let (min, max) = (self.arities.start, self.arities.end);
let doc = util::doc(&self.def);
let path = match &self.opts.mod_in_name {
None => {
let crate_mod_in_name = util::mod_in_name_path();
quote!({
if #crate_mod_in_name.load(::std::sync::atomic::Ordering::Relaxed) {
module_path!()
} else {
""
}
})
}
Some(true) => quote!(module_path!()),
Some(false) => quote!(""),
};
let lisp_name = match &self.opts.name {
Some(name) => name.clone(),
None => util::lisp_name(&self.def.ident),
};
// TODO: Consider defining `extern "C" fn` directly instead of using export_functions! and
// CallEnv wrapper.
quote! {
#define_wrapper
fn #exporter(env: &::emacs::Env) -> ::emacs::Result<()> {
let prefix = ::emacs::globals::lisp_path(#path);
::emacs::export_functions! {
env, prefix, {
#lisp_name => (#wrapper, #min..#max, #doc),
}
}
Ok(())
}
}
}
/// Generates the registrator function. It will be called when the shared lib is loaded (by the
/// OS, before `emacs_module_init` is called by Emacs), to add the exporter to the list of
/// functions `emacs_module_init` will call (provided that it's generated by
/// [`#[emacs::module]`]).
///
/// [`#[emacs::module]`]: attr.module.html
pub fn gen_registrator(&self) -> TokenStream2 {
let exporter = self.exporter_ident();
let registrator = self.registrator_ident();
let init_fns = util::init_fns_path();
let name = format!("{}", self.def.ident);
quote! {
#[::emacs::deps::ctor::ctor]
fn #registrator() {
let mut full_path = module_path!().to_owned();
full_path.push_str("::");
full_path.push_str(#name);
let mut funcs = #init_fns.lock()
.expect("Failed to acquire a write lock on map of initializers");
funcs.insert(full_path, ::std::boxed::Box::new(#exporter));
}
}
}
fn wrapper_ident(&self) -> Ident {
util::concat("__emr_O_", &self.def.ident)
}
fn exporter_ident(&self) -> Ident {
util::concat("__emrs_E_", &self.def.ident)
}
fn registrator_ident(&self) -> Ident {
util::concat("__emrs_R_", &self.def.ident)
}
}
fn check_signature(decl: &FnDecl) -> Result<(Vec<Arg>, Range<usize>, Span), TokenStream2> {
let mut i: usize = 0;
let mut err = TokenStream2::new();
let mut has_env = false;
let mut args: Vec<Arg> = vec![];
let errors = &mut err;
for fn_arg in &decl.inputs {
match fn_arg {
FnArg::Captured(capt) => {
let ty = &capt.ty;
let span = fn_arg.span();
args.push(if is_env(ty) {
match ty {
syn::Type::Reference(_) => (),
_ => report(errors, fn_arg, "Can only take an &Env, not an Env"),
}
if has_env {
report(errors, fn_arg, "&Env must be passed only once")
}
has_env = true;
Arg::Env { span }
} else {
let access = match ty {
syn::Type::Reference(syn::TypeReference { mutability, .. }) => {
match mutability {
Some(_) => Access::RefMut,
None => Access::Ref,
}
}
_ => Access::Owned,
};
let a = Arg::Val { span, access, nth: i };
i += 1;
a
});
}
FnArg::SelfRef(_) => report(errors, fn_arg, "Cannot take &self argument"),
FnArg::SelfValue(_) => report(errors, fn_arg, "Cannot take self argument"),
// TODO: Support this.
FnArg::Ignored(_) => report(errors, fn_arg, "Ignored argument is not supported"),
FnArg::Inferred(_) => {
report(errors, fn_arg, "Argument with inferred type is not supported")
}
}
}
// TODO: Make the Span span the whole return type.
let output_span = match &decl.output {
syn::ReturnType::Type(_, ty) => ty.span(),
_ => {
report(errors, &decl.fn_token, "Must return emacs::Result<T> where T: IntoLisp<'_>");
decl.fn_token.span()
}
};
if err.is_empty() {
Ok((args, Range { start: i, end: i }, output_span))
} else {
Err(err)
}
}
// XXX
fn is_env(ty: &syn::Type) -> bool {
match ty {
syn::Type::Reference(syn::TypeReference { elem, .. }) => is_env(elem),
syn::Type::Path(syn::TypePath { qself: None, ref path }) => {
let str_path = format!("{}", quote!(#path));
str_path.ends_with("Env")
}
_ => false,
}
}