use std::{
collections::{hash_map::DefaultHasher, HashMap},
env,
ffi::OsStr,
fs,
hash::{Hash, Hasher},
io,
process::{Child, Command, Stdio},
};
use crate::{FuncImplType, Keyword, Word, Words};
mod splrs;
pub struct RustApp {
binary: String,
dir: String,
}
impl RustApp {
pub fn get_binary(&self) -> &str {
&self.binary
}
pub fn execute<I, S>(&self, args: I) -> Result<Child, io::Error>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::new(self.binary.clone()).args(args).spawn()
}
pub fn delete(self) {
fs::remove_dir_all(self.dir).expect("unable to delete RustApp");
}
}
pub struct RustFunction {
fn_name: String,
content: String,
}
pub struct RustAppBuilder {
rust_functions: Vec<RustFunction>,
to_embed: HashMap<String, String>,
default_file: String,
name: Option<String>,
}
impl Hash for RustAppBuilder {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_usize(self.rust_functions.len());
for f in &self.rust_functions {
f.fn_name.hash(state);
}
for (k, _) in &self.to_embed {
k.hash(state);
}
}
}
impl RustAppBuilder {
pub fn new() -> RustAppBuilder {
Self {
rust_functions: Vec::new(),
to_embed: HashMap::new(),
default_file: "repl.spl".to_owned(),
name: None,
}
}
pub fn add_source(&mut self, name: String, source: String) {
self.to_embed.insert(name, source);
}
pub fn set_name(&mut self, name: String) {
self.name = Some(name);
}
pub fn prepare(&mut self, spl: Words) -> bool {
let mut needs_new = false;
for word in spl.words {
match word {
Word::Key(Keyword::FuncOf(name, content, FuncImplType::Rust)) => {
self.rust_functions.push(splrs::to_rust(name, content));
needs_new = true;
}
_ => (),
}
}
needs_new
}
pub fn set_default_file(&mut self, name: String) {
self.default_file = name;
}
pub fn build(self, output: bool) -> Result<RustApp, io::Error> {
let tmp = "."; let name = match self.name {
Some(x) => x,
None => {
let mut hash = DefaultHasher::new();
self.hash(&mut hash);
let hash = hash.finish();
hash.to_string()
}
};
let _ = Command::new("cargo")
.arg("new")
.arg(format!("spl-{name}"))
.current_dir(tmp)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap()
.wait();
Command::new("cargo")
.arg("add")
.arg(format!("spl@{}", env!("CARGO_PKG_VERSION")))
.current_dir(format!("{tmp}/spl-{name}"))
.stdout(Stdio::null())
.stderr(if output { Stdio::inherit() } else { Stdio::null() })
.spawn()
.unwrap()
.wait()?;
let mut runtime_init = String::new();
let mut code = String::new();
for func in self.rust_functions.into_iter().enumerate() {
code += &format!(
"fn spl_oxidizer_{}(stack: &mut Stack) -> OError {{ {} Ok(()) }}\n",
func.0, func.1.content
);
runtime_init += &format!(
"rt.native_functions.insert({:?}, (0, FuncImpl::Native(spl_oxidizer_{})));\n",
func.1.fn_name, func.0
)
}
for (name, data) in self.to_embed.into_iter() {
runtime_init += &format!("rt.embedded_files.insert({:?}, {:?});\n", name, data);
}
fs::write(
format!("{tmp}/spl-{name}/src/main.rs"),
stringify! {
use spl::{runtime::*, *};
use std::env::args;
fn main() {
let mut rt = Runtime::new();
runtime_init
rt.set();
if let Err(x) = start_file_in_runtime(
&args()
.nth(1)
.unwrap_or("default_file".to_owned()),
) {
println!("{x:?}");
}
Runtime::reset();
}
}
.to_owned()
.replace("default_file", &self.default_file)
.replace("runtime_init", &runtime_init)
+ &code,
)?;
Command::new("cargo")
.arg("build")
.arg("--release")
.current_dir(format!("{tmp}/spl-{name}"))
.stdout(if output {
Stdio::inherit()
} else {
Stdio::null()
})
.stderr(if output {
Stdio::inherit()
} else {
Stdio::null()
})
.spawn()
.unwrap()
.wait()?;
Ok(RustApp {
dir: format!("{tmp}/spl-{name}"),
binary: {
let dir = format!("{tmp}/spl-{name}/target/release/");
fs::read_dir(dir)
.expect("unable to build: dir was not created.")
.filter(|x| {
let x = x
.as_ref()
.expect("file system did something i cannot comprehend");
let n = x.file_name().into_string().unwrap();
x.file_type().expect("file system uhhh?????").is_file()
&& !n.ends_with(".d")
&& !n.starts_with(".")
})
.next()
.expect("cargo was unable to build the binary")
.expect("file system did something i cannot comprehend")
.path()
.into_os_string()
.into_string()
.expect("bad unicode in file path")
},
})
}
}
impl Default for RustAppBuilder {
fn default() -> Self {
Self::new()
}
}