use crate::pipeline::*;
pub(crate) fn eval_operand<H: Host>(
host: &H,
ext: &H::Ext,
operand: &Operand,
trace: &H::Trace,
scope: Scope<'_, H::Ext>,
) -> Vec<Value> {
match operand {
Operand::Lit(v) => vec![v.clone()],
Operand::Pattern(_) => Vec::new(),
Operand::Match {
scrutinee,
arms,
other,
} => {
let subject = discard(|| operand_scalar(host, ext, scrutinee, trace, scope));
for (test, regex, result) in arms {
let hit = discard(|| {
if *regex {
let pat = operand_scalar(host, ext, test, trace, scope);
regex_test(&subject, &pat, true)
} else {
let t = operand_scalar(host, ext, test, trace, scope);
value_eq(&subject, &t, &|e| host.scale_of(e))
}
});
if hit {
return eval_operand(host, ext, result, trace, scope);
}
}
eval_operand(host, ext, other, trace, scope)
}
Operand::Now => vec![
run_option(|o| o.now)
.map(|(secs, nanos)| Value::Instant {
secs,
nanos,
offset_min: Some(0),
calendar: Default::default(),
})
.unwrap_or(Value::Null),
],
Operand::Recall(r) => {
note_all(&recall_origins(scope.register, r));
vec![recall(scope.register, r)]
}
Operand::Topic | Operand::Seed => {
if let Some(p) = scope.prov {
note_all(&p.origins);
}
vec![
scope
.topic
.cloned()
.or_else(|| with_run(|s| s.enclosing_topic.last().cloned()))
.unwrap_or(Value::Null),
]
}
Operand::Field { base, name } if matches!(base.as_ref(), Operand::Recall(_)) => {
let Operand::Recall(r) = base.as_ref() else {
unreachable!()
};
note_all(&recall_field_origins(scope.register, r, name));
vec![match recall(scope.register, r) {
Value::Record(fields) => fields
.into_iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v)
.unwrap_or(Value::Null),
Value::Table(t) => t.column(name).unwrap_or(Value::Null),
_ => Value::Null,
}]
}
Operand::ValueMeta { base, key } => {
let (_, o) = capture(|| eval_operand(host, ext, base, trace, scope));
note_all(&o);
vec![host.origins_meta(&o, key).unwrap_or(Value::Null)]
}
Operand::Field { base, name } if matches!(**base, Operand::Topic) && scope.topic.is_none() => {
note(host.origin_at(ext).position);
vec![host.project(ext, &Projection::CoreMeta(name.clone()))]
}
Operand::Field { base, name }
if matches!(**base, Operand::Topic)
&& !matches!(
scope.topic,
Some(Value::Record(_) | Value::List(_) | Value::Table(_))
) =>
{
if crate::graph::navigate::CORE_FIELDS.contains(&name.as_str()) {
note(host.origin_at(ext).position);
vec![host.project(ext, &Projection::CoreMeta(name.clone()))]
} else {
vec![Value::Null]
}
}
Operand::Field { base, name } => eval_operand(host, ext, base, trace, scope)
.into_iter()
.map(|v| record_field(v, name))
.collect(),
Operand::List(items) => vec![Value::list(
items
.iter()
.flat_map(|item| eval_operand(host, ext, item, trace, scope))
.collect(),
)],
Operand::Coalesce { left, right } => {
let v = operand_value(host, ext, left, trace, scope);
if matches!(v, Value::Null) {
eval_operand(host, ext, right, trace, scope)
} else {
vec![v]
}
}
Operand::Index { base, rows, cols } => {
let v = operand_value(host, ext, base, trace, scope);
match crate::stdlib::index_value(v, rows, cols.as_ref()) {
Ok(v) => vec![v],
Err(why) => {
crate::pipeline::record_refusal(why);
vec![Value::Null]
}
}
}
Operand::Range { from, to, step } => {
let int = |e: &Operand, what: &str| -> Result<i64, String> {
let v = operand_value(host, ext, e, trace, scope);
match v.numeric_reading() {
Some(Value::Int(n)) => Ok(n),
_ => Err(format!(
"a range's {what} is an integer, not {}",
crate::stdlib::kind_of(&v)
)),
}
};
let bounds = (|| -> Result<Vec<Value>, String> {
let a = int(from, "start")?;
let b = int(to, "end")?;
let s = match step {
Some(s) => int(s, "step")?,
None => 1,
};
if s == 0 {
return Err("a range's step is not zero".into());
}
let s = match step {
Some(_) => s,
None if b < a => -1,
None => 1,
};
let mut out = Vec::new();
let mut i = a;
while if s > 0 { i <= b } else { i >= b } {
out.push(Value::Int(i));
i = match i.checked_add(s) {
Some(n) => n,
None => break,
};
}
Ok(out)
})();
match bounds {
Ok(vs) => vs,
Err(why) => {
crate::pipeline::record_refusal(why);
vec![Value::Null]
}
}
}
Operand::RecordArray { header, rows } => {
let cell = |e: &Operand| operand_value(host, ext, e, trace, scope);
match record_array(header, rows, cell) {
Ok(v) => vec![v],
Err(why) => {
crate::pipeline::record_refusal(why);
vec![Value::Null]
}
}
}
Operand::Table { header, rows } => {
let cell = |e: &Operand| operand_value(host, ext, e, trace, scope);
match table_literal(header.clone(), rows, cell) {
Ok(t) => vec![Value::table(t)],
Err(why) => {
crate::pipeline::record_refusal(why);
vec![Value::Null]
}
}
}
Operand::NamedCaptures => vec![Value::Record(
scope
.named
.iter()
.map(|(k, v)| (k.clone(), Value::Str(v.clone())))
.collect(),
)],
Operand::Ordinal => vec![
scope
.ordinal
.map(|i| Value::Int(i as i64))
.unwrap_or(Value::Null),
],
Operand::Cond { cond, then, other } => {
let taken = if discard(|| eval_pred_expr(host, ext, cond, trace, scope)) {
then
} else {
other
};
eval_operand(host, ext, taken, trace, scope)
}
Operand::PeerReg { at, reg } => {
let Some(peers) = scope.peers else {
return vec![Value::Null];
};
let keys = discard(|| host.operand_keys(ext, at, trace, scope));
let mut out = Vec::new();
for k in keys {
for &i in peers.at(&k) {
let p = &peers.caps[i];
note_all(&recall_prov(&p.register, reg).origins);
out.push(recall(&p.register, reg));
}
}
out
}
Operand::Topics => host.capsae(scope, None),
Operand::Positions { projection } => host.positions(scope, projection.as_ref()),
Operand::Matrix { node, full } => matrix(scope, *node, *full),
Operand::CtxTable => ctx_table(host, scope),
Operand::Piped { expr, commands } => {
let capsa = |x: H::Ext, topic: Option<Value>, prov: Prov| Thread::<H::Ext> {
prov,
register: scope.register.to_vec(),
topic,
members: Vec::new(),
captures: scope.captures.to_vec(),
named: scope.named.to_vec(),
ext: x,
};
let mut caps: Vec<Thread<H::Ext>> = match expr.as_ref() {
Operand::Rel {
steps,
projection,
anchor,
} => host
.rel_threads(ext, steps, anchor, projection.as_ref(), trace, scope)
.into_iter()
.map(|(x, topic, prov)| capsa(x, topic, prov))
.collect(),
Operand::Seed if scope.members.is_empty() => {
let topic = scope
.topic
.cloned()
.or_else(|| with_run(|s| s.enclosing_topic.last().cloned()));
vec![capsa(
host.restate(&scope, ext),
topic,
scope.prov.cloned().unwrap_or_else(|| host.prov_at(ext)),
)]
}
Operand::Topic | Operand::Seed if !scope.members.is_empty() => scope
.members
.iter()
.map(|m| match &m.topic {
Some(t) => capsa(
host.restate(&scope, &m.ext),
Some(t.clone()),
m.prov.clone(),
),
None => capsa(
host.restate(&scope, &m.ext),
Some(host.node_scalar(&m.ext)),
host.prov_at(&m.ext),
),
})
.collect(),
_ => {
let (values, o) = capture(|| eval_operand(host, ext, expr, trace, scope));
let prov = Prov::leaf(o);
values
.into_iter()
.map(|v| capsa(host.restate(&scope, ext), Some(v), prov.clone()))
.collect()
}
};
for command in commands.iter() {
if caps.is_empty() && matches!(command, Command::Func(c) if c.name == "default") {
caps.push(capsa(
host.restate(&scope, ext),
Some(Value::Null),
Prov::default(),
));
}
if matches!(command, Command::Agg(_) | Command::Select(_))
&& caps.len() == 1
&& matches!(caps[0].topic, Some(Value::List(_)))
{
let whole = caps.pop().expect("one capsa");
let items = whole.topic.and_then(Value::into_items).unwrap_or_default();
caps = items
.into_iter()
.enumerate()
.map(|(i, v)| {
capsa(
host.restate(&scope, &whole.ext),
Some(v),
whole.prov.item(i),
)
})
.collect();
}
let resolved;
let command = match command {
Command::Func(call)
if stdlib::known_scalar(&call.name)
&& !matches!(call.name.as_str(), "rec" | "record")
&& call.args.iter().any(|a| matches!(a, Arg::Expr(_))) =>
{
resolved = Command::Func(FnCall {
name: call.name.clone(),
args: call
.args
.iter()
.map(|a| match a {
Arg::Expr(e) => {
Arg::Lit(operand_scalar(host, ext, e, trace, scope))
}
other => other.clone(),
})
.collect(),
});
&resolved
}
other => other,
};
let had_nodes = caps.iter().any(|c| c.topic.is_none());
caps = apply_command(command, caps, host, trace, Some(&scope));
if !had_nodes {
for c in &mut caps {
if c.topic.is_none() {
c.topic = Some(Value::Null);
}
}
}
}
for c in &caps {
note_all(&c.prov.origins);
}
caps.into_iter()
.map(|c| c.topic.unwrap_or(Value::Bool(true)))
.collect()
}
Operand::Edges { projection } => host.edges(scope, projection.as_ref()),
Operand::Edge { projection } => host.edge(scope, projection.as_ref()),
Operand::Param(_) => vec![Value::Null],
Operand::Invoking(inner) => match scope.outer {
Some(o) if bare_cursor(inner) => host.node_ref(o.ext).into_iter().collect(),
Some(o) => eval_operand(
host,
if H::Ext::standing(&o.view) {
o.ext
} else {
ext
},
inner,
trace,
*o,
),
None => vec![Value::Null],
},
Operand::Position(inner) if bare_cursor(inner) => {
host.node_ref(ext).into_iter().collect()
}
Operand::Cursor => {
let topic = operand_value(host, ext, &Operand::Topic, trace, scope);
let position = host.node_ref(ext).unwrap_or(Value::Null);
vec![Value::Record(vec![
("topic".to_string(), topic),
("position".to_string(), position),
])]
}
Operand::Position(inner) if !host.placed(ext) => Vec::new(),
Operand::Position(inner) => eval_operand(host, ext, inner, trace, scope),
Operand::Driver(inner) => match scope.driver {
Some(d) if bare_cursor(inner) => host.node_ref(d.ext).into_iter().collect(),
Some(d) => eval_operand(
host,
if H::Ext::standing(&d.view) {
d.ext
} else {
ext
},
inner,
trace,
*d,
),
None => vec![Value::Null],
},
Operand::Interp(segs) => {
let mut out = String::new();
for seg in segs {
match seg {
InterpSeg::Text(t) => out.push_str(t),
InterpSeg::Expr(e) => {
let v = operand_scalar(host, ext, e, trace, scope);
out.push_str(&v.to_string());
}
InterpSeg::Strict(e, msg) => {
let v = operand_scalar(host, ext, e, trace, scope);
if matches!(v, Value::Null) {
record_refusal(match msg {
Some(m) => m.clone(),
None => format!(
"the strict expansion '${{{}:?}}' produced nothing",
crate::syntax::unparse::operand_text(e)
),
});
} else {
out.push_str(&v.to_string());
}
}
InterpSeg::Default(e, f) => {
let v = operand_scalar(host, ext, e, trace, scope);
let v = if matches!(v, Value::Null) {
operand_scalar(host, ext, f, trace, scope)
} else {
v
};
out.push_str(&v.to_string());
}
}
}
vec![Value::Str(out)]
}
Operand::Capture(n) => vec![
scope
.captures
.get(n - 1)
.map(|s| Value::Str(s.clone()))
.unwrap_or(Value::Null),
],
Operand::Rel {
steps,
projection,
anchor,
} => host.rel(ext, steps, anchor, projection.as_ref(), trace, scope),
Operand::Arith { op, left, right } => {
let scale = |e: &str| host.scale_of(e);
let ls = forking(eval_operand(host, ext, left, trace, scope));
let rs = forking(eval_operand(host, ext, right, trace, scope));
let mut out = Vec::with_capacity(ls.len() * rs.len());
for l in &ls {
let l = retype_shaped_lit(left, l.clone(), &scale);
for r in &rs {
let r = retype_shaped_lit(right, r.clone(), &scale);
out.push(match crate::stdlib::arith::broadcast(*op, &l, &r, &scale) {
Ok(v) => v,
Err(why) => {
crate::pipeline::record_refusal(why);
Value::Null
}
});
}
}
out
}
Operand::Concat { mode, left, right } => {
let ls = forking(eval_operand(host, ext, left, trace, scope));
let rs: Vec<Result<Value, String>> = match right.as_ref() {
Operand::Row(items) => {
let row: Vec<(Option<String>, Value)> = items
.iter()
.map(|(name, e)| {
(name.clone(), gathered(eval_operand(host, ext, e, trace, scope)))
})
.collect();
ls.iter()
.map(|l| crate::stdlib::concat_row(*mode, l.clone(), row.clone()))
.collect()
}
_ => {
let rs = forking(eval_operand(host, ext, right, trace, scope));
let mut out = Vec::with_capacity(ls.len() * rs.len());
for l in &ls {
for r in &rs {
out.push(crate::stdlib::concat(*mode, l.clone(), r.clone()));
}
}
out
}
};
rs.into_iter()
.map(|out| match out {
Ok(v) => v,
Err(why) => {
crate::pipeline::record_refusal(format!("{}: {why}", mode.symbol()));
Value::Null
}
})
.collect()
}
Operand::Row(_) => vec![Value::Null],
Operand::Neg(inner) => forking(eval_operand(host, ext, inner, trace, scope))
.into_iter()
.map(|v| match v.numeric_reading() {
Some(Value::Int(n)) => n
.checked_neg()
.map(Value::Int)
.unwrap_or(Value::Float(-(n as f64))),
Some(Value::Float(f)) => Value::Float(-f),
_ => Value::Null,
})
.collect(),
Operand::Gathered(inner) => vec![gathered(eval_operand(host, ext, inner, trace, scope))],
Operand::Group(e) => vec![Value::Bool(discard(|| {
eval_pred_expr(host, ext, e, trace, scope)
}))],
Operand::Ctx {
index,
steps,
projection,
} => host.ctx(ext, *index, steps, projection.as_ref(), trace, scope),
}
}
pub(crate) fn bare_cursor(inner: &Operand) -> bool {
matches!(
inner,
Operand::Rel {
steps,
projection: None,
anchor: Anchor::Current,
} if steps.is_empty()
)
}
fn forking(values: Vec<Value>) -> Vec<Value> {
if values.is_empty() {
vec![Value::Null]
} else {
values
}
}
fn gathered(mut values: Vec<Value>) -> Value {
if values.len() == 1 {
values.pop().expect("one")
} else {
Value::list(values)
}
}
pub(crate) fn operand_scalar<H: Host>(
host: &H,
ext: &H::Ext,
expr: &Operand,
trace: &H::Trace,
scope: Scope<'_, H::Ext>,
) -> Value {
eval_operand(host, ext, expr, trace, scope)
.into_iter()
.next()
.unwrap_or(Value::Null)
}
pub(crate) fn record_fields_prov<H: Host>(
args: &[Arg],
host: &H,
ext: &H::Ext,
trace: &H::Trace,
scope: Scope<'_, H::Ext>,
) -> Result<Vec<(String, Value, Prov)>, String> {
let mut fields: Vec<(String, Value, Prov)> = Vec::new();
let mut args = args.iter().peekable();
while let Some(arg) = args.next() {
match arg {
Arg::Lit(Value::Str(name)) => {
let (value, prov) = match args.next() {
Some(Arg::Expr(e)) => {
let (v, o) = capture(|| operand_value(host, ext, e, trace, scope));
(v, Prov::leaf(o))
}
Some(Arg::Lit(v)) => (v.clone(), Prov::default()),
Some(Arg::Range(_, _))
| Some(Arg::Named { .. })
| Some(Arg::Opt { .. })
| Some(Arg::Pairs { .. })
| None => (Value::Null, Prov::default()),
};
fields.push((name.clone(), value, prov));
}
Arg::Expr(e) => {
let name = crate::syntax::ast::auto_field_name(e)
.unwrap_or_default()
.to_string();
let (v, o) = capture(|| operand_value(host, ext, e, trace, scope));
fields.push((name, v, Prov::leaf(o)));
}
Arg::Lit(v) => fields.push((String::new(), v.clone(), Prov::default())),
Arg::Pairs { keys, values } => {
let k = operand_value(host, ext, keys, trace, scope);
let (v, o) = capture(|| operand_value(host, ext, values, trace, scope));
let names: Vec<String> = match &k {
Value::List(l) => l
.iter()
.map(|n| match n {
Value::Str(s) => s.clone(),
other => other.to_string(),
})
.collect(),
other => {
return Err(format!(
"the pairwise field takes a list of names on the left of '=', not {}",
crate::stdlib::kind_of(other)
));
}
};
let items: Vec<Value> = match &v {
Value::List(l) => l.to_vec(),
other => {
return Err(format!(
"the pairwise field takes a list of values on the right of '=', not {}",
crate::stdlib::kind_of(other)
));
}
};
if names.len() != items.len() {
return Err(format!(
"the pairwise field's names and values differ in number, {} and {}",
names.len(),
items.len()
));
}
for (name, value) in names.into_iter().zip(items) {
if fields.iter().any(|(n, _, _)| *n == name) {
return Err(format!("'{name}' is already a field of the record"));
}
fields.push((name, value, Prov::leaf(o.clone())));
}
}
Arg::Range(_, _) | Arg::Named { .. } | Arg::Opt { .. } => {}
}
}
Ok(fields)
}
pub(crate) fn extract_captures<H: Host>(
e: &PredExpr,
host: &H,
ext: &H::Ext,
trace: &H::Trace,
scope: Scope<'_, H::Ext>,
) -> Option<Captured> {
match e {
PredExpr::Or(a, b) | PredExpr::And(a, b) => {
let first = extract_captures(a, host, ext, trace, scope);
extract_captures(b, host, ext, trace, scope).or(first)
}
PredExpr::Not(a) => extract_captures(a, host, ext, trace, scope),
PredExpr::Compare(l, CmpOp::Match, r) => {
let pattern = operand_scalar(host, ext, r, trace, scope);
let re = compiled_regex(&pattern.to_string())?;
if re.captures_len() <= 1 {
return None;
}
eval_operand(host, ext, l, trace, scope)
.iter()
.find_map(|v| {
let text = v.to_string();
re.captures(&text).map(|caps| {
let numbered = caps
.iter()
.skip(1)
.map(|g| g.map_or(String::new(), |m| m.as_str().to_string()))
.collect();
let named = re
.capture_names()
.enumerate()
.filter_map(|(i, n)| {
n.map(|n| {
let text = caps
.get(i)
.map_or(String::new(), |m| m.as_str().to_string());
(n.to_string(), text)
})
})
.collect();
(numbered, named)
})
})
}
PredExpr::Compare(..) | PredExpr::Chain(..) | PredExpr::Truthy(_) => None,
}
}
pub(crate) fn eval_pred_expr<H: Host>(
host: &H,
ext: &H::Ext,
e: &PredExpr,
trace: &H::Trace,
scope: Scope<'_, H::Ext>,
) -> bool {
match e {
PredExpr::Or(a, b) => {
eval_pred_expr(host, ext, a, trace, scope) || eval_pred_expr(host, ext, b, trace, scope)
}
PredExpr::And(a, b) => {
eval_pred_expr(host, ext, a, trace, scope) && eval_pred_expr(host, ext, b, trace, scope)
}
PredExpr::Not(a) => !eval_pred_expr(host, ext, a, trace, scope),
PredExpr::Compare(l, op, r) => {
if let Operand::Pattern(segs) = r {
let lhs = eval_operand(host, ext, l, trace, scope);
return match op {
CmpOp::Ne => lhs.iter().any(|a| !pattern_test(segs, a)),
_ => lhs.iter().any(|a| pattern_test(segs, a)),
};
}
let lhs = eval_operand(host, ext, l, trace, scope);
let rhs = eval_operand(host, ext, r, trace, scope);
holds(l, &lhs, *op, r, &rhs, &|e| host.scale_of(e))
}
PredExpr::Chain(operands, ops) => {
let values: Vec<Vec<Value>> = operands
.iter()
.map(|o| eval_operand(host, ext, o, trace, scope))
.collect();
ops.iter().enumerate().all(|(i, op)| {
holds(
&operands[i],
&values[i],
*op,
&operands[i + 1],
&values[i + 1],
&|e| host.scale_of(e),
)
})
}
PredExpr::Truthy(o) => eval_operand(host, ext, o, trace, scope)
.iter()
.any(Value::is_truthy),
}
}
pub(crate) fn record_array(
header: &[String],
rows: &[Vec<(Option<String>, Operand)>],
mut cell: impl FnMut(&Operand) -> Value,
) -> Result<Value, String> {
let mut names: Vec<String> = header.to_vec();
let mut records: Vec<Vec<(String, Value)>> = Vec::with_capacity(rows.len());
for (i, row) in rows.iter().enumerate() {
let n = i + 1;
let mut fields: Vec<(String, Value)> = Vec::with_capacity(row.len());
let mut next = 0;
for (name, e) in row {
let v = cell(e);
let name = match name {
Some(name) => {
if !names.contains(name) {
names.push(name.clone());
}
name.clone()
}
None => {
if next >= header.len() {
return Err(format!(
"row {n} has more positional values than the header has names ({}); name the rest",
header.len()
));
}
next += 1;
header[next - 1].clone()
}
};
match fields.iter_mut().find(|(k, _)| *k == name) {
Some((_, slot)) => *slot = v,
None => fields.push((name, v)),
}
}
records.push(fields);
}
Ok(Value::list(
records
.into_iter()
.map(|fields| {
Value::Record(
names
.iter()
.map(|k| {
(
k.clone(),
fields
.iter()
.find(|(n, _)| n == k)
.map(|(_, v)| v.clone())
.unwrap_or(Value::Null),
)
})
.collect(),
)
})
.collect(),
))
}
pub(crate) fn table_literal(
mut header: Option<Vec<String>>,
rows: &[crate::syntax::ast::TableRow],
mut cell: impl FnMut(&Operand) -> Value,
) -> Result<crate::Table, String> {
use crate::syntax::ast::TableRow;
let mut out: Vec<Vec<Value>> = Vec::with_capacity(rows.len());
let mut width: Option<usize> = header.as_ref().map(Vec::len);
for (i, row) in rows.iter().enumerate() {
let n = i + 1;
let mut values: Vec<Value> = match row {
TableRow::List(items) => items.iter().map(&mut cell).collect(),
TableRow::Record(items) => {
if header.is_none()
&& out.is_empty()
&& items.iter().any(|(name, _)| name.is_some())
{
let names: Vec<String> = items
.iter()
.enumerate()
.map(|(j, (name, _))| {
name.clone().ok_or_else(|| {
format!("row {n}: item {} has no name while the others name their columns", j + 1)
})
})
.collect::<Result<_, _>>()?;
width = Some(names.len());
header = Some(names);
}
let Some(h) = header.as_ref() else {
if let Some((name, _)) = items.iter().find(|(name, _)| name.is_some()) {
return Err(format!(
"row {n}: '{}' names no column of an array (its columns are positions); write the header first",
name.as_deref().unwrap_or("")
));
}
let values: Vec<Value> = items.iter().map(|(_, e)| cell(e)).collect();
let w = *width.get_or_insert(values.len());
if values.len() > w {
return Err(format!(
"row {n} has {} values, the table {w} columns",
values.len()
));
}
let mut values = values;
values.resize(w, Value::Null);
out.push(values);
continue;
};
let mut values = vec![Value::Null; h.len()];
let mut next = 0;
for (name, e) in items {
let v = cell(e);
match name {
Some(name) => match h.iter().position(|c| c == name) {
Some(p) => values[p] = v,
None => {
return Err(format!(
"row {n}: '{name}' is not a column of the table (the header: {})",
h.join(", ")
));
}
},
None => {
if next >= h.len() {
return Err(format!(
"row {n} has {} values, the header {} columns",
items.len(),
h.len()
));
}
values[next] = v;
next += 1;
}
}
}
values
}
};
let w = *width.get_or_insert(values.len());
if values.len() > w {
return Err(format!(
"row {n} has {} values, the table {w} columns",
values.len()
));
}
values.resize(w, Value::Null);
if let Some((j, v)) = values.iter().enumerate().find(|(_, v)| !flat_cell(v)) {
return Err(format!(
"row {n}, column {}: a table is flat, its cell a scalar or null, not {}",
j + 1,
crate::stdlib::kind_of(v)
));
}
out.push(values);
}
Ok(crate::Table::new(header, out))
}
pub(crate) fn operand_value<H: Host>(
host: &H,
ext: &H::Ext,
expr: &Operand,
trace: &H::Trace,
scope: Scope<'_, H::Ext>,
) -> Value {
let values = eval_operand(host, ext, expr, trace, scope);
match values.len() {
0 => Value::Null,
1 => values.into_iter().next().unwrap(),
_ => Value::list(values),
}
}
pub(crate) fn group_value<H: Host>(
host: &H,
ext: &H::Ext,
expr: &Operand,
trace: &H::Trace,
scope: Scope<'_, H::Ext>,
) -> Value {
if scope.members.is_empty() {
return operand_scalar(host, ext, expr, trace, scope);
}
let values = eval_operand(host, ext, expr, trace, scope);
match values.len() {
0 => Value::Null,
1 => values.into_iter().next().unwrap(),
_ => Value::list(values),
}
}
fn holds(
l: &Operand,
lhs: &[Value],
op: CmpOp,
r: &Operand,
rhs: &[Value],
scale: crate::stdlib::arith::UnitScale,
) -> bool {
let spread = |o: &Operand, vs: &[Value]| -> Vec<Value> {
if matches!(o, Operand::Range { .. }) {
vec![Value::list(vs.to_vec())]
} else {
vs.to_vec()
}
};
match op {
CmpOp::Within => {
let rs = spread(r, rhs);
lhs.iter()
.any(|a| rs.iter().any(|b| compare(a, op, b, scale)))
}
CmpOp::Encloses => {
let ls = spread(l, lhs);
ls.iter()
.any(|a| rhs.iter().any(|b| compare(a, op, b, scale)))
}
_ => lhs
.iter()
.any(|a| rhs.iter().any(|b| compare(a, op, b, scale))),
}
}
pub(crate) fn ctx_table<H: Host>(host: &H, scope: Scope<'_, H::Ext>) -> Vec<Value> {
if scope.peers.is_none() {
return vec![Value::Null];
}
let items = |vs: Vec<Value>| match vs.into_iter().next() {
Some(v @ Value::List(_)) => v.into_items().unwrap_or_default(),
_ => Vec::new(),
};
let topics = items(host.capsae(scope, None));
let positions = items(host.positions(scope, None));
let rows: Vec<Vec<Value>> = topics
.into_iter()
.zip(positions)
.map(|(t, p)| vec![t, p])
.collect();
vec![Value::table(crate::value::Table::new(
Some(vec!["topic".to_string(), "position".to_string()]),
rows,
))]
}
pub(crate) fn matrix<X: crate::thread::Attachment>(
scope: Scope<'_, X>,
node: bool,
full: bool,
) -> Vec<Value> {
let Some(peers) = scope.peers else {
return vec![Value::Null];
};
let part = match (node, full) {
(false, false) => RegRef::Record,
(false, true) => RegRef::FullRecord,
(true, false) => RegRef::NodeRecord,
(true, true) => RegRef::NodeFullRecord,
};
let spelled = match (node, full) {
(false, false) => "#.",
(false, true) => "#.*",
(true, false) => "##.",
(true, true) => "##.*",
};
let mut header: Vec<String> = Vec::new();
let mut records: Vec<Vec<(String, Value)>> = Vec::new();
for p in &peers.caps {
let fields = match recall(&p.register, &part) {
Value::Record(f) => f,
_ => Vec::new(),
};
for (k, _) in &fields {
if !header.iter().any(|h| h == k) {
header.push(k.clone());
}
}
records.push(fields);
}
let mut rows = Vec::with_capacity(records.len());
for fields in records {
let mut row = Vec::with_capacity(header.len());
for h in &header {
let v = fields
.iter()
.find(|(k, _)| k == h)
.map(|(_, v)| v.clone())
.unwrap_or(Value::Null);
if matches!(
v,
Value::List(_) | Value::Record(_) | Value::Table(_) | Value::Foreign(_)
) {
record_refusal(format!(
"'{spelled}' holds {} under '{h}': a table cell is flat (read the regula with '$.{h}' instead)",
crate::stdlib::kind_of(&v)
));
return vec![Value::Null];
}
row.push(v);
}
rows.push(row);
}
vec![Value::table(crate::value::Table::new(Some(header), rows))]
}