csharpbindgen/
lib.rs

1//! csharpbindgen is a library for generating low-level C# bindings
2//! from Rust code.
3//! 
4//! It is currently in a very primitive state, largely designed for use by the
5//! [Unity Pathfinder plugin][plugin] and missing many features.
6//! 
7//! ## Quick start
8//! 
9//! The library is intended for use via a [Cargo build script][build].
10//! 
11//! Here's an example of a simple program that converts some simple Rust code
12//! into C#:
13//! 
14//! ```
15//! let rust = r#"
16//!   pub unsafe extern "C" fn my_func(foo: i32) -> f32 { /* ... */ }
17//! "#;
18//! 
19//! let code = csharpbindgen::Builder::new("MyDll", rust.to_string())
20//!     .class_name("MyStuff")
21//!     .generate()
22//!     .unwrap();
23//!
24//! println!("{}", code);
25//! ```
26//!
27//! This will print out something like the following C# code:
28//! 
29//! ```csharp
30//! // This file has been auto-generated, please do not edit it.
31//!
32//! using System;
33//! using System.Runtime.InteropServices;
34//!
35//! internal class MyStuff {
36//!     [DllImport("MyDll")]
37//!     internal static extern float my_func(Int32 foo);
38//! }
39//! ```
40//! 
41//! For a more complete example, see the Unity Pathfinder plugin's [`build.rs`][].
42//! 
43//! 
44//! [plugin]: https://github.com/toolness/pathfinder-unity-fun
45//! [build]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
46//! [`build.rs`]: https://github.com/toolness/pathfinder-unity-fun/blob/master/build.rs
47
48use std::collections::HashMap;
49use std::borrow::Borrow;
50use std::fmt::{Formatter, Display};
51use std::fmt;
52use std::rc::Rc;
53use syn::Item;
54
55mod error;
56mod symbol_config;
57mod ignores;
58
59use symbol_config::{SymbolConfigManager, SymbolConfig};
60use error::Result;
61pub use error::Error;
62
63const INDENT: &'static str = "    ";
64
65/// Enumeration for C#'s access modifiers.
66#[derive(Clone, Copy)]
67pub enum CSAccess {
68    Private,
69    Protected,
70    Internal,
71    Public
72}
73
74impl Display for CSAccess {
75    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
76        write!(f, "{}", match self {
77            CSAccess::Private => "private",
78            CSAccess::Protected => "protected",
79            CSAccess::Internal => "internal",
80            CSAccess::Public => "public"
81        })
82    }
83}
84
85impl Default for CSAccess {
86    fn default() -> Self {
87        CSAccess::Internal
88    }
89}
90
91struct CSTypeDef {
92    name: String,
93    ty: CSType
94}
95
96impl CSTypeDef {
97    pub fn from_rust_type_def(rust_type_def: &syn::ItemType) -> Result<Self> {
98        Ok(CSTypeDef {
99            name: rust_type_def.ident.to_string(),
100            ty: CSType::from_rust_type(&rust_type_def.ty)?
101        })
102    }
103}
104
105#[derive(Clone)]
106struct CSType {
107    name: String,
108    is_ptr: bool,
109    st: Option<Rc<CSStruct>>
110}
111
112impl CSType {
113    pub fn from_rust_type(rust_type: &syn::Type) -> Result<Self> {
114        match rust_type {
115            syn::Type::Path(type_path) => {
116                let last = type_path.path.segments.last()
117                  .expect("expected at least one path segment on type!");
118                Ok(CSType {
119                    name: last.value().ident.to_string(),
120                    is_ptr: false,
121                    st: None
122                })
123            },
124            syn::Type::Ptr(type_ptr) => {
125                let mut wrapped_type = CSType::from_rust_type(&type_ptr.elem)?;
126                if wrapped_type.is_ptr {
127                    return unsupported(format!(
128                        "double pointers for {} are unsupported!", wrapped_type.name
129                    ));
130                }
131                wrapped_type.is_ptr = true;
132                Ok(wrapped_type)
133            },
134            _ => {
135                unsupported(format!("the type is unsupported"))
136            }
137        }
138    }
139}
140
141impl Display for CSType {
142    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
143        let name = to_cs_primitive(&self.name);
144        if self.is_ptr {
145            if self.st.is_some() {
146                write!(f, "ref {}", name)
147            } else {
148                write!(f, "IntPtr /* {} */", name)
149            }
150        } else {
151            write!(f, "{}", name)
152        }
153    }
154}
155
156struct CSConst {
157    name: String,
158    ty: CSType,
159    value: String,
160    cfg: SymbolConfig
161}
162
163impl CSConst {
164    pub fn from_rust_const(rust_const: &syn::ItemConst, cfg: SymbolConfig) -> Result<Self> {
165        let value = if let syn::Expr::Lit(expr_lit) = &rust_const.expr.borrow() {
166            if let syn::Lit::Int(lit_int) = &expr_lit.lit {
167                lit_int.value().to_string()
168            } else {
169                return unsupported(format!(
170                    "Unsupported const expression literal value: {:?}", expr_lit))
171            }
172        } else {
173            return unsupported(format!(
174                "Unsupported const expression value: {:?}", rust_const.expr))
175        };
176        Ok(CSConst {
177            name: munge_cs_name(rust_const.ident.to_string()),
178            ty: CSType::from_rust_type(&rust_const.ty)?,
179            value,
180            cfg
181        })
182    }
183}
184
185struct CSStructField {
186    name: String,
187    ty: CSType,
188}
189
190impl CSStructField {
191    pub fn from_named_rust_field(rust_field: &syn::Field) -> Result<Self> {
192        Ok(CSStructField {
193            name: munge_cs_name(rust_field.ident.as_ref().unwrap().to_string()),
194            ty: CSType::from_rust_type(&rust_field.ty)?
195        })
196    }
197
198    pub fn to_string(&self) -> String {
199        to_cs_var_decl(&self.ty, &self.name)
200    }
201}
202
203struct CSStruct {
204    name: String,
205    fields: Vec<CSStructField>,
206    cfg: SymbolConfig
207}
208
209impl CSStruct {
210    pub fn from_rust_struct(rust_struct: &syn::ItemStruct, cfg: SymbolConfig) -> Result<Self> {
211        let mut fields = vec![];
212
213        if let syn::Fields::Named(rust_fields) = &rust_struct.fields {
214            for rust_field in rust_fields.named.iter() {
215                fields.push(CSStructField::from_named_rust_field(rust_field)?);
216            }
217        }
218        Ok(CSStruct {
219            name: rust_struct.ident.to_string(),
220            fields,
221            cfg
222        })
223    }
224}
225
226impl Display for CSStruct {
227    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
228        writeln!(f, "[Serializable]")?;
229        writeln!(f, "[StructLayout(LayoutKind.Sequential)]")?;
230        writeln!(f, "{} struct {} {{", self.cfg.access, self.name)?;
231        for field in self.fields.iter() {
232            writeln!(f, "{}{} {};", INDENT, self.cfg.access, field.to_string())?;
233        }
234
235        let constructor_args: Vec<String> = self.fields
236          .iter()
237          .map(|field| field.to_string())
238          .collect();
239        writeln!(f, "\n{}{} {}({}) {{", INDENT, self.cfg.access, self.name, constructor_args.join(", "))?;
240        for field in self.fields.iter() {
241            writeln!(f, "{}{}this.{} = {};", INDENT, INDENT, field.name, field.name)?;
242        }
243        writeln!(f, "{}}}", INDENT)?;
244
245        writeln!(f, "}}")
246    }
247}
248
249struct CSFuncArg {
250    name: String,
251    ty: CSType
252}
253
254impl CSFuncArg {
255    pub fn from_rust_arg_captured(rust_arg: &syn::ArgCaptured) -> Result<Self> {
256        if let syn::Pat::Ident(pat_ident) = &rust_arg.pat {
257            Ok(CSFuncArg {
258                name: munge_cs_name(pat_ident.ident.to_string()),
259                ty: CSType::from_rust_type(&rust_arg.ty)?
260            })
261        } else {
262            unsupported(format!("captured arg pattern is unsupported: {:?}", rust_arg.pat))
263        }
264    }
265
266    pub fn to_string(&self) -> String {
267        to_cs_var_decl(&self.ty, &self.name)
268    }
269}
270
271struct CSFunc {
272    name: String,
273    args: Vec<CSFuncArg>,
274    return_ty: Option<CSType>,
275    cfg: SymbolConfig
276}
277
278impl CSFunc {
279    pub fn from_rust_fn(rust_fn: &syn::ItemFn, cfg: SymbolConfig) -> Result<Self> {
280        let mut args = vec![];
281
282        for input in rust_fn.decl.inputs.iter() {
283            if let syn::FnArg::Captured(cap) = input {
284                args.push(CSFuncArg::from_rust_arg_captured(&cap)?);
285            } else {
286                return unsupported(format!(
287                    "Input for function '{}' is unsupported: {:?}",
288                    rust_fn.ident.to_string(),
289                    input
290                ));
291            }
292        }
293
294        let return_ty = match &rust_fn.decl.output {
295            syn::ReturnType::Default => None,
296            syn::ReturnType::Type(_, ty) => {
297                Some(CSType::from_rust_type(&ty)?)
298            }
299        };
300
301        Ok(CSFunc {
302            name: rust_fn.ident.to_string(),
303            args,
304            return_ty,
305            cfg
306        })
307    }
308}
309
310impl Display for CSFunc {
311    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
312        let return_ty = match &self.return_ty {
313            None => String::from("void"),
314            Some(ty) => ty.to_string()
315        };
316        let args: Vec<String> = self.args
317          .iter()
318          .map(|arg| arg.to_string())
319          .collect();
320        write!(f, "{} static extern {} {}({});", self.cfg.access, return_ty, self.name, args.join(", "))
321    }
322}
323
324struct CSFile {
325    class_name: String,
326    dll_name: String,
327    consts: Vec<CSConst>,
328    structs: Vec<Rc<CSStruct>>,
329    funcs: Vec<CSFunc>,
330    type_defs: HashMap<String, CSTypeDef>
331}
332
333impl CSFile {
334    pub fn new(class_name: String, dll_name: String) -> Self {
335        CSFile {
336            class_name,
337            dll_name,
338            consts: vec![],
339            structs: vec![],
340            funcs: vec![],
341            type_defs: HashMap::new()
342        }
343    }
344
345    pub fn populate_from_rust_file(
346        &mut self,
347        rust_file: &syn::File,
348        cfg_mgr: &SymbolConfigManager
349    ) -> Result<()> {
350        for item in rust_file.items.iter() {
351            match item {
352                Item::Const(item_const) => {
353                    if let Some(cfg) = cfg_mgr.get(&item_const.ident) {
354                        let cs_const = error::add_ident(
355                            CSConst::from_rust_const(&item_const, cfg), &item_const.ident)?;
356                        self.consts.push(cs_const);
357                    }
358                },
359                Item::Struct(item_struct) => {
360                    if let Some(cfg) = cfg_mgr.get(&item_struct.ident) {
361                        let cs_struct = error::add_ident(
362                            CSStruct::from_rust_struct(&item_struct, cfg), &item_struct.ident)?;
363                        self.structs.push(Rc::new(cs_struct));
364                    }
365                },
366                Item::Fn(item_fn) => {
367                    if item_fn.abi.is_some() {
368                        if let Some(cfg) = cfg_mgr.get(&item_fn.ident) {
369                            let cs_func = error::add_ident(
370                                CSFunc::from_rust_fn(&item_fn, cfg), &item_fn.ident)?;
371                            self.funcs.push(cs_func);
372                        }
373                    }
374                },
375                Item::Type(item_type) => {
376                    if let Some(_cfg) = cfg_mgr.get(&item_type.ident) {
377                        let type_def = error::add_ident(
378                            CSTypeDef::from_rust_type_def(&item_type), &item_type.ident)?;
379                        self.type_defs.insert(type_def.name.clone(), type_def);
380                    }
381                },
382                _ => {}
383            }
384        }
385
386        Ok(())
387    }
388
389    fn resolve_types(&mut self) -> Result<()> {
390        let mut struct_map: HashMap<&str, &Rc<CSStruct>> = HashMap::new();
391
392        for st in self.structs.iter() {
393            struct_map.insert(&st.name, &st);
394        }
395
396        for func in self.funcs.iter_mut() {
397            for arg in func.args.iter_mut() {
398                if let Some(ty) = resolve_type_def(&arg.ty, &self.type_defs)? {
399                    arg.ty = ty;
400                }
401                if let Some(st) = struct_map.get(&arg.ty.name.as_ref()) {
402                    arg.ty.st = Some((*st).clone());
403                }
404            }
405            if let Some(return_ty) = &func.return_ty {
406                if let Some(ty) = resolve_type_def(return_ty, &self.type_defs)? {
407                    func.return_ty = Some(ty);
408                }
409            }
410        }
411
412        Ok(())
413    }
414}
415
416impl Display for CSFile {
417    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
418        writeln!(f, "// This file has been auto-generated, please do not edit it.\n")?;
419        writeln!(f, "using System;")?;
420        writeln!(f, "using System.Runtime.InteropServices;\n")?;
421
422        for st in self.structs.iter() {
423            writeln!(f, "{}", st)?;
424        }
425        writeln!(f, "{} class {} {{", CSAccess::default(), self.class_name)?;
426        for con in self.consts.iter() {
427            writeln!(f, "{}{} const {} {} = {};\n", INDENT, con.cfg.access, con.ty, con.name, con.value)?;
428        }
429        for func in self.funcs.iter() {
430            writeln!(f, "{}[DllImport(\"{}\")]", INDENT, self.dll_name)?;
431            writeln!(f, "{}{}\n", INDENT, func)?;
432        }
433        writeln!(f, "}}")
434    }
435}
436
437/// A [builder pattern] for the Rust-to-C# conversion process.
438/// 
439/// [builder pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
440pub struct Builder {
441    class_name: String,
442    dll_name: String,
443    rust_code: String,
444    sconfig: SymbolConfigManager
445}
446
447impl Builder {
448    /// Creates a new instance with the following arguments:
449    /// 
450    /// * `dll_name` is the name of the DLL that the C# `DllImport` attribute
451    ///   will be bound to for all exported functions.
452    /// 
453    /// * `rust_code` is the source Rust code to convert into C#.
454    pub fn new<T: AsRef<str>>(
455        dll_name: T,
456        rust_code: String
457    ) -> Self {
458        Builder {
459            class_name: String::from("RustExports"),
460            dll_name: String::from(dll_name.as_ref()),
461            rust_code,
462            sconfig: SymbolConfigManager::new()
463        }
464    }
465
466    /// Sets the name of the C# class that will contain all exported functions.
467    /// If never called, the C# class will be called `RustExports`.
468    pub fn class_name<T: AsRef<str>>(mut self, class_name: T) -> Self {
469        self.class_name = String::from(class_name.as_ref());
470        self
471    }
472
473    /// Specifies a list of Rust identifier patterns to be ignored (i.e., not
474    /// exported to C#).
475    /// 
476    /// The pattern syntax is currently very simple: if it ends with a `*`, it
477    /// matches any Rust identifier that starts with the part of the pattern before
478    /// the `*` (e.g., `Boop*` matches `BoopJones` and `BoopFoo`). Otherwise, it
479    /// represents an exact match to a Rust identifier.
480    pub fn ignore(mut self, ignores: &[&str]) -> Self {
481        self.sconfig.ignores.add_static_array(ignores);
482        self
483    }
484
485    /// Specifies that the given Rust identifier should be exported to C# with the
486    /// given C# access modifier. By default, all exports are given the `internal`
487    /// access modifier.
488    pub fn access<T: AsRef<str>>(mut self, symbol_name: T, access: CSAccess) -> Self {
489        self.sconfig.config_map.insert(String::from(symbol_name.as_ref()), SymbolConfig {
490            access
491        });
492        self
493    }
494
495    /// Performs the conversion of source Rust code to C#.
496    pub fn generate(self) -> Result<String> {
497        let syntax = parse_file(&self.rust_code)?;
498        let mut program = CSFile::new(self.class_name, self.dll_name);
499        program.populate_from_rust_file(&syntax, &self.sconfig)?;
500        program.resolve_types()?;
501        Ok(format!("{}", program))
502    }
503}
504
505fn parse_file(rust_code: &String) -> Result<syn::File> {
506    match syn::parse_file(rust_code) {
507        Ok(result) => Ok(result),
508        Err(err) => Err(Error::SynError(err))
509    }
510}
511
512fn resolve_type_def(ty: &CSType, type_defs: &HashMap<String, CSTypeDef>) -> Result<Option<CSType>> {
513    if let Some(type_def) = type_defs.get(&ty.name) {
514        if ty.is_ptr && type_def.ty.is_ptr {
515            unsupported(format!(
516                "double pointer to {} via type {} is unsupported!",
517                type_def.ty.name,
518                type_def.name
519            ))
520        } else {
521            Ok(Some(type_def.ty.clone()))
522        }
523    } else {
524        Ok(None)
525    }
526}
527
528fn munge_cs_name(name: String) -> String {
529    match name.as_ref() {
530        "string" => String::from("str"),
531        _ => name
532    }
533}
534
535fn to_cs_primitive<'a>(type_name: &'a str) -> &'a str {
536    match type_name {
537        "u8" => "byte",
538        "f32" => "float",
539        "i32" => "Int32",
540        "u32" => "UInt32",
541        "usize" => "UIntPtr",
542        _ => type_name
543    }
544}
545
546fn to_cs_var_decl<T: AsRef<str>>(ty: &CSType, name: T) -> String {
547    format!("{} {}", ty, name.as_ref())
548}
549
550fn unsupported<T>(msg: String) -> Result<T> {
551    Err(Error::UnsupportedError(msg, None))
552}
553
554#[cfg(test)]
555mod tests {
556    use super::*;
557
558    #[test]
559    fn test_it_errors_on_invalid_rust_code() {
560        let err = Builder::new("Blarg", String::from("HELLO THERE"))
561          .generate()
562          .unwrap_err();
563        let err_msg = format!("{}", err);
564        assert_eq!(err_msg, "Couldn't parse Rust code: expected `!`");
565    }
566
567    #[test]
568    fn test_it_errors_on_unsupported_rust_code() {
569        let err = Builder::new("Blarg", String::from(r#"
570            pub type MyFunkyThing = fn() -> void;
571        "#)).generate().unwrap_err();
572        assert_eq!(
573            format!("{}", err),
574            "Unable to export C# code while processing symbol \"MyFunkyThing\" because the type is unsupported"
575        );
576    }
577}