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