rumya_binding/
lib.rs

1//! # Rumya Binding
2//! Rumya programming language's binding for Rust.
3//! You can utilize Rumya program embedded in your project.
4//! ```
5//! let rumya = Rumya::new().set_rumya(PATH);
6//! let result = rumya.eval::<i32>("let x = 0. for i in 1 ~ 10 do x += i. x");
7//! assert_eq!(result, Some(45));
8//! ```
9use std::io::Write;
10use std::process::Command;
11use std::str::FromStr;
12use std::{
13    fs::{remove_file, File},
14    path::Path,
15};
16
17macro_rules! some {
18    ($result_value: expr) => {
19        if let Ok(ok) = $result_value {
20            Some(ok)
21        } else {
22            None
23        }
24    };
25}
26
27/// # Environment
28/// This structure is an environment that manages interpreter paths of Rumya and her base technology Lamuta.
29#[derive(Clone)]
30pub struct Rumya {
31    rumya_path: String,
32    lamuta_path: String,
33}
34
35impl Rumya {
36    /// # Constructer
37    /// Rumya's interpreter path would setted by `rumya.lm`, Lamuta's one is `lamuta` in default.
38    pub fn new() -> Self {
39        Self {
40            rumya_path: "rumya.lm".to_string(),
41            lamuta_path: "lamuta".to_string(),
42        }
43    }
44
45    /// # Rumya setter
46    /// This methods sets interpreter path of Rumya
47    pub fn set_rumya(&self, path: &str) -> Self {
48        let path = Path::new(path);
49        Self {
50            rumya_path: path.display().to_string(),
51            ..self.clone()
52        }
53    }
54
55    /// # Lamuta setter
56    /// This methods sets interpreter path of Lamuta
57    pub fn set_lamuta(&self, path: &str) -> Self {
58        let path = Path::new(path);
59        Self {
60            lamuta_path: path.display().to_string(),
61            ..self.clone()
62        }
63    }
64
65    /// # Evaluater
66    /// This methods evaluate provided Rumya code and convert result value to type you specified.
67    /// It temporary creates file `Rumya-binding.temp.lm` for evaluate at runtime.
68    pub fn eval<T: Sized + FromStr>(&self, code: &str) -> Option<T> {
69        const TEMP_FILE_NAME: &str = "Rumya-binding.temp.lm";
70        let mut temp_file = some!(File::create(TEMP_FILE_NAME))?;
71        let code = format!("print\nbegin\n{code}\nend\n");
72        some!(temp_file.write_all(code.as_bytes()))?;
73
74        let output = some!(Command::new(&self.lamuta_path)
75            .args([&self.rumya_path, TEMP_FILE_NAME])
76            .output())?;
77
78        if output.status.success() {
79            let stdout = String::from_utf8_lossy(&output.stdout);
80            some!(remove_file(TEMP_FILE_NAME))?;
81            some!(T::from_str(stdout.lines().last()?))
82        } else {
83            return None;
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn it_works() {
94        // This path is only my environment, change your Rumya path when you test
95        const PATH: &str = "/Users/kajizukataichi/Desktop/repositories/Rumya/rumya.lm";
96        let rumya = Rumya::new().set_rumya(PATH);
97        let result = rumya.eval::<i32>("let x = 0. for i in 1 ~ 10 do x += i. x");
98        assert_eq!(result, Some(45));
99    }
100}