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 let list = self.maybe_compound_list_boxed().await?;
73
74 let close = self.take_token_raw().await?;
75 if close.id != Operator(CloseParen) {
76 let opening_location = open.word.location;
77 let cause = SyntaxError::UnclosedSubshell { opening_location }.into();
78 let location = close.word.location;
79 return Err(Error { cause, location });
80 }
81
82 if list.0.is_empty() {
84 let cause = SyntaxError::EmptySubshell.into();
85 let location = close.word.location;
86 return Err(Error { cause, location });
87 }
88
89 Ok(CompoundCommand::Subshell {
90 body: Rc::new(list),
91 location: open.word.location,
92 })
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::super::error::ErrorCause;
99 use super::super::lex::Lexer;
100 use super::*;
101 use crate::alias::{AliasSet, HashEntry};
102 use crate::source::Location;
103 use crate::source::Source;
104 use assert_matches::assert_matches;
105 use futures_util::FutureExt;
106
107 #[test]
108 fn parser_grouping_short() {
109 let mut lexer = Lexer::with_code("{ :; }");
110 let mut parser = Parser::new(&mut lexer);
111
112 let result = parser.compound_command().now_or_never().unwrap();
113 let compound_command = result.unwrap().unwrap();
114 assert_matches!(compound_command, CompoundCommand::Grouping(list) => {
115 assert_eq!(list.to_string(), ":");
116 });
117 }
118
119 #[test]
120 fn parser_grouping_long() {
121 let mut lexer = Lexer::with_code("{ foo; bar& }");
122 let mut parser = Parser::new(&mut lexer);
123
124 let result = parser.compound_command().now_or_never().unwrap();
125 let compound_command = result.unwrap().unwrap();
126 assert_matches!(compound_command, CompoundCommand::Grouping(list) => {
127 assert_eq!(list.to_string(), "foo; bar&");
128 });
129 }
130
131 #[test]
132 fn parser_grouping_unclosed() {
133 let mut lexer = Lexer::with_code(" { oh no ");
134 let mut parser = Parser::new(&mut lexer);
135
136 let result = parser.compound_command().now_or_never().unwrap();
137 let e = result.unwrap_err();
138 assert_matches!(e.cause,
139 ErrorCause::Syntax(SyntaxError::UnclosedGrouping { opening_location }) => {
140 assert_eq!(*opening_location.code.value.borrow(), " { oh no ");
141 assert_eq!(opening_location.code.start_line_number.get(), 1);
142 assert_eq!(*opening_location.code.source, Source::Unknown);
143 assert_eq!(opening_location.range, 1..2);
144 });
145 assert_eq!(*e.location.code.value.borrow(), " { oh no ");
146 assert_eq!(e.location.code.start_line_number.get(), 1);
147 assert_eq!(*e.location.code.source, Source::Unknown);
148 assert_eq!(e.location.range, 9..9);
149 }
150
151 #[test]
152 fn parser_grouping_empty_posix() {
153 let mut lexer = Lexer::with_code("{ }");
154 let mut parser = Parser::new(&mut lexer);
155
156 let result = parser.compound_command().now_or_never().unwrap();
157 let e = result.unwrap_err();
158 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyGrouping));
159 assert_eq!(*e.location.code.value.borrow(), "{ }");
160 assert_eq!(e.location.code.start_line_number.get(), 1);
161 assert_eq!(*e.location.code.source, Source::Unknown);
162 assert_eq!(e.location.range, 2..3);
163 }
164
165 #[test]
166 fn parser_grouping_aliasing() {
167 let mut lexer = Lexer::with_code(" { :; end ");
168 #[allow(clippy::mutable_key_type)]
169 let mut aliases = AliasSet::new();
170 let origin = Location::dummy("");
171 aliases.insert(HashEntry::new(
172 "{".to_string(),
173 "".to_string(),
174 false,
175 origin.clone(),
176 ));
177 aliases.insert(HashEntry::new(
178 "}".to_string(),
179 "".to_string(),
180 false,
181 origin.clone(),
182 ));
183 aliases.insert(HashEntry::new(
184 "end".to_string(),
185 "}".to_string(),
186 false,
187 origin,
188 ));
189 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
190
191 let result = parser.compound_command().now_or_never().unwrap();
192 let compound_command = result.unwrap().unwrap();
193 assert_matches!(compound_command, CompoundCommand::Grouping(list) => {
194 assert_eq!(list.to_string(), ":");
195 });
196 }
197
198 #[test]
199 fn parser_subshell_short() {
200 let mut lexer = Lexer::with_code("(:)");
201 let mut parser = Parser::new(&mut lexer);
202
203 let result = parser.compound_command().now_or_never().unwrap();
204 let compound_command = result.unwrap().unwrap();
205 assert_matches!(compound_command, CompoundCommand::Subshell { body, location } => {
206 assert_eq!(body.to_string(), ":");
207 assert_eq!(*location.code.value.borrow(), "(:)");
208 assert_eq!(location.code.start_line_number.get(), 1);
209 assert_eq!(*location.code.source, Source::Unknown);
210 assert_eq!(location.range, 0..1);
211 });
212 }
213
214 #[test]
215 fn parser_subshell_long() {
216 let mut lexer = Lexer::with_code("( foo& bar; )");
217 let mut parser = Parser::new(&mut lexer);
218
219 let result = parser.compound_command().now_or_never().unwrap();
220 let compound_command = result.unwrap().unwrap();
221 assert_matches!(compound_command, CompoundCommand::Subshell { body, location } => {
222 assert_eq!(body.to_string(), "foo& bar");
223 assert_eq!(*location.code.value.borrow(), "( foo& bar; )");
224 assert_eq!(location.code.start_line_number.get(), 1);
225 assert_eq!(*location.code.source, Source::Unknown);
226 assert_eq!(location.range, 0..1);
227 });
228 }
229
230 #[test]
231 fn parser_subshell_unclosed() {
232 let mut lexer = Lexer::with_code(" ( oh no");
233 let mut parser = Parser::new(&mut lexer);
234
235 let result = parser.compound_command().now_or_never().unwrap();
236 let e = result.unwrap_err();
237 assert_matches!(e.cause,
238 ErrorCause::Syntax(SyntaxError::UnclosedSubshell { opening_location }) => {
239 assert_eq!(*opening_location.code.value.borrow(), " ( oh no");
240 assert_eq!(opening_location.code.start_line_number.get(), 1);
241 assert_eq!(*opening_location.code.source, Source::Unknown);
242 assert_eq!(opening_location.range, 1..2);
243 });
244 assert_eq!(*e.location.code.value.borrow(), " ( oh no");
245 assert_eq!(e.location.code.start_line_number.get(), 1);
246 assert_eq!(*e.location.code.source, Source::Unknown);
247 assert_eq!(e.location.range, 8..8);
248 }
249
250 #[test]
251 fn parser_subshell_empty_posix() {
252 let mut lexer = Lexer::with_code("( )");
253 let mut parser = Parser::new(&mut lexer);
254
255 let result = parser.compound_command().now_or_never().unwrap();
256 let e = result.unwrap_err();
257 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptySubshell));
258 assert_eq!(*e.location.code.value.borrow(), "( )");
259 assert_eq!(e.location.code.start_line_number.get(), 1);
260 assert_eq!(*e.location.code.source, Source::Unknown);
261 assert_eq!(e.location.range, 2..3);
262 }
263}