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#[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 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 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 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 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 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 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 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 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 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
382pub 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
394pub 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
410pub 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
421pub 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
433pub 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 TextOp::LenChars
843 | TextOp::LenBytes
844 | TextOp::LenLines
845 | TextOp::ToBytes => test_apply1_none_on_invalid(Text(b)),
846 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 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 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 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 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}