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 | { |
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 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 | |