Skip to main content

yash_syntax/parser/
function.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2020 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Syntax parser for function definition command
18
19use 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    /// Parses a function definition command that does not start with the
36    /// `function` reserved word.
37    ///
38    /// This function must be called just after a [simple
39    /// command](Self::simple_command) has been parsed.
40    /// The simple command must be passed as an argument.
41    /// If the simple command has only one word and the next token is `(`, it is
42    /// parsed as a function definition command.
43    /// Otherwise, the simple command is returned intact.
44    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(); // alias
253        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(); // alias
294        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}