1#![allow(missing_docs)]
16
17use std::borrow::Borrow;
18use std::collections::VecDeque;
19use std::iter;
20use std::mem;
21
22use bstr::BStr;
23use bstr::BString;
24use itertools::Itertools as _;
25
26use crate::diff::Diff;
27use crate::diff::DiffHunk;
28use crate::diff::DiffHunkKind;
29use crate::merge::Merge;
30
31#[derive(PartialEq, Eq, Clone, Debug)]
33pub struct DiffLine<'a> {
34 pub line_number: DiffLineNumber,
35 pub hunks: Vec<(DiffLineHunkSide, &'a BStr)>,
36}
37
38impl DiffLine<'_> {
39 pub fn has_left_content(&self) -> bool {
40 self.hunks
41 .iter()
42 .any(|&(side, _)| side != DiffLineHunkSide::Right)
43 }
44
45 pub fn has_right_content(&self) -> bool {
46 self.hunks
47 .iter()
48 .any(|&(side, _)| side != DiffLineHunkSide::Left)
49 }
50
51 pub fn is_unmodified(&self) -> bool {
52 self.hunks
53 .iter()
54 .all(|&(side, _)| side == DiffLineHunkSide::Both)
55 }
56
57 fn take(&mut self) -> Self {
58 DiffLine {
59 line_number: self.line_number,
60 hunks: mem::take(&mut self.hunks),
61 }
62 }
63}
64
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub struct DiffLineNumber {
67 pub left: u32,
68 pub right: u32,
69}
70
71#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73pub enum DiffLineHunkSide {
74 Both,
75 Left,
76 Right,
77}
78
79pub struct DiffLineIterator<'a, I> {
80 diff_hunks: iter::Fuse<I>,
81 current_line: DiffLine<'a>,
82 queued_lines: VecDeque<DiffLine<'a>>,
83}
84
85impl<'a, I> DiffLineIterator<'a, I>
86where
87 I: Iterator,
88 I::Item: Borrow<DiffHunk<'a>>,
89{
90 pub fn new(diff_hunks: I) -> Self {
92 let line_number = DiffLineNumber { left: 1, right: 1 };
93 Self::with_line_number(diff_hunks, line_number)
94 }
95
96 pub fn with_line_number(diff_hunks: I, line_number: DiffLineNumber) -> Self {
99 let current_line = DiffLine {
100 line_number,
101 hunks: vec![],
102 };
103 DiffLineIterator {
104 diff_hunks: diff_hunks.fuse(),
105 current_line,
106 queued_lines: VecDeque::new(),
107 }
108 }
109}
110
111impl<I> DiffLineIterator<'_, I> {
112 pub fn next_line_number(&self) -> DiffLineNumber {
115 let next_line = self.queued_lines.front().unwrap_or(&self.current_line);
116 next_line.line_number
117 }
118}
119
120impl<'a, I> Iterator for DiffLineIterator<'a, I>
121where
122 I: Iterator,
123 I::Item: Borrow<DiffHunk<'a>>,
124{
125 type Item = DiffLine<'a>;
126
127 fn next(&mut self) -> Option<Self::Item> {
128 while self.queued_lines.is_empty() {
131 let Some(hunk) = self.diff_hunks.next() else {
132 break;
133 };
134 let hunk = hunk.borrow();
135 match hunk.kind {
136 DiffHunkKind::Matching => {
137 debug_assert!(hunk.contents.iter().all_equal());
139 let text = hunk.contents[0];
140 let lines = text.split_inclusive(|b| *b == b'\n').map(BStr::new);
141 for line in lines {
142 self.current_line.hunks.push((DiffLineHunkSide::Both, line));
143 if line.ends_with(b"\n") {
144 self.queued_lines.push_back(self.current_line.take());
145 self.current_line.line_number.left += 1;
146 self.current_line.line_number.right += 1;
147 }
148 }
149 }
150 DiffHunkKind::Different => {
151 let [left_text, right_text] = hunk.contents[..]
152 .try_into()
153 .expect("hunk should have exactly two inputs");
154 let left_lines = left_text.split_inclusive(|b| *b == b'\n').map(BStr::new);
155 for left_line in left_lines {
156 self.current_line
157 .hunks
158 .push((DiffLineHunkSide::Left, left_line));
159 if left_line.ends_with(b"\n") {
160 self.queued_lines.push_back(self.current_line.take());
161 self.current_line.line_number.left += 1;
162 }
163 }
164 let right_lines = right_text.split_inclusive(|b| *b == b'\n').map(BStr::new);
165 for right_line in right_lines {
166 self.current_line
167 .hunks
168 .push((DiffLineHunkSide::Right, right_line));
169 if right_line.ends_with(b"\n") {
170 self.queued_lines.push_back(self.current_line.take());
171 self.current_line.line_number.right += 1;
172 }
173 }
174 }
175 }
176 }
177
178 if let Some(line) = self.queued_lines.pop_front() {
179 return Some(line);
180 }
181
182 if !self.current_line.hunks.is_empty() {
183 return Some(self.current_line.take());
184 }
185
186 None
187 }
188}
189
190#[derive(Clone, Debug, Eq, PartialEq)]
192pub struct ConflictDiffHunk<'input> {
193 pub kind: DiffHunkKind,
194 pub lefts: Merge<&'input BStr>,
195 pub rights: Merge<&'input BStr>,
196}
197
198pub fn conflict_diff_hunks<'input, I>(
202 diff_hunks: I,
203 num_lefts: usize,
204) -> impl Iterator<Item = ConflictDiffHunk<'input>>
205where
206 I: IntoIterator,
207 I::Item: Borrow<DiffHunk<'input>>,
208{
209 fn to_merge<'input>(contents: &[&'input BStr]) -> Merge<&'input BStr> {
210 if contents.iter().all_equal() {
213 Merge::resolved(contents[0])
214 } else {
215 Merge::from_vec(contents)
216 }
217 }
218
219 diff_hunks.into_iter().map(move |hunk| {
220 let hunk = hunk.borrow();
221 let (lefts, rights) = hunk.contents.split_at(num_lefts);
222 if let ([left], [right]) = (lefts, rights) {
223 ConflictDiffHunk {
225 kind: hunk.kind,
226 lefts: Merge::resolved(left),
227 rights: Merge::resolved(right),
228 }
229 } else {
230 let lefts = to_merge(lefts);
231 let rights = to_merge(rights);
232 let kind = match hunk.kind {
233 DiffHunkKind::Matching => DiffHunkKind::Matching,
234 DiffHunkKind::Different if lefts == rights => DiffHunkKind::Matching,
235 DiffHunkKind::Different => DiffHunkKind::Different,
236 };
237 ConflictDiffHunk {
238 kind,
239 lefts,
240 rights,
241 }
242 }
243 })
244}
245
246#[derive(PartialEq, Eq, Clone, Debug)]
249pub enum MergeResult {
250 Resolved(BString),
252 Conflict(Vec<Merge<BString>>),
254}
255
256pub fn merge_hunks<T: AsRef<[u8]>>(inputs: &Merge<T>) -> MergeResult {
260 merge_inner(inputs)
261}
262
263pub fn merge<T: AsRef<[u8]>>(inputs: &Merge<T>) -> Merge<BString> {
269 merge_inner(inputs)
270}
271
272pub fn try_merge<T: AsRef<[u8]>>(inputs: &Merge<T>) -> Option<BString> {
277 merge_inner(inputs)
278}
279
280fn merge_inner<'input, T: AsRef<[u8]>, B: FromMergeHunks<'input>>(inputs: &'input Merge<T>) -> B {
281 let num_diffs = inputs.removes().len();
285 let diff = Diff::by_line(inputs.removes().chain(inputs.adds()));
286 let hunks = resolve_diff_hunks(&diff, num_diffs);
287 B::from_hunks(hunks)
288}
289
290trait FromMergeHunks<'input>: Sized {
292 fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self;
293}
294
295impl<'input> FromMergeHunks<'input> for MergeResult {
296 fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self {
297 collect_hunks(hunks)
298 }
299}
300
301impl<'input> FromMergeHunks<'input> for Merge<BString> {
302 fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self {
303 collect_merged(hunks)
304 }
305}
306
307impl<'input> FromMergeHunks<'input> for Option<BString> {
308 fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self {
309 collect_resolved(hunks)
310 }
311}
312
313fn collect_hunks<'input>(hunks: impl IntoIterator<Item = Merge<&'input BStr>>) -> MergeResult {
316 let mut resolved_hunk = BString::new(vec![]);
317 let mut merge_hunks: Vec<Merge<BString>> = vec![];
318 for hunk in hunks {
319 if let Some(&content) = hunk.as_resolved() {
320 resolved_hunk.extend_from_slice(content);
321 } else {
322 if !resolved_hunk.is_empty() {
323 merge_hunks.push(Merge::resolved(resolved_hunk));
324 resolved_hunk = BString::new(vec![]);
325 }
326 merge_hunks.push(hunk.map(|&s| s.to_owned()));
327 }
328 }
329
330 if merge_hunks.is_empty() {
331 MergeResult::Resolved(resolved_hunk)
332 } else {
333 if !resolved_hunk.is_empty() {
334 merge_hunks.push(Merge::resolved(resolved_hunk));
335 }
336 MergeResult::Conflict(merge_hunks)
337 }
338}
339
340fn collect_merged<'input>(hunks: impl IntoIterator<Item = Merge<&'input BStr>>) -> Merge<BString> {
343 let mut maybe_resolved = Merge::resolved(BString::default());
344 for hunk in hunks {
345 if let Some(&content) = hunk.as_resolved() {
346 for buf in maybe_resolved.iter_mut() {
347 buf.extend_from_slice(content);
348 }
349 } else {
350 maybe_resolved = match maybe_resolved.into_resolved() {
351 Ok(content) => Merge::from_vec(vec![content; hunk.as_slice().len()]),
352 Err(conflict) => conflict,
353 };
354 assert_eq!(maybe_resolved.as_slice().len(), hunk.as_slice().len());
355 for (buf, s) in iter::zip(maybe_resolved.iter_mut(), hunk) {
356 buf.extend_from_slice(s);
357 }
358 }
359 }
360 maybe_resolved
361}
362
363fn collect_resolved<'input>(
365 hunks: impl IntoIterator<Item = Merge<&'input BStr>>,
366) -> Option<BString> {
367 hunks
368 .into_iter()
369 .map(|hunk| hunk.into_resolved().ok())
370 .collect()
371}
372
373fn resolve_diff_hunks<'a, 'input>(
375 diff: &'a Diff<'input>,
376 num_diffs: usize,
377) -> impl Iterator<Item = Merge<&'input BStr>> + use<'a, 'input> {
378 diff.hunks().map(move |diff_hunk| match diff_hunk.kind {
379 DiffHunkKind::Matching => {
380 debug_assert!(diff_hunk.contents.iter().all_equal());
381 Merge::resolved(diff_hunk.contents[0])
382 }
383 DiffHunkKind::Different => {
384 let merge = Merge::from_removes_adds(
385 diff_hunk.contents[..num_diffs].iter().copied(),
386 diff_hunk.contents[num_diffs..].iter().copied(),
387 );
388 match merge.resolve_trivial() {
389 Some(&content) => Merge::resolved(content),
390 None => merge,
391 }
392 }
393 })
394}
395
396#[cfg(test)]
397mod tests {
398 use indoc::indoc;
399
400 use super::*;
401
402 fn conflict<const N: usize>(values: [&[u8]; N]) -> Merge<BString> {
403 Merge::from_vec(values.map(hunk).to_vec())
404 }
405
406 fn resolved(value: &[u8]) -> Merge<BString> {
407 Merge::resolved(hunk(value))
408 }
409
410 fn hunk(data: &[u8]) -> BString {
411 data.into()
412 }
413
414 #[test]
415 fn test_diff_line_iterator_line_numbers() {
416 let mut line_iter = DiffLineIterator::with_line_number(
417 [DiffHunk::different(["a\nb", "c\nd\n"])].into_iter(),
418 DiffLineNumber { left: 1, right: 10 },
419 );
420 assert_eq!(
422 line_iter.next_line_number(),
423 DiffLineNumber { left: 1, right: 10 }
424 );
425 assert_eq!(
426 line_iter.next().unwrap(),
427 DiffLine {
428 line_number: DiffLineNumber { left: 1, right: 10 },
429 hunks: vec![(DiffLineHunkSide::Left, "a\n".as_ref())],
430 }
431 );
432 assert_eq!(
434 line_iter.next_line_number(),
435 DiffLineNumber { left: 2, right: 10 }
436 );
437 assert_eq!(
438 line_iter.next().unwrap(),
439 DiffLine {
440 line_number: DiffLineNumber { left: 2, right: 10 },
441 hunks: vec![
442 (DiffLineHunkSide::Left, "b".as_ref()),
443 (DiffLineHunkSide::Right, "c\n".as_ref()),
444 ],
445 }
446 );
447 assert_eq!(
449 line_iter.next_line_number(),
450 DiffLineNumber { left: 2, right: 11 }
451 );
452 assert_eq!(
453 line_iter.next().unwrap(),
454 DiffLine {
455 line_number: DiffLineNumber { left: 2, right: 11 },
456 hunks: vec![(DiffLineHunkSide::Right, "d\n".as_ref())],
457 }
458 );
459 assert_eq!(
461 line_iter.next_line_number(),
462 DiffLineNumber { left: 2, right: 12 }
463 );
464 assert!(line_iter.next().is_none());
465 assert_eq!(
466 line_iter.next_line_number(),
467 DiffLineNumber { left: 2, right: 12 }
468 );
469 }
470
471 #[test]
472 fn test_conflict_diff_hunks_no_conflicts() {
473 let diff_hunks = [
474 DiffHunk::matching(["a\n"].repeat(2)),
475 DiffHunk::different(["b\n", "c\n"]),
476 ];
477 let num_lefts = 1;
478 insta::assert_debug_snapshot!(
479 conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
480 [
481 ConflictDiffHunk {
482 kind: Matching,
483 lefts: Resolved(
484 "a\n",
485 ),
486 rights: Resolved(
487 "a\n",
488 ),
489 },
490 ConflictDiffHunk {
491 kind: Different,
492 lefts: Resolved(
493 "b\n",
494 ),
495 rights: Resolved(
496 "c\n",
497 ),
498 },
499 ]
500 "#);
501 }
502
503 #[test]
504 fn test_conflict_diff_hunks_simple_conflicts() {
505 let diff_hunks = [
506 DiffHunk::different(["a\n", "X\n", "b\n", "c\n"]),
508 DiffHunk::matching(["d\n"].repeat(4)),
509 DiffHunk::different(["e\n", "e\n", "e\n", "f\n"]),
511 ];
512 let num_lefts = 3;
513 insta::assert_debug_snapshot!(
514 conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
515 [
516 ConflictDiffHunk {
517 kind: Different,
518 lefts: Conflicted(
519 [
520 "a\n",
521 "X\n",
522 "b\n",
523 ],
524 ),
525 rights: Resolved(
526 "c\n",
527 ),
528 },
529 ConflictDiffHunk {
530 kind: Matching,
531 lefts: Resolved(
532 "d\n",
533 ),
534 rights: Resolved(
535 "d\n",
536 ),
537 },
538 ConflictDiffHunk {
539 kind: Different,
540 lefts: Resolved(
541 "e\n",
542 ),
543 rights: Resolved(
544 "f\n",
545 ),
546 },
547 ]
548 "#);
549 }
550
551 #[test]
552 fn test_conflict_diff_hunks_matching_conflicts() {
553 let diff_hunks = [
554 DiffHunk::different(["a\n", "X\n", "b\n", "a\n", "X\n", "b\n"]),
556 DiffHunk::matching(["c\n"].repeat(6)),
557 ];
558 let num_lefts = 3;
559 insta::assert_debug_snapshot!(
560 conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
561 [
562 ConflictDiffHunk {
563 kind: Matching,
564 lefts: Conflicted(
565 [
566 "a\n",
567 "X\n",
568 "b\n",
569 ],
570 ),
571 rights: Conflicted(
572 [
573 "a\n",
574 "X\n",
575 "b\n",
576 ],
577 ),
578 },
579 ConflictDiffHunk {
580 kind: Matching,
581 lefts: Resolved(
582 "c\n",
583 ),
584 rights: Resolved(
585 "c\n",
586 ),
587 },
588 ]
589 "#);
590 }
591
592 #[test]
593 fn test_conflict_diff_hunks_no_trivial_resolution() {
594 let diff_hunks = [DiffHunk::different(["a", "b", "a", "a"])];
595 let num_lefts = 1;
596 insta::assert_debug_snapshot!(
597 conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
598 [
599 ConflictDiffHunk {
600 kind: Different,
601 lefts: Resolved(
602 "a",
603 ),
604 rights: Conflicted(
605 [
606 "b",
607 "a",
608 "a",
609 ],
610 ),
611 },
612 ]
613 "#);
614 let num_lefts = 3;
615 insta::assert_debug_snapshot!(
616 conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
617 [
618 ConflictDiffHunk {
619 kind: Different,
620 lefts: Conflicted(
621 [
622 "a",
623 "b",
624 "a",
625 ],
626 ),
627 rights: Resolved(
628 "a",
629 ),
630 },
631 ]
632 "#);
633 }
634
635 #[test]
636 fn test_merge_single_hunk() {
637 assert_eq!(
639 merge_hunks(&conflict([b"", b"", b""])),
640 MergeResult::Resolved(hunk(b""))
641 );
642 assert_eq!(
644 merge_hunks(&conflict([b"a", b"a", b"a"])),
645 MergeResult::Resolved(hunk(b"a"))
646 );
647 assert_eq!(
649 merge_hunks(&conflict([b"", b"a\n", b"a\n"])),
650 MergeResult::Resolved(hunk(b""))
651 );
652 assert_eq!(
654 merge_hunks(&conflict([b"a\n", b"a\n", b""])),
655 MergeResult::Resolved(hunk(b""))
656 );
657 assert_eq!(
659 merge_hunks(&conflict([b"", b"a\n", b""])),
660 MergeResult::Resolved(hunk(b""))
661 );
662 assert_eq!(
664 merge_hunks(&conflict([b"a b", b"a", b"a"])),
665 MergeResult::Resolved(hunk(b"a b"))
666 );
667 assert_eq!(
669 merge_hunks(&conflict([b"a", b"a", b"a b"])),
670 MergeResult::Resolved(hunk(b"a b"))
671 );
672 assert_eq!(
674 merge_hunks(&conflict([b"a\n", b"", b"a\n", b"", b"a\n"])),
675 MergeResult::Resolved(hunk(b"a\n"))
676 );
677 assert_eq!(
679 merge_hunks(&conflict([b"b", b"a", b"b", b"", b"b"])),
680 MergeResult::Conflict(vec![conflict([b"b", b"a", b"b", b"", b"b"])])
681 );
682 assert_eq!(
684 merge_hunks(&conflict([b"", b"a\n", b"", b"a\n", b"", b"a\n", b""])),
685 MergeResult::Resolved(hunk(b""))
686 );
687 assert_eq!(
689 merge_hunks(&conflict([b"b\n", b"a\n", b"", b"a\n", b""])),
690 MergeResult::Conflict(vec![conflict([b"b\n", b"a\n", b"", b"a\n", b""])])
691 );
692 assert_eq!(
694 merge_hunks(&conflict([b"b", b"a", b"b", b"a", b"b"])),
695 MergeResult::Resolved(hunk(b"b"))
696 );
697 assert_eq!(
699 merge_hunks(&conflict([b"", b"a\n", b"b\n"])),
700 MergeResult::Conflict(vec![conflict([b"", b"a\n", b"b\n"])])
701 );
702 assert_eq!(
704 merge_hunks(&conflict([b"b\n", b"a\n", b""])),
705 MergeResult::Conflict(vec![conflict([b"b\n", b"a\n", b""])])
706 );
707 assert_eq!(
709 merge_hunks(&conflict([b"b", b"a", b"c"])),
710 MergeResult::Conflict(vec![conflict([b"b", b"a", b"c"])])
711 );
712 assert_eq!(
714 merge_hunks(&conflict([b"a", b"a", b"", b"a", b"a"])),
715 MergeResult::Resolved(hunk(b""))
716 );
717 assert_eq!(
719 merge_hunks(&conflict([b"b", b"a", b"a", b"a", b"b"])),
720 MergeResult::Resolved(hunk(b"b"))
721 );
722 assert_eq!(
724 merge_hunks(&conflict([b"b", b"a", b"a", b"a", b"c"])),
725 MergeResult::Conflict(vec![conflict([b"b", b"a", b"a", b"a", b"c"])])
726 );
727 assert_eq!(
731 merge_hunks(&conflict([b"b", b"a", b"a", b"b", b"c"])),
732 MergeResult::Resolved(hunk(b"c"))
733 );
734 assert_eq!(
736 merge_hunks(&conflict([b"c", b"a", b"d", b"b", b"e"])),
737 MergeResult::Conflict(vec![conflict([b"c", b"a", b"d", b"b", b"e"])])
738 );
739 assert_eq!(
741 merge_hunks(&conflict([b"c", b"a", b"c", b"b", b"c"])),
742 MergeResult::Conflict(vec![conflict([b"c", b"a", b"c", b"b", b"c"])])
743 );
744 }
745
746 #[test]
747 fn test_merge_multi_hunk() {
748 let inputs = conflict([b"a\nb\n", b"a\n", b"a\nc\n"]);
750 assert_eq!(
751 merge_hunks(&inputs),
752 MergeResult::Conflict(vec![resolved(b"a\n"), conflict([b"b\n", b"", b"c\n"])])
753 );
754 assert_eq!(merge(&inputs), conflict([b"a\nb\n", b"a\n", b"a\nc\n"]));
755 assert_eq!(try_merge(&inputs), None);
756
757 let inputs = conflict([b"a2\nb\nc\n", b"a\nb\nc\n", b"a\nb\nc2\n"]);
759 assert_eq!(
760 merge_hunks(&inputs),
761 MergeResult::Resolved(hunk(b"a2\nb\nc2\n"))
762 );
763 assert_eq!(merge(&inputs), resolved(b"a2\nb\nc2\n"));
764 assert_eq!(try_merge(&inputs), Some(hunk(b"a2\nb\nc2\n")));
765
766 let inputs = conflict([b"a\nb1\nc\n", b"a\nb\nc\n", b"a\nb2\nc\n"]);
768 assert_eq!(
769 merge_hunks(&inputs),
770 MergeResult::Conflict(vec![
771 resolved(b"a\n"),
772 conflict([b"b1\n", b"b\n", b"b2\n"]),
773 resolved(b"c\n"),
774 ])
775 );
776 assert_eq!(
777 merge(&inputs),
778 conflict([b"a\nb1\nc\n", b"a\nb\nc\n", b"a\nb2\nc\n"])
779 );
780 assert_eq!(try_merge(&inputs), None);
781
782 let inputs = conflict([b"a\nb\nc\n", b"a1\nb\nc\n", b"a2\nb\nc2\n"]);
784 assert_eq!(
785 merge_hunks(&inputs),
786 MergeResult::Conflict(vec![
787 conflict([b"a\n", b"a1\n", b"a2\n"]),
788 resolved(b"b\nc2\n"),
789 ])
790 );
791 assert_eq!(
792 merge(&inputs),
793 conflict([b"a\nb\nc2\n", b"a1\nb\nc2\n", b"a2\nb\nc2\n"])
794 );
795 assert_eq!(try_merge(&inputs), None);
796
797 let base = indoc! {b"
813 a {
814 p
815 }
816 "};
817 let left = indoc! {b"
818 a {
819 q
820 }
821
822 b {
823 x
824 }
825 "};
826 let right = indoc! {b"
827 a {
828 p
829 }
830
831 b {
832 x
833 }
834 "};
835 let merged = indoc! {b"
836 a {
837 q
838 }
839
840 b {
841 x
842 }
843
844 b {
845 x
846 }
847 "};
848 assert_eq!(merge(&conflict([left, base, right])), resolved(merged));
849 }
850}