yash_syntax/parser/
while_loop.rs1use super::core::Parser;
20use super::core::Result;
21use super::error::Error;
22use super::error::SyntaxError;
23use super::lex::Keyword::{Until, While};
24use super::lex::TokenId::Token;
25use crate::syntax::CompoundCommand;
26
27impl Parser<'_, '_> {
28 pub async fn while_loop(&mut self) -> Result<CompoundCommand> {
36 let open = self.take_token_raw().await?;
37 assert_eq!(open.id, Token(Some(While)));
38
39 let condition = self.maybe_compound_list_boxed().await?;
40
41 if condition.0.is_empty() {
43 let cause = SyntaxError::EmptyWhileCondition.into();
44 let location = self.take_token_raw().await?.word.location;
45 return Err(Error { cause, location });
46 }
47
48 let body = match self.do_clause().await? {
49 Some(body) => body,
50 None => {
51 let opening_location = open.word.location;
52 let cause = SyntaxError::UnclosedWhileClause { opening_location }.into();
53 let location = self.take_token_raw().await?.word.location;
54 return Err(Error { cause, location });
55 }
56 };
57
58 Ok(CompoundCommand::While { condition, body })
59 }
60
61 pub async fn until_loop(&mut self) -> Result<CompoundCommand> {
69 let open = self.take_token_raw().await?;
70 assert_eq!(open.id, Token(Some(Until)));
71
72 let condition = self.maybe_compound_list_boxed().await?;
73
74 if condition.0.is_empty() {
76 let cause = SyntaxError::EmptyUntilCondition.into();
77 let location = self.take_token_raw().await?.word.location;
78 return Err(Error { cause, location });
79 }
80
81 let body = match self.do_clause().await? {
82 Some(body) => body,
83 None => {
84 let opening_location = open.word.location;
85 let cause = SyntaxError::UnclosedUntilClause { opening_location }.into();
86 let location = self.take_token_raw().await?.word.location;
87 return Err(Error { cause, location });
88 }
89 };
90
91 Ok(CompoundCommand::Until { condition, body })
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::super::error::ErrorCause;
98 use super::super::lex::Lexer;
99 use super::super::lex::TokenId::EndOfInput;
100 use super::*;
101 use crate::alias::{AliasSet, HashEntry};
102 use crate::source::Location;
103 use crate::source::Source;
104 use assert_matches::assert_matches;
105 use futures_util::FutureExt;
106
107 #[test]
108 fn parser_while_loop_short() {
109 let mut lexer = Lexer::with_code("while true; do :; done");
110 let mut parser = Parser::new(&mut lexer);
111
112 let result = parser.compound_command().now_or_never().unwrap();
113 let compound_command = result.unwrap().unwrap();
114 assert_matches!(compound_command, CompoundCommand::While { condition, body } => {
115 assert_eq!(condition.to_string(), "true");
116 assert_eq!(body.to_string(), ":");
117 });
118
119 let next = parser.peek_token().now_or_never().unwrap().unwrap();
120 assert_eq!(next.id, EndOfInput);
121 }
122
123 #[test]
124 fn parser_while_loop_long() {
125 let mut lexer = Lexer::with_code("while false; true& do foo; bar& done");
126 let mut parser = Parser::new(&mut lexer);
127
128 let result = parser.compound_command().now_or_never().unwrap();
129 let compound_command = result.unwrap().unwrap();
130 assert_matches!(compound_command, CompoundCommand::While { condition, body } => {
131 assert_eq!(condition.to_string(), "false; true&");
132 assert_eq!(body.to_string(), "foo; bar&");
133 });
134
135 let next = parser.peek_token().now_or_never().unwrap().unwrap();
136 assert_eq!(next.id, EndOfInput);
137 }
138
139 #[test]
140 fn parser_while_loop_unclosed() {
141 let mut lexer = Lexer::with_code("while :");
142 let mut parser = Parser::new(&mut lexer);
143
144 let result = parser.compound_command().now_or_never().unwrap();
145 let e = result.unwrap_err();
146 assert_matches!(e.cause,
147 ErrorCause::Syntax(SyntaxError::UnclosedWhileClause { opening_location }) => {
148 assert_eq!(*opening_location.code.value.borrow(), "while :");
149 assert_eq!(opening_location.code.start_line_number.get(), 1);
150 assert_eq!(*opening_location.code.source, Source::Unknown);
151 assert_eq!(opening_location.range, 0..5);
152 });
153 assert_eq!(*e.location.code.value.borrow(), "while :");
154 assert_eq!(e.location.code.start_line_number.get(), 1);
155 assert_eq!(*e.location.code.source, Source::Unknown);
156 assert_eq!(e.location.range, 7..7);
157 }
158
159 #[test]
160 fn parser_while_loop_empty_posix() {
161 let mut lexer = Lexer::with_code(" while do :; done");
162 let mut parser = Parser::new(&mut lexer);
163
164 let result = parser.compound_command().now_or_never().unwrap();
165 let e = result.unwrap_err();
166 assert_eq!(
167 e.cause,
168 ErrorCause::Syntax(SyntaxError::EmptyWhileCondition)
169 );
170 assert_eq!(*e.location.code.value.borrow(), " while do :; done");
171 assert_eq!(e.location.code.start_line_number.get(), 1);
172 assert_eq!(*e.location.code.source, Source::Unknown);
173 assert_eq!(e.location.range, 7..9);
174 }
175
176 #[test]
177 fn parser_while_loop_aliasing() {
178 let mut lexer = Lexer::with_code(" while :; DO :; done");
179 #[allow(clippy::mutable_key_type)]
180 let mut aliases = AliasSet::new();
181 let origin = Location::dummy("");
182 aliases.insert(HashEntry::new(
183 "DO".to_string(),
184 "do".to_string(),
185 false,
186 origin.clone(),
187 ));
188 aliases.insert(HashEntry::new(
189 "while".to_string(),
190 ";;".to_string(),
191 false,
192 origin,
193 ));
194 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
195
196 let result = parser.compound_command().now_or_never().unwrap();
197 let compound_command = result.unwrap().unwrap();
198 assert_eq!(compound_command.to_string(), "while :; do :; done");
199
200 let next = parser.peek_token().now_or_never().unwrap().unwrap();
201 assert_eq!(next.id, EndOfInput);
202 }
203
204 #[test]
205 fn parser_until_loop_short() {
206 let mut lexer = Lexer::with_code("until true; do :; done");
207 let mut parser = Parser::new(&mut lexer);
208
209 let result = parser.compound_command().now_or_never().unwrap();
210 let compound_command = result.unwrap().unwrap();
211 assert_matches!(compound_command, CompoundCommand::Until { condition, body } => {
212 assert_eq!(condition.to_string(), "true");
213 assert_eq!(body.to_string(), ":");
214 });
215
216 let next = parser.peek_token().now_or_never().unwrap().unwrap();
217 assert_eq!(next.id, EndOfInput);
218 }
219
220 #[test]
221 fn parser_until_loop_long() {
222 let mut lexer = Lexer::with_code("until false; true& do foo; bar& done");
223 let mut parser = Parser::new(&mut lexer);
224
225 let result = parser.compound_command().now_or_never().unwrap();
226 let compound_command = result.unwrap().unwrap();
227 assert_matches!(compound_command, CompoundCommand::Until { condition, body } => {
228 assert_eq!(condition.to_string(), "false; true&");
229 assert_eq!(body.to_string(), "foo; bar&");
230 });
231
232 let next = parser.peek_token().now_or_never().unwrap().unwrap();
233 assert_eq!(next.id, EndOfInput);
234 }
235
236 #[test]
237 fn parser_until_loop_unclosed() {
238 let mut lexer = Lexer::with_code("until :");
239 let mut parser = Parser::new(&mut lexer);
240
241 let result = parser.compound_command().now_or_never().unwrap();
242 let e = result.unwrap_err();
243 assert_matches!(e.cause,
244 ErrorCause::Syntax(SyntaxError::UnclosedUntilClause { opening_location }) => {
245 assert_eq!(*opening_location.code.value.borrow(), "until :");
246 assert_eq!(opening_location.code.start_line_number.get(), 1);
247 assert_eq!(*opening_location.code.source, Source::Unknown);
248 assert_eq!(opening_location.range, 0..5);
249 });
250 assert_eq!(*e.location.code.value.borrow(), "until :");
251 assert_eq!(e.location.code.start_line_number.get(), 1);
252 assert_eq!(*e.location.code.source, Source::Unknown);
253 assert_eq!(e.location.range, 7..7);
254 }
255
256 #[test]
257 fn parser_until_loop_empty_posix() {
258 let mut lexer = Lexer::with_code(" until do :; done");
259 let mut parser = Parser::new(&mut lexer);
260
261 let result = parser.compound_command().now_or_never().unwrap();
262 let e = result.unwrap_err();
263 assert_eq!(
264 e.cause,
265 ErrorCause::Syntax(SyntaxError::EmptyUntilCondition)
266 );
267 assert_eq!(*e.location.code.value.borrow(), " until do :; done");
268 assert_eq!(e.location.code.start_line_number.get(), 1);
269 assert_eq!(*e.location.code.source, Source::Unknown);
270 assert_eq!(e.location.range, 8..10);
271 }
272
273 #[test]
274 fn parser_until_loop_aliasing() {
275 let mut lexer = Lexer::with_code(" until :; DO :; done");
276 #[allow(clippy::mutable_key_type)]
277 let mut aliases = AliasSet::new();
278 let origin = Location::dummy("");
279 aliases.insert(HashEntry::new(
280 "DO".to_string(),
281 "do".to_string(),
282 false,
283 origin.clone(),
284 ));
285 aliases.insert(HashEntry::new(
286 "until".to_string(),
287 ";;".to_string(),
288 false,
289 origin,
290 ));
291 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
292
293 let result = parser.compound_command().now_or_never().unwrap();
294 let compound_command = result.unwrap().unwrap();
295 assert_eq!(compound_command.to_string(), "until :; do :; done");
296
297 let next = parser.peek_token().now_or_never().unwrap().unwrap();
298 assert_eq!(next.id, EndOfInput);
299 }
300}