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 super::lex::is_portable_name;
27use crate::syntax::Command;
28use crate::syntax::FunctionDefinition;
29use crate::syntax::MaybeLiteral as _;
30use crate::syntax::SimpleCommand;
31use std::rc::Rc;
32use yash_env::builtin::is_posix_special_builtin_name;
33
34impl Parser<'_, '_> {
35 pub async fn short_function_definition(&mut self, mut intro: SimpleCommand) -> Result<Command> {
45 if !intro.is_one_word() || self.peek_token().await?.id != Operator(OpenParen) {
46 return Ok(Command::Simple(intro));
47 }
48
49 let open = self.take_token_raw().await?;
50 debug_assert_eq!(open.id, Operator(OpenParen));
51
52 let close = self.take_token_auto(&[]).await?;
53 if close.id != Operator(CloseParen) {
54 return Err(Error {
55 cause: SyntaxError::UnmatchedParenthesis.into(),
56 location: close.word.location,
57 });
58 }
59
60 let name = intro.words.pop().unwrap().0;
61 debug_assert!(intro.is_empty());
62
63 if self.mode().portable {
64 if let Some(s) = name.to_string_if_literal()
65 && is_portable_name(&s)
66 {
67 if is_posix_special_builtin_name(&s) {
68 let cause = SyntaxError::SpecialBuiltinFunctionName.into();
69 let location = name.location;
70 return Err(Error { cause, location });
71 }
72 } else {
73 let cause = SyntaxError::NonPortableFunctionName.into();
74 let location = name.location;
75 return Err(Error { cause, location });
76 }
77 }
78
79 loop {
80 while self.newline_and_here_doc_contents().await? {}
81
82 return match self.full_compound_command().await? {
83 Some(body) => Ok(Command::Function(FunctionDefinition {
84 has_keyword: false,
85 name,
86 body: Rc::new(body),
87 })),
88 None => {
89 let next = match self.take_token_manual(false).await? {
90 Rec::AliasSubstituted => continue,
91 Rec::Parsed(next) => next,
92 };
93 let cause = if let Token(_) = next.id {
94 SyntaxError::InvalidFunctionBody.into()
95 } else {
96 SyntaxError::MissingFunctionBody.into()
97 };
98 let location = next.word.location;
99 Err(Error { cause, location })
100 }
101 };
102 }
103 }
104}
105
106#[allow(
107 clippy::bool_assert_comparison,
108 reason = "to make the expected values clearer"
109)]
110#[cfg(test)]
111mod tests {
112 use super::super::error::ErrorCause;
113 use super::super::lex::Lexer;
114 use super::super::lex::TokenId::EndOfInput;
115 use super::*;
116 use crate::alias::{AliasSet, HashEntry};
117 use crate::source::Location;
118 use crate::source::Source;
119 use crate::syntax::ExpansionMode;
120 use assert_matches::assert_matches;
121 use futures_util::FutureExt as _;
122
123 #[test]
124 fn parser_short_function_definition_not_one_word_name() {
125 let mut lexer = Lexer::with_code("(");
126 let mut parser = Parser::new(&mut lexer);
127 let c = SimpleCommand {
128 assigns: vec![],
129 words: vec![],
130 redirs: vec![].into(),
131 };
132
133 let result = parser.short_function_definition(c).now_or_never().unwrap();
134 let command = result.unwrap();
135 assert_matches!(command, Command::Simple(c) => {
136 assert_eq!(c.to_string(), "");
137 });
138
139 let next = parser.peek_token().now_or_never().unwrap().unwrap();
140 assert_eq!(next.id, Operator(OpenParen));
141 }
142
143 #[test]
144 fn parser_short_function_definition_eof() {
145 let mut lexer = Lexer::with_code("");
146 let mut parser = Parser::new(&mut lexer);
147 let c = SimpleCommand {
148 assigns: vec![],
149 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
150 redirs: vec![].into(),
151 };
152
153 let result = parser.short_function_definition(c).now_or_never().unwrap();
154 let command = result.unwrap();
155 assert_matches!(command, Command::Simple(c) => {
156 assert_eq!(c.to_string(), "foo");
157 });
158 }
159
160 #[test]
161 fn parser_short_function_definition_unmatched_parenthesis() {
162 let mut lexer = Lexer::with_code("( ");
163 let mut parser = Parser::new(&mut lexer);
164 let c = SimpleCommand {
165 assigns: vec![],
166 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
167 redirs: vec![].into(),
168 };
169
170 let result = parser.short_function_definition(c).now_or_never().unwrap();
171 let e = result.unwrap_err();
172 assert_eq!(
173 e.cause,
174 ErrorCause::Syntax(SyntaxError::UnmatchedParenthesis)
175 );
176 assert_eq!(*e.location.code.value.borrow(), "( ");
177 assert_eq!(e.location.code.start_line_number.get(), 1);
178 assert_eq!(*e.location.code.source, Source::Unknown);
179 assert_eq!(e.location.range, 2..2);
180 }
181
182 #[test]
183 fn parser_short_function_definition_missing_function_body() {
184 let mut lexer = Lexer::with_code("( ) ");
185 let mut parser = Parser::new(&mut lexer);
186 let c = SimpleCommand {
187 assigns: vec![],
188 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
189 redirs: vec![].into(),
190 };
191
192 let result = parser.short_function_definition(c).now_or_never().unwrap();
193 let e = result.unwrap_err();
194 assert_eq!(
195 e.cause,
196 ErrorCause::Syntax(SyntaxError::MissingFunctionBody)
197 );
198 assert_eq!(*e.location.code.value.borrow(), "( ) ");
199 assert_eq!(e.location.code.start_line_number.get(), 1);
200 assert_eq!(*e.location.code.source, Source::Unknown);
201 assert_eq!(e.location.range, 4..4);
202 }
203
204 #[test]
205 fn parser_short_function_definition_invalid_function_body() {
206 let mut lexer = Lexer::with_code("() foo ; ");
207 let mut parser = Parser::new(&mut lexer);
208 let c = SimpleCommand {
209 assigns: vec![],
210 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
211 redirs: vec![].into(),
212 };
213
214 let result = parser.short_function_definition(c).now_or_never().unwrap();
215 let e = result.unwrap_err();
216 assert_eq!(
217 e.cause,
218 ErrorCause::Syntax(SyntaxError::InvalidFunctionBody)
219 );
220 assert_eq!(*e.location.code.value.borrow(), "() foo ; ");
221 assert_eq!(e.location.code.start_line_number.get(), 1);
222 assert_eq!(*e.location.code.source, Source::Unknown);
223 assert_eq!(e.location.range, 3..6);
224 }
225
226 #[test]
227 fn parser_short_function_definition_close_parenthesis_alias() {
228 let mut lexer = Lexer::with_code(" a b ");
229 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
230 let mut aliases = AliasSet::new();
231 let origin = Location::dummy("");
232 aliases.insert(HashEntry::new(
233 "a".to_string(),
234 "f( ".to_string(),
235 false,
236 origin.clone(),
237 ));
238 aliases.insert(HashEntry::new(
239 "b".to_string(),
240 " c".to_string(),
241 false,
242 origin.clone(),
243 ));
244 aliases.insert(HashEntry::new(
245 "c".to_string(),
246 " )\n\n(:)".to_string(),
247 false,
248 origin,
249 ));
250 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
251
252 parser.simple_command().now_or_never().unwrap().unwrap(); let sc = parser.simple_command().now_or_never().unwrap();
254 let sc = sc.unwrap().unwrap().unwrap();
255 let result = parser.short_function_definition(sc).now_or_never().unwrap();
256 let command = result.unwrap();
257 assert_matches!(command, Command::Function(f) => {
258 assert_eq!(f.has_keyword, false);
259 assert_eq!(f.name.to_string(), "f");
260 assert_eq!(f.body.to_string(), "(:)");
261 });
262
263 let next = parser.peek_token().now_or_never().unwrap().unwrap();
264 assert_eq!(next.id, EndOfInput);
265 }
266
267 #[test]
268 fn parser_short_function_definition_body_alias_and_newline() {
269 let mut lexer = Lexer::with_code(" a b ");
270 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
271 let mut aliases = AliasSet::new();
272 let origin = Location::dummy("");
273 aliases.insert(HashEntry::new(
274 "a".to_string(),
275 "f() ".to_string(),
276 false,
277 origin.clone(),
278 ));
279 aliases.insert(HashEntry::new(
280 "b".to_string(),
281 " c".to_string(),
282 false,
283 origin.clone(),
284 ));
285 aliases.insert(HashEntry::new(
286 "c".to_string(),
287 "\n\n(:)".to_string(),
288 false,
289 origin,
290 ));
291 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
292
293 parser.simple_command().now_or_never().unwrap().unwrap(); let sc = parser.simple_command().now_or_never().unwrap();
295 let sc = sc.unwrap().unwrap().unwrap();
296 let result = parser.short_function_definition(sc).now_or_never().unwrap();
297 let command = result.unwrap();
298 assert_matches!(command, Command::Function(f) => {
299 assert_eq!(f.has_keyword, false);
300 assert_eq!(f.name.to_string(), "f");
301 assert_eq!(f.body.to_string(), "(:)");
302 });
303
304 let next = parser.peek_token().now_or_never().unwrap().unwrap();
305 assert_eq!(next.id, EndOfInput);
306 }
307
308 fn portable_mode() -> yash_env::parser::Mode {
309 let mut mode = yash_env::parser::Mode::default();
310 mode.portable = true;
311 mode
312 }
313
314 #[test]
315 fn parser_short_function_definition_name_starting_with_digit_rejected_in_portable_mode() {
316 let mut lexer = Lexer::with_code("()");
317 lexer.set_mode(portable_mode());
318 let mut parser = Parser::new(&mut lexer);
319 let c = SimpleCommand {
320 assigns: vec![],
321 words: vec![("1a".parse().unwrap(), ExpansionMode::Multiple)],
322 redirs: vec![].into(),
323 };
324
325 let result = parser.short_function_definition(c).now_or_never().unwrap();
326 let e = result.unwrap_err();
327 assert_eq!(
328 e.cause,
329 ErrorCause::Syntax(SyntaxError::NonPortableFunctionName)
330 );
331 }
332
333 #[test]
334 fn parser_short_function_definition_quoted_name_rejected_in_portable_mode() {
335 let mut lexer = Lexer::with_code("()");
336 lexer.set_mode(portable_mode());
337 let mut parser = Parser::new(&mut lexer);
338 let c = SimpleCommand {
339 assigns: vec![],
340 words: vec![("'a'".parse().unwrap(), ExpansionMode::Multiple)],
341 redirs: vec![].into(),
342 };
343
344 let result = parser.short_function_definition(c).now_or_never().unwrap();
345 let e = result.unwrap_err();
346 assert_eq!(
347 e.cause,
348 ErrorCause::Syntax(SyntaxError::NonPortableFunctionName)
349 );
350 }
351
352 #[test]
353 fn parser_short_function_definition_name_with_expansion_rejected_in_portable_mode() {
354 let mut lexer = Lexer::with_code("()");
355 lexer.set_mode(portable_mode());
356 let mut parser = Parser::new(&mut lexer);
357 let c = SimpleCommand {
358 assigns: vec![],
359 words: vec![("$a".parse().unwrap(), ExpansionMode::Multiple)],
360 redirs: vec![].into(),
361 };
362
363 let result = parser.short_function_definition(c).now_or_never().unwrap();
364 let e = result.unwrap_err();
365 assert_eq!(
366 e.cause,
367 ErrorCause::Syntax(SyntaxError::NonPortableFunctionName)
368 );
369 }
370
371 #[test]
372 fn parser_short_function_definition_portable_name_allowed_in_portable_mode() {
373 let mut lexer = Lexer::with_code("() { :; }");
374 lexer.set_mode(portable_mode());
375 let mut parser = Parser::new(&mut lexer);
376 let c = SimpleCommand {
377 assigns: vec![],
378 words: vec![("_Az9".parse().unwrap(), ExpansionMode::Multiple)],
379 redirs: vec![].into(),
380 };
381
382 let result = parser.short_function_definition(c).now_or_never().unwrap();
383 let command = result.unwrap();
384 assert_matches!(command, Command::Function(f) => {
385 assert_eq!(f.name.to_string(), "_Az9");
386 });
387 }
388
389 #[test]
390 fn parser_short_function_definition_special_builtin_name_rejected_in_portable_mode() {
391 let mut lexer = Lexer::with_code("()");
392 lexer.set_mode(portable_mode());
393 let mut parser = Parser::new(&mut lexer);
394 let c = SimpleCommand {
395 assigns: vec![],
396 words: vec![("break".parse().unwrap(), ExpansionMode::Multiple)],
397 redirs: vec![].into(),
398 };
399
400 let result = parser.short_function_definition(c).now_or_never().unwrap();
401 let e = result.unwrap_err();
402 assert_eq!(
403 e.cause,
404 ErrorCause::Syntax(SyntaxError::SpecialBuiltinFunctionName)
405 );
406 }
407
408 #[test]
409 fn parser_short_function_definition_special_builtin_name_allowed_without_portable() {
410 let mut lexer = Lexer::with_code("() { :; }");
411 let mut parser = Parser::new(&mut lexer);
412 let c = SimpleCommand {
413 assigns: vec![],
414 words: vec![("break".parse().unwrap(), ExpansionMode::Multiple)],
415 redirs: vec![].into(),
416 };
417
418 let result = parser.short_function_definition(c).now_or_never().unwrap();
419 let command = result.unwrap();
420 assert_matches!(command, Command::Function(f) => {
421 assert_eq!(f.name.to_string(), "break");
422 });
423 }
424
425 #[test]
426 fn parser_short_function_definition_non_special_name_allowed_in_portable_mode() {
427 let mut lexer = Lexer::with_code("() { :; }");
428 lexer.set_mode(portable_mode());
429 let mut parser = Parser::new(&mut lexer);
430 let c = SimpleCommand {
431 assigns: vec![],
432 words: vec![("source".parse().unwrap(), ExpansionMode::Multiple)],
433 redirs: vec![].into(),
434 };
435
436 let result = parser.short_function_definition(c).now_or_never().unwrap();
437 let command = result.unwrap();
438 assert_matches!(command, Command::Function(f) => {
439 assert_eq!(f.name.to_string(), "source");
440 });
441 }
442
443 #[test]
444 fn parser_short_function_definition_non_portable_name_allowed_without_portable() {
445 let mut lexer = Lexer::with_code("() { :; }");
446 let mut parser = Parser::new(&mut lexer);
447 let c = SimpleCommand {
448 assigns: vec![],
449 words: vec![("1a".parse().unwrap(), ExpansionMode::Multiple)],
450 redirs: vec![].into(),
451 };
452
453 let result = parser.short_function_definition(c).now_or_never().unwrap();
454 let command = result.unwrap();
455 assert_matches!(command, Command::Function(f) => {
456 assert_eq!(f.name.to_string(), "1a");
457 });
458 }
459
460 #[test]
461 fn parser_short_function_definition_alias_inapplicable() {
462 let mut lexer = Lexer::with_code("()b");
463 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
464 let mut aliases = AliasSet::new();
465 let origin = Location::dummy("");
466 aliases.insert(HashEntry::new(
467 "b".to_string(),
468 " c".to_string(),
469 false,
470 origin.clone(),
471 ));
472 aliases.insert(HashEntry::new(
473 "c".to_string(),
474 "(:)".to_string(),
475 false,
476 origin,
477 ));
478 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
479 let c = SimpleCommand {
480 assigns: vec![],
481 words: vec![("f".parse().unwrap(), ExpansionMode::Multiple)],
482 redirs: vec![].into(),
483 };
484
485 let result = parser.short_function_definition(c).now_or_never().unwrap();
486 let e = result.unwrap_err();
487 assert_eq!(
488 e.cause,
489 ErrorCause::Syntax(SyntaxError::InvalidFunctionBody)
490 );
491 assert_eq!(*e.location.code.value.borrow(), "()b");
492 assert_eq!(e.location.code.start_line_number.get(), 1);
493 assert_eq!(*e.location.code.source, Source::Unknown);
494 assert_eq!(e.location.range, 2..3);
495 }
496}