1use std::cell::RefCell;
2use std::fmt;
3use std::fs;
4use std::path::Path;
5use std::sync::atomic::AtomicBool;
6use std::sync::Arc;
7
8use tracing::info;
9
10use crate::interpreter::value::{RFlow, RValue};
11use crate::interpreter::{with_interpreter_state, Interpreter};
12use crate::parser::ast::Expr;
13use crate::parser::{parse_program, ParseError};
14
15#[derive(Debug)]
16pub struct EvalOutput {
17 pub value: RValue,
18 pub visible: bool,
19}
20
21#[derive(Debug)]
22pub enum SessionError {
23 Parse(Box<ParseError>),
24 Runtime(RFlow),
25 CannotRead {
26 path: String,
27 source: std::io::Error,
28 },
29}
30
31impl SessionError {
32 pub fn render(&self) -> String {
36 match self {
37 SessionError::Parse(err) => err.render(),
38 other => format!("{}", other),
39 }
40 }
41}
42
43impl fmt::Display for SessionError {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 SessionError::Parse(err) => write!(f, "{}", err),
47 SessionError::Runtime(err) => write!(f, "{}", err),
48 SessionError::CannotRead { path, source } => {
49 write!(f, "Error reading file '{}': {}", path, source)
50 }
51 }
52 }
53}
54
55impl std::error::Error for SessionError {
56 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
57 match self {
58 SessionError::CannotRead { source, .. } => Some(source),
59 SessionError::Parse(_) | SessionError::Runtime(_) => None,
60 }
61 }
62}
63
64struct SharedBuf(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
67
68impl std::io::Write for SharedBuf {
69 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
70 let mut guard = self.0.lock().unwrap_or_else(|e| e.into_inner());
71 guard.extend_from_slice(buf);
72 Ok(buf.len())
73 }
74 fn flush(&mut self) -> std::io::Result<()> {
75 Ok(())
76 }
77}
78
79pub struct Session {
80 interpreter: Interpreter,
81 captured_stdout: Option<std::sync::Arc<std::sync::Mutex<Vec<u8>>>>,
83 captured_stderr: Option<std::sync::Arc<std::sync::Mutex<Vec<u8>>>>,
85}
86
87impl Default for Session {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl Session {
94 pub fn new() -> Self {
95 Session {
96 interpreter: Interpreter::new(),
97 captured_stdout: None,
98 captured_stderr: None,
99 }
100 }
101
102 pub fn new_with_captured_output() -> Self {
106 let mut interp = Interpreter::new();
107 let stdout_buf = std::sync::Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
108 let stderr_buf = std::sync::Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
109 interp.stdout = RefCell::new(Box::new(SharedBuf(stdout_buf.clone())));
110 interp.stderr = RefCell::new(Box::new(SharedBuf(stderr_buf.clone())));
111 interp.set_color_stderr(false);
112 Session {
113 interpreter: interp,
114 captured_stdout: Some(stdout_buf),
115 captured_stderr: Some(stderr_buf),
116 }
117 }
118
119 pub fn captured_stdout(&self) -> String {
124 match &self.captured_stdout {
125 Some(buf) => {
126 let guard = buf.lock().unwrap_or_else(|e| e.into_inner());
127 String::from_utf8_lossy(&guard).into_owned()
128 }
129 None => String::new(),
130 }
131 }
132
133 pub fn captured_stderr(&self) -> String {
137 match &self.captured_stderr {
138 Some(buf) => {
139 let guard = buf.lock().unwrap_or_else(|e| e.into_inner());
140 String::from_utf8_lossy(&guard).into_owned()
141 }
142 None => String::new(),
143 }
144 }
145
146 pub fn eval_expr(&mut self, expr: &Expr) -> Result<EvalOutput, SessionError> {
147 self.interpreter.last_value_invisible.set(false);
150 let value = with_interpreter_state(&mut self.interpreter, |interp| interp.eval(expr))
151 .map_err(SessionError::Runtime)?;
152 let runtime_invisible = self.interpreter.take_invisible();
154 let syntactic_invisible = is_invisible_result(expr);
155 Ok(EvalOutput {
156 visible: !runtime_invisible && !syntactic_invisible,
157 value,
158 })
159 }
160
161 pub fn eval_source(&mut self, source: &str) -> Result<EvalOutput, SessionError> {
162 let ast = parse_program(source).map_err(SessionError::Parse)?;
163 self.eval_expr(&ast)
164 }
165
166 pub fn auto_print(&mut self, value: &crate::interpreter::value::RValue) {
170 use crate::interpreter::value::RValue;
171 if matches!(value, RValue::Null) {
172 self.interpreter.write_stdout("NULL\n");
174 return;
175 }
176 let print_code = "print(.miniR.auto_print_value)";
177 self.interpreter
179 .global_env
180 .set(".miniR.auto_print_value".to_string(), value.clone());
181 self.eval_source(print_code).ok();
182 self.interpreter
183 .global_env
184 .remove(".miniR.auto_print_value");
185 }
186
187 pub fn eval_file(&mut self, path: impl AsRef<Path>) -> Result<EvalOutput, SessionError> {
188 let path = path.as_ref();
189 info!(path = %path.display(), "loading source file");
190 let source = read_source(path)?;
191 let ast = match parse_program(&source) {
192 Ok(ast) => ast,
193 Err(mut err) => {
194 err.filename = Some(path.display().to_string());
195 return Err(SessionError::Parse(err));
196 }
197 };
198 self.interpreter
199 .source_stack
200 .borrow_mut()
201 .push((path.display().to_string(), source));
202 let result = self.eval_expr(&ast);
203 self.interpreter.source_stack.borrow_mut().pop();
204 result
205 }
206
207 pub fn interpreter(&self) -> &Interpreter {
208 &self.interpreter
209 }
210
211 pub fn format_last_traceback(&self) -> Option<String> {
213 self.interpreter.format_traceback()
214 }
215
216 pub fn render_error(&self, err: &SessionError) -> String {
218 let base = err.render();
219 if matches!(err, SessionError::Runtime(_)) {
220 if let Some(tb) = self.interpreter.format_traceback() {
221 return format!("{}\nTraceback (most recent call last):\n{}\n", base, tb);
222 }
223 }
224 base
225 }
226
227 #[cfg(feature = "plot")]
229 pub fn set_plot_sender(&self, tx: crate::interpreter::graphics::egui_device::PlotSender) {
230 *self.interpreter.plot_tx.borrow_mut() = Some(tx);
231 }
232
233 pub fn set_option(&self, name: &str, value: RValue) {
235 self.interpreter
236 .options
237 .borrow_mut()
238 .insert(name.to_string(), value);
239 }
240
241 pub fn sync_terminal_width(&self) {
248 let cols = detect_terminal_width();
249 self.set_option(
250 "width",
251 RValue::vec(crate::interpreter::value::Vector::Integer(
252 vec![Some(cols)].into(),
253 )),
254 );
255 }
256
257 pub fn generate_rd_docs(dir: &Path) -> Result<usize, std::io::Error> {
262 crate::interpreter::builtins::generate_rd_docs(dir)
263 }
264
265 pub fn interrupt_flag(&self) -> Arc<AtomicBool> {
269 self.interpreter.interrupt_flag()
270 }
271
272 #[cfg(feature = "signal")]
278 pub fn install_signal_handler(&self) -> std::io::Result<()> {
279 #[cfg(unix)]
280 {
281 use signal_hook::consts::SIGINT;
282 signal_hook::flag::register(SIGINT, self.interrupt_flag())?;
283 }
284 Ok(())
285 }
286
287 #[cfg(not(feature = "signal"))]
289 pub fn install_signal_handler(&self) -> std::io::Result<()> {
290 Ok(())
291 }
292}
293
294pub fn is_invisible_result(ast: &Expr) -> bool {
295 match ast {
296 Expr::Assign { .. } => true,
297 Expr::For { .. } => true,
298 Expr::While { .. } => true,
299 Expr::Repeat { .. } => true,
300 Expr::Call { func, .. } => {
301 matches!(func.as_ref(), Expr::Symbol(name) if name == "invisible")
302 }
303 Expr::Program(exprs) => exprs.last().is_some_and(is_invisible_result),
304 Expr::Block(exprs) => exprs.last().is_some_and(is_invisible_result),
305 _ => false,
306 }
307}
308
309fn detect_terminal_width() -> i64 {
313 #[cfg(feature = "repl")]
315 {
316 if let Ok((cols, _)) = crossterm::terminal::size() {
317 return i64::from(cols).clamp(10, 10000);
318 }
319 }
320
321 #[cfg(feature = "terminal-size")]
323 {
324 if let Some((terminal_size::Width(w), _)) = terminal_size::terminal_size() {
325 return i64::from(w).clamp(10, 10000);
326 }
327 }
328
329 80
331}
332
333fn read_source(path: &Path) -> Result<String, SessionError> {
334 match fs::read_to_string(path) {
335 Ok(source) => Ok(source),
336 Err(source_err) if source_err.kind() == std::io::ErrorKind::InvalidData => fs::read(path)
337 .map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
338 .map_err(|source| SessionError::CannotRead {
339 path: path.display().to_string(),
340 source,
341 }),
342 Err(source) => Err(SessionError::CannotRead {
343 path: path.display().to_string(),
344 source,
345 }),
346 }
347}