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
79#[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 async fn has_length_prefix(&mut self) -> Result<bool> {
99 if !self.skip_if(|c| c == '#').await? {
100 return Ok(false);
101 }
102
103 if let Some(c) = self.peek_char().await? {
107 if matches!(c, '}' | '+' | '=' | ':' | '%') {
109 return Ok(false);
110 }
111
112 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 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 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 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 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 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 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 #[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 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 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}