use crate::dialect::Dialect;
use crate::error::{ErrorCode, RexactError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Flags {
pub i: bool,
pub m: bool,
pub s: bool,
pub u: bool,
pub x: bool,
pub a: bool,
}
impl Flags {
pub fn none() -> Self {
Self::default()
}
pub fn union(self, o: Flags) -> Flags {
Flags {
i: self.i | o.i,
m: self.m | o.m,
s: self.s | o.s,
u: self.u | o.u,
x: self.x | o.x,
a: self.a | o.a,
}
}
pub fn minus(self, o: Flags) -> Flags {
Flags {
i: self.i & !o.i,
m: self.m & !o.m,
s: self.s & !o.s,
u: self.u & !o.u,
x: self.x & !o.x,
a: self.a & !o.a,
}
}
fn set(&mut self, c: char) -> bool {
let slot = match c {
'i' => &mut self.i,
'm' => &mut self.m,
's' => &mut self.s,
'u' => &mut self.u,
'x' => &mut self.x,
'a' => &mut self.a,
_ => return false,
};
*slot = true;
true
}
pub fn valid_letters(dialect: Dialect) -> &'static str {
match dialect {
Dialect::Ecma => "i m s u (g y d accepted and ignored)",
Dialect::Python => "i m s x a (u implicit)",
Dialect::Go => "i m s (U accepted and ignored)",
Dialect::Rust => "i m s u x a (a = ASCII mode, like (?-u))",
Dialect::Pcre2 => "i m s u x",
Dialect::Java => "i m s u x (u = UNICODE_CASE and UNICODE_CHARACTER_CLASS)",
}
}
pub fn from_letters(
dialect: Dialect,
letters: &str,
) -> Result<(Flags, Vec<String>), RexactError> {
let bad = |why: String| {
let mut e = RexactError::new(ErrorCode::P003);
e.detail = Some(format!(
"{why}; valid flags: {}",
Flags::valid_letters(dialect)
));
e
};
let mut f = Flags::none();
let mut notes = Vec::new();
let mut seen = String::new();
for c in letters.chars() {
if seen.contains(c) && dialect == Dialect::Ecma {
return Err(bad(format!("duplicate flag '{c}'")));
}
seen.push(c);
let allowed = match dialect {
Dialect::Ecma => matches!(c, 'i' | 'm' | 's' | 'u'),
Dialect::Python => matches!(c, 'i' | 'm' | 's' | 'x' | 'a'),
Dialect::Go => matches!(c, 'i' | 'm' | 's'),
Dialect::Rust => matches!(c, 'i' | 'm' | 's' | 'u' | 'x' | 'a'),
Dialect::Pcre2 | Dialect::Java => matches!(c, 'i' | 'm' | 's' | 'u' | 'x'),
};
if allowed {
f.set(c);
} else if dialect == Dialect::Ecma && matches!(c, 'g' | 'y' | 'd') {
notes.push(format!(
"flag '{c}' does not change the language and is ignored"
));
} else if dialect == Dialect::Ecma && c == 'v' {
let mut e = RexactError::new(ErrorCode::U007);
e.detail = Some("flag 'v' (set notation) is not yet supported".into());
return Err(e);
} else if dialect == Dialect::Go && c == 'U' {
notes
.push("flag 'U' only swaps greediness and does not change the language".into());
} else if dialect == Dialect::Python && c == 'u' {
notes.push("flag 'u' is implicit for Python str patterns".into());
} else {
return Err(bad(format!(
"flag '{c}' is not valid for {}",
dialect.name()
)));
}
}
Ok((f, notes))
}
}