|
|
|
@ -5,8 +5,10 @@ use std::cmp::Reverse;
|
|
|
|
|
|
|
|
|
|
/// Convert annotated test string to test string and selection.
|
|
|
|
|
///
|
|
|
|
|
/// `^` for `anchor` and `|` for head (`@` for primary), both must appear
|
|
|
|
|
/// or otherwise it will panic.
|
|
|
|
|
/// `#[|` for primary selection with head before anchor followed by `]#`.
|
|
|
|
|
/// `#(|` for secondary selection with head before anchor followed by `)#`.
|
|
|
|
|
/// `#[` for primary selection with head after anchor followed by `|]#`.
|
|
|
|
|
/// `#(` for secondary selection with head after anchor followed by `|)#`.
|
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
@ -15,7 +17,7 @@ use std::cmp::Reverse;
|
|
|
|
|
/// use smallvec::smallvec;
|
|
|
|
|
///
|
|
|
|
|
/// assert_eq!(
|
|
|
|
|
/// print("^a@b|c^"),
|
|
|
|
|
/// print("#[a|]#b#(|c)#"),
|
|
|
|
|
/// ("abc".to_owned(), Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0))
|
|
|
|
|
/// );
|
|
|
|
|
/// ```
|
|
|
|
@ -26,56 +28,101 @@ use std::cmp::Reverse;
|
|
|
|
|
/// Panics when missing head or anchor.
|
|
|
|
|
/// Panics when head come after head or anchor come after anchor.
|
|
|
|
|
pub fn print(s: &str) -> (String, Selection) {
|
|
|
|
|
let mut anchor = None;
|
|
|
|
|
let mut head = None;
|
|
|
|
|
let mut primary = None;
|
|
|
|
|
let mut ranges = SmallVec::new();
|
|
|
|
|
let mut i = 0;
|
|
|
|
|
let s = s
|
|
|
|
|
.chars()
|
|
|
|
|
.filter(|c| {
|
|
|
|
|
match c {
|
|
|
|
|
'^' if anchor != None => panic!("anchor without head {s:?}"),
|
|
|
|
|
'^' if head == None => anchor = Some(i),
|
|
|
|
|
'^' => ranges.push(Range::new(i, head.take().unwrap())),
|
|
|
|
|
'|' if head != None => panic!("head without anchor {s:?}"),
|
|
|
|
|
'|' if anchor == None => head = Some(i),
|
|
|
|
|
'|' => ranges.push(Range::new(anchor.take().unwrap(), i)),
|
|
|
|
|
'@' if primary != None => panic!("head (primary) already appeared {s:?}"),
|
|
|
|
|
'@' if head != None => panic!("head (primary) without anchor {s:?}"),
|
|
|
|
|
'@' if anchor == None => {
|
|
|
|
|
primary = Some(ranges.len());
|
|
|
|
|
head = Some(i);
|
|
|
|
|
let mut iter = s.chars().peekable();
|
|
|
|
|
let mut left = String::with_capacity(s.len());
|
|
|
|
|
'outer: while let Some(c) = iter.next() {
|
|
|
|
|
let start = left.len();
|
|
|
|
|
if c == '#' {
|
|
|
|
|
if iter.next_if_eq(&'[').is_some() {
|
|
|
|
|
if primary.is_some() {
|
|
|
|
|
panic!("primary `#[` already appeared {left:?} {s:?}");
|
|
|
|
|
}
|
|
|
|
|
'@' => {
|
|
|
|
|
primary = Some(ranges.len());
|
|
|
|
|
ranges.push(Range::new(anchor.take().unwrap(), i));
|
|
|
|
|
if iter.next_if_eq(&'|').is_some() {
|
|
|
|
|
while let Some(c) = iter.next() {
|
|
|
|
|
if c == ']' && iter.next_if_eq(&'#').is_some() {
|
|
|
|
|
primary = Some(ranges.len());
|
|
|
|
|
ranges.push(Range::new(left.len(), start));
|
|
|
|
|
continue 'outer;
|
|
|
|
|
} else {
|
|
|
|
|
left.push(c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
panic!("missing primary end `]#` {left:?} {s:?}");
|
|
|
|
|
} else {
|
|
|
|
|
while let Some(c) = iter.next() {
|
|
|
|
|
if c == '|' {
|
|
|
|
|
if let Some(cc) = iter.next_if_eq(&']') {
|
|
|
|
|
if iter.next_if_eq(&'#').is_some() {
|
|
|
|
|
primary = Some(ranges.len());
|
|
|
|
|
ranges.push(Range::new(start, left.len()));
|
|
|
|
|
continue 'outer;
|
|
|
|
|
} else {
|
|
|
|
|
left.push(c);
|
|
|
|
|
left.push(cc);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
left.push(c);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
left.push(c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
panic!("missing primary end `|]#` {left:?} {s:?}");
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
i += 1;
|
|
|
|
|
return true;
|
|
|
|
|
} else if iter.next_if_eq(&'(').is_some() {
|
|
|
|
|
if iter.next_if_eq(&'|').is_some() {
|
|
|
|
|
while let Some(c) = iter.next() {
|
|
|
|
|
if c == ')' && iter.next_if_eq(&'#').is_some() {
|
|
|
|
|
ranges.push(Range::new(left.len(), start));
|
|
|
|
|
continue 'outer;
|
|
|
|
|
} else {
|
|
|
|
|
left.push(c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
panic!("missing end `)#` {left:?} {s:?}");
|
|
|
|
|
} else {
|
|
|
|
|
while let Some(c) = iter.next() {
|
|
|
|
|
if c == '|' {
|
|
|
|
|
if let Some(cc) = iter.next_if_eq(&')') {
|
|
|
|
|
if iter.next_if_eq(&'#').is_some() {
|
|
|
|
|
ranges.push(Range::new(start, left.len()));
|
|
|
|
|
continue 'outer;
|
|
|
|
|
} else {
|
|
|
|
|
left.push(c);
|
|
|
|
|
left.push(cc);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
left.push(c);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
left.push(c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
panic!("missing end `|)#` {left:?} {s:?}");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
false
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
if head.is_some() {
|
|
|
|
|
panic!("missing anchor (|) {s:?}");
|
|
|
|
|
}
|
|
|
|
|
if anchor.is_some() {
|
|
|
|
|
panic!("missing head (^) {s:?}");
|
|
|
|
|
} else {
|
|
|
|
|
left.push(c);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
left.push(c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let primary = match primary {
|
|
|
|
|
Some(i) => i,
|
|
|
|
|
None => panic!("missing primary (@) {s:?}"),
|
|
|
|
|
None => panic!("missing primary `#[|]#` {s:?}"),
|
|
|
|
|
};
|
|
|
|
|
let selection = Selection::new(ranges, primary);
|
|
|
|
|
(s, selection)
|
|
|
|
|
(left, selection)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convert test string and selection to annotated test string.
|
|
|
|
|
///
|
|
|
|
|
/// `^` for `anchor` and `|` for head (`@` for primary).
|
|
|
|
|
/// `#[|` for primary selection with head before anchor followed by `]#`.
|
|
|
|
|
/// `#(|` for secondary selection with head before anchor followed by `)#`.
|
|
|
|
|
/// `#[` for primary selection with head after anchor followed by `|]#`.
|
|
|
|
|
/// `#(` for secondary selection with head after anchor followed by `|)#`.
|
|
|
|
|
///
|
|
|
|
|
/// # Examples
|
|
|
|
|
///
|
|
|
|
@ -85,28 +132,30 @@ pub fn print(s: &str) -> (String, Selection) {
|
|
|
|
|
///
|
|
|
|
|
/// assert_eq!(
|
|
|
|
|
/// plain("abc", Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0)),
|
|
|
|
|
/// "^a@b|c^".to_owned()
|
|
|
|
|
/// "#[a|]#b#(|c)#".to_owned()
|
|
|
|
|
/// );
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn plain(s: &str, selection: Selection) -> String {
|
|
|
|
|
let primary = selection.primary_index();
|
|
|
|
|
let mut out = String::with_capacity(s.len() + 2 * selection.len());
|
|
|
|
|
let mut out = String::with_capacity(s.len() + 5 * selection.len());
|
|
|
|
|
out.push_str(s);
|
|
|
|
|
let mut insertion: Vec<_> = selection
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.flat_map(|(i, range)| {
|
|
|
|
|
[
|
|
|
|
|
// sort like this before reversed so anchor < head later
|
|
|
|
|
(range.head, if i == primary { '@' } else { '|' }),
|
|
|
|
|
(range.anchor, '^'),
|
|
|
|
|
]
|
|
|
|
|
// sort like this before reversed so anchor < head later
|
|
|
|
|
match (range.anchor < range.head, i == primary) {
|
|
|
|
|
(true, true) => [(range.anchor, "#["), (range.head, "|]#")],
|
|
|
|
|
(true, false) => [(range.anchor, "#("), (range.head, "|)#")],
|
|
|
|
|
(false, true) => [(range.anchor, "]#"), (range.head, "#[|")],
|
|
|
|
|
(false, false) => [(range.anchor, ")#"), (range.head, "#(|")],
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
// insert in reverse order
|
|
|
|
|
insertion.sort_unstable_by_key(|k| Reverse(k.0));
|
|
|
|
|
for (i, c) in insertion {
|
|
|
|
|
out.insert(i, c);
|
|
|
|
|
for (i, s) in insertion {
|
|
|
|
|
out.insert_str(i, s);
|
|
|
|
|
}
|
|
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|