use super::words::is_bool_word;
use std::fmt;
#[derive(Debug, Clone)]
pub struct Quarq {
pub source: String,
pub body: std::sync::Arc<Vec<crate::syntax::ast::Command>>,
pub projector: Option<Box<Value>>,
pub holes: std::sync::Arc<Vec<crate::syntax::ast::Hole>>,
pub form: Option<Box<crate::syntax::expand::Form>>,
pub at: Option<Box<Value>>,
}
pub const QUARQ_TEXT_FIELD: &str = "unq";
impl Quarq {
pub fn is_template(&self) -> bool {
!self.holes.is_empty()
}
pub fn unq_text(&self) -> &str {
self.source
.strip_prefix("q(")
.and_then(|s| s.strip_suffix(')'))
.unwrap_or(&self.source)
}
pub fn core_field(&self, key: &str) -> Option<Value> {
if key == QUARQ_TEXT_FIELD {
Some(Value::Str(self.unq_text().to_string()))
} else {
None
}
}
pub fn into_form(self) -> crate::syntax::expand::Form {
match self.form {
Some(f) => *f,
None => crate::syntax::expand::Form::Pipeline((*self.body).clone()),
}
}
}
impl PartialEq for Quarq {
fn eq(&self, other: &Self) -> bool {
self.source == other.source
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Int(i64),
Float(f64),
Str(String),
List(std::sync::Arc<Vec<Value>>),
Record(Record),
Table(std::sync::Arc<Table>),
Instant {
secs: i64,
nanos: u32,
offset_min: Option<i16>,
calendar: crate::calendar::Calendar,
},
Period {
start: i64,
grain: crate::period::Grain,
calendar: crate::calendar::Calendar,
},
Quantity {
value: f64,
base: String,
written: Option<(f64, String)>,
},
Foreign(ForeignValue),
Projection {
ladder: u8,
key: String,
typing: Option<Typing>,
},
Quarq(Quarq),
Node(NodeRef),
}
#[derive(Clone)]
pub struct NodeRef {
pub id: u64,
pub locator: std::sync::Arc<str>,
}
impl NodeRef {
pub fn new(id: u64, locator: impl Into<std::sync::Arc<str>>) -> NodeRef {
NodeRef {
id,
locator: locator.into(),
}
}
}
impl PartialEq for NodeRef {
fn eq(&self, other: &NodeRef) -> bool {
self.id == other.id
}
}
impl Eq for NodeRef {}
impl std::hash::Hash for NodeRef {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}
impl std::fmt::Debug for NodeRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Node({}#{})", self.locator, self.id)
}
}
fn nest_by_levels(fields: Vec<(String, Value)>) -> Value {
if !fields.iter().any(|(k, _)| k.contains(':')) {
return Value::record(fields);
}
let mut out: Vec<(String, Value)> = Vec::new();
let mut groups: Vec<(String, Vec<(String, Value)>)> = Vec::new();
for (k, v) in fields {
match k.split_once(':') {
Some((outer, inner)) => match groups.iter_mut().find(|(o, _)| *o == outer) {
Some((_, g)) => g.push((inner.to_string(), v)),
None => {
out.push((outer.to_string(), Value::Null));
groups.push((outer.to_string(), vec![(inner.to_string(), v)]));
}
},
None => out.push((k, v)),
}
}
for (o, g) in groups {
if let Some(slot) = out
.iter_mut()
.find(|(k, v)| *k == o && matches!(v, Value::Null))
{
slot.1 = nest_by_levels(g);
}
}
Value::record(out)
}
fn nest_levels(levels: &[Vec<Value>], cells: &[Value], depth: usize) -> Value {
let width = levels.first().map_or(0, Vec::len);
if depth + 1 >= width {
return Value::Record(Record::keyed(
levels
.iter()
.zip(cells)
.map(|(l, c)| (l.get(depth).cloned().unwrap_or(Value::Null), c.clone()))
.collect(),
));
}
let mut keys: Vec<Value> = Vec::new();
for l in levels {
let k = l.get(depth).cloned().unwrap_or(Value::Null);
if !keys.contains(&k) {
keys.push(k);
}
}
Value::Record(Record::keyed(
keys.into_iter()
.map(|k| {
let (sub_levels, sub_cells): (Vec<Vec<Value>>, Vec<Value>) = levels
.iter()
.zip(cells)
.filter(|(l, _)| l.get(depth) == Some(&k))
.map(|(l, c)| (l.clone(), c.clone()))
.unzip();
let v = nest_levels(&sub_levels, &sub_cells, depth + 1);
(k, v)
})
.collect(),
))
}
#[derive(Debug, Clone, Default)]
pub struct Record {
fields: Vec<(String, Value)>,
keys: Option<Vec<Value>>,
declared: Option<Vec<Option<ColumnType>>>,
}
impl Record {
pub fn new(fields: Vec<(String, Value)>) -> Record {
Record {
fields,
keys: None,
declared: None,
}
}
pub fn keyed(pairs: Vec<(Value, Value)>) -> Record {
let typed = pairs.iter().any(|(k, _)| !matches!(k, Value::Str(_)));
let keys = typed.then(|| pairs.iter().map(|(k, _)| k.clone()).collect());
let fields = pairs
.into_iter()
.map(|(k, v)| (Record::key_text(&k), v))
.collect();
Record {
fields,
keys,
declared: None,
}
}
pub fn key_text(k: &Value) -> String {
match k {
Value::Str(s) => s.clone(),
other => other.to_string(),
}
}
pub fn key(&self, i: usize) -> Value {
match self.keys.as_ref().and_then(|k| k.get(i)) {
Some(k) => k.clone(),
None => Value::Str(
self.fields
.get(i)
.map(|(k, _)| k.clone())
.unwrap_or_default(),
),
}
}
pub fn key_values(&self) -> Vec<Value> {
(0..self.fields.len()).map(|i| self.key(i)).collect()
}
pub fn has_typed_keys(&self) -> bool {
self.keys.is_some()
}
pub fn find_key(&self, k: &Value) -> Option<usize> {
let same = |a: &Value, b: &Value| match (a, b) {
(Value::Int(x), Value::Float(y)) | (Value::Float(y), Value::Int(x)) => *x as f64 == *y,
_ => a == b,
};
(0..self.fields.len()).find(|&i| same(&self.key(i), k))
}
pub fn get(&self, k: &str) -> Option<&Value> {
self.fields.iter().find(|(f, _)| f == k).map(|(_, v)| v)
}
pub fn declaration(&self, i: usize) -> Option<ColumnType> {
self.declared
.as_ref()
.and_then(|d| d.get(i))
.cloned()
.flatten()
}
pub fn has_declarations(&self) -> bool {
self.declared
.as_ref()
.is_some_and(|d| d.iter().any(Option::is_some))
}
pub fn set_value(&mut self, i: usize, v: Value) {
if let Some(f) = self.fields.get_mut(i) {
f.1 = v;
}
}
pub fn declare(&mut self, i: usize, ty: Option<ColumnType>) {
let d = self
.declared
.get_or_insert_with(|| vec![None; self.fields.len()]);
d.resize(self.fields.len(), None);
d[i] = ty;
if d.iter().all(Option::is_none) {
self.declared = None;
}
}
pub fn push_field(&mut self, k: String, v: Value) {
self.push_keyed(Value::Str(k), v, None);
}
pub fn push_keyed(&mut self, k: Value, v: Value, ty: Option<ColumnType>) {
let n = self.fields.len();
if !matches!(k, Value::Str(_)) {
let keys = self.keys.get_or_insert_with(|| vec![Value::Null; n]);
keys.resize(n, Value::Null);
for (i, slot) in keys.iter_mut().enumerate() {
if matches!(slot, Value::Null) {
*slot = Value::Str(self.fields[i].0.clone());
}
}
keys.push(k.clone());
} else if let Some(keys) = &mut self.keys {
keys.resize(n, Value::Null);
keys.push(k.clone());
}
if let Some(d) = &mut self.declared {
d.resize(n, None);
d.push(ty);
} else if ty.is_some() {
let mut d = vec![None; n];
d.push(ty);
self.declared = Some(d);
}
self.fields.push((Record::key_text(&k), v));
}
pub fn set(&mut self, i: usize, v: Value) {
if let Some(f) = self.fields.get_mut(i) {
f.1 = v;
}
}
pub fn into_fields(self) -> Vec<(String, Value)> {
self.fields
}
pub fn map_values(self, mut f: impl FnMut(Value) -> Value) -> Record {
Record {
fields: self.fields.into_iter().map(|(k, v)| (k, f(v))).collect(),
keys: self.keys,
declared: self.declared,
}
}
pub fn with_values(&self, values: Vec<Value>) -> Record {
Record {
fields: self
.fields
.iter()
.zip(values)
.map(|((k, _), v)| (k.clone(), v))
.collect(),
keys: self.keys.clone(),
declared: self.declared.clone(),
}
}
pub fn filter_fields(self, mut keep: impl FnMut(usize, &(String, Value)) -> bool) -> Record {
let kept: Vec<bool> = self
.fields
.iter()
.enumerate()
.map(|(i, f)| keep(i, f))
.collect();
let pick = |v: Vec<Value>| -> Vec<Value> {
v.into_iter()
.zip(&kept)
.filter(|(_, k)| **k)
.map(|(x, _)| x)
.collect()
};
Record {
fields: self
.fields
.into_iter()
.zip(&kept)
.filter(|(_, k)| **k)
.map(|(f, _)| f)
.collect(),
keys: self.keys.map(pick),
declared: self.declared.map(|d| {
d.into_iter()
.zip(&kept)
.filter(|(_, k)| **k)
.map(|(x, _)| x)
.collect()
}),
}
}
pub fn key_quarb(&self, i: usize) -> String {
match self.keys.as_ref().and_then(|k| k.get(i)) {
Some(Value::Str(_)) | None => quarb_key(&self.fields[i].0),
Some(k) => k.to_quarb(),
}
}
}
impl PartialEq for Record {
fn eq(&self, other: &Record) -> bool {
self.fields.len() == other.fields.len()
&& self
.fields
.iter()
.zip(other.fields.iter())
.enumerate()
.all(|(i, ((_, a), (_, b)))| a == b && self.key(i) == other.key(i))
}
}
impl std::ops::Deref for Record {
type Target = Vec<(String, Value)>;
fn deref(&self) -> &Vec<(String, Value)> {
&self.fields
}
}
impl std::ops::DerefMut for Record {
fn deref_mut(&mut self) -> &mut Vec<(String, Value)> {
&mut self.fields
}
}
impl From<Vec<(String, Value)>> for Record {
fn from(fields: Vec<(String, Value)>) -> Record {
Record::new(fields)
}
}
impl FromIterator<(String, Value)> for Record {
fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Record {
Record::new(iter.into_iter().collect())
}
}
impl IntoIterator for Record {
type Item = (String, Value);
type IntoIter = std::vec::IntoIter<(String, Value)>;
fn into_iter(self) -> Self::IntoIter {
self.fields.into_iter()
}
}
impl<'a> IntoIterator for &'a Record {
type Item = &'a (String, Value);
type IntoIter = std::slice::Iter<'a, (String, Value)>;
fn into_iter(self) -> Self::IntoIter {
self.fields.iter()
}
}
#[derive(Debug, Clone)]
pub struct Table {
pub header: Option<Vec<String>>,
columns: Vec<Column>,
n: usize,
pub declared: Vec<Option<ColumnType>>,
pub index: usize,
pub name: Option<String>,
}
impl PartialEq for Table {
fn eq(&self, other: &Table) -> bool {
self.header == other.header
&& self.n == other.n
&& self.width() == other.width()
&& self.declared == other.declared
&& self.index == other.index
&& self.name == other.name
&& (0..self.width()).all(|j| (0..self.n).all(|i| self.cell(i, j) == other.cell(i, j)))
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub(crate) struct Nulls {
bits: Vec<u64>,
len: usize,
count: usize,
}
impl Nulls {
fn get(&self, i: usize) -> bool {
self.bits
.get(i / 64)
.is_some_and(|w| (w >> (i % 64)) & 1 == 1)
}
fn push(&mut self, null: bool) {
if null {
let w = self.len / 64;
if self.bits.len() <= w {
self.bits.resize(w + 1, 0);
}
self.bits[w] |= 1 << (self.len % 64);
self.count += 1;
}
self.len += 1;
}
fn nulls(n: usize) -> Nulls {
let mut out = Nulls::default();
for _ in 0..n {
out.push(true);
}
out
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Column {
Null(usize),
Bool {
cells: Vec<bool>,
nulls: Nulls,
},
Int {
cells: Vec<i64>,
nulls: Nulls,
},
Float {
cells: Vec<f64>,
nulls: Nulls,
},
Text {
bytes: Vec<u8>,
ends: Vec<u32>,
nulls: Nulls,
},
Mixed(Vec<Value>),
}
impl Column {
fn len(&self) -> usize {
match self {
Column::Null(n) => *n,
Column::Bool { cells, .. } => cells.len(),
Column::Int { cells, .. } => cells.len(),
Column::Float { cells, .. } => cells.len(),
Column::Text { ends, .. } => ends.len(),
Column::Mixed(cells) => cells.len(),
}
}
fn get(&self, i: usize) -> Value {
match self {
Column::Null(_) => Value::Null,
Column::Bool { cells, nulls } => match cells.get(i) {
Some(b) if !nulls.get(i) => Value::Bool(*b),
_ => Value::Null,
},
Column::Int { cells, nulls } => match cells.get(i) {
Some(x) if !nulls.get(i) => Value::Int(*x),
_ => Value::Null,
},
Column::Float { cells, nulls } => match cells.get(i) {
Some(x) if !nulls.get(i) => Value::Float(*x),
_ => Value::Null,
},
Column::Text { bytes, ends, nulls } => match ends.get(i) {
Some(&end) if !nulls.get(i) => {
let start = if i == 0 { 0 } else { ends[i - 1] as usize };
Value::Str(String::from_utf8_lossy(&bytes[start..end as usize]).into_owned())
}
_ => Value::Null,
},
Column::Mixed(cells) => cells.get(i).cloned().unwrap_or(Value::Null),
}
}
fn opened_by(v: &Value, n: usize) -> Column {
let nulls = Nulls::nulls(n);
match v {
Value::Bool(_) => Column::Bool {
cells: vec![false; n],
nulls,
},
Value::Int(_) => Column::Int {
cells: vec![0; n],
nulls,
},
Value::Float(_) => Column::Float {
cells: vec![0.0; n],
nulls,
},
Value::Str(_) => Column::Text {
bytes: Vec::new(),
ends: vec![0; n],
nulls,
},
_ => Column::Mixed(vec![Value::Null; n]),
}
}
fn push(&mut self, v: Value) {
let v = match self {
Column::Null(n) => {
if matches!(v, Value::Null) {
*n += 1;
return;
}
let n = *n;
*self = Column::opened_by(&v, n);
return self.push(v);
}
Column::Bool { cells, nulls } => match v {
Value::Bool(b) => {
cells.push(b);
nulls.push(false);
return;
}
Value::Null => {
cells.push(false);
nulls.push(true);
return;
}
other => other,
},
Column::Int { cells, nulls } => match v {
Value::Int(x) => {
cells.push(x);
nulls.push(false);
return;
}
Value::Null => {
cells.push(0);
nulls.push(true);
return;
}
other => other,
},
Column::Float { cells, nulls } => match v {
Value::Float(x) => {
cells.push(x);
nulls.push(false);
return;
}
Value::Null => {
cells.push(0.0);
nulls.push(true);
return;
}
other => other,
},
Column::Text { bytes, ends, nulls } => match v {
Value::Str(s) if bytes.len() + s.len() <= u32::MAX as usize => {
bytes.extend_from_slice(s.as_bytes());
ends.push(bytes.len() as u32);
nulls.push(false);
return;
}
Value::Null => {
ends.push(bytes.len() as u32);
nulls.push(true);
return;
}
other => other,
},
Column::Mixed(cells) => {
cells.push(v);
return;
}
};
*self = Column::Mixed(self.to_values());
self.push(v);
}
fn take(&self, idx: &[usize]) -> Column {
match self {
Column::Null(_) => Column::Null(idx.len()),
Column::Bool { cells, nulls } => {
let mut taken = Nulls::default();
let cells = idx
.iter()
.map(|&i| {
taken.push(nulls.get(i) || i >= cells.len());
cells.get(i).copied().unwrap_or(false)
})
.collect();
Column::Bool {
cells,
nulls: taken,
}
}
Column::Int { cells, nulls } => {
let mut taken = Nulls::default();
let cells = idx
.iter()
.map(|&i| {
taken.push(nulls.get(i) || i >= cells.len());
cells.get(i).copied().unwrap_or(0)
})
.collect();
Column::Int {
cells,
nulls: taken,
}
}
Column::Float { cells, nulls } => {
let mut taken = Nulls::default();
let cells = idx
.iter()
.map(|&i| {
taken.push(nulls.get(i) || i >= cells.len());
cells.get(i).copied().unwrap_or(0.0)
})
.collect();
Column::Float {
cells,
nulls: taken,
}
}
Column::Text { bytes, ends, nulls } => {
let mut out_bytes = Vec::new();
let mut out_ends = Vec::with_capacity(idx.len());
let mut taken = Nulls::default();
for &i in idx {
match ends.get(i) {
Some(&end) if !nulls.get(i) => {
let start = if i == 0 { 0 } else { ends[i - 1] as usize };
out_bytes.extend_from_slice(&bytes[start..end as usize]);
taken.push(false);
}
_ => taken.push(true),
}
out_ends.push(out_bytes.len() as u32);
}
Column::Text {
bytes: out_bytes,
ends: out_ends,
nulls: taken,
}
}
Column::Mixed(cells) => Column::Mixed(
idx.iter()
.map(|&i| cells.get(i).cloned().unwrap_or(Value::Null))
.collect(),
),
}
}
fn to_values(&self) -> Vec<Value> {
(0..self.len()).map(|i| self.get(i)).collect()
}
fn slice(&self, from: usize, to: usize) -> Column {
let mut out = Column::Null(0);
for i in from..to.min(self.len()) {
out.push(self.get(i));
}
out
}
fn any_cell(&self, f: impl Fn(&Value) -> bool) -> bool {
match self {
Column::Mixed(cells) => cells.iter().any(f),
_ => false,
}
}
}
const TEXT_RANK: usize = ColumnType::ORDER.len() - 1;
#[derive(Debug)]
struct ColumnBuilder {
raw: Column,
typed: Option<Column>,
leader: usize,
base: Option<String>,
declared: Option<ColumnType>,
failed: Option<(usize, String)>,
}
impl ColumnBuilder {
fn new(n_before: usize, declared: Option<ColumnType>) -> ColumnBuilder {
ColumnBuilder {
raw: Column::Null(n_before),
typed: Some(Column::Null(n_before)),
leader: 0,
base: None,
declared,
failed: None,
}
}
fn len(&self) -> usize {
self.raw.len()
}
fn push_text(&mut self, s: &str) {
if self.declared.is_some() || self.leader > TEXT_RANK {
return self.push(Value::Str(s.to_string()));
}
if self.leader == TEXT_RANK {
if self.typed.is_none() {
self.raw.push(Value::Str(s.to_string()));
} else {
self.raw.push(Value::Str(s.to_string()));
self.typed
.as_mut()
.expect("typed")
.push(Value::Str(s.to_string()));
}
return;
}
match coerce_text(s, &ColumnType::ORDER[self.leader]) {
Some(v) if self.base_fits(&v) => {
self.raw.push(Value::Str(s.to_string()));
self.typed
.as_mut()
.expect("a leader below text keeps its column")
.push(v);
}
_ => {
self.raw.push(Value::Str(s.to_string()));
self.widen();
}
}
}
fn push(&mut self, cell: Value) {
if let Some(ty) = &self.declared {
let i = self.raw.len();
match cast_cell(&cell, ty) {
Some(v) => self.typed.as_mut().expect("declared").push(v),
None => {
if self.failed.is_none() {
self.failed = Some((i, held_text(&cell)));
}
self.typed.as_mut().expect("declared").push(Value::Null);
}
}
self.raw.push(Value::Null);
return;
}
if self.leader > TEXT_RANK {
self.raw.push(cell);
return;
}
if matches!(cell, Value::Projection { .. }) {
self.leader = TEXT_RANK + 1;
self.typed = None;
self.raw.push(cell);
return;
}
if self.leader == TEXT_RANK
&& self.typed.is_none()
&& !matches!(cell, Value::Str(_) | Value::Null)
{
self.typed = Some(
Self::coerced_all(&self.raw, &ColumnType::Text, &mut None)
.expect("every cell has a text form"),
);
}
let coerced = match &cell {
Value::Null => Some(Value::Null),
other => coerce_cell(other, &ColumnType::ORDER[self.leader]),
};
match coerced {
Some(v) if self.base_fits(&v) => {
if let Some(t) = self.typed.as_mut() {
t.push(v);
}
self.raw.push(cell);
}
_ => {
self.raw.push(cell);
self.widen();
}
}
}
fn base_fits(&mut self, v: &Value) -> bool {
if ColumnType::ORDER[self.leader] != ColumnType::Quantity {
return true;
}
match (v, &self.base) {
(Value::Quantity { base, .. }, Some(first)) => base == first,
(Value::Quantity { base, .. }, None) => {
self.base = Some(base.clone());
true
}
_ => true,
}
}
fn widen(&mut self) {
for rank in self.leader + 1..=TEXT_RANK {
let t = &ColumnType::ORDER[rank];
let mut base = None;
if let Some(col) = Self::coerced_all(&self.raw, t, &mut base) {
self.leader = rank;
self.base = base;
self.typed = if rank == TEXT_RANK
&& matches!(self.raw, Column::Text { .. } | Column::Null(_))
{
None
} else {
Some(col)
};
return;
}
}
unreachable!("text is the top: every scalar has a text form")
}
fn coerced_all(raw: &Column, t: &ColumnType, base: &mut Option<String>) -> Option<Column> {
let mut out = Column::Null(0);
for i in 0..raw.len() {
let v = coerce_cell(&raw.get(i), t)?;
if *t == ColumnType::Quantity
&& let Value::Quantity { base: b, .. } = &v
{
match base {
Some(first) if first != b => return None,
Some(_) => {}
None => *base = Some(b.clone()),
}
}
out.push(v);
}
Some(out)
}
fn finish(self) -> Result<Column, (usize, String)> {
if let Some(failed) = self.failed {
return Err(failed);
}
Ok(match self.typed {
Some(t) => t,
None => self.raw,
})
}
}
#[derive(Debug)]
pub struct TableBuilder {
header: Option<Vec<String>>,
at: std::collections::HashMap<String, usize>,
columns: Vec<ColumnBuilder>,
declared: Vec<Option<ColumnType>>,
n: usize,
}
impl Default for TableBuilder {
fn default() -> Self {
TableBuilder::new()
}
}
impl TableBuilder {
pub fn new() -> TableBuilder {
TableBuilder {
header: Some(Vec::new()),
at: std::collections::HashMap::new(),
columns: Vec::new(),
declared: Vec::new(),
n: 0,
}
}
pub fn shaped(header: Option<Vec<String>>, declared: Vec<Option<ColumnType>>) -> TableBuilder {
let columns = header
.as_ref()
.map(|h| {
(0..h.len())
.map(|j| ColumnBuilder::new(0, declared.get(j).cloned().flatten()))
.collect()
})
.unwrap_or_default();
let at = header
.as_ref()
.map(|h| h.iter().cloned().enumerate().map(|(j, k)| (k, j)).collect())
.unwrap_or_default();
TableBuilder {
header,
at,
columns,
declared,
n: 0,
}
}
pub fn n_rows(&self) -> usize {
self.n
}
fn open_width(&mut self, w: usize) {
while self.columns.len() < w {
let j = self.columns.len();
self.columns.push(ColumnBuilder::new(
self.n,
self.declared.get(j).cloned().flatten(),
));
}
}
pub fn push_row(&mut self, row: Vec<Value>) {
if self.header.is_none() && self.n == 0 {
self.open_width(row.len());
}
let w = self.columns.len();
let mut cells = row.into_iter();
for col in self.columns.iter_mut() {
col.push(cells.next().unwrap_or(Value::Null));
}
debug_assert!(w == self.columns.len());
self.n += 1;
}
pub fn push_text_row(&mut self, row: &[&str]) {
if self.header.is_none() && self.n == 0 {
self.open_width(row.len());
}
for (j, col) in self.columns.iter_mut().enumerate() {
match row.get(j) {
Some(s) if !s.is_empty() => col.push_text(s),
_ => col.push(Value::Null),
}
}
self.n += 1;
}
pub fn push_record(&mut self, fields: impl IntoIterator<Item = (String, Value)>) {
let mut row: Vec<Option<Value>> = vec![None; self.columns.len()];
for (k, v) in fields {
let j = match self.at.get(&k) {
Some(&j) => j,
None => {
let j = self.columns.len();
self.at.insert(k.clone(), j);
self.header.get_or_insert_with(Vec::new).push(k);
self.columns.push(ColumnBuilder::new(self.n, None));
row.push(None);
j
}
};
row[j] = Some(v);
}
for (j, c) in row.into_iter().enumerate() {
self.columns[j].push(c.unwrap_or(Value::Null));
}
self.n += 1;
}
pub fn finish(self) -> Result<Table, String> {
let header = self.header;
let name_of = |j: usize| -> String {
match &header {
Some(h) => h.get(j).cloned().unwrap_or_else(|| (j + 1).to_string()),
None => (j + 1).to_string(),
}
};
let mut columns = Vec::with_capacity(self.columns.len());
for (j, col) in self.columns.into_iter().enumerate() {
let ty = col.declared.clone();
match col.finish() {
Ok(c) => columns.push(c),
Err((i, held)) => {
return Err(format!(
"column '{}' is declared {}, and row {} holds {}, which does not cast to it",
name_of(j),
ty.map(|t| t.word()).unwrap_or_default(),
i + 1,
held,
));
}
}
}
let mut declared = self.declared;
let w = columns.len();
declared.resize(w, None);
if declared.iter().all(Option::is_none) {
declared.clear();
}
Ok(Table {
header,
columns,
n: self.n,
declared,
index: 0,
name: None,
})
}
}
fn record_array_keys(items: &[Value]) -> Option<&[(String, Value)]> {
if items.len() < 2 {
return None;
}
let Value::Record(first) = &items[0] else {
return None;
};
if first.is_empty() {
return None;
}
let same = items.iter().all(|v| match v {
Value::Record(o) => {
o.len() == first.len() && o.iter().zip(first.iter()).all(|((a, _), (b, _))| a == b)
}
_ => false,
});
same.then_some(first.as_slice())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ColumnType {
Boolean,
Integer,
Float,
Instant,
Duration,
Period,
Quantity,
Node,
Text,
Tuple(std::sync::Arc<Vec<ColumnType>>),
Unit(std::sync::Arc<str>),
}
impl ColumnType {
pub fn unit(&self) -> Option<&str> {
match self {
ColumnType::Unit(u) => Some(u),
_ => None,
}
}
pub fn word(&self) -> String {
match self {
ColumnType::Boolean => "bool".to_string(),
ColumnType::Integer => "int".to_string(),
ColumnType::Float => "float".to_string(),
ColumnType::Instant => "instant".to_string(),
ColumnType::Duration => "duration".to_string(),
ColumnType::Period => "period".to_string(),
ColumnType::Quantity => "quantity".to_string(),
ColumnType::Node => "node".to_string(),
ColumnType::Text => "text".to_string(),
ColumnType::Unit(u) => format!("_{u}"),
ColumnType::Tuple(items) => {
format!(
"({})",
items
.iter()
.map(ColumnType::word)
.collect::<Vec<_>>()
.join("; ")
)
}
}
}
pub fn from_word(word: &str) -> Option<ColumnType> {
if let Some(u) = word.strip_prefix('_') {
return unit_shaped(u).then(|| ColumnType::Unit(u.into()));
}
if let Some(inner) = word.strip_prefix('(').and_then(|w| w.strip_suffix(')')) {
let items: Option<Vec<ColumnType>> = inner
.split([';', ','])
.map(|w| ColumnType::from_word(w.trim()))
.collect();
return items
.filter(|v| !v.is_empty())
.map(|v| ColumnType::Tuple(std::sync::Arc::new(v)));
}
ColumnType::ORDER.into_iter().find(|t| t.word() == word)
}
pub fn words() -> Vec<String> {
ColumnType::ORDER.iter().map(ColumnType::word).collect()
}
const ORDER: [ColumnType; 9] = [
ColumnType::Boolean,
ColumnType::Integer,
ColumnType::Float,
ColumnType::Instant,
ColumnType::Duration,
ColumnType::Period,
ColumnType::Quantity,
ColumnType::Node,
ColumnType::Text,
];
pub fn of_value(v: &Value) -> Option<ColumnType> {
ColumnType::of(v)
}
fn of(v: &Value) -> Option<ColumnType> {
Some(match v {
Value::Null => return None,
Value::Bool(_) => ColumnType::Boolean,
Value::Int(_) => ColumnType::Integer,
Value::Float(_) => ColumnType::Float,
Value::Instant { .. } => ColumnType::Instant,
Value::Quantity { base, .. } if base == "s" => ColumnType::Duration,
Value::Period { .. } => ColumnType::Period,
Value::Quantity { .. } => ColumnType::Quantity,
Value::Node(_) => ColumnType::Node,
_ => ColumnType::Text,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Typing {
Declared(ColumnType),
Star(Option<ColumnType>),
}
impl Typing {
pub fn spelling(&self) -> String {
match self {
Typing::Declared(t) => format!("<{}>", t.word()),
Typing::Star(None) => "<*>".to_string(),
Typing::Star(Some(t)) => format!("<*; {}>", t.word()),
}
}
pub fn declared(&self) -> Option<ColumnType> {
match self {
Typing::Declared(t) => Some(t.clone()),
Typing::Star(_) => None,
}
}
pub fn unit(&self) -> Option<&str> {
match self {
Typing::Declared(ColumnType::Unit(u)) => Some(u),
_ => None,
}
}
}
fn unit_shaped(u: &str) -> bool {
use crate::value::quantity::is_name_char;
u.chars().next().is_some_and(is_name_char)
&& u.chars()
.all(|c| is_name_char(c) || c.is_ascii_digit() || matches!(c, '*' | '/' | '^'))
}
pub(crate) fn cast_unit(v: &Value, unit: &str) -> Option<Value> {
use crate::value::quantity::{parse_unit_text, scale_by, unscale_by};
if matches!(v, Value::Null) {
return Some(Value::Null);
}
let (factor, base, _, _) = parse_unit_text(&format!("1 {unit}"))?;
let minted = |n: f64| Value::Quantity {
value: scale_by(n, factor),
base: base.clone(),
written: Some((n, unit.to_string())),
};
let converted = |value: f64, held: &str| {
(held == base).then(|| Value::Quantity {
value,
base: base.clone(),
written: Some((unscale_by(value, factor), unit.to_string())),
})
};
match v {
Value::Int(n) => Some(minted(*n as f64)),
Value::Float(x) => Some(minted(*x)),
Value::Quantity {
value, base: held, ..
} => converted(*value, held),
Value::Str(s) => {
match crate::value::number::read(s, crate::value::number::Decimal::Point) {
Some(Value::Int(n)) => Some(minted(n as f64)),
Some(Value::Float(x)) => Some(minted(x)),
_ => {
let (value, held, _, _) = parse_unit_text(s)?;
converted(value, &held)
}
}
}
_ => None,
}
}
pub(crate) fn unit_reading(v: Value, unit: &str) -> Value {
match v {
Value::List(items) => Value::list(
items
.iter()
.map(|i| unit_reading(i.clone(), unit))
.collect(),
),
other => cast_unit(&other, unit).unwrap_or(Value::Null),
}
}
pub(crate) fn cast_cell(v: &Value, t: &ColumnType) -> Option<Value> {
if let ColumnType::Unit(u) = t {
return cast_unit(v, u);
}
if let ColumnType::Tuple(items) = t {
if matches!(v, Value::Null) {
return Some(Value::Null);
}
let Value::List(cells) = v else { return None };
if cells.len() != items.len() {
return None;
}
let cast: Option<Vec<Value>> = cells
.iter()
.zip(items.iter())
.map(|(c, ty)| cast_cell(c, ty))
.collect();
return cast.map(Value::list);
}
coerce_cell(v, t).or_else(|| match (v, t) {
(Value::Float(f), ColumnType::Integer) if f.fract() == 0.0 && f.abs() < 9.0e15 => {
Some(Value::Int(*f as i64))
}
_ => None,
})
}
pub(crate) fn held_text(c: &Value) -> String {
match c {
Value::Str(s) => format!("\"{s}\""),
other => other.display_form(),
}
}
pub(crate) fn coerce_text(s: &str, t: &ColumnType) -> Option<Value> {
use ColumnType as T;
match t {
T::Tuple(_) => None,
T::Unit(u) => cast_unit(&Value::Str(s.to_string()), u),
T::Boolean => match s {
"true" => Some(Value::Bool(true)),
"false" => Some(Value::Bool(false)),
_ => None,
},
T::Integer => match crate::value::number::read(s, crate::value::number::Decimal::Point) {
Some(n @ Value::Int(_)) => Some(n),
_ => None,
},
T::Float => match crate::value::number::read(s, crate::value::number::Decimal::Point) {
Some(Value::Int(n)) => Some(Value::Float(n as f64)),
Some(f @ Value::Float(_)) => Some(f),
_ => None,
},
T::Instant => {
crate::temporal::parse_iso(s).map(|(secs, nanos, offset_min)| Value::Instant {
secs,
nanos,
offset_min,
calendar: Default::default(),
})
}
T::Duration => crate::temporal::parse_span(s)
.map(|(secs, nanos)| Value::seconds(secs, nanos))
.or_else(|| {
crate::value::quantity::parse_unit_text(s)
.filter(|(_, base, ..)| base == "s")
.map(|(value, base, wv, wu)| Value::Quantity {
value,
base,
written: Some((wv, wu)),
})
}),
T::Period => crate::period::Period::parse(s).map(Value::period),
T::Quantity => {
crate::value::quantity::parse_unit_text(s).map(|(value, base, mag, unit)| {
Value::Quantity {
value,
base,
written: Some((mag, unit)),
}
})
}
T::Node => None,
T::Text => Some(Value::Str(s.to_string())),
}
}
fn coerce_cell(v: &Value, t: &ColumnType) -> Option<Value> {
use ColumnType as T;
if matches!(v, Value::Null) {
return Some(Value::Null);
}
if let Value::Str(s) = v {
return coerce_text(s, t);
}
match t {
T::Tuple(_) => None,
T::Unit(u) => cast_unit(v, u),
T::Boolean => match v {
Value::Bool(_) => Some(v.clone()),
Value::Str(s) if s == "true" => Some(Value::Bool(true)),
Value::Str(s) if s == "false" => Some(Value::Bool(false)),
_ => None,
},
T::Integer => match v {
Value::Bool(b) => Some(Value::Int(i64::from(*b))),
Value::Int(_) => Some(v.clone()),
Value::Str(_) => match v.numeric_reading() {
Some(n @ Value::Int(_)) => Some(n),
_ => None,
},
_ => None,
},
T::Float => match v {
Value::Bool(b) => Some(Value::Float(if *b { 1.0 } else { 0.0 })),
Value::Int(n) => Some(Value::Float(*n as f64)),
Value::Float(_) => Some(v.clone()),
Value::Str(_) => match v.numeric_reading() {
Some(Value::Int(n)) => Some(Value::Float(n as f64)),
Some(f @ Value::Float(_)) => Some(f),
_ => None,
},
_ => None,
},
T::Instant => match v {
Value::Instant { .. } => Some(v.clone()),
Value::Str(s) => {
crate::temporal::parse_iso(s).map(|(secs, nanos, offset_min)| Value::Instant {
secs,
nanos,
offset_min,
calendar: Default::default(),
})
}
_ => None,
},
T::Duration => match v {
q if q.is_duration() => Some(q.clone()),
Value::Str(s) => crate::temporal::parse_span(s)
.map(|(secs, nanos)| Value::seconds(secs, nanos))
.or_else(|| {
crate::value::quantity::parse_unit_text(s)
.filter(|(_, base, ..)| base == "s")
.map(|(value, base, wv, wu)| Value::Quantity {
value,
base,
written: Some((wv, wu)),
})
}),
_ => None,
},
T::Period => match v {
Value::Period { .. } => Some(v.clone()),
Value::Str(s) => crate::period::Period::parse(s).map(Value::period),
_ => None,
},
T::Quantity => match v {
Value::Quantity { .. } => Some(v.clone()),
Value::Str(s) => {
crate::value::quantity::parse_unit_text(s).map(|(value, base, mag, unit)| {
Value::Quantity {
value,
base,
written: Some((mag, unit)),
}
})
}
_ => None,
},
T::Node => match v {
Value::Node(_) => Some(v.clone()),
_ => None,
},
T::Text => Some(match v {
Value::Str(_) => v.clone(),
other => Value::Str(other.to_string()),
}),
}
}
fn type_raw(col: &Column) -> Column {
let n = col.len();
'types: for t in ColumnType::ORDER {
let mut out = Column::Null(0);
let mut base: Option<String> = None;
for i in 0..n {
let Some(v) = coerce_cell(&col.get(i), &t) else {
continue 'types;
};
if t == ColumnType::Quantity
&& let Value::Quantity { base: b, .. } = &v
{
match &base {
Some(first) if first != b => continue 'types,
Some(_) => {}
None => base = Some(b.clone()),
}
}
out.push(v);
}
return out;
}
unreachable!("text is the top: every scalar has a text form")
}
pub(crate) fn type_column(cells: Vec<Value>) -> (ColumnType, Vec<Value>) {
for t in ColumnType::ORDER {
let coerced: Option<Vec<Value>> = cells.iter().map(|c| coerce_cell(c, &t)).collect();
let Some(coerced) = coerced else { continue };
if t == ColumnType::Quantity {
let mut bases = coerced.iter().filter_map(|c| match c {
Value::Quantity { base, .. } => Some(base.as_str()),
_ => None,
});
if let Some(first) = bases.next()
&& bases.any(|b| b != first)
{
continue;
}
}
return (t, coerced);
}
unreachable!("text is the top: every scalar has a text form")
}
#[derive(Clone, Copy)]
pub struct Row<'a> {
table: &'a Table,
i: usize,
}
impl<'a> Row<'a> {
pub fn get(&self, j: usize) -> Value {
self.table.cell(self.i, j)
}
pub fn index(&self) -> usize {
self.i
}
pub fn cells(&self) -> Vec<Value> {
(0..self.table.width()).map(|j| self.get(j)).collect()
}
pub fn iter(&self) -> impl Iterator<Item = Value> + use<'a> {
let (t, i) = (self.table, self.i);
(0..t.width()).map(move |j| t.cell(i, j))
}
pub fn len(&self) -> usize {
self.table.width()
}
pub fn is_empty(&self) -> bool {
self.table.width() == 0
}
}
impl Table {
pub fn assemble(
header: Option<Vec<String>>,
rows: Vec<Vec<Value>>,
declared: Vec<Option<ColumnType>>,
index: usize,
name: Option<String>,
) -> Table {
let w = match &header {
Some(h) => h.len(),
None => rows.first().map_or(0, Vec::len),
};
let mut columns = vec![Column::Null(0); w];
let mut n = 0;
for row in rows {
let mut cells = row.into_iter();
for col in columns.iter_mut() {
col.push(cells.next().unwrap_or(Value::Null));
}
n += 1;
}
Table {
header,
columns,
n,
declared,
index,
name,
}
}
pub fn n_rows(&self) -> usize {
self.n
}
pub fn cell(&self, i: usize, j: usize) -> Value {
self.columns.get(j).map_or(Value::Null, |c| c.get(i))
}
pub fn rows(&self) -> impl Iterator<Item = Row<'_>> + '_ {
(0..self.n_rows()).map(move |i| Row { table: self, i })
}
pub fn row_at(&self, i: usize) -> Option<Row<'_>> {
(i < self.n_rows()).then_some(Row { table: self, i })
}
pub fn row_cells(&self, i: usize) -> Vec<Value> {
self.row_at(i).map(|r| r.cells()).unwrap_or_default()
}
pub fn rows_vec(&self) -> Vec<Vec<Value>> {
self.rows().map(|r| r.cells()).collect()
}
pub fn take_rows(&self, idx: &[usize]) -> Table {
Table {
header: self.header.clone(),
columns: self.columns.iter().map(|c| c.take(idx)).collect(),
n: idx.len(),
declared: self.declared.clone(),
index: self.index,
name: self.name.clone(),
}
}
pub fn keep_rows(&self, rows: Vec<Vec<Value>>) -> Table {
Table::assemble(
self.header.clone(),
rows,
self.declared.clone(),
self.index,
self.name.clone(),
)
}
pub fn cast_columns(&self, casts: &[(String, ColumnType)]) -> Result<Table, String> {
let mut t = self.clone();
for (name, ty) in casts {
let Some(j) = (0..t.width()).find(|&j| t.column_name(j) == *name) else {
return Err(format!(
"--cast names column '{name}', and the table has no such column (its columns: {})",
(0..t.width())
.map(|j| t.column_name(j))
.collect::<Vec<_>>()
.join(", ")
));
};
let mut b = ColumnBuilder::new(0, Some(ty.clone()));
for i in 0..t.n {
b.push(t.columns[j].get(i));
}
match b.finish() {
Ok(col) => t.columns[j] = col,
Err((i, held)) => {
return Err(format!(
"column '{name}' is declared {}, and row {} holds {held}, which does not cast to it",
ty.word(),
i + 1
));
}
}
if t.declared.len() < t.width() {
t.declared.resize(t.width(), None);
}
t.declared[j] = Some(ty.clone());
}
Ok(t)
}
pub fn prepend_column(
&self,
name: String,
cells: Vec<Value>,
declared: Option<ColumnType>,
) -> Table {
let mut col = Column::Null(0);
for c in cells {
col.push(c);
}
let mut columns = Vec::with_capacity(self.columns.len() + 1);
columns.push(col);
columns.extend(self.columns.iter().cloned());
let mut header: Vec<String> = Vec::with_capacity(columns.len());
header.push(name);
header.extend((0..self.width()).map(|j| self.column_name(j)));
let mut decl: Vec<Option<ColumnType>> = Vec::with_capacity(columns.len());
decl.push(declared);
decl.extend((0..self.width()).map(|j| self.declaration(j)));
if decl.iter().all(Option::is_none) {
decl.clear();
}
Table {
header: Some(header),
columns,
n: self.n,
declared: decl,
index: 0,
name: self.name.clone(),
}
}
pub fn autocast(&self, only: Option<&[usize]>) -> Table {
let mut t = self.clone();
for j in 0..t.columns.len() {
if only.is_some_and(|o| !o.contains(&j))
|| t.declaration(j).is_some()
|| t.columns[j].any_cell(|c| matches!(c, Value::Projection { .. }))
{
continue;
}
t.columns[j] = type_raw(&t.columns[j]);
}
t
}
pub fn column_cells(&self, j: usize) -> Vec<Value> {
self.columns
.get(j)
.map_or_else(|| vec![Value::Null; self.n], Column::to_values)
}
}
impl Table {
pub fn new(header: Option<Vec<String>>, rows: Vec<Vec<Value>>) -> Table {
Table::typed(header, rows, Vec::new())
.unwrap_or_else(|_| unreachable!("no column is declared"))
}
pub fn declared(
header: Vec<(String, Option<ColumnType>)>,
rows: Vec<Vec<Value>>,
) -> Result<Table, String> {
let names: Vec<String> = header.iter().map(|(n, _)| n.clone()).collect();
let declared: Vec<Option<ColumnType>> = header.iter().map(|(_, t)| t.clone()).collect();
Table::typed(Some(names), rows, declared)
}
pub fn typed(
header: Option<Vec<String>>,
rows: Vec<Vec<Value>>,
mut declared: Vec<Option<ColumnType>>,
) -> Result<Table, String> {
let w = match &header {
Some(h) => h.len(),
None => rows.first().map_or(0, Vec::len),
};
declared.resize(w, None);
if declared.iter().all(Option::is_none) {
declared.clear();
}
let mut b = TableBuilder::shaped(header, declared);
for row in rows {
b.push_row(row);
}
b.finish()
}
pub(crate) fn retyped(mut self) -> Result<Table, String> {
if self.n == 0 {
return Ok(self);
}
for j in 0..self.columns.len() {
let coerced = match self.declared.get(j).cloned().flatten() {
Some(ty) => {
let mut out = Column::Null(0);
for i in 0..self.n {
let c = self.columns[j].get(i);
match cast_cell(&c, &ty) {
Some(v) => out.push(v),
None => {
return Err(format!(
"column '{}' is declared {}, and row {} holds {}, which does not cast to it",
self.column_name(j),
ty.word(),
i + 1,
held_text(&c),
));
}
}
}
out
}
None if self.columns[j].any_cell(|c| matches!(c, Value::Projection { .. })) => {
continue;
}
None => type_raw(&self.columns[j]),
};
self.columns[j] = coerced;
}
Ok(self)
}
pub fn rebuild(&self, rows: Vec<Vec<Value>>) -> Result<Table, String> {
Table::typed(self.header.clone(), rows, self.declared.clone())
.map(|t| t.with_index(self.index).with_name(self.name.clone()))
}
pub fn with_index(mut self, n: usize) -> Table {
self.index = n.min(self.width());
self
}
pub fn drop_index(self) -> Table {
self.with_index(0)
}
pub fn indexed_by(&self, name: &str, ty: Option<ColumnType>) -> Result<Table, String> {
self.indexed_by_levels(&[(name.to_string(), ty)])
}
pub fn indexed_by_levels(
&self,
levels: &[(String, Option<ColumnType>)],
) -> Result<Table, String> {
let w = self.width();
let mut order: Vec<usize> = Vec::with_capacity(w);
for (name, _) in levels {
let j = match &self.header {
Some(h) => h.iter().position(|c| c == name),
None => None,
}
.or_else(|| name.parse::<usize>().ok().filter(|j| *j >= 1 && *j <= w).map(|j| j - 1))
.ok_or_else(|| {
format!(
"--index names column '{name}', and the table has no such column (its columns: {})",
(0..w).map(|j| self.column_name(j)).collect::<Vec<_>>().join(", ")
)
})?;
if order.contains(&j) {
return Err(format!("--index names column '{name}' twice"));
}
order.push(j);
}
let n = order.len();
let chosen = order.clone();
order.extend((0..w).filter(|k| !chosen.contains(k)));
let header = self
.header
.as_ref()
.map(|h| order.iter().map(|k| h[*k].clone()).collect());
let mut declared: Vec<Option<ColumnType>> =
order.iter().map(|k| self.declaration(*k)).collect();
for (i, (_, ty)) in levels.iter().enumerate() {
if ty.is_some() {
declared[i] = ty.clone();
}
}
let rows = self
.rows()
.map(|r| order.iter().map(|k| r.get(*k)).collect())
.collect();
Ok(Table::typed(header, rows, declared)?.with_index(n))
}
pub fn labels(&self) -> Option<Vec<Value>> {
if self.index == 0 {
return None;
}
if self.index == 1 {
return Some(self.rows().map(|r| r.get(0)).collect());
}
let names: Vec<String> = (0..self.index).map(|j| self.column_name(j)).collect();
Some(
self.rows()
.map(|r| {
Value::record(
names
.iter()
.enumerate()
.map(|(j, n)| (n.clone(), r.get(j)))
.collect(),
)
})
.collect(),
)
}
pub fn label_name(&self, row: &[Value]) -> String {
row[..self.index]
.iter()
.map(Record::key_text)
.collect::<Vec<_>>()
.join(":")
}
pub fn level_names(&self) -> Vec<String> {
(0..self.index).map(|j| self.column_name(j)).collect()
}
pub fn rows_at_levels(&self, levels: &[Value]) -> Vec<usize> {
let types = self.types();
let wanted: Vec<Value> = levels
.iter()
.enumerate()
.map(|(j, l)| match types.get(j) {
Some(t) => cast_cell(l, t).unwrap_or_else(|| l.clone()),
None => l.clone(),
})
.collect();
self.rows()
.enumerate()
.filter(|(_, r)| wanted.iter().enumerate().all(|(j, w)| r.get(j) == *w))
.map(|(i, _)| i)
.collect()
}
pub fn sub_columns(&self, prefix: &str) -> Option<Table> {
let h = self.header.as_ref()?;
let lead = format!("{prefix}:");
let under: Vec<usize> = (self.index..self.width())
.filter(|&j| h[j].starts_with(&lead))
.collect();
if under.is_empty() {
return None;
}
let order: Vec<usize> = (0..self.index).chain(under.iter().copied()).collect();
let header: Vec<String> = order
.iter()
.map(|&j| {
if j < self.index {
h[j].clone()
} else {
h[j][lead.len()..].to_string()
}
})
.collect();
let declared: Vec<Option<ColumnType>> =
order.iter().map(|&j| self.declaration(j)).collect();
let rows: Vec<Vec<Value>> = self
.rows()
.map(|r| order.iter().map(|&j| r.get(j)).collect())
.collect();
Some(
Table::typed(Some(header), rows, declared)
.unwrap_or_else(|_| Table::new(None, Vec::new()))
.with_index(self.index),
)
}
pub fn header_has_levels(&self) -> bool {
self.header
.as_ref()
.is_some_and(|h| h[self.index..].iter().any(|n| n.contains(':')))
}
pub fn stacked(&self) -> Result<Table, String> {
if self.index == 0 {
return Err("stack moves the header's innermost level into the index, and the table has no index: name one with #(-i=:col)".into());
}
let h = self
.header
.clone()
.unwrap_or_else(|| (1..=self.width()).map(|j| j.to_string()).collect());
let data: Vec<usize> = (self.index..self.width()).collect();
let split = |name: &str| -> (Option<String>, String) {
match name.rsplit_once(':') {
Some((outer, inner)) => (Some(outer.to_string()), inner.to_string()),
None => (None, name.to_string()),
}
};
let mut inner_values: Vec<String> = Vec::new();
let mut outers: Vec<Option<String>> = Vec::new();
for &j in &data {
let (outer, inner) = split(&h[j]);
if !inner_values.contains(&inner) {
inner_values.push(inner);
}
if !outers.contains(&outer) {
outers.push(outer);
}
}
let mut header: Vec<String> = h[..self.index].to_vec();
header.push("variable".to_string());
let new_cols: Vec<Option<String>> = outers.clone();
header.extend(
new_cols
.iter()
.map(|o| o.clone().unwrap_or_else(|| "value".to_string())),
);
let mut rows: Vec<Vec<Value>> = Vec::new();
for r in self.rows() {
for inner in &inner_values {
let mut row: Vec<Value> = (0..self.index).map(|j| r.get(j)).collect();
row.push(Value::Str(inner.clone()));
for outer in &new_cols {
let name = match outer {
Some(o) => format!("{o}:{inner}"),
None => inner.clone(),
};
let cell = h
.iter()
.position(|n| *n == name)
.map(|j| r.get(j))
.unwrap_or(Value::Null);
row.push(cell);
}
rows.push(row);
}
}
Ok(Table::new(Some(header), rows).with_index(self.index + 1))
}
pub fn unstacked(&self) -> Result<Table, String> {
if self.index == 0 {
return Err("unstack moves the index's innermost level into the header, and the table has no index level to move".into());
}
let h = self
.header
.clone()
.unwrap_or_else(|| (1..=self.width()).map(|j| j.to_string()).collect());
let k = self.index - 1;
let data: Vec<usize> = (self.index..self.width()).collect();
let mut values: Vec<Value> = Vec::new();
for r in self.rows() {
let v = r.get(k);
if !values.contains(&v) {
values.push(v);
}
}
let mut seen: Vec<Vec<Value>> = Vec::with_capacity(self.n_rows());
for r in self.rows() {
let key: Vec<Value> = (0..self.index).map(|j| r.get(j)).collect();
if seen.contains(&key) {
return Err(format!(
"the label {} stands on several rows, so it has no one cell to put under its column: aggregate the rows first (| mean(--by=:col)), or read them with | @",
self.label_name(&r.cells())
));
}
seen.push(key);
}
let mut header: Vec<String> = h[..k].to_vec();
for &j in &data {
for v in &values {
header.push(if data.len() == 1 {
Record::key_text(v)
} else {
format!("{}:{}", h[j], Record::key_text(v))
});
}
}
let mut keys: Vec<Vec<Value>> = Vec::new();
for r in self.rows() {
let key: Vec<Value> = (0..k).map(|j| r.get(j)).collect();
if !keys.contains(&key) {
keys.push(key);
}
}
let rows: Vec<Vec<Value>> = keys
.iter()
.map(|key| {
let mut row = key.clone();
for &j in &data {
for v in &values {
let cell = self
.rows()
.find(|r| (0..k).all(|i| r.get(i) == key[i]) && r.get(k) == *v)
.map(|r| r.get(j))
.unwrap_or(Value::Null);
row.push(cell);
}
}
row
})
.collect();
Ok(Table::new(Some(header), rows).with_index(k))
}
pub fn sub_table(&self, rows: &[usize], k: usize) -> Table {
let header = self.header.as_ref().map(|h| h[k..].to_vec());
let declared: Vec<Option<ColumnType>> =
(k..self.width()).map(|j| self.declaration(j)).collect();
let body: Vec<Vec<Value>> = rows
.iter()
.map(|&i| self.row_cells(i)[k..].to_vec())
.collect();
Table::typed(header, body, declared)
.unwrap_or_else(|_| {
Table::new(
self.header.as_ref().map(|h| h[k..].to_vec()),
rows.iter()
.map(|&i| self.row_cells(i)[k..].to_vec())
.collect(),
)
})
.with_index(self.index.saturating_sub(k))
}
pub fn rows_labelled(&self, label: &Value) -> Vec<usize> {
let Some(labels) = self.labels() else {
return Vec::new();
};
let ty = self.types().first().cloned();
let wanted = match ty {
Some(t) => cast_cell(label, &t).unwrap_or_else(|| label.clone()),
None => label.clone(),
};
labels
.iter()
.enumerate()
.filter(|(_, l)| **l == wanted)
.map(|(i, _)| i)
.collect()
}
pub fn column_value(&self, name: &str) -> Result<Value, String> {
let Some(list) = self.column(name) else {
return Ok(self
.sub_columns(name)
.map(Value::table)
.unwrap_or(Value::Null));
};
let Some(labels) = self.labels() else {
return Ok(list);
};
let mut seen: Vec<&Value> = Vec::with_capacity(labels.len());
for l in &labels {
if seen.contains(&l) {
return Err(format!(
"the label {} stands on several rows, so the column '{name}' is no record keyed by it: read the rows with | @, or look the label up with [[{}]]",
l.to_quarb(),
l.to_quarb()
));
}
seen.push(l);
}
let cells = list.into_items().unwrap_or_default();
if self.index == 1 {
return Ok(Value::Record(Record::keyed(
labels.into_iter().zip(cells).collect(),
)));
}
let levels: Vec<Vec<Value>> = self
.rows()
.map(|r| (0..self.index).map(|j| r.get(j)).collect())
.collect();
Ok(nest_levels(&levels, &cells, 0))
}
pub fn with_rows(&self, rows: Vec<Vec<Value>>) -> Table {
if self.declared.is_empty() {
return Table::new(self.header.clone(), rows)
.with_index(self.index)
.with_name(self.name.clone());
}
match Table::typed(self.header.clone(), rows.clone(), self.declared.clone()) {
Ok(t) => t.with_index(self.index).with_name(self.name.clone()),
Err(_) => Table::new(self.header.clone(), rows)
.with_index(self.index)
.with_name(self.name.clone()),
}
}
pub fn with_name(mut self, name: Option<String>) -> Table {
self.name = name.filter(|n| !n.is_empty() && n != "column");
self
}
pub fn column_name(&self, j: usize) -> String {
match &self.header {
Some(h) => h.get(j).cloned().unwrap_or_else(|| (j + 1).to_string()),
None => (j + 1).to_string(),
}
}
pub fn declaration(&self, j: usize) -> Option<ColumnType> {
self.declared.get(j).cloned().flatten()
}
pub fn header_projectors(&self) -> Vec<Value> {
let types = self.types();
(0..self.width())
.map(|j| {
let typing = match self.declaration(j) {
Some(t) => Typing::Declared(t),
None => Typing::Star(Some(types[j].clone())),
};
Value::typed_projection(1, self.column_name(j), Some(typing))
})
.collect()
}
pub fn matrix(rows: Vec<Vec<Value>>) -> Table {
let w = rows.first().map_or(0, Vec::len);
let cells: Vec<Value> = rows.into_iter().flatten().collect();
let (_, coerced) = type_column(cells);
let rows: Vec<Vec<Value>> = if w == 0 {
Vec::new()
} else {
coerced.chunks(w).map(|c| c.to_vec()).collect()
};
Table::assemble(None, rows, Vec::new(), 0, None)
}
pub fn types(&self) -> Vec<ColumnType> {
(0..self.width())
.map(|j| {
if let Some(ty) = self.declaration(j) {
return ty;
}
self.rows()
.find_map(|r| ColumnType::of(&r.get(j)))
.unwrap_or(ColumnType::Text)
})
.collect()
}
pub fn types_value(&self) -> Value {
Value::list(
self.types()
.into_iter()
.map(|t| Value::Str(t.word().to_string()))
.collect(),
)
}
pub fn is_flat(&self) -> bool {
self.rows().flat_map(|r| r.iter()).all(|c| {
!matches!(
c,
Value::List(_) | Value::Record(_) | Value::Table(_) | Value::Foreign(_)
)
})
}
pub fn into_records(self) -> Value {
Value::list(
(0..self.n)
.map(|i| {
let r = self.row_cells(i);
match &self.header {
Some(h) => Value::Record(h.iter().cloned().zip(r).collect()),
None => Value::list(r),
}
})
.collect(),
)
}
pub fn width(&self) -> usize {
match &self.header {
Some(h) => h.len(),
None => self.columns.len(),
}
}
pub fn shape(&self) -> Value {
Value::list(vec![
Value::Int(self.n_rows() as i64),
Value::Int(self.width() as i64),
])
}
pub fn kind(&self) -> String {
format!(
"{} {}×{}",
if self.header.is_some() {
"a frame"
} else {
"an array"
},
self.n_rows(),
self.width()
)
}
pub fn is_sample(&self) -> bool {
self.header.is_none() && self.width() == 1
}
pub fn rows_value(&self) -> Value {
Value::list(self.rows().map(|r| self.row_value(&r.cells())).collect())
}
fn row_value(&self, row: &[Value]) -> Value {
match &self.header {
Some(h) if self.header_has_levels() => {
nest_by_levels(h.iter().cloned().zip(row.iter().cloned()).collect())
}
Some(h) => Value::Record(h.iter().cloned().zip(row.iter().cloned()).collect()),
None if self.width() == 1 => row.first().cloned().unwrap_or(Value::Null),
None => Value::list(row.to_vec()),
}
}
pub fn row(&self, i: usize) -> Option<Value> {
(i >= 1)
.then(|| self.row_at(i - 1))
.flatten()
.map(|r| self.row_value(&r.cells()))
}
pub fn columns_value(&self) -> Value {
if self.index > 0 || self.header_has_levels() {
let fields: Vec<(String, Value)> = (self.index..self.width())
.map(|j| {
let name = self.column_name(j);
let v = self
.column_value(&name)
.unwrap_or_else(|_| self.column_at(j));
(name, v)
})
.collect();
return nest_by_levels(fields);
}
let cols: Vec<Value> = (0..self.width()).map(|j| self.column_at(j)).collect();
let keys: Vec<String> = match &self.header {
Some(h) => h.clone(),
None => (1..=self.width()).map(|j| j.to_string()).collect(),
};
Value::Record(keys.into_iter().zip(cols).collect())
}
pub fn header_value(&self) -> Value {
match &self.header {
Some(h) => Value::list(h.iter().cloned().map(Value::Str).collect()),
None => Value::Null,
}
}
pub fn slice(&self, a: Option<i64>, b: Option<i64>) -> Table {
let n = self.n as i64;
let from = a.unwrap_or(1).clamp(1, n + 1) - 1;
let to = b.unwrap_or(n).clamp(0, n);
let (from, to) = if from < to {
(from as usize, to as usize)
} else {
(0, 0)
};
Table {
header: self.header.clone(),
columns: self.columns.iter().map(|c| c.slice(from, to)).collect(),
n: to - from,
declared: self.declared.clone(),
index: self.index,
name: self.name.clone(),
}
}
pub fn to_quarb_lines(&self) -> String {
let mut parts: Vec<String> = Vec::with_capacity(self.n_rows() + 1);
if let Some(h) = &self.header {
let names: Vec<String> = h.iter().map(|k| quarb_key(k)).collect();
parts.push(format!("@({})", names.join("; ")));
}
let sigil = if self.header.is_some() { '%' } else { '@' };
for r in self.rows() {
let cells: Vec<String> = r.iter().map(|v| v.to_quarb()).collect();
parts.push(format!("{sigil}({})", cells.join("; ")));
}
if parts.len() <= 1 {
return format!("#({})", parts.join(""));
}
format!("#({})", parts.join(";\n "))
}
fn column_at(&self, j: usize) -> Value {
Value::list(self.rows().map(|r| r.get(j)).collect())
}
pub fn column(&self, name: &str) -> Option<Value> {
match &self.header {
Some(h) => h
.iter()
.position(|c| c == name)
.or_else(|| {
name.parse::<usize>()
.ok()
.filter(|&j| j >= 1 && j <= self.width())
.map(|j| j - 1)
})
.map(|j| self.column_at(j)),
None => name
.parse::<usize>()
.ok()
.filter(|j| *j >= 1 && *j <= self.width())
.map(|j| self.column_at(j - 1)),
}
}
pub fn transposed(&self) -> Table {
let w = self.width();
Table::new(
None,
(0..w)
.map(|j| self.rows().map(|r| r.get(j)).collect())
.collect(),
)
}
pub fn to_quarb(&self) -> String {
let mut parts: Vec<String> = Vec::with_capacity(self.n_rows() + 1);
if let Some(h) = &self.header {
let names: Vec<String> = h.iter().map(|k| quarb_key(k)).collect();
parts.push(format!("@({})", names.join("; ")));
}
let sigil = if self.header.is_some() { '%' } else { '@' };
for r in self.rows() {
let cells: Vec<String> = r.iter().map(|v| v.to_quarb()).collect();
parts.push(format!("{sigil}({})", cells.join("; ")));
}
format!("#({})", parts.join("; "))
}
}
pub trait Foreign: Send + Sync {
fn kind(&self) -> String;
fn display(&self) -> String;
fn rows(&self) -> Result<Value, String>;
fn columns(&self) -> Result<Value, String>;
fn as_any(&self) -> &dyn std::any::Any;
}
#[derive(Clone)]
pub struct ForeignValue(pub std::sync::Arc<dyn Foreign>);
impl ForeignValue {
pub fn new(f: impl Foreign + 'static) -> ForeignValue {
ForeignValue(std::sync::Arc::new(f))
}
pub fn refusal(&self, command: &str) -> String {
format!(
"{command}: {} is a library value; '| @' exports its rows, '| %' its columns",
self.0.kind()
)
}
}
impl std::ops::Deref for ForeignValue {
type Target = dyn Foreign;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl PartialEq for ForeignValue {
fn eq(&self, other: &ForeignValue) -> bool {
std::sync::Arc::ptr_eq(&self.0, &other.0)
}
}
impl std::fmt::Debug for ForeignValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Foreign({})", self.0.kind())
}
}
impl Value {
pub fn record(fields: Vec<(String, Value)>) -> Value {
Value::Record(Record::new(fields))
}
pub fn projection(ladder: u8, key: impl Into<String>) -> Value {
Value::Projection {
ladder,
key: key.into(),
typing: None,
}
}
pub fn typed_projection(ladder: u8, key: impl Into<String>, typing: Option<Typing>) -> Value {
Value::Projection {
ladder,
key: key.into(),
typing,
}
}
pub fn declared_type(&self) -> Option<ColumnType> {
match self {
Value::Projection {
typing: Some(t), ..
} => t.declared(),
_ => None,
}
}
pub fn quarq(body: Vec<crate::syntax::ast::Command>, projector: Option<Value>) -> Value {
let source = crate::syntax::unparse::quarq_source(&body);
Value::Quarq(Quarq {
source,
body: std::sync::Arc::new(body),
projector: projector.map(Box::new),
holes: std::sync::Arc::new(Vec::new()),
form: None,
at: None,
})
}
pub fn quarq_form(form: crate::syntax::expand::Form) -> Value {
let body = form.as_commands();
let source = if body.is_empty() {
format!("q({})", form.text())
} else {
crate::syntax::unparse::quarq_source(&body)
};
Value::Quarq(Quarq {
source,
body: std::sync::Arc::new(body),
projector: None,
holes: std::sync::Arc::new(Vec::new()),
form: Some(Box::new(form)),
at: None,
})
}
pub fn quarq_template(
body: Vec<crate::syntax::ast::Command>,
holes: Vec<crate::syntax::ast::Hole>,
) -> Value {
let source = crate::syntax::unparse::quarq_source(&body);
Value::Quarq(Quarq {
source,
body: std::sync::Arc::new(body),
projector: None,
holes: std::sync::Arc::new(holes),
form: None,
at: None,
})
}
pub fn holds_projector(&self) -> bool {
match self {
Value::Projection { .. } => true,
Value::List(items) => items.iter().any(Value::holds_projector),
Value::Record(fields) => fields.iter().any(|(_, v)| v.holds_projector()),
Value::Table(t) => t.rows().flat_map(|r| r.iter()).any(|v| v.holds_projector()),
_ => false,
}
}
pub fn projector_spelling(ladder: u8, key: &str, typing: Option<&Typing>) -> String {
let mut s = Value::projection_spelling(ladder, key);
if let Some(t) = typing {
s.push_str(&t.spelling());
}
s
}
pub fn projection_spelling(ladder: u8, key: &str) -> String {
let ident = |k: &str| {
let mut cs = k.chars();
(matches!(cs.next(), Some(c) if c.is_alphabetic() || c == '_')
&& cs.all(|c| c.is_alphanumeric() || c == '_' || c == '-'))
|| (!k.is_empty() && k.bytes().all(|b| b.is_ascii_digit()))
};
let bare = ident(key) || (key.contains(':') && key.split(':').all(ident));
let key = if bare {
key.to_string()
} else {
format!("'{}'", key.replace('\\', "\\\\").replace('\'', "\\'"))
};
format!("{}{key}", ":".repeat(usize::from(ladder.clamp(1, 3))))
}
pub fn list(items: Vec<Value>) -> Value {
Value::List(std::sync::Arc::new(items))
}
pub fn table(t: Table) -> Value {
Value::Table(std::sync::Arc::new(t))
}
pub fn into_items(self) -> Option<Vec<Value>> {
match self {
Value::List(l) => Some(std::sync::Arc::try_unwrap(l).unwrap_or_else(|a| (*a).clone())),
_ => None,
}
}
pub fn bytes(n: i64) -> Value {
Value::Quantity {
value: n as f64 * 8.0,
base: "b".to_string(),
written: Some((n as f64, "B".to_string())),
}
}
pub fn is_truthy(&self) -> bool {
match self {
Value::Null => false,
Value::Bool(b) => *b,
Value::Int(n) => *n != 0,
Value::Float(f) => *f != 0.0,
Value::Str(s) => !s.is_empty(),
Value::List(l) => !l.is_empty(),
Value::Record(o) => !o.is_empty(),
Value::Table(t) => t.n_rows() != 0,
Value::Instant { .. } => true,
Value::Period { .. } => true,
Value::Quantity { value, .. } => *value != 0.0,
Value::Foreign(f) => {
crate::value::ambient::record_refusal(f.refusal("a predicate"));
false
}
Value::Node(_) => true,
Value::Projection { .. } => true,
Value::Quarq(_) => true,
}
}
pub fn temporal_reading(&self) -> Option<(i64, u32)> {
match self {
Value::Instant { secs, nanos, .. } => Some((*secs, *nanos)),
Value::Period { start, .. } => Some((*start, 0)),
Value::Int(n) => Some((*n, 0)),
Value::Float(f) if f.is_finite() => {
let secs = f.floor() as i64;
let nanos = ((f - f.floor()) * 1e9) as u32;
Some((secs, nanos))
}
Value::Str(s) => crate::temporal::parse_iso(s).map(|(s, n, _)| (s, n)),
_ => None,
}
}
pub fn calendar_reading(&self) -> Option<crate::period::Period> {
use crate::period::Period;
match self {
Value::Period {
start,
grain,
calendar,
} => Some(Period {
start: *start,
grain: *grain,
calendar: *calendar,
}),
Value::Int(_) => None,
Value::Instant {
secs,
nanos,
offset_min,
calendar,
} => Some(Period::of_instant(*secs, *nanos, *offset_min).in_calendar(*calendar)),
Value::Str(s) => year_word(s).or_else(|| Period::parse(s)).or_else(|| {
crate::temporal::parse_iso(s)
.map(|(secs, nanos, off)| Period::of_instant(secs, nanos, off))
}),
_ => None,
}
}
pub fn period(p: crate::period::Period) -> Value {
Value::Period {
start: p.start,
grain: p.grain,
calendar: p.calendar,
}
}
pub fn unital_reading(&self) -> Option<(f64, String)> {
match self {
Value::Quantity { value, base, .. } => Some((*value, base.clone())),
Value::Str(s) => crate::value::quantity::parse_unit_text(s)
.map(|(v, base, ..)| (v, base.to_string())),
_ => None,
}
}
pub fn durational_reading(&self) -> Option<(i64, u32)> {
match self {
Value::Quantity { .. } => self.span_parts(),
Value::Str(s) => crate::value::quantity::parse_unit_text(s)
.filter(|(_, base, ..)| base == "s")
.map(|(v, ..)| split_seconds(v))
.or_else(|| crate::temporal::parse_span(s)),
_ => None,
}
}
pub fn seconds(secs: i64, nanos: u32) -> Value {
Value::Quantity {
value: secs as f64 + nanos as f64 / 1e9,
base: "s".to_string(),
written: None,
}
}
pub fn is_duration(&self) -> bool {
matches!(self, Value::Quantity { base, .. } if base == "s")
}
pub fn span_parts(&self) -> Option<(i64, u32)> {
match self {
Value::Quantity { value, base, .. } if base == "s" && value.is_finite() => {
Some(split_seconds(*value))
}
_ => None,
}
}
pub fn to_quarb(&self) -> String {
match self {
Value::Null => "null".to_string(),
Value::Str(s) => quarb_string(s),
Value::List(l) => {
if let Some(keys) = record_array_keys(l) {
let mut parts: Vec<String> = Vec::with_capacity(l.len() + 1);
let names: Vec<String> = keys.iter().map(|(k, _)| quarb_key(k)).collect();
parts.push(format!("@({})", names.join("; ")));
for item in l.iter() {
if let Value::Record(o) = item {
let cells: Vec<String> = o.iter().map(|(_, v)| v.to_quarb()).collect();
parts.push(format!("%({})", cells.join("; ")));
}
}
return format!("@%({})", parts.join("; "));
}
let inner: Vec<String> = l.iter().map(Value::to_quarb).collect();
format!("@({})", inner.join("; "))
}
Value::Record(o) => {
let inner: Vec<String> = o
.iter()
.enumerate()
.map(|(i, (_, v))| format!("{} = {}", o.key_quarb(i), v.to_quarb()))
.collect();
format!("%({})", inner.join("; "))
}
Value::Table(t) => t.to_quarb(),
Value::Projection {
ladder,
key,
typing,
} => Value::projector_spelling(*ladder, key, typing.as_ref()),
Value::Quarq(q) => q.source.clone(),
other => other.to_string(),
}
}
pub fn table_or_records(header: Option<Vec<String>>, rows: Vec<Vec<Value>>) -> Value {
let raw = Table::assemble(header, rows, Vec::new(), 0, None);
if raw.is_flat() {
Value::table(
raw.retyped()
.unwrap_or_else(|_| unreachable!("no column is declared")),
)
} else {
raw.into_records()
}
}
pub fn display_form(&self) -> String {
match self {
Value::Null => "null".to_string(),
Value::List(_) | Value::Record(_) => self.to_quarb(),
Value::Projection {
ladder,
key,
typing,
} => Value::projector_spelling(*ladder, key, typing.as_ref()),
Value::Quarq(q) => q.source.clone(),
Value::Table(t) => t.to_quarb_lines(),
other => other.to_string(),
}
}
pub fn to_json(&self) -> String {
match self {
Value::Null => "null".to_string(),
Value::Bool(b) => b.to_string(),
Value::Int(n) => n.to_string(),
Value::Float(f) if f.is_finite() => format_float(*f),
Value::Float(_) => "null".to_string(),
Value::Str(s) => json_string(s),
Value::List(l) => {
let inner: Vec<String> = l.iter().map(Value::to_json).collect();
format!("[{}]", inner.join(", "))
}
Value::Record(o) => {
let inner: Vec<String> = o
.iter()
.map(|(k, v)| format!("{}: {}", json_string(k), v.to_json()))
.collect();
format!("{{{}}}", inner.join(", "))
}
Value::Instant { .. } | Value::Period { .. } | Value::Quantity { .. } => {
json_string(&self.to_string())
}
Value::Table(t) => t.rows_value().to_json(),
Value::Foreign(f) => match f.rows() {
Ok(rows) => rows.to_json(),
Err(_) => json_string(&f.display()),
},
Value::Node(n) => json_string(&n.locator),
Value::Projection {
ladder,
key,
typing,
} => json_string(&Value::projector_spelling(*ladder, key, typing.as_ref())),
Value::Quarq(q) => json_string(&q.source),
}
}
pub fn equality_key(&self) -> Option<String> {
match self {
Value::Int(_) | Value::Float(_) | Value::Str(_) => Some(match self.numeric_reading() {
Some(Value::Int(n)) => format!("n:{n}"),
Some(Value::Float(f)) if f.fract() == 0.0 && f.abs() < 9.2e18 => {
format!("n:{}", f as i64)
}
Some(Value::Float(f)) => format!("n:{f}"),
_ => format!("s:{self}"),
}),
Value::Bool(b) => Some(format!("b:{b}")),
Value::Null => Some("0:".to_string()),
Value::Node(n) => Some(format!("node:{}", n.id)),
Value::Projection { key, .. } => Some(format!("s:{key}")),
Value::Quarq(q) => Some(format!("q:{}", q.source)),
_ => None,
}
}
pub fn numeric(&self) -> Option<f64> {
match self {
Value::Int(n) => Some(*n as f64),
Value::Float(f) => Some(*f),
Value::Str(s) => crate::value::number::read(s, crate::value::number::Decimal::Point)
.and_then(|v| v.numeric()),
Value::Quantity { value, .. } => Some(*value),
_ => None,
}
}
pub fn numeric_reading(&self) -> Option<Value> {
match self {
Value::Int(n) => Some(Value::Int(*n)),
Value::Float(f) => Some(Value::Float(*f)),
Value::Str(s) => crate::value::number::read(s, crate::value::number::Decimal::Point),
_ => None,
}
}
pub fn compare(&self, other: &Value) -> std::cmp::Ordering {
self.compare_with(other, &crate::value::quantity::scale_expr)
}
pub fn compare_with(
&self,
other: &Value,
scale: &dyn Fn(&str) -> Option<(f64, String)>,
) -> std::cmp::Ordering {
sort_key(self, scale).cmp_key(&sort_key(other, scale))
}
pub(crate) fn sort_key_with(&self, scale: &dyn Fn(&str) -> Option<(f64, String)>) -> SortKey {
sort_key(self, scale)
}
}
#[derive(Debug, Clone)]
pub(crate) enum SortKey {
Null,
Bool(bool),
Line(f64, i64, String),
Period(i64, u8),
Text(String),
ListText(String),
RecordText(String),
}
impl PartialEq for SortKey {
fn eq(&self, other: &SortKey) -> bool {
self.cmp_key(other) == std::cmp::Ordering::Equal
}
}
impl Eq for SortKey {}
impl std::hash::Hash for SortKey {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.rank().hash(state);
match self {
SortKey::Null => {}
SortKey::Bool(b) => b.hash(state),
SortKey::Line(f, i, dim) => {
dim.hash(state);
(if *f == 0.0 { 0.0f64 } else { *f }).to_bits().hash(state);
i.hash(state);
}
SortKey::Period(start, grain) => {
start.hash(state);
grain.hash(state);
}
SortKey::Text(s) | SortKey::ListText(s) | SortKey::RecordText(s) => s.hash(state),
}
}
}
impl SortKey {
fn line(f: f64) -> SortKey {
SortKey::line_of(f, String::new())
}
fn line_of(f: f64, dim: String) -> SortKey {
let whole = f.fract() == 0.0 && f.abs() < 9.2e18;
SortKey::Line(f, if whole { f as i64 } else { 0 }, dim)
}
fn rank(&self) -> u8 {
match self {
SortKey::Null => 0,
SortKey::Bool(_) => 1,
SortKey::Line(..) => 2,
SortKey::Period(..) => 3,
SortKey::Text(_) => 4,
SortKey::ListText(_) => 5,
SortKey::RecordText(_) => 6,
}
}
pub(crate) fn cmp_key(&self, other: &SortKey) -> std::cmp::Ordering {
use SortKey::*;
match (self, other) {
(Bool(a), Bool(b)) => a.cmp(b),
(Line(a, i, da), Line(b, j, db)) => {
da.cmp(db).then_with(|| match (a.is_nan(), b.is_nan()) {
(true, true) => i.cmp(j),
(true, false) => std::cmp::Ordering::Greater,
(false, true) => std::cmp::Ordering::Less,
(false, false) => {
if *a == 0.0 && *b == 0.0 {
i.cmp(j)
} else {
a.total_cmp(b).then(i.cmp(j))
}
}
})
}
(Period(a, g), Period(b, h)) => a.cmp(b).then(g.cmp(h)),
(Text(a), Text(b)) | (ListText(a), ListText(b)) | (RecordText(a), RecordText(b)) => {
a.cmp(b)
}
_ => self.rank().cmp(&other.rank()),
}
}
}
fn sort_key(v: &Value, scale: &dyn Fn(&str) -> Option<(f64, String)>) -> SortKey {
match v {
Value::Null => SortKey::Null,
Value::Bool(b) => SortKey::Bool(*b),
Value::Int(n) => SortKey::Line(*n as f64, *n, String::new()),
Value::Float(f) => SortKey::line(*f),
Value::Instant { secs, nanos, .. } => SortKey::line(*secs as f64 + *nanos as f64 / 1e9),
Value::Period { start, grain, .. } => SortKey::Period(*start, *grain as u8),
Value::Quantity { value, base, .. } => {
SortKey::line_of(*value, crate::value::quantity::dimension_of(base))
}
Value::Foreign(x) => {
SortKey::RecordText(format!("\u{0}foreign:{:p}", std::sync::Arc::as_ptr(&x.0)))
}
Value::Node(n) => SortKey::RecordText(format!("\u{0}node:{}", n.locator)),
Value::Projection { key, .. } => sort_key(&Value::Str(key.clone()), scale),
Value::Quarq(q) => sort_key(&Value::Str(q.source.clone()), scale),
Value::Table(t) => SortKey::RecordText(format!("\u{0}table:{}", t.to_quarb())),
Value::Str(s) => {
if let Some(f) = crate::value::number::read(s, crate::value::number::Decimal::Point)
.and_then(|v| v.numeric())
{
SortKey::line(f)
} else if let Some((bv, base, ..)) =
crate::value::quantity::parse_unit_text_with(s, scale)
{
SortKey::line_of(bv, crate::value::quantity::dimension_of(&base))
} else if let Some((secs, nanos)) = crate::temporal::parse_span(s) {
SortKey::line_of(secs as f64 + nanos as f64 / 1e9, "T".to_string())
} else if let Some((secs, nanos, _)) = crate::temporal::parse_iso(s) {
SortKey::line(secs as f64 + nanos as f64 / 1e9)
} else {
SortKey::Text(s.clone())
}
}
Value::List(_) => SortKey::ListText(v.to_string()),
Value::Record(_) => SortKey::RecordText(v.to_string()),
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Null => Ok(()),
Value::Bool(b) => write!(f, "{b}"),
Value::Int(n) => write!(f, "{n}"),
Value::Float(x) => write!(f, "{}", format_float(*x)),
Value::Str(s) => write!(f, "{s}"),
Value::Projection { key, .. } => write!(f, "{key}"),
Value::Quarq(q) => write!(f, "{}", q.source),
Value::List(items) => {
let parts: Vec<String> = items.iter().map(|v| v.to_string()).collect();
write!(f, "{}", parts.join(", "))
}
Value::Record(_) => write!(f, "{}", self.to_quarb()),
Value::Instant {
secs,
nanos,
offset_min,
..
} => write!(
f,
"{}",
crate::temporal::format_instant(*secs, *nanos, *offset_min)
),
Value::Period {
start,
grain,
calendar,
} => write!(
f,
"{}",
crate::period::Period {
start: *start,
grain: *grain,
calendar: *calendar
}
.format()
),
Value::Quantity {
value,
base,
written,
} => match written {
Some((v, u)) => write!(
f,
"{}_{}",
format_magnitude(*v),
crate::value::quantity::canonical_unit(u)
),
None if base == "s" => {
write!(
f,
"{}",
crate::value::quantity::duration_text(*value, false)
)
}
None => write!(f, "{}_{}", format_magnitude(*value), base),
},
Value::Foreign(x) => write!(f, "{}", x.display()),
Value::Table(t) => write!(f, "{}", t.to_quarb()),
Value::Node(n) => write!(f, "{}", n.locator),
}
}
}
pub(crate) fn quantital_pair_with(
a: &Value,
b: &Value,
scale: &dyn Fn(&str) -> Option<(f64, String)>,
) -> Option<(f64, f64)> {
let read = |v: &Value| -> Option<(f64, String, Option<String>)> {
match v {
Value::Quantity {
value,
base,
written,
} => Some((
*value,
base.clone(),
written.as_ref().map(|(_, u)| u.clone()),
)),
Value::Str(s) => crate::value::quantity::parse_unit_text_with(s, scale)
.map(|(bv, b, _, wu)| (bv, b, Some(wu)))
.or_else(|| {
crate::temporal::parse_span(s).map(|(secs, nanos)| {
(secs as f64 + nanos as f64 / 1e9, "s".to_string(), None)
})
}),
_ => None,
}
};
let ratio = |base: &str| crate::value::quantity::dimension_of(base) == "1";
match (read(a), read(b)) {
(Some((va, ba, _)), Some((vb, bb, _))) => (ba == bb).then_some((va, vb)),
(Some((va, ba, _)), None) if ratio(&ba) => b.numeric().map(|n| (va, n)),
(None, Some((vb, bb, _))) if ratio(&bb) => a.numeric().map(|n| (n, vb)),
_ => None,
}
}
pub fn split_seconds_pub(f: f64) -> (i64, u32) {
split_seconds(f)
}
pub(crate) fn split_seconds(f: f64) -> (i64, u32) {
let secs = f.floor();
let nanos = ((f - secs) * 1e9).round();
if nanos >= 1e9 {
(secs as i64 + 1, 0)
} else {
(secs as i64, nanos as u32)
}
}
fn year_word(s: &str) -> Option<crate::period::Period> {
let body = s.strip_suffix('-')?;
let (neg, digits) = match body.strip_prefix('-') {
Some(d) => (true, d),
None => (false, body),
};
if digits.is_empty() || digits.len() > 6 || !digits.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
let y: i64 = digits.parse().ok()?;
crate::period::Period::of(
if neg { -y } else { y },
1,
1,
0,
0,
0,
crate::period::Grain::Year,
)
}
pub fn pretty_float(f: f64, precision: usize) -> String {
if f.is_nan() || f.is_infinite() {
return format_float(f);
}
let fixed = format!("{f:.precision$}");
match fixed.split_once('.') {
Some((whole, frac)) => {
let frac = frac.trim_end_matches('0');
let whole = if whole == "-0" && frac.is_empty() {
"0"
} else {
whole
};
if frac.is_empty() {
format!("{whole}.0")
} else {
format!("{whole}.{frac}")
}
}
None => fixed,
}
}
pub(crate) fn format_magnitude(f: f64) -> String {
let s = format_float(f);
match s.strip_suffix(".0") {
Some(whole) => whole.to_string(),
None => s,
}
}
pub(crate) fn format_float(f: f64) -> String {
if f.is_nan() {
return "nan".to_string();
}
if f.is_infinite() {
return if f > 0.0 { "inf" } else { "-inf" }.to_string();
}
let plain = |f: f64| -> String {
if f.fract() == 0.0 {
format!("{f:.1}")
} else {
f.to_string()
}
};
if f == 0.0 {
return plain(f);
}
let sci = format!("{f:e}");
let (mantissa, exponent) = sci.split_once('e').expect("an exponent");
let exponent: i32 = exponent.parse().expect("a decimal exponent");
if (-4..16).contains(&exponent) {
return plain(f);
}
format!(
"{mantissa}e{}{:02}",
if exponent < 0 { '-' } else { '+' },
exponent.abs()
)
}
fn json_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => {
out.push_str(&format!("\\u{:04x}", c as u32));
}
c => out.push(c),
}
}
out.push('"');
out
}
impl From<i64> for Value {
fn from(n: i64) -> Self {
Value::Int(n)
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Bool(b)
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::Str(s)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::Str(s.to_string())
}
}
#[cfg(test)]
mod tests {
#[test]
fn a_key_prints_bare_when_it_reads_bare() {
use super::quarb_key;
for k in ["a", "a_b", "in-degree", "x-1", "a-b-c", "x_", "é", ".3"] {
assert_eq!(quarb_key(k), k, "{k} prints bare");
}
for (k, want) in [
("e f", r#""e f""#),
("a-", r#""a-""#),
("a--b", r#""a--b""#),
("-a", r#""-a""#),
("_x", r#""_x""#),
("1a", r#""1a""#),
("a.b", r#""a.b""#),
("null", r#""null""#),
("true", r#""true""#),
("nan", r#""nan""#),
(".and.", r#"".and.""#),
("", r#""""#),
] {
assert_eq!(quarb_key(k), want, "{k} prints quoted");
}
}
#[test]
fn floats_print_as_python_prints_them() {
for (f, want) in [
(2.89e-82, "2.89e-82"),
(0.00001, "1e-05"),
(1e16, "1e+16"),
(1.2345678901234568e29, "1.2345678901234568e+29"),
(0.001, "0.001"),
(0.0001, "0.0001"),
(1234567890123456.0, "1234567890123456.0"),
(3.0, "3.0"),
(-5e-8, "-5e-08"),
(0.0, "0.0"),
] {
assert_eq!(format_float(f), want, "{f}");
assert_eq!(Value::Float(f).to_string(), want);
}
assert_eq!(Value::Float(2.89e-82).to_json(), "2.89e-82");
}
use super::*;
#[test]
fn sort_key_hashes_like_it_compares() {
use std::collections::HashSet;
let scale = |_: &str| None;
let keys: HashSet<SortKey> = [
Value::Str("1".into()),
Value::Int(1),
Value::Float(1.0),
Value::Str("x".into()),
Value::Str("X".into()),
Value::Null,
Value::Bool(true),
]
.iter()
.map(|v| v.sort_key_with(&scale))
.collect();
assert_eq!(keys.len(), 5);
assert_eq!(
Value::Int(1).sort_key_with(&scale),
Value::Str(" 1.0 ".into()).sort_key_with(&scale)
);
assert_ne!(
Value::Str("x".into()).sort_key_with(&scale),
Value::Str("y".into()).sort_key_with(&scale)
);
}
#[test]
fn json_rendering() {
let obj = Value::record(vec![
("name".into(), Value::Str("A\"da\n".into())),
("age".into(), Value::Int(36)),
("score".into(), Value::Float(2.5)),
("ok".into(), Value::Bool(true)),
("gone".into(), Value::Null),
(
"tags".into(),
Value::list(vec![Value::Str("x".into()), Value::Int(1)]),
),
]);
assert_eq!(
obj.to_json(),
r#"{"name": "A\"da\n", "age": 36, "score": 2.5, "ok": true, "gone": null, "tags": ["x", 1]}"#
);
assert_eq!(
obj.to_quarb(),
"%(name = \"A\\\"da\n\"; age = 36; score = 2.5; ok = true; gone = null; tags = @(\"x\"; 1))"
);
assert_eq!(obj.to_string(), obj.to_quarb());
assert!(!Value::record(Vec::new()).is_truthy());
assert_eq!(Value::list(Vec::new()).to_quarb(), "@()");
assert_eq!(Value::record(Vec::new()).to_quarb(), "%()");
assert_eq!(
Value::record(vec![("my-key".into(), Value::Int(1))]).to_quarb(),
"%(my-key = 1)"
);
assert_eq!(
Value::record(vec![("my key".into(), Value::Int(1))]).to_quarb(),
"%(\"my key\" = 1)"
);
assert_eq!(
Value::list(vec![Value::Str("a".into()), Value::Str("b".into())]).to_string(),
"a, b"
);
}
}
pub fn quarb_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
match c {
'"' | '\\' => {
out.push('\\');
out.push(c);
}
'$' if chars.peek() == Some(&'{') => {
out.push('\\');
out.push('$');
}
'(' if chars.peek() == Some(&',') => {
out.push('\\');
out.push('(');
}
_ => out.push(c),
}
}
out.push('"');
out
}
pub fn quarb_key(k: &str) -> String {
if is_bare_key(k) {
k.to_string()
} else {
quarb_string(k)
}
}
pub fn is_bare_key(k: &str) -> bool {
if k.len() > 1 && k.starts_with('.') && k[1..].bytes().all(|c| c.is_ascii_digit()) {
return true;
}
let mut cs = k.chars();
matches!(cs.next(), Some(c) if c.is_alphabetic())
&& cs.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
&& !k.ends_with('-')
&& !k.contains("--")
&& !is_bool_word(k)
&& !matches!(k, "true" | "false" | "null" | "nan" | "inf")
}
#[cfg(test)]
mod one_pass_typing_tests {
use super::*;
fn s(x: &str) -> Value {
Value::Str(x.to_string())
}
fn both(rows: Vec<Vec<Value>>) -> (Table, Table) {
let header = Some((0..rows[0].len()).map(|j| format!("c{j}")).collect());
let one = Table::typed(header.clone(), rows.clone(), Vec::new()).unwrap();
let two = Table::assemble(header, rows, Vec::new(), 0, None)
.retyped()
.unwrap();
(one, two)
}
#[test]
fn the_leader_meets_the_pass() {
let rows = vec![
vec![
s("1"),
s("1.50"),
s("1.50"),
s("3 kg"),
s("true"),
Value::Null,
s("2024-01-02"),
s("x"),
],
vec![
s("2"),
s("2"),
s("x"),
s("4 kg"),
s("false"),
Value::Null,
s("2024-01-03T04:05:06Z"),
Value::Int(1),
],
vec![
s("3"),
Value::Null,
s("2.5"),
s("5 m"),
s("1"),
Value::Null,
s("nope"),
Value::Float(2.5),
],
];
let (one, two) = both(rows);
assert_eq!(one, two);
assert_eq!(
one.types(),
vec![
ColumnType::Integer,
ColumnType::Float,
ColumnType::Text,
ColumnType::Text,
ColumnType::Text,
ColumnType::Text,
ColumnType::Text,
ColumnType::Text,
]
);
assert_eq!(one.cell(0, 2), s("1.50"));
assert_eq!(one.cell(1, 7), s("1"));
}
#[test]
fn the_late_widenings() {
let mut rows: Vec<Vec<Value>> = (0..200)
.map(|i| vec![s(&i.to_string()), s(&i.to_string())])
.collect();
rows.push(vec![s("0.5"), s("w")]);
let (one, two) = both(rows);
assert_eq!(one, two);
assert_eq!(one.types(), vec![ColumnType::Float, ColumnType::Text]);
assert_eq!(one.cell(3, 0), Value::Float(3.0));
assert_eq!(one.cell(3, 1), s("3"));
let (one, two) = both(vec![vec![s("3 kg"), s("3 kg")], vec![s("4 kg"), s("4 m")]]);
assert_eq!(one, two);
assert_eq!(one.types(), vec![ColumnType::Quantity, ColumnType::Text]);
let p = Value::projection(1, "k".to_string());
let (one, two) = both(vec![vec![p.clone(), s("1")], vec![s("2"), s("2")]]);
assert_eq!(one, two);
assert_eq!(one.cell(0, 0), p);
assert_eq!(one.cell(1, 1), Value::Int(2));
}
#[test]
fn a_declared_column_casts_or_refuses_as_before() {
let header = Some(vec!["n".to_string(), "t".to_string()]);
let declared = vec![Some(ColumnType::Integer), Some(ColumnType::Text)];
let rows = vec![vec![s("1"), Value::Int(5)], vec![Value::Null, s("x")]];
let one = Table::typed(header.clone(), rows.clone(), declared.clone()).unwrap();
let two = Table::assemble(header.clone(), rows, declared.clone(), 0, None)
.retyped()
.unwrap();
assert_eq!(one, two);
assert_eq!(one.cell(0, 1), s("5"));
let bad = vec![vec![s("1"), s("a")], vec![s("x"), s("b")]];
let e1 = Table::typed(header.clone(), bad.clone(), declared.clone()).unwrap_err();
let e2 = Table::assemble(header, bad, declared, 0, None)
.retyped()
.unwrap_err();
assert_eq!(e1, e2);
assert!(
e1.contains("column 'n' is declared int, and row 2 holds \"x\""),
"{e1}"
);
}
#[test]
fn text_rows_read_as_records_do() {
let cols = vec!["a".to_string(), "b".to_string()];
let mut by_text = TableBuilder::shaped(Some(cols.clone()), Vec::new());
by_text.push_text_row(&["1", ""]);
by_text.push_text_row(&["2", "x", "extra"]);
by_text.push_text_row(&["3"]);
let mut by_record = TableBuilder::shaped(Some(cols), Vec::new());
by_record.push_record(vec![("a".to_string(), s("1"))]);
by_record.push_record(vec![("a".to_string(), s("2")), ("b".to_string(), s("x"))]);
by_record.push_record(vec![("a".to_string(), s("3"))]);
assert_eq!(by_text.finish().unwrap(), by_record.finish().unwrap());
}
}