use crate::syntax::TextUnit::Literal;
use crate::syntax::Word;
use crate::syntax::WordUnit::{self, Tilde, Unquoted};
fn parse_tilde<'a, I>(units: I, delimit_at_colon: bool) -> Option<(usize, String, bool)>
where
I: IntoIterator<Item = &'a WordUnit>,
{
let mut units = units.into_iter();
if units.next() != Some(&Unquoted(Literal('~'))) {
return None;
}
let mut name = String::new();
let mut count = 1;
for unit in units {
match unit {
Unquoted(Literal('/')) => return Some((count, name, true)),
Unquoted(Literal(':')) if delimit_at_colon => break,
Unquoted(Literal(c)) => {
name.push(*c);
count += 1;
}
_ => return None,
}
}
Some((count, name, false))
}
impl Word {
#[inline]
pub fn parse_tilde_front(&mut self) {
if let Some((len, name, followed_by_slash)) = parse_tilde(&self.units, false) {
self.units.splice(
..len,
std::iter::once(Tilde {
name,
followed_by_slash,
}),
);
}
}
#[inline]
pub fn parse_tilde_everywhere(&mut self) {
self.parse_tilde_everywhere_after(0);
}
pub fn parse_tilde_everywhere_after(&mut self, index: usize) {
let mut i = index;
loop {
if let Some((len, name, followed_by_slash)) = parse_tilde(&self.units[i..], true) {
self.units.splice(
i..i + len,
std::iter::once(Tilde {
name,
followed_by_slash,
}),
);
i += 1;
}
let Some(colon) = self.units[i..]
.iter()
.position(|unit| unit == &Unquoted(Literal(':')))
else {
break;
};
i += colon + 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::syntax::Text;
use crate::syntax::TextUnit::Backslashed;
use crate::syntax::WordUnit::{DoubleQuote, SingleQuote};
use std::str::FromStr;
fn parse_tilde_front(word: &Word) -> Word {
let mut word = word.clone();
word.parse_tilde_front();
word
}
fn parse_tilde_everywhere(word: &Word) -> Word {
let mut word = word.clone();
word.parse_tilde_everywhere();
word
}
#[test]
fn word_parse_tilde_front_not_starting_with_tilde() {
let input = Word::from_str("").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result, input);
let input = Word::from_str("a").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result, input);
let input = Word::from_str("''").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result, input);
}
#[test]
fn word_parse_tilde_front_only_tilde() {
let input = Word::from_str("~").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[Tilde {
name: "".to_string(),
followed_by_slash: false
}]
);
}
#[test]
fn word_parse_tilde_front_with_name() {
let input = Word::from_str("~foo").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[Tilde {
name: "foo".to_string(),
followed_by_slash: false
}]
);
}
#[test]
fn word_parse_tilde_front_ending_with_slash() {
let input = Word::from_str("~bar/''").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Tilde {
name: "bar".to_string(),
followed_by_slash: true,
},
Unquoted(Literal('/')),
SingleQuote("".to_string()),
]
);
}
#[test]
fn word_parse_tilde_front_including_colon() {
let input = Word::from_str("~bar:baz").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[Tilde {
name: "bar:baz".to_string(),
followed_by_slash: false
}]
);
}
#[test]
fn word_parse_tilde_front_interrupted_by_non_literal() {
let input = Word::from_str(r"~foo\/").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Unquoted(Literal('~')),
Unquoted(Literal('f')),
Unquoted(Literal('o')),
Unquoted(Literal('o')),
Unquoted(Backslashed('/')),
]
);
let input = Word::from_str("~bar''").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Unquoted(Literal('~')),
Unquoted(Literal('b')),
Unquoted(Literal('a')),
Unquoted(Literal('r')),
SingleQuote("".to_string()),
]
);
}
#[test]
fn word_parse_tilde_front_not_after_colon() {
let input = Word::from_str("a~").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result, input);
let input = Word::from_str("/~a").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result, input);
let input = Word::from_str("''~/").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result, input);
}
#[test]
fn word_parse_tilde_front_after_colon() {
let input = Word::from_str(":~").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[Unquoted(Literal(':')), Unquoted(Literal('~'))]
);
let input = Word::from_str(":~foo/a:~bar").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Unquoted(Literal(':')),
Unquoted(Literal('~')),
Unquoted(Literal('f')),
Unquoted(Literal('o')),
Unquoted(Literal('o')),
Unquoted(Literal('/')),
Unquoted(Literal('a')),
Unquoted(Literal(':')),
Unquoted(Literal('~')),
Unquoted(Literal('b')),
Unquoted(Literal('a')),
Unquoted(Literal('r')),
]
);
let input = Word::from_str("~a/b:~c/d").unwrap();
let result = parse_tilde_front(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Tilde {
name: "a".to_string(),
followed_by_slash: true,
},
Unquoted(Literal('/')),
Unquoted(Literal('b')),
Unquoted(Literal(':')),
Unquoted(Literal('~')),
Unquoted(Literal('c')),
Unquoted(Literal('/')),
Unquoted(Literal('d')),
]
);
}
#[test]
fn word_parse_tilde_everywhere_not_starting_with_tilde() {
let input = Word::from_str("").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result, input);
let input = Word::from_str("a").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result, input);
let input = Word::from_str("''").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result, input);
}
#[test]
fn word_parse_tilde_everywhere_only_tilde() {
let input = Word::from_str("~").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[Tilde {
name: "".to_string(),
followed_by_slash: false
}]
);
}
#[test]
fn word_parse_tilde_everywhere_with_name() {
let input = Word::from_str("~foo").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[Tilde {
name: "foo".to_string(),
followed_by_slash: false
}]
);
}
#[test]
fn word_parse_tilde_everywhere_ending_with_slash() {
let input = Word::from_str("~bar/''").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Tilde {
name: "bar".to_string(),
followed_by_slash: true
},
Unquoted(Literal('/')),
SingleQuote("".to_string()),
]
);
}
#[test]
fn word_parse_tilde_everywhere_ending_with_colon() {
let input = Word::from_str("~bar:\"\"").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Tilde {
name: "bar".to_string(),
followed_by_slash: false
},
Unquoted(Literal(':')),
DoubleQuote(Text(vec![])),
]
);
}
#[test]
fn word_parse_tilde_everywhere_interrupted_by_non_literal() {
let input = Word::from_str(r"~foo\/").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Unquoted(Literal('~')),
Unquoted(Literal('f')),
Unquoted(Literal('o')),
Unquoted(Literal('o')),
Unquoted(Backslashed('/')),
]
);
let input = Word::from_str("~bar''").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Unquoted(Literal('~')),
Unquoted(Literal('b')),
Unquoted(Literal('a')),
Unquoted(Literal('r')),
SingleQuote("".to_string()),
]
);
}
#[test]
fn word_parse_tilde_everywhere_not_after_colon() {
let input = Word::from_str("a~").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result, input);
let input = Word::from_str("/~a").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result, input);
let input = Word::from_str("''~/").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result, input);
}
#[test]
fn word_parse_tilde_everywhere_after_colon() {
let input = Word::from_str(":~").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Unquoted(Literal(':')),
Tilde {
name: "".to_string(),
followed_by_slash: false
}
]
);
let input = Word::from_str(":~foo/a:~bar").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Unquoted(Literal(':')),
Tilde {
name: "foo".to_string(),
followed_by_slash: true,
},
Unquoted(Literal('/')),
Unquoted(Literal('a')),
Unquoted(Literal(':')),
Tilde {
name: "bar".to_string(),
followed_by_slash: false
},
]
);
let input = Word::from_str("~a/b:~c/d::~").unwrap();
let result = parse_tilde_everywhere(&input);
assert_eq!(result.location, input.location);
assert_eq!(
result.units,
[
Tilde {
name: "a".to_string(),
followed_by_slash: true,
},
Unquoted(Literal('/')),
Unquoted(Literal('b')),
Unquoted(Literal(':')),
Tilde {
name: "c".to_string(),
followed_by_slash: true,
},
Unquoted(Literal('/')),
Unquoted(Literal('d')),
Unquoted(Literal(':')),
Unquoted(Literal(':')),
Tilde {
name: "".to_string(),
followed_by_slash: false
},
]
);
}
}