C API wrapper (#14)

* std based C API

* Build and test C API with Travis

* Use full lto for release profile

* Add CI for GitHub Actions

* Move demangle crate to a `crates` directory

* Run rustfmt

* Use a `match` instead of `unwrap`

* Run rustfmt
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c242c5b..61465ce 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -12,7 +12,8 @@
     - uses: actions/checkout@master
     - name: Install Rust
       run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }}
-    - run: cargo test
+    - run: cargo build --all
+    - run: cargo test --all
 
   rustfmt:
     name: Rustfmt
diff --git a/Cargo.toml b/Cargo.toml
index 5b5a4e9..0701278 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,9 +11,15 @@
 Rust compiler symbol demangling.
 """
 
+[workspace]
+members = ["crates/capi"]
+
 [dependencies]
 core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core' }
 compiler_builtins = { version = '0.1.2', optional = true }
 
 [features]
 rustc-dep-of-std = ['core', 'compiler_builtins']
+
+[profile.release]
+lto = true
diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml
new file mode 100644
index 0000000..0f1fb07
--- /dev/null
+++ b/crates/capi/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "rustc-demangle-capi"
+version = "0.1.0"
+authors = ["Torste Aikio <zokier@gmail.com>"]
+
+[lib]
+name = "rustc_demangle"
+crate-type = ["staticlib", "cdylib"]
+
+[dependencies]
+rustc-demangle = { version = "0.1.16", path = "../.." }
diff --git a/crates/capi/include/rustc_demangle.h b/crates/capi/include/rustc_demangle.h
new file mode 100644
index 0000000..61c4aa1
--- /dev/null
+++ b/crates/capi/include/rustc_demangle.h
@@ -0,0 +1,18 @@
+#ifndef RUST_DEMANGLE_H_
+#define RUST_DEMANGLE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Demangles symbol given in `mangled` argument into `out` buffer
+//
+// Returns 0 if `mangled` is not Rust symbol or if `out` buffer is too small
+// Returns 1 otherwise
+int rustc_demangle(const char *mangled, char *out, size_t out_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // RUSTC_DEMANGLE_H_
diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs
new file mode 100644
index 0000000..51e3103
--- /dev/null
+++ b/crates/capi/src/lib.rs
@@ -0,0 +1,160 @@
+extern crate rustc_demangle;
+
+use std::io::Write;
+use std::os::raw::{c_char, c_int};
+
+/// C-style interface for demangling.
+/// Demangles symbol given in `mangled` argument into `out` buffer
+///
+/// Unsafe as it handles buffers by raw pointers.
+///
+/// Returns 0 if `mangled` is not Rust symbol or if `out` buffer is too small
+/// Returns 1 otherwise
+#[no_mangle]
+pub unsafe extern "C" fn rustc_demangle(
+    mangled: *const c_char,
+    out: *mut c_char,
+    out_size: usize,
+) -> c_int {
+    let mangled_str = match std::ffi::CStr::from_ptr(mangled).to_str() {
+        Ok(s) => s,
+        Err(_) => return 0,
+    };
+    match rustc_demangle::try_demangle(mangled_str) {
+        Ok(demangle) => {
+            let mut out_slice = std::slice::from_raw_parts_mut(out as *mut u8, out_size);
+            match write!(out_slice, "{:#}\0", demangle) {
+                Ok(_) => return 1,
+                Err(_) => return 0,
+            }
+        }
+        Err(_) => return 0,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std;
+    use std::os::raw::c_char;
+    #[test]
+    fn demangle_c_str_large() {
+        let mangled = "_ZN4testE\0";
+        let mut out_buf: Vec<u8> = vec![42; 8];
+        let res = unsafe {
+            super::rustc_demangle(
+                mangled.as_ptr() as *const c_char,
+                out_buf.as_mut_ptr() as *mut c_char,
+                8,
+            )
+        };
+        assert_eq!(res, 1);
+        let out_str = std::str::from_utf8(&out_buf[..5]).unwrap();
+        assert_eq!(out_str, "test\0");
+    }
+
+    #[test]
+    fn demangle_c_str_exact() {
+        let mangled = "_ZN4testE\0";
+        let mut out_buf: Vec<u8> = vec![42; 8];
+        let res = unsafe {
+            super::rustc_demangle(
+                mangled.as_ptr() as *const c_char,
+                out_buf.as_mut_ptr() as *mut c_char,
+                5,
+            )
+        };
+        assert_eq!(res, 1);
+        let out_str = std::str::from_utf8(&out_buf).unwrap();
+        assert_eq!(out_str, "test\0***");
+    }
+
+    #[test]
+    fn demangle_c_str_small() {
+        let mangled = "_ZN4testE\0";
+        let mut out_buf: Vec<u8> = vec![42; 8];
+        let res = unsafe {
+            super::rustc_demangle(
+                mangled.as_ptr() as *const c_char,
+                out_buf.as_mut_ptr() as *mut c_char,
+                4,
+            )
+        };
+        assert_eq!(res, 0);
+        let out_str = std::str::from_utf8(&out_buf[4..]).unwrap();
+        assert_eq!(out_str, "****");
+    }
+
+    #[test]
+    fn demangle_c_str_smaller() {
+        let mangled = "_ZN4testE\0";
+        let mut out_buf: Vec<u8> = vec![42; 8];
+        let res = unsafe {
+            super::rustc_demangle(
+                mangled.as_ptr() as *const c_char,
+                out_buf.as_mut_ptr() as *mut c_char,
+                3,
+            )
+        };
+        assert_eq!(res, 0);
+        let out_str = std::str::from_utf8(&out_buf[3..]).unwrap();
+        assert_eq!(out_str, "*****");
+    }
+
+    #[test]
+    fn demangle_c_str_zero() {
+        let mangled = "_ZN4testE\0";
+        let mut out_buf: Vec<u8> = vec![42; 8];
+        let res = unsafe {
+            super::rustc_demangle(
+                mangled.as_ptr() as *const c_char,
+                out_buf.as_mut_ptr() as *mut c_char,
+                0,
+            )
+        };
+        assert_eq!(res, 0);
+        let out_str = std::str::from_utf8(&out_buf).unwrap();
+        assert_eq!(out_str, "********");
+    }
+
+    #[test]
+    fn demangle_c_str_not_rust_symbol() {
+        let mangled = "la la la\0";
+        let mut out_buf: Vec<u8> = vec![42; 8];
+        let res = unsafe {
+            super::rustc_demangle(
+                mangled.as_ptr() as *const c_char,
+                out_buf.as_mut_ptr() as *mut c_char,
+                8,
+            )
+        };
+        assert_eq!(res, 0);
+    }
+
+    #[test]
+    fn demangle_c_str_null() {
+        let mangled = "\0";
+        let mut out_buf: Vec<u8> = vec![42; 8];
+        let res = unsafe {
+            super::rustc_demangle(
+                mangled.as_ptr() as *const c_char,
+                out_buf.as_mut_ptr() as *mut c_char,
+                8,
+            )
+        };
+        assert_eq!(res, 0);
+    }
+
+    #[test]
+    fn demangle_c_str_invalid_utf8() {
+        let mangled = [116, 101, 115, 116, 165, 0];
+        let mut out_buf: Vec<u8> = vec![42; 8];
+        let res = unsafe {
+            super::rustc_demangle(
+                mangled.as_ptr() as *const c_char,
+                out_buf.as_mut_ptr() as *mut c_char,
+                8,
+            )
+        };
+        assert_eq!(res, 0);
+    }
+}