yash_syntax/parser/
compound_command.rs1use 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(
92 clippy::bool_assert_comparison,
93 reason = "to make the expected values clearer"
94)]
95#[cfg(test)]
96mod tests {
97 use super::super::error::ErrorCause;
98 use super::super::lex::Lexer;
99 use super::super::lex::Operator::Semicolon;
100 use super::super::lex::TokenId::EndOfInput;
101 use super::*;
102 use crate::alias::{AliasSet, HashEntry};
103 use crate::source::Location;
104 use crate::source::Source;
105 use crate::syntax::Command;
106 use crate::syntax::ExpansionMode;
107 use crate::syntax::SimpleCommand;
108 use assert_matches::assert_matches;
109 use futures_util::FutureExt as _;
110
111 #[test]
112 fn parser_do_clause_none() {
113 let mut lexer = Lexer::with_code("done");
114 let mut parser = Parser::new(&mut lexer);
115
116 let result = parser.do_clause().now_or_never().unwrap().unwrap();
117 assert!(result.is_none(), "result should be none: {result:?}");
118 }
119
120 #[test]
121 fn parser_do_clause_short() {
122 let mut lexer = Lexer::with_code("do :; done");
123 let mut parser = Parser::new(&mut lexer);
124
125 let result = parser.do_clause().now_or_never().unwrap().unwrap().unwrap();
126 assert_eq!(result.to_string(), ":");
127
128 let next = parser.peek_token().now_or_never().unwrap().unwrap();
129 assert_eq!(next.id, EndOfInput);
130 }
131
132 #[test]
133 fn parser_do_clause_long() {
134 let mut lexer = Lexer::with_code("do foo; bar& done");
135 let mut parser = Parser::new(&mut lexer);
136
137 let result = parser.do_clause().now_or_never().unwrap().unwrap().unwrap();
138 assert_eq!(result.to_string(), "foo; bar&");
139
140 let next = parser.peek_token().now_or_never().unwrap().unwrap();
141 assert_eq!(next.id, EndOfInput);
142 }
143
144 #[test]
145 fn parser_do_clause_unclosed() {
146 let mut lexer = Lexer::with_code(" do not close ");
147 let mut parser = Parser::new(&mut lexer);
148
149 let e = parser.do_clause().now_or_never().unwrap().unwrap_err();
150 assert_matches!(e.cause,
151 ErrorCause::Syntax(SyntaxError::UnclosedDoClause { opening_location }) => {
152 assert_eq!(*opening_location.code.value.borrow(), " do not close ");
153 assert_eq!(opening_location.code.start_line_number.get(), 1);
154 assert_eq!(*opening_location.code.source, Source::Unknown);
155 assert_eq!(opening_location.range, 1..3);
156 });
157 assert_eq!(*e.location.code.value.borrow(), " do not close ");
158 assert_eq!(e.location.code.start_line_number.get(), 1);
159 assert_eq!(*e.location.code.source, Source::Unknown);
160 assert_eq!(e.location.range, 14..14);
161 }
162
163 #[test]
164 fn parser_do_clause_empty_posix() {
165 let mut lexer = Lexer::with_code("do done");
166 let mut parser = Parser::new(&mut lexer);
167
168 let e = parser.do_clause().now_or_never().unwrap().unwrap_err();
169 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyDoClause));
170 assert_eq!(*e.location.code.value.borrow(), "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, 3..7);
174 }
175
176 #[test]
177 fn parser_do_clause_aliasing() {
178 let mut lexer = Lexer::with_code(" do :; end ");
179 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
180 let mut aliases = AliasSet::new();
181 let origin = Location::dummy("");
182 aliases.insert(HashEntry::new(
183 "do".to_string(),
184 "".to_string(),
185 false,
186 origin.clone(),
187 ));
188 aliases.insert(HashEntry::new(
189 "done".to_string(),
190 "".to_string(),
191 false,
192 origin.clone(),
193 ));
194 aliases.insert(HashEntry::new(
195 "end".to_string(),
196 "done".to_string(),
197 false,
198 origin,
199 ));
200 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
201
202 let result = parser.do_clause().now_or_never().unwrap().unwrap().unwrap();
203 assert_eq!(result.to_string(), ":");
204
205 let next = parser.peek_token().now_or_never().unwrap().unwrap();
206 assert_eq!(next.id, EndOfInput);
207 }
208
209 #[test]
210 fn parser_compound_command_none() {
211 let mut lexer = Lexer::with_code("}");
212 let mut parser = Parser::new(&mut lexer);
213
214 let option = parser.compound_command().now_or_never().unwrap().unwrap();
215 assert_eq!(option, None);
216 }
217
218 #[test]
219 fn parser_full_compound_command_without_redirections() {
220 let mut lexer = Lexer::with_code("(:)");
221 let mut parser = Parser::new(&mut lexer);
222
223 let result = parser.full_compound_command().now_or_never().unwrap();
224 let FullCompoundCommand { command, redirs } = result.unwrap().unwrap();
225 assert_eq!(command.to_string(), "(:)");
226 assert_eq!(redirs, []);
227 }
228
229 #[test]
230 fn parser_full_compound_command_with_redirections() {
231 let mut lexer = Lexer::with_code("(command) <foo >bar ;");
232 let mut parser = Parser::new(&mut lexer);
233
234 let result = parser.full_compound_command().now_or_never().unwrap();
235 let FullCompoundCommand { command, redirs } = result.unwrap().unwrap();
236 assert_eq!(command.to_string(), "(command)");
237 assert_eq!(redirs.len(), 2);
238 assert_eq!(redirs[0].to_string(), "<foo");
239 assert_eq!(redirs[1].to_string(), ">bar");
240
241 let next = parser.peek_token().now_or_never().unwrap().unwrap();
242 assert_eq!(next.id, Operator(Semicolon));
243 }
244
245 #[test]
246 fn parser_full_compound_command_none() {
247 let mut lexer = Lexer::with_code("}");
248 let mut parser = Parser::new(&mut lexer);
249
250 let result = parser.full_compound_command().now_or_never().unwrap();
251 assert_eq!(result, Ok(None));
252 }
253
254 #[test]
255 fn parser_short_function_definition_ok() {
256 let mut lexer = Lexer::with_code(" ( ) ( : ) > /dev/null ");
257 let mut parser = Parser::new(&mut lexer);
258 let c = SimpleCommand {
259 assigns: vec![],
260 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
261 redirs: vec![].into(),
262 };
263
264 let result = parser.short_function_definition(c).now_or_never().unwrap();
265 let command = result.unwrap();
266 assert_matches!(command, Command::Function(f) => {
267 assert_eq!(f.has_keyword, false);
268 assert_eq!(f.name.to_string(), "foo");
269 assert_eq!(f.body.to_string(), "(:) >/dev/null");
270 });
271
272 let next = parser.peek_token().now_or_never().unwrap().unwrap();
273 assert_eq!(next.id, EndOfInput);
274 }
275}