1use crate::dialect::Dialect;
4use crate::error::{ErrorCode, RexactError};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub struct Flags {
10 pub i: bool,
12 pub m: bool,
14 pub s: bool,
16 pub u: bool,
18 pub x: bool,
20 pub a: bool,
22}
23
24impl Flags {
25 pub fn none() -> Self {
27 Self::default()
28 }
29
30 pub fn union(self, o: Flags) -> Flags {
32 Flags {
33 i: self.i | o.i,
34 m: self.m | o.m,
35 s: self.s | o.s,
36 u: self.u | o.u,
37 x: self.x | o.x,
38 a: self.a | o.a,
39 }
40 }
41
42 pub fn minus(self, o: Flags) -> Flags {
44 Flags {
45 i: self.i & !o.i,
46 m: self.m & !o.m,
47 s: self.s & !o.s,
48 u: self.u & !o.u,
49 x: self.x & !o.x,
50 a: self.a & !o.a,
51 }
52 }
53
54 fn set(&mut self, c: char) -> bool {
55 let slot = match c {
56 'i' => &mut self.i,
57 'm' => &mut self.m,
58 's' => &mut self.s,
59 'u' => &mut self.u,
60 'x' => &mut self.x,
61 'a' => &mut self.a,
62 _ => return false,
63 };
64 *slot = true;
65 true
66 }
67
68 pub fn valid_letters(dialect: Dialect) -> &'static str {
70 match dialect {
71 Dialect::Ecma => "i m s u (g y d accepted and ignored)",
72 Dialect::Python => "i m s x a (u implicit)",
73 Dialect::Go => "i m s (U accepted and ignored)",
74 Dialect::Rust => "i m s u x a (a = ASCII mode, like (?-u))",
75 Dialect::Pcre2 => "i m s u x",
76 Dialect::Java => "i m s u x (u = UNICODE_CASE and UNICODE_CHARACTER_CLASS)",
77 }
78 }
79
80 pub fn from_letters(
83 dialect: Dialect,
84 letters: &str,
85 ) -> Result<(Flags, Vec<String>), RexactError> {
86 let bad = |why: String| {
87 let mut e = RexactError::new(ErrorCode::P003);
88 e.detail = Some(format!(
89 "{why}; valid flags: {}",
90 Flags::valid_letters(dialect)
91 ));
92 e
93 };
94 let mut f = Flags::none();
95 let mut notes = Vec::new();
96 let mut seen = String::new();
97 for c in letters.chars() {
98 if seen.contains(c) && dialect == Dialect::Ecma {
99 return Err(bad(format!("duplicate flag '{c}'")));
100 }
101 seen.push(c);
102 let allowed = match dialect {
103 Dialect::Ecma => matches!(c, 'i' | 'm' | 's' | 'u'),
104 Dialect::Python => matches!(c, 'i' | 'm' | 's' | 'x' | 'a'),
105 Dialect::Go => matches!(c, 'i' | 'm' | 's'),
106 Dialect::Rust => matches!(c, 'i' | 'm' | 's' | 'u' | 'x' | 'a'),
107 Dialect::Pcre2 | Dialect::Java => matches!(c, 'i' | 'm' | 's' | 'u' | 'x'),
108 };
109 if allowed {
110 f.set(c);
111 } else if dialect == Dialect::Ecma && matches!(c, 'g' | 'y' | 'd') {
112 notes.push(format!(
113 "flag '{c}' does not change the language and is ignored"
114 ));
115 } else if dialect == Dialect::Ecma && c == 'v' {
116 let mut e = RexactError::new(ErrorCode::U007);
117 e.detail = Some("flag 'v' (set notation) is not yet supported".into());
118 return Err(e);
119 } else if dialect == Dialect::Go && c == 'U' {
120 notes
121 .push("flag 'U' only swaps greediness and does not change the language".into());
122 } else if dialect == Dialect::Python && c == 'u' {
123 notes.push("flag 'u' is implicit for Python str patterns".into());
124 } else {
125 return Err(bad(format!(
126 "flag '{c}' is not valid for {}",
127 dialect.name()
128 )));
129 }
130 }
131 Ok((f, notes))
132 }
133}