metrale_grammar/regex/
converter.rs1use super::RegexError;
7use super::cursor::Cursor;
8use super::escape_handlers::handle_escape;
9use super::sub_handlers::quant_char;
10use crate::support::encoding::TCodepoint;
11use crate::support::escape::print_as_escaped;
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ConvertOutput {
17 pub ebnf: String,
19 pub warnings: Vec<String>,
22}
23
24pub struct RegexConverter {
27 pub(super) cur: Cursor,
28 pub(super) result: String,
29 pub(super) parenthesis_level: i32,
30}
31
32impl RegexConverter {
33 pub fn new(regex: &str) -> Result<Self, RegexError> {
36 Ok(RegexConverter {
37 cur: Cursor::new(regex)?,
38 result: String::new(),
39 parenthesis_level: 0,
40 })
41 }
42
43 fn add_segment(&mut self, element: &str) {
46 if !self.result.is_empty() {
47 self.result.push(' ');
48 }
49 self.result.push_str(element);
50 }
51
52 pub fn convert(mut self) -> Result<ConvertOutput, RegexError> {
54 let mut is_empty = true;
55 while !self.cur.at_end() {
56 let c = self.cur.peek();
57 match c {
58 _ if c == '^' as TCodepoint => {
59 if !self.cur.at_start() {
60 self.cur.warn(
61 "'^' should be at the start of the regex, but found in the \
62 middle. It is ignored.",
63 );
64 }
65 self.cur.bump();
66 }
67 _ if c == '$' as TCodepoint => {
68 if !self.cur.at_last() {
69 self.cur.warn(
70 "'$' should be at the end of the regex, but found in the \
71 middle. It is ignored.",
72 );
73 }
74 self.cur.bump();
75 }
76 _ if c == '[' as TCodepoint => {
77 is_empty = false;
78 let cls = self.handle_character_class()?;
79 self.add_segment(&cls);
80 }
81 _ if c == '(' as TCodepoint => {
82 is_empty = false;
83 self.cur.bump();
84 self.parenthesis_level += 1;
85 self.add_segment("(");
86 if !self.cur.at_end() && self.cur.peek() == '?' as TCodepoint {
87 self.cur.bump();
88 self.handle_group_modifier()?;
89 }
90 }
91 _ if c == ')' as TCodepoint => {
92 is_empty = false;
93 if self.parenthesis_level == 0 {
94 return Err(self.cur.error("Unmatched ')'"));
95 }
96 if self.cur.prev() == Some('|' as TCodepoint) {
99 self.add_segment("\"\"");
100 }
101 self.parenthesis_level -= 1;
102 self.add_segment(")");
103 self.cur.bump();
104 }
105 _ if c == '*' as TCodepoint || c == '+' as TCodepoint || c == '?' as TCodepoint => {
106 is_empty = false;
107 self.result.push(quant_char(c));
108 self.cur.bump();
109 self.consume_optional_non_greedy();
110 self.reject_consecutive_quantifier()?;
111 }
112 _ if c == '{' as TCodepoint => {
113 is_empty = false;
114 let rep = self.handle_repetition_range()?;
115 self.result.push_str(&rep);
116 self.consume_optional_non_greedy();
117 self.reject_consecutive_quantifier()?;
118 }
119 _ if c == '|' as TCodepoint => {
120 is_empty = false;
121 self.add_segment("|");
122 self.cur.bump();
123 }
124 _ if c == '\\' as TCodepoint => {
125 is_empty = false;
126 let esc = handle_escape(&mut self.cur)?;
127 self.add_segment(&esc);
128 }
129 _ if c == '.' as TCodepoint => {
130 is_empty = false;
131 self.add_segment("[\\u0000-\\U0010FFFF]");
132 self.cur.bump();
133 }
134 _ => {
135 is_empty = false;
136 let escaped = print_as_escaped(c, &HashMap::new());
137 self.add_segment(&format!("\"{escaped}\""));
138 self.cur.bump();
139 }
140 }
141 }
142 if self.parenthesis_level != 0 {
143 return Err(self.cur.error("The parenthesis is not closed."));
144 }
145 if is_empty {
146 self.add_segment("\"\"");
147 }
148 Ok(ConvertOutput {
149 ebnf: self.result,
150 warnings: self.cur.warnings,
151 })
152 }
153
154 fn consume_optional_non_greedy(&mut self) {
157 if !self.cur.at_end() && self.cur.peek() == '?' as TCodepoint {
158 self.cur.bump();
159 }
160 }
161
162 fn reject_consecutive_quantifier(&self) -> Result<(), RegexError> {
166 if self.cur.at_end() {
167 return Ok(());
168 }
169 let c = self.cur.peek();
170 if c == '{' as TCodepoint
171 || c == '*' as TCodepoint
172 || c == '+' as TCodepoint
173 || c == '?' as TCodepoint
174 {
175 return Err(self
176 .cur
177 .error("Two consecutive repetition modifiers are not allowed."));
178 }
179 Ok(())
180 }
181}