ffi_gen/
lib.rs

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
//! # ffi-gen
//!
//! Call rust from any language.
#![deny(missing_docs)]
#![feature(vec_into_raw_parts)]

mod abi;
mod dart;
mod js;
mod parser;
mod rust;

use crate::abi::{
    export, import, AbiFunction, AbiFuture, AbiIter, AbiObject, AbiStream, AbiType, FunctionType,
    NumType, Return, Var,
};
use crate::dart::DartGenerator;
use crate::js::{JsGenerator, TsGenerator, WasmMultiValueShim};
use crate::parser::Interface;
use crate::rust::RustGenerator;
use anyhow::{Context, Result};
use std::path::Path;
use std::process::Command;

pub use crate::abi::Abi;

/// Main entry point to `ffi-gen`.
pub struct FfiGen {
    iface: Interface,
}

impl FfiGen {
    /// Takes a path to an ffi-gen interface description file and constructs
    /// a new `FfiGen` instance.
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
        let s = std::fs::read_to_string(path)?;
        let iface = Interface::parse(&s)?;
        Ok(Self { iface })
    }

    /// Generates the rust api.
    pub fn generate_rust(&self, abi: Abi) -> Result<String> {
        let rust = RustGenerator::new(abi);
        let rust = rust.generate(self.iface.clone()).to_file_string()?;
        Ok(rust)
    }

    /// Patches the ffi functions in a wasm blob to use multi-value returns.
    pub fn wasm_multi_value_shim<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        WasmMultiValueShim::new().run(path, self.iface.clone())
    }

    /// Generates dart bindings for the rust api.
    pub fn generate_dart<P: AsRef<Path>>(
        &self,
        path: P,
        library: &str,
        cdylib: &str,
    ) -> Result<()> {
        let dart = DartGenerator::new(library.to_string(), cdylib.to_string());
        let dart = dart.generate(self.iface.clone()).to_file_string()?;
        std::fs::write(path.as_ref(), dart)?;
        let status = Command::new(if cfg!(windows) { "dart.bat" } else { "dart" })
            .arg("format")
            .arg(path.as_ref())
            .status()
            .context("dart not installed")?;
        if !status.success() {
            anyhow::bail!("dart format failed");
        }
        Ok(())
    }

    /// Generates js bindings for the rust api.
    pub fn generate_js<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let js = JsGenerator::default();
        let js = js.generate(self.iface.clone()).to_file_string()?;
        std::fs::write(path.as_ref(), js)?;
        let status = Command::new("prettier")
            .arg("--write")
            .arg(path.as_ref())
            .status()
            .context("prettier not installed")?;
        if !status.success() {
            anyhow::bail!("prettier failed");
        }
        Ok(())
    }

    /// Generates typescript type definitions for the js bindings.
    pub fn generate_ts<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let ts = TsGenerator::default();
        let ts = ts.generate(self.iface.clone()).to_file_string()?;
        std::fs::write(path.as_ref(), ts)?;
        let status = Command::new("prettier")
            .arg("--write")
            .arg(path.as_ref())
            .status()
            .context("prettier not installed")?;
        if !status.success() {
            anyhow::bail!("prettier failed");
        }
        Ok(())
    }
}

#[cfg(feature = "test_runner")]
#[doc(hidden)]
pub mod test_runner {
    pub use crate::dart::test_runner::compile_pass as compile_pass_dart;
    pub use crate::js::test_runner::compile_pass as compile_pass_js;
    pub use crate::js::test_runner::compile_pass_ts;
    pub use crate::rust::test_runner::compile_pass as compile_pass_rust;

    #[macro_export]
    macro_rules! compile_pass {
        ($ident:ident, $iface:expr, ($($api:tt)*), ($($rust:tt)*), ($($dart:tt)*), ($($js:tt)*), ($($ts:tt)*)) => {
            mod $ident {
                #[test]
                fn rust() {
                    $crate::test_runner::compile_pass_rust($iface, genco::quote!($($api)*), genco::quote!($($rust)*)).unwrap();
                }

                #[test]
                fn dart() {
                    $crate::test_runner::compile_pass_dart($iface, genco::quote!($($api)*), genco::quote!($($dart)*)).unwrap();
                }

                #[test]
                fn js() {
                    $crate::test_runner::compile_pass_js($iface, genco::quote!($($api)*), genco::quote!($($js)*)).unwrap();
                }

                #[test]
                fn ts() {
                    $crate::test_runner::compile_pass_ts($iface, genco::quote!($($ts)*)).unwrap();
                }
            }
        }
    }

    #[macro_export]
    macro_rules! compile_pass_no_js {
        ($ident:ident, $iface:expr, ($($api:tt)*), ($($rust:tt)*), ($($dart:tt)*)) => {
            mod $ident {
                #[test]
                fn rust() {
                    $crate::test_runner::compile_pass_rust($iface, genco::quote!($($api)*), genco::quote!($($rust)*)).unwrap();
                }

                #[test]
                fn dart() {
                    $crate::test_runner::compile_pass_dart($iface, genco::quote!($($api)*), genco::quote!($($dart)*)).unwrap();
                }
            }
        }
    }
}