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