1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use im_ternary_tree::TernaryTreeList;

use crate::primes::{Calcit, CalcitErr, CalcitItems};

pub fn new_set(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  let mut ys = rpds::HashTrieSet::new_sync();
  for x in xs {
    ys.insert_mut(x.to_owned());
  }
  Ok(Calcit::Set(ys))
}

pub fn call_include(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  match (xs.get(0), xs.get(1)) {
    (Some(Calcit::Set(xs)), Some(a)) => {
      let mut ys = xs.to_owned();
      ys.insert_mut(a.to_owned());
      Ok(Calcit::Set(ys))
    }
    (Some(a), _) => CalcitErr::err_str(format!("&include expect a set, but got: {a}")),
    (a, b) => CalcitErr::err_str(format!("invalid arguments for &include: {a:?} {b:?}")),
  }
}

pub fn call_exclude(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  match (xs.get(0), xs.get(1)) {
    (Some(Calcit::Set(xs)), Some(a)) => {
      let mut ys = xs.to_owned();
      ys.remove_mut(a);
      Ok(Calcit::Set(ys))
    }
    (Some(a), _) => CalcitErr::err_str(format!("&exclude expect a set, but got: {a}")),
    (a, b) => CalcitErr::err_str(format!("invalid arguments for &exclude: {a:?} {b:?}")),
  }
}
pub fn call_difference(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  match (xs.get(0), xs.get(1)) {
    (Some(Calcit::Set(a)), Some(Calcit::Set(b))) => {
      // rpds::HashTrieSetSync::difference has different semantics
      // https://docs.rs/im/12.2.0/im/struct.HashSet.html#method.difference
      let mut ys = a.to_owned();
      for item in b {
        ys.remove_mut(item);
      }
      Ok(Calcit::Set(ys))
    }
    (Some(a), Some(b)) => CalcitErr::err_str(format!("&difference expected 2 sets: {a} {b}")),
    (a, b) => CalcitErr::err_str(format!("&difference expected 2 arguments: {a:?} {b:?}")),
  }
}
pub fn call_union(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  match (xs.get(0), xs.get(1)) {
    (Some(Calcit::Set(a)), Some(Calcit::Set(b))) => {
      let mut c = a.to_owned();
      for x in b.iter() {
        c.insert_mut(x.to_owned());
      }
      Ok(Calcit::Set(c))
    }
    (Some(a), Some(b)) => CalcitErr::err_str(format!("&union expected 2 sets: {a} {b}")),
    (a, b) => CalcitErr::err_str(format!("&union expected 2 arguments: {a:?} {b:?}")),
  }
}
pub fn call_intersection(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  match (xs.get(0), xs.get(1)) {
    (Some(Calcit::Set(a)), Some(Calcit::Set(b))) => {
      let mut c: rpds::HashTrieSetSync<Calcit> = rpds::HashTrieSet::new_sync();
      for x in a.iter() {
        if b.contains(x) {
          c.insert_mut(x.to_owned())
        }
      }
      Ok(Calcit::Set(c))
    }
    (Some(a), Some(b)) => CalcitErr::err_str(format!("&set:intersection expected 2 sets: {a} {b}")),
    (a, b) => CalcitErr::err_str(format!("&set:intersection expected 2 arguments: {a:?} {b:?}")),
  }
}

/// turn hashset into list with a random order from internals
pub fn set_to_list(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  match xs.get(0) {
    Some(Calcit::Set(xs)) => {
      let mut ys: CalcitItems = TernaryTreeList::Empty;
      for x in xs {
        ys = ys.push_right(x.to_owned());
      }
      Ok(Calcit::List(ys))
    }
    Some(a) => CalcitErr::err_str(format!("&set:to-list expected a set: {a}")),
    None => CalcitErr::err_str("&set:to-list expected 1 argument, got none"),
  }
}

pub fn count(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  match xs.get(0) {
    Some(Calcit::Set(ys)) => Ok(Calcit::Number(ys.size() as f64)),
    Some(a) => CalcitErr::err_str(format!("set count expected a set, got: {a}")),
    None => CalcitErr::err_str("set count expected 1 argument"),
  }
}

pub fn empty_ques(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  match xs.get(0) {
    Some(Calcit::Set(ys)) => Ok(Calcit::Bool(ys.is_empty())),
    Some(a) => CalcitErr::err_str(format!("set empty? expected some set, got: {a}")),
    None => CalcitErr::err_str("set empty? expected 1 argument"),
  }
}

pub fn includes_ques(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  match (xs.get(0), xs.get(1)) {
    (Some(Calcit::Set(xs)), Some(a)) => Ok(Calcit::Bool(xs.contains(a))),
    (Some(a), ..) => CalcitErr::err_str(format!("sets `includes?` expected set, got: {a}")),
    (None, ..) => CalcitErr::err_str(format!("sets `includes?` expected 2 arguments, got: {xs:?}")),
  }
}

/// use builtin function since sets need to be handled specifically
pub fn destruct(xs: &CalcitItems) -> Result<Calcit, CalcitErr> {
  match xs.get(0) {
    Some(Calcit::Set(ys)) => match ys.iter().next() {
      // first element of a set might be random
      Some(y0) => {
        let mut zs = ys.to_owned();
        zs.remove_mut(y0);
        Ok(Calcit::List(vec![y0.to_owned(), Calcit::Set(zs)].into()))
      }
      None => Ok(Calcit::Nil),
    },
    Some(a) => CalcitErr::err_str(format!("&set:destruct expected a set, got: {a}")),
    None => CalcitErr::err_str("&set:destruct expected 1 argument"),
  }
}