1use super::core::WordContext;
20use super::core::WordLexer;
21use crate::parser::core::Result;
22use crate::parser::error::Error;
23use crate::parser::error::SyntaxError;
24use crate::syntax::BackquoteUnit;
25use crate::syntax::TextUnit;
26
27impl WordLexer<'_, '_> {
28 async fn backquote_unit(&mut self) -> Result<Option<BackquoteUnit>> {
30 if self.skip_if(|c| c == '\\').await? {
31 let double_quote_escapable = match self.context {
32 WordContext::Word => false,
33 WordContext::Text => true,
34 };
35 let is_escapable =
36 |c| matches!(c, '$' | '`' | '\\') || c == '"' && double_quote_escapable;
37 if let Some(c) = self.consume_char_if(is_escapable).await? {
38 return Ok(Some(BackquoteUnit::Backslashed(c.value)));
39 } else {
40 return Ok(Some(BackquoteUnit::Literal('\\')));
41 }
42 }
43
44 if let Some(c) = self.consume_char_if(|c| c != '`').await? {
45 return Ok(Some(BackquoteUnit::Literal(c.value)));
46 }
47
48 Ok(None)
49 }
50
51 pub async fn backquote(&mut self) -> Result<Option<TextUnit>> {
62 let start = self.index();
63 let opening_location = match self.consume_char_if(|c| c == '`').await? {
64 None => return Ok(None),
65 Some(c) => c.location.clone(),
66 };
67
68 let mut content = Vec::new();
69 while let Some(unit) = self.backquote_unit().await? {
70 content.push(unit);
71 }
72
73 if self.skip_if(|c| c == '`').await? {
74 let location = self.location_range(start..self.index());
75 Ok(Some(TextUnit::Backquote { content, location }))
76 } else {
77 let cause = SyntaxError::UnclosedBackquote { opening_location }.into();
78 let location = self.location().await?.clone();
79 Err(Error { cause, location })
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::parser::error::ErrorCause;
88 use crate::parser::lex::Lexer;
89 use crate::source::Source;
90 use assert_matches::assert_matches;
91 use futures_util::FutureExt;
92
93 #[test]
94 fn lexer_backquote_not_backquote() {
95 let mut lexer = Lexer::with_code("X");
96 let mut lexer = WordLexer {
97 lexer: &mut lexer,
98 context: WordContext::Word,
99 };
100 let result = lexer.backquote().now_or_never().unwrap().unwrap();
101 assert_eq!(result, None);
102 }
103
104 #[test]
105 fn lexer_backquote_empty() {
106 let mut lexer = Lexer::with_code("``");
107 let mut lexer = WordLexer {
108 lexer: &mut lexer,
109 context: WordContext::Word,
110 };
111 let result = lexer.backquote().now_or_never().unwrap().unwrap().unwrap();
112 assert_matches!(result, TextUnit::Backquote { content, location } => {
113 assert_eq!(content, []);
114 assert_eq!(location.range, 0..2);
115 });
116
117 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
118 }
119
120 #[test]
121 fn lexer_backquote_literals() {
122 let mut lexer = Lexer::with_code("`echo`");
123 let mut lexer = WordLexer {
124 lexer: &mut lexer,
125 context: WordContext::Word,
126 };
127 let result = lexer.backquote().now_or_never().unwrap().unwrap().unwrap();
128 assert_matches!(result, TextUnit::Backquote { content, location } => {
129 assert_eq!(
130 content,
131 [
132 BackquoteUnit::Literal('e'),
133 BackquoteUnit::Literal('c'),
134 BackquoteUnit::Literal('h'),
135 BackquoteUnit::Literal('o')
136 ]
137 );
138 assert_eq!(location.range, 0..6);
139 });
140
141 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
142 }
143
144 #[test]
145 fn lexer_backquote_with_escapes_double_quote_escapable() {
146 let mut lexer = Lexer::with_code(r#"`a\a\$\`\\\"\'`"#);
147 let mut lexer = WordLexer {
148 lexer: &mut lexer,
149 context: WordContext::Text,
150 };
151 let result = lexer.backquote().now_or_never().unwrap().unwrap().unwrap();
152 assert_matches!(result, TextUnit::Backquote { content, location } => {
153 assert_eq!(
154 content,
155 [
156 BackquoteUnit::Literal('a'),
157 BackquoteUnit::Literal('\\'),
158 BackquoteUnit::Literal('a'),
159 BackquoteUnit::Backslashed('$'),
160 BackquoteUnit::Backslashed('`'),
161 BackquoteUnit::Backslashed('\\'),
162 BackquoteUnit::Backslashed('"'),
163 BackquoteUnit::Literal('\\'),
164 BackquoteUnit::Literal('\'')
165 ]
166 );
167 assert_eq!(location.range, 0..15);
168 });
169
170 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
171 }
172
173 #[test]
174 fn lexer_backquote_with_escapes_double_quote_not_escapable() {
175 let mut lexer = Lexer::with_code(r#"`a\a\$\`\\\"\'`"#);
176 let mut lexer = WordLexer {
177 lexer: &mut lexer,
178 context: WordContext::Word,
179 };
180 let result = lexer.backquote().now_or_never().unwrap().unwrap().unwrap();
181 assert_matches!(result, TextUnit::Backquote { content, location } => {
182 assert_eq!(
183 content,
184 [
185 BackquoteUnit::Literal('a'),
186 BackquoteUnit::Literal('\\'),
187 BackquoteUnit::Literal('a'),
188 BackquoteUnit::Backslashed('$'),
189 BackquoteUnit::Backslashed('`'),
190 BackquoteUnit::Backslashed('\\'),
191 BackquoteUnit::Literal('\\'),
192 BackquoteUnit::Literal('"'),
193 BackquoteUnit::Literal('\\'),
194 BackquoteUnit::Literal('\'')
195 ]
196 );
197 assert_eq!(location.range, 0..15);
198 });
199
200 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
201 }
202
203 #[test]
204 fn lexer_backquote_line_continuation() {
205 let mut lexer = Lexer::with_code("`\\\na\\\n\\\nb\\\n`");
206 let mut lexer = WordLexer {
207 lexer: &mut lexer,
208 context: WordContext::Word,
209 };
210 let result = lexer.backquote().now_or_never().unwrap().unwrap().unwrap();
211 assert_matches!(result, TextUnit::Backquote { content, location } => {
212 assert_eq!(
213 content,
214 [BackquoteUnit::Literal('a'), BackquoteUnit::Literal('b')]
215 );
216 assert_eq!(location.range, 0..12);
217 });
218
219 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
220 }
221
222 #[test]
223 fn lexer_backquote_unclosed_empty() {
224 let mut lexer = Lexer::with_code("`");
225 let mut lexer = WordLexer {
226 lexer: &mut lexer,
227 context: WordContext::Word,
228 };
229 let e = lexer.backquote().now_or_never().unwrap().unwrap_err();
230 assert_matches!(e.cause,
231 ErrorCause::Syntax(SyntaxError::UnclosedBackquote { opening_location }) => {
232 assert_eq!(*opening_location.code.value.borrow(), "`");
233 assert_eq!(opening_location.code.start_line_number.get(), 1);
234 assert_eq!(*opening_location.code.source, Source::Unknown);
235 assert_eq!(opening_location.range, 0..1);
236 });
237 assert_eq!(*e.location.code.value.borrow(), "`");
238 assert_eq!(e.location.code.start_line_number.get(), 1);
239 assert_eq!(*e.location.code.source, Source::Unknown);
240 assert_eq!(e.location.range, 1..1);
241 }
242
243 #[test]
244 fn lexer_backquote_unclosed_nonempty() {
245 let mut lexer = Lexer::with_code("`foo");
246 let mut lexer = WordLexer {
247 lexer: &mut lexer,
248 context: WordContext::Word,
249 };
250 let e = lexer.backquote().now_or_never().unwrap().unwrap_err();
251 assert_matches!(e.cause, ErrorCause::Syntax(SyntaxError::UnclosedBackquote { opening_location }) => {
252 assert_eq!(*opening_location.code.value.borrow(), "`foo");
253 assert_eq!(opening_location.code.start_line_number.get(), 1);
254 assert_eq!(*opening_location.code.source, Source::Unknown);
255 assert_eq!(opening_location.range, 0..1);
256 });
257 assert_eq!(*e.location.code.value.borrow(), "`foo");
258 assert_eq!(e.location.code.start_line_number.get(), 1);
259 assert_eq!(*e.location.code.source, Source::Unknown);
260 assert_eq!(e.location.range, 4..4);
261 }
262}