yash_syntax/parser/lex/
dollar.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//! Part of the lexer that parses dollar units
18//!
19//! Note that the detail lexer for each type of dollar units in another
20//! dedicated module.
21
22use super::core::WordLexer;
23use crate::parser::core::Result;
24use crate::syntax::TextUnit;
25
26impl WordLexer<'_, '_> {
27    /// Parses a text unit that starts with `$`.
28    ///
29    /// If the next character is `$`, a parameter expansion, command
30    /// substitution, or arithmetic expansion is parsed. Otherwise, no
31    /// characters are consumed and the return value is `Ok(None)`.
32    ///
33    /// This function does not parse dollar-single-quotes. They are handled in
34    /// [`word_unit`](Self::word_unit).
35    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}