Skip to main content

pipa/lang/
eval.rs

1// SPDX-FileCopyrightText: Copyright 2026 olav@occy.org
2// SPDX-License-Identifier: MPL-2.0
3
4//! A container for the results of an evaluation.
5
6use super::adapter::Adapter;
7use super::node::Node;
8use crate::func;
9use crate::lang::args::should_append_stdin;
10use crate::lang::error::Error;
11use crate::lang::error::ErrorKind;
12use crate::lang::label::Label;
13use crate::lang::machine::Machine;
14use crate::lang::node::pipe_from_nodes;
15use crate::lang::parse;
16use crate::value::Value;
17use crate::value::keys::Keys;
18use crate::value::list::List;
19use crate::value::map::Map;
20use crate::value::operation::Operation;
21use crate::value::operation::fold_map_value;
22use crate::value::operation::get_value;
23use crate::value::operation::insert_map_value;
24use crate::value::span::Span;
25use crate::value::tracer::Segment;
26use crate::value::tracer::Tracer;
27use either::Either;
28use std::borrow::Cow;
29
30#[derive(Debug, Default)]
31pub struct Eval<T> {
32    pub result: T,
33    #[doc(hidden)]
34    pub ops: Vec<Operation>,
35}
36
37impl<T: Into<Value>> From<T> for Eval<Value> {
38    fn from(value: T) -> Self {
39        Self {
40            result: value.into(),
41            ops: Vec::default(),
42        }
43    }
44}
45
46impl<T: Into<Keys>> From<T> for Eval<Keys> {
47    fn from(value: T) -> Self {
48        Self {
49            result: value.into(),
50            ops: Vec::default(),
51        }
52    }
53}
54
55pub(crate) fn eval(
56    adapter: &impl Adapter,
57    map: &mut Map,
58    span: Span,
59    node: Node,
60) -> Result<Eval<Value>, Error> {
61    let mut machine = Machine::new(map, span);
62
63    eval_machine(adapter, &mut machine, node)
64}
65
66fn eval_machine(
67    adapter: &impl Adapter,
68    machine: &mut Machine<'_>,
69    node: Node,
70) -> Result<Eval<Value>, Error> {
71    let result = eval_node(adapter, machine, None, node)?;
72
73    Ok(Eval {
74        result: result.unwrap_or_default(),
75        ops: core::mem::take(&mut machine.ops),
76    })
77}
78
79pub(crate) fn eval_keys(
80    adapter: &impl Adapter,
81    map: &mut Map,
82    span: Span,
83    node: Node,
84) -> Result<Option<Eval<Keys>>, Error> {
85    let mut machine = Machine::new(map, span);
86    let result = keys_from_node(adapter, &mut machine, node)?;
87
88    Ok(result.map(|result| Eval {
89        ops: machine.ops,
90        result,
91    }))
92}
93
94pub(crate) fn eval_node(
95    adapter: &impl Adapter,
96    machine: &mut Machine<'_>,
97    stdin: Option<&Value>,
98    node: Node,
99) -> Result<Option<Value>, Error> {
100    match node {
101        Node::Bytes(s, n) => Ok(Some(eval_bytes(adapter, s, n))),
102        Node::Tag(s, n) => Ok(Some(eval_tag(adapter, s, n))),
103        Node::Label(..) => eval_context_label(adapter, machine, stdin, &node).map(Some),
104        Node::List(..) => eval_list(adapter, machine, stdin, node),
105        Node::Map(..) => eval_map(adapter, machine, stdin, node),
106        Node::Pipe(..) => eval_node_list(adapter, machine, stdin, node),
107        Node::Paren(..) => eval_node_list(adapter, machine, stdin, node),
108        Node::Bracket(..) => eval_node_list(adapter, machine, stdin, node),
109        Node::Semicolon(..) => eval_node_list(adapter, machine, stdin, node),
110        Node::And(..) => eval_node_list(adapter, machine, stdin, node),
111        Node::Or(..) => eval_node_list(adapter, machine, stdin, node),
112        Node::Thunk(..) => eval_node_list(adapter, machine, stdin, node),
113        Node::Comma(_) => Err(eval_error(adapter, machine, "unexpected comma")),
114        Node::Space(_) => Ok(None),
115    }
116}
117
118pub(crate) fn eval_node_list(
119    adapter: &impl Adapter,
120    machine: &mut Machine<'_>,
121    stdin: Option<&Value>,
122    mut node: Node,
123) -> Result<Option<Value>, Error> {
124    let head = node.list().first().cloned();
125
126    if node.list().is_empty() {
127        return Ok(None);
128    }
129
130    if let Some(Node::Label(..)) = &head {
131        match eval_func(adapter, machine, stdin, node)? {
132            Either::Right(v) => return Ok(v),
133            Either::Left(n) => node = n,
134        }
135    }
136
137    if node.list().len() > 1 && !has_space(&node) {
138        if let Some(Node::List(..)) = &head {
139            return eval_list_getter(adapter, machine, stdin, node);
140        } else if let Some(Node::Map(..)) = &head {
141            return eval_map_getter(adapter, machine, stdin, node);
142        }
143    }
144
145    if node.list().len() > 1 && !has_pipe(&node) && (has_tag(&node) || has_space(&node)) {
146        return eval_concat(adapter, machine, stdin, &node);
147    }
148
149    if let Some(Node::Label(..)) = &head {
150        return eval_context_getter(adapter, machine, stdin, node);
151    }
152
153    let mut skip_to_pipe = false;
154    let mut stdin = stdin.cloned();
155
156    for node in node.into_list().unwrap_or_default() {
157        if skip_to_pipe {
158            match node {
159                Node::Semicolon(..) => skip_to_pipe = false,
160                Node::Pipe(..) => skip_to_pipe = false,
161                Node::Or(..) => skip_to_pipe = false,
162                _ => continue,
163            }
164        }
165
166        stdin = match node {
167            Node::Label(_, n) => {
168                return Err(eval_error_data(
169                    adapter,
170                    machine,
171                    "unexpected label",
172                    n.as_bytes(),
173                ));
174            }
175            Node::Thunk(..) => {
176                return Err(thunk_error(adapter, machine));
177            }
178            Node::Comma(_) => {
179                return Err(eval_error(adapter, machine, "unexpected comma"));
180            }
181            Node::And(..) if stdin.as_ref().is_some_and(|v| !v.is_empty()) => {
182                eval_node_list(adapter, machine, None, node)?
183            }
184            Node::Or(..) if stdin.as_ref().is_none_or(Value::is_empty) => {
185                eval_node_list(adapter, machine, None, node)?
186            }
187            Node::And(..) => {
188                skip_to_pipe = true;
189                stdin
190            }
191            Node::Or(..) => {
192                skip_to_pipe = true;
193                stdin
194            }
195            Node::Bytes(s, n) => Some(eval_bytes(adapter, s, n)),
196            Node::Tag(s, n) => Some(eval_tag(adapter, s, n)),
197            Node::List(..) => eval_list(adapter, machine, stdin.as_ref(), node)?,
198            Node::Map(..) => eval_map(adapter, machine, stdin.as_ref(), node)?,
199            Node::Pipe(..) => eval_node_list(adapter, machine, stdin.as_ref(), node)?,
200            Node::Paren(..) => eval_node_list(adapter, machine, stdin.as_ref(), node)?,
201            Node::Bracket(..) => eval_node_list(adapter, machine, stdin.as_ref(), node)?,
202            Node::Semicolon(..) => eval_node_list(adapter, machine, None, node)?,
203            Node::Space(_) => stdin,
204        };
205    }
206
207    Ok(stdin)
208}
209
210fn eval_func(
211    adapter: &impl Adapter,
212    machine: &mut Machine<'_>,
213    stdin: Option<&Value>,
214    mut node: Node,
215) -> Result<Either<Node, Option<Value>>, Error> {
216    let Some(head) = node.list().first().and_then(Node::as_label).cloned() else {
217        return Err(eval_error(adapter, machine, "expected label"));
218    };
219
220    let prefix = head.as_prefix();
221
222    if prefix == Some(b"fn") {
223        return match func::call(adapter, machine, stdin, node, head.as_name())? {
224            Either::Right(p) => Ok(Either::Right(Some(p))),
225            Either::Left(_) => Err(eval_error_data(
226                adapter,
227                machine,
228                "unknown func",
229                head.as_name(),
230            )),
231        };
232    }
233
234    if prefix.is_none() && !machine.map.contains_key(head.as_name()) {
235        match func::call(adapter, machine, stdin, node, head.as_name())? {
236            Either::Right(p) => return Ok(Either::Right(Some(p))),
237            Either::Left(l) => node = l,
238        }
239    }
240
241    if let Some(prefix) = prefix.filter(|p| !p.is_empty()) {
242        return Err(eval_error_data(adapter, machine, "unknown prefix", prefix));
243    }
244
245    Ok(Either::Left(node))
246}
247
248fn eval_bytes(adapter: &impl Adapter, span: Span, node: Vec<u8>) -> Value {
249    let tracer = Tracer::default()
250        .with_source(adapter.template_source().unwrap_or_default())
251        .with_span(span);
252
253    Value::from(node).with_tracer(tracer)
254}
255
256fn eval_tag(adapter: &impl Adapter, span: Span, node: Vec<u8>) -> Value {
257    eval_bytes(adapter, span, node).with_safe()
258}
259
260fn eval_list(
261    adapter: &impl Adapter,
262    machine: &mut Machine<'_>,
263    stdin: Option<&Value>,
264    node: Node,
265) -> Result<Option<Value>, Error> {
266    let tracer = Tracer::default()
267        .with_source(adapter.template_source().unwrap_or_default())
268        .with_span(Span::from(&node));
269
270    let list = node
271        .into_list()
272        .unwrap_or_default()
273        .into_iter()
274        .filter_map(|n| match n {
275            Node::Bytes(s, n) => Some(Ok(Some(eval_bytes(adapter, s, n)))),
276            Node::Tag(s, n) => Some(Ok(Some(eval_tag(adapter, s, n)))),
277            Node::Label(..) => Some(eval_context_label(adapter, machine, stdin, &n).map(Some)),
278            Node::List(..) => Some(eval_list(adapter, machine, stdin, n)),
279            Node::Map(..) => Some(eval_map(adapter, machine, stdin, n)),
280            Node::Pipe(..) => Some(eval_node_list(adapter, machine, stdin, n)),
281            Node::Paren(..) => Some(eval_node_list(adapter, machine, stdin, n)),
282            Node::Bracket(..) => Some(eval_node_list(adapter, machine, stdin, n)),
283            Node::And(..) => Some(eval_node_list(adapter, machine, stdin, n)),
284            Node::Or(..) => Some(eval_node_list(adapter, machine, stdin, n)),
285            Node::Semicolon(..) => Some(eval_node_list(adapter, machine, stdin, n)),
286            Node::Thunk(..) => Some(eval_node_list(adapter, machine, stdin, n)),
287            Node::Space(_) => Some(Err(eval_error(adapter, machine, "unexpected space"))),
288            Node::Comma(_) => None,
289        })
290        .collect::<Result<Vec<_>, _>>()?
291        .into_iter()
292        .map(Option::unwrap_or_default)
293        .collect::<List>();
294
295    Ok(Some(Value::from(list).with_tracer(tracer)))
296}
297
298fn eval_map(
299    adapter: &impl Adapter,
300    machine: &mut Machine<'_>,
301    stdin: Option<&Value>,
302    node: Node,
303) -> Result<Option<Value>, Error> {
304    let tracer = Tracer::default()
305        .with_source(adapter.template_source().unwrap_or_default())
306        .with_span(Span::from(&node));
307
308    let list = node.into_list().unwrap_or_default();
309    let mut split = Vec::with_capacity(list.len());
310    let mut map = Map::default();
311    let mut key = None;
312
313    // Split colon suffixed labels: `{a:1}` => `{a 1}`.
314    for node in list {
315        if let Some(l) = node.as_label() {
316            if let Some(p) = l.as_prefix().filter(|l| !l.is_empty()) {
317                if let Some(s) = l.as_suffix().filter(|l| !l.is_empty()) {
318                    split.push(parse::parse(
319                        adapter.template_source(),
320                        Span::from(&node),
321                        p,
322                    )?);
323                    split.push(parse::parse(
324                        adapter.template_source(),
325                        Span::from(&node),
326                        s,
327                    )?);
328                    continue;
329                }
330            }
331        }
332        split.push(node);
333    }
334
335    // Expand comma suffixed shorthands: `{a,b}` => `{a a b}`.
336    for node in split {
337        match (core::mem::take(&mut key), node) {
338            (None, Node::Comma(..)) => {}
339            (None, n) => key = Some(n),
340            (Some(k), Node::Comma(..)) => map.fold_key(
341                keys_from_node_default(adapter, machine, k.clone())?.join(),
342                eval_node(adapter, machine, stdin, k)?,
343            ),
344            (Some(k), n) => {
345                map.fold_key(
346                    keys_from_node_default(adapter, machine, k.clone())?.join(),
347                    eval_node(adapter, machine, stdin, n)?,
348                );
349            }
350        }
351    }
352
353    // Expand non-comma shorthands: `{a a b}` => `{a a b b}`.
354    if let Some(k) = key {
355        match k {
356            Node::Bytes(..) | Node::Label(..) => map.fold_key(
357                keys_from_node_default(adapter, machine, k.clone())?.join(),
358                eval_node(adapter, machine, stdin, k)?,
359            ),
360            _ => return Err(eval_error(adapter, machine, "bad map syntax")),
361        }
362    }
363
364    Ok(Some(Value::from(map).with_tracer(tracer)))
365}
366
367pub(crate) fn eval_concat(
368    adapter: &impl Adapter,
369    machine: &mut Machine<'_>,
370    stdin: Option<&Value>,
371    node: &Node,
372) -> Result<Option<Value>, Error> {
373    let mut values = eval_spaced(adapter, machine, stdin, false, node)?;
374
375    if values.len() == 1 {
376        return Ok(values.remove(0));
377    }
378
379    let value = values.into_iter().flatten().collect::<List>();
380    let value = Value::from(value).with_meta("internal-format", "concat");
381
382    Ok(Some(value))
383}
384
385pub(crate) fn eval_spaced(
386    adapter: &impl Adapter,
387    machine: &mut Machine<'_>,
388    stdin: Option<&Value>,
389    stdin_append: bool,
390    node: &Node,
391) -> Result<Vec<Option<Value>>, Error> {
392    let groups = group_spaced_nodes(node.list());
393
394    let mut values = groups
395        .into_iter()
396        .filter(|n| !n.is_empty())
397        .map(pipe_from_nodes)
398        .map(|n| eval_node_list(adapter, machine, stdin, n))
399        .collect::<Result<Vec<_>, _>>()?;
400
401    if stdin_append && stdin.is_some() && should_append_stdin(node) {
402        values.push(stdin.cloned());
403    }
404
405    Ok(values)
406}
407
408fn group_spaced_nodes(list: &[Node]) -> Vec<Vec<Node>> {
409    let mut groups = Vec::with_capacity(list.len());
410    let mut group = Vec::default();
411
412    let mut push = |group: &mut Vec<Node>| {
413        if !group.is_empty() {
414            groups.push(core::mem::take(group));
415        }
416    };
417
418    for node in list {
419        match node {
420            Node::Space(_) => {
421                push(&mut group);
422            }
423            node @ Node::Tag(..) => {
424                push(&mut group);
425                group.push(node.clone());
426                push(&mut group);
427            }
428            node => {
429                group.push(node.clone());
430            }
431        }
432    }
433
434    push(&mut group);
435    groups
436}
437
438fn eval_list_getter(
439    adapter: &impl Adapter,
440    machine: &mut Machine<'_>,
441    stdin: Option<&Value>,
442    mut node: Node,
443) -> Result<Option<Value>, Error> {
444    let Some(literal) = node.try_remove(0) else {
445        return Ok(None);
446    };
447
448    let literal = eval_list(adapter, machine, stdin, literal)?;
449    eval_literal_getter(adapter, machine, literal.as_ref(), node)
450}
451
452fn eval_map_getter(
453    adapter: &impl Adapter,
454    machine: &mut Machine<'_>,
455    stdin: Option<&Value>,
456    mut node: Node,
457) -> Result<Option<Value>, Error> {
458    let Some(literal) = node.try_remove(0) else {
459        return Ok(None);
460    };
461
462    let literal = eval_map(adapter, machine, stdin, literal)?;
463    eval_literal_getter(adapter, machine, literal.as_ref(), node)
464}
465
466fn eval_literal_getter(
467    adapter: &impl Adapter,
468    machine: &mut Machine<'_>,
469    literal: Option<&Value>,
470    node: Node,
471) -> Result<Option<Value>, Error> {
472    if node.list().is_empty() {
473        return Err(eval_error(adapter, machine, "missing keys"));
474    }
475
476    let node = node.into_list().unwrap_or_default();
477
478    let Some(keys) = keys_from_node_list(adapter, machine, node)? else {
479        return Ok(None);
480    };
481
482    Ok(match literal {
483        Some(l) => get_value(l, keys.as_slice()).cloned(),
484        None => get_value(&Value::default(), keys.as_slice()).cloned(),
485    })
486}
487
488pub(crate) fn eval_context_getter(
489    adapter: &impl Adapter,
490    machine: &mut Machine<'_>,
491    stdin: Option<&Value>,
492    node: Node,
493) -> Result<Option<Value>, Error> {
494    let node = node.into_list().unwrap_or_default();
495
496    let Some(keys) = keys_from_node_list(adapter, machine, node)? else {
497        return Ok(None);
498    };
499
500    trace_getter(adapter, machine, stdin, keys).map(Some)
501}
502
503fn eval_context_label(
504    adapter: &impl Adapter,
505    machine: &mut Machine<'_>,
506    stdin: Option<&Value>,
507    node: &Node,
508) -> Result<Value, Error> {
509    let Some(keys) = node.as_label().map(Label::as_keys) else {
510        return Err(eval_error(adapter, machine, "expected keys from label"));
511    };
512
513    trace_getter(adapter, machine, stdin, keys)
514}
515
516fn trace_getter(
517    adapter: &impl Adapter,
518    machine: &mut Machine<'_>,
519    stdin: Option<&Value>,
520    mut keys: Keys,
521) -> Result<Value, Error> {
522    let mut stdin = stdin.map(Cow::Borrowed);
523
524    if keys.is_placeholder() {
525        return Ok(stdin.map(Cow::into_owned).unwrap_or_default());
526    }
527
528    if keys.is_context_spread() {
529        return Ok(machine.map.clone().into());
530    }
531
532    if keys.has_leading_placeholder() {
533        keys = keys.without_first();
534        stdin = stdin
535            .map(Cow::into_owned)
536            .map(Value::into_map)
537            .map(Value::from)
538            .map(Cow::Owned);
539    }
540
541    let value = match stdin.as_deref() {
542        Some(Value::Map(stdin)) => adapter.value_get(stdin, &keys),
543        Some(Value::List(stdin)) => keys.as_usize().and_then(|k| stdin.get(k)).cloned(),
544        Some(Value::Bytes(_)) => None,
545        None => None,
546    };
547
548    let mut value = value
549        .or_else(|| adapter.value_get(machine.map, &keys))
550        .unwrap_or_default();
551
552    let tracer = value
553        .take_tracer()
554        .with_source(adapter.template_source().unwrap_or_default())
555        .with_keys(keys.clone());
556    let value = value.with_tracer(tracer);
557
558    match adapter.record_getter(&value) {
559        Err(e) => return Err(eval_error(adapter, machine, &e)),
560        Ok(Some(o)) => machine.ops.push(o),
561        Ok(None) => {}
562    }
563
564    Ok(value)
565}
566
567pub(crate) fn record_setter(
568    adapter: &impl Adapter,
569    machine: &mut Machine<'_>,
570    keys: &Keys,
571    mut value: Value,
572) -> Result<Value, Error> {
573    let tracer = value
574        .take_tracer()
575        .with_source(adapter.template_source().unwrap_or_default())
576        .with_segment(Segment::Setter)
577        .with_keys(keys.clone());
578
579    let value = value.with_tracer(tracer);
580    fold_map_value(machine.map, keys, value.clone());
581
582    if let Some(op) = adapter
583        .record_setter(&value)
584        .map_err(|e| eval_error(adapter, machine, &e))?
585    {
586        machine.ops.push(op);
587    }
588
589    Ok(value)
590}
591
592pub(crate) fn replace_operation(
593    adapter: &impl Adapter,
594    machine: &mut Machine<'_>,
595    keys: &Keys,
596    prev: &Value,
597    next: &Value,
598) -> Result<(), Error> {
599    debug_assert_eq!(prev.tracer(), next.tracer());
600    insert_map_value(machine.map, keys, next.clone());
601
602    let ops = adapter
603        .erase_operation(prev)
604        .map_err(|e| eval_error(adapter, machine, &e))?;
605
606    for op in ops {
607        adapter
608            .record_operation(&op.with_value(next.clone()))
609            .map_err(|e| eval_error(adapter, machine, &e))?;
610    }
611
612    Ok(())
613}
614
615pub(crate) fn keys_from_node(
616    adapter: &impl Adapter,
617    machine: &mut Machine<'_>,
618    node: Node,
619) -> Result<Option<Keys>, Error> {
620    match node {
621        Node::Space(_) => Err(eval_error(adapter, machine, "unexpected space")),
622        Node::Comma(_) => Err(eval_error(adapter, machine, "unexpected comma")),
623        Node::List(..) => Err(eval_error(adapter, machine, "unexpected list")),
624        Node::Map(..) => Err(eval_error(adapter, machine, "unexpected map")),
625        Node::Bytes(_, n) => Ok(Some(Keys::parse(n.as_slice()))),
626        Node::Tag(_, n) => Ok(Some(Keys::parse(n.as_slice()))),
627        Node::Label(_, n) => Ok(Some(n.as_keys())),
628        Node::Pipe(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
629        Node::Paren(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
630        Node::Bracket(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
631        Node::And(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
632        Node::Or(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
633        Node::Semicolon(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
634        Node::Thunk(_, n) => Ok(keys_from_node_list(adapter, machine, n)?),
635    }
636}
637
638pub(crate) fn keys_from_node_list(
639    adapter: &impl Adapter,
640    machine: &mut Machine<'_>,
641    list: Vec<Node>,
642) -> Result<Option<Keys>, Error> {
643    let node = list.into_iter().map(|n| match n {
644        Node::Space(_) => Err(eval_error(adapter, machine, "unexpected space")),
645        Node::List(..) => Err(eval_error(adapter, machine, "unexpected list")),
646        Node::Map(..) => Err(eval_error(adapter, machine, "unexpected map")),
647        Node::Thunk(..) => Err(eval_error(adapter, machine, "unexpected thunk")),
648        Node::Bytes(_, n) => Ok(Keys::parse(n.as_slice())),
649        Node::Label(_, n) => Ok(n.as_keys()),
650        _ if n.list().is_empty() => Err(eval_error(adapter, machine, "empty keys")),
651        _ => {
652            let mut e = eval_machine(adapter, machine, n)?;
653            machine.ops.append(&mut e.ops);
654            Ok(Keys::new(e.result.join()))
655        }
656    });
657
658    match Keys::try_from(node.collect::<Result<Vec<_>, _>>()?) {
659        Ok(keys) => Ok(Some(keys)),
660        Err(_) => Ok(None),
661    }
662}
663
664fn keys_from_node_default(
665    adapter: &impl Adapter,
666    machine: &mut Machine<'_>,
667    node: Node,
668) -> Result<Keys, Error> {
669    Ok(keys_from_node(adapter, machine, node)?.unwrap_or_default())
670}
671
672fn has_space(node: &Node) -> bool {
673    node.list().iter().any(|n| matches!(n, Node::Space(..)))
674}
675
676fn has_tag(node: &Node) -> bool {
677    node.list().iter().any(|n| matches!(n, Node::Tag(..)))
678}
679
680fn has_pipe(node: &Node) -> bool {
681    node.list().iter().any(Node::is_pipe)
682}
683
684fn eval_error_data<T: Into<String>>(
685    adapter: &impl Adapter,
686    machine: &Machine<'_>,
687    message: T,
688    data: &[u8],
689) -> Error {
690    eval_error(
691        adapter,
692        machine,
693        format!("{}: {}", message.into(), String::from_utf8_lossy(data)),
694    )
695}
696
697fn thunk_error(adapter: &impl Adapter, machine: &Machine<'_>) -> Error {
698    eval_error(
699        adapter,
700        machine,
701        "first-class functions not yet implemented",
702    )
703}
704
705fn eval_error<T: Into<String>>(adapter: &impl Adapter, machine: &Machine<'_>, message: T) -> Error {
706    Error {
707        kind: ErrorKind::Eval,
708        source: adapter.template_source().map(String::from),
709        span: machine.span,
710        message: message.into(),
711    }
712}