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