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::from_memory("$foo", Source::Unknown);
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::from_memory("${@};", Source::Unknown);
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::from_memory("X${foo_123}<", Source::Unknown);
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::from_memory("${123}<", Source::Unknown);
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::from_memory("${00}<", Source::Unknown);
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::from_memory(
320            "${9999999999999999999999999999999999999999}",
321            Source::Unknown,
322        );
323        let mut lexer = WordLexer {
324            lexer: &mut lexer,
325            context: WordContext::Word,
326        };
327        lexer.peek_char().now_or_never().unwrap().unwrap();
328        lexer.consume_char();
329
330        let result = lexer.braced_param(0).now_or_never().unwrap();
331        let param = result.unwrap().unwrap();
332        assert_eq!(param.param.r#type, ParamType::Positional(usize::MAX));
333    }
334
335    #[test]
336    fn lexer_braced_param_invalid_param() {
337        let mut lexer = Lexer::from_memory("${0_0}", Source::Unknown);
338        let mut lexer = WordLexer {
339            lexer: &mut lexer,
340            context: WordContext::Word,
341        };
342        lexer.peek_char().now_or_never().unwrap().unwrap();
343        lexer.consume_char();
344
345        let e = lexer.braced_param(0).now_or_never().unwrap().unwrap_err();
346        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidParam));
347        assert_eq!(*e.location.code.value.borrow(), "${0_0}");
348        assert_eq!(e.location.code.start_line_number.get(), 1);
349        assert_eq!(*e.location.code.source, Source::Unknown);
350        assert_eq!(e.location.range, 2..5);
351    }
352
353    /// Tests that the parameter expansion `${0}` is parsed as a special
354    /// parameter `0`. Compare [`lexer_braced_param_positional_zero`].
355    #[test]
356    fn lexer_braced_param_special_zero() {
357        let mut lexer = Lexer::from_memory("${0}<", Source::Unknown);
358        let mut lexer = WordLexer {
359            lexer: &mut lexer,
360            context: WordContext::Word,
361        };
362        lexer.peek_char().now_or_never().unwrap().unwrap();
363        lexer.consume_char();
364
365        let result = lexer.braced_param(0).now_or_never().unwrap();
366        let param = result.unwrap().unwrap();
367        assert_eq!(param.param.id, "0");
368        assert_eq!(param.param.r#type, ParamType::Special(SpecialParam::Zero));
369        assert_eq!(param.modifier, Modifier::None);
370        // TODO assert about other param members
371        assert_eq!(*param.location.code.value.borrow(), "${0}<");
372        assert_eq!(param.location.code.start_line_number.get(), 1);
373        assert_eq!(*param.location.code.source, Source::Unknown);
374        assert_eq!(param.location.range, 0..4);
375
376        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
377    }
378
379    #[test]
380    fn lexer_braced_param_special_hash() {
381        let mut lexer = Lexer::from_memory("${#}<", Source::Unknown);
382        let mut lexer = WordLexer {
383            lexer: &mut lexer,
384            context: WordContext::Word,
385        };
386        lexer.peek_char().now_or_never().unwrap().unwrap();
387        lexer.consume_char();
388
389        let result = lexer.braced_param(0).now_or_never().unwrap();
390        let param = result.unwrap().unwrap();
391        assert_eq!(param.param, Param::from(SpecialParam::Number));
392        assert_eq!(param.modifier, Modifier::None);
393        // TODO assert about other param members
394        assert_eq!(*param.location.code.value.borrow(), "${#}<");
395        assert_eq!(param.location.code.start_line_number.get(), 1);
396        assert_eq!(*param.location.code.source, Source::Unknown);
397        assert_eq!(param.location.range, 0..4);
398
399        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
400    }
401
402    #[test]
403    fn lexer_braced_param_missing_name() {
404        let mut lexer = Lexer::from_memory("${};", Source::Unknown);
405        let mut lexer = WordLexer {
406            lexer: &mut lexer,
407            context: WordContext::Word,
408        };
409        lexer.peek_char().now_or_never().unwrap().unwrap();
410        lexer.consume_char();
411
412        let e = lexer.braced_param(0).now_or_never().unwrap().unwrap_err();
413        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyParam));
414        assert_eq!(*e.location.code.value.borrow(), "${};");
415        assert_eq!(e.location.code.start_line_number.get(), 1);
416        assert_eq!(*e.location.code.source, Source::Unknown);
417        assert_eq!(e.location.range, 2..3);
418    }
419
420    #[test]
421    fn lexer_braced_param_unclosed_without_name() {
422        let mut lexer = Lexer::from_memory("${;", Source::Unknown);
423        let mut lexer = WordLexer {
424            lexer: &mut lexer,
425            context: WordContext::Word,
426        };
427        lexer.peek_char().now_or_never().unwrap().unwrap();
428        lexer.consume_char();
429
430        let e = lexer.braced_param(0).now_or_never().unwrap().unwrap_err();
431        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyParam));
432        assert_eq!(*e.location.code.value.borrow(), "${;");
433        assert_eq!(e.location.code.start_line_number.get(), 1);
434        assert_eq!(*e.location.code.source, Source::Unknown);
435        assert_eq!(e.location.range, 2..3);
436    }
437
438    #[test]
439    fn lexer_braced_param_unclosed_with_name() {
440        let mut lexer = Lexer::from_memory("${_;", Source::Unknown);
441        let mut lexer = WordLexer {
442            lexer: &mut lexer,
443            context: WordContext::Word,
444        };
445        lexer.peek_char().now_or_never().unwrap().unwrap();
446        lexer.consume_char();
447
448        let e = lexer.braced_param(0).now_or_never().unwrap().unwrap_err();
449        assert_matches!(e.cause,
450            ErrorCause::Syntax(SyntaxError::UnclosedParam { opening_location }) => {
451            assert_eq!(*opening_location.code.value.borrow(), "${_;");
452            assert_eq!(opening_location.code.start_line_number.get(), 1);
453            assert_eq!(*opening_location.code.source, Source::Unknown);
454            assert_eq!(opening_location.range, 0..2);
455        });
456        assert_eq!(*e.location.code.value.borrow(), "${_;");
457        assert_eq!(e.location.code.start_line_number.get(), 1);
458        assert_eq!(*e.location.code.source, Source::Unknown);
459        assert_eq!(e.location.range, 3..4);
460    }
461
462    #[test]
463    fn lexer_braced_param_length_alphanumeric_name() {
464        let mut lexer = Lexer::from_memory("${#foo_123}<", Source::Unknown);
465        let mut lexer = WordLexer {
466            lexer: &mut lexer,
467            context: WordContext::Word,
468        };
469        lexer.peek_char().now_or_never().unwrap().unwrap();
470        lexer.consume_char();
471
472        let result = lexer.braced_param(0).now_or_never().unwrap();
473        let param = result.unwrap().unwrap();
474        assert_eq!(param.param, Param::variable("foo_123"));
475        assert_eq!(param.modifier, Modifier::Length);
476        // TODO assert about other param members
477        assert_eq!(*param.location.code.value.borrow(), "${#foo_123}<");
478        assert_eq!(param.location.code.start_line_number.get(), 1);
479        assert_eq!(*param.location.code.source, Source::Unknown);
480        assert_eq!(param.location.range, 0..11);
481
482        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
483    }
484
485    #[test]
486    fn lexer_braced_param_length_hash() {
487        let mut lexer = Lexer::from_memory("${##}<", Source::Unknown);
488        let mut lexer = WordLexer {
489            lexer: &mut lexer,
490            context: WordContext::Word,
491        };
492        lexer.peek_char().now_or_never().unwrap().unwrap();
493        lexer.consume_char();
494
495        let result = lexer.braced_param(0).now_or_never().unwrap();
496        let param = result.unwrap().unwrap();
497        assert_eq!(param.param, Param::from(SpecialParam::Number));
498        assert_eq!(param.modifier, Modifier::Length);
499        // TODO assert about other param members
500        assert_eq!(*param.location.code.value.borrow(), "${##}<");
501        assert_eq!(param.location.code.start_line_number.get(), 1);
502        assert_eq!(*param.location.code.source, Source::Unknown);
503        assert_eq!(param.location.range, 0..5);
504
505        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
506    }
507
508    #[test]
509    fn lexer_braced_param_length_question() {
510        let mut lexer = Lexer::from_memory("${#?}<", Source::Unknown);
511        let mut lexer = WordLexer {
512            lexer: &mut lexer,
513            context: WordContext::Word,
514        };
515        lexer.peek_char().now_or_never().unwrap().unwrap();
516        lexer.consume_char();
517
518        let result = lexer.braced_param(0).now_or_never().unwrap();
519        let param = result.unwrap().unwrap();
520        assert_eq!(param.param, Param::from(SpecialParam::Question));
521        assert_eq!(param.modifier, Modifier::Length);
522        // TODO assert about other param members
523        assert_eq!(*param.location.code.value.borrow(), "${#?}<");
524        assert_eq!(param.location.code.start_line_number.get(), 1);
525        assert_eq!(*param.location.code.source, Source::Unknown);
526        assert_eq!(param.location.range, 0..5);
527
528        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
529    }
530
531    #[test]
532    fn lexer_braced_param_length_hyphen() {
533        let mut lexer = Lexer::from_memory("${#-}<", Source::Unknown);
534        let mut lexer = WordLexer {
535            lexer: &mut lexer,
536            context: WordContext::Word,
537        };
538        lexer.peek_char().now_or_never().unwrap().unwrap();
539        lexer.consume_char();
540
541        let result = lexer.braced_param(0).now_or_never().unwrap();
542        let param = result.unwrap().unwrap();
543        assert_eq!(param.param, Param::from(SpecialParam::Hyphen));
544        assert_eq!(param.modifier, Modifier::Length);
545        // TODO assert about other param members
546        assert_eq!(*param.location.code.value.borrow(), "${#-}<");
547        assert_eq!(param.location.code.start_line_number.get(), 1);
548        assert_eq!(*param.location.code.source, Source::Unknown);
549        assert_eq!(param.location.range, 0..5);
550
551        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
552    }
553
554    #[test]
555    fn lexer_braced_param_switch_minimum() {
556        let mut lexer = Lexer::from_memory("${x+})", Source::Unknown);
557        let mut lexer = WordLexer {
558            lexer: &mut lexer,
559            context: WordContext::Word,
560        };
561        lexer.peek_char().now_or_never().unwrap().unwrap();
562        lexer.consume_char();
563
564        let result = lexer.braced_param(0).now_or_never().unwrap();
565        let param = result.unwrap().unwrap();
566        assert_eq!(param.param, Param::variable("x"));
567        assert_matches!(param.modifier, Modifier::Switch(switch) => {
568            assert_eq!(switch.r#type, SwitchType::Alter);
569            assert_eq!(switch.condition, SwitchCondition::Unset);
570            assert_eq!(switch.word.to_string(), "");
571        });
572        // TODO assert about other param members
573        assert_eq!(*param.location.code.value.borrow(), "${x+})");
574        assert_eq!(param.location.code.start_line_number.get(), 1);
575        assert_eq!(*param.location.code.source, Source::Unknown);
576        assert_eq!(param.location.range, 0..5);
577
578        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(')')));
579    }
580
581    #[test]
582    fn lexer_braced_param_switch_full() {
583        let mut lexer = Lexer::from_memory("${foo:?'!'})", Source::Unknown);
584        let mut lexer = WordLexer {
585            lexer: &mut lexer,
586            context: WordContext::Word,
587        };
588        lexer.peek_char().now_or_never().unwrap().unwrap();
589        lexer.consume_char();
590
591        let result = lexer.braced_param(0).now_or_never().unwrap();
592        let param = result.unwrap().unwrap();
593        assert_eq!(param.param, Param::variable("foo"));
594        assert_matches!(param.modifier, Modifier::Switch(switch) => {
595            assert_eq!(switch.r#type, SwitchType::Error);
596            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
597            assert_eq!(switch.word.to_string(), "'!'");
598        });
599        // TODO assert about other param members
600        assert_eq!(*param.location.code.value.borrow(), "${foo:?'!'})");
601        assert_eq!(param.location.code.start_line_number.get(), 1);
602        assert_eq!(*param.location.code.source, Source::Unknown);
603        assert_eq!(param.location.range, 0..11);
604
605        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(')')));
606    }
607
608    #[test]
609    fn lexer_braced_param_hash_suffix_alter() {
610        let mut lexer = Lexer::from_memory("${#+?}<", Source::Unknown);
611        let mut lexer = WordLexer {
612            lexer: &mut lexer,
613            context: WordContext::Word,
614        };
615        lexer.peek_char().now_or_never().unwrap().unwrap();
616        lexer.consume_char();
617
618        let result = lexer.braced_param(0).now_or_never().unwrap();
619        let param = result.unwrap().unwrap();
620        assert_eq!(param.param, Param::from(SpecialParam::Number));
621        assert_matches!(param.modifier, Modifier::Switch(switch) => {
622            assert_eq!(switch.r#type, SwitchType::Alter);
623            assert_eq!(switch.condition, SwitchCondition::Unset);
624            assert_eq!(switch.word.to_string(), "?");
625        });
626        // TODO assert about other param members
627        assert_eq!(*param.location.code.value.borrow(), "${#+?}<");
628        assert_eq!(param.location.code.start_line_number.get(), 1);
629        assert_eq!(*param.location.code.source, Source::Unknown);
630        assert_eq!(param.location.range, 0..6);
631
632        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
633    }
634
635    #[test]
636    fn lexer_braced_param_hash_suffix_default() {
637        let mut lexer = Lexer::from_memory("${#--}<", Source::Unknown);
638        let mut lexer = WordLexer {
639            lexer: &mut lexer,
640            context: WordContext::Word,
641        };
642        lexer.peek_char().now_or_never().unwrap().unwrap();
643        lexer.consume_char();
644
645        let result = lexer.braced_param(0).now_or_never().unwrap();
646        let param = result.unwrap().unwrap();
647        assert_eq!(param.param, Param::from(SpecialParam::Number));
648        assert_matches!(param.modifier, Modifier::Switch(switch) => {
649            assert_eq!(switch.r#type, SwitchType::Default);
650            assert_eq!(switch.condition, SwitchCondition::Unset);
651            assert_eq!(switch.word.to_string(), "-");
652        });
653        // TODO assert about other param members
654        assert_eq!(*param.location.code.value.borrow(), "${#--}<");
655        assert_eq!(param.location.code.start_line_number.get(), 1);
656        assert_eq!(*param.location.code.source, Source::Unknown);
657        assert_eq!(param.location.range, 0..6);
658
659        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
660    }
661
662    #[test]
663    fn lexer_braced_param_hash_suffix_assign() {
664        let mut lexer = Lexer::from_memory("${#=?}<", Source::Unknown);
665        let mut lexer = WordLexer {
666            lexer: &mut lexer,
667            context: WordContext::Word,
668        };
669        lexer.peek_char().now_or_never().unwrap().unwrap();
670        lexer.consume_char();
671
672        let result = lexer.braced_param(0).now_or_never().unwrap();
673        let param = result.unwrap().unwrap();
674        assert_eq!(param.param, Param::from(SpecialParam::Number));
675        assert_matches!(param.modifier, Modifier::Switch(switch) => {
676            assert_eq!(switch.r#type, SwitchType::Assign);
677            assert_eq!(switch.condition, SwitchCondition::Unset);
678            assert_eq!(switch.word.to_string(), "?");
679        });
680        // TODO assert about other param members
681        assert_eq!(*param.location.code.value.borrow(), "${#=?}<");
682        assert_eq!(param.location.code.start_line_number.get(), 1);
683        assert_eq!(*param.location.code.source, Source::Unknown);
684        assert_eq!(param.location.range, 0..6);
685
686        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
687    }
688
689    #[test]
690    fn lexer_braced_param_hash_suffix_error() {
691        let mut lexer = Lexer::from_memory("${#??}<", Source::Unknown);
692        let mut lexer = WordLexer {
693            lexer: &mut lexer,
694            context: WordContext::Word,
695        };
696        lexer.peek_char().now_or_never().unwrap().unwrap();
697        lexer.consume_char();
698
699        let result = lexer.braced_param(0).now_or_never().unwrap();
700        let param = result.unwrap().unwrap();
701        assert_eq!(param.param, Param::from(SpecialParam::Number));
702        assert_matches!(param.modifier, Modifier::Switch(switch) => {
703            assert_eq!(switch.r#type, SwitchType::Error);
704            assert_eq!(switch.condition, SwitchCondition::Unset);
705            assert_eq!(switch.word.to_string(), "?");
706        });
707        // TODO assert about other param members
708        assert_eq!(*param.location.code.value.borrow(), "${#??}<");
709        assert_eq!(param.location.code.start_line_number.get(), 1);
710        assert_eq!(*param.location.code.source, Source::Unknown);
711        assert_eq!(param.location.range, 0..6);
712
713        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
714    }
715
716    #[test]
717    fn lexer_braced_param_hash_suffix_with_colon() {
718        let mut lexer = Lexer::from_memory("${#:-}<", Source::Unknown);
719        let mut lexer = WordLexer {
720            lexer: &mut lexer,
721            context: WordContext::Word,
722        };
723        lexer.peek_char().now_or_never().unwrap().unwrap();
724        lexer.consume_char();
725
726        let result = lexer.braced_param(0).now_or_never().unwrap();
727        let param = result.unwrap().unwrap();
728        assert_eq!(param.param, Param::from(SpecialParam::Number));
729        assert_matches!(param.modifier, Modifier::Switch(switch) => {
730            assert_eq!(switch.r#type, SwitchType::Default);
731            assert_eq!(switch.condition, SwitchCondition::UnsetOrEmpty);
732            assert_eq!(switch.word.to_string(), "");
733        });
734        // TODO assert about other param members
735        assert_eq!(*param.location.code.value.borrow(), "${#:-}<");
736        assert_eq!(param.location.code.start_line_number.get(), 1);
737        assert_eq!(*param.location.code.source, Source::Unknown);
738        assert_eq!(param.location.range, 0..6);
739
740        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('<')));
741    }
742
743    #[test]
744    fn lexer_braced_param_hash_with_longest_prefix_trim() {
745        let mut lexer = Lexer::from_memory("${###};", Source::Unknown);
746        let mut lexer = WordLexer {
747            lexer: &mut lexer,
748            context: WordContext::Word,
749        };
750        lexer.peek_char().now_or_never().unwrap().unwrap();
751        lexer.consume_char();
752
753        let result = lexer.braced_param(0).now_or_never().unwrap();
754        let param = result.unwrap().unwrap();
755        assert_eq!(param.param, Param::from(SpecialParam::Number));
756        assert_matches!(param.modifier, Modifier::Trim(trim) => {
757            assert_eq!(trim.side, TrimSide::Prefix);
758            assert_eq!(trim.length, TrimLength::Longest);
759            assert_eq!(trim.pattern.to_string(), "");
760        });
761        // TODO assert about other param members
762        assert_eq!(*param.location.code.value.borrow(), "${###};");
763        assert_eq!(param.location.code.start_line_number.get(), 1);
764        assert_eq!(*param.location.code.source, Source::Unknown);
765        assert_eq!(param.location.range, 0..6);
766
767        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(';')));
768    }
769
770    #[test]
771    fn lexer_braced_param_hash_with_suffix_trim() {
772        let mut lexer = Lexer::from_memory("${#%};", Source::Unknown);
773        let mut lexer = WordLexer {
774            lexer: &mut lexer,
775            context: WordContext::Word,
776        };
777        lexer.peek_char().now_or_never().unwrap().unwrap();
778        lexer.consume_char();
779
780        let result = lexer.braced_param(0).now_or_never().unwrap();
781        let param = result.unwrap().unwrap();
782        assert_eq!(param.param, Param::from(SpecialParam::Number));
783        assert_matches!(param.modifier, Modifier::Trim(trim) => {
784            assert_eq!(trim.side, TrimSide::Suffix);
785            assert_eq!(trim.length, TrimLength::Shortest);
786            assert_eq!(trim.pattern.to_string(), "");
787        });
788        // TODO assert about other param members
789        assert_eq!(*param.location.code.value.borrow(), "${#%};");
790        assert_eq!(param.location.code.start_line_number.get(), 1);
791        assert_eq!(*param.location.code.source, Source::Unknown);
792        assert_eq!(param.location.range, 0..5);
793
794        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some(';')));
795    }
796
797    #[test]
798    fn lexer_braced_param_multiple_modifier() {
799        let mut lexer = Lexer::from_memory("${#x+};", Source::Unknown);
800        let mut lexer = WordLexer {
801            lexer: &mut lexer,
802            context: WordContext::Word,
803        };
804        lexer.peek_char().now_or_never().unwrap().unwrap();
805        lexer.consume_char();
806
807        let e = lexer.braced_param(0).now_or_never().unwrap().unwrap_err();
808        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MultipleModifier));
809        assert_eq!(*e.location.code.value.borrow(), "${#x+};");
810        assert_eq!(e.location.range, 4..5);
811    }
812
813    #[test]
814    fn lexer_braced_param_line_continuations() {
815        let mut lexer = Lexer::from_memory("${\\\n#\\\n\\\na_\\\n1\\\n\\\n}z", Source::Unknown);
816        let mut lexer = WordLexer {
817            lexer: &mut lexer,
818            context: WordContext::Word,
819        };
820        lexer.peek_char().now_or_never().unwrap().unwrap();
821        lexer.consume_char();
822
823        let result = lexer.braced_param(0).now_or_never().unwrap();
824        let param = result.unwrap().unwrap();
825        assert_eq!(param.param, Param::variable("a_1"));
826        assert_eq!(param.modifier, Modifier::Length);
827        // TODO assert about other param members
828        assert_eq!(
829            *param.location.code.value.borrow(),
830            "${\\\n#\\\n\\\na_\\\n1\\\n\\\n}z"
831        );
832        assert_eq!(param.location.code.start_line_number.get(), 1);
833        assert_eq!(*param.location.code.source, Source::Unknown);
834        assert_eq!(param.location.range, 0..19);
835
836        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('z')));
837    }
838
839    #[test]
840    fn lexer_braced_param_line_continuations_hash() {
841        let mut lexer = Lexer::from_memory("${#\\\n\\\n}z", Source::Unknown);
842        let mut lexer = WordLexer {
843            lexer: &mut lexer,
844            context: WordContext::Word,
845        };
846        lexer.peek_char().now_or_never().unwrap().unwrap();
847        lexer.consume_char();
848
849        let result = lexer.braced_param(0).now_or_never().unwrap();
850        let param = result.unwrap().unwrap();
851        assert_eq!(param.param, Param::from(SpecialParam::Number));
852        assert_eq!(param.modifier, Modifier::None);
853        // TODO assert about other param members
854        assert_eq!(*param.location.code.value.borrow(), "${#\\\n\\\n}z");
855        assert_eq!(param.location.code.start_line_number.get(), 1);
856        assert_eq!(*param.location.code.source, Source::Unknown);
857        assert_eq!(param.location.range, 0..8);
858
859        assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('z')));
860    }
861}