clvmr/
core_ops.rs

1use crate::allocator::{Allocator, NodePtr, SExp};
2use crate::cost::Cost;
3use crate::error::{EvalErr, Result};
4use crate::op_utils::{first, get_args, nilp, rest};
5use crate::reduction::{Reduction, Response};
6
7const FIRST_COST: Cost = 30;
8const IF_COST: Cost = 33;
9// Cons cost lowered from 245. It only allocates a pair, which is small
10const CONS_COST: Cost = 50;
11// Rest cost lowered from 77 since it doesn't allocate anything and it should be
12// the same as first
13const REST_COST: Cost = 30;
14const LISTP_COST: Cost = 19;
15const EQ_BASE_COST: Cost = 117;
16const EQ_COST_PER_BYTE: Cost = 1;
17
18pub fn op_if(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response {
19    let [cond, affirmative, negative] = get_args::<3>(a, input, "i")?;
20    let chosen_node = if nilp(a, cond) { negative } else { affirmative };
21    Ok(Reduction(IF_COST, chosen_node))
22}
23
24pub fn op_cons(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response {
25    let [n1, n2] = get_args::<2>(a, input, "c")?;
26    let r = a.new_pair(n1, n2)?;
27    Ok(Reduction(CONS_COST, r))
28}
29
30pub fn op_first(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response {
31    let [n] = get_args::<1>(a, input, "f")?;
32    Ok(Reduction(FIRST_COST, first(a, n)?))
33}
34
35pub fn op_rest(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response {
36    let [n] = get_args::<1>(a, input, "r")?;
37    Ok(Reduction(REST_COST, rest(a, n)?))
38}
39
40pub fn op_listp(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response {
41    let [n] = get_args::<1>(a, input, "l")?;
42    match a.sexp(n) {
43        SExp::Pair(_, _) => Ok(Reduction(LISTP_COST, a.one())),
44        _ => Ok(Reduction(LISTP_COST, a.nil())),
45    }
46}
47
48pub fn op_raise(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response {
49    // if given a single argument we should raise the single argument rather
50    // than the full list of arguments. brun also used to behave this way.
51    // if the single argument here is a pair then don't throw it unwrapped
52    // as it'd potentially look the same as a throw of multiple arguments.
53    let throw_value = if let Ok([value]) = get_args::<1>(a, input, "") {
54        match a.sexp(value) {
55            SExp::Atom => value,
56            _ => input,
57        }
58    } else {
59        input
60    };
61
62    Err(EvalErr::Raise(throw_value))
63}
64
65fn ensure_atom(a: &Allocator, n: NodePtr, op: &str) -> Result<()> {
66    if let SExp::Atom = a.sexp(n) {
67        Ok(())
68    } else {
69        Err(EvalErr::InvalidOpArg(n, format!("{op} used on list")))?
70    }
71}
72
73pub fn op_eq(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response {
74    let [s0, s1] = get_args::<2>(a, input, "=")?;
75    ensure_atom(a, s0, "=")?;
76    ensure_atom(a, s1, "=")?;
77    let eq = a.atom_eq(s0, s1);
78    let cost = EQ_BASE_COST + (a.atom_len(s0) as Cost + a.atom_len(s1) as Cost) * EQ_COST_PER_BYTE;
79    Ok(Reduction(cost, if eq { a.one() } else { a.nil() }))
80}