use super::adapter::Adapter;
use super::node::Node;
use crate::func;
use crate::lang::args::should_append_stdin;
use crate::lang::error::Error;
use crate::lang::error::ErrorKind;
use crate::lang::label::Label;
use crate::lang::machine::Machine;
use crate::lang::node::pipe_from_nodes;
use crate::lang::parse;
use crate::value::Value;
use crate::value::keys::Keys;
use crate::value::list::List;
use crate::value::map::Map;
use crate::value::operation::Operation;
use crate::value::operation::fold_map_value;
use crate::value::operation::get_value;
use crate::value::operation::insert_map_value;
use crate::value::span::Span;
use crate::value::tracer::Segment;
use crate::value::tracer::Tracer;
use either::Either;
use std::borrow::Cow;
#[derive(Debug, Default)]
pub struct Eval<T> {
pub result: T,
#[doc(hidden)]
pub ops: Vec<Operation>,
}
impl<T: Into<Value>> From<T> for Eval<Value> {
fn from(value: T) -> Self {
Self {
result: value.into(),
ops: Vec::default(),
}
}
}
impl<T: Into<Keys>> From<T> for Eval<Keys> {
fn from(value: T) -> Self {
Self {
result: value.into(),
ops: Vec::default(),
}
}
}
pub(crate) fn eval(
adapter: &impl Adapter,
map: &mut Map,
span: Span,
node: Node,
) -> Result<Eval<Value>, Error> {
let mut machine = Machine::new(map, span);
eval_machine(adapter, &mut machine, node)
}
fn eval_machine(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
node: Node,
) -> Result<Eval<Value>, Error> {
let result = eval_node(adapter, machine, None, node)?;
Ok(Eval {
result: result.unwrap_or_default(),
ops: core::mem::take(&mut machine.ops),
})
}
pub(crate) fn eval_keys(
adapter: &impl Adapter,
map: &mut Map,
span: Span,
node: Node,
) -> Result<Option<Eval<Keys>>, Error> {
let mut machine = Machine::new(map, span);
let result = keys_from_node(adapter, &mut machine, node)?;
Ok(result.map(|result| Eval {
ops: machine.ops,
result,
}))
}
pub(crate) fn eval_node(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
node: Node,
) -> Result<Option<Value>, Error> {
match node {
Node::Bytes(s, n) => Ok(Some(eval_bytes(adapter, s, n))),
Node::Tag(s, n) => Ok(Some(eval_tag(adapter, s, n))),
Node::Label(..) => eval_context_label(adapter, machine, stdin, &node).map(Some),
Node::List(..) => eval_list(adapter, machine, stdin, node),
Node::Map(..) => eval_map(adapter, machine, stdin, node),
Node::Pipe(..) => eval_node_list(adapter, machine, stdin, node),
Node::Paren(..) => eval_node_list(adapter, machine, stdin, node),
Node::Bracket(..) => eval_node_list(adapter, machine, stdin, node),
Node::Semicolon(..) => eval_node_list(adapter, machine, stdin, node),
Node::And(..) => eval_node_list(adapter, machine, stdin, node),
Node::Or(..) => eval_node_list(adapter, machine, stdin, node),
Node::Thunk(..) => eval_node_list(adapter, machine, stdin, node),
Node::Comma(_) => Err(eval_error(adapter, machine, "unexpected comma")),
Node::Space(_) => Ok(None),
}
}
pub(crate) fn eval_node_list(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
mut node: Node,
) -> Result<Option<Value>, Error> {
let head = node.list().first().cloned();
if node.list().is_empty() {
return Ok(None);
}
if let Some(Node::Label(..)) = &head {
match eval_func(adapter, machine, stdin, node)? {
Either::Right(v) => return Ok(v),
Either::Left(n) => node = n,
}
}
if node.list().len() > 1 && !has_space(&node) {
if let Some(Node::List(..)) = &head {
return eval_list_getter(adapter, machine, stdin, node);
} else if let Some(Node::Map(..)) = &head {
return eval_map_getter(adapter, machine, stdin, node);
}
}
if node.list().len() > 1 && !has_pipe(&node) && (has_tag(&node) || has_space(&node)) {
return eval_concat(adapter, machine, stdin, &node);
}
if let Some(Node::Label(..)) = &head {
return eval_context_getter(adapter, machine, stdin, node);
}
let mut skip_to_pipe = false;
let mut stdin = stdin.cloned();
for node in node.into_list().unwrap_or_default() {
if skip_to_pipe {
match node {
Node::Semicolon(..) => skip_to_pipe = false,
Node::Pipe(..) => skip_to_pipe = false,
Node::Or(..) => skip_to_pipe = false,
_ => continue,
}
}
stdin = match node {
Node::Label(_, n) => {
return Err(eval_error_data(
adapter,
machine,
"unexpected label",
n.as_bytes(),
));
}
Node::Thunk(..) => {
return Err(thunk_error(adapter, machine));
}
Node::Comma(_) => {
return Err(eval_error(adapter, machine, "unexpected comma"));
}
Node::And(..) if stdin.as_ref().is_some_and(|v| !v.is_empty()) => {
eval_node_list(adapter, machine, None, node)?
}
Node::Or(..) if stdin.as_ref().is_none_or(Value::is_empty) => {
eval_node_list(adapter, machine, None, node)?
}
Node::And(..) => {
skip_to_pipe = true;
stdin
}
Node::Or(..) => {
skip_to_pipe = true;
stdin
}
Node::Bytes(s, n) => Some(eval_bytes(adapter, s, n)),
Node::Tag(s, n) => Some(eval_tag(adapter, s, n)),
Node::List(..) => eval_list(adapter, machine, stdin.as_ref(), node)?,
Node::Map(..) => eval_map(adapter, machine, stdin.as_ref(), node)?,
Node::Pipe(..) => eval_node_list(adapter, machine, stdin.as_ref(), node)?,
Node::Paren(..) => eval_node_list(adapter, machine, stdin.as_ref(), node)?,
Node::Bracket(..) => eval_node_list(adapter, machine, stdin.as_ref(), node)?,
Node::Semicolon(..) => eval_node_list(adapter, machine, None, node)?,
Node::Space(_) => stdin,
};
}
Ok(stdin)
}
fn eval_func(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
mut node: Node,
) -> Result<Either<Node, Option<Value>>, Error> {
let Some(head) = node.list().first().and_then(Node::as_label).cloned() else {
return Err(eval_error(adapter, machine, "expected label"));
};
let prefix = head.as_prefix();
if prefix == Some(b"fn") {
return match func::call(adapter, machine, stdin, node, head.as_name())? {
Either::Right(p) => Ok(Either::Right(Some(p))),
Either::Left(_) => Err(eval_error_data(
adapter,
machine,
"unknown func",
head.as_name(),
)),
};
}
if prefix.is_none() && !machine.map.contains_key(head.as_name()) {
match func::call(adapter, machine, stdin, node, head.as_name())? {
Either::Right(p) => return Ok(Either::Right(Some(p))),
Either::Left(l) => node = l,
}
}
if let Some(prefix) = prefix.filter(|p| !p.is_empty()) {
return Err(eval_error_data(adapter, machine, "unknown prefix", prefix));
}
Ok(Either::Left(node))
}
fn eval_bytes(adapter: &impl Adapter, span: Span, node: Vec<u8>) -> Value {
let tracer = Tracer::default()
.with_source(adapter.template_source().unwrap_or_default())
.with_span(span);
Value::from(node).with_tracer(tracer)
}
fn eval_tag(adapter: &impl Adapter, span: Span, node: Vec<u8>) -> Value {
eval_bytes(adapter, span, node).with_safe()
}
fn eval_list(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
node: Node,
) -> Result<Option<Value>, Error> {
let tracer = Tracer::default()
.with_source(adapter.template_source().unwrap_or_default())
.with_span(Span::from(&node));
let list = node
.into_list()
.unwrap_or_default()
.into_iter()
.filter_map(|n| match n {
Node::Bytes(s, n) => Some(Ok(Some(eval_bytes(adapter, s, n)))),
Node::Tag(s, n) => Some(Ok(Some(eval_tag(adapter, s, n)))),
Node::Label(..) => Some(eval_context_label(adapter, machine, stdin, &n).map(Some)),
Node::List(..) => Some(eval_list(adapter, machine, stdin, n)),
Node::Map(..) => Some(eval_map(adapter, machine, stdin, n)),
Node::Pipe(..) => Some(eval_node_list(adapter, machine, stdin, n)),
Node::Paren(..) => Some(eval_node_list(adapter, machine, stdin, n)),
Node::Bracket(..) => Some(eval_node_list(adapter, machine, stdin, n)),
Node::And(..) => Some(eval_node_list(adapter, machine, stdin, n)),
Node::Or(..) => Some(eval_node_list(adapter, machine, stdin, n)),
Node::Semicolon(..) => Some(eval_node_list(adapter, machine, stdin, n)),
Node::Thunk(..) => Some(eval_node_list(adapter, machine, stdin, n)),
Node::Space(_) => Some(Err(eval_error(adapter, machine, "unexpected space"))),
Node::Comma(_) => None,
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.map(Option::unwrap_or_default)
.collect::<List>();
Ok(Some(Value::from(list).with_tracer(tracer)))
}
fn eval_map(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
node: Node,
) -> Result<Option<Value>, Error> {
let tracer = Tracer::default()
.with_source(adapter.template_source().unwrap_or_default())
.with_span(Span::from(&node));
let list = node.into_list().unwrap_or_default();
let mut split = Vec::with_capacity(list.len());
let mut map = Map::default();
let mut key = None;
for node in list {
if let Some(l) = node.as_label() {
if let Some(p) = l.as_prefix().filter(|l| !l.is_empty()) {
if let Some(s) = l.as_suffix().filter(|l| !l.is_empty()) {
split.push(parse::parse(
adapter.template_source(),
Span::from(&node),
p,
)?);
split.push(parse::parse(
adapter.template_source(),
Span::from(&node),
s,
)?);
continue;
}
}
}
split.push(node);
}
for node in split {
match (core::mem::take(&mut key), node) {
(None, Node::Comma(..)) => {}
(None, n) => key = Some(n),
(Some(k), Node::Comma(..)) => map.fold_key(
keys_from_node_default(adapter, machine, k.clone())?.join(),
eval_node(adapter, machine, stdin, k)?,
),
(Some(k), n) => {
map.fold_key(
keys_from_node_default(adapter, machine, k.clone())?.join(),
eval_node(adapter, machine, stdin, n)?,
);
}
}
}
if let Some(k) = key {
match k {
Node::Bytes(..) | Node::Label(..) => map.fold_key(
keys_from_node_default(adapter, machine, k.clone())?.join(),
eval_node(adapter, machine, stdin, k)?,
),
_ => return Err(eval_error(adapter, machine, "bad map syntax")),
}
}
Ok(Some(Value::from(map).with_tracer(tracer)))
}
pub(crate) fn eval_concat(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
node: &Node,
) -> Result<Option<Value>, Error> {
let mut values = eval_spaced(adapter, machine, stdin, false, node)?;
if values.len() == 1 {
return Ok(values.remove(0));
}
let value = values.into_iter().flatten().collect::<List>();
let value = Value::from(value).with_meta("internal-format", "concat");
Ok(Some(value))
}
pub(crate) fn eval_spaced(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
stdin_append: bool,
node: &Node,
) -> Result<Vec<Option<Value>>, Error> {
let groups = group_spaced_nodes(node.list());
let mut values = groups
.into_iter()
.filter(|n| !n.is_empty())
.map(pipe_from_nodes)
.map(|n| eval_node_list(adapter, machine, stdin, n))
.collect::<Result<Vec<_>, _>>()?;
if stdin_append && stdin.is_some() && should_append_stdin(node) {
values.push(stdin.cloned());
}
Ok(values)
}
fn group_spaced_nodes(list: &[Node]) -> Vec<Vec<Node>> {
let mut groups = Vec::with_capacity(list.len());
let mut group = Vec::default();
let mut push = |group: &mut Vec<Node>| {
if !group.is_empty() {
groups.push(core::mem::take(group));
}
};
for node in list {
match node {
Node::Space(_) => {
push(&mut group);
}
node @ Node::Tag(..) => {
push(&mut group);
group.push(node.clone());
push(&mut group);
}
node => {
group.push(node.clone());
}
}
}
push(&mut group);
groups
}
fn eval_list_getter(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
mut node: Node,
) -> Result<Option<Value>, Error> {
let Some(literal) = node.try_remove(0) else {
return Ok(None);
};
let literal = eval_list(adapter, machine, stdin, literal)?;
eval_literal_getter(adapter, machine, literal.as_ref(), node)
}
fn eval_map_getter(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
mut node: Node,
) -> Result<Option<Value>, Error> {
let Some(literal) = node.try_remove(0) else {
return Ok(None);
};
let literal = eval_map(adapter, machine, stdin, literal)?;
eval_literal_getter(adapter, machine, literal.as_ref(), node)
}
fn eval_literal_getter(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
literal: Option<&Value>,
node: Node,
) -> Result<Option<Value>, Error> {
if node.list().is_empty() {
return Err(eval_error(adapter, machine, "missing keys"));
}
let node = node.into_list().unwrap_or_default();
let Some(keys) = keys_from_node_list(adapter, machine, node)? else {
return Ok(None);
};
Ok(match literal {
Some(l) => get_value(l, keys.as_slice()).cloned(),
None => get_value(&Value::default(), keys.as_slice()).cloned(),
})
}
pub(crate) fn eval_context_getter(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
node: Node,
) -> Result<Option<Value>, Error> {
let node = node.into_list().unwrap_or_default();
let Some(keys) = keys_from_node_list(adapter, machine, node)? else {
return Ok(None);
};
trace_getter(adapter, machine, stdin, keys).map(Some)
}
fn eval_context_label(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
node: &Node,
) -> Result<Value, Error> {
let Some(keys) = node.as_label().map(Label::as_keys) else {
return Err(eval_error(adapter, machine, "expected keys from label"));
};
trace_getter(adapter, machine, stdin, keys)
}
fn trace_getter(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
stdin: Option<&Value>,
mut keys: Keys,
) -> Result<Value, Error> {
let mut stdin = stdin.map(Cow::Borrowed);
if keys.is_placeholder() {
return Ok(stdin.map(Cow::into_owned).unwrap_or_default());
}
if keys.is_context_spread() {
return Ok(machine.map.clone().into());
}
if keys.has_leading_placeholder() {
keys = keys.without_first();
stdin = stdin
.map(Cow::into_owned)
.map(Value::into_map)
.map(Value::from)
.map(Cow::Owned);
}
let value = match stdin.as_deref() {
Some(Value::Map(stdin)) => adapter.value_get(stdin, &keys),
Some(Value::List(stdin)) => keys.as_usize().and_then(|k| stdin.get(k)).cloned(),
Some(Value::Bytes(_)) => None,
None => None,
};
let mut value = value
.or_else(|| adapter.value_get(machine.map, &keys))
.unwrap_or_default();
let tracer = value
.take_tracer()
.with_source(adapter.template_source().unwrap_or_default())
.with_keys(keys.clone());
let value = value.with_tracer(tracer);
match adapter.record_getter(&value) {
Err(e) => return Err(eval_error(adapter, machine, &e)),
Ok(Some(o)) => machine.ops.push(o),
Ok(None) => {}
}
Ok(value)
}
pub(crate) fn record_setter(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
keys: &Keys,
mut value: Value,
) -> Result<Value, Error> {
let tracer = value
.take_tracer()
.with_source(adapter.template_source().unwrap_or_default())
.with_segment(Segment::Setter)
.with_keys(keys.clone());
let value = value.with_tracer(tracer);
fold_map_value(machine.map, keys, value.clone());
if let Some(op) = adapter
.record_setter(&value)
.map_err(|e| eval_error(adapter, machine, &e))?
{
machine.ops.push(op);
}
Ok(value)
}
pub(crate) fn replace_operation(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
keys: &Keys,
prev: &Value,
next: &Value,
) -> Result<(), Error> {
debug_assert_eq!(prev.tracer(), next.tracer());
insert_map_value(machine.map, keys, next.clone());
let ops = adapter
.erase_operation(prev)
.map_err(|e| eval_error(adapter, machine, &e))?;
for op in ops {
adapter
.record_operation(&op.with_value(next.clone()))
.map_err(|e| eval_error(adapter, machine, &e))?;
}
Ok(())
}
pub(crate) fn keys_from_node(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
node: Node,
) -> Result<Option<Keys>, Error> {
match node {
Node::Space(_) => Err(eval_error(adapter, machine, "unexpected space")),
Node::Comma(_) => Err(eval_error(adapter, machine, "unexpected comma")),
Node::List(..) => Err(eval_error(adapter, machine, "unexpected list")),
Node::Map(..) => Err(eval_error(adapter, machine, "unexpected map")),
Node::Bytes(_, n) => Ok(Some(Keys::parse(n.as_slice()))),
Node::Tag(_, n) => Ok(Some(Keys::parse(n.as_slice()))),
Node::Label(_, n) => Ok(Some(n.as_keys())),
Node::Pipe(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
Node::Paren(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
Node::Bracket(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
Node::And(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
Node::Or(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
Node::Semicolon(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
Node::Thunk(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
}
}
pub(crate) fn keys_from_node_list(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
list: Vec<Node>,
) -> Result<Option<Keys>, Error> {
let node = list.into_iter().map(|n| match n {
Node::Space(_) => Err(eval_error(adapter, machine, "unexpected space")),
Node::List(..) => Err(eval_error(adapter, machine, "unexpected list")),
Node::Map(..) => Err(eval_error(adapter, machine, "unexpected map")),
Node::Thunk(..) => Err(eval_error(adapter, machine, "unexpected thunk")),
Node::Bytes(_, n) => Ok(Keys::parse(n.as_slice())),
Node::Label(_, n) => Ok(n.as_keys()),
_ if n.list().is_empty() => Err(eval_error(adapter, machine, "empty keys")),
_ => {
let mut e = eval_machine(adapter, machine, n)?;
machine.ops.append(&mut e.ops);
Ok(Keys::new(e.result.join()))
}
});
match Keys::try_from(node.collect::<Result<Vec<_>, _>>()?) {
Ok(keys) => Ok(Some(keys)),
Err(_) => Ok(None),
}
}
fn keys_from_node_default(
adapter: &impl Adapter,
machine: &mut Machine<'_>,
node: Node,
) -> Result<Keys, Error> {
Ok(keys_from_node(adapter, machine, node)?.unwrap_or_default())
}
fn has_space(node: &Node) -> bool {
node.list().iter().any(|n| matches!(n, Node::Space(..)))
}
fn has_tag(node: &Node) -> bool {
node.list().iter().any(|n| matches!(n, Node::Tag(..)))
}
fn has_pipe(node: &Node) -> bool {
node.list().iter().any(Node::is_pipe)
}
fn eval_error_data<T: Into<String>>(
adapter: &impl Adapter,
machine: &Machine<'_>,
message: T,
data: &[u8],
) -> Error {
eval_error(
adapter,
machine,
format!("{}: {}", message.into(), String::from_utf8_lossy(data)),
)
}
fn thunk_error(adapter: &impl Adapter, machine: &Machine<'_>) -> Error {
eval_error(
adapter,
machine,
"first-class functions not yet implemented",
)
}
fn eval_error<T: Into<String>>(adapter: &impl Adapter, machine: &Machine<'_>, message: T) -> Error {
Error {
kind: ErrorKind::Eval,
source: adapter.template_source().map(String::from),
span: machine.span,
message: message.into(),
}
}