1 use super::errors::{ErrorKind, ParserError};
2 use super::{core::Parser, core::Result, slice::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.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 
get_text_slice( &mut self, ) -> Result<(usize, usize, TextElementType, TextElementTermination)>156     fn get_text_slice(
157         &mut self,
158     ) -> Result<(usize, usize, TextElementType, TextElementTermination)> {
159         let start_pos = self.ptr;
160         let mut text_element_type = TextElementType::Blank;
161 
162         while let Some(b) = get_current_byte!(self) {
163             match b {
164                 b' ' => self.ptr += 1,
165                 b'\n' => {
166                     self.ptr += 1;
167                     return Ok((
168                         start_pos,
169                         self.ptr,
170                         text_element_type,
171                         TextElementTermination::LineFeed,
172                     ));
173                 }
174                 b'\r' if self.is_byte_at(b'\n', self.ptr + 1) => {
175                     self.ptr += 1;
176                     return Ok((
177                         start_pos,
178                         self.ptr - 1,
179                         text_element_type,
180                         TextElementTermination::CRLF,
181                     ));
182                 }
183                 b'{' => {
184                     return Ok((
185                         start_pos,
186                         self.ptr,
187                         text_element_type,
188                         TextElementTermination::PlaceableStart,
189                     ));
190                 }
191                 b'}' => {
192                     return error!(ErrorKind::UnbalancedClosingBrace, self.ptr);
193                 }
194                 _ => {
195                     text_element_type = TextElementType::NonBlank;
196                     self.ptr += 1
197                 }
198             }
199         }
200         Ok((
201             start_pos,
202             self.ptr,
203             text_element_type,
204             TextElementTermination::EOF,
205         ))
206     }
207 }
208