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