yatima_core/prim/
text.rs

1use num_bigint::BigUint;
2use sp_ropey::Rope;
3use sp_ipld::Ipld;
4
5use sp_std::{
6  borrow::ToOwned,
7  fmt,
8  vec::Vec,
9};
10
11use alloc::string::{
12  String,
13  ToString,
14};
15
16use crate::{
17  defs,
18  ipld_error::IpldError,
19  literal::Literal,
20  parse,
21  term::Term,
22  yatima,
23};
24
25use core::convert::{
26  TryFrom,
27  TryInto,
28};
29
30/// Primitive 8-bit signed integer operations
31#[derive(PartialEq, Eq, Clone, Copy, Debug)]
32pub enum TextOp {
33  Cons,
34  LenChars,
35  LenLines,
36  LenBytes,
37  Append,
38  Insert,
39  Remove,
40  Take,
41  Drop,
42  Eql,
43  Lte,
44  Lth,
45  Gte,
46  Gth,
47  Char,
48  Byte,
49  Line,
50  CharAtByte,
51  ByteAtChar,
52  LineAtByte,
53  LineAtChar,
54  LineStartChar,
55  LineStartByte,
56  ToBytes,
57}
58
59impl TextOp {
60  /// Gets the syntax string of an i8 operation
61  pub fn symbol(self) -> String {
62    match self {
63      Self::Cons => "cons".to_owned(),
64      Self::Append => "append".to_owned(),
65      Self::Insert => "insert".to_owned(),
66      Self::Remove => "remove".to_owned(),
67      Self::Take => "take".to_owned(),
68      Self::Drop => "drop".to_owned(),
69      Self::Eql => "eql".to_owned(),
70      Self::Lte => "lte".to_owned(),
71      Self::Lth => "lth".to_owned(),
72      Self::Gte => "gte".to_owned(),
73      Self::Gth => "gth".to_owned(),
74      Self::LenChars => "len_chars".to_owned(),
75      Self::LenBytes => "len_bytes".to_owned(),
76      Self::LenLines => "len_lines".to_owned(),
77      Self::Char => "char".to_owned(),
78      Self::Byte => "byte".to_owned(),
79      Self::Line => "line".to_owned(),
80      Self::CharAtByte => "char_at_byte".to_owned(),
81      Self::ByteAtChar => "byte_at_char".to_owned(),
82      Self::LineAtByte => "line_at_byte".to_owned(),
83      Self::LineAtChar => "line_at_char".to_owned(),
84      Self::LineStartByte => "line_start_byte".to_owned(),
85      Self::LineStartChar => "line_start_char".to_owned(),
86      Self::ToBytes => "to_bytes".to_owned(),
87    }
88  }
89
90  /// Gets an i8 operation from a syntax string
91  pub fn from_symbol(x: &str) -> Option<Self> {
92    match x {
93      "cons" => Some(Self::Cons),
94      "append" => Some(Self::Append),
95      "insert" => Some(Self::Insert),
96      "remove" => Some(Self::Remove),
97      "take" => Some(Self::Take),
98      "drop" => Some(Self::Drop),
99      "eql" => Some(Self::Eql),
100      "lte" => Some(Self::Lte),
101      "lth" => Some(Self::Lth),
102      "gte" => Some(Self::Gte),
103      "gth" => Some(Self::Gth),
104      "len_chars" => Some(Self::LenChars),
105      "len_bytes" => Some(Self::LenBytes),
106      "len_lines" => Some(Self::LenLines),
107      "char" => Some(Self::Char),
108      "byte" => Some(Self::Byte),
109      "line" => Some(Self::Line),
110      "char_at_byte" => Some(Self::CharAtByte),
111      "byte_at_char" => Some(Self::ByteAtChar),
112      "line_at_byte" => Some(Self::LineAtByte),
113      "line_at_char" => Some(Self::LineAtChar),
114      "line_start_byte" => Some(Self::LineStartByte),
115      "line_start_char" => Some(Self::LineStartChar),
116      "to_bytes" => Some(Self::ToBytes),
117      _ => None,
118    }
119  }
120
121  /// Returns the type of an i8 operation
122  pub fn type_of(self) -> Term {
123    match self {
124      Self::Cons => yatima!("∀ #Char #Text -> #Text"),
125      Self::LenChars => yatima!("∀ #Text -> #Nat"),
126      Self::LenLines => yatima!("∀ #Text -> #Nat"),
127      Self::LenBytes => yatima!("∀ #Text -> #Nat"),
128      Self::Append => yatima!("∀ #Text #Text -> #Text"),
129      Self::Insert => yatima!("∀ #Nat #Text #Text -> #Text"),
130      Self::Remove => yatima!("∀ #Nat #Nat #Text -> #Text"),
131      Self::Take => yatima!("∀ #Nat #Text -> #Text"),
132      Self::Drop => yatima!("∀ #Nat #Text -> #Text"),
133      Self::Eql => yatima!("∀ #Text #Text -> #Bool"),
134      Self::Lte => yatima!("∀ #Text #Text -> #Bool"),
135      Self::Lth => yatima!("∀ #Text #Text -> #Bool"),
136      Self::Gte => yatima!("∀ #Text #Text -> #Bool"),
137      Self::Gth => yatima!("∀ #Text #Text -> #Bool"),
138      Self::Char => yatima!("∀ #Nat #Text -> #Char"),
139      Self::Byte => yatima!("∀ #Nat #Text -> #U8"),
140      Self::Line => yatima!("∀ #Nat #Text -> #Text"),
141      Self::CharAtByte => yatima!("∀ #Nat #Text -> #Nat"),
142      Self::ByteAtChar => yatima!("∀ #Nat #Text -> #Nat"),
143      Self::LineAtByte => yatima!("∀ #Nat #Text -> #Nat"),
144      Self::LineAtChar => yatima!("∀ #Nat #Text -> #Nat"),
145      Self::LineStartChar => yatima!("∀ #Nat #Text -> #Nat"),
146      Self::LineStartByte => yatima!("∀ #Nat #Text -> #Nat"),
147      Self::ToBytes => yatima!("∀ #Text -> #Bytes"),
148    }
149  }
150
151  /// Converts an i8 operation into an IPLD object
152  pub fn to_ipld(self) -> Ipld {
153    match self {
154      Self::Cons => Ipld::Integer(0),
155      Self::LenChars => Ipld::Integer(1),
156      Self::LenLines => Ipld::Integer(2),
157      Self::LenBytes => Ipld::Integer(3),
158      Self::Append => Ipld::Integer(4),
159      Self::Insert => Ipld::Integer(5),
160      Self::Remove => Ipld::Integer(6),
161      Self::Take => Ipld::Integer(7),
162      Self::Drop => Ipld::Integer(8),
163      Self::Eql => Ipld::Integer(9),
164      Self::Lte => Ipld::Integer(10),
165      Self::Lth => Ipld::Integer(11),
166      Self::Gte => Ipld::Integer(12),
167      Self::Gth => Ipld::Integer(13),
168      Self::Char => Ipld::Integer(14),
169      Self::Byte => Ipld::Integer(15),
170      Self::Line => Ipld::Integer(16),
171      Self::CharAtByte => Ipld::Integer(17),
172      Self::ByteAtChar => Ipld::Integer(18),
173      Self::LineAtByte => Ipld::Integer(19),
174      Self::LineAtChar => Ipld::Integer(20),
175      Self::LineStartChar => Ipld::Integer(21),
176      Self::LineStartByte => Ipld::Integer(22),
177      Self::ToBytes => Ipld::Integer(23),
178    }
179  }
180
181  /// Converts an IPLD object into an i8 operation
182  pub fn from_ipld(ipld: &Ipld) -> Result<Self, IpldError> {
183    match ipld {
184      Ipld::Integer(0) => Ok(Self::Cons),
185      Ipld::Integer(1) => Ok(Self::LenChars),
186      Ipld::Integer(2) => Ok(Self::LenLines),
187      Ipld::Integer(3) => Ok(Self::LenBytes),
188      Ipld::Integer(4) => Ok(Self::Append),
189      Ipld::Integer(5) => Ok(Self::Insert),
190      Ipld::Integer(6) => Ok(Self::Remove),
191      Ipld::Integer(7) => Ok(Self::Take),
192      Ipld::Integer(8) => Ok(Self::Drop),
193      Ipld::Integer(9) => Ok(Self::Eql),
194      Ipld::Integer(10) => Ok(Self::Lte),
195      Ipld::Integer(11) => Ok(Self::Lth),
196      Ipld::Integer(12) => Ok(Self::Gte),
197      Ipld::Integer(13) => Ok(Self::Gth),
198      Ipld::Integer(14) => Ok(Self::Char),
199      Ipld::Integer(15) => Ok(Self::Byte),
200      Ipld::Integer(16) => Ok(Self::Line),
201      Ipld::Integer(17) => Ok(Self::CharAtByte),
202      Ipld::Integer(18) => Ok(Self::ByteAtChar),
203      Ipld::Integer(19) => Ok(Self::LineAtByte),
204      Ipld::Integer(20) => Ok(Self::LineAtChar),
205      Ipld::Integer(21) => Ok(Self::LineStartChar),
206      Ipld::Integer(22) => Ok(Self::LineStartByte),
207      Ipld::Integer(23) => Ok(Self::ToBytes),
208      xs => Err(IpldError::TextOp(xs.to_owned())),
209    }
210  }
211
212  /// Returns the number of parameters used in the operation
213  pub fn arity(self) -> u64 {
214    match self {
215      Self::Cons => 2,
216      Self::LenChars => 1,
217      Self::LenLines => 1,
218      Self::LenBytes => 1,
219      Self::Append => 2,
220      Self::Insert => 3,
221      Self::Remove => 3,
222      Self::Take => 2,
223      Self::Drop => 2,
224      Self::Eql => 2,
225      Self::Lte => 2,
226      Self::Lth => 2,
227      Self::Gte => 2,
228      Self::Gth => 2,
229      Self::Char => 2,
230      Self::Byte => 2,
231      Self::Line => 2,
232      Self::CharAtByte => 2,
233      Self::ByteAtChar => 2,
234      Self::LineAtByte => 2,
235      Self::LineAtChar => 2,
236      Self::LineStartChar => 2,
237      Self::LineStartByte => 2,
238      Self::ToBytes => 1,
239    }
240  }
241
242  /// Applies a unary operation to a literal and returns it if successful
243  pub fn apply1(self, x: &Literal) -> Option<Literal> {
244    use Literal::*;
245    match (self, x) {
246      (Self::LenChars, Text(xs)) => Some(Nat(xs.len_chars().into())),
247      (Self::LenBytes, Text(xs)) => Some(Nat(xs.len_bytes().into())),
248      (Self::LenLines, Text(xs)) => Some(Nat(xs.len_lines().into())),
249      (Self::ToBytes, Text(xs)) => Some(Bytes(xs.bytes().collect::<Vec<u8>>())),
250      _ => None,
251    }
252  }
253
254  /// Applies a binary operation to a literal and returns it if successful
255  pub fn apply2(self, x: &Literal, y: &Literal) -> Option<Literal> {
256    use Literal::*;
257    match (self, x, y) {
258      (Self::Cons, Char(c), Text(cs)) => {
259        let mut cs = cs.clone();
260        cs.insert_char(0, *c);
261        Some(Text(cs))
262      }
263      (Self::Append, Text(xs), Text(ys)) => {
264        let mut xs = xs.clone();
265        xs.append(ys.clone());
266        Some(Text(xs))
267      }
268      (Self::Take, Nat(x), Text(xs)) => {
269        let (xs, _) = safe_split(x, xs.clone());
270        Some(Text(xs))
271      }
272      (Self::Drop, Nat(x), Text(xs)) => {
273        let (_, ys) = safe_split(x, xs.clone());
274        Some(Text(ys))
275      }
276      (Self::Eql, Text(xs), Text(ys)) => Some(Bool(xs == ys)),
277      (Self::Lte, Text(xs), Text(ys)) => Some(Bool(xs <= ys)),
278      (Self::Lth, Text(xs), Text(ys)) => Some(Bool(xs < ys)),
279      (Self::Gte, Text(xs), Text(ys)) => Some(Bool(xs >= ys)),
280      (Self::Gth, Text(xs), Text(ys)) => Some(Bool(xs > ys)),
281      (Self::Char, Nat(idx), Text(ys)) => {
282        let idx: usize = idx.clone().try_into().ok()?;
283        if idx < ys.len_chars() { Some(Char(ys.char(idx))) } else { None }
284      }
285      (Self::Byte, Nat(idx), Text(ys)) => {
286        let idx: usize = idx.clone().try_into().ok()?;
287        if idx < ys.len_chars() { Some(U8(ys.byte(idx))) } else { None }
288      }
289      (Self::Line, Nat(idx), Text(ys)) => {
290        let idx: usize = idx.clone().try_into().ok()?;
291        if idx < ys.len_lines() {
292          Some(Text(ys.line(idx).into()))
293        }
294        else {
295          None
296        }
297      }
298      (Self::CharAtByte, Nat(idx), Text(ys)) => {
299        let idx: usize = idx.clone().try_into().ok()?;
300        if idx < ys.len_bytes() {
301          Some(Nat(ys.byte_to_char(idx).into()))
302        }
303        else {
304          None
305        }
306      }
307      (Self::ByteAtChar, Nat(idx), Text(ys)) => {
308        let idx: usize = idx.clone().try_into().ok()?;
309        if idx < ys.len_chars() {
310          Some(Nat(ys.char_to_byte(idx).into()))
311        }
312        else {
313          None
314        }
315      }
316      (Self::LineAtChar, Nat(idx), Text(ys)) => {
317        let idx: usize = idx.clone().try_into().ok()?;
318        if idx < ys.len_chars() {
319          Some(Nat(ys.char_to_line(idx).into()))
320        }
321        else {
322          None
323        }
324      }
325      (Self::LineAtByte, Nat(idx), Text(ys)) => {
326        let idx: usize = idx.clone().try_into().ok()?;
327        if idx < ys.len_bytes() {
328          Some(Nat(ys.byte_to_line(idx).into()))
329        }
330        else {
331          None
332        }
333      }
334      (Self::LineStartChar, Nat(idx), Text(ys)) => {
335        let idx: usize = idx.clone().try_into().ok()?;
336        if idx < ys.len_lines() {
337          Some(Nat(ys.line_to_char(idx).into()))
338        }
339        else {
340          None
341        }
342      }
343      (Self::LineStartByte, Nat(idx), Text(ys)) => {
344        let idx: usize = idx.clone().try_into().ok()?;
345        if idx < ys.len_lines() {
346          Some(Nat(ys.line_to_byte(idx).into()))
347        }
348        else {
349          None
350        }
351      }
352      _ => None,
353    }
354  }
355
356  /// Applies a ternary operation to a literal and returns it if successful
357  pub fn apply3(
358    self,
359    x: &Literal,
360    y: &Literal,
361    z: &Literal,
362  ) -> Option<Literal> {
363    use Literal::*;
364    match (self, x, y, z) {
365      (Self::Insert, Nat(x), Text(y), Text(xs)) => {
366        Some(Text(safe_insert(x, y.clone(), xs.clone())))
367      }
368      (Self::Remove, Nat(x), Nat(y), Text(xs)) => {
369        Some(Text(safe_remove(x, y, xs.clone())))
370      }
371      _ => None,
372    }
373  }
374}
375
376impl fmt::Display for TextOp {
377  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378    write!(f, "{}", self.symbol())
379  }
380}
381
382/// Inserts text if given index is valid
383pub fn safe_insert(idx: &BigUint, ys: Rope, mut xs: Rope) -> Rope {
384  let idx = usize::try_from(idx);
385  match idx {
386    Ok(idx) if idx <= xs.len_chars() => {
387      xs.insert(idx, &ys.to_string());
388      xs
389    }
390    _ => xs,
391  }
392}
393
394/// Removes text between two indices
395pub fn safe_remove(from: &BigUint, upto: &BigUint, mut xs: Rope) -> Rope {
396  let from = usize::try_from(from);
397  let upto = usize::try_from(upto);
398  let len = xs.len_chars();
399  match (from, upto) {
400    (Ok(from), Ok(upto))
401      if (from <= len) && (upto <= len) && (upto >= from) =>
402    {
403      xs.remove(from..upto);
404      xs
405    }
406    _ => xs,
407  }
408}
409
410/// Returns the first character if it exists
411pub fn safe_head(mut x: Rope) -> Option<(char, Rope)> {
412  if x.len_chars() == 0 {
413    None
414  }
415  else {
416    let tail = x.split_off(1);
417    Some((x.char(0), tail))
418  }
419}
420
421/// Splits text in two
422pub fn safe_split(idx: &BigUint, mut xs: Rope) -> (Rope, Rope) {
423  let idx = usize::try_from(idx);
424  match idx {
425    Ok(idx) if idx <= xs.len_chars() => {
426      let ys = xs.split_off(idx);
427      (xs, ys)
428    }
429    _ => (xs, Rope::from_str("")),
430  }
431}
432
433/// Gets a line of text at given index
434pub fn safe_line(idx: BigUint, xs: Rope) -> Rope {
435  if let Ok(i) = usize::try_from(idx) {
436    if i > xs.len_chars() { Rope::from_str("") } else { Rope::from(xs.line(i)) }
437  }
438  else {
439    Rope::from_str("")
440  }
441}
442
443#[cfg(test)]
444pub mod tests {
445  use super::*;
446  use crate::prim::tests::TestArg3;
447  use core::fmt::Debug;
448  use quickcheck::{
449    Arbitrary,
450    Gen,
451    TestResult,
452  };
453  use rand::Rng;
454  use sp_std::mem;
455  use Literal::{
456    Bool,
457    Bytes,
458    Char,
459    Nat,
460    Text,
461    U8,
462  };
463  impl Arbitrary for TextOp {
464    fn arbitrary(_g: &mut Gen) -> Self {
465      let mut rng = rand::thread_rng();
466      let gen: u32 = rng.gen_range(0..=23);
467      match gen {
468        0 => Self::Cons,
469        1 => Self::LenChars,
470        2 => Self::LenLines,
471        3 => Self::LenBytes,
472        4 => Self::Append,
473        5 => Self::Insert,
474        6 => Self::Remove,
475        7 => Self::Take,
476        8 => Self::Drop,
477        9 => Self::Eql,
478        10 => Self::Lte,
479        11 => Self::Lth,
480        12 => Self::Gte,
481        13 => Self::Gth,
482        14 => Self::Char,
483        15 => Self::Byte,
484        16 => Self::Line,
485        17 => Self::CharAtByte,
486        18 => Self::ByteAtChar,
487        19 => Self::LineAtByte,
488        20 => Self::LineAtChar,
489        21 => Self::LineStartChar,
490        22 => Self::LineStartByte,
491        _ => Self::ToBytes,
492      }
493    }
494  }
495
496  #[quickcheck]
497  fn text_op_ipld(x: TextOp) -> bool {
498    match TextOp::from_ipld(&x.to_ipld()) {
499      Ok(y) => x == y,
500      _ => false,
501    }
502  }
503
504  #[test]
505  fn test_safe_head() {
506    let rope: Rope = Rope::from_str("foo");
507    let res = safe_head(rope);
508    assert_eq!(res, Some(('f', Rope::from_str("oo"))));
509  }
510
511  #[test]
512  fn test_safe_split() {
513    let rope: Rope = Rope::from_str("foo");
514    let res = safe_split(&0u64.into(), rope.clone());
515    assert_eq!(res, (Rope::from_str(""), Rope::from_str("foo")));
516    let res = safe_split(&1u64.into(), rope.clone());
517    assert_eq!(res, (Rope::from_str("f"), Rope::from_str("oo")));
518    let res = safe_split(&4u64.into(), rope.clone());
519    assert_eq!(res, (Rope::from_str("foo"), Rope::from_str("")));
520    let res = safe_split(&u128::MAX.into(), rope.clone());
521    assert_eq!(res, (Rope::from_str("foo"), Rope::from_str("")));
522  }
523
524  #[quickcheck]
525  fn test_apply(
526    op: TextOp,
527    a: String,
528    b: char,
529    c: String,
530    d: u64,
531    e: u64,
532  ) -> TestResult {
533    let a = Rope::from(a);
534    let c = Rope::from(c);
535    let big = BigUint::from;
536    let apply1_text = |expected: Option<Literal>| -> TestResult {
537      TestResult::from_bool(TextOp::apply1(op, &Text(a.clone())) == expected)
538    };
539
540    let apply2_char_text = |expected: Option<Literal>| -> TestResult {
541      TestResult::from_bool(
542        TextOp::apply2(op, &Char(b), &Text(a.clone())) == expected,
543      )
544    };
545
546    let apply2_text_text = |expected: Option<Literal>| -> TestResult {
547      TestResult::from_bool(
548        TextOp::apply2(op, &Text(a.clone()), &Text(c.clone())) == expected,
549      )
550    };
551
552    let apply2_nat_text = |expected: Option<Literal>| -> TestResult {
553      TestResult::from_bool(
554        TextOp::apply2(op, &Nat(big(d)), &Text(a.clone())) == expected,
555      )
556    };
557
558    let apply3_nat_text_text = |expected: Option<Literal>| -> TestResult {
559      TestResult::from_bool(
560        TextOp::apply3(op, &Nat(big(d)), &Text(a.clone()), &Text(c.clone()))
561          == expected,
562      )
563    };
564
565    let apply3_nat_nat_text = |expected: Option<Literal>| -> TestResult {
566      TestResult::from_bool(
567        TextOp::apply3(op, &Nat(big(d)), &Nat(big(e)), &Text(a.clone()))
568          == expected,
569      )
570    };
571
572    match op {
573      TextOp::Cons => {
574        let mut cs = a.clone();
575        cs.insert_char(0, b);
576        apply2_char_text(Some(Text(cs)))
577      }
578      TextOp::LenChars => apply1_text(Some(Nat(a.len_chars().into()))),
579      TextOp::LenLines => apply1_text(Some(Nat(a.len_lines().into()))),
580      TextOp::LenBytes => apply1_text(Some(Nat(a.len_bytes().into()))),
581      TextOp::Append => {
582        let mut xs = a.clone();
583        xs.append(c.clone());
584        apply2_text_text(Some(Text(xs)))
585      }
586      TextOp::Insert => apply3_nat_text_text(Some(Text(safe_insert(
587        &big(d),
588        a.clone(),
589        c.clone(),
590      )))),
591      TextOp::Remove => apply3_nat_nat_text(Some(Text(safe_remove(
592        &big(d),
593        &big(e),
594        a.clone(),
595      )))),
596      TextOp::Take => {
597        apply2_nat_text(Some(Text(safe_split(&big(d), a.clone()).0)))
598      }
599      TextOp::Drop => {
600        apply2_nat_text(Some(Text(safe_split(&big(d), a.clone()).1)))
601      }
602      TextOp::Eql => apply2_text_text(Some(Bool(a == c))),
603      TextOp::Lte => apply2_text_text(Some(Bool(a <= c))),
604      TextOp::Lth => apply2_text_text(Some(Bool(a < c))),
605      TextOp::Gte => apply2_text_text(Some(Bool(a >= c))),
606      TextOp::Gth => apply2_text_text(Some(Bool(a > c))),
607      TextOp::Char => {
608        let idx: Option<usize> = big(d).clone().try_into().ok();
609        match idx {
610          None => apply2_nat_text(None),
611          Some(idx) => {
612            if idx < a.len_chars() {
613              apply2_nat_text(Some(Char(a.char(idx))))
614            }
615            else {
616              apply2_nat_text(None)
617            }
618          }
619        }
620      }
621      TextOp::Byte => {
622        let idx: Option<usize> = big(d).clone().try_into().ok();
623        match idx {
624          None => apply2_nat_text(None),
625          Some(idx) => {
626            if idx < a.len_chars() {
627              apply2_nat_text(Some(U8(a.byte(idx))))
628            }
629            else {
630              apply2_nat_text(None)
631            }
632          }
633        }
634      }
635      TextOp::Line => {
636        let idx: Option<usize> = big(d).clone().try_into().ok();
637        match idx {
638          None => apply2_nat_text(None),
639          Some(idx) => {
640            if idx < a.len_lines() {
641              apply2_nat_text(Some(Text(a.line(idx).into())))
642            }
643            else {
644              apply2_nat_text(None)
645            }
646          }
647        }
648      }
649      TextOp::CharAtByte => {
650        let idx: Option<usize> = big(d).clone().try_into().ok();
651        match idx {
652          None => apply2_nat_text(None),
653          Some(idx) => {
654            if idx < a.len_bytes() {
655              apply2_nat_text(Some(Nat(a.byte_to_char(idx).into())))
656            }
657            else {
658              apply2_nat_text(None)
659            }
660          }
661        }
662      }
663      TextOp::ByteAtChar => {
664        let idx: Option<usize> = big(d).clone().try_into().ok();
665        match idx {
666          None => apply2_nat_text(None),
667          Some(idx) => {
668            if idx < a.len_chars() {
669              apply2_nat_text(Some(Nat(a.char_to_byte(idx).into())))
670            }
671            else {
672              apply2_nat_text(None)
673            }
674          }
675        }
676      }
677      TextOp::LineAtByte => {
678        let idx: Option<usize> = big(d).clone().try_into().ok();
679        match idx {
680          None => apply2_nat_text(None),
681          Some(idx) => {
682            if idx < a.len_bytes() {
683              apply2_nat_text(Some(Nat(a.byte_to_line(idx).into())))
684            }
685            else {
686              apply2_nat_text(None)
687            }
688          }
689        }
690      }
691      TextOp::LineAtChar => {
692        let idx: Option<usize> = big(d).clone().try_into().ok();
693        match idx {
694          None => apply2_nat_text(None),
695          Some(idx) => {
696            if idx < a.len_chars() {
697              apply2_nat_text(Some(Nat(a.char_to_line(idx).into())))
698            }
699            else {
700              apply2_nat_text(None)
701            }
702          }
703        }
704      }
705      TextOp::LineStartChar => {
706        let idx: Option<usize> = big(d).clone().try_into().ok();
707        match idx {
708          None => apply2_nat_text(None),
709          Some(idx) => {
710            if idx < a.len_lines() {
711              apply2_nat_text(Some(Nat(a.line_to_char(idx).into())))
712            }
713            else {
714              apply2_nat_text(None)
715            }
716          }
717        }
718      }
719      TextOp::LineStartByte => {
720        let idx: Option<usize> = big(d).clone().try_into().ok();
721        match idx {
722          None => apply2_nat_text(None),
723          Some(idx) => {
724            if idx < a.len_lines() {
725              apply2_nat_text(Some(Nat(a.line_to_byte(idx).into())))
726            }
727            else {
728              apply2_nat_text(None)
729            }
730          }
731        }
732      }
733      TextOp::ToBytes => {
734        apply1_text(Some(Bytes(a.bytes().collect::<Vec<u8>>())))
735      }
736    }
737  }
738
739  #[derive(Debug, Clone)]
740  struct ArgsApplyNoneOnInvalid(
741    TextOp,
742    Literal,
743    String,
744    char,
745    String,
746    u64,
747    u64,
748    bool,
749    TestArg3,
750  );
751
752  impl Arbitrary for ArgsApplyNoneOnInvalid {
753    fn arbitrary(g: &mut Gen) -> ArgsApplyNoneOnInvalid {
754      ArgsApplyNoneOnInvalid(
755        TextOp::arbitrary(g),
756        Literal::arbitrary(g),
757        String::arbitrary(g),
758        char::arbitrary(g),
759        String::arbitrary(g),
760        u64::arbitrary(g),
761        u64::arbitrary(g),
762        bool::arbitrary(g),
763        TestArg3::arbitrary(g),
764      )
765    }
766  }
767
768  #[quickcheck]
769  fn test_apply_none_on_invalid(args: ArgsApplyNoneOnInvalid) -> TestResult {
770    let ArgsApplyNoneOnInvalid(op, a, b, c, d, e, f, test_arg_2, test_arg_3) =
771      args;
772    let b = Rope::from(b);
773    let d = Rope::from(d);
774    let big = BigUint::from;
775    let test_apply1_none_on_invalid = |valid_arg: Literal| -> TestResult {
776      if mem::discriminant(&valid_arg) == mem::discriminant(&a) {
777        TestResult::discard()
778      }
779      else {
780        TestResult::from_bool(TextOp::apply1(op, &a) == None)
781      }
782    };
783
784    let test_apply2_none_on_invalid =
785      |valid_arg: Literal, a_: Literal, b_: Literal| -> TestResult {
786        let go = || TestResult::from_bool(TextOp::apply2(op, &a_, &b_) == None);
787        if test_arg_2 {
788          if mem::discriminant(&valid_arg) == mem::discriminant(&a_) {
789            TestResult::discard()
790          }
791          else {
792            go()
793          }
794        }
795        else {
796          if mem::discriminant(&valid_arg) == mem::discriminant(&b_) {
797            TestResult::discard()
798          }
799          else {
800            go()
801          }
802        }
803      };
804
805    let test_apply3_none_on_invalid = |valid_arg: Literal,
806                                       a_: Literal,
807                                       b_: Literal,
808                                       c_: Literal|
809     -> TestResult {
810      let go =
811        || TestResult::from_bool(TextOp::apply3(op, &a_, &b_, &c_) == None);
812      match test_arg_3 {
813        TestArg3::A => {
814          if mem::discriminant(&valid_arg) == mem::discriminant(&a_) {
815            TestResult::discard()
816          }
817          else {
818            go()
819          }
820        }
821        TestArg3::B => {
822          if mem::discriminant(&valid_arg) == mem::discriminant(&b_) {
823            TestResult::discard()
824          }
825          else {
826            go()
827          }
828        }
829        TestArg3::C => {
830          if mem::discriminant(&valid_arg) == mem::discriminant(&c_) {
831            TestResult::discard()
832          }
833          else {
834            go()
835          }
836        }
837      }
838    };
839
840    match op {
841      // Arity 1, valid is Text.
842      TextOp::LenChars
843      | TextOp::LenBytes
844      | TextOp::LenLines
845      | TextOp::ToBytes => test_apply1_none_on_invalid(Text(b)),
846      // Arity 2, valid are Char on a and Text on b.
847      TextOp::Cons => {
848        if test_arg_2 {
849          test_apply2_none_on_invalid(Char(c), a, Text(b.clone()))
850        }
851        else {
852          test_apply2_none_on_invalid(Text(b.clone()), Char(c), a)
853        }
854      }
855      // Arity 2, valid are Text on a and b.
856      TextOp::Append
857      | TextOp::Eql
858      | TextOp::Lte
859      | TextOp::Lth
860      | TextOp::Gte
861      | TextOp::Gth => {
862        if test_arg_2 {
863          test_apply2_none_on_invalid(Text(b.clone()), a, Text(b.clone()))
864        }
865        else {
866          test_apply2_none_on_invalid(Text(b.clone()), Text(b.clone()), a)
867        }
868      }
869      // Arity 2, valid are Nat on a and Text on b.
870      TextOp::Take
871      | TextOp::Drop
872      | TextOp::Char
873      | TextOp::Byte
874      | TextOp::Line
875      | TextOp::CharAtByte
876      | TextOp::ByteAtChar
877      | TextOp::LineAtChar
878      | TextOp::LineAtByte
879      | TextOp::LineStartChar
880      | TextOp::LineStartByte => {
881        if test_arg_2 {
882          test_apply2_none_on_invalid(Nat(big(e)), a, Text(b.clone()))
883        }
884        else {
885          test_apply2_none_on_invalid(Text(b.clone()), Nat(big(e)), a)
886        }
887      }
888      // Arity 3, valid are Nat on a, Text on b and Text on c.
889      TextOp::Insert => match test_arg_3 {
890        TestArg3::A => test_apply3_none_on_invalid(
891          Nat(big(e)),
892          a,
893          Text(b.clone()),
894          Text(d.clone()),
895        ),
896        TestArg3::B => test_apply3_none_on_invalid(
897          Text(b.clone()),
898          Nat(big(e)),
899          a,
900          Text(b.clone()),
901        ),
902        TestArg3::C => test_apply3_none_on_invalid(
903          Text(b.clone()),
904          Nat(big(e)),
905          Text(b.clone()),
906          a,
907        ),
908      },
909      // Arity 3, valid are Nat on a, Nat on b and Text on c.
910      TextOp::Remove => match test_arg_3 {
911        TestArg3::A => test_apply3_none_on_invalid(
912          Nat(big(e)),
913          a,
914          Nat(big(e)),
915          Text(d.clone()),
916        ),
917        TestArg3::B => test_apply3_none_on_invalid(
918          Nat(big(e)),
919          Nat(big(e)),
920          a,
921          Text(b.clone()),
922        ),
923        TestArg3::C => test_apply3_none_on_invalid(
924          Text(b.clone()),
925          Nat(big(e)),
926          Nat(big(f)),
927          a,
928        ),
929      },
930    }
931  }
932}