1 use super::errors::{ErrorKind, ParserError};
2 use super::{Parser, Result, Slice};
3 use crate::ast;
4 
5 #[derive(Debug, PartialEq)]
6 enum TextElementTermination {
7     LineFeed,
8     CRLF,
9     PlaceableStart,
10     EOF,
11 }
12 
13 // This enum tracks the placement of the text element in the pattern, which is needed for
14 // dedentation logic.
15 #[derive(Debug, PartialEq)]
16 enum TextElementPosition {
17     InitialLineStart,
18     LineStart,
19     Continuation,
20 }
21 
22 // This enum allows us to mark pointers in the source which will later become text elements
23 // but without slicing them out of the source string. This makes the indentation adjustments
24 // cheaper since they'll happen on the pointers, rather than extracted slices.
25 #[derive(Debug)]
26 enum PatternElementPlaceholders<S> {
27     Placeable(ast::Expression<S>),
28     // (start, end, indent, position)
29     TextElement(usize, usize, usize, TextElementPosition),
30 }
31 
32 // This enum tracks whether the text element is blank or not.
33 // This is important to identify text elements which should not be taken into account
34 // when calculating common indent.
35 #[derive(Debug, PartialEq)]
36 enum TextElementType {
37     Blank,
38     NonBlank,
39 }
40 
41 impl<'s, S> Parser<S>
42 where
43     S: Slice<'s>,
44 {
get_pattern(&mut self) -> Result<Option<ast::Pattern<S>>>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.is_current_byte(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 self.ptr >= self.length {
74                         break;
75                     }
76                     let b = self.source.as_ref().as_bytes().get(self.ptr);
77                     if indent == 0 {
78                         if b != Some(&b'\n') {
79                             break;
80                         }
81                     } else if !Self::is_byte_pattern_continuation(*b.unwrap()) {
82                         self.ptr = slice_start;
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::Continuation,
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                             ast::PatternElement::TextElement { value }
146                         } else {
147                             ast::PatternElement::TextElement { value }
148                         }
149                     }
150                 })
151                 .collect();
152             return Ok(Some(ast::Pattern { elements }));
153         }
154 
155         Ok(None)
156     }
157 
get_text_slice( &mut self, ) -> Result<(usize, usize, TextElementType, TextElementTermination)>158     fn get_text_slice(
159         &mut self,
160     ) -> Result<(usize, usize, TextElementType, TextElementTermination)> {
161         let start_pos = self.ptr;
162         let mut text_element_type = TextElementType::Blank;
163 
164         while let Some(b) = self.source.as_ref().as_bytes().get(self.ptr) {
165             match b {
166                 b' ' => self.ptr += 1,
167                 b'\n' => {
168                     self.ptr += 1;
169                     return Ok((
170                         start_pos,
171                         self.ptr,
172                         text_element_type,
173                         TextElementTermination::LineFeed,
174                     ));
175                 }
176                 b'\r' if self.is_byte_at(b'\n', self.ptr + 1) => {
177                     self.ptr += 1;
178                     return Ok((
179                         start_pos,
180                         self.ptr - 1,
181                         text_element_type,
182                         TextElementTermination::CRLF,
183                     ));
184                 }
185                 b'{' => {
186                     return Ok((
187                         start_pos,
188                         self.ptr,
189                         text_element_type,
190                         TextElementTermination::PlaceableStart,
191                     ));
192                 }
193                 b'}' => {
194                     return error!(ErrorKind::UnbalancedClosingBrace, self.ptr);
195                 }
196                 _ => {
197                     text_element_type = TextElementType::NonBlank;
198                     self.ptr += 1
199                 }
200             }
201         }
202         Ok((
203             start_pos,
204             self.ptr,
205             text_element_type,
206             TextElementTermination::EOF,
207         ))
208     }
209 }
210