use sim_kernel::{Args, Symbol, Value};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum ArgumentOrigin {
KernelPosition(usize),
Guest(Symbol),
}
#[derive(Clone)]
pub enum ArgumentInput {
Positional(Value),
Named {
name: Symbol,
value: Value,
},
Receiver(Value),
Remainder(Value),
Unconsumed(Value),
}
#[derive(Clone)]
pub struct BoundArgument {
input: ArgumentInput,
origin: ArgumentOrigin,
}
impl BoundArgument {
pub fn new(input: ArgumentInput, origin: ArgumentOrigin) -> Self {
Self { input, origin }
}
pub const fn input(&self) -> &ArgumentInput {
&self.input
}
pub const fn origin(&self) -> &ArgumentOrigin {
&self.origin
}
}
#[derive(Clone, Default)]
pub struct CallInput {
arguments: Vec<BoundArgument>,
}
impl CallInput {
pub const fn new() -> Self {
Self {
arguments: Vec::new(),
}
}
pub fn push(&mut self, input: ArgumentInput, origin: ArgumentOrigin) {
self.arguments.push(BoundArgument::new(input, origin));
}
pub fn with(mut self, input: ArgumentInput, origin: ArgumentOrigin) -> Self {
self.push(input, origin);
self
}
pub fn arguments(&self) -> &[BoundArgument] {
&self.arguments
}
}
impl From<Args> for CallInput {
fn from(args: Args) -> Self {
Self {
arguments: args
.into_vec()
.into_iter()
.enumerate()
.map(|(position, value)| {
BoundArgument::new(
ArgumentInput::Positional(value),
ArgumentOrigin::KernelPosition(position),
)
})
.collect(),
}
}
}
#[derive(Clone, Default)]
pub struct BoundCall {
arguments: Vec<BoundArgument>,
}
impl BoundCall {
pub fn arguments(&self) -> &[BoundArgument] {
&self.arguments
}
pub fn positional(&self) -> impl Iterator<Item = &BoundArgument> {
self.select(|input| matches!(input, ArgumentInput::Positional(_)))
}
pub fn named(&self) -> impl Iterator<Item = &BoundArgument> {
self.select(|input| matches!(input, ArgumentInput::Named { .. }))
}
pub fn receivers(&self) -> impl Iterator<Item = &BoundArgument> {
self.select(|input| matches!(input, ArgumentInput::Receiver(_)))
}
pub fn remainder(&self) -> impl Iterator<Item = &BoundArgument> {
self.select(|input| matches!(input, ArgumentInput::Remainder(_)))
}
pub fn unconsumed(&self) -> impl Iterator<Item = &BoundArgument> {
self.select(|input| matches!(input, ArgumentInput::Unconsumed(_)))
}
fn select(
&self,
predicate: impl Fn(&ArgumentInput) -> bool,
) -> impl Iterator<Item = &BoundArgument> {
self.arguments
.iter()
.filter(move |argument| predicate(&argument.input))
}
}
pub fn bind(input: CallInput) -> BoundCall {
BoundCall {
arguments: input.arguments,
}
}
#[cfg(test)]
mod tests {
use super::*;
use sim_kernel::testing::bare_cx;
fn origin(name: &str) -> ArgumentOrigin {
ArgumentOrigin::Guest(Symbol::new(name))
}
#[test]
fn duplicate_names_reach_policy_as_distinct_ordered_occurrences() {
let cx = bare_cx();
let first = cx.factory().symbol(Symbol::new("first")).unwrap();
let second = cx.factory().symbol(Symbol::new("second")).unwrap();
let name = Symbol::new("option");
let input = CallInput::new()
.with(
ArgumentInput::Named {
name: name.clone(),
value: first,
},
origin("call:4"),
)
.with(
ArgumentInput::Named {
name,
value: second,
},
origin("call:9"),
);
let bound = bind(input);
let origins = bound.named().map(BoundArgument::origin).collect::<Vec<_>>();
assert_eq!(origins, vec![&origin("call:4"), &origin("call:9")]);
}
#[test]
fn every_input_class_remains_visible_and_stably_ordered() {
let cx = bare_cx();
let make = || {
let value = |name| cx.factory().symbol(Symbol::new(name)).unwrap();
let positional = value("positional");
let receiver = value("receiver");
let remainder = value("remainder");
let unconsumed = value("unconsumed");
CallInput::from(Args::new(vec![positional]))
.with(ArgumentInput::Receiver(receiver), origin("receiver"))
.with(ArgumentInput::Remainder(remainder), origin("spread"))
.with(ArgumentInput::Unconsumed(unconsumed), origin("unknown"))
};
let project = |bound: BoundCall| {
bound
.arguments()
.iter()
.map(|argument| argument.origin().clone())
.collect::<Vec<_>>()
};
assert_eq!(project(bind(make())), project(bind(make())));
let bound = bind(make());
assert_eq!(bound.positional().count(), 1);
assert_eq!(bound.receivers().count(), 1);
assert_eq!(bound.remainder().count(), 1);
assert_eq!(bound.unconsumed().count(), 1);
}
}