yash_syntax/parser/
grouping.rs1use super::core::Parser;
20use super::core::Result;
21use super::error::Error;
22use super::error::SyntaxError;
23use super::lex::Keyword::{CloseBrace, OpenBrace};
24use super::lex::Operator::{CloseParen, OpenParen};
25use super::lex::TokenId::{Operator, Token};
26use crate::syntax::CompoundCommand;
27use std::rc::Rc;
28
29impl Parser<'_, '_> {
30 pub async fn grouping(&mut self) -> Result<CompoundCommand> {
38 let open = self.take_token_raw().await?;
39 assert_eq!(open.id, Token(Some(OpenBrace)));
40
41 let list = self.maybe_compound_list_boxed().await?;
42
43 let close = self.take_token_raw().await?;
44 if close.id != Token(Some(CloseBrace)) {
45 let opening_location = open.word.location;
46 let cause = SyntaxError::UnclosedGrouping { opening_location }.into();
47 let location = close.word.location;
48 return Err(Error { cause, location });
49 }
50
51 if list.0.is_empty() {
53 let cause = SyntaxError::EmptyGrouping.into();
54 let location = close.word.location;
55 return Err(Error { cause, location });
56 }
57
58 Ok(CompoundCommand::Grouping(list))
59 }
60
61 pub async fn subshell(&mut self) -> Result<CompoundCommand> {
69 let open = self.take_token_raw().await?;
70 assert_eq!(open.id, Operator(OpenParen));
71
72 if self.mode().portable {
75 let next = self.peek_token().await?;
76 if next.id == Operator(OpenParen) && next.index == open.index + 1 {
77 let location = next.word.location.clone();
78 let cause = SyntaxError::UnsupportedArithmeticCommand.into();
79 return Err(Error { cause, location });
80 }
81 }
82
83 let list = self.maybe_compound_list_boxed().await?;
84
85 let close = self.take_token_raw().await?;
86 if close.id != Operator(CloseParen) {
87 let opening_location = open.word.location;
88 let cause = SyntaxError::UnclosedSubshell { opening_location }.into();
89 let location = close.word.location;
90 return Err(Error { cause, location });
91 }
92
93 if list.0.is_empty() {
95 let cause = SyntaxError::EmptySubshell.into();
96 let location = close.word.location;
97 return Err(Error { cause, location });
98 }
99
100 Ok(CompoundCommand::Subshell {
101 body: Rc::new(list),
102 location: open.word.location,
103 })
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::super::error::ErrorCause;
110 use super::super::lex::Lexer;
111 use super::*;
112 use crate::alias::{AliasSet, HashEntry};
113 use crate::source::Location;
114 use crate::source::Source;
115 use assert_matches::assert_matches;
116 use futures_util::FutureExt as _;
117
118 #[test]
119 fn parser_grouping_short() {
120 let mut lexer = Lexer::with_code("{ :; }");
121 let mut parser = Parser::new(&mut lexer);
122
123 let result = parser.compound_command().now_or_never().unwrap();
124 let compound_command = result.unwrap().unwrap();
125 assert_matches!(compound_command, CompoundCommand::Grouping(list) => {
126 assert_eq!(list.to_string(), ":");
127 });
128 }
129
130 #[test]
131 fn parser_grouping_long() {
132 let mut lexer = Lexer::with_code("{ foo; bar& }");
133 let mut parser = Parser::new(&mut lexer);
134
135 let result = parser.compound_command().now_or_never().unwrap();
136 let compound_command = result.unwrap().unwrap();
137 assert_matches!(compound_command, CompoundCommand::Grouping(list) => {
138 assert_eq!(list.to_string(), "foo; bar&");
139 });
140 }
141
142 #[test]
143 fn parser_grouping_unclosed() {
144 let mut lexer = Lexer::with_code(" { oh no ");
145 let mut parser = Parser::new(&mut lexer);
146
147 let result = parser.compound_command().now_or_never().unwrap();
148 let e = result.unwrap_err();
149 assert_matches!(e.cause,
150 ErrorCause::Syntax(SyntaxError::UnclosedGrouping { opening_location }) => {
151 assert_eq!(*opening_location.code.value.borrow(), " { oh no ");
152 assert_eq!(opening_location.code.start_line_number.get(), 1);
153 assert_eq!(*opening_location.code.source, Source::Unknown);
154 assert_eq!(opening_location.range, 1..2);
155 });
156 assert_eq!(*e.location.code.value.borrow(), " { oh no ");
157 assert_eq!(e.location.code.start_line_number.get(), 1);
158 assert_eq!(*e.location.code.source, Source::Unknown);
159 assert_eq!(e.location.range, 9..9);
160 }
161
162 #[test]
163 fn parser_grouping_empty_posix() {
164 let mut lexer = Lexer::with_code("{ }");
165 let mut parser = Parser::new(&mut lexer);
166
167 let result = parser.compound_command().now_or_never().unwrap();
168 let e = result.unwrap_err();
169 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyGrouping));
170 assert_eq!(*e.location.code.value.borrow(), "{ }");
171 assert_eq!(e.location.code.start_line_number.get(), 1);
172 assert_eq!(*e.location.code.source, Source::Unknown);
173 assert_eq!(e.location.range, 2..3);
174 }
175
176 #[test]
177 fn parser_grouping_aliasing() {
178 let mut lexer = Lexer::with_code(" { :; end ");
179 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
180 let mut aliases = AliasSet::new();
181 let origin = Location::dummy("");
182 aliases.insert(HashEntry::new(
183 "{".to_string(),
184 "".to_string(),
185 false,
186 origin.clone(),
187 ));
188 aliases.insert(HashEntry::new(
189 "}".to_string(),
190 "".to_string(),
191 false,
192 origin.clone(),
193 ));
194 aliases.insert(HashEntry::new(
195 "end".to_string(),
196 "}".to_string(),
197 false,
198 origin,
199 ));
200 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
201
202 let result = parser.compound_command().now_or_never().unwrap();
203 let compound_command = result.unwrap().unwrap();
204 assert_matches!(compound_command, CompoundCommand::Grouping(list) => {
205 assert_eq!(list.to_string(), ":");
206 });
207 }
208
209 #[test]
210 fn parser_subshell_short() {
211 let mut lexer = Lexer::with_code("(:)");
212 let mut parser = Parser::new(&mut lexer);
213
214 let result = parser.compound_command().now_or_never().unwrap();
215 let compound_command = result.unwrap().unwrap();
216 assert_matches!(compound_command, CompoundCommand::Subshell { body, location } => {
217 assert_eq!(body.to_string(), ":");
218 assert_eq!(*location.code.value.borrow(), "(:)");
219 assert_eq!(location.code.start_line_number.get(), 1);
220 assert_eq!(*location.code.source, Source::Unknown);
221 assert_eq!(location.range, 0..1);
222 });
223 }
224
225 #[test]
226 fn parser_subshell_long() {
227 let mut lexer = Lexer::with_code("( foo& bar; )");
228 let mut parser = Parser::new(&mut lexer);
229
230 let result = parser.compound_command().now_or_never().unwrap();
231 let compound_command = result.unwrap().unwrap();
232 assert_matches!(compound_command, CompoundCommand::Subshell { body, location } => {
233 assert_eq!(body.to_string(), "foo& bar");
234 assert_eq!(*location.code.value.borrow(), "( foo& bar; )");
235 assert_eq!(location.code.start_line_number.get(), 1);
236 assert_eq!(*location.code.source, Source::Unknown);
237 assert_eq!(location.range, 0..1);
238 });
239 }
240
241 #[test]
242 fn parser_subshell_unclosed() {
243 let mut lexer = Lexer::with_code(" ( oh no");
244 let mut parser = Parser::new(&mut lexer);
245
246 let result = parser.compound_command().now_or_never().unwrap();
247 let e = result.unwrap_err();
248 assert_matches!(e.cause,
249 ErrorCause::Syntax(SyntaxError::UnclosedSubshell { opening_location }) => {
250 assert_eq!(*opening_location.code.value.borrow(), " ( oh no");
251 assert_eq!(opening_location.code.start_line_number.get(), 1);
252 assert_eq!(*opening_location.code.source, Source::Unknown);
253 assert_eq!(opening_location.range, 1..2);
254 });
255 assert_eq!(*e.location.code.value.borrow(), " ( oh no");
256 assert_eq!(e.location.code.start_line_number.get(), 1);
257 assert_eq!(*e.location.code.source, Source::Unknown);
258 assert_eq!(e.location.range, 8..8);
259 }
260
261 #[test]
262 fn parser_subshell_empty_posix() {
263 let mut lexer = Lexer::with_code("( )");
264 let mut parser = Parser::new(&mut lexer);
265
266 let result = parser.compound_command().now_or_never().unwrap();
267 let e = result.unwrap_err();
268 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptySubshell));
269 assert_eq!(*e.location.code.value.borrow(), "( )");
270 assert_eq!(e.location.code.start_line_number.get(), 1);
271 assert_eq!(*e.location.code.source, Source::Unknown);
272 assert_eq!(e.location.range, 2..3);
273 }
274
275 fn portable_mode() -> yash_env::parser::Mode {
276 let mut mode = yash_env::parser::Mode::default();
277 mode.portable = true;
278 mode
279 }
280
281 #[test]
282 fn parser_subshell_double_open_paren_rejected_in_portable_mode() {
283 let mut lexer = Lexer::with_code("((:))");
284 lexer.set_mode(portable_mode());
285 let mut parser = Parser::new(&mut lexer);
286
287 let e = parser
288 .compound_command()
289 .now_or_never()
290 .unwrap()
291 .unwrap_err();
292 assert_eq!(
293 e.cause,
294 ErrorCause::Syntax(SyntaxError::UnsupportedArithmeticCommand)
295 );
296 assert_eq!(e.location.range, 1..2);
298 }
299
300 #[test]
301 fn parser_subshell_spaced_open_parens_accepted_in_portable_mode() {
302 let mut lexer = Lexer::with_code("( (:))");
304 lexer.set_mode(portable_mode());
305 let mut parser = Parser::new(&mut lexer);
306
307 let result = parser.compound_command().now_or_never().unwrap();
308 let compound_command = result.unwrap().unwrap();
309 assert_matches!(compound_command, CompoundCommand::Subshell { body, .. } => {
310 assert_eq!(body.to_string(), "(:)");
311 });
312 }
313
314 #[test]
315 fn parser_subshell_double_open_paren_accepted_without_portable() {
316 let mut lexer = Lexer::with_code("((:))");
318 let mut parser = Parser::new(&mut lexer);
319
320 let result = parser.compound_command().now_or_never().unwrap();
321 let compound_command = result.unwrap().unwrap();
322 assert_matches!(compound_command, CompoundCommand::Subshell { body, .. } => {
323 assert_eq!(body.to_string(), "(:)");
324 });
325 }
326}