use super::{format_expand1, format_skip, ExpandState, FormatVariables};
#[derive(Debug)]
pub(super) struct FormatModifier {
pub(super) modifier: String,
pub(super) argv: Vec<String>,
}
const SINGLE_NO_ARG: &[u8] = b"labcdnwETSWPL<>!";
const SINGLE_WITH_ARG: &[u8] = b"mCLNPSst=pReqW";
fn is_modifier_end(ch: u8) -> bool {
ch == b';' || ch == b':'
}
pub(super) fn parse_modifiers<'a, V>(
state: &mut ExpandState,
body: &'a str,
variables: &V,
) -> (Vec<FormatModifier>, &'a str)
where
V: FormatVariables + ?Sized,
{
let bytes = body.as_bytes();
let mut modifiers = Vec::new();
let mut i = 0;
while i < bytes.len() && bytes[i] != b':' {
if bytes[i] == b';' {
i += 1;
}
if i >= bytes.len() {
break;
}
if SINGLE_NO_ARG.contains(&bytes[i]) && i + 1 < bytes.len() && is_modifier_end(bytes[i + 1])
{
modifiers.push(FormatModifier {
modifier: String::from(bytes[i] as char),
argv: Vec::new(),
});
i += 1;
continue;
}
if let Some(pair) = double_no_arg_at(bytes, i) {
if i + 2 < bytes.len() && is_modifier_end(bytes[i + 2]) {
modifiers.push(FormatModifier {
modifier: pair.to_owned(),
argv: Vec::new(),
});
i += 2;
continue;
}
}
if !SINGLE_WITH_ARG.contains(&bytes[i]) {
break;
}
let c = bytes[i] as char;
if i + 1 < bytes.len() && is_modifier_end(bytes[i + 1]) {
modifiers.push(FormatModifier {
modifier: String::from(c),
argv: Vec::new(),
});
i += 1;
continue;
}
if i + 1 >= bytes.len() {
break;
}
let next = bytes[i + 1];
if !next.is_ascii_punctuation() || next == b'-' {
let rest = &bytes[i + 1..];
if let Some(end_off) = format_skip(rest, b":;") {
let raw = &body[i + 1..i + 1 + end_off];
let expanded = format_expand1(state, raw, variables);
modifiers.push(FormatModifier {
modifier: String::from(c),
argv: vec![expanded],
});
i = i + 1 + end_off;
continue;
}
break;
}
let wrapper = next;
let mut argv = Vec::new();
let mut j = i + 1; loop {
if j < bytes.len()
&& bytes[j] == wrapper
&& j + 1 < bytes.len()
&& is_modifier_end(bytes[j + 1])
{
j += 1;
break;
}
let rest = &bytes[j + 1..];
let end_chars = [wrapper, b';', b':'];
let end_off = match format_skip(rest, &end_chars) {
Some(off) => off,
None => break,
};
let raw = &body[j + 1..j + 1 + end_off];
let expanded = format_expand1(state, raw, variables);
argv.push(expanded);
j = j + 1 + end_off;
if j >= bytes.len() || is_modifier_end(bytes[j]) {
break;
}
}
modifiers.push(FormatModifier {
modifier: String::from(c),
argv,
});
i = j;
continue;
}
if i < bytes.len() && bytes[i] == b':' {
(modifiers, &body[i + 1..])
} else {
(Vec::new(), body)
}
}
fn double_no_arg_at(bytes: &[u8], index: usize) -> Option<&'static str> {
match bytes.get(index..index + 2)? {
b"||" => Some("||"),
b"&&" => Some("&&"),
b"!!" => Some("!!"),
b"!=" => Some("!="),
b"==" => Some("=="),
b"<=" => Some("<="),
b">=" => Some(">="),
_ => None,
}
}