qroissant/crates/qroissant-python/src/repr/format.rs

278 lines
9.1 KiB
Rust

//! High-level format functions for each q value shape.
//!
//! Each function produces a multi-line ASCII repr string. Rendering is driven
//! by the active [`FormattingOptions`] (read from the process-wide global).
use qroissant_core::Atom as CoreAtom;
use qroissant_core::Dictionary as CoreDictionary;
use qroissant_core::List as CoreList;
use qroissant_core::Table as CoreTable;
use qroissant_core::Value as CoreValue;
use qroissant_core::Vector as CoreVector;
use qroissant_core::VectorData;
use super::cell::atom_primitive_label;
use super::cell::format_atom_cell;
use super::cell::format_atom_raw;
use super::cell::format_char_vector;
use super::cell::format_vector_item;
use super::cell::primitive_label;
use super::cell::truncate;
use super::options::active_options;
use super::render::PreviewSlot;
use super::render::preview_slots;
use super::render::render_preview;
// ---------------------------------------------------------------------------
// Attribute helper
// ---------------------------------------------------------------------------
fn attribute_label(attribute: qroissant_core::Attribute) -> &'static str {
match attribute {
qroissant_core::Attribute::None => "none",
qroissant_core::Attribute::Sorted => "sorted",
qroissant_core::Attribute::Unique => "unique",
qroissant_core::Attribute::Parted => "parted",
qroissant_core::Attribute::Grouped => "grouped",
}
}
// ---------------------------------------------------------------------------
// Atom
// ---------------------------------------------------------------------------
pub fn format_atom(atom: &CoreAtom) -> String {
let label = atom_primitive_label(atom);
render_preview(
vec![format!("Atom [{label}]")],
vec!["value".to_string()],
vec![vec![format_atom_cell(atom)]],
vec!["shape: (1,)".to_string()],
)
}
// ---------------------------------------------------------------------------
// Vector
// ---------------------------------------------------------------------------
pub fn format_vector(vector: &CoreVector) -> String {
let len = vector.len();
let data = vector.data();
let label = primitive_label(data);
let attr = vector.attribute();
let rows = match data {
VectorData::Char(chars) => {
vec![vec![format_char_vector(chars)]]
}
_ => {
let opts = active_options();
preview_slots(len, opts.max_rows, opts.row_display)
.into_iter()
.map(|slot| match slot {
PreviewSlot::Index(i) => vec![format_vector_item(data, i)],
PreviewSlot::Ellipsis => vec!["...".to_string()],
})
.collect()
}
};
render_preview(
vec![format!("Vector [{label}, attr={}]", attribute_label(attr))],
vec!["value".to_string()],
rows,
vec![format!("shape: ({len},)")],
)
}
// ---------------------------------------------------------------------------
// List
// ---------------------------------------------------------------------------
/// Compact single-line summary of any `CoreValue` (used for list/dict cells).
fn inline_value_summary(value: &CoreValue) -> String {
match value {
CoreValue::Atom(atom) => truncate(format!(
"{} [{}]",
format_atom_raw(atom),
atom_primitive_label(atom)
)),
CoreValue::Vector(vector) => {
let label = primitive_label(vector.data());
let len = vector.len();
match vector.data() {
VectorData::Char(chars) => truncate(format_char_vector(chars)),
_ => truncate(format!("vector<{label}>[{len}]")),
}
}
CoreValue::List(list) => truncate(format!("list[{}]", list.len())),
CoreValue::Dictionary(dict) => truncate(format!("dict[{}]", dict.len())),
CoreValue::Table(table) => {
truncate(format!("table[{}x{}]", table.len(), table.num_columns()))
}
CoreValue::UnaryPrimitive { opcode } => truncate(format!("unary(0x{opcode:02x})")),
}
}
pub fn format_list(list: &CoreList) -> String {
let len = list.len();
let opts = active_options();
let attr = list.attribute();
let rows = preview_slots(len, opts.max_rows, opts.row_display)
.into_iter()
.map(|slot| match slot {
PreviewSlot::Index(i) => vec![inline_value_summary(&list.values()[i])],
PreviewSlot::Ellipsis => vec!["...".to_string()],
})
.collect();
render_preview(
vec![format!("List [list, attr={}]", attribute_label(attr))],
vec!["value".to_string()],
rows,
vec![format!("shape: ({len},)")],
)
}
// ---------------------------------------------------------------------------
// Dictionary
// ---------------------------------------------------------------------------
pub fn format_dictionary(dict: &CoreDictionary) -> String {
let size = dict.len();
let sorted = dict.sorted();
let all_rows = vec![
vec!["keys".to_string(), inline_value_summary(dict.keys())],
vec!["values".to_string(), inline_value_summary(dict.values())],
];
let opts = active_options();
let rows = preview_slots(all_rows.len(), opts.max_rows, opts.row_display)
.into_iter()
.map(|slot| match slot {
PreviewSlot::Index(i) => all_rows[i].clone(),
PreviewSlot::Ellipsis => vec!["...".to_string(), "...".to_string()],
})
.collect();
render_preview(
vec![format!("Dictionary [dict, sorted={sorted}]")],
vec!["part".to_string(), "value".to_string()],
rows,
vec![format!("shape: ({size},)")],
)
}
// ---------------------------------------------------------------------------
// Table
// ---------------------------------------------------------------------------
fn column_primitive_label(col: &CoreValue) -> &'static str {
match col {
CoreValue::Vector(v) => primitive_label(v.data()),
CoreValue::List(_) => "list",
CoreValue::Atom(_) => "atom",
_ => "?",
}
}
fn table_cell(col: &CoreValue, row_index: usize) -> String {
match col {
CoreValue::Vector(v) => match v.data() {
VectorData::Char(chars) => {
// Show a single char per cell
if row_index < chars.len() {
(chars[row_index] as char).to_string()
} else {
"?".to_string()
}
}
data => format_vector_item(data, row_index),
},
CoreValue::Atom(atom) => format_atom_cell(atom),
CoreValue::List(list) => {
if row_index < list.len() {
inline_value_summary(&list.values()[row_index])
} else {
"?".to_string()
}
}
_ => inline_value_summary(col),
}
}
fn column_name(raw: &[u8]) -> String {
String::from_utf8_lossy(raw).into_owned()
}
pub fn format_table(table: &CoreTable) -> String {
let num_rows = table.len();
let num_cols = table.num_columns();
let opts = active_options();
let visible_cols = num_cols.min(opts.max_columns);
// Build headers: "name\ntype" for each visible column
let mut headers: Vec<String> = table
.column_names()
.iter()
.zip(table.columns().iter())
.take(visible_cols)
.map(|(name, col)| {
let col_name = truncate(column_name(name));
let type_label = column_primitive_label(col);
format!("{col_name}\n{type_label}")
})
.collect();
if num_cols > visible_cols {
headers.push("...\n...".to_string());
} else if headers.is_empty() {
headers.push("value".to_string());
}
// Build rows
let row_slots = preview_slots(num_rows, opts.max_rows, opts.row_display);
let columns = table.columns();
let body_rows: Vec<Vec<String>> = row_slots
.into_iter()
.map(|slot| {
let mut row: Vec<String> = match slot {
PreviewSlot::Index(row_i) => (0..visible_cols)
.map(|col_i| table_cell(&columns[col_i], row_i))
.collect(),
PreviewSlot::Ellipsis => vec!["...".to_string(); visible_cols.max(1)],
};
if num_cols > visible_cols {
row.push("...".to_string());
}
row
})
.collect();
render_preview(
vec![format!(
"Table [table, attr={}]",
attribute_label(table.attribute())
)],
headers,
body_rows,
vec![format!("shape: ({num_rows}, {num_cols})")],
)
}
// ---------------------------------------------------------------------------
// UnaryPrimitive
// ---------------------------------------------------------------------------
#[allow(dead_code)]
pub fn format_unary_primitive(opcode: i8) -> String {
render_preview(
vec!["UnaryPrimitive [unary_primitive]".to_string()],
vec!["opcode".to_string()],
vec![vec![format!("0x{opcode:02x}")]],
vec!["shape: (1,)".to_string()],
)
}