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));
}
}