moz-cheddar 0.4.2

A library to automatically generate C header files from Rust source files.
Documentation
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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
//! Functions for actually parsing the source file.

use syntax::ast;
use syntax::print;

use types;
use Error;
use Level;

// TODO: we should use our own parse state which tracks what types are callable from C
//     - then we can give decent errors for ones that aren't
//     - will require multiple passes of the module
//         - each time a struct, enum, or typedef changes do another pass
//         - only store errors on the final pass
//             - maybe there will be some errors which will need to be stored before then
//     - search inside struct as well for whitelisted types
//     - possibly also search other crates when encountering a path

/// Check that an expected path has been `pub use`d.
fn check_pub_use(item: &ast::Item, expected: &ast::Path) -> bool {
    if let ast::ItemKind::Use(ref path) = item.node {
        // API has to be public to be used.
        if let ast::Visibility::Public = item.vis {
            // Easiest way to ensure all of API has been brought into scope.
            if let ast::ViewPath_::ViewPathGlob(ref path) = path.node {
                let mut segments = path.segments.iter();
                if path.is_global() {
                    segments.next();
                }
                return segments.eq(expected.segments.iter());
            }
        }
    }

    false
}


/// The main entry point when looking for a specific module.
///
/// Determines which module to parse, ensures it is `pub use`ed then hands off to
/// `cheddar::parse::parse_mod`.
pub fn parse_crate(krate: &ast::Crate, path: &ast::Path) -> Result<String, Vec<Error>> {
    // First look to see if the module has been `pub use`d.
    if !krate.module.items.iter().any(|item| check_pub_use(&item, &path)) {
        return Err(vec![
            Error {
                level: Level::Error,
                span: None,
                message: format!("module `{}` has not been brought into global scope", path),
            },
            Error {
                level: Level::Help,
                span: None,
                message: format!("try putting `pub use {}::*` in your root source file", path),
            },
        ]);
    }

    // For each module in the path, look for the corresponding module in the source.
    let mut current_module = &krate.module;
    for module in &path.segments {
        let mut found = false;
        for item in &current_module.items {
            if let ast::ItemKind::Mod(ref new_module) = item.node {
                if module.identifier == item.ident {
                    current_module = new_module;
                    found = true;
                    break;
                }
            }
        }

        if !found {
            return Err(vec![Error {
                level: Level::Fatal,
                span: None,
                message: format!("module `{}` could not be found", module.identifier),
            }]);
        }
    }

    parse_mod(&current_module)
}

/// The manager of moz-cheddar and entry point when the crate is the module.
///
/// Iterates through all items in the module and dispatches to correct methods, then pulls all
/// the results together into a header.
pub fn parse_mod(module: &ast::Mod) -> Result<String, Vec<Error>> {
    let mut buffer = String::new();
    let mut errors = vec![];
    for item in &module.items {
        // If it's not visible it can't be called from C.
        if let ast::Visibility::Inherited = item.vis { continue; }

        // Dispatch to correct method.
        let res = match item.node {
            // TODO: Check for ItemStatic and ItemConst as well.
            //     - How would this work?
            //     - Is it even possible?
            ast::ItemKind::Ty(..) => parse_ty(item),
            ast::ItemKind::Enum(..) => parse_enum(item),
            ast::ItemKind::Struct(..) => parse_struct(item),
            ast::ItemKind::Fn(..) => parse_fn(item),
            _ => Ok(None),
        };

        match res {
            // Display any non-fatal errors, fatal errors are handled at cause.
            Err(error) => errors.push(error),
            Ok(Some(buf)) => buffer.push_str(&buf),
            // TODO: put span notes in these or would that just get annoying?
            Ok(None) => {},  // Item should not be written to header.
        };
    }

    if errors.is_empty() {
        Ok(buffer)
    } else {
        Err(errors)
    }
}

/// Convert `pub type A = B;` into `typedef B A;`.
///
/// Aborts if A is generic.
fn parse_ty(item: &ast::Item) -> Result<Option<String>, Error> {
    let (_, docs) = parse_attr(&item.attrs, |_| true, |attr| retrieve_docstring(attr, ""));

    let mut buffer = String::new();
    buffer.push_str(&docs);

    let name = item.ident.name.as_str();
    let new_type = match item.node {
        ast::ItemKind::Ty(ref ty, ref generics) => {
            // Can not yet convert generics.
            if generics.is_parameterized() { return Ok(None); }

            try_some!(types::rust_to_c(&*ty, &name))
        },
        _ => {
            return Err(Error {
                level: Level::Bug,
                span: Some(item.span),
                message: "`parse_ty` called on wrong `Item_`".into(),
            });
        },
    };

    buffer.push_str(&format!("typedef {};\n\n", new_type));

    Ok(Some(buffer))
}

/// Convert a Rust enum into a C enum.
///
/// The Rust enum must be marked with `#[repr(C)]` and must be public otherwise the function
/// will abort.
///
/// Cheddar will error if the enum if generic or if it contains non-unit variants.
fn parse_enum(item: &ast::Item) -> Result<Option<String>, Error> {
    let (repr_c, docs) = parse_attr(&item.attrs, check_repr_c, |attr| retrieve_docstring(attr, ""));
    // If it's not #[repr(C)] then it can't be called from C.
    if !repr_c { return Ok(None); }

    let mut buffer = String::new();
    buffer.push_str(&docs);

    let name = item.ident.name.as_str();
    buffer.push_str(&format!("typedef enum {} {{\n", name));
    if let ast::ItemKind::Enum(ref definition, ref generics) = item.node {
        if generics.is_parameterized() {
            return Err(Error {
                level: Level::Error,
                span: Some(item.span),
                message: "cheddar can not handle parameterized `#[repr(C)]` enums".into(),
            });
        }

        for var in &definition.variants {
            if !var.node.data.is_unit() {
                return Err(Error {
                    level: Level::Error,
                    span: Some(var.span),
                    message: "cheddar can not handle `#[repr(C)]` enums with non-unit variants".into(),
                });
            }

            let (_, docs) = parse_attr(&var.node.attrs, |_| true, |attr| retrieve_docstring(attr, "\t"));
            buffer.push_str(&docs);

            buffer.push_str(&format!("\t{}_{},\n", name, print::pprust::variant_to_string(var)));
        }
    } else {
        return Err(Error {
            level: Level::Bug,
            span: Some(item.span),
            message: "`parse_enum` called on wrong `Item_`".into(),
        });
    }

    buffer.push_str(&format!("}} {};\n\n", name));

    Ok(Some(buffer))
}

/// Convert a Rust struct into a C struct.
///
/// The rust struct must be marked `#[repr(C)]` and must be public otherwise the function will
/// abort.
///
/// Cheddar will error if the struct is generic or if the struct is a unit or tuple struct.
fn parse_struct(item: &ast::Item) -> Result<Option<String>, Error> {
    let (repr_c, docs) = parse_attr(&item.attrs, check_repr_c, |attr| retrieve_docstring(attr, ""));
    // If it's not #[repr(C)] then it can't be called from C.
    if !repr_c { return Ok(None); }

    let mut buffer = String::new();
    buffer.push_str(&docs);

    let name = item.ident.name.as_str();
    buffer.push_str(&format!("typedef struct {}", name));

    if let ast::ItemKind::Struct(ref variants, ref generics) = item.node {
        if generics.is_parameterized() {
            return Err(Error {
                level: Level::Error,
                span: Some(item.span),
                message: "cheddar can not handle parameterized `#[repr(C)]` structs".into(),
            });
        }

        if variants.is_struct() {
            buffer.push_str(" {\n");

            for field in variants.fields() {
                let (_, docs) = parse_attr(&field.attrs, |_| true, |attr| retrieve_docstring(attr, "\t"));
                buffer.push_str(&docs);

                let name = match field.ident {
                    Some(name) => name.name.as_str(),
                    None => unreachable!("a tuple struct snuck through"),
                };
                let ty = try_some!(types::rust_to_c(&*field.ty, &name));
                buffer.push_str(&format!("\t{};\n", ty));
            }

            buffer.push_str("}");
        } else if variants.is_tuple() && variants.fields().len() == 1 {
            // #[repr(C)] pub struct Foo(Bar);  =>  typedef struct Foo Foo;
        } else {
            return Err(Error {
                level: Level::Error,
                span: Some(item.span),
                message: "cheddar can not handle unit or tuple `#[repr(C)]` structs with >1 members".into(),
            });
        }
    } else {
        return Err(Error {
            level: Level::Bug,
            span: Some(item.span),
            message: "`parse_struct` called on wrong `Item_`".into(),
        });
    }

    buffer.push_str(&format!(" {};\n\n", name));

    Ok(Some(buffer))
}

/// Convert a Rust function declaration into a C function declaration.
///
/// The function declaration must be marked `#[no_mangle]` and have a C ABI otherwise the
/// function will abort.
///
/// If the declaration is generic or diverges then cheddar will error.
fn parse_fn(item: &ast::Item) -> Result<Option<String>, Error> {
    let (no_mangle, docs) = parse_attr(&item.attrs, check_no_mangle, |attr| retrieve_docstring(attr, ""));
    // If it's not #[no_mangle] then it can't be called from C.
    if !no_mangle { return Ok(None); }

    let mut buffer = String::new();
    let name = item.ident.name.as_str();
    buffer.push_str(&docs);

    if let ast::ItemKind::Fn(ref fn_decl, _, _, abi, ref generics, _) = item.node {
        use syntax::abi::Abi;
        match abi {
            // If it doesn't have a C ABI it can't be called from C.
            Abi::C | Abi::Cdecl | Abi::Stdcall | Abi::Fastcall | Abi::System => {},
            _ => return Ok(None),
        }

        if generics.is_parameterized() {
            return Err(Error {
                level: Level::Error,
                span: Some(item.span),
                message: "cheddar can not handle parameterized extern functions".into(),
            });
        }

        let fn_decl: &ast::FnDecl = &*fn_decl;
        // Handle the case when the return type is a function pointer (which requires that the
        // entire declaration is wrapped by the function pointer type) by first creating the name
        // and parameters, then passing that whole thing to `rust_to_c`.
        let mut buf_without_return = format!("{}(", name);

        let has_args = !fn_decl.inputs.is_empty();

        for arg in &fn_decl.inputs {
            use syntax::ast::{PatKind, BindingMode};
            let arg_name = match arg.pat.node {
                PatKind::Ident(BindingMode::ByValue(_), ref ident, None) => {
                    ident.node.name.to_string()
                }
                _ => return Err(Error {
                    level: Level::Error,
                    span: None,
                    message: format!("cheddar only supports by-value arguments:
    incorrect argument `{}` in function definition `{}`",
                        print::pprust::pat_to_string(&*arg.pat), name),
                })
            };
            let arg_type = try_some!(types::rust_to_c(&*arg.ty, &arg_name));
            buf_without_return.push_str(&arg_type);
            buf_without_return.push_str(", ");
        }

        if has_args {
            // Remove the trailing comma and space.
            buf_without_return.pop();
            buf_without_return.pop();
        } else {
            buf_without_return.push_str("void");
        }

        buf_without_return.push(')');

        let output_type = &fn_decl.output;
        let full_declaration = match *output_type {
            ast::FunctionRetTy::Ty(ref ty) if ty.node == ast::TyKind::Never => {
                return Err(Error {
                    level: Level::Error,
                    span: Some(ty.span),
                    message: "panics across a C boundary are naughty!".into(),
                });
            },
            ast::FunctionRetTy::Default(..) => format!("void {}", buf_without_return),
            ast::FunctionRetTy::Ty(ref ty) => try_some!(types::rust_to_c(&*ty, &buf_without_return)),
        };

        buffer.push_str(&full_declaration);
        buffer.push_str(";\n\n");
    } else {
        return Err(Error {
            level: Level::Bug,
            span: Some(item.span),
            message: "`parse_fn` called on wrong `Item_`".into(),
        });
    }

    Ok(Some(buffer))
}


// TODO: Maybe it would be wise to use syntax::attr here.
/// Loop through a list of attributes.
///
/// Check that at least one attribute matches some criteria (usually #[repr(C)] or #[no_mangle])
/// and optionally retrieve a String from it (usually a docstring).
fn parse_attr<C, R>(attrs: &[ast::Attribute], check: C, retrieve: R) -> (bool, String)
    where C: Fn(&ast::Attribute) -> bool,
          R: Fn(&ast::Attribute) -> Option<String>,
{
    let mut check_passed = false;
    let mut retrieved_str = String::new();
    for attr in attrs {
        // Don't want to accidently set it to false after it's been set to true.
        if !check_passed { check_passed = check(attr); }
        // If this attribute has any strings to retrieve, retrieve them.
        if let Some(string) = retrieve(attr) { retrieved_str.push_str(&string); }
    }

    (check_passed, retrieved_str)
}

/// Check the attribute is #[repr(C)].
fn check_repr_c(attr: &ast::Attribute) -> bool {
    match attr.value.node {
        ast::MetaItemKind::List(ref word) if attr.name() == "repr" => match word.first() {
            Some(word) => match word.node {
                // Return true only if attribute is #[repr(C)].
                ast::NestedMetaItemKind::MetaItem(ref item) if item.name == "C" => true,
                _ => false,
            },
            _ => false,
        },
        _ => false,
    }
}

/// Check the attribute is #[no_mangle].
fn check_no_mangle(attr: &ast::Attribute) -> bool {
    match attr.value.node {
        ast::MetaItemKind::Word if attr.name() == "no_mangle" => true,
        _ => false,
    }
}

/// If the attribute is  a docstring, indent it the required amount and return it.
fn retrieve_docstring(attr: &ast::Attribute, prepend: &str) -> Option<String> {
    match attr.value.node {
        ast::MetaItemKind::NameValue(ref val) if attr.name() == "doc" => match val.node {
            // Docstring attributes omit the trailing newline.
            ast::LitKind::Str(ref docs, _) => Some(format!("{}{}\n", prepend, docs)),
            _ => unreachable!("docs must be literal strings"),
        },
        _ => None,
    }
}