//! 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 = 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> = row_slots .into_iter() .map(|slot| { let mut row: Vec = 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()], ) }