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 crate::syntax::Command;
27use crate::syntax::FunctionDefinition;
28use crate::syntax::SimpleCommand;
29use std::rc::Rc;
30
31impl Parser<'_, '_> {
32 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 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(); 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(); 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}