1use serde_json::Value;
14use std::cell::RefCell;
15use std::io::{BufRead, BufReader, Write};
16use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
17use std::sync::{Condvar, Mutex};
18
19struct Interpreter {
22 runtime: &'static str,
23 variable: &'static str,
24 default: &'static str,
25 arguments: [&'static str; 2],
26 script: &'static str,
27}
28
29const INTERPRETERS: [Interpreter; 2] = [
30 Interpreter {
31 runtime: "python",
32 variable: "WARP_PYTHON",
33 default: "python3",
34 arguments: ["-u", "-c"],
35 script: FOREIGN_PYTHON_LOOP,
36 },
37 Interpreter {
38 runtime: "js",
39 variable: "WARP_NODE",
40 default: "node",
41 arguments: ["--no-warnings", "-e"],
42 script: FOREIGN_JS_LOOP,
43 },
44];
45pub const BIG_INTEGER_KEY: &str = "$int";
47pub const CODEPOINT_KEY: &str = "$char";
49pub const WARP_FUNCTION_KEY: &str = "$warp_function";
51pub type Callback<'a> = &'a mut dyn FnMut(usize, Value) -> Result<Value, String>;
53thread_local! {
54 static HELD: RefCell<Vec<(&'static str, Runtime)>> = const { RefCell::new(Vec::new()) };
57}
58const FOREIGN_PYTHON_LOOP: &str = include_str!("foreign_python.py"); const FOREIGN_JS_LOOP: &str = r#"
64const handles = [];
65const handle = value => (handles.push(value), { "$handle": handles.length, type: value?.constructor?.name ?? typeof value, text: String(value).slice(0, 200) });
66const unplain = value => Array.isArray(value) ? value.map(unplain)
67 : value !== null && typeof value === "object" ? ("$handle" in value ? handles[value.$handle - 1] : "$warp_function" in value ? warpFunction(value.$warp_function)
68 : Object.fromEntries(Object.entries(value).map(([key, item]) => [key, unplain(item)])))
69 : value;
70const plain = value => {
71 if (typeof value === "bigint") return value >= -(2n ** 63n) && value < 2n ** 63n ? Number(value) : { "$int": value.toString() };
72 if (value === undefined || value === null || typeof value === "symbol") return null; // ø, not a handle of null
73 if (Array.isArray(value)) return value.map(plain);
74 const prototype = value !== null && typeof value === "object" ? Object.getPrototypeOf(value) : undefined;
75 if (prototype === Object.prototype || prototype === null) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, plain(item)]));
76 if (typeof value === "function" || typeof value === "object") return handle(value); // a Date, a Map, an instance, a function
77 return value;
78};
79// what warp's operators on a value of this runtime forward to (lowering/foreign/foreign_modules.rs)
80const operator = { add: (a, b) => a + b, sub: (a, b) => a - b, mul: (a, b) => a * b, truediv: (a, b) => a / b, mod: (a, b) => a % b, pow: (a, b) => a ** b,
81 lt: (a, b) => a < b, gt: (a, b) => a > b, le: (a, b) => a <= b, ge: (a, b) => a >= b, eq: (a, b) => a === b, ne: (a, b) => a !== b, neg: a => -a,
82 getitem: (a, i) => typeof a.get === "function" ? a.get(i) : a[i], len: a => a.length ?? a.size, list: a => Array.from(a) };
83// `use js self` / `use js window`: node mirrors a page, its global object is globalThis
84const load = name => name === "operator" ? operator : name === "self" || name === "window" ? globalThis : name in globalThis ? globalThis[name] : (() => { try { return require(name); } catch (failure) { return import(name); } })();
85// the answers own stdout: what a module prints (console.log) goes to stderr
86const reply = answer => require("fs").writeSync(1, JSON.stringify(answer) + "\n");
87process.stdout.write = process.stderr.write.bind(process.stderr);
88// stdin read synchronously, so a warp function called back answers before the JavaScript calling it goes on
89const decoder = new (require("string_decoder").StringDecoder)("utf8");
90let buffered = "";
91const readLine = () => {
92 const chunk = Buffer.alloc(65536);
93 while (!buffered.includes("\n")) {
94 let count;
95 try { count = require("fs").readSync(0, chunk, 0, chunk.length, null); } catch (failure) { if (failure.code === "EAGAIN") continue; throw failure; }
96 if (count === 0) return null;
97 buffered += decoder.write(chunk.subarray(0, count));
98 }
99 const line = buffered.slice(0, buffered.indexOf("\n"));
100 buffered = buffered.slice(line.length + 1);
101 return line;
102};
103// a request's answer; each `yield` waits for what may be a promise (lowering/foreign/foreign_modules.rs CONSTRUCTOR_MEMBER: member
104// "" constructs the module, `new URL(…)`; SETTER_PREFIX: "set innerHTML" sets the member to the one argument)
105const SETTER = /^set (.+)$/;
106function* answerOf(request) {
107 try {
108 let owner = null, value = typeof request.module === "object" ? unplain(request.module) : yield load(request.module);
109 const set = request.member.match(SETTER);
110 if (request.member === "") value = new value(...unplain(request.arguments));
111 else if (set) value = value[set[1]] = unplain(request.arguments)[0];
112 else {
113 for (const part of request.member.split(".")) {
114 if (value?.[part] === undefined) throw new ReferenceError(`${request.module} has no ${request.member}`);
115 [owner, value] = [value, value[part]];
116 }
117 if (request.arguments !== null) value = yield value.apply(owner, unplain(request.arguments));
118 }
119 return { value: plain(value) };
120 } catch (failure) {
121 return { error: failure instanceof Error ? failure.name + ": " + failure.message : String(failure) };
122 }
123}
124// a request of the program: its promises awaited
125const awaited = async steps => {
126 let step = steps.next();
127 while (!step.done) step = await Promise.resolve(step.value).then(value => steps.next(value), failure => steps.throw(failure));
128 return step.value;
129};
130// a request of a warp function called back, which the JavaScript calling it waits for: no promise can be awaited there
131const immediate = steps => {
132 let step = steps.next();
133 while (!step.done) {
134 const promised = typeof step.value?.then === "function";
135 if (promised) step.value.then(undefined, () => {}); // its failure is not the program's: no unhandled rejection
136 step = promised ? steps.throw(new Error("gives a promise, which a warp function called back cannot wait for")) : steps.next(step.value);
137 }
138 return step.value;
139};
140// a warp function given as an argument: called back through warp, answering the foreign calls it makes meanwhile
141const warpFunction = index => (...values) => {
142 reply({ callback: index, arguments: values.map(plain) });
143 for (let line; (line = readLine()) !== null;) {
144 const message = JSON.parse(line);
145 if ("module" in message) reply(immediate(answerOf(message)));
146 else if ("error" in message) throw new Error(message.error);
147 else return unplain(message.value);
148 }
149 throw new Error("warp ended");
150};
151(async () => {
152 for (let line; (line = readLine()) !== null;) reply(await awaited(answerOf(JSON.parse(line))));
153})();
154"#;
155
156struct Runtime {
157 _child: Child,
158 input: ChildStdin,
159 output: BufReader<ChildStdout>,
160}
161
162static RUNNING: Mutex<Vec<(&'static str, Option<Runtime>)>> = Mutex::new(Vec::new());
164static RETURNED: Condvar = Condvar::new();
165
166pub fn is_interpreted(runtime: &str) -> bool {
168 interpreter(runtime).is_some()
169}
170
171fn interpreter(runtime: &str) -> Option<&'static Interpreter> {
172 INTERPRETERS.iter().find(|interpreter| interpreter.runtime == runtime)
173}
174
175pub fn call(
178 runtime: &str,
179 module_name: &str,
180 module: Value,
181 member: &str,
182 arguments: Value,
183 callback: Callback,
184) -> Result<Value, String> {
185 let interpreter = interpreter(runtime).ok_or_else(|| format!("no foreign runtime {runtime}"))?;
186 let request = serde_json::json!({"module": module, "member": member, "arguments": arguments});
187 let mut answer =
188 exchange(interpreter, &request.to_string(), callback).map_err(|failure| format!("{runtime}: {failure}"))?;
189 match (answer.get_mut("value").map(Value::take), answer.get("error")) {
190 (_, Some(Value::String(error))) => Err(format!("{runtime} {module_name}.{member}: {error}")),
191 (Some(value), _) => Ok(value),
192 _ => Err(format!("{runtime} answered neither a value nor an error: {answer}")),
193 }
194}
195
196fn exchange(interpreter: &'static Interpreter, request: &str, callback: Callback) -> Result<Value, String> {
199 let held = take_held(interpreter.runtime);
200 let nested = held.is_some();
201 let child = match held {
202 Some(child) => child,
203 None => borrow(interpreter)?,
204 };
205 let (answer, child) = talk(interpreter.runtime, child, request, callback);
206 match (nested, child) {
207 (true, Some(child)) => HELD.with_borrow_mut(|held| held.push((interpreter.runtime, child))),
208 (true, None) => {}
209 (false, child) => give_back(interpreter.runtime, child),
210 }
211 answer
212}
213
214fn talk(
217 runtime: &'static str,
218 mut child: Runtime,
219 request: &str,
220 callback: Callback,
221) -> (Result<Value, String>, Option<Runtime>) {
222 let mut line = request.to_string();
223 loop {
224 let mut answer = String::new();
225 let sent = writeln!(child.input, "{line}").and_then(|_| child.input.flush());
226 if sent.is_err() || child.output.read_line(&mut answer).unwrap_or(0) == 0 {
227 return (Err("the interpreter ended".into()), None);
228 }
229 let mut answer: Value = match serde_json::from_str(&answer) {
230 Ok(answer) => answer,
231 Err(failure) => return (Err(format!("answered no JSON: {failure}")), Some(child)),
232 };
233 let Some(function) = answer.get("callback").and_then(Value::as_u64) else {
234 return (Ok(answer), Some(child));
235 };
236 HELD.with_borrow_mut(|held| held.push((runtime, child)));
237 let arguments = answer.get_mut("arguments").map(Value::take).unwrap_or(Value::Null);
238 let result = callback(function as usize, arguments);
239 let Some(held) = take_held(runtime) else {
240 return (Err("the interpreter ended".into()), None);
241 };
242 child = held;
243 line = match result {
244 Ok(value) => serde_json::json!({ "value": value }),
245 Err(failure) => serde_json::json!({ "error": failure }),
246 }
247 .to_string();
248 }
249}
250
251fn take_held(runtime: &str) -> Option<Runtime> {
253 HELD.with_borrow_mut(|held| held.iter().rposition(|(name, _)| *name == runtime).map(|index| held.remove(index).1))
254}
255
256fn borrow(interpreter: &'static Interpreter) -> Result<Runtime, String> {
258 let mut running = RUNNING.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
259 loop {
260 let Some((_, slot)) = running.iter_mut().find(|(runtime, _)| *runtime == interpreter.runtime) else {
261 running.push((interpreter.runtime, None));
262 drop(running);
263 return start(interpreter).inspect_err(|_| give_back(interpreter.runtime, None));
264 };
265 if let Some(child) = slot.take() {
266 return Ok(child);
267 }
268 running = RETURNED.wait(running).unwrap_or_else(|poisoned| poisoned.into_inner());
269 }
270}
271
272fn give_back(runtime: &'static str, child: Option<Runtime>) {
274 let mut running = RUNNING.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
275 match child {
276 Some(child) => {
277 if let Some((_, slot)) = running.iter_mut().find(|(name, _)| *name == runtime) {
278 *slot = Some(child);
279 }
280 }
281 None => running.retain(|(name, _)| *name != runtime),
282 }
283 RETURNED.notify_all();
284}
285
286fn start(interpreter: &Interpreter) -> Result<Runtime, String> {
287 let program = std::env::var(interpreter.variable).unwrap_or_else(|_| interpreter.default.to_string());
288 let mut child = Command::new(&program)
289 .args(interpreter.arguments)
290 .arg(interpreter.script)
291 .stdin(Stdio::piped())
292 .stdout(Stdio::piped())
293 .stderr(Stdio::inherit())
294 .spawn()
295 .map_err(|failure| {
296 format!("cannot start {program} ({failure}); set {} to an interpreter", interpreter.variable)
297 })?;
298 let input = child.stdin.take().ok_or("no stdin")?;
299 let output = BufReader::new(child.stdout.take().ok_or("no stdout")?);
300 Ok(Runtime { _child: child, input, output })
301}