yatima_core/prim/
bytes.rs

1use num_bigint::BigUint;
2use sp_ipld::Ipld;
3use sp_std::{
4  borrow::ToOwned,
5  fmt,
6  vec::Vec,
7};
8
9use alloc::string::String;
10
11use crate::{
12  defs,
13  ipld_error::IpldError,
14  literal::Literal,
15  parse,
16  prim::bits,
17  term::Term,
18  yatima,
19};
20
21use core::convert::TryFrom;
22
23/// Primitive byte operations
24#[derive(PartialEq, Eq, Clone, Copy, Debug)]
25pub enum BytesOp {
26  Cons,
27  Len,
28  Head,
29  Tail,
30  Take,
31  Drop,
32  Append,
33  Insert,
34  Remove,
35  Index,
36  ToBits,
37}
38
39impl BytesOp {
40  /// Gets the syntax string of a Bytes operation
41  pub fn symbol(self) -> String {
42    match self {
43      Self::Cons => "cons".to_owned(),
44      Self::Len => "len".to_owned(),
45      Self::Head => "head".to_owned(),
46      Self::Tail => "tail".to_owned(),
47      Self::Take => "take".to_owned(),
48      Self::Drop => "drop".to_owned(),
49      Self::Append => "append".to_owned(),
50      Self::Insert => "insert".to_owned(),
51      Self::Remove => "remove".to_owned(),
52      Self::Index => "index".to_owned(),
53      Self::ToBits => "to_Bits".to_owned(),
54    }
55  }
56
57  /// Gets a Bytes operation from a syntax string
58  pub fn from_symbol(x: &str) -> Option<Self> {
59    match x {
60      "cons" => Some(Self::Cons),
61      "len" => Some(Self::Len),
62      "head" => Some(Self::Head),
63      "tail" => Some(Self::Tail),
64      "take" => Some(Self::Take),
65      "drop" => Some(Self::Drop),
66      "append" => Some(Self::Append),
67      "insert" => Some(Self::Insert),
68      "remove" => Some(Self::Remove),
69      "index" => Some(Self::Index),
70      "to_Bits" => Some(Self::ToBits),
71      _ => None,
72    }
73  }
74
75  /// Returns the type of a Bytes operation
76  pub fn type_of(self) -> Term {
77    match self {
78      Self::Cons => yatima!("∀ #U8 #Bytes -> #Bytes"),
79      Self::Len => yatima!("∀ #Bytes -> #Nat"),
80      Self::Head => yatima!("∀ #Bytes -> #U8"),
81      Self::Tail => yatima!("∀ #Bytes -> #Bytes"),
82      Self::Take => yatima!("∀ #Nat #Bytes -> #Bytes"),
83      Self::Drop => yatima!("∀ #Nat #Bytes -> #Bytes"),
84      Self::Append => yatima!("∀ #Bytes #Bytes -> #Bytes"),
85      Self::Insert => yatima!("∀ #U8 #Nat #Bytes -> #Bytes"),
86      Self::Remove => yatima!("∀ #Nat #Bytes -> #Bytes"),
87      Self::Index => yatima!("∀ #Nat #Bytes -> #U8"),
88      Self::ToBits => yatima!("∀ #Nat #Bytes -> #Bits"),
89    }
90  }
91
92  /// Converts a Bytes operation into an IPLD object
93  pub fn to_ipld(self) -> Ipld {
94    match self {
95      Self::Cons => Ipld::Integer(0),
96      Self::Len => Ipld::Integer(1),
97      Self::Head => Ipld::Integer(2),
98      Self::Tail => Ipld::Integer(3),
99      Self::Take => Ipld::Integer(4),
100      Self::Drop => Ipld::Integer(5),
101      Self::Append => Ipld::Integer(6),
102      Self::Insert => Ipld::Integer(7),
103      Self::Remove => Ipld::Integer(8),
104      Self::Index => Ipld::Integer(9),
105      Self::ToBits => Ipld::Integer(10),
106    }
107  }
108
109  /// Converts an IPLD object into a Bytes operation
110  pub fn from_ipld(ipld: &Ipld) -> Result<Self, IpldError> {
111    match ipld {
112      Ipld::Integer(0) => Ok(Self::Cons),
113      Ipld::Integer(1) => Ok(Self::Len),
114      Ipld::Integer(2) => Ok(Self::Head),
115      Ipld::Integer(3) => Ok(Self::Tail),
116      Ipld::Integer(4) => Ok(Self::Take),
117      Ipld::Integer(5) => Ok(Self::Drop),
118      Ipld::Integer(6) => Ok(Self::Append),
119      Ipld::Integer(7) => Ok(Self::Insert),
120      Ipld::Integer(8) => Ok(Self::Remove),
121      Ipld::Integer(9) => Ok(Self::Index),
122      Ipld::Integer(10) => Ok(Self::ToBits),
123      xs => Err(IpldError::BytesOp(xs.to_owned())),
124    }
125  }
126
127  /// Returns the number of parameters used in the operation
128  pub fn arity(self) -> u64 {
129    match self {
130      Self::Cons => 2,
131      Self::Len => 1,
132      Self::Head => 1,
133      Self::Tail => 1,
134      Self::Take => 2,
135      Self::Drop => 2,
136      Self::Append => 2,
137      Self::Insert => 3,
138      Self::Remove => 2,
139      Self::Index => 2,
140      Self::ToBits => 2,
141    }
142  }
143
144  /// Applies a unary operation to a literal and returns it if successful
145  pub fn apply1(self, x: &Literal) -> Option<Literal> {
146    use Literal::*;
147    match (self, x) {
148      (Self::Len, Bytes(xs)) => Some(Nat(xs.len().into())),
149      (Self::Head, Bytes(xs)) => {
150        let x = xs.last();
151        x.map(|x| U8(*x))
152      }
153      (Self::Tail, Bytes(xs)) => Some(Bytes(if xs.is_empty() {
154        vec![]
155      }
156      else {
157        xs[0..xs.len() - 1].to_vec()
158      })),
159      _ => None,
160    }
161  }
162
163  /// Applies a binary operation to a literal and returns it if successful
164  pub fn apply2(self, x: &Literal, y: &Literal) -> Option<Literal> {
165    use Literal::*;
166    match (self, x, y) {
167      (Self::Cons, U8(x), Bytes(xs)) => {
168        let mut xs = xs.clone();
169        xs.push(*x);
170        Some(Bytes(xs))
171      }
172      (Self::Drop, Nat(x), Bytes(xs)) => {
173        let (_, ys) = safe_split(x, xs);
174        Some(Bytes(ys))
175      }
176      (Self::Take, Nat(x), Bytes(xs)) => {
177        let (xs, _) = safe_split(x, xs);
178        Some(Bytes(xs))
179      }
180      (Self::Append, Bytes(xs), Bytes(ys)) => {
181        let mut xs = xs.clone();
182        xs.extend_from_slice(&ys);
183        Some(Bytes(xs))
184      }
185      (Self::Remove, Nat(idx), Bytes(xs)) => {
186        let idx = usize::try_from(idx);
187        match idx {
188          Ok(idx) if idx < xs.len() => {
189            let mut xs = xs.clone();
190            xs.remove(idx);
191            Some(Bytes(xs))
192          }
193          _ => Some(Bytes(xs.clone())),
194        }
195      }
196      (Self::Index, Nat(idx), Bytes(xs)) => {
197        let idx = usize::try_from(idx);
198        match idx {
199          Ok(idx) if idx < xs.len() => {
200            let x = xs[idx];
201            Some(U8(x))
202          }
203          _ => None,
204        }
205      }
206      (Self::ToBits, Nat(idx), Bytes(xs)) => match usize::try_from(idx) {
207        Ok(x) => Some(Literal::Bits(bits::bytes_to_bits(x, xs))),
208        _ => None,
209      },
210      _ => None,
211    }
212  }
213
214  /// Applies a ternary operation to a literal and returns it if successful
215  pub fn apply3(
216    self,
217    x: &Literal,
218    y: &Literal,
219    z: &Literal,
220  ) -> Option<Literal> {
221    use Literal::*;
222    match (self, x, y, z) {
223      (Self::Insert, Nat(idx), U8(y), Bytes(xs)) => {
224        let idx = usize::try_from(idx);
225        match idx {
226          Ok(idx) if idx < xs.len() => {
227            let mut xs = xs.clone();
228            xs.insert(idx, *y);
229            Some(Bytes(xs))
230          }
231          _ => Some(Bytes(xs.clone())),
232        }
233      }
234      _ => None,
235    }
236  }
237}
238
239/// Split bytes
240pub fn safe_split(idx: &BigUint, xs: &Vec<u8>) -> (Vec<u8>, Vec<u8>) {
241  let idx = usize::try_from(idx);
242  match idx {
243    Ok(idx) if idx <= xs.len() => {
244      let ys = xs[0..idx].to_vec();
245      let zs = xs[idx..].to_vec();
246      (ys, zs)
247    }
248    _ => (xs.clone(), vec![]),
249  }
250}
251
252impl fmt::Display for BytesOp {
253  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254    write!(f, "{}", self.symbol())
255  }
256}
257
258#[cfg(test)]
259pub mod tests {
260  use super::*;
261  use crate::prim::{
262    tests::TestArg3,
263    BitsOp,
264    BytesOp,
265  };
266  use quickcheck::{
267    Arbitrary,
268    Gen,
269    TestResult,
270  };
271  use rand::Rng;
272  use sp_std::{
273    convert::TryInto,
274    mem,
275  };
276  use Literal::{
277    Bits,
278    Bytes,
279    Nat,
280    U8,
281  };
282  impl Arbitrary for BytesOp {
283    fn arbitrary(_g: &mut Gen) -> Self {
284      let mut rng = rand::thread_rng();
285      let gen: u32 = rng.gen_range(0..=10);
286      match gen {
287        0 => Self::Cons,
288        1 => Self::Len,
289        2 => Self::Head,
290        3 => Self::Tail,
291        4 => Self::Take,
292        5 => Self::Drop,
293        6 => Self::Append,
294        7 => Self::Insert,
295        8 => Self::Remove,
296        9 => Self::Index,
297        _ => Self::ToBits,
298      }
299    }
300  }
301
302  #[quickcheck]
303  fn bytes_op_ipld(x: BytesOp) -> bool {
304    match BytesOp::from_ipld(&x.to_ipld()) {
305      Ok(y) => x == y,
306      _ => false,
307    }
308  }
309
310  #[quickcheck]
311  fn test_apply(
312    op: BytesOp,
313    a: Vec<u8>,
314    b: u8,
315    c: u64,
316    d: Vec<u8>,
317  ) -> TestResult {
318    let big = BigUint::from;
319    let apply1_bytes = |expected: Option<Literal>| -> TestResult {
320      TestResult::from_bool(BytesOp::apply1(op, &Bytes(a.clone())) == expected)
321    };
322
323    let apply2_u8_bytes = |expected: Option<Literal>| -> TestResult {
324      TestResult::from_bool(
325        BytesOp::apply2(op, &U8(b), &Bytes(a.clone())) == expected,
326      )
327    };
328
329    let apply2_nat_bytes = |expected: Option<Literal>| -> TestResult {
330      TestResult::from_bool(
331        BytesOp::apply2(op, &Nat(big(c)), &Bytes(a.clone())) == expected,
332      )
333    };
334
335    let apply2_bytes_bytes = |expected: Option<Literal>| -> TestResult {
336      TestResult::from_bool(
337        BytesOp::apply2(op, &Bytes(a.clone()), &Bytes(d.clone())) == expected,
338      )
339    };
340
341    let apply3_nat_u8_bytes = |expected: Option<Literal>| -> TestResult {
342      TestResult::from_bool(
343        BytesOp::apply3(op, &Nat(big(c)), &U8(b), &Bytes(a.clone()))
344          == expected,
345      )
346    };
347
348    let from_bool = TestResult::from_bool;
349
350    match op {
351      BytesOp::Cons => {
352        let mut a = a.clone();
353        a.push(b);
354        apply2_u8_bytes(Some(Bytes(a)))
355      }
356      BytesOp::Len => apply1_bytes(Some(Nat(a.len().into()))),
357      BytesOp::Head => apply1_bytes(a.last().map(|z| U8(*z))),
358      BytesOp::Tail => apply1_bytes(Some(Bytes(if a.is_empty() {
359        vec![]
360      }
361      else {
362        a[0..a.len() - 1].to_vec()
363      }))),
364      BytesOp::Take => apply2_nat_bytes(Some(Bytes(safe_split(&big(c), &a).0))),
365      BytesOp::Drop => apply2_nat_bytes(Some(Bytes(safe_split(&big(c), &a).1))),
366      BytesOp::Append => {
367        let mut a = a.clone();
368        a.extend_from_slice(&d);
369        apply2_bytes_bytes(Some(Bytes(a)))
370      }
371      BytesOp::Insert => {
372        let idx = usize::try_from(c);
373        match idx {
374          Ok(idx) if idx < a.len() => {
375            let mut a = a.clone();
376            a.insert(idx, b);
377            apply3_nat_u8_bytes(Some(Bytes(a)))
378          }
379          _ => apply3_nat_u8_bytes(Some(Bytes(a.clone()))),
380        }
381      }
382      BytesOp::Remove => {
383        let idx = usize::try_from(c);
384        match idx {
385          Ok(idx) if idx < a.len() => {
386            let mut a = a.clone();
387            a.remove(idx);
388            apply2_nat_bytes(Some(Bytes(a)))
389          }
390          _ => apply2_nat_bytes(Some(Bytes(a.clone()))),
391        }
392      }
393      BytesOp::Index => {
394        let idx = usize::try_from(c);
395        match idx {
396          Ok(idx) if idx < a.len() => apply2_nat_bytes(Some(U8(a[idx]))),
397          _ => apply2_nat_bytes(None),
398        }
399      }
400      BytesOp::ToBits => {
401        let bits = BytesOp::apply1(op, &Bytes(a.clone()));
402        match bits {
403          None => apply1_bytes(None),
404          Some(bits_) => match bits_.clone() {
405            Bits(_) => {
406              let bytes = BitsOp::apply2(
407                BitsOp::ToBytes,
408                &Nat(big(a.len().try_into().unwrap())),
409                &bits_,
410              );
411              match bytes {
412                None => apply1_bytes(None),
413                Some(bytes_) => from_bool(bytes_ == Bytes(a.clone())),
414              }
415            }
416            _ => apply1_bytes(None),
417          },
418        }
419      }
420    }
421  }
422
423  #[quickcheck]
424  fn test_apply_none_on_invalid(
425    op: BytesOp,
426    a: Literal,
427    b: Vec<u8>,
428    c: u8,
429    d: u64,
430    test_arg_2: bool,
431    test_arg_3: TestArg3,
432  ) -> TestResult {
433    let big = BigUint::from;
434    let test_apply1_none_on_invalid = |valid_arg: Literal| -> TestResult {
435      if mem::discriminant(&valid_arg) == mem::discriminant(&a) {
436        TestResult::discard()
437      }
438      else {
439        TestResult::from_bool(BytesOp::apply1(op, &a) == None)
440      }
441    };
442
443    let test_apply2_none_on_invalid =
444      |valid_arg: Literal, a_: Literal, b_: Literal| -> TestResult {
445        let go =
446          || TestResult::from_bool(BytesOp::apply2(op, &a_, &b_) == None);
447        if test_arg_2 {
448          if mem::discriminant(&valid_arg) == mem::discriminant(&a_) {
449            TestResult::discard()
450          }
451          else {
452            go()
453          }
454        }
455        else {
456          if mem::discriminant(&valid_arg) == mem::discriminant(&b_) {
457            TestResult::discard()
458          }
459          else {
460            go()
461          }
462        }
463      };
464
465    let test_apply3_none_on_invalid = |valid_arg: Literal,
466                                       a_: Literal,
467                                       b_: Literal,
468                                       c_: Literal|
469     -> TestResult {
470      let go =
471        || TestResult::from_bool(BytesOp::apply3(op, &a_, &b_, &c_) == None);
472      match test_arg_3 {
473        TestArg3::A => {
474          if mem::discriminant(&valid_arg) == mem::discriminant(&a_) {
475            TestResult::discard()
476          }
477          else {
478            go()
479          }
480        }
481        TestArg3::B => {
482          if mem::discriminant(&valid_arg) == mem::discriminant(&b_) {
483            TestResult::discard()
484          }
485          else {
486            go()
487          }
488        }
489        TestArg3::C => {
490          if mem::discriminant(&valid_arg) == mem::discriminant(&c_) {
491            TestResult::discard()
492          }
493          else {
494            go()
495          }
496        }
497      }
498    };
499
500    match op {
501      // Arity 1, valid is Bytes.
502      BytesOp::Len | BytesOp::Head | BytesOp::Tail => {
503        test_apply1_none_on_invalid(Bytes(b))
504      }
505      // Arity 2, valid are U8 on a and Bytes on b.
506      BytesOp::Cons => {
507        if test_arg_2 {
508          test_apply2_none_on_invalid(U8(c), a, Bytes(b))
509        }
510        else {
511          test_apply2_none_on_invalid(Bytes(b), U8(c), a)
512        }
513      }
514      // Arity 2, valid are Nat on a and Bytes on b.
515      BytesOp::Take
516      | BytesOp::Drop
517      | BytesOp::Remove
518      | BytesOp::Index
519      | BytesOp::ToBits => {
520        if test_arg_2 {
521          test_apply2_none_on_invalid(Nat(big(d)), a, Bytes(b))
522        }
523        else {
524          test_apply2_none_on_invalid(Bytes(b), Nat(big(d)), a)
525        }
526      }
527      // Arity 2, valid are Bytes on a and b.
528      BytesOp::Append => {
529        if test_arg_2 {
530          test_apply2_none_on_invalid(Bytes(b.clone()), a, Bytes(b))
531        }
532        else {
533          test_apply2_none_on_invalid(Bytes(b.clone()), Bytes(b), a)
534        }
535      }
536      // Arity 3, valid are Nat on a, U8 on b and Bytes on c.
537      BytesOp::Insert => match test_arg_3 {
538        TestArg3::A => {
539          test_apply3_none_on_invalid(Nat(big(d)), a, U8(c), Bytes(b))
540        }
541        TestArg3::B => {
542          test_apply3_none_on_invalid(U8(c), Nat(big(d)), a, Bytes(b))
543        }
544        TestArg3::C => {
545          test_apply3_none_on_invalid(Bytes(b), Nat(big(d)), U8(c), a)
546        }
547      },
548    }
549  }
550}