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 crate::syntax::Command;
27use crate::syntax::FunctionDefinition;
28use crate::syntax::SimpleCommand;
29use std::rc::Rc;
30
31impl Parser<'_, '_> {
32    /// Parses a function definition command that does not start with the
33    /// `function` reserved word.
34    ///
35    /// This function must be called just after a [simple
36    /// command](Self::simple_command) has been parsed.
37    /// The simple command must be passed as an argument.
38    /// If the simple command has only one word and the next token is `(`, it is
39    /// parsed as a function definition command.
40    /// Otherwise, the simple command is returned intact.
41    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        // TODO reject invalid name if POSIXly-correct
60
61        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(); // alias
232        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(); // alias
273        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}