1use super::core::Parser;
23use super::core::Result;
24use super::error::Error;
25use super::error::SyntaxError;
26use super::lex::Keyword::{Case, Do, Done, For, If, OpenBrace, Until, While};
27use super::lex::Operator::OpenParen;
28use super::lex::TokenId::{Operator, Token};
29use crate::syntax::CompoundCommand;
30use crate::syntax::FullCompoundCommand;
31use crate::syntax::List;
32
33impl Parser<'_, '_> {
34 pub async fn do_clause(&mut self) -> Result<Option<List>> {
38 if self.peek_token().await?.id != Token(Some(Do)) {
39 return Ok(None);
40 }
41
42 let open = self.take_token_raw().await?;
43
44 let list = self.maybe_compound_list_boxed().await?;
45
46 let close = self.take_token_raw().await?;
47 if close.id != Token(Some(Done)) {
48 let opening_location = open.word.location;
49 let cause = SyntaxError::UnclosedDoClause { opening_location }.into();
50 let location = close.word.location;
51 return Err(Error { cause, location });
52 }
53
54 if list.0.is_empty() {
56 let cause = SyntaxError::EmptyDoClause.into();
57 let location = close.word.location;
58 return Err(Error { cause, location });
59 }
60
61 Ok(Some(list))
62 }
63
64 pub async fn compound_command(&mut self) -> Result<Option<CompoundCommand>> {
66 match self.peek_token().await?.id {
67 Token(Some(OpenBrace)) => self.grouping().await.map(Some),
68 Operator(OpenParen) => self.subshell().await.map(Some),
69 Token(Some(For)) => self.for_loop().await.map(Some),
70 Token(Some(While)) => self.while_loop().await.map(Some),
71 Token(Some(Until)) => self.until_loop().await.map(Some),
72 Token(Some(If)) => self.if_command().await.map(Some),
73 Token(Some(Case)) => self.case_command().await.map(Some),
74 _ => Ok(None),
75 }
76 }
77
78 pub async fn full_compound_command(&mut self) -> Result<Option<FullCompoundCommand>> {
80 let command = match self.compound_command().await? {
81 Some(command) => command,
82 None => return Ok(None),
83 };
84 let redirs = self.redirections().await?;
85 Ok(Some(FullCompoundCommand { command, redirs }))
88 }
89}
90
91#[allow(clippy::bool_assert_comparison)]
92#[cfg(test)]
93mod tests {
94 use super::super::error::ErrorCause;
95 use super::super::lex::Lexer;
96 use super::super::lex::Operator::Semicolon;
97 use super::super::lex::TokenId::EndOfInput;
98 use super::*;
99 use crate::alias::{AliasSet, EmptyGlossary, HashEntry};
100 use crate::source::Location;
101 use crate::source::Source;
102 use crate::syntax::Command;
103 use crate::syntax::SimpleCommand;
104 use assert_matches::assert_matches;
105 use futures_util::FutureExt;
106
107 #[test]
108 fn parser_do_clause_none() {
109 let mut lexer = Lexer::from_memory("done", Source::Unknown);
110 let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
111
112 let result = parser.do_clause().now_or_never().unwrap().unwrap();
113 assert!(result.is_none(), "result should be none: {result:?}");
114 }
115
116 #[test]
117 fn parser_do_clause_short() {
118 let mut lexer = Lexer::from_memory("do :; done", Source::Unknown);
119 let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
120
121 let result = parser.do_clause().now_or_never().unwrap().unwrap().unwrap();
122 assert_eq!(result.to_string(), ":");
123
124 let next = parser.peek_token().now_or_never().unwrap().unwrap();
125 assert_eq!(next.id, EndOfInput);
126 }
127
128 #[test]
129 fn parser_do_clause_long() {
130 let mut lexer = Lexer::from_memory("do foo; bar& done", Source::Unknown);
131 let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
132
133 let result = parser.do_clause().now_or_never().unwrap().unwrap().unwrap();
134 assert_eq!(result.to_string(), "foo; bar&");
135
136 let next = parser.peek_token().now_or_never().unwrap().unwrap();
137 assert_eq!(next.id, EndOfInput);
138 }
139
140 #[test]
141 fn parser_do_clause_unclosed() {
142 let mut lexer = Lexer::from_memory(" do not close ", Source::Unknown);
143 let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
144
145 let e = parser.do_clause().now_or_never().unwrap().unwrap_err();
146 assert_matches!(e.cause,
147 ErrorCause::Syntax(SyntaxError::UnclosedDoClause { opening_location }) => {
148 assert_eq!(*opening_location.code.value.borrow(), " do not close ");
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, 1..3);
152 });
153 assert_eq!(*e.location.code.value.borrow(), " do not close ");
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, 14..14);
157 }
158
159 #[test]
160 fn parser_do_clause_empty_posix() {
161 let mut lexer = Lexer::from_memory("do done", Source::Unknown);
162 let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
163
164 let e = parser.do_clause().now_or_never().unwrap().unwrap_err();
165 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyDoClause));
166 assert_eq!(*e.location.code.value.borrow(), "do done");
167 assert_eq!(e.location.code.start_line_number.get(), 1);
168 assert_eq!(*e.location.code.source, Source::Unknown);
169 assert_eq!(e.location.range, 3..7);
170 }
171
172 #[test]
173 fn parser_do_clause_aliasing() {
174 let mut lexer = Lexer::from_memory(" do :; end ", Source::Unknown);
175 #[allow(clippy::mutable_key_type)]
176 let mut aliases = AliasSet::new();
177 let origin = Location::dummy("");
178 aliases.insert(HashEntry::new(
179 "do".to_string(),
180 "".to_string(),
181 false,
182 origin.clone(),
183 ));
184 aliases.insert(HashEntry::new(
185 "done".to_string(),
186 "".to_string(),
187 false,
188 origin.clone(),
189 ));
190 aliases.insert(HashEntry::new(
191 "end".to_string(),
192 "done".to_string(),
193 false,
194 origin,
195 ));
196 let mut parser = Parser::new(&mut lexer, &aliases);
197
198 let result = parser.do_clause().now_or_never().unwrap().unwrap().unwrap();
199 assert_eq!(result.to_string(), ":");
200
201 let next = parser.peek_token().now_or_never().unwrap().unwrap();
202 assert_eq!(next.id, EndOfInput);
203 }
204
205 #[test]
206 fn parser_compound_command_none() {
207 let mut lexer = Lexer::from_memory("}", Source::Unknown);
208 let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
209
210 let option = parser.compound_command().now_or_never().unwrap().unwrap();
211 assert_eq!(option, None);
212 }
213
214 #[test]
215 fn parser_full_compound_command_without_redirections() {
216 let mut lexer = Lexer::from_memory("(:)", Source::Unknown);
217 let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
218
219 let result = parser.full_compound_command().now_or_never().unwrap();
220 let FullCompoundCommand { command, redirs } = result.unwrap().unwrap();
221 assert_eq!(command.to_string(), "(:)");
222 assert_eq!(redirs, []);
223 }
224
225 #[test]
226 fn parser_full_compound_command_with_redirections() {
227 let mut lexer = Lexer::from_memory("(command) <foo >bar ;", Source::Unknown);
228 let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
229
230 let result = parser.full_compound_command().now_or_never().unwrap();
231 let FullCompoundCommand { command, redirs } = result.unwrap().unwrap();
232 assert_eq!(command.to_string(), "(command)");
233 assert_eq!(redirs.len(), 2);
234 assert_eq!(redirs[0].to_string(), "<foo");
235 assert_eq!(redirs[1].to_string(), ">bar");
236
237 let next = parser.peek_token().now_or_never().unwrap().unwrap();
238 assert_eq!(next.id, Operator(Semicolon));
239 }
240
241 #[test]
242 fn parser_full_compound_command_none() {
243 let mut lexer = Lexer::from_memory("}", Source::Unknown);
244 let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
245
246 let result = parser.full_compound_command().now_or_never().unwrap();
247 assert_eq!(result, Ok(None));
248 }
249
250 #[test]
251 fn parser_short_function_definition_ok() {
252 let mut lexer = Lexer::from_memory(" ( ) ( : ) > /dev/null ", Source::Unknown);
253 let mut parser = Parser::new(&mut lexer, &EmptyGlossary);
254 let c = SimpleCommand {
255 assigns: vec![],
256 words: vec!["foo".parse().unwrap()],
257 redirs: vec![].into(),
258 };
259
260 let result = parser.short_function_definition(c).now_or_never().unwrap();
261 let command = result.unwrap();
262 assert_matches!(command, Command::Function(f) => {
263 assert_eq!(f.has_keyword, false);
264 assert_eq!(f.name.to_string(), "foo");
265 assert_eq!(f.body.to_string(), "(:) >/dev/null");
266 });
267
268 let next = parser.peek_token().now_or_never().unwrap().unwrap();
269 assert_eq!(next.id, EndOfInput);
270 }
271}