1use super::core::Parser;
20use super::core::Rec;
21use super::core::Result;
22use super::error::Error;
23use super::error::SyntaxError;
24use super::lex::Operator::{CloseParen, OpenParen};
25use super::lex::TokenId::{Operator, Token};
26use crate::syntax::Command;
27use crate::syntax::FunctionDefinition;
28use crate::syntax::SimpleCommand;
29use std::rc::Rc;
30
31impl Parser<'_, '_> {
32 pub async fn short_function_definition(&mut self, mut intro: SimpleCommand) -> Result<Command> {
42 if !intro.is_one_word() || self.peek_token().await?.id != Operator(OpenParen) {
43 return Ok(Command::Simple(intro));
44 }
45
46 let open = self.take_token_raw().await?;
47 debug_assert_eq!(open.id, Operator(OpenParen));
48
49 let close = self.take_token_auto(&[]).await?;
50 if close.id != Operator(CloseParen) {
51 return Err(Error {
52 cause: SyntaxError::UnmatchedParenthesis.into(),
53 location: close.word.location,
54 });
55 }
56
57 let name = intro.words.pop().unwrap().0;
58 debug_assert!(intro.is_empty());
59 loop {
62 while self.newline_and_here_doc_contents().await? {}
63
64 return match self.full_compound_command().await? {
65 Some(body) => Ok(Command::Function(FunctionDefinition {
66 has_keyword: false,
67 name,
68 body: Rc::new(body),
69 })),
70 None => {
71 let next = match self.take_token_manual(false).await? {
72 Rec::AliasSubstituted => continue,
73 Rec::Parsed(next) => next,
74 };
75 let cause = if let Token(_) = next.id {
76 SyntaxError::InvalidFunctionBody.into()
77 } else {
78 SyntaxError::MissingFunctionBody.into()
79 };
80 let location = next.word.location;
81 Err(Error { cause, location })
82 }
83 };
84 }
85 }
86}
87
88#[allow(
89 clippy::bool_assert_comparison,
90 reason = "to make the expected values clearer"
91)]
92#[cfg(test)]
93mod tests {
94 use super::super::error::ErrorCause;
95 use super::super::lex::Lexer;
96 use super::super::lex::TokenId::EndOfInput;
97 use super::*;
98 use crate::alias::{AliasSet, HashEntry};
99 use crate::source::Location;
100 use crate::source::Source;
101 use crate::syntax::ExpansionMode;
102 use assert_matches::assert_matches;
103 use futures_util::FutureExt as _;
104
105 #[test]
106 fn parser_short_function_definition_not_one_word_name() {
107 let mut lexer = Lexer::with_code("(");
108 let mut parser = Parser::new(&mut lexer);
109 let c = SimpleCommand {
110 assigns: vec![],
111 words: vec![],
112 redirs: vec![].into(),
113 };
114
115 let result = parser.short_function_definition(c).now_or_never().unwrap();
116 let command = result.unwrap();
117 assert_matches!(command, Command::Simple(c) => {
118 assert_eq!(c.to_string(), "");
119 });
120
121 let next = parser.peek_token().now_or_never().unwrap().unwrap();
122 assert_eq!(next.id, Operator(OpenParen));
123 }
124
125 #[test]
126 fn parser_short_function_definition_eof() {
127 let mut lexer = Lexer::with_code("");
128 let mut parser = Parser::new(&mut lexer);
129 let c = SimpleCommand {
130 assigns: vec![],
131 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
132 redirs: vec![].into(),
133 };
134
135 let result = parser.short_function_definition(c).now_or_never().unwrap();
136 let command = result.unwrap();
137 assert_matches!(command, Command::Simple(c) => {
138 assert_eq!(c.to_string(), "foo");
139 });
140 }
141
142 #[test]
143 fn parser_short_function_definition_unmatched_parenthesis() {
144 let mut lexer = Lexer::with_code("( ");
145 let mut parser = Parser::new(&mut lexer);
146 let c = SimpleCommand {
147 assigns: vec![],
148 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
149 redirs: vec![].into(),
150 };
151
152 let result = parser.short_function_definition(c).now_or_never().unwrap();
153 let e = result.unwrap_err();
154 assert_eq!(
155 e.cause,
156 ErrorCause::Syntax(SyntaxError::UnmatchedParenthesis)
157 );
158 assert_eq!(*e.location.code.value.borrow(), "( ");
159 assert_eq!(e.location.code.start_line_number.get(), 1);
160 assert_eq!(*e.location.code.source, Source::Unknown);
161 assert_eq!(e.location.range, 2..2);
162 }
163
164 #[test]
165 fn parser_short_function_definition_missing_function_body() {
166 let mut lexer = Lexer::with_code("( ) ");
167 let mut parser = Parser::new(&mut lexer);
168 let c = SimpleCommand {
169 assigns: vec![],
170 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
171 redirs: vec![].into(),
172 };
173
174 let result = parser.short_function_definition(c).now_or_never().unwrap();
175 let e = result.unwrap_err();
176 assert_eq!(
177 e.cause,
178 ErrorCause::Syntax(SyntaxError::MissingFunctionBody)
179 );
180 assert_eq!(*e.location.code.value.borrow(), "( ) ");
181 assert_eq!(e.location.code.start_line_number.get(), 1);
182 assert_eq!(*e.location.code.source, Source::Unknown);
183 assert_eq!(e.location.range, 4..4);
184 }
185
186 #[test]
187 fn parser_short_function_definition_invalid_function_body() {
188 let mut lexer = Lexer::with_code("() foo ; ");
189 let mut parser = Parser::new(&mut lexer);
190 let c = SimpleCommand {
191 assigns: vec![],
192 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
193 redirs: vec![].into(),
194 };
195
196 let result = parser.short_function_definition(c).now_or_never().unwrap();
197 let e = result.unwrap_err();
198 assert_eq!(
199 e.cause,
200 ErrorCause::Syntax(SyntaxError::InvalidFunctionBody)
201 );
202 assert_eq!(*e.location.code.value.borrow(), "() foo ; ");
203 assert_eq!(e.location.code.start_line_number.get(), 1);
204 assert_eq!(*e.location.code.source, Source::Unknown);
205 assert_eq!(e.location.range, 3..6);
206 }
207
208 #[test]
209 fn parser_short_function_definition_close_parenthesis_alias() {
210 let mut lexer = Lexer::with_code(" a b ");
211 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
212 let mut aliases = AliasSet::new();
213 let origin = Location::dummy("");
214 aliases.insert(HashEntry::new(
215 "a".to_string(),
216 "f( ".to_string(),
217 false,
218 origin.clone(),
219 ));
220 aliases.insert(HashEntry::new(
221 "b".to_string(),
222 " c".to_string(),
223 false,
224 origin.clone(),
225 ));
226 aliases.insert(HashEntry::new(
227 "c".to_string(),
228 " )\n\n(:)".to_string(),
229 false,
230 origin,
231 ));
232 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
233
234 parser.simple_command().now_or_never().unwrap().unwrap(); let sc = parser.simple_command().now_or_never().unwrap();
236 let sc = sc.unwrap().unwrap().unwrap();
237 let result = parser.short_function_definition(sc).now_or_never().unwrap();
238 let command = result.unwrap();
239 assert_matches!(command, Command::Function(f) => {
240 assert_eq!(f.has_keyword, false);
241 assert_eq!(f.name.to_string(), "f");
242 assert_eq!(f.body.to_string(), "(:)");
243 });
244
245 let next = parser.peek_token().now_or_never().unwrap().unwrap();
246 assert_eq!(next.id, EndOfInput);
247 }
248
249 #[test]
250 fn parser_short_function_definition_body_alias_and_newline() {
251 let mut lexer = Lexer::with_code(" a b ");
252 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
253 let mut aliases = AliasSet::new();
254 let origin = Location::dummy("");
255 aliases.insert(HashEntry::new(
256 "a".to_string(),
257 "f() ".to_string(),
258 false,
259 origin.clone(),
260 ));
261 aliases.insert(HashEntry::new(
262 "b".to_string(),
263 " c".to_string(),
264 false,
265 origin.clone(),
266 ));
267 aliases.insert(HashEntry::new(
268 "c".to_string(),
269 "\n\n(:)".to_string(),
270 false,
271 origin,
272 ));
273 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
274
275 parser.simple_command().now_or_never().unwrap().unwrap(); let sc = parser.simple_command().now_or_never().unwrap();
277 let sc = sc.unwrap().unwrap().unwrap();
278 let result = parser.short_function_definition(sc).now_or_never().unwrap();
279 let command = result.unwrap();
280 assert_matches!(command, Command::Function(f) => {
281 assert_eq!(f.has_keyword, false);
282 assert_eq!(f.name.to_string(), "f");
283 assert_eq!(f.body.to_string(), "(:)");
284 });
285
286 let next = parser.peek_token().now_or_never().unwrap().unwrap();
287 assert_eq!(next.id, EndOfInput);
288 }
289
290 #[test]
291 fn parser_short_function_definition_alias_inapplicable() {
292 let mut lexer = Lexer::with_code("()b");
293 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
294 let mut aliases = AliasSet::new();
295 let origin = Location::dummy("");
296 aliases.insert(HashEntry::new(
297 "b".to_string(),
298 " c".to_string(),
299 false,
300 origin.clone(),
301 ));
302 aliases.insert(HashEntry::new(
303 "c".to_string(),
304 "(:)".to_string(),
305 false,
306 origin,
307 ));
308 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
309 let c = SimpleCommand {
310 assigns: vec![],
311 words: vec![("f".parse().unwrap(), ExpansionMode::Multiple)],
312 redirs: vec![].into(),
313 };
314
315 let result = parser.short_function_definition(c).now_or_never().unwrap();
316 let e = result.unwrap_err();
317 assert_eq!(
318 e.cause,
319 ErrorCause::Syntax(SyntaxError::InvalidFunctionBody)
320 );
321 assert_eq!(*e.location.code.value.borrow(), "()b");
322 assert_eq!(e.location.code.start_line_number.get(), 1);
323 assert_eq!(*e.location.code.source, Source::Unknown);
324 assert_eq!(e.location.range, 2..3);
325 }
326}