1use 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// This enum tracks the placement of the text element in the pattern, which is needed for
14// dedentation logic.
15#[derive(Debug, PartialEq)]
16enum 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)]
26enum 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)]
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 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