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>>,
}
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> {
(0..self.fields.len()).find(|&i| 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 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, PartialEq)]
pub struct Table {
pub header: Option<Vec<String>>,
pub rows: Vec<Vec<Value>>,
pub declared: Vec<Option<ColumnType>>,
pub index: usize,
pub name: Option<String>,
}
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,
Text,
Tuple(std::sync::Arc<Vec<ColumnType>>),
}
impl ColumnType {
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::Text => "text".to_string(),
ColumnType::Tuple(items) => {
format!("({})", items.iter().map(ColumnType::word).collect::<Vec<_>>().join("; "))
}
}
}
pub fn from_word(word: &str) -> Option<ColumnType> {
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; 8] = [
ColumnType::Boolean,
ColumnType::Integer,
ColumnType::Float,
ColumnType::Instant,
ColumnType::Duration,
ColumnType::Period,
ColumnType::Quantity,
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,
_ => 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(crate) fn cast_cell(v: &Value, t: &ColumnType) -> Option<Value> {
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(),
}
}
fn coerce_cell(v: &Value, t: &ColumnType) -> Option<Value> {
use ColumnType as T;
if matches!(v, Value::Null) {
return Some(Value::Null);
}
match t {
T::Tuple(_) => None,
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::Text => Some(match v {
Value::Str(_) => v.clone(),
other => Value::Str(other.to_string()),
}),
}
}
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")
}
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 mut t = Table { header, rows, declared: Vec::new(), index: 0, name: None };
let w = t.width();
declared.resize(w, None);
if declared.iter().all(Option::is_none) {
declared.clear();
}
t.declared = declared;
if w == 0 {
return Ok(t);
}
for r in &mut t.rows {
r.resize(w, Value::Null);
}
if t.rows.is_empty() {
return Ok(t);
}
for j in 0..w {
let cells: Vec<Value> = t.rows.iter().map(|r| r[j].clone()).collect();
let coerced = match t.declared.get(j).cloned().flatten() {
Some(ty) => {
let mut out = Vec::with_capacity(cells.len());
for (i, c) in cells.iter().enumerate() {
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",
t.column_name(j),
ty.word(),
i + 1,
held_text(c),
));
}
}
}
out
}
None => type_column(cells).1,
};
for (r, c) in t.rows.iter_mut().zip(coerced) {
r[j] = c;
}
}
Ok(t)
}
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
.iter()
.map(|r| order.iter().map(|k| r.get(*k).cloned().unwrap_or(Value::Null)).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.iter().map(|r| r.first().cloned().unwrap_or(Value::Null)).collect());
}
let names: Vec<String> = (0..self.index).map(|j| self.column_name(j)).collect();
Some(
self.rows
.iter()
.map(|r| {
Value::record(
names
.iter()
.enumerate()
.map(|(j, n)| (n.clone(), r.get(j).cloned().unwrap_or(Value::Null)))
.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
.iter()
.enumerate()
.filter(|(_, r)| wanted.iter().enumerate().all(|(j, w)| r.get(j) == Some(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
.iter()
.map(|r| order.iter().map(|&j| r.get(j).cloned().unwrap_or(Value::Null)).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> = r[..self.index].to_vec();
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).and_then(|j| r.get(j).cloned()).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).cloned().unwrap_or(Value::Null);
if !values.contains(&v) {
values.push(v);
}
}
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 = r[..k].to_vec();
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
.iter()
.find(|r| r[..k] == key[..] && r.get(k) == Some(v))
.and_then(|r| r.get(j).cloned())
.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.rows[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.rows[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
.iter()
.map(|r| r[..self.index].to_vec())
.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 { header: None, rows, declared: Vec::new(), index: 0, name: None }
}
pub fn types(&self) -> Vec<ColumnType> {
(0..self.width())
.map(|j| {
if let Some(ty) = self.declaration(j) {
return ty;
}
self.rows
.iter()
.find_map(|r| r.get(j).and_then(ColumnType::of))
.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.iter().flatten().all(|c| {
!matches!(
c,
Value::List(_) | Value::Record(_) | Value::Table(_) | Value::Foreign(_)
)
})
}
pub fn into_records(self) -> Value {
let Table { header, rows, .. } = self;
Value::list(
rows.into_iter()
.map(|r| match &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.rows.first().map_or(0, Vec::len),
}
}
pub fn shape(&self) -> Value {
Value::list(vec![
Value::Int(self.rows.len() 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.rows.len(),
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.iter().map(|r| self.row_value(r)).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.rows.get(i - 1))
.flatten()
.map(|r| self.row_value(r))
}
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.rows.len() as i64;
let from = a.unwrap_or(1).clamp(1, n + 1) - 1;
let to = b.unwrap_or(n).clamp(0, n);
Table {
header: self.header.clone(),
rows: if from < to {
self.rows[from as usize..to as usize].to_vec()
} else {
Vec::new()
},
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.rows.len() + 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(Value::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
.iter()
.map(|r| r.get(j).cloned().unwrap_or(Value::Null))
.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
.iter()
.map(|r| r.get(j).cloned().unwrap_or(Value::Null))
.collect()
})
.collect(),
)
}
pub fn to_quarb(&self) -> String {
let mut parts: Vec<String> = Vec::with_capacity(self.rows.len() + 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(Value::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),
})
}
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.iter().flatten().any(Value::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.rows.is_empty(),
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(t: Table) -> Value {
if t.is_flat() {
Value::table(Table::new(t.header, t.rows))
} else {
t.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_float(*v),
crate::value::quantity::canonical_unit(u)
),
None if base == "s" => {
write!(
f,
"{}",
crate::value::quantity::duration_text(*value, false)
)
}
None => write!(f, "{}_{}", format_float(*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() { whole.to_string() } else { format!("{whole}.{frac}") }
}
None => fixed,
}
}
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();
}
if f == 0.0 {
return f.to_string();
}
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 f.to_string();
}
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 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"),
(3.0, "3"),
(-5e-8, "-5e-08"),
(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::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 {
let position = k.len() > 1 && k.starts_with('.') && k[1..].bytes().all(|c| c.is_ascii_digit());
let bare = position
|| !k.is_empty()
&& k.chars()
.next()
.is_some_and(|c| c.is_alphabetic() || c == '_')
&& k.chars().all(|c| c.is_alphanumeric() || c == '_')
&& !is_bool_word(k);
if bare { k.to_string() } else { quarb_string(k) }
}