use crate::lang::adapter::Adapter;
use crate::lang::arity::parse_func_name;
use crate::lang::error::Error;
use crate::lang::error::ErrorKind;
use crate::lang::eval::eval_spaced;
use crate::lang::machine::Machine;
use crate::lang::node::Node;
use crate::value::Value;
use crate::value::number::Number;
use crate::value::span::Span;
pub(crate) fn eval_args(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
mut node: Node,
) -> Result<Vec<Option<Value>>, Error> {
let Some(Node::Label(..)) = node.try_remove(0) else {
return Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: "missing function name".into(),
});
};
if !matches!(node.try_remove(0), None | Some(Node::Space(..))) {
return Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: "expected space after function name".into(),
});
}
eval_spaced(adapter, machine, stdin, true, &node)
}
pub(crate) fn eval_unary_arg(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
node: Node,
) -> Result<Option<Value>, Error> {
let len = node.list().len();
let std = stdin.is_some() && should_append_stdin(&node);
if len == 0 || len > 3 || len > 1 && std || len == 1 && !std {
return Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: "expected exactly 1 argument".into(),
});
}
Ok(eval_args(adapter, machine, stdin, node)?.pop().flatten())
}
pub(crate) fn eval_numeric_args(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
node: Node,
) -> Result<Option<Vec<Number>>, Error> {
Ok(Some(
eval_args(adapter, machine, stdin, node)?
.into_iter()
.map(|v| v.and_then(|v| v.as_number()).unwrap_or_default())
.collect(),
))
}
pub(crate) fn eval_string_args(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
node: Node,
) -> Result<Option<Vec<String>>, Error> {
Ok(eval_args(adapter, machine, stdin, node)?
.into_iter()
.map(|v| v.and_then(Value::into_string))
.collect())
}
pub(crate) fn take_thunk_arg(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
node: &mut Node,
) -> Result<Node, Error> {
let Some(Node::Space(..)) = node.try_remove(1) else {
return Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: format!(
"missing space after function name: {}",
parse_func_name(adapter, machine, node)?
),
});
};
let Some(arg) = node.try_remove(1) else {
return Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: format!(
"expects an argument: {}",
parse_func_name(adapter, machine, node)?
),
});
};
if !matches!(arg, Node::Thunk(..)) {
return Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: format!(
"expects a function as first argument: {}",
parse_func_name(adapter, machine, node)?
),
});
}
into_thunk_arg(adapter, machine, node, arg)
}
pub(crate) fn into_thunk_arg(
adapter: &impl Adapter,
machine: &Machine<'_>,
node: &Node,
arg: Node,
) -> Result<Node, Error> {
match arg {
Node::Thunk(..) => Ok(arg),
Node::Bytes(..) | Node::Label(..) | Node::List(..) | Node::Map(..) | Node::Paren(..) => {
Ok(Node::Thunk(Span::from(&arg), vec![arg]))
}
_ => Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: format!(
"function expects an argument: {}",
parse_func_name(adapter, machine, node)?
),
}),
}
}
pub(crate) fn thunk_as_pipe(
adapter: &impl Adapter,
machine: &Machine<'_>,
node: Node,
) -> Result<Node, Error> {
let span = Span::from(&node);
if let Node::Thunk(..) = node {
if let Some(list) = node.into_list() {
return Ok(Node::Pipe(span, list));
}
}
Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: "expected thunk for pipe".into(),
})
}
pub(crate) fn should_append_stdin(list: &Node) -> bool {
matches!(list, Node::Pipe(..) | Node::Thunk(..)) && !has_placeholders(list)
}
fn has_placeholders(list: &Node) -> bool {
list.list().iter().any(|n| match n {
Node::Label(_, n) if n.as_bytes() == b"_" => true,
Node::Label(_, n) if n.as_bytes().starts_with(b"_.") => true,
Node::Label(_, n) if n.as_bytes().starts_with(b"_[") => true,
Node::Label(..) => false,
Node::Tag(..) => false,
Node::Bytes(..) => false,
Node::Space(..) => false,
Node::Comma(..) => false,
Node::Thunk(..) => false,
Node::And(..) => has_placeholders(n),
Node::List(..) => has_placeholders(n),
Node::Map(..) => has_placeholders(n),
Node::Or(..) => has_placeholders(n),
Node::Pipe(..) => has_placeholders(n),
Node::Paren(..) => has_placeholders(n),
Node::Bracket(..) => has_placeholders(n),
Node::Semicolon(..) => has_placeholders(n),
})
}
pub(crate) fn arg_into_string(value: Option<Value>) -> Option<String> {
value.map(|v| String::from_utf8_lossy(&v.join()).into_owned())
}