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(clippy::bool_assert_comparison)]
89#[cfg(test)]
90mod tests {
91 use super::super::error::ErrorCause;
92 use super::super::lex::Lexer;
93 use super::super::lex::TokenId::EndOfInput;
94 use super::*;
95 use crate::alias::{AliasSet, HashEntry};
96 use crate::source::Location;
97 use crate::source::Source;
98 use crate::syntax::ExpansionMode;
99 use assert_matches::assert_matches;
100 use futures_util::FutureExt;
101
102 #[test]
103 fn parser_short_function_definition_not_one_word_name() {
104 let mut lexer = Lexer::with_code("(");
105 let mut parser = Parser::new(&mut lexer);
106 let c = SimpleCommand {
107 assigns: vec![],
108 words: vec![],
109 redirs: vec![].into(),
110 };
111
112 let result = parser.short_function_definition(c).now_or_never().unwrap();
113 let command = result.unwrap();
114 assert_matches!(command, Command::Simple(c) => {
115 assert_eq!(c.to_string(), "");
116 });
117
118 let next = parser.peek_token().now_or_never().unwrap().unwrap();
119 assert_eq!(next.id, Operator(OpenParen));
120 }
121
122 #[test]
123 fn parser_short_function_definition_eof() {
124 let mut lexer = Lexer::with_code("");
125 let mut parser = Parser::new(&mut lexer);
126 let c = SimpleCommand {
127 assigns: vec![],
128 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
129 redirs: vec![].into(),
130 };
131
132 let result = parser.short_function_definition(c).now_or_never().unwrap();
133 let command = result.unwrap();
134 assert_matches!(command, Command::Simple(c) => {
135 assert_eq!(c.to_string(), "foo");
136 });
137 }
138
139 #[test]
140 fn parser_short_function_definition_unmatched_parenthesis() {
141 let mut lexer = Lexer::with_code("( ");
142 let mut parser = Parser::new(&mut lexer);
143 let c = SimpleCommand {
144 assigns: vec![],
145 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
146 redirs: vec![].into(),
147 };
148
149 let result = parser.short_function_definition(c).now_or_never().unwrap();
150 let e = result.unwrap_err();
151 assert_eq!(
152 e.cause,
153 ErrorCause::Syntax(SyntaxError::UnmatchedParenthesis)
154 );
155 assert_eq!(*e.location.code.value.borrow(), "( ");
156 assert_eq!(e.location.code.start_line_number.get(), 1);
157 assert_eq!(*e.location.code.source, Source::Unknown);
158 assert_eq!(e.location.range, 2..2);
159 }
160
161 #[test]
162 fn parser_short_function_definition_missing_function_body() {
163 let mut lexer = Lexer::with_code("( ) ");
164 let mut parser = Parser::new(&mut lexer);
165 let c = SimpleCommand {
166 assigns: vec![],
167 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
168 redirs: vec![].into(),
169 };
170
171 let result = parser.short_function_definition(c).now_or_never().unwrap();
172 let e = result.unwrap_err();
173 assert_eq!(
174 e.cause,
175 ErrorCause::Syntax(SyntaxError::MissingFunctionBody)
176 );
177 assert_eq!(*e.location.code.value.borrow(), "( ) ");
178 assert_eq!(e.location.code.start_line_number.get(), 1);
179 assert_eq!(*e.location.code.source, Source::Unknown);
180 assert_eq!(e.location.range, 4..4);
181 }
182
183 #[test]
184 fn parser_short_function_definition_invalid_function_body() {
185 let mut lexer = Lexer::with_code("() foo ; ");
186 let mut parser = Parser::new(&mut lexer);
187 let c = SimpleCommand {
188 assigns: vec![],
189 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
190 redirs: vec![].into(),
191 };
192
193 let result = parser.short_function_definition(c).now_or_never().unwrap();
194 let e = result.unwrap_err();
195 assert_eq!(
196 e.cause,
197 ErrorCause::Syntax(SyntaxError::InvalidFunctionBody)
198 );
199 assert_eq!(*e.location.code.value.borrow(), "() foo ; ");
200 assert_eq!(e.location.code.start_line_number.get(), 1);
201 assert_eq!(*e.location.code.source, Source::Unknown);
202 assert_eq!(e.location.range, 3..6);
203 }
204
205 #[test]
206 fn parser_short_function_definition_close_parenthesis_alias() {
207 let mut lexer = Lexer::with_code(" a b ");
208 #[allow(clippy::mutable_key_type)]
209 let mut aliases = AliasSet::new();
210 let origin = Location::dummy("");
211 aliases.insert(HashEntry::new(
212 "a".to_string(),
213 "f( ".to_string(),
214 false,
215 origin.clone(),
216 ));
217 aliases.insert(HashEntry::new(
218 "b".to_string(),
219 " c".to_string(),
220 false,
221 origin.clone(),
222 ));
223 aliases.insert(HashEntry::new(
224 "c".to_string(),
225 " )\n\n(:)".to_string(),
226 false,
227 origin,
228 ));
229 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
230
231 parser.simple_command().now_or_never().unwrap().unwrap(); let sc = parser.simple_command().now_or_never().unwrap();
233 let sc = sc.unwrap().unwrap().unwrap();
234 let result = parser.short_function_definition(sc).now_or_never().unwrap();
235 let command = result.unwrap();
236 assert_matches!(command, Command::Function(f) => {
237 assert_eq!(f.has_keyword, false);
238 assert_eq!(f.name.to_string(), "f");
239 assert_eq!(f.body.to_string(), "(:)");
240 });
241
242 let next = parser.peek_token().now_or_never().unwrap().unwrap();
243 assert_eq!(next.id, EndOfInput);
244 }
245
246 #[test]
247 fn parser_short_function_definition_body_alias_and_newline() {
248 let mut lexer = Lexer::with_code(" a b ");
249 #[allow(clippy::mutable_key_type)]
250 let mut aliases = AliasSet::new();
251 let origin = Location::dummy("");
252 aliases.insert(HashEntry::new(
253 "a".to_string(),
254 "f() ".to_string(),
255 false,
256 origin.clone(),
257 ));
258 aliases.insert(HashEntry::new(
259 "b".to_string(),
260 " c".to_string(),
261 false,
262 origin.clone(),
263 ));
264 aliases.insert(HashEntry::new(
265 "c".to_string(),
266 "\n\n(:)".to_string(),
267 false,
268 origin,
269 ));
270 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
271
272 parser.simple_command().now_or_never().unwrap().unwrap(); let sc = parser.simple_command().now_or_never().unwrap();
274 let sc = sc.unwrap().unwrap().unwrap();
275 let result = parser.short_function_definition(sc).now_or_never().unwrap();
276 let command = result.unwrap();
277 assert_matches!(command, Command::Function(f) => {
278 assert_eq!(f.has_keyword, false);
279 assert_eq!(f.name.to_string(), "f");
280 assert_eq!(f.body.to_string(), "(:)");
281 });
282
283 let next = parser.peek_token().now_or_never().unwrap().unwrap();
284 assert_eq!(next.id, EndOfInput);
285 }
286
287 #[test]
288 fn parser_short_function_definition_alias_inapplicable() {
289 let mut lexer = Lexer::with_code("()b");
290 #[allow(clippy::mutable_key_type)]
291 let mut aliases = AliasSet::new();
292 let origin = Location::dummy("");
293 aliases.insert(HashEntry::new(
294 "b".to_string(),
295 " c".to_string(),
296 false,
297 origin.clone(),
298 ));
299 aliases.insert(HashEntry::new(
300 "c".to_string(),
301 "(:)".to_string(),
302 false,
303 origin,
304 ));
305 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
306 let c = SimpleCommand {
307 assigns: vec![],
308 words: vec![("f".parse().unwrap(), ExpansionMode::Multiple)],
309 redirs: vec![].into(),
310 };
311
312 let result = parser.short_function_definition(c).now_or_never().unwrap();
313 let e = result.unwrap_err();
314 assert_eq!(
315 e.cause,
316 ErrorCause::Syntax(SyntaxError::InvalidFunctionBody)
317 );
318 assert_eq!(*e.location.code.value.borrow(), "()b");
319 assert_eq!(e.location.code.start_line_number.get(), 1);
320 assert_eq!(*e.location.code.source, Source::Unknown);
321 assert_eq!(e.location.range, 2..3);
322 }
323}