yash_syntax/parser/lex/
dollar.rs1use super::core::WordLexer;
23use crate::parser::core::Result;
24use crate::syntax::TextUnit;
25
26impl WordLexer<'_, '_> {
27 pub async fn dollar_unit(&mut self) -> Result<Option<TextUnit>> {
36 let start_index = self.index();
37 if !self.skip_if(|c| c == '$').await? {
38 return Ok(None);
39 }
40
41 if let Some(result) = self.raw_param(start_index).await? {
42 return Ok(Some(result));
43 }
44 if let Some(result) = self.braced_param(start_index).await? {
45 return Ok(Some(TextUnit::BracedParam(result)));
46 }
47 if let Some(result) = self.arithmetic_expansion(start_index).await? {
48 return Ok(Some(result));
49 }
50 if let Some(result) = self.command_substitution(start_index).await? {
51 return Ok(Some(result));
52 }
53
54 self.rewind(start_index);
55 Ok(None)
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use crate::parser::lex::Lexer;
63 use crate::parser::lex::WordContext;
64 use crate::source::Source;
65 use crate::syntax::Literal;
66 use crate::syntax::Param;
67 use crate::syntax::SpecialParam;
68 use crate::syntax::Text;
69 use assert_matches::assert_matches;
70 use futures_util::FutureExt;
71
72 #[test]
73 fn lexer_dollar_unit_no_dollar() {
74 let mut lexer = Lexer::with_code("foo");
75 let mut lexer = WordLexer {
76 lexer: &mut lexer,
77 context: WordContext::Word,
78 };
79 let result = lexer.dollar_unit().now_or_never().unwrap().unwrap();
80 assert_eq!(result, None);
81
82 let mut lexer = Lexer::with_code("()");
83 let mut lexer = WordLexer {
84 lexer: &mut lexer,
85 context: WordContext::Word,
86 };
87 let result = lexer.dollar_unit().now_or_never().unwrap().unwrap();
88 assert_eq!(result, None);
89 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('(')));
90
91 let mut lexer = Lexer::with_code("");
92 let mut lexer = WordLexer {
93 lexer: &mut lexer,
94 context: WordContext::Word,
95 };
96 let result = lexer.dollar_unit().now_or_never().unwrap().unwrap();
97 assert_eq!(result, None);
98 }
99
100 #[test]
101 fn lexer_dollar_unit_dollar_followed_by_non_special() {
102 let mut lexer = Lexer::with_code("$;");
103 let mut lexer = WordLexer {
104 lexer: &mut lexer,
105 context: WordContext::Word,
106 };
107 let result = lexer.dollar_unit().now_or_never().unwrap().unwrap();
108 assert_eq!(result, None);
109 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('$')));
110
111 let mut lexer = Lexer::with_code("$&");
112 let mut lexer = WordLexer {
113 lexer: &mut lexer,
114 context: WordContext::Word,
115 };
116 let result = lexer.dollar_unit().now_or_never().unwrap().unwrap();
117 assert_eq!(result, None);
118 }
119
120 #[test]
121 fn lexer_dollar_unit_raw_special_parameter() {
122 let mut lexer = Lexer::with_code("$0");
123 let mut lexer = WordLexer {
124 lexer: &mut lexer,
125 context: WordContext::Word,
126 };
127 let result = lexer.dollar_unit().now_or_never().unwrap();
128 let text_unit = result.unwrap().unwrap();
129 assert_matches!(text_unit, TextUnit::RawParam { param, location } => {
130 assert_eq!(param, Param::from(SpecialParam::Zero));
131 assert_eq!(*location.code.value.borrow(), "$0");
132 assert_eq!(location.code.start_line_number.get(), 1);
133 assert_eq!(*location.code.source, Source::Unknown);
134 assert_eq!(location.range, 0..2);
135 });
136 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
137 }
138
139 #[test]
140 fn lexer_dollar_unit_command_substitution() {
141 let mut lexer = Lexer::with_code("$()");
142 let mut lexer = WordLexer {
143 lexer: &mut lexer,
144 context: WordContext::Word,
145 };
146 let text_unit = lexer.dollar_unit().now_or_never().unwrap();
147 let text_unit = text_unit.unwrap().unwrap();
148 assert_matches!(text_unit, TextUnit::CommandSubst { location, content } => {
149 assert_eq!(*location.code.value.borrow(), "$()");
150 assert_eq!(location.code.start_line_number.get(), 1);
151 assert_eq!(*location.code.source, Source::Unknown);
152 assert_eq!(location.range, 0..3);
153 assert_eq!(&*content, "");
154 });
155 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
156
157 let mut lexer = Lexer::with_code("$( foo bar )");
158 let mut lexer = WordLexer {
159 lexer: &mut lexer,
160 context: WordContext::Word,
161 };
162 let result = lexer.dollar_unit().now_or_never().unwrap();
163 let text_unit = result.unwrap().unwrap();
164 assert_matches!(text_unit, TextUnit::CommandSubst { location, content } => {
165 assert_eq!(*location.code.value.borrow(), "$( foo bar )");
166 assert_eq!(location.code.start_line_number.get(), 1);
167 assert_eq!(*location.code.source, Source::Unknown);
168 assert_eq!(location.range, 0..12);
169 assert_eq!(&*content, " foo bar ");
170 });
171 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
172 }
173
174 #[test]
175 fn lexer_dollar_unit_arithmetic_expansion() {
176 let mut lexer = Lexer::with_code("$((1))");
177 let mut lexer = WordLexer {
178 lexer: &mut lexer,
179 context: WordContext::Word,
180 };
181 let result = lexer.dollar_unit().now_or_never().unwrap();
182 let text_unit = result.unwrap().unwrap();
183 assert_matches!(text_unit, TextUnit::Arith { content, location } => {
184 assert_eq!(content, Text(vec![Literal('1')]));
185 assert_eq!(*location.code.value.borrow(), "$((1))");
186 assert_eq!(location.code.start_line_number.get(), 1);
187 assert_eq!(*location.code.source, Source::Unknown);
188 assert_eq!(location.range, 0..6);
189 });
190 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
191 }
192
193 #[test]
194 fn lexer_dollar_unit_line_continuation() {
195 let mut lexer = Lexer::with_code("$\\\n\\\n0");
196 let mut lexer = WordLexer {
197 lexer: &mut lexer,
198 context: WordContext::Word,
199 };
200 let result = lexer.dollar_unit().now_or_never().unwrap();
201 let text_unit = result.unwrap().unwrap();
202 assert_matches!(text_unit, TextUnit::RawParam { param, .. } => {
203 assert_eq!(param, Param::from(SpecialParam::Zero));
204 });
205 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
206 }
207}