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