blendtutor_core/runner/r.rs
1//! R execution mechanics: the R-specific half of a language run — which program
2//! and which flags — over the shared subprocess core in [`super::subprocess`].
3//!
4//! This module owns *only* the R specifics (ADR-0005, §4.1); callers depend
5//! on the [`Runner`](super::Runner) trait, not on [`RRunner`], and the
6//! spawn/timeout/temp-cwd dance is shared with every other language (§4.2).
7//!
8//! R does not declare packages (ADR-0011): R lessons use system-installed
9//! libraries, and the runner stays `Rscript --vanilla -e`. The `Interpreter`
10//! type changed from `&'static [&'static str]` to `Vec<String>` (so Python can
11//! inject `--with` flags), so R builds its fixed args dynamically — but the
12//! invocation is byte-identical.
13
14use std::time::Duration;
15
16use super::subprocess::{self, Interpreter};
17use super::{ExecutionResult, Runner, RunnerError, Timeout};
18
19/// Build the R interpreter descriptor: `Rscript --vanilla -e`. The args are
20/// fixed (R has no packages concern, ADR-0011) but built as a `Vec<String>`
21/// because `Interpreter.code_args` is now `Vec<String>` (relaxed from
22/// `&'static [&'static str]` so Python can inject runtime `--with` flags).
23fn r_interpreter() -> Interpreter {
24 Interpreter {
25 program: "Rscript",
26 code_args: vec!["--vanilla".into(), "-e".into()],
27 }
28}
29
30/// A [`Runner`] backed by a real `Rscript` subprocess.
31#[derive(Debug, Clone)]
32pub struct RRunner {
33 timeout: Timeout,
34}
35
36impl RRunner {
37 /// Build a runner that kills any execution exceeding `timeout`.
38 pub fn new(timeout: Timeout) -> Self {
39 Self { timeout }
40 }
41}
42
43impl Default for RRunner {
44 /// A 30-second bound — generous for a lesson exercise, finite for a runaway.
45 fn default() -> Self {
46 Self::new(Timeout(Duration::from_secs(30)))
47 }
48}
49
50impl Runner for RRunner {
51 async fn execute(
52 &self,
53 code: &str,
54 _checks: &[String],
55 ) -> Result<ExecutionResult, RunnerError> {
56 let interpreter = r_interpreter();
57 subprocess::run(&interpreter, code, self.timeout).await
58 }
59}