1use std::{
6 collections::{hash_map::DefaultHasher, HashMap},
7 env,
8 ffi::OsStr,
9 fs,
10 hash::{Hash, Hasher},
11 io,
12 process::{Child, Command, Stdio},
13};
14
15use crate::{FuncImplType, Keyword, Word, Words};
16
17mod splrs;
18
19pub struct RustApp {
21 binary: String,
23 dir: String,
25}
26
27impl RustApp {
28 pub fn get_binary(&self) -> &str {
30 &self.binary
31 }
32
33 pub fn execute<I, S>(&self, args: I) -> Result<Child, io::Error>
35 where
36 I: IntoIterator<Item = S>,
37 S: AsRef<OsStr>,
38 {
39 Command::new(self.binary.clone()).args(args).spawn()
40 }
41
42 pub fn delete(self) {
44 fs::remove_dir_all(self.dir).expect("unable to delete RustApp");
45 }
46}
47
48pub struct RustFunction {
50 fn_name: String,
51 content: String,
52}
53
54pub struct RustAppBuilder {
56 rust_functions: Vec<RustFunction>,
57 to_embed: HashMap<String, String>,
58 default_file: String,
59 name: Option<String>,
60}
61
62impl Hash for RustAppBuilder {
63 fn hash<H: Hasher>(&self, state: &mut H) {
64 state.write_usize(self.rust_functions.len());
65 for f in &self.rust_functions {
66 f.fn_name.hash(state);
67 }
68 for (k, _) in &self.to_embed {
69 k.hash(state);
70 }
71 }
72}
73
74impl RustAppBuilder {
75 pub fn new() -> RustAppBuilder {
76 Self {
77 rust_functions: Vec::new(),
78 to_embed: HashMap::new(),
79 default_file: "repl.spl".to_owned(),
80 name: None,
81 }
82 }
83
84 pub fn add_source(&mut self, name: String, source: String) {
86 self.to_embed.insert(name, source);
87 }
88
89 pub fn set_name(&mut self, name: String) {
91 self.name = Some(name);
92 }
93
94 pub fn prepare(&mut self, spl: Words) -> bool {
96 let mut needs_new = false;
97 for word in spl.words {
98 match word {
99 Word::Key(Keyword::FuncOf(name, content, FuncImplType::Rust)) => {
100 self.rust_functions.push(splrs::to_rust(name, content));
101 needs_new = true;
102 }
103 _ => (),
104 }
105 }
106 needs_new
107 }
108
109 pub fn set_default_file(&mut self, name: String) {
111 self.default_file = name;
112 }
113
114 pub fn build(self, output: bool) -> Result<RustApp, io::Error> {
116 let tmp = "."; let name = match self.name {
119 Some(x) => x,
120 None => {
121 let mut hash = DefaultHasher::new();
122 self.hash(&mut hash);
123 let hash = hash.finish();
124 hash.to_string()
125 }
126 };
127 let _ = Command::new("cargo")
128 .arg("new")
129 .arg(format!("spl-{name}"))
130 .current_dir(tmp)
131 .stdout(Stdio::null())
132 .stderr(Stdio::null())
133 .spawn()
134 .unwrap()
135 .wait();
136 Command::new("cargo")
137 .arg("add")
138 .arg(format!("spl@{}", env!("CARGO_PKG_VERSION")))
139 .current_dir(format!("{tmp}/spl-{name}"))
140 .stdout(Stdio::null())
141 .stderr(if output { Stdio::inherit() } else { Stdio::null() })
142 .spawn()
143 .unwrap()
144 .wait()?;
145 let mut runtime_init = String::new();
146 let mut code = String::new();
147 for func in self.rust_functions.into_iter().enumerate() {
148 code += &format!(
149 "fn spl_oxidizer_{}(stack: &mut Stack) -> OError {{ {} Ok(()) }}\n",
150 func.0, func.1.content
151 );
152 runtime_init += &format!(
153 "rt.native_functions.insert({:?}, (0, FuncImpl::Native(spl_oxidizer_{})));\n",
154 func.1.fn_name, func.0
155 )
156 }
157 for (name, data) in self.to_embed.into_iter() {
158 runtime_init += &format!("rt.embedded_files.insert({:?}, {:?});\n", name, data);
159 }
160 fs::write(
161 format!("{tmp}/spl-{name}/src/main.rs"),
162 stringify! {
163 use spl::{runtime::*, *};
164
165 use std::env::args;
166
167 fn main() {
168 let mut rt = Runtime::new();
169 runtime_init
170 rt.set();
171 if let Err(x) = start_file_in_runtime(
172 &args()
173 .nth(1)
174 .unwrap_or("default_file".to_owned()),
175 ) {
176 println!("{x:?}");
177 }
178 Runtime::reset();
179 }
180 }
181 .to_owned()
182 .replace("default_file", &self.default_file)
183 .replace("runtime_init", &runtime_init)
184 + &code,
185 )?;
186 Command::new("cargo")
187 .arg("build")
188 .arg("--release")
189 .current_dir(format!("{tmp}/spl-{name}"))
190 .stdout(if output {
191 Stdio::inherit()
192 } else {
193 Stdio::null()
194 })
195 .stderr(if output {
196 Stdio::inherit()
197 } else {
198 Stdio::null()
199 })
200 .spawn()
201 .unwrap()
202 .wait()?;
203 Ok(RustApp {
204 dir: format!("{tmp}/spl-{name}"),
205 binary: {
206 let dir = format!("{tmp}/spl-{name}/target/release/");
208 fs::read_dir(dir)
209 .expect("unable to build: dir was not created.")
210 .filter(|x| {
211 let x = x
212 .as_ref()
213 .expect("file system did something i cannot comprehend");
214 let n = x.file_name().into_string().unwrap();
215 x.file_type().expect("file system uhhh?????").is_file()
216 && !n.ends_with(".d")
217 && !n.starts_with(".")
218 })
219 .next()
220 .expect("cargo was unable to build the binary")
221 .expect("file system did something i cannot comprehend")
222 .path()
223 .into_os_string()
224 .into_string()
225 .expect("bad unicode in file path")
226 },
227 })
228 }
229}
230
231impl Default for RustAppBuilder {
232 fn default() -> Self {
233 Self::new()
234 }
235}