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