1use 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#[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
437pub struct Builder {
441 class_name: String,
442 dll_name: String,
443 rust_code: String,
444 sconfig: SymbolConfigManager
445}
446
447impl Builder {
448 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 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 pub fn ignore(mut self, ignores: &[&str]) -> Self {
481 self.sconfig.ignores.add_static_array(ignores);
482 self
483 }
484
485 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 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}