rumya_binding/
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
//! # Rumya
//! Rumya programming language's binding for Rust.
//! You can utilize Rumya program embedded in your project.
//! ```
//! let rumya = Rumya::new().set_rumya(PATH);
//! let result = rumya.eval::<i32>("let x = 0. for i in 1 ~ 10 do x += i. x");
//! assert_eq!(result, Some(45));
//! ```
use std::io::Write;
use std::process::Command;
use std::str::FromStr;
use std::{
    fs::{remove_file, File},
    path::Path,
};

macro_rules! some {
    ($result_value: expr) => {
        if let Ok(ok) = $result_value {
            Some(ok)
        } else {
            None
        }
    };
}

/// # Environment
/// This structure is an environment that manages interpreter paths of Rumya and her base technology Lamuta.
#[derive(Clone)]
pub struct Rumya {
    rumya_path: String,
    lamuta_path: String,
}

impl Rumya {
    /// # Constructer
    /// Rumya's interpreter path would setted by `rumya.lm`, Lamuta's one is `lamuta` in default.
    pub fn new() -> Self {
        Self {
            rumya_path: "rumya.lm".to_string(),
            lamuta_path: "lamuta".to_string(),
        }
    }

    /// # Rumya setter
    /// This methods sets interpreter path of Rumya
    pub fn set_rumya(&self, path: &str) -> Self {
        let path = Path::new(path);
        Self {
            rumya_path: path.display().to_string(),
            ..self.clone()
        }
    }

    /// # Lamuta setter
    /// This methods sets interpreter path of Lamuta
    pub fn set_lamuta(&self, path: &str) -> Self {
        let path = Path::new(path);
        Self {
            lamuta_path: path.display().to_string(),
            ..self.clone()
        }
    }

    /// # Evaluater
    /// This methods evaluate provided Rumya code and convert result value to type you specified.
    /// It temporary creates file `Rumya-binding.temp.lm` for evaluate at runtime.
    pub fn eval<T: Sized + FromStr>(&self, code: &str) -> Option<T> {
        const TEMP_FILE_NAME: &str = "Rumya-binding.temp.lm";
        let mut temp_file = some!(File::create(TEMP_FILE_NAME))?;
        let code = format!("print\nbegin\n{code}\nend\n");
        some!(temp_file.write_all(code.as_bytes()))?;

        let output = some!(Command::new(&self.lamuta_path)
            .args([&self.rumya_path, TEMP_FILE_NAME])
            .output())?;

        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout);
            some!(remove_file(TEMP_FILE_NAME))?;
            some!(T::from_str(stdout.lines().last()?))
        } else {
            return None;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        // This path is only my environment, change your Rumya path when you test
        const PATH: &str = "/Users/kajizukataichi/Desktop/repositories/Rumya/rumya.lm";
        let rumya = Rumya::new().set_rumya(PATH);
        let result = rumya.eval::<i32>("let x = 0. for i in 1 ~ 10 do x += i. x");
        assert_eq!(result, Some(45));
    }
}