Skip to main content

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