1use crate::interpreter::value::*;
7use crate::interpreter::BuiltinContext;
8use minir_macros::{builtin, interpreter_builtin};
9use tabled::settings::style::Style;
10use tabled::settings::{Modify, Width};
11use tabled::{builder::Builder, settings::object::Columns};
12
13use super::has_class;
14
15struct TableData {
19 col_names: Vec<String>,
20 col_types: Vec<&'static str>,
21 row_names: Vec<String>,
22 columns: Vec<Vec<String>>,
24 nrow: usize,
25}
26
27fn extract_data_frame(val: &RValue) -> Option<TableData> {
31 let list = match val {
32 RValue::List(l) if has_class(val, "data.frame") => l,
33 _ => return None,
34 };
35
36 if list.values.is_empty() {
37 return Some(TableData {
38 col_names: Vec::new(),
39 col_types: Vec::new(),
40 row_names: Vec::new(),
41 columns: Vec::new(),
42 nrow: 0,
43 });
44 }
45
46 let col_names: Vec<String> = list
47 .values
48 .iter()
49 .enumerate()
50 .map(|(i, (name, _))| name.clone().unwrap_or_else(|| format!("V{}", i + 1)))
51 .collect();
52
53 let nrow = list
54 .get_attr("row.names")
55 .map(|v| v.length())
56 .unwrap_or_else(|| list.values.first().map(|(_, v)| v.length()).unwrap_or(0));
57
58 let row_names: Vec<String> = match list.get_attr("row.names") {
59 Some(RValue::Vector(rv)) => match &rv.inner {
60 Vector::Character(chars) => chars
61 .iter()
62 .map(|c| c.clone().unwrap_or_else(|| "NA".to_string()))
63 .collect(),
64 Vector::Integer(ints) => ints
65 .iter()
66 .map(|i| match i {
67 Some(v) => v.to_string(),
68 None => "NA".to_string(),
69 })
70 .collect(),
71 _ => (1..=nrow).map(|i| i.to_string()).collect(),
72 },
73 _ => (1..=nrow).map(|i| i.to_string()).collect(),
74 };
75
76 let col_types: Vec<&'static str> = list
77 .values
78 .iter()
79 .map(|(_, value)| match value {
80 RValue::Vector(rv) => rv.inner.type_name(),
81 RValue::Null => "NULL",
82 _ => "list",
83 })
84 .collect();
85
86 let columns: Vec<Vec<String>> = list
87 .values
88 .iter()
89 .map(|(_, value)| match value {
90 RValue::Vector(rv) => format_column_values(&rv.inner, nrow),
91 RValue::Null => vec!["NULL".to_string(); nrow],
92 other => vec![format!("{}", other); nrow],
93 })
94 .collect();
95
96 Some(TableData {
97 col_names,
98 col_types,
99 row_names,
100 columns,
101 nrow,
102 })
103}
104
105fn format_column_values(v: &Vector, nrow: usize) -> Vec<String> {
107 use crate::interpreter::value::vector::{format_r_complex, format_r_double};
108
109 let len = v.len();
110 (0..nrow)
111 .map(|i| {
112 if i >= len {
113 return "NA".to_string();
114 }
115 match v {
116 Vector::Raw(vals) => format!("{:02x}", vals[i]),
117 Vector::Logical(vals) => match vals[i] {
118 Some(true) => "TRUE".to_string(),
119 Some(false) => "FALSE".to_string(),
120 None => "NA".to_string(),
121 },
122 Vector::Integer(vals) => match vals.get_opt(i) {
123 Some(n) => n.to_string(),
124 None => "NA".to_string(),
125 },
126 Vector::Double(vals) => match vals.get_opt(i) {
127 Some(f) => format_r_double(f),
128 None => "NA".to_string(),
129 },
130 Vector::Complex(vals) => match vals[i] {
131 Some(c) => format_r_complex(c),
132 None => "NA".to_string(),
133 },
134 Vector::Character(vals) => match &vals[i] {
135 Some(s) => s.clone(),
136 None => "NA".to_string(),
137 },
138 }
139 })
140 .collect()
141}
142
143#[interpreter_builtin(name = "View", min_args = 1)]
157fn interp_view(
158 args: &[RValue],
159 _named: &[(String, RValue)],
160 context: &BuiltinContext,
161) -> Result<RValue, RError> {
162 let val = &args[0];
163
164 let data = extract_data_frame(val).ok_or_else(|| {
165 RError::new(
166 RErrorKind::Argument,
167 "View() requires a data.frame. Use as.data.frame() to convert other objects."
168 .to_string(),
169 )
170 })?;
171
172 #[cfg(feature = "plot")]
174 {
175 let tx = context.interpreter().plot_tx.borrow();
176 if let Some(tx) = tx.as_ref() {
177 use crate::interpreter::graphics::view::ColType;
178 let table_data = crate::interpreter::graphics::view::TableData {
179 title: "View".to_string(),
180 headers: data.col_names.clone(),
181 col_types: data
182 .col_types
183 .iter()
184 .map(|t| match *t {
185 "dbl" => ColType::Double,
186 "int" => ColType::Integer,
187 "chr" => ColType::Character,
188 "lgl" => ColType::Logical,
189 _ => ColType::Other,
190 })
191 .collect(),
192 row_names: data.row_names.clone(),
193 rows: {
194 let nrow = data.nrow;
195 let ncol = data.columns.len();
196 (0..nrow)
197 .map(|r| {
198 (0..ncol)
199 .map(|c| {
200 data.columns
201 .get(c)
202 .and_then(|col| col.get(r).cloned())
203 .unwrap_or_else(|| "NA".to_string())
204 })
205 .collect()
206 })
207 .collect()
208 },
209 };
210 let _ =
211 tx.send(crate::interpreter::graphics::egui_device::PlotMessage::View(table_data));
212 context.interpreter().set_invisible();
213 return Ok(val.clone());
214 }
215 }
216
217 if data.nrow == 0 {
218 context.write(&format!(
219 "data frame with 0 rows and {} columns: {}\n",
220 data.col_names.len(),
221 data.col_names.join(", ")
222 ));
223 return Ok(val.clone());
224 }
225
226 let max_display_rows: usize = 20;
227 let max_col_width: usize = 30;
228 let display_rows = data.nrow.min(max_display_rows);
229
230 let headers: Vec<String> = std::iter::once(String::new()) .chain(
233 data.col_names
234 .iter()
235 .zip(data.col_types.iter())
236 .map(|(name, ty)| format!("{} <{}>", name, short_type_name(ty))),
237 )
238 .collect();
239
240 let mut builder = Builder::new();
241 builder.push_record(&headers);
242
243 for row in 0..display_rows {
244 let row_name = data
245 .row_names
246 .get(row)
247 .cloned()
248 .unwrap_or_else(|| (row + 1).to_string());
249 let mut cells: Vec<String> = vec![row_name];
250 for col in &data.columns {
251 cells.push(col.get(row).cloned().unwrap_or_else(|| "NA".to_string()));
252 }
253 builder.push_record(&cells);
254 }
255
256 let mut table = builder.build();
257 table
258 .with(Style::rounded())
259 .with(Modify::new(Columns::new(1..)).with(Width::truncate(max_col_width).suffix("...")));
260
261 context.write(&format!("{}\n", table));
262
263 if data.nrow > max_display_rows {
264 context.write(&format!(
265 "... {} more rows ({} total)\n",
266 data.nrow - max_display_rows,
267 data.nrow
268 ));
269 }
270
271 Ok(val.clone())
272}
273
274#[builtin(min_args = 1)]
287fn builtin_kable(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
288 let val = &args[0];
289
290 let data = extract_data_frame(val).ok_or_else(|| {
291 RError::new(
292 RErrorKind::Argument,
293 "kable() requires a data.frame. Use as.data.frame() to convert other objects."
294 .to_string(),
295 )
296 })?;
297
298 let format_owned = named
300 .iter()
301 .find(|(n, _)| n == "format")
302 .and_then(|(_, v)| v.as_vector())
303 .and_then(|v| v.as_character_scalar());
304 let format = format_owned.as_deref().unwrap_or("pipe");
305
306 if data.nrow == 0 {
307 let header = data.col_names.join(" | ");
308 return Ok(RValue::vec(Vector::Character(vec![Some(header)].into())));
309 }
310
311 let headers: Vec<String> = data.col_names.clone();
313
314 let mut builder = Builder::new();
315 builder.push_record(&headers);
316
317 for row in 0..data.nrow {
318 let cells: Vec<String> = data
319 .columns
320 .iter()
321 .map(|col| col.get(row).cloned().unwrap_or_else(|| "NA".to_string()))
322 .collect();
323 builder.push_record(&cells);
324 }
325
326 let mut table = builder.build();
327
328 match format {
329 "pipe" | "markdown" => {
330 table.with(Style::markdown());
331 }
332 "simple" => {
333 table.with(Style::psql());
334 }
335 _ => {
336 table.with(Style::markdown());
337 }
338 }
339
340 let output = table.to_string();
341 Ok(RValue::vec(Vector::Character(vec![Some(output)].into())))
342}
343
344pub(crate) fn str_data_frame(val: &RValue) -> Option<String> {
357 let data = extract_data_frame(val)?;
358
359 let mut out = String::new();
360 out.push_str(&format!(
361 "'data.frame':\t{} obs. of {} variables:\n",
362 data.nrow,
363 data.col_names.len()
364 ));
365
366 let max_preview = 10;
367
368 let mut builder = Builder::new();
370
371 for (i, col_name) in data.col_names.iter().enumerate() {
372 let ty = data.col_types[i];
373 let short_ty = short_type_name(ty);
374
375 let preview: String = data.columns[i]
377 .iter()
378 .take(max_preview)
379 .map(|val| {
380 if ty == "character" {
381 format!("\"{}\"", val)
382 } else {
383 val.clone()
384 }
385 })
386 .collect::<Vec<_>>()
387 .join(" ");
388
389 let ellipsis = if data.nrow > max_preview { " ..." } else { "" };
390
391 builder.push_record([
392 format!(" $ {}", col_name),
393 format!(": {}", short_ty),
394 format!(" {}{}", preview, ellipsis),
395 ]);
396 }
397
398 let mut table = builder.build();
399 table.with(Style::empty());
400
401 out.push_str(&table.to_string());
402 Some(out)
403}
404
405fn short_type_name(ty: &str) -> &str {
407 match ty {
408 "integer" => "int",
409 "double" => "num",
410 "character" => "chr",
411 "logical" => "lgl",
412 "complex" => "cpl",
413 "raw" => "raw",
414 "NULL" => "NULL",
415 "list" => "list",
416 _ => ty,
417 }
418}
419
420