use crate::pipeline::*;
type Group<X> = (Vec<Value>, Vec<(String, Value, Prov)>, Vec<Thread<X>>);
fn projected_properties(command: &Command) -> Vec<String> {
struct Props(Vec<String>);
impl visit::Visitor for Props {
fn operand(&mut self, o: &Operand) {
if let Operand::Rel {
steps,
projection: Some(Projection::Property(Some(name))),
anchor: Anchor::Current,
} = o
&& steps.is_empty()
{
if !self.0.contains(name) {
self.0.push(name.clone());
}
return;
}
visit::walk_operand(self, o);
}
fn command(&mut self, s: &Command) {
if !matches!(s, Command::Subcontext { .. }) {
visit::walk_command(self, s);
}
}
}
let mut v = Props(Vec::new());
v.command(command);
v.0
}
pub(crate) fn command_reads_context(command: &Command) -> bool {
struct Reads(bool);
impl visit::Visitor for Reads {
fn operand(&mut self, o: &Operand) {
if matches!(
o,
Operand::Topics
| Operand::Positions { .. }
| Operand::Matrix { .. }
| Operand::CtxTable
| Operand::PeerReg { .. }
) {
self.0 = true;
} else {
visit::walk_operand(self, o);
}
}
fn command(&mut self, s: &Command) {
if !matches!(s, Command::Subcontext { .. }) {
visit::walk_command(self, s);
}
}
fn elem(&mut self, e: &PathElem) {
if !matches!(
e,
PathElem::Push {
body: PushBody::Query(_),
..
}
) {
visit::walk_elem(self, e);
}
}
}
let mut v = Reads(false);
v.command(command);
v.0
}
pub(crate) fn record_field(v: Value, name: &str) -> Value {
match v {
Value::Table(t) => t.column(name).unwrap_or(Value::Null),
Value::Record(fields) => fields
.into_iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v)
.unwrap_or(Value::Null),
Value::List(items) => Value::list(
items
.iter()
.cloned()
.map(|item| record_field(item, name))
.collect(),
),
_ => Value::Null,
}
}
pub(crate) fn topic_field(op: &Operand) -> Option<Vec<String>> {
fn chain(op: &Operand) -> Option<Vec<String>> {
match op {
Operand::Topic => Some(Vec::new()),
Operand::Field { base, name } => {
let mut names = chain(base)?;
names.push(name.clone());
Some(names)
}
_ => None,
}
}
chain(op).filter(|names| !names.is_empty())
}
pub(crate) fn apply_commands<H: Host>(
commands: &[Command],
offset: usize,
top: bool,
mut caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
) -> Vec<Thread<H::Ext>> {
let mut i = 0;
while i < commands.len() {
if top {
with_run(|s| s.command = (offset + i) as u32 + 1);
}
if let (Command::Agg(call), Some(Command::Func(next))) = (&commands[i], commands.get(i + 1))
{
if call.name == "group" && next.name == "count" && next.args.is_empty() {
caps = keyed_agg(call, caps, host, trace, outer, None, true);
i += 2;
continue;
}
}
caps = apply_command(&commands[i], caps, host, trace, outer);
i += 1;
}
caps
}
pub(crate) fn apply_command<H: Host>(
command: &Command,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
) -> Vec<Thread<H::Ext>> {
let snapshot = command_reads_context(command).then(|| Peers::new(caps.clone()));
let peers = snapshot.as_ref();
let names = projected_properties(command);
if !names.is_empty() {
let names: Vec<&str> = names.iter().map(String::as_str).collect();
host.prefetch(&caps, &names);
}
match command {
Command::Func(call)
if matches!(call.name.as_str(), "markdown" | "html" | "atrep" | "conllu") =>
{
caps.into_iter()
.map(|mut c| {
let v = match &c.topic {
Some(t) => {
Value::Str(host.render_values(std::slice::from_ref(t), &call.name))
}
None => Value::Str(host.render_node(&c.ext, &call.name)),
};
c.prov = effective_prov(host, &c);
c.topic = Some(v);
c
})
.collect()
}
Command::Func(call) if call.name == "json" || call.name == "jsonl" => caps
.into_iter()
.map(|mut c| {
let v = match &c.topic {
Some(t) => Value::Str(t.to_json()),
None => Value::Str(host.node_to_json_text(&c.ext)),
};
c.prov = effective_prov(host, &c);
c.topic = Some(v);
c
})
.collect(),
Command::Func(call) if call.name == "link" => caps
.into_iter()
.map(|mut c| {
c.topic = Some(host.property_of(&c.ext, "link").unwrap_or(Value::Null));
c.prov = host.prov_at(&c.ext);
c
})
.collect(),
#[cfg(not(feature = "kaiv"))]
Command::Func(call) if call.name == "kaiv" => {
record_refusal("kaiv output is not compiled in (feature kaiv)".into());
caps
}
#[cfg(feature = "kaiv")]
Command::Func(call) if call.name == "kaiv" => caps
.into_iter()
.map(|mut c| {
let doc = match &c.topic {
Some(t) => crate::pipeline::kaiv_out::document(std::slice::from_ref(t)),
None => crate::pipeline::kaiv_out::document(std::slice::from_ref(&host.node_to_json(&c.ext,
))),
};
c.prov = effective_prov(host, &c);
c.topic = Some(Value::Str(doc.unwrap_or_else(|e| e)));
c
})
.collect(),
Command::Func(call) if call.name == "xml" => caps
.into_iter()
.map(|mut c| {
let xml = match &c.topic {
Some(t) => host.value_to_xml(t, "item"),
None => host.node_to_xml(&c.ext),
};
c.prov = effective_prov(host, &c);
c.topic = Some(Value::Str(xml));
c
})
.collect(),
Command::Func(call) if crate::pipeline::env::split_env(&call.name).is_some() => {
env_command(call, caps, host, trace, outer, peers)
}
Command::Agg(call) if crate::pipeline::env::split_env(&call.name).is_some() => {
env_agg_command(call, caps, host, trace, outer, peers)
}
Command::Agg(call) if call.of_expr().is_some() => {
let expr = call.of_expr().expect("checked");
let caps = of_values(expr, caps, host, trace, outer, peers);
apply_command(&Command::Agg(call.without_of()), caps, host, trace, outer)
}
Command::Func(call) if call.name == "sh" && !run_option(|o| o.shell_allowed()) => {
record_refusal("the sh(...) command runs external commands; pass --allow-shell".into());
Vec::new()
}
Command::Func(call) if call.name == "sh" => {
shell_command(call, caps, host, trace, outer, peers)
}
Command::Func(call) if call.name == "exchange" => {
exchange_command(call, caps, host, trace, outer, peers)
}
Command::Func(call) if call.name == "default" => {
default_command(call, caps, host, trace, outer, peers)
}
Command::Func(call) if matches!(call.name.as_str(), "record" | "rec") => caps
.into_iter()
.enumerate()
.map(|(i, mut c)| {
let scope = c.scope(Some(i + 1), peers, outer);
match record_fields_prov(&call.args, host, &c.ext, trace, scope) {
Ok(f) => {
let (fields, prov) = split_fields(f);
c.topic = Some(Value::Record(fields));
c.prov = prov;
}
Err(why) => {
crate::pipeline::record_refusal(why);
c.topic = Some(Value::Null);
}
}
c
})
.collect(),
Command::RecordWith(call) => record_with_command(call, caps, host, trace, outer, peers),
Command::Func(call) if stdlib::known_keyed(&call.name) => caps
.into_iter()
.map(|mut c| {
if c.members.is_empty() {
return c;
}
let members = keyed_agg(
call,
std::mem::take(&mut c.members),
host,
trace,
outer,
peers,
false,
);
c.topic = Some(Value::list(
members
.iter()
.map(|m| {
m.topic
.clone()
.unwrap_or_else(|| host.node_scalar(&m.ext))
})
.collect(),
));
c.prov = Prov::list(members.iter().map(|m| effective_prov(host, m)).collect());
c.members = members;
c
})
.collect(),
Command::Func(call) if stdlib::association(&call.name) => caps
.into_iter()
.map(|mut c| {
let members = std::mem::take(&mut c.members);
c.topic = Some(if members.is_empty() {
Value::Null
} else {
associate_caps(call, &members, host, trace, outer, peers)
});
c.prov = c.prov.flat();
c
})
.collect(),
Command::Func(call)
if matches!(call.name.as_str(), "#" | "%" | "@" | "fields")
&& caps.iter().any(|c| c.topic.is_none()) =>
{
caps.into_iter()
.map(|mut c| {
let v = match c.topic.take() {
Some(t) => stdlib::apply_scalar(call, t, &|e| host.scale_of(e))
.into_iter()
.next()
.unwrap_or(Value::Null),
None if call.name == "%" => Value::Record(
host.property_names_of(&c.ext)
.into_iter()
.map(|n| {
let v = host.property_of(&c.ext, &n).unwrap_or(Value::Null);
(n, v)
})
.collect(),
),
None if call.name == "fields" => Value::list(
crate::graph::navigate::CORE_FIELDS
.iter()
.map(|k| Value::projection(1, *k))
.collect(),
),
None if call.name == "@" => {
record_refusal(
"@: a node has no row reading; '| %' reads its properties as a \
record, '| #' a tabula node as a table"
.into(),
);
Value::Null
}
None => match host.node_table(&c.ext) {
Ok(t) => Value::table(t),
Err(why) => {
record_refusal(format!("#: {why}"));
Value::Null
}
},
};
c.prov = effective_prov(host, &c);
c.topic = Some(v);
c
})
.collect()
}
Command::Func(call)
if stdlib::known_agg(&call.name)
&& !matches!(
call.name.as_str(),
"number" | "ngrams" | "csv" | "table" | "pretty"
) =>
{
agg_as_func_command(call, caps, host, trace, outer, peers)
}
Command::Func(call) => func_command(call, caps, host, trace, outer, peers),
Command::Repeat {
command: inner,
min,
max,
} => repeat_command(inner, min, max, caps, host, trace, outer),
Command::Map(inner) => map_command(inner, caps, host, trace, outer),
Command::Spread { outer } => caps
.into_iter()
.flat_map(|c| {
let mut values = match c.topic.clone() {
Some(l @ Value::List(_)) => l.into_items().unwrap_or_default(),
Some(Value::Null) | None => Vec::new(),
Some(other) => vec![other],
};
let was_list = matches!(c.topic, Some(Value::List(_)));
if *outer && values.is_empty() {
values.push(Value::Null);
}
let provs: Vec<Prov> = (0..values.len())
.map(|i| {
if was_list {
c.prov.item(i)
} else {
c.prov.clone()
}
})
.collect();
values.into_iter().zip(provs).map(move |(v, prov)| Thread::<H::Ext> {
prov,
register: c.register.clone(),
topic: Some(v),
members: Vec::new(),
captures: c.captures.clone(),
named: c.named.clone(),
ext: c.ext.clone(),
})
})
.collect(),
Command::Nav(branch) => host.navigate(branch, caps, trace, outer, peers),
Command::Push(name) => caps
.into_iter()
.map(|mut c| {
let (value, prov) = match c.topic.clone() {
None => match host.node_ref(&c.ext) {
Some(r) => (r, Prov::at(host.origin_at(&c.ext).position, command_now())),
None => return c,
},
Some(topic) => (topic, c.prov.clone()),
};
c.register.push(Reg {
prov,
site: host.origin_at(&c.ext),
name: name.clone(),
value,
});
c
})
.collect(),
Command::Subcontext { name, body } => {
subcontext_command(name, body, caps, host, trace, outer, peers)
}
Command::Expr(expr) => expr_command(expr, caps, host, trace, outer, peers),
Command::ExprPush { name, expr } => {
expr_push_command(name, expr, caps, host, trace, outer, peers)
}
Command::FieldsPush => caps
.into_iter()
.map(|mut c| {
match &c.topic {
Some(Value::Record(fields)) => {
for (k, v) in fields.clone() {
c.register.push(Reg {
prov: c.prov.part(&k),
site: host.origin_at(&c.ext),
name: Some(k),
value: v,
});
}
}
other => record_refusal(format!(
"the fields push '| .%' takes a record topic, not {}; a plain '| .' files any topic as one regula",
match other {
Some(Value::Null) | None => "a node with no topic".to_string(),
Some(Value::Str(_)) => "text".to_string(),
Some(Value::Int(_) | Value::Float(_)) => "a number".to_string(),
Some(Value::Bool(_)) => "a boolean".to_string(),
Some(Value::List(_)) => "a list".to_string(),
Some(_) => "a value".to_string(),
}
)),
}
c
})
.collect(),
Command::RecordPush {
name,
call,
enriched,
} => record_push_command(name, call, enriched, caps, host, trace, outer, peers),
Command::AggPush { name, body } => {
if caps.is_empty() {
return caps;
}
let seed = seed_capsa(&caps, host);
let scope = seed.scope(None, peers, outer);
let (value, o) =
capture(|| operand_value(host, &seed.ext, body, trace, scope));
let prov = Prov::leaf(o);
caps.into_iter()
.map(|mut c| {
c.register.push(Reg {
prov: prov.clone(),
site: host.origin_at(&c.ext),
name: name.clone(),
value: value.clone(),
});
c
})
.collect()
}
Command::AggRecord { call, enriched } => {
let seed = seed_capsa(&caps, host);
let scope = seed.scope(None, peers, outer);
let built = match record_fields_prov(&call.args, host, &seed.ext, trace, scope) {
Ok(f) => f,
Err(why) => {
crate::pipeline::record_refusal(why);
let mut out = seed;
out.topic = Some(Value::Null);
return vec![out];
}
};
let (fields, prov) = if *enriched {
let mut fields = named_register_fields(&seed.register);
let mut parts = named_register_parts(&seed.register);
for (name, value, p) in built {
match fields.iter_mut().find(|(n, _)| n == &name) {
Some((_, v)) => *v = value,
None => fields.push((name.clone(), value)),
}
match parts.iter_mut().find(|(n, _)| n == &name) {
Some((_, q)) => *q = p,
None => parts.push((name, p)),
}
}
(fields, Prov::record(parts))
} else {
split_fields(built)
};
let mut out = seed;
out.topic = Some(Value::Record(fields));
out.prov = prov;
vec![out]
}
Command::AggExpr(expr) => {
let seed = seed_capsa(&caps, host);
expr_command(expr, vec![seed], host, trace, outer, peers)
}
Command::AggRecall(r) => {
let mut out = seed_capsa(&caps, host);
out.topic = Some(recall(&out.register, r));
out.prov = recall_prov(&out.register, r);
vec![out]
}
Command::Agg(call) if call.name == "ungroup" => caps
.into_iter()
.flat_map(|c| {
if c.members.is_empty() {
return vec![c];
}
let key_regs = c.register;
c.members
.into_iter()
.map(|mut m| {
m.register.extend(key_regs.iter().cloned());
m
})
.collect()
})
.collect(),
Command::Agg(call) if call.name == "window" => {
window_command(call, caps, host, trace, outer, peers)
}
Command::Agg(call) if call.name == "shift" => {
shift_command(call, caps, host, trace, outer, peers)
}
Command::Agg(call) if call.name == "zscore" => {
zscore_command(call, caps, host, trace, outer, peers)
}
Command::Agg(call) if matches!(call.name.as_str(), "number" | "outline") => {
outline_command(call, caps, host, trace, outer, peers)
}
Command::Agg(call) if call.name == "tree" => {
tree_command(call, caps, host, trace, outer, peers)
}
Command::Agg(call) if call.name == "timeline" => {
timeline_command(call, caps, host, trace, outer, peers)
}
Command::Agg(call) if stdlib::association(&call.name) => {
association_command(call, caps, host, trace, outer, peers)
}
Command::Agg(call) if stdlib::known_keyed(&call.name) => {
keyed_agg(call, caps, host, trace, outer, peers, false)
}
Command::Agg(call)
if matches!(
call.name.as_str(),
"sort" | "unique" | "reverse" | "first" | "last"
) =>
{
order_command(call, caps, host)
}
Command::Select(pred @ (Predicate::Index(_) | Predicate::Range(_, _))) => {
positional(caps, pred)
}
Command::Select(Predicate::Expr(e)) => {
apply_command(&Command::Filter(e.clone()), caps, host, trace, outer)
}
Command::Filter(cond) => filter_command(cond, caps, host, trace, outer, peers),
Command::Agg(call)
if call.name == "jaccard" && call.args.iter().any(|a| matches!(a, Arg::Expr(_))) =>
{
let resolved = match caps.first() {
Some(c) => FnCall {
name: call.name.clone(),
args: call
.args
.iter()
.map(|a| match a {
Arg::Expr(e) => {
let mut vs =
eval_operand(host, &c.ext, e, trace, c.scope(None, peers, outer));
Arg::Lit(if vs.len() == 1 {
vs.pop().expect("one value")
} else {
Value::list(vs)
})
}
other => other.clone(),
})
.collect(),
},
None => call.clone(),
};
agg_command(&resolved, caps, host)
}
Command::Agg(call) => agg_command(call, caps, host),
Command::Recall(r) => caps
.into_iter()
.map(|mut c| {
c.prov = recall_prov(&c.register, r);
c.topic = Some(recall(&c.register, r));
c
})
.collect(),
}
}
pub(crate) fn default_command<H: Host>(
call: &FnCall,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.map(|mut c| {
let topic = c.topic.take().unwrap_or(Value::Null);
let taken = matches!(topic, Value::Null);
let (v, o) = capture(|| {
if taken {
match call.args.first() {
Some(Arg::Lit(v)) => v.clone(),
Some(Arg::Expr(e)) => operand_scalar(
host,
&c.ext,
e,
trace,
Scope {
prov: None,
register: &c.register,
members: &c.members,
topic: None,
ordinal: None,
captures: &c.captures,
named: &c.named,
peers,
outer,
driver: None,
ext: &c.ext,
view: c.ext.view(),
crossing: None,
witness: None,
},
),
_ => Value::Null,
}
} else {
topic
}
});
c.prov = if taken { Prov::leaf(o) } else { c.prov.flat() };
c.topic = Some(v);
c
})
.collect()
}
pub(crate) fn record_with_command<H: Host>(
call: &FnCall,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.enumerate()
.map(|(i, mut c)| {
let scope = c.scope(Some(i + 1), peers, outer);
let built = match record_fields_prov(&call.args, host, &c.ext, trace, scope) {
Ok(f) => f,
Err(why) => {
crate::pipeline::record_refusal(why);
c.topic = Some(Value::Null);
return c;
}
};
let mut fields = named_register_fields(&c.register);
let mut parts = named_register_parts(&c.register);
for (name, value, p) in built {
match fields.iter_mut().find(|(n, _)| n == &name) {
Some((_, v)) => *v = value,
None => fields.push((name.clone(), value)),
}
match parts.iter_mut().find(|(n, _)| n == &name) {
Some((_, q)) => *q = p,
None => parts.push((name, p)),
}
}
c.topic = Some(Value::Record(fields));
c.prov = Prov::record(parts);
c
})
.collect()
}
pub(crate) fn agg_as_func_command<H: Host>(
call: &FnCall,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
let stripped = call.without_of();
let of = call.of_expr();
let call = if of.is_some() { &stripped } else { call };
caps.into_iter()
.map(|mut c| {
if of.is_none()
&& let Some(topic @ (Value::Record(_) | Value::Table(_))) = &c.topic
&& let Some(v) = stdlib::reduce_structure(call, topic, &|e| host.scale_of(e))
{
c.topic = Some(v);
c.prov = c.prov.flat();
c.members = Vec::new();
return c;
}
let items = match of {
Some(expr) => {
let members = std::mem::take(&mut c.members);
let threads: Vec<Thread<H::Ext>> = if !members.is_empty() {
c.topic.take();
members
} else {
let as_thread = |topic: Option<Value>, prov: Prov| Thread::<H::Ext> {
prov,
register: c.register.clone(),
topic,
members: Vec::new(),
captures: c.captures.clone(),
named: c.named.clone(),
ext: c.ext.clone(),
};
match c.topic.take() {
Some(l @ Value::List(_)) => l
.into_items()
.unwrap_or_default()
.into_iter()
.enumerate()
.map(|(i, v)| as_thread(Some(v), c.prov.item(i)))
.collect(),
Some(Value::Table(t)) => t
.as_ref()
.clone()
.into_records()
.into_items()
.unwrap_or_default()
.into_iter()
.enumerate()
.map(|(i, v)| as_thread(Some(v), c.prov.item(i)))
.collect(),
Some(Value::Record(fields)) => fields
.into_iter()
.map(|(k, v)| {
let prov = c.prov.part(&k);
as_thread(Some(v), prov)
})
.collect(),
Some(other) => vec![as_thread(Some(other), c.prov.clone())],
None => vec![as_thread(None, c.prov.clone())],
}
};
of_values(expr, threads, host, trace, outer, peers)
.into_iter()
.map(|t| t.topic.unwrap_or(Value::Null))
.collect()
}
None => match c.topic.take() {
Some(l @ Value::List(_)) => l.into_items().unwrap_or_default(),
Some(other) => vec![other],
None => vec![host.node_scalar(&c.ext)],
},
};
let mut out = stdlib::apply(call, items, &|e| host.scale_of(e));
c.topic = Some(match out.len() {
1 => out.pop().expect("len checked"),
_ => Value::list(out),
});
c.prov = c.prov.flat();
c.members = Vec::new();
c
})
.collect()
}
pub(crate) fn repeat_command<H: Host>(
inner: &Command,
min: &usize,
max: &Option<usize>,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
) -> Vec<Thread<H::Ext>> {
let mut caps = caps;
let n_max = host.quantifier_bound();
if let Some(n) = max
&& *n > n_max
{
record_refusal(format!(
"the loop's explicit ceiling ({n}) exceeds the quantifier \
bound ({n_max}); raise --quantifier-bound"
));
return Vec::new();
}
if *min > n_max {
record_refusal(format!(
"the loop's minimum ({min}) exceeds the quantifier bound \
({n_max}); raise --quantifier-bound"
));
return Vec::new();
}
let limit = max.unwrap_or(n_max);
let exact = *max == Some(*min);
let signature = |caps: &[Thread<H::Ext>]| -> Vec<RoundState<<H::Ext as Attachment>::Key>> {
caps.iter()
.map(|c| {
(
c.ext.key(),
c.topic.clone(),
named_register_fields(&c.register),
)
})
.collect()
};
let mut round = 0;
while round < limit {
let before = if exact || round < *min {
None
} else {
Some(signature(&caps))
};
caps = apply_command(inner, caps, host, trace, outer);
round += 1;
if let Some(b) = before
&& b == signature(&caps)
{
break;
}
}
caps
}
pub(crate) fn subcontext_command<H: Host>(
name: &Option<String>,
body: &Query,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.enumerate()
.map(|(i, mut c)| {
let scope = c.scope(Some(i + 1), peers, outer);
let (value, prov) = host.subcontext_traced(body, &c.ext, trace, Some(&scope));
c.register.push(Reg {
prov,
site: host.origin_at(&c.ext),
name: name.clone(),
value,
});
c
})
.collect()
}
pub(crate) fn expr_command<H: Host>(
expr: &Operand,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.enumerate()
.flat_map(|(i, mut c)| {
let scope = c.scope(Some(i + 1), peers, outer);
if c.members.is_empty() {
let (values, o) = capture(|| eval_operand(host, &c.ext, expr, trace, scope));
let empty_range = values.is_empty() && matches!(expr, Operand::Range { .. });
if values.len() > 1 || empty_range {
return values
.into_iter()
.map(|v| {
let mut k = c.clone();
k.topic = Some(v);
k.prov = Prov::leaf(o.clone());
k
})
.collect::<Vec<_>>();
}
}
let (value, o) = capture(|| group_value(host, &c.ext, expr, trace, scope));
c.topic = Some(value);
c.prov = Prov::leaf(o);
vec![c]
})
.collect()
}
pub(crate) fn expr_push_command<H: Host>(
name: &Option<String>,
expr: &Operand,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.enumerate()
.map(|(i, mut c)| {
let scope = c.scope(Some(i + 1), peers, outer);
let (value, o) = capture(|| operand_value(host, &c.ext, expr, trace, scope));
c.register.push(Reg {
prov: Prov::leaf(o),
site: host.origin_at(&c.ext),
name: name.clone(),
value,
});
c
})
.collect()
}
pub(crate) fn record_push_command<H: Host>(
name: &Option<String>,
call: &FnCall,
enriched: &bool,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.enumerate()
.map(|(i, mut c)| {
let scope = c.scope(Some(i + 1), peers, outer);
let built = match record_fields_prov(&call.args, host, &c.ext, trace, scope) {
Ok(f) => f,
Err(why) => {
crate::pipeline::record_refusal(why);
return c;
}
};
let (fields, prov) = if *enriched {
let mut fields = named_register_fields(&c.register);
let mut parts = named_register_parts(&c.register);
for (name, value, p) in built {
match fields.iter_mut().find(|(n, _)| n == &name) {
Some((_, v)) => *v = value,
None => fields.push((name.clone(), value)),
}
match parts.iter_mut().find(|(n, _)| n == &name) {
Some((_, q)) => *q = p,
None => parts.push((name, p)),
}
}
(fields, Prov::record(parts))
} else {
split_fields(built)
};
c.register.push(Reg {
prov,
site: host.origin_at(&c.ext),
name: name.clone(),
value: Value::Record(fields),
});
c
})
.collect()
}
pub(crate) fn order_command<H: Host>(
call: &FnCall,
caps: Vec<Thread<H::Ext>>,
host: &H,
) -> Vec<Thread<H::Ext>> {
if caps.is_empty() && matches!(call.name.as_str(), "first" | "last") {
return vec![Thread::bare(host.unplaced_ext(), Some(Value::Null))];
}
let is_node: Vec<bool> = caps.iter().map(|c| c.topic.is_none()).collect();
let mut caps: Vec<Thread<H::Ext>> = caps
.into_iter()
.map(|mut c| {
if c.topic.is_none() {
c.prov = host.prov_at(&c.ext);
}
c
})
.collect();
let topic_of = |c: &Thread<H::Ext>| {
c.topic
.clone()
.unwrap_or_else(|| host.node_scalar(&c.ext))
};
match call.name.as_str() {
"sort" => match stdlib::text_order(call) {
Some(cmp) => {
caps.sort_by(|a, b| cmp(&topic_of(a).to_string(), &topic_of(b).to_string()))
}
None => {
let collate = stdlib::default_text_order();
let scale = |e: &str| host.scale_of(e);
caps.sort_by(|a, b| {
stdlib::default_cmp(&topic_of(a), &topic_of(b), &scale, collate.as_deref())
})
}
},
"reverse" => caps.reverse(),
"unique" => {
let mut kept: Vec<Thread<H::Ext>> = Vec::new();
let mut by_node: HashMap<<H::Ext as Attachment>::Key, usize> = HashMap::new();
let mut by_value: HashMap<crate::value::SortKey, usize> = HashMap::new();
let scale = |e: &str| host.scale_of(e);
for (c, node) in caps.drain(..).zip(is_node.iter()) {
let seen = if *node {
by_node.get(&c.ext.key()).copied()
} else {
by_value.get(&topic_of(&c).sort_key_with(&scale)).copied()
};
match seen {
Some(i) => kept[i].prov.origins.union(&c.prov.origins),
None => {
if *node {
by_node.insert(c.ext.key(), kept.len());
} else {
by_value.insert(topic_of(&c).sort_key_with(&scale), kept.len());
}
kept.push(c);
}
}
}
caps = kept;
}
"first" => {
let (s, e) = stdlib::rank_span(call.args.first(), caps.len(), false)
.unwrap_or((0, caps.len().min(1)));
caps.truncate(e);
caps.drain(..s);
}
"last" => {
let (s, e) = stdlib::rank_span(call.args.first(), caps.len(), true)
.unwrap_or((caps.len().saturating_sub(1), caps.len()));
caps.truncate(e);
caps.drain(..s);
}
_ => {}
}
caps
}
pub(crate) fn shell_command<H: Host>(
call: &FnCall,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.enumerate()
.map(|(i, mut c)| {
let scope = c.scope(Some(i + 1), peers, outer);
let (cmd, o) = capture(|| {
call.args.first().map(|a| match a {
Arg::Lit(v) => v.to_string(),
Arg::Expr(e) => operand_scalar(host, &c.ext, e, trace, scope).to_string(),
Arg::Range(..) | Arg::Named { .. } | Arg::Opt { .. } | Arg::Pairs { .. } => {
String::new()
}
})
});
c.topic = Some(match cmd {
Some(cmd) if !cmd.is_empty() => {
run_shell(&cmd, c.topic.as_ref()).unwrap_or(Value::Null)
}
_ => Value::Null,
});
let mut prov = c.prov.flat();
prov.origins.union(&o);
c.prov = prov;
c
})
.collect()
}
pub(crate) fn func_command<H: Host>(
call: &FnCall,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.flat_map(|c| {
let topic = c.topic.clone().unwrap_or(Value::Null);
let prov = c.prov.flat();
let resolved;
let call = if call.args.iter().any(|a| matches!(a, Arg::Expr(_))) {
resolved = FnCall {
name: call.name.clone(),
args: call
.args
.iter()
.map(|a| match a {
Arg::Expr(e) => Arg::Lit(operand_scalar(
host,
&c.ext,
e,
trace,
c.scope(None, peers, outer),
)),
other => other.clone(),
})
.collect(),
};
&resolved
} else {
call
};
stdlib::apply_scalar(call, topic, &|e| host.scale_of(e))
.into_iter()
.map(move |v| Thread::<H::Ext> {
prov: prov.clone(),
register: c.register.clone(),
topic: Some(v),
members: Vec::new(),
captures: c.captures.clone(),
named: c.named.clone(),
ext: c.ext.clone(),
})
})
.collect()
}
pub(crate) fn map_command<H: Host>(
inner: &Command,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.map(|mut c| {
let (items, was_list) = match c.topic.take() {
Some(l @ Value::List(_)) => (l.into_items().unwrap_or_default(), true),
Some(Value::Null) | None => {
c.topic = Some(Value::Null);
return c;
}
Some(other) => (vec![other], false),
};
let provs: Vec<Prov> = (0..items.len())
.map(|i| {
if was_list {
c.prov.item(i)
} else {
c.prov.clone()
}
})
.collect();
let pseudo: Vec<Thread<H::Ext>> = items
.into_iter()
.zip(provs)
.map(|(v, prov)| Thread::<H::Ext> {
prov,
register: c.register.clone(),
topic: Some(v),
members: Vec::new(),
captures: c.captures.clone(),
named: c.named.clone(),
ext: c.ext.clone(),
})
.collect();
let (mut out, mut provs): (Vec<Value>, Vec<Prov>) =
apply_command(inner, pseudo, host, trace, outer)
.into_iter()
.map(|p| (p.topic.unwrap_or(Value::Null), p.prov))
.unzip();
if was_list || out.len() != 1 {
c.prov = Prov::list(provs);
c.topic = Some(Value::list(out));
} else {
c.prov = provs.pop().unwrap_or_default();
c.topic = Some(out.pop().expect("len checked"));
}
c
})
.collect()
}
pub(crate) fn filter_command<H: Host>(
cond: &PredExpr,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.enumerate()
.filter_map(|(i, mut c)| {
let scope = c.scope(Some(i + 1), peers, outer);
let hit = if !host.witnessed(&c.ext) {
host.exists_binding(&c.ext, &[cond], trace, scope)
} else {
eval_pred_expr(host, &c.ext, cond, trace, scope)
};
if !hit {
return None;
}
let groups = extract_captures(cond, host, &c.ext, trace, scope);
if let Some((groups, named)) = groups {
c.captures = groups;
c.named = named;
}
Some(c)
})
.collect()
}
fn of_values<H: Host>(
expr: &Operand,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.enumerate()
.map(|(i, mut c)| {
let scope = c.scope(Some(i + 1), peers, outer);
let (value, o) = capture(|| group_value(host, &c.ext, expr, trace, scope));
c.topic = Some(value);
c.prov = Prov::leaf(o);
c
})
.collect()
}
pub(crate) fn agg_command<H: Host>(
call: &FnCall,
caps: Vec<Thread<H::Ext>>,
host: &H,
) -> Vec<Thread<H::Ext>> {
if call.name == "encoding" {
record_refusal(
"the encoding command stands last: '… @| csv @| encoding(windows-1251)'; \
the tool that prints the output applies it"
.into(),
);
return Vec::new();
}
let columns: Vec<&str> = if matches!(call.name.as_str(), "csv" | "table" | "pretty")
|| call.name == stdlib::ROWS_FOR_RENDERING
{
stdlib::split_csv_delimiter(call)
.1
.iter()
.filter_map(|a| match a {
Arg::Lit(Value::Str(s)) => Some(s.as_str()),
_ => None,
})
.collect()
} else {
Vec::new()
};
let topics: Vec<Value> = caps
.iter()
.map(|c| match &c.topic {
Some(t) => t.clone(),
None if call.name == "#" => host.node_to_json(&c.ext),
None if !columns.is_empty() => Value::Record(
columns
.iter()
.map(|col| {
(
col.to_string(),
host.property_of(&c.ext, col).unwrap_or(Value::Null),
)
})
.collect(),
),
None => host.node_scalar(&c.ext),
})
.collect();
if matches!(call.name.as_str(), "combinations" | "pairs" | "triples")
&& caps.len() > 1
&& caps.iter().all(|c| c.topic.is_none())
&& topics
.iter()
.filter(|t| !matches!(t, Value::Null))
.map(|t| t.to_string())
.collect::<std::collections::HashSet<_>>()
.len()
<= 1
{
record_refusal(format!(
"'{0}' combines values, and these nodes have none to tell \
them apart (a node stands by its name): project a value \
first — '| ::title @| {0}'",
call.name
));
return Vec::new();
}
let mut origins = Origins::default();
for c in &caps {
match &c.topic {
Some(_) => origins.union(&c.prov.origins),
None => origins.insert(host.origin_at(&c.ext)),
}
}
let node = host.unplaced_ext();
let selects = matches!(
call.name.as_str(),
"max" | "min" | "first" | "last" | "unique" | "sort" | "reverse"
);
let per_input: Vec<Origins> = if selects {
caps.iter()
.map(|c| match &c.topic {
Some(_) => c.prov.origins.clone(),
None => {
let mut o = Origins::default();
o.insert(host.origin_at(&c.ext));
o
}
})
.collect()
} else {
Vec::new()
};
let inputs = if selects { topics.clone() } else { Vec::new() };
let register = shared_register(&caps);
stdlib::apply(call, topics, &|e| host.scale_of(e))
.into_iter()
.map(|v| Thread::<H::Ext> {
prov: Prov::leaf(
inputs
.iter()
.position(|t| *t == v)
.map(|i| per_input[i].clone())
.unwrap_or_else(|| origins.clone()),
),
register: register.clone(),
topic: Some(v),
members: Vec::new(),
captures: Vec::new(),
named: Vec::new(),
ext: node.clone(),
})
.collect()
}
pub(crate) fn shared_register<X: Attachment>(caps: &[Thread<X>]) -> Vec<Reg> {
let Some((first, rest)) = caps.split_first() else {
return Vec::new();
};
first
.register
.iter()
.filter(|r| {
rest.iter().all(|c| {
c.register
.iter()
.any(|q| q.name == r.name && q.value == r.value)
})
})
.cloned()
.collect()
}
pub(crate) fn seed_capsa<H: Host>(caps: &[Thread<H::Ext>], host: &H) -> Thread<H::Ext> {
let (topics, provs): (Vec<Value>, Vec<Prov>) = caps
.iter()
.map(|c| match &c.topic {
Some(t) => (t.clone(), c.prov.clone()),
None => (host.node_scalar(&c.ext), host.prov_at(&c.ext)),
})
.unzip();
Thread::<H::Ext> {
prov: Prov::list(provs),
register: shared_register(caps),
topic: Some(Value::list(topics)),
members: Vec::new(),
captures: Vec::new(),
named: Vec::new(),
ext: caps
.first()
.map_or_else(|| host.root_ext(), |c| c.ext.bare()),
}
}
pub(crate) fn keyed_agg<H: Host>(
call: &FnCall,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
counting: bool,
) -> Vec<Thread<H::Ext>> {
let (count, key_args): (Option<&Arg>, &[Arg]) = match call.args.split_first() {
Some((sel @ (Arg::Lit(Value::Int(_)) | Arg::Range(_, _)), rest))
if matches!(call.name.as_str(), "top" | "bottom") =>
{
(Some(sel), rest)
}
_ => (None, &call.args),
};
let key_of = |c: &Thread<H::Ext>, i: usize| -> Vec<Value> {
key_args
.iter()
.filter_map(|a| match a {
Arg::Expr(e) => Some(operand_scalar(
host,
&c.ext,
e,
trace,
c.scope(Some(i + 1), peers, outer),
)),
Arg::Lit(_)
| Arg::Range(_, _)
| Arg::Named { .. }
| Arg::Opt { .. }
| Arg::Pairs { .. } => None,
})
.collect()
};
let compare_keys = |a: &[Value], b: &[Value]| -> Ordering {
a.iter()
.zip(b.iter())
.map(|(x, y)| x.compare_with(y, &|e| host.scale_of(e)))
.find(|o| *o != Ordering::Equal)
.unwrap_or(Ordering::Equal)
};
let has_null = |k: &[Value]| k.iter().any(|v| matches!(v, Value::Null));
let has_nan = |k: &[Value]| k.iter().any(stdlib::is_nan);
if call.name == "group" {
let mut groups: Vec<Group<H::Ext>> = Vec::new();
let mut tally: Vec<(i64, Origins, H::Ext)> = Vec::new();
let member_origins = |c: &Thread<H::Ext>| match &c.topic {
Some(_) => c.prov.origins.clone(),
None => host.origins_at(&c.ext),
};
let mut index: HashMap<Vec<SortKey>, usize> = HashMap::new();
for (i, c) in caps.into_iter().enumerate() {
let scope = c.scope(Some(i + 1), peers, outer);
let fields = match record_fields_prov(&call.args, host, &c.ext, trace, scope) {
Ok(f) => f,
Err(why) => {
crate::pipeline::record_refusal(why);
continue;
}
};
let key: Vec<Value> = fields.iter().map(|(_, v, _)| v.clone()).collect();
if key.iter().any(|v| matches!(v, Value::Null)) {
continue;
}
let hashed: Vec<SortKey> = key
.iter()
.map(|v| v.sort_key_with(&|e| host.scale_of(e)))
.collect();
match index.get(&hashed) {
Some(&at) => {
let (_, kf, members) = &mut groups[at];
for ((_, _, p), (_, _, q)) in kf.iter_mut().zip(&fields) {
p.origins.union(&q.origins);
}
if counting {
let (n, acc, _) = &mut tally[at];
*n += 1;
acc.union(&member_origins(&c));
} else {
members.push(c)
}
}
None => {
index.insert(hashed, groups.len());
if counting {
tally.push((1, member_origins(&c), c.ext.bare()));
groups.push((key, fields, Vec::new()))
} else {
groups.push((key, fields, vec![c]))
}
}
}
}
if counting {
return groups
.into_iter()
.zip(tally)
.map(|((_, fields, _), (n, origins, node))| {
let register = fields
.into_iter()
.map(|(name, value, prov)| Reg {
prov,
site: host.origin_at(&node),
name: Some(name),
value,
})
.collect();
Thread::<H::Ext> {
prov: Prov::leaf(origins),
register,
topic: Some(Value::Int(n)),
members: Vec::new(),
captures: Vec::new(),
named: Vec::new(),
ext: node.clone(),
}
})
.collect();
}
return groups
.into_iter()
.map(|(_, fields, members)| {
let node = members[0].ext.bare();
let register = fields
.into_iter()
.map(|(name, value, prov)| Reg {
prov,
site: host.origin_at(&node),
name: Some(name),
value,
})
.collect();
let prov = Prov::list(
members
.iter()
.map(|m| match &m.topic {
Some(_) => m.prov.clone(),
None => host.prov_at(&m.ext),
})
.collect(),
);
let topics: Vec<Value> = members
.iter()
.map(|m| m.topic.clone().unwrap_or_else(|| host.node_scalar(&m.ext)))
.collect();
Thread::<H::Ext> {
prov,
register,
topic: Some(Value::list(topics)),
members,
captures: Vec::new(),
named: Vec::new(),
ext: node.clone(),
}
})
.collect();
}
let mut keyed: Vec<(Vec<Value>, Thread<H::Ext>)> = caps
.into_iter()
.enumerate()
.map(|(i, c)| (key_of(&c, i), c))
.collect();
match call.name.as_str() {
"sort_by" => {
keyed.sort_by(|(a, _), (b, _)| {
has_null(a)
.cmp(&has_null(b))
.then_with(|| has_nan(a).cmp(&has_nan(b)))
.then_with(|| compare_keys(a, b))
});
}
"top" | "bottom" => {
keyed.retain(|(k, _)| !has_null(k) && !has_nan(k));
if call.name == "top" {
keyed.sort_by(|(a, _), (b, _)| compare_keys(b, a));
} else {
keyed.sort_by(|(a, _), (b, _)| compare_keys(a, b));
}
let (s, e) = stdlib::rank_span(count, keyed.len(), false).unwrap_or((0, 0));
keyed.truncate(e);
keyed.drain(..s);
}
"unique_by" => {
let mut seen: HashSet<Vec<SortKey>> = HashSet::new();
keyed.retain(|(k, _)| {
let hashed: Vec<SortKey> = k
.iter()
.map(|v| v.sort_key_with(&|e| host.scale_of(e)))
.collect();
seen.insert(hashed)
});
}
"min_by" | "max_by" => {
keyed.retain(|(k, _)| !has_null(k) && !has_nan(k));
let want = if call.name == "min_by" {
Ordering::Less
} else {
Ordering::Greater
};
let extreme = keyed
.iter()
.map(|(k, _)| k.clone())
.reduce(|a, b| if compare_keys(&b, &a) == want { b } else { a });
if let Some(extreme) = extreme {
keyed.retain(|(k, _)| compare_keys(k, &extreme) == Ordering::Equal);
}
}
_ => {}
}
keyed.into_iter().map(|(_, c)| c).collect()
}
pub(crate) fn exchange_command<H: Host>(
call: &FnCall,
caps: Vec<Thread<H::Ext>>,
host: &H,
trace: &H::Trace,
outer: Option<&Scope<'_, H::Ext>>,
peers: Option<&Peers<H::Ext>>,
) -> Vec<Thread<H::Ext>> {
caps.into_iter()
.map(|mut c| {
let topic = c.topic.take().unwrap_or(Value::Null);
let scope = c.scope(None, peers, outer);
let arg = |n: usize| -> Option<Value> {
match call.args.get(n) {
Some(Arg::Lit(v)) => Some(v.clone()),
Some(Arg::Expr(e)) => Some(operand_scalar(host, &c.ext, e, trace, scope)),
_ => None,
}
};
let rates = arg(0).unwrap_or(Value::Null);
let target = arg(1).map(|v| v.to_string());
let v =
match crate::value::quantity::exchange(&topic, &rates, target.as_deref(), &|e| {
host.scale_of(e)
}) {
Ok(v) => v,
Err(why) => {
record_refusal(why);
Value::Null
}
};
c.prov = c.prov.flat();
c.topic = Some(v);
c
})
.collect()
}