Skip to main content

mruby_compiler2_sys/
lib.rs

1use std::ptr::null_mut;
2
3mod bindings {
4    #![allow(nonstandard_style)]
5    #![allow(unused)]
6    #![allow(unnecessary_transmutes)]
7    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
8
9    #[cfg(target_arch = "wasm32")]
10    unsafe extern "C" {
11        pub fn mrc_ccontext_new(mrb: *mut ::std::os::raw::c_void) -> *mut mrc_ccontext;
12        pub fn mrc_ccontext_free(c: *mut mrc_ccontext);
13        pub fn mrc_load_string_cxt(
14            c: *mut mrc_ccontext,
15            source: *mut *const u8,
16            length: usize,
17        ) -> *mut mrc_irep;
18        pub fn mrc_dump_irep(
19            c: *mut mrc_ccontext,
20            irep: *const mrc_irep,
21            flags: u8,
22            bin: *mut *mut u8,
23            bin_size: *mut usize,
24        ) -> ::std::os::raw::c_int;
25        pub fn mrc_irep_free(c: *mut mrc_ccontext, irep: *mut mrc_irep);
26    }
27
28    #[cfg(all(target_arch = "wasm32", feature = "std"))]
29    unsafe extern "C" {
30        pub fn fdopen(
31            arg1: ::std::os::raw::c_int,
32            arg2: *const ::std::os::raw::c_char,
33        ) -> *mut FILE;
34        pub fn mrc_codedump_all(c: *mut mrc_ccontext, irep: *mut mrc_irep);
35        pub fn mrc_dump_irep_cfunc(
36            c: *mut mrc_ccontext,
37            irep: *const mrc_irep,
38            flags: u8,
39            fp: *mut FILE,
40            initname: *const ::std::os::raw::c_char,
41        ) -> ::std::os::raw::c_int;
42    }
43}
44use bindings::{
45    MRC_DUMP_OK, mrc_ccontext, mrc_ccontext_free, mrc_ccontext_new, mrc_dump_irep, mrc_irep,
46    mrc_irep_free, mrc_load_string_cxt,
47};
48
49#[cfg(feature = "std")]
50use std::os::unix::io::AsRawFd;
51
52#[cfg(feature = "std")]
53use bindings::{FILE, fdopen, mrc_codedump_all, mrc_dump_irep_cfunc};
54
55#[derive(Debug)]
56pub struct MRubyCompiler2Error {
57    details: String,
58}
59
60impl MRubyCompiler2Error {
61    fn new(msg: &str) -> MRubyCompiler2Error {
62        MRubyCompiler2Error {
63            details: msg.to_string(),
64        }
65    }
66
67    #[allow(unused)]
68    fn from_error<E: std::error::Error>(msg: &str, err: E) -> MRubyCompiler2Error {
69        MRubyCompiler2Error {
70            details: format!("{}: {}", msg, err.to_string()),
71        }
72    }
73}
74
75impl std::fmt::Display for MRubyCompiler2Error {
76    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
77        write!(f, "{}", self.details)
78    }
79}
80
81impl std::error::Error for MRubyCompiler2Error {}
82
83pub struct MRubyCompiler2Context {
84    c: *mut mrc_ccontext,
85}
86
87impl MRubyCompiler2Context {
88    /// Creates a new MRubyCompiler2Context
89    pub unsafe fn new() -> Self {
90        unsafe {
91            let ccontext = mrc_ccontext_new(null_mut());
92            MRubyCompiler2Context { c: ccontext }
93        }
94    }
95
96    /// Compiles the given mruby code into mruby bytecode binary
97    /// Returns the bytecode as a `Vec<u8>`
98    pub unsafe fn compile(&mut self, code: &str) -> Result<Vec<u8>, MRubyCompiler2Error> {
99        unsafe {
100            let c_code = std::ffi::CString::new(code)
101                .map_err(|_| MRubyCompiler2Error::new("Code includes null bytes"))?;
102            let mut ptr = c_code.as_ptr() as *const u8;
103            let irep =
104                mrc_load_string_cxt(self.c, &mut ptr as *mut *const u8, c_code.as_bytes().len());
105
106            if irep.is_null() {
107                return Err(MRubyCompiler2Error::new("Failed to compile code"));
108            }
109
110            // Set dummy capacity, deduced from code length
111            // And leak for safety rather than memory efficiency
112            let bin: &'static mut [u8] = Vec::with_capacity(code.len() * 2).leak();
113            let bin_ptr = bin.as_mut_ptr();
114            let mut bin_size: usize = 0;
115
116            let result = mrc_dump_irep(
117                self.c,
118                irep as *mut mrc_irep,
119                0,
120                &bin_ptr as *const *mut u8 as *mut *mut u8,
121                &mut bin_size as *mut usize,
122            );
123            mrc_irep_free(self.c, irep as *mut mrc_irep);
124            if result as u32 != MRC_DUMP_OK {
125                return Err(MRubyCompiler2Error::new("Failed to dump irep binary"));
126            }
127
128            let newvec = Vec::from_raw_parts(bin_ptr, bin_size, bin_size);
129            Ok(newvec)
130        }
131    }
132
133    /// Dumps the compiled bytecode of the given mruby code to stdout
134    #[cfg(feature = "std")]
135    pub unsafe fn dump_bytecode(&mut self, code: &str) -> Result<(), MRubyCompiler2Error> {
136        unsafe {
137            let c_code = std::ffi::CString::new(code)
138                .map_err(|_| MRubyCompiler2Error::new("Code includes null bytes"))?;
139            let mut ptr = c_code.as_ptr() as *const u8;
140            let irep =
141                mrc_load_string_cxt(self.c, &mut ptr as *mut *const u8, c_code.as_bytes().len());
142
143            if irep.is_null() {
144                return Err(MRubyCompiler2Error::new("Failed to compile code"));
145            }
146
147            mrc_codedump_all(self.c, irep as *mut mrc_irep);
148            mrc_irep_free(self.c, irep as *mut mrc_irep);
149            Ok(())
150        }
151    }
152
153    /// Compiles the given mruby code and writes the bytecode to the specified file path
154    #[cfg(feature = "std")]
155    pub unsafe fn compile_to_file(
156        &mut self,
157        code: &str,
158        path: &std::path::Path,
159    ) -> Result<(), Box<dyn std::error::Error>> {
160        let bin = unsafe { self.compile(code) }?;
161        let mut out = std::fs::File::create(path)?;
162        std::io::Write::write_all(&mut out, &bin)?;
163        Ok(())
164    }
165
166    /// Compiles the given mruby code and writes the bytecode as a C function to the specified file path
167    #[cfg(feature = "std")]
168    pub unsafe fn compile_to_c_function(
169        &mut self,
170        code: &str,
171        initname: &str,
172        path: &std::path::Path,
173    ) -> Result<(), MRubyCompiler2Error> {
174        let out = std::fs::File::create(path)
175            .map_err(|e| MRubyCompiler2Error::from_error("Failed to create file", e))?;
176
177        unsafe {
178            let c_code = std::ffi::CString::new(code)
179                .map_err(|e| MRubyCompiler2Error::from_error("Code includes null bytes", e))?;
180            let mut ptr = c_code.as_ptr() as *const u8;
181            let irep =
182                mrc_load_string_cxt(self.c, &mut ptr as *mut *const u8, c_code.as_bytes().len());
183
184            if irep.is_null() {
185                return Err(MRubyCompiler2Error::new("Failed to compile code"));
186            }
187            let fd = out.as_raw_fd();
188            let mode_str = std::ffi::CString::new("w").unwrap();
189            let fp = fdopen(fd, mode_str.as_ptr());
190            std::mem::forget(out);
191
192            let initname = std::ffi::CString::new(initname)
193                .map_err(|e| MRubyCompiler2Error::from_error("Initname includes null bytes", e))?;
194
195            let result = mrc_dump_irep_cfunc(self.c, irep, 0, fp as *mut FILE, initname.as_ptr());
196            mrc_irep_free(self.c, irep as *mut mrc_irep);
197            if result as u32 != MRC_DUMP_OK {
198                return Err(MRubyCompiler2Error::new("Failed to dump irep binary"));
199            }
200            Ok(())
201        }
202    }
203}
204
205impl Drop for MRubyCompiler2Context {
206    fn drop(&mut self) {
207        unsafe {
208            mrc_ccontext_free(self.c);
209        }
210    }
211}