fluent_syntax/parser/
pattern.rs1use super::errors::{ErrorKind, ParserError};
2use super::{core::Parser, core::Result, slice::Slice};
3use crate::ast;
4
5#[derive(Debug, PartialEq)]
6enum TextElementTermination {
7 LineFeed,
8 Crlf,
9 PlaceableStart,
10 Eof,
11}
12
13#[derive(Debug, PartialEq)]
16enum TextElementPosition {
17 InitialLineStart,
18 LineStart,
19 Continuation,
20}
21
22#[derive(Debug)]
26enum PatternElementPlaceholders<S> {
27 Placeable(ast::Expression<S>),
28 TextElement(usize, usize, usize, TextElementPosition),
30}
31
32#[derive(Debug, PartialEq)]
36enum TextElementType {
37 Blank,
38 NonBlank,
39}
40
41impl<'s, S> Parser<S>
42where
43 S: Slice<'s>,
44{
45 pub(super) fn get_pattern(&mut self) -> Result<Option<ast::Pattern<S>>> {
46 let mut elements = vec![];
47 let mut last_non_blank = None;
48 let mut common_indent = None;
49
50 self.skip_blank_inline();
51
52 let mut text_element_role = if self.skip_eol() {
53 self.skip_blank_block();
54 TextElementPosition::LineStart
55 } else {
56 TextElementPosition::InitialLineStart
57 };
58
59 while self.ptr < self.length {
60 if self.take_byte_if(b'{') {
61 if text_element_role == TextElementPosition::LineStart {
62 common_indent = Some(0);
63 }
64 let exp = self.get_placeable()?;
65 last_non_blank = Some(elements.len());
66 elements.push(PatternElementPlaceholders::Placeable(exp));
67 text_element_role = TextElementPosition::Continuation;
68 } else {
69 let slice_start = self.ptr;
70 let mut indent = 0;
71 if text_element_role == TextElementPosition::LineStart {
72 indent = self.skip_blank_inline();
73 if let Some(b) = get_current_byte!(self) {
74 if indent == 0 {
75 if b != &b'\r' && b != &b'\n' {
76 break;
77 }
78 } else if !Self::is_byte_pattern_continuation(*b) {
79 self.ptr = slice_start;
80 break;
81 }
82 } else {
83 break;
84 }
85 }
86 let (start, end, text_element_type, termination_reason) = self.get_text_slice()?;
87 if start != end {
88 if text_element_role == TextElementPosition::LineStart
89 && text_element_type == TextElementType::NonBlank
90 {
91 if let Some(common) = common_indent {
92 if indent < common {
93 common_indent = Some(indent);
94 }
95 } else {
96 common_indent = Some(indent);
97 }
98 }
99 if text_element_role != TextElementPosition::LineStart
100 || text_element_type == TextElementType::NonBlank
101 || termination_reason == TextElementTermination::LineFeed
102 {
103 if text_element_type == TextElementType::NonBlank {
104 last_non_blank = Some(elements.len());
105 }
106 elements.push(PatternElementPlaceholders::TextElement(
107 slice_start,
108 end,
109 indent,
110 text_element_role,
111 ));
112 }
113 }
114
115 text_element_role = match termination_reason {
116 TextElementTermination::LineFeed => TextElementPosition::LineStart,
117 TextElementTermination::Crlf => TextElementPosition::LineStart,
118 TextElementTermination::PlaceableStart => TextElementPosition::Continuation,
119 TextElementTermination::Eof => TextElementPosition::Continuation,
120 };
121 }
122 }
123
124 if let Some(last_non_blank) = last_non_blank {
125 let elements = elements
126 .into_iter()
127 .take(last_non_blank + 1)
128 .enumerate()
129 .map(|(i, elem)| match elem {
130 PatternElementPlaceholders::Placeable(expression) => {
131 ast::PatternElement::Placeable { expression }
132 }
133 PatternElementPlaceholders::TextElement(start, end, indent, role) => {
134 let start = if role == TextElementPosition::LineStart {
135 common_indent.map_or_else(
136 || start + indent,
137 |common_indent| start + std::cmp::min(indent, common_indent),
138 )
139 } else {
140 start
141 };
142 let mut value = self.source.slice(start..end);
143 if last_non_blank == i {
144 value.trim();
145 }
146 ast::PatternElement::TextElement { value }
147 }
148 })
149 .collect();
150 return Ok(Some(ast::Pattern { elements }));
151 }
152
153 Ok(None)
154 }
155
156 fn get_text_slice(
157 &mut self,
158 ) -> Result<(usize, usize, TextElementType, TextElementTermination)> {
159 let start_pos = self.ptr;
160 let Some(rest) = get_remaining_bytes!(self) else {
161 return Ok((
162 start_pos,
163 self.ptr,
164 TextElementType::Blank,
165 TextElementTermination::Eof,
166 ));
167 };
168 let end = memchr::memchr3(b'\n', b'{', b'}', rest);
169 let element_type = |text: &[u8]| {
170 if text.iter().any(|&c| c != b' ') {
171 TextElementType::NonBlank
172 } else {
173 TextElementType::Blank
174 }
175 };
176 match end.map(|p| &rest[..=p]) {
177 Some([text @ .., b'}']) => {
178 self.ptr += text.len();
179 error!(ErrorKind::UnbalancedClosingBrace, self.ptr)
180 }
181 Some([text @ .., b'\r', b'\n']) => {
182 self.ptr += text.len() + 1;
183 Ok((
184 start_pos,
185 self.ptr - 1,
186 element_type(text),
187 TextElementTermination::Crlf,
188 ))
189 }
190 Some([text @ .., b'\n']) => {
191 self.ptr += text.len() + 1;
192 Ok((
193 start_pos,
194 self.ptr,
195 element_type(text),
196 TextElementTermination::LineFeed,
197 ))
198 }
199 Some([text @ .., b'{']) => {
200 self.ptr += text.len();
201 Ok((
202 start_pos,
203 self.ptr,
204 element_type(text),
205 TextElementTermination::PlaceableStart,
206 ))
207 }
208 None => {
209 self.ptr += rest.len();
210 Ok((
211 start_pos,
212 self.ptr,
213 element_type(rest),
214 TextElementTermination::Eof,
215 ))
216 }
217 _ => unreachable!(),
218 }
219 }
220}