yash_syntax/parser/lex/
braced_param.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 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 braced parameter expansion
18
19use super::core::WordLexer;
20use super::raw_param::is_portable_name;
21use super::raw_param::is_portable_name_char;
22use crate::parser::core::Result;
23use crate::parser::error::Error;
24use crate::parser::error::SyntaxError;
25use crate::syntax::BracedParam;
26use crate::syntax::Modifier;
27use crate::syntax::Param;
28use crate::syntax::ParamType;
29use crate::syntax::SpecialParam;
30use std::num::IntErrorKind;
31
32/// Tests if a character can be part of a variable name.
33///
34/// The current implementation is the same as [`is_portable_name_char`].
35/// Other (POSIXly non-portable) characters may be supported in the future.
36pub fn is_name_char(c: char) -> bool {
37    // TODO support other Unicode name characters
38    is_portable_name_char(c)
39}
40
41/// Tests if a string is a valid name.
42///
43/// The current implementation is the same as [`is_portable_name`].
44/// Other (POSIXly non-portable) characters may be allowed in the future.
45pub fn is_name(s: &str) -> bool {
46    // TODO support other Unicode name characters
47    is_portable_name(s)
48}
49
50/// Determines the type of the parameter.
51///
52/// This function assumes the argument contains [name characters](is_name_char)
53/// only.
54///
55/// - If the argument does not start with a digit, it is a named parameter.
56/// - Otherwise, it is a positional parameter.
57///   However, if it contains non-digit characters, it is an error.
58///
59/// This function does not care for special parameters other than `0`.
60/// The special parameter `0` is recognized only if the argument is exactly
61/// a single-digit `0`, as required by POSIX.
62#[must_use]
63fn type_of_id(id: &str) -> Option<ParamType> {
64    if id == "0" {
65        return Some(ParamType::Special(SpecialParam::Zero));
66    }
67    if id.starts_with(|c: char| c.is_ascii_digit()) {
68        return match id.parse() {
69            Ok(index) => Some(ParamType::Positional(index)),
70            Err(e) => match e.kind() {
71                IntErrorKind::PosOverflow => Some(ParamType::Positional(usize::MAX)),
72                _ => None,
73            },
74        };
75    }
76    Some(ParamType::Variable)
77}
78
79impl WordLexer<'_, '_> {
80    /// Tests if there is a length prefix (`#`).
81    ///
82    /// This function may consume many characters, possibly beyond the length
83    /// prefix, regardless of the result. The caller should remember the index
84    /// before calling this function and rewind afterwards.
85    async fn has_length_prefix(&mut self) -> Result<bool> {
86        if !self.skip_if(|c| c == '#').await? {
87            return Ok(false);
88        }
89
90        // A parameter expansion cannot have both a prefix and suffix modifier.
91        // For example, `${#-?}` is not considered to have a prefix. We need to
92        // look ahead to see if it is okay to treat the `#` as a prefix.
93        if let Some(c) = self.peek_char().await? {
94            // Check characters that cannot be a special parameter.
95            if matches!(c, '}' | '+' | '=' | ':' | '%') {
96                return Ok(false);
97            }
98
99            // Check characters that can be either a special parameter or the
100            // beginning of a modifier
101            if matches!(c, '-' | '?' | '#') {
102                self.consume_char();
103                if let Some(c) = self.peek_char().await? {
104                    return Ok(c == '}');
105                }
106            }
107        }
108
109        Ok(true)
110    }
111
112    /// Consumes a length prefix (`#`) if any.
113    async fn length_prefix(&mut self) -> Result<bool> {
114        let initial_index = self.index();
115        let has_length_prefix = self.has_length_prefix().await?;
116        self.rewind(initial_index);
117        if has_length_prefix {
118            self.peek_char().await?;
119            self.consume_char();
120        }
121        Ok(has_length_prefix)
122    }
123
124    /// Parses a parameter expansion that is enclosed in braces.
125    ///
126    /// The initial `$` must have been consumed before calling this function.
127    /// This functions checks if the next character is an opening brace. If so,
128    /// the following characters are parsed as a parameter expansion up to and
129    /// including the closing brace. Otherwise, no characters are consumed and
130    /// the return value is `Ok(None)`.
131    ///
132    /// The `start_index` parameter should be the index for the initial `$`. It is
133    /// used to construct the result, but this function does not check if it
134    /// actually points to the `$`.
135    pub async fn braced_param(&mut self, start_index: usize) -> Result<Option<BracedParam>> {
136        if !self.skip_if(|c| c == '{').await? {
137            return Ok(None);
138        }
139
140        let opening_location = self.location_range(start_index..self.index());
141
142        let has_length_prefix = self.length_prefix().await?;
143
144        let param_start_index = self.index();
145
146        let c = self.peek_char().await?.unwrap();
147        let param = if is_name_char(c) {
148            self.consume_char();
149
150            // Parse the remaining characters of the parameter name
151            let mut id = c.to_string();
152            while let Some(c) = self.consume_char_if(is_name_char).await? {
153                id.push(c.value);
154            }
155
156            let Some(r#type) = type_of_id(&id) else {
157                let cause = SyntaxError::InvalidParam.into();
158                let location = self.location_range(param_start_index..self.index());
159                return Err(Error { cause, location });
160            };
161            Param { id, r#type }
162        } else if let Some(special) = SpecialParam::from_char(c) {
163            self.consume_char();
164            Param {
165                id: c.to_string(),
166                r#type: special.into(),
167            }
168        } else {
169            let cause = SyntaxError::EmptyParam.into();
170            let location = self.location().await?.clone();
171            return Err(Error { cause, location });
172        };
173
174        let suffix_location = self.location().await?.clone();
175        let suffix = self.suffix_modifier().await?;
176
177        if !self.skip_if(|c| c == '}').await? {
178            let cause = SyntaxError::UnclosedParam { opening_location }.into();
179            let location = self.location().await?.clone();
180            return Err(Error { cause, location });
181        }
182
183        let modifier = match (has_length_prefix, suffix) {
184            (true, Modifier::None) => Modifier::Length,
185            (true, _) => {
186                let cause = SyntaxError::MultipleModifier.into();
187                let location = suffix_location;
188                return Err(Error { cause, location });
189            }
190            (false, suffix) => suffix,
191        };
192
193        Ok(Some(BracedParam {
194            param,
195            modifier,
196            location: self.location_range(start_index..self.index()),
197        }))
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use crate::parser::error::ErrorCause;
205    use crate::parser::lex::Lexer;
206    use crate::parser::lex::WordContext;
207    use crate::source::Source;
208    use crate::syntax::SwitchAction;
209    use crate::syntax::SwitchCondition;
210    use crate::syntax::TrimLength;
211    use crate::syntax::TrimSide;
212    use assert_matches::assert_matches;
213    use futures_util::FutureExt;
214
215    #[test]
216    fn lexer_braced_param_none() {
217        let mut lexer = Lexer::with_code("$foo");
218        lexer.peek_char().now_or_never().unwrap().unwrap();
219        lexer.consume_char();
220        let mut lexer = WordLexer {
221            lexer: &mut lexer,
222            context: WordContext::Word,
223        };
224        assert_eq!(lexer.braced_param(0).now_or_never().unwrap(), Ok(None));
225        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('f')));
226    }
227
228    #[test]
229    fn lexer_braced_param_minimum() {
230        let mut lexer = Lexer::with_code("${@};");
231        lexer.peek_char().now_or_never().unwrap().unwrap();
232        lexer.consume_char();
233        let mut lexer = WordLexer {
234            lexer: &mut lexer,
235            context: WordContext::Word,
236        };
237
238        let result = lexer.braced_param(0).now_or_never().unwrap();
239        let param = result.unwrap().unwrap();
240        assert_eq!(param.param, Param::from(SpecialParam::At));
241        assert_eq!(param.modifier, Modifier::None);
242        // TODO assert about other param members
243        assert_eq!(*param.location.code.value.borrow(), "${@};");
244        assert_eq!(param.location.code.start_line_number.get(), 1);
245        assert_eq!(*param.location.code.source, Source::Unknown);
246        assert_eq!(param.location.range, 0..4);
247
248        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(';')));
249    }
250
251    #[test]
252    fn lexer_braced_param_alphanumeric_name() {
253        let mut lexer = Lexer::with_code("X${foo_123}<");
254        let mut lexer = WordLexer {
255            lexer: &mut lexer,
256            context: WordContext::Word,
257        };
258        lexer.peek_char().now_or_never().unwrap().unwrap();
259        lexer.consume_char();
260        lexer.peek_char().now_or_never().unwrap().unwrap();
261        lexer.consume_char();
262
263        let result = lexer.braced_param(1).now_or_never().unwrap();
264        let param = result.unwrap().unwrap();
265        assert_eq!(param.param, Param::variable("foo_123"));
266        assert_eq!(param.modifier, Modifier::None);
267        // TODO assert about other param members
268        assert_eq!(*param.location.code.value.borrow(), "X${foo_123}<");
269        assert_eq!(param.location.code.start_line_number.get(), 1);
270        assert_eq!(*param.location.code.source, Source::Unknown);
271        assert_eq!(param.location.range, 1..11);
272
273        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
274    }
275
276    #[test]
277    fn lexer_braced_param_positional() {
278        let mut lexer = Lexer::with_code("${123}<");
279        let mut lexer = WordLexer {
280            lexer: &mut lexer,
281            context: WordContext::Word,
282        };
283        lexer.peek_char().now_or_never().unwrap().unwrap();
284        lexer.consume_char();
285
286        let result = lexer.braced_param(0).now_or_never().unwrap();
287        let param = result.unwrap().unwrap();
288        assert_eq!(param.param, Param::from(123));
289        assert_eq!(param.modifier, Modifier::None);
290        // TODO assert about other param members
291        assert_eq!(*param.location.code.value.borrow(), "${123}<");
292        assert_eq!(param.location.code.start_line_number.get(), 1);
293        assert_eq!(*param.location.code.source, Source::Unknown);
294        assert_eq!(param.location.range, 0..6);
295
296        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
297    }
298
299    /// Tests that the parameter expansion `${00}` is parsed as a positional
300    /// parameter with the index 0. Compare [`lexer_braced_param_special_zero`].
301    #[test]
302    fn lexer_braced_param_positional_zero() {
303        let mut lexer = Lexer::with_code("${00}<");
304        let mut lexer = WordLexer {
305            lexer: &mut lexer,
306            context: WordContext::Word,
307        };
308        lexer.peek_char().now_or_never().unwrap().unwrap();
309        lexer.consume_char();
310
311        let result = lexer.braced_param(0).now_or_never().unwrap();
312        let param = result.unwrap().unwrap();
313        assert_eq!(param.param.id, "00");
314        assert_eq!(param.param.r#type, ParamType::Positional(0));
315        assert_eq!(param.modifier, Modifier::None);
316        // TODO assert about other param members
317        assert_eq!(*param.location.code.value.borrow(), "${00}<");
318        assert_eq!(param.location.code.start_line_number.get(), 1);
319        assert_eq!(*param.location.code.source, Source::Unknown);
320        assert_eq!(param.location.range, 0..5);
321
322        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
323    }
324
325    #[test]
326    fn lexer_braced_param_positional_overflow() {
327        // This overflow is reported at the execution time of the script, not at
328        // the parsing time.
329        let mut lexer = Lexer::with_code("${9999999999999999999999999999999999999999}");
330        let mut lexer = WordLexer {
331            lexer: &mut lexer,
332            context: WordContext::Word,
333        };
334        lexer.peek_char().now_or_never().unwrap().unwrap();
335        lexer.consume_char();
336
337        let result = lexer.braced_param(0).now_or_never().unwrap();
338        let param = result.unwrap().unwrap();
339        assert_eq!(param.param.r#type, ParamType::Positional(usize::MAX));
340    }
341
342    #[test]
343    fn lexer_braced_param_invalid_param() {
344        let mut lexer = Lexer::with_code("${0_0}");
345        let mut lexer = WordLexer {
346            lexer: &mut lexer,
347            context: WordContext::Word,
348        };
349        lexer.peek_char().now_or_never().unwrap().unwrap();
350        lexer.consume_char();
351
352        let e = lexer.braced_param(0).now_or_never().unwrap().unwrap_err();
353        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidParam));
354        assert_eq!(*e.location.code.value.borrow(), "${0_0}");
355        assert_eq!(e.location.code.start_line_number.get(), 1);
356        assert_eq!(*e.location.code.source, Source::Unknown);
357        assert_eq!(e.location.range, 2..5);
358    }
359
360    /// Tests that the parameter expansion `${0}` is parsed as a special
361    /// parameter `0`. Compare [`lexer_braced_param_positional_zero`].
362    #[test]
363    fn lexer_braced_param_special_zero() {
364        let mut lexer = Lexer::with_code("${0}<");
365        let mut lexer = WordLexer {
366            lexer: &mut lexer,
367            context: WordContext::Word,
368        };
369        lexer.peek_char().now_or_never().unwrap().unwrap();
370        lexer.consume_char();
371
372        let result = lexer.braced_param(0).now_or_never().unwrap();
373        let param = result.unwrap().unwrap();
374        assert_eq!(param.param.id, "0");
375        assert_eq!(param.param.r#type, ParamType::Special(SpecialParam::Zero));
376        assert_eq!(param.modifier, Modifier::None);
377        // TODO assert about other param members
378        assert_eq!(*param.location.code.value.borrow(), "${0}<");
379        assert_eq!(param.location.code.start_line_number.get(), 1);
380        assert_eq!(*param.location.code.source, Source::Unknown);
381        assert_eq!(param.location.range, 0..4);
382
383        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
384    }
385
386    #[test]
387    fn lexer_braced_param_special_hash() {
388        let mut lexer = Lexer::with_code("${#}<");
389        let mut lexer = WordLexer {
390            lexer: &mut lexer,
391            context: WordContext::Word,
392        };
393        lexer.peek_char().now_or_never().unwrap().unwrap();
394        lexer.consume_char();
395
396        let result = lexer.braced_param(0).now_or_never().unwrap();
397        let param = result.unwrap().unwrap();
398        assert_eq!(param.param, Param::from(SpecialParam::Number));
399        assert_eq!(param.modifier, Modifier::None);
400        // TODO assert about other param members
401        assert_eq!(*param.location.code.value.borrow(), "${#}<");
402        assert_eq!(param.location.code.start_line_number.get(), 1);
403        assert_eq!(*param.location.code.source, Source::Unknown);
404        assert_eq!(param.location.range, 0..4);
405
406        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
407    }
408
409    #[test]
410    fn lexer_braced_param_missing_name() {
411        let mut lexer = Lexer::with_code("${};");
412        let mut lexer = WordLexer {
413            lexer: &mut lexer,
414            context: WordContext::Word,
415        };
416        lexer.peek_char().now_or_never().unwrap().unwrap();
417        lexer.consume_char();
418
419        let e = lexer.braced_param(0).now_or_never().unwrap().unwrap_err();
420        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyParam));
421        assert_eq!(*e.location.code.value.borrow(), "${};");
422        assert_eq!(e.location.code.start_line_number.get(), 1);
423        assert_eq!(*e.location.code.source, Source::Unknown);
424        assert_eq!(e.location.range, 2..3);
425    }
426
427    #[test]
428    fn lexer_braced_param_unclosed_without_name() {
429        let mut lexer = Lexer::with_code("${;");
430        let mut lexer = WordLexer {
431            lexer: &mut lexer,
432            context: WordContext::Word,
433        };
434        lexer.peek_char().now_or_never().unwrap().unwrap();
435        lexer.consume_char();
436
437        let e = lexer.braced_param(0).now_or_never().unwrap().unwrap_err();
438        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyParam));
439        assert_eq!(*e.location.code.value.borrow(), "${;");
440        assert_eq!(e.location.code.start_line_number.get(), 1);
441        assert_eq!(*e.location.code.source, Source::Unknown);
442        assert_eq!(e.location.range, 2..3);
443    }
444
445    #[test]
446    fn lexer_braced_param_unclosed_with_name() {
447        let mut lexer = Lexer::with_code("${_;");
448        let mut lexer = WordLexer {
449            lexer: &mut lexer,
450            context: WordContext::Word,
451        };
452        lexer.peek_char().now_or_never().unwrap().unwrap();
453        lexer.consume_char();
454
455        let e = lexer.braced_param(0).now_or_never().unwrap().unwrap_err();
456        assert_matches!(e.cause,
457            ErrorCause::Syntax(SyntaxError::UnclosedParam { opening_location }) => {
458            assert_eq!(*opening_location.code.value.borrow(), "${_;");
459            assert_eq!(opening_location.code.start_line_number.get(), 1);
460            assert_eq!(*opening_location.code.source, Source::Unknown);
461            assert_eq!(opening_location.range, 0..2);
462        });
463        assert_eq!(*e.location.code.value.borrow(), "${_;");
464        assert_eq!(e.location.code.start_line_number.get(), 1);
465        assert_eq!(*e.location.code.source, Source::Unknown);
466        assert_eq!(e.location.range, 3..4);
467    }
468
469    #[test]
470    fn lexer_braced_param_length_alphanumeric_name() {
471        let mut lexer = Lexer::with_code("${#foo_123}<");
472        let mut lexer = WordLexer {
473            lexer: &mut lexer,
474            context: WordContext::Word,
475        };
476        lexer.peek_char().now_or_never().unwrap().unwrap();
477        lexer.consume_char();
478
479        let result = lexer.braced_param(0).now_or_never().unwrap();
480        let param = result.unwrap().unwrap();
481        assert_eq!(param.param, Param::variable("foo_123"));
482        assert_eq!(param.modifier, Modifier::Length);
483        // TODO assert about other param members
484        assert_eq!(*param.location.code.value.borrow(), "${#foo_123}<");
485        assert_eq!(param.location.code.start_line_number.get(), 1);
486        assert_eq!(*param.location.code.source, Source::Unknown);
487        assert_eq!(param.location.range, 0..11);
488
489        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
490    }
491
492    #[test]
493    fn lexer_braced_param_length_hash() {
494        let mut lexer = Lexer::with_code("${##}<");
495        let mut lexer = WordLexer {
496            lexer: &mut lexer,
497            context: WordContext::Word,
498        };
499        lexer.peek_char().now_or_never().unwrap().unwrap();
500        lexer.consume_char();
501
502        let result = lexer.braced_param(0).now_or_never().unwrap();
503        let param = result.unwrap().unwrap();
504        assert_eq!(param.param, Param::from(SpecialParam::Number));
505        assert_eq!(param.modifier, Modifier::Length);
506        // TODO assert about other param members
507        assert_eq!(*param.location.code.value.borrow(), "${##}<");
508        assert_eq!(param.location.code.start_line_number.get(), 1);
509        assert_eq!(*param.location.code.source, Source::Unknown);
510        assert_eq!(param.location.range, 0..5);
511
512        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
513    }
514
515    #[test]
516    fn lexer_braced_param_length_question() {
517        let mut lexer = Lexer::with_code("${#?}<");
518        let mut lexer = WordLexer {
519            lexer: &mut lexer,
520            context: WordContext::Word,
521        };
522        lexer.peek_char().now_or_never().unwrap().unwrap();
523        lexer.consume_char();
524
525        let result = lexer.braced_param(0).now_or_never().unwrap();
526        let param = result.unwrap().unwrap();
527        assert_eq!(param.param, Param::from(SpecialParam::Question));
528        assert_eq!(param.modifier, Modifier::Length);
529        // TODO assert about other param members
530        assert_eq!(*param.location.code.value.borrow(), "${#?}<");
531        assert_eq!(param.location.code.start_line_number.get(), 1);
532        assert_eq!(*param.location.code.source, Source::Unknown);
533        assert_eq!(param.location.range, 0..5);
534
535        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
536    }
537
538    #[test]
539    fn lexer_braced_param_length_hyphen() {
540        let mut lexer = Lexer::with_code("${#-}<");
541        let mut lexer = WordLexer {
542            lexer: &mut lexer,
543            context: WordContext::Word,
544        };
545        lexer.peek_char().now_or_never().unwrap().unwrap();
546        lexer.consume_char();
547
548        let result = lexer.braced_param(0).now_or_never().unwrap();
549        let param = result.unwrap().unwrap();
550        assert_eq!(param.param, Param::from(SpecialParam::Hyphen));
551        assert_eq!(param.modifier, Modifier::Length);
552        // TODO assert about other param members
553        assert_eq!(*param.location.code.value.borrow(), "${#-}<");
554        assert_eq!(param.location.code.start_line_number.get(), 1);
555        assert_eq!(*param.location.code.source, Source::Unknown);
556        assert_eq!(param.location.range, 0..5);
557
558        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
559    }
560
561    #[test]
562    fn lexer_braced_param_switch_minimum() {
563        let mut lexer = Lexer::with_code("${x+})");
564        let mut lexer = WordLexer {
565            lexer: &mut lexer,
566            context: WordContext::Word,
567        };
568        lexer.peek_char().now_or_never().unwrap().unwrap();
569        lexer.consume_char();
570
571        let result = lexer.braced_param(0).now_or_never().unwrap();
572        let param = result.unwrap().unwrap();
573        assert_eq!(param.param, Param::variable("x"));
574        assert_matches!(param.modifier, Modifier::Switch(switch) => {
575            assert_eq!(switch.action, SwitchAction::Alter);
576            assert_eq!(switch.condition, SwitchCondition::Unset);
577            assert_eq!(switch.word.to_string(), "");
578        });
579        // TODO assert about other param members
580        assert_eq!(*param.location.code.value.borrow(), "${x+})");
581        assert_eq!(param.location.code.start_line_number.get(), 1);
582        assert_eq!(*param.location.code.source, Source::Unknown);
583        assert_eq!(param.location.range, 0..5);
584
585        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(')')));
586    }
587
588    #[test]
589    fn lexer_braced_param_switch_full() {
590        let mut lexer = Lexer::with_code("${foo:?'!'})");
591        let mut lexer = WordLexer {
592            lexer: &mut lexer,
593            context: WordContext::Word,
594        };
595        lexer.peek_char().now_or_never().unwrap().unwrap();
596        lexer.consume_char();
597
598        let result = lexer.braced_param(0).now_or_never().unwrap();
599        let param = result.unwrap().unwrap();
600        assert_eq!(param.param, Param::variable("foo"));
601        assert_matches!(param.modifier, Modifier::Switch(switch) => {
602            assert_eq!(switch.action, SwitchAction::Error);
603            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
604            assert_eq!(switch.word.to_string(), "'!'");
605        });
606        // TODO assert about other param members
607        assert_eq!(*param.location.code.value.borrow(), "${foo:?'!'})");
608        assert_eq!(param.location.code.start_line_number.get(), 1);
609        assert_eq!(*param.location.code.source, Source::Unknown);
610        assert_eq!(param.location.range, 0..11);
611
612        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(')')));
613    }
614
615    #[test]
616    fn lexer_braced_param_hash_suffix_alter() {
617        let mut lexer = Lexer::with_code("${#+?}<");
618        let mut lexer = WordLexer {
619            lexer: &mut lexer,
620            context: WordContext::Word,
621        };
622        lexer.peek_char().now_or_never().unwrap().unwrap();
623        lexer.consume_char();
624
625        let result = lexer.braced_param(0).now_or_never().unwrap();
626        let param = result.unwrap().unwrap();
627        assert_eq!(param.param, Param::from(SpecialParam::Number));
628        assert_matches!(param.modifier, Modifier::Switch(switch) => {
629            assert_eq!(switch.action, SwitchAction::Alter);
630            assert_eq!(switch.condition, SwitchCondition::Unset);
631            assert_eq!(switch.word.to_string(), "?");
632        });
633        // TODO assert about other param members
634        assert_eq!(*param.location.code.value.borrow(), "${#+?}<");
635        assert_eq!(param.location.code.start_line_number.get(), 1);
636        assert_eq!(*param.location.code.source, Source::Unknown);
637        assert_eq!(param.location.range, 0..6);
638
639        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
640    }
641
642    #[test]
643    fn lexer_braced_param_hash_suffix_default() {
644        let mut lexer = Lexer::with_code("${#--}<");
645        let mut lexer = WordLexer {
646            lexer: &mut lexer,
647            context: WordContext::Word,
648        };
649        lexer.peek_char().now_or_never().unwrap().unwrap();
650        lexer.consume_char();
651
652        let result = lexer.braced_param(0).now_or_never().unwrap();
653        let param = result.unwrap().unwrap();
654        assert_eq!(param.param, Param::from(SpecialParam::Number));
655        assert_matches!(param.modifier, Modifier::Switch(switch) => {
656            assert_eq!(switch.action, SwitchAction::Default);
657            assert_eq!(switch.condition, SwitchCondition::Unset);
658            assert_eq!(switch.word.to_string(), "-");
659        });
660        // TODO assert about other param members
661        assert_eq!(*param.location.code.value.borrow(), "${#--}<");
662        assert_eq!(param.location.code.start_line_number.get(), 1);
663        assert_eq!(*param.location.code.source, Source::Unknown);
664        assert_eq!(param.location.range, 0..6);
665
666        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
667    }
668
669    #[test]
670    fn lexer_braced_param_hash_suffix_assign() {
671        let mut lexer = Lexer::with_code("${#=?}<");
672        let mut lexer = WordLexer {
673            lexer: &mut lexer,
674            context: WordContext::Word,
675        };
676        lexer.peek_char().now_or_never().unwrap().unwrap();
677        lexer.consume_char();
678
679        let result = lexer.braced_param(0).now_or_never().unwrap();
680        let param = result.unwrap().unwrap();
681        assert_eq!(param.param, Param::from(SpecialParam::Number));
682        assert_matches!(param.modifier, Modifier::Switch(switch) => {
683            assert_eq!(switch.action, SwitchAction::Assign);
684            assert_eq!(switch.condition, SwitchCondition::Unset);
685            assert_eq!(switch.word.to_string(), "?");
686        });
687        // TODO assert about other param members
688        assert_eq!(*param.location.code.value.borrow(), "${#=?}<");
689        assert_eq!(param.location.code.start_line_number.get(), 1);
690        assert_eq!(*param.location.code.source, Source::Unknown);
691        assert_eq!(param.location.range, 0..6);
692
693        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
694    }
695
696    #[test]
697    fn lexer_braced_param_hash_suffix_error() {
698        let mut lexer = Lexer::with_code("${#??}<");
699        let mut lexer = WordLexer {
700            lexer: &mut lexer,
701            context: WordContext::Word,
702        };
703        lexer.peek_char().now_or_never().unwrap().unwrap();
704        lexer.consume_char();
705
706        let result = lexer.braced_param(0).now_or_never().unwrap();
707        let param = result.unwrap().unwrap();
708        assert_eq!(param.param, Param::from(SpecialParam::Number));
709        assert_matches!(param.modifier, Modifier::Switch(switch) => {
710            assert_eq!(switch.action, SwitchAction::Error);
711            assert_eq!(switch.condition, SwitchCondition::Unset);
712            assert_eq!(switch.word.to_string(), "?");
713        });
714        // TODO assert about other param members
715        assert_eq!(*param.location.code.value.borrow(), "${#??}<");
716        assert_eq!(param.location.code.start_line_number.get(), 1);
717        assert_eq!(*param.location.code.source, Source::Unknown);
718        assert_eq!(param.location.range, 0..6);
719
720        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
721    }
722
723    #[test]
724    fn lexer_braced_param_hash_suffix_with_colon() {
725        let mut lexer = Lexer::with_code("${#:-}<");
726        let mut lexer = WordLexer {
727            lexer: &mut lexer,
728            context: WordContext::Word,
729        };
730        lexer.peek_char().now_or_never().unwrap().unwrap();
731        lexer.consume_char();
732
733        let result = lexer.braced_param(0).now_or_never().unwrap();
734        let param = result.unwrap().unwrap();
735        assert_eq!(param.param, Param::from(SpecialParam::Number));
736        assert_matches!(param.modifier, Modifier::Switch(switch) => {
737            assert_eq!(switch.action, SwitchAction::Default);
738            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
739            assert_eq!(switch.word.to_string(), "");
740        });
741        // TODO assert about other param members
742        assert_eq!(*param.location.code.value.borrow(), "${#:-}<");
743        assert_eq!(param.location.code.start_line_number.get(), 1);
744        assert_eq!(*param.location.code.source, Source::Unknown);
745        assert_eq!(param.location.range, 0..6);
746
747        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
748    }
749
750    #[test]
751    fn lexer_braced_param_hash_with_longest_prefix_trim() {
752        let mut lexer = Lexer::with_code("${###};");
753        let mut lexer = WordLexer {
754            lexer: &mut lexer,
755            context: WordContext::Word,
756        };
757        lexer.peek_char().now_or_never().unwrap().unwrap();
758        lexer.consume_char();
759
760        let result = lexer.braced_param(0).now_or_never().unwrap();
761        let param = result.unwrap().unwrap();
762        assert_eq!(param.param, Param::from(SpecialParam::Number));
763        assert_matches!(param.modifier, Modifier::Trim(trim) => {
764            assert_eq!(trim.side, TrimSide::Prefix);
765            assert_eq!(trim.length, TrimLength::Longest);
766            assert_eq!(trim.pattern.to_string(), "");
767        });
768        // TODO assert about other param members
769        assert_eq!(*param.location.code.value.borrow(), "${###};");
770        assert_eq!(param.location.code.start_line_number.get(), 1);
771        assert_eq!(*param.location.code.source, Source::Unknown);
772        assert_eq!(param.location.range, 0..6);
773
774        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(';')));
775    }
776
777    #[test]
778    fn lexer_braced_param_hash_with_suffix_trim() {
779        let mut lexer = Lexer::with_code("${#%};");
780        let mut lexer = WordLexer {
781            lexer: &mut lexer,
782            context: WordContext::Word,
783        };
784        lexer.peek_char().now_or_never().unwrap().unwrap();
785        lexer.consume_char();
786
787        let result = lexer.braced_param(0).now_or_never().unwrap();
788        let param = result.unwrap().unwrap();
789        assert_eq!(param.param, Param::from(SpecialParam::Number));
790        assert_matches!(param.modifier, Modifier::Trim(trim) => {
791            assert_eq!(trim.side, TrimSide::Suffix);
792            assert_eq!(trim.length, TrimLength::Shortest);
793            assert_eq!(trim.pattern.to_string(), "");
794        });
795        // TODO assert about other param members
796        assert_eq!(*param.location.code.value.borrow(), "${#%};");
797        assert_eq!(param.location.code.start_line_number.get(), 1);
798        assert_eq!(*param.location.code.source, Source::Unknown);
799        assert_eq!(param.location.range, 0..5);
800
801        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(';')));
802    }
803
804    #[test]
805    fn lexer_braced_param_multiple_modifier() {
806        let mut lexer = Lexer::with_code("${#x+};");
807        let mut lexer = WordLexer {
808            lexer: &mut lexer,
809            context: WordContext::Word,
810        };
811        lexer.peek_char().now_or_never().unwrap().unwrap();
812        lexer.consume_char();
813
814        let e = lexer.braced_param(0).now_or_never().unwrap().unwrap_err();
815        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MultipleModifier));
816        assert_eq!(*e.location.code.value.borrow(), "${#x+};");
817        assert_eq!(e.location.range, 4..5);
818    }
819
820    #[test]
821    fn lexer_braced_param_line_continuations() {
822        let mut lexer = Lexer::with_code("${\\\n#\\\n\\\na_\\\n1\\\n\\\n}z");
823        let mut lexer = WordLexer {
824            lexer: &mut lexer,
825            context: WordContext::Word,
826        };
827        lexer.peek_char().now_or_never().unwrap().unwrap();
828        lexer.consume_char();
829
830        let result = lexer.braced_param(0).now_or_never().unwrap();
831        let param = result.unwrap().unwrap();
832        assert_eq!(param.param, Param::variable("a_1"));
833        assert_eq!(param.modifier, Modifier::Length);
834        // TODO assert about other param members
835        assert_eq!(
836            *param.location.code.value.borrow(),
837            "${\\\n#\\\n\\\na_\\\n1\\\n\\\n}z"
838        );
839        assert_eq!(param.location.code.start_line_number.get(), 1);
840        assert_eq!(*param.location.code.source, Source::Unknown);
841        assert_eq!(param.location.range, 0..19);
842
843        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('z')));
844    }
845
846    #[test]
847    fn lexer_braced_param_line_continuations_hash() {
848        let mut lexer = Lexer::with_code("${#\\\n\\\n}z");
849        let mut lexer = WordLexer {
850            lexer: &mut lexer,
851            context: WordContext::Word,
852        };
853        lexer.peek_char().now_or_never().unwrap().unwrap();
854        lexer.consume_char();
855
856        let result = lexer.braced_param(0).now_or_never().unwrap();
857        let param = result.unwrap().unwrap();
858        assert_eq!(param.param, Param::from(SpecialParam::Number));
859        assert_eq!(param.modifier, Modifier::None);
860        // TODO assert about other param members
861        assert_eq!(*param.location.code.value.borrow(), "${#\\\n\\\n}z");
862        assert_eq!(param.location.code.start_line_number.get(), 1);
863        assert_eq!(*param.location.code.source, Source::Unknown);
864        assert_eq!(param.location.range, 0..8);
865
866        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('z')));
867    }
868}