1use super::Env;
20use super::Error;
21use super::Phrase;
22use super::to_field;
23use crate::expansion::AssignReadOnlyError;
24use crate::expansion::ErrorCause;
25use crate::expansion::attr::Origin;
26use crate::expansion::attr_strip::Strip;
27use crate::expansion::expand_word;
28use crate::expansion::initial::Expand as _;
29use crate::expansion::quote_removal::skip_quotes;
30use yash_env::variable::Scope;
31use yash_env::variable::Value;
32use yash_syntax::source::Location;
33use yash_syntax::syntax::Param;
34use yash_syntax::syntax::ParamType;
35use yash_syntax::syntax::Switch;
36use yash_syntax::syntax::SwitchAction;
37use yash_syntax::syntax::SwitchCondition;
38use yash_syntax::syntax::Word;
39
40#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
42#[non_exhaustive]
43pub enum Vacancy {
44 Unset,
46 EmptyScalar,
48 ValuelessArray,
50 EmptyValueArray,
52}
53
54impl Vacancy {
55 #[inline]
60 #[must_use]
61 pub fn of<'a, I: Into<Option<&'a Value>>>(value: I) -> Option<Vacancy> {
62 fn inner(value: Option<&Value>) -> Option<Vacancy> {
63 use Vacancy::*;
64 match value {
65 None => Some(Unset),
66 Some(Value::Scalar(scalar)) if scalar.is_empty() => Some(EmptyScalar),
67 Some(Value::Array(array)) if array.is_empty() => Some(ValuelessArray),
68 Some(Value::Array(array)) if array.len() == 1 && array[0].is_empty() => {
69 Some(EmptyValueArray)
70 }
71 Some(_) => None,
72 }
73 }
74 inner(value.into())
75 }
76
77 pub fn description(&self) -> &'static str {
78 use Vacancy::*;
79 match self {
80 Unset => "unset variable",
81 EmptyScalar => "empty string",
82 ValuelessArray => "empty array",
83 EmptyValueArray => "array with empty string",
84 }
85 }
86}
87
88impl std::fmt::Display for Vacancy {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 self.description().fmt(f)
91 }
92}
93
94#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
99#[error("{} ({}: {})", self.message_or_default(), .param, .vacancy)]
100#[non_exhaustive]
101pub struct VacantError {
102 pub param: Param,
104 pub vacancy: Vacancy,
106 pub message: Option<String>,
108}
109
110impl VacantError {
111 #[must_use]
116 pub fn message_or_default(&self) -> &str {
117 self.message
118 .as_deref()
119 .unwrap_or("parameter expansion with empty value")
120 }
121}
122
123#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
125#[error("{cause}")]
126pub struct NonassignableError {
127 pub cause: NonassignableErrorCause,
129 pub vacancy: Vacancy,
132}
133
134#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
135#[non_exhaustive]
136pub enum NonassignableErrorCause {
137 #[error("parameter `{param}` is not an assignable variable")]
139 NotVariable { param: Param },
140 }
149
150#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
152enum ValueCondition {
153 Occupied,
154 Vacant(Vacancy),
155}
156
157impl ValueCondition {
158 fn with<V: Into<Option<Vacancy>>>(cond: SwitchCondition, vacancy: V) -> Self {
159 fn inner(cond: SwitchCondition, vacancy: Option<Vacancy>) -> ValueCondition {
160 match (cond, vacancy) {
161 (_, None) => ValueCondition::Occupied,
162
163 (SwitchCondition::UnsetOrEmpty, Some(vacancy)) => ValueCondition::Vacant(vacancy),
164
165 (_, Some(Vacancy::Unset)) => ValueCondition::Vacant(Vacancy::Unset),
166
167 (
168 SwitchCondition::Unset,
169 Some(Vacancy::EmptyScalar | Vacancy::ValuelessArray | Vacancy::EmptyValueArray),
170 ) => ValueCondition::Occupied,
171 }
172 }
173 inner(cond, vacancy.into())
174 }
175}
176
177fn attribute(mut phrase: Phrase) -> Phrase {
183 phrase.for_each_char_mut(|c| match c.origin {
184 Origin::Literal => c.origin = Origin::SoftExpansion,
185 Origin::HardExpansion | Origin::SoftExpansion => (),
186 });
187 phrase
188}
189
190async fn assign(
196 env: &mut Env<'_>,
197 param: &Param,
198 vacancy: Vacancy,
199 value: &Word,
200 location: Location,
201) -> Result<Phrase, Error> {
202 if param.r#type != ParamType::Variable {
204 let param = param.clone();
205 let cause = NonassignableErrorCause::NotVariable { param };
206 let cause = ErrorCause::NonassignableParameter(NonassignableError { cause, vacancy });
207 return Err(Error { cause, location });
208 }
209 let value_phrase = attribute(value.expand(env).await?);
210 let joined_value = value_phrase.ifs_join(&env.inner.variables);
211 let final_value = skip_quotes(joined_value).strip().collect::<String>();
212 let result = to_field(&final_value).into();
213 env.inner
214 .get_or_create_variable(¶m.id, Scope::Global)
215 .assign(final_value, location)
216 .map_err(|e| {
217 let location = e.assigned_location.unwrap();
218 let cause = ErrorCause::AssignReadOnly(AssignReadOnlyError {
219 name: param.id.to_owned(),
220 new_value: e.new_value,
221 read_only_location: e.read_only_location,
222 vacancy: Some(vacancy),
223 });
224 Error { cause, location }
225 })?;
226 Ok(result)
227}
228
229async fn vacant_expansion_error_message(
231 env: &mut Env<'_>,
232 message_word: &Word,
233) -> Result<Option<String>, Error> {
234 if message_word.units.is_empty() {
235 return Ok(None);
236 }
237
238 let (message_field, exit_status) = expand_word(env.inner, message_word).await?;
239 if exit_status.is_some() {
240 env.last_command_subst_exit_status = exit_status;
241 }
242 Ok(Some(message_field.value))
243}
244
245async fn vacant_expansion_error(
247 env: &mut Env<'_>,
248 param: &Param,
249 vacancy: Vacancy,
250 message_word: &Word,
251 location: Location,
252) -> Error {
253 let message = match vacant_expansion_error_message(env, message_word).await {
254 Ok(message) => message,
255 Err(error) => return error,
256 };
257 let cause = ErrorCause::VacantExpansion(VacantError {
258 param: param.clone(),
259 vacancy,
260 message,
261 });
262 Error { cause, location }
263}
264
265pub async fn apply(
271 env: &mut Env<'_>,
272 switch: &Switch,
273 param: &Param,
274 value: Option<&Value>,
275 location: &Location,
276) -> Option<Result<Phrase, Error>> {
277 use SwitchAction::*;
278 use ValueCondition::*;
279 let cond = ValueCondition::with(switch.condition, Vacancy::of(value));
280 match (switch.action, cond) {
281 (Alter, Vacant(_)) | (Default, Occupied) | (Assign, Occupied) | (Error, Occupied) => None,
282
283 (Alter, Occupied) | (Default, Vacant(_)) => {
284 Some(switch.word.expand(env).await.map(attribute))
285 }
286
287 (Assign, Vacant(vacancy)) => {
288 Some(assign(env, param, vacancy, &switch.word, location.clone()).await)
289 }
290
291 (Error, Vacant(vacancy)) => Some(Err(vacant_expansion_error(
292 env,
293 param,
294 vacancy,
295 &switch.word,
296 location.clone(),
297 )
298 .await)),
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::super::to_field;
305 use super::*;
306 use crate::expansion::attr::AttrChar;
307 use assert_matches::assert_matches;
308 use futures_util::FutureExt;
309 use yash_env::variable::IFS;
310 use yash_syntax::syntax::SpecialParam;
311 use yash_syntax::syntax::SwitchAction::*;
312 use yash_syntax::syntax::SwitchCondition::*;
313
314 #[test]
315 fn vacancy_of_values() {
316 let vacancy = Vacancy::of(&None);
317 assert_eq!(vacancy, Some(Vacancy::Unset));
318 let vacancy = Vacancy::of(&Some(Value::scalar("")));
319 assert_eq!(vacancy, Some(Vacancy::EmptyScalar));
320 let vacancy = Vacancy::of(&Some(Value::scalar(".")));
321 assert_eq!(vacancy, None);
322 let vacancy = Vacancy::of(&Some(Value::Array(vec![])));
323 assert_eq!(vacancy, Some(Vacancy::ValuelessArray));
324 let vacancy = Vacancy::of(&Some(Value::array([""])));
325 assert_eq!(vacancy, Some(Vacancy::EmptyValueArray));
326 let vacancy = Vacancy::of(&Some(Value::array(["."])));
327 assert_eq!(vacancy, None);
328 let vacancy = Vacancy::of(&Some(Value::array(["", ""])));
329 assert_eq!(vacancy, None);
330 }
331
332 #[test]
333 fn attributing() {
334 let phrase = Phrase::Field(vec![
335 AttrChar {
336 value: 'a',
337 origin: Origin::Literal,
338 is_quoted: false,
339 is_quoting: false,
340 },
341 AttrChar {
342 value: 'b',
343 origin: Origin::SoftExpansion,
344 is_quoted: false,
345 is_quoting: false,
346 },
347 AttrChar {
348 value: 'c',
349 origin: Origin::HardExpansion,
350 is_quoted: false,
351 is_quoting: false,
352 },
353 ]);
354
355 let phrase = attribute(phrase);
356 assert_eq!(
357 phrase,
358 Phrase::Field(vec![
359 AttrChar {
360 value: 'a',
361 origin: Origin::SoftExpansion,
362 is_quoted: false,
363 is_quoting: false,
364 },
365 AttrChar {
366 value: 'b',
367 origin: Origin::SoftExpansion,
368 is_quoted: false,
369 is_quoting: false,
370 },
371 AttrChar {
372 value: 'c',
373 origin: Origin::HardExpansion,
374 is_quoted: false,
375 is_quoting: false,
376 },
377 ])
378 );
379 }
380
381 #[test]
382 fn alter_with_vacant_value() {
383 let mut env = yash_env::Env::new_virtual();
384 let mut env = Env::new(&mut env);
385 let switch = Switch {
386 action: Alter,
387 condition: Unset,
388 word: "foo".parse().unwrap(),
389 };
390 let param = Param::variable("var");
391 let location = Location::dummy("somewhere");
392 let result = apply(&mut env, &switch, ¶m, None, &location)
393 .now_or_never()
394 .unwrap();
395 assert_eq!(result, None);
396 }
397
398 #[test]
399 fn alter_with_occupied_value() {
400 let mut env = yash_env::Env::new_virtual();
401 let mut env = Env::new(&mut env);
402 let switch = Switch {
403 action: Alter,
404 condition: Unset,
405 word: "foo".parse().unwrap(),
406 };
407 let param = Param::variable("var");
408 let value = Value::scalar("bar");
409 let location = Location::dummy("somewhere");
410 let result = apply(&mut env, &switch, ¶m, Some(&value), &location)
411 .now_or_never()
412 .unwrap();
413 assert_eq!(result, Some(Ok(Phrase::Field(to_field("foo")))));
414 }
415
416 #[test]
417 fn default_with_vacant_value() {
418 let mut env = yash_env::Env::new_virtual();
419 let mut env = Env::new(&mut env);
420 let switch = Switch {
421 action: Default,
422 condition: Unset,
423 word: "foo".parse().unwrap(),
424 };
425 let param = Param::variable("var");
426 let location = Location::dummy("somewhere");
427 let result = apply(&mut env, &switch, ¶m, None, &location)
428 .now_or_never()
429 .unwrap();
430 assert_eq!(result, Some(Ok(Phrase::Field(to_field("foo")))));
431 }
432
433 #[test]
434 fn default_with_occupied_value() {
435 let mut env = yash_env::Env::new_virtual();
436 let mut env = Env::new(&mut env);
437 let switch = Switch {
438 action: Default,
439 condition: Unset,
440 word: "foo".parse().unwrap(),
441 };
442 let param = Param::variable("var");
443 let value = Value::scalar("bar");
444 let location = Location::dummy("somewhere");
445 let result = apply(&mut env, &switch, ¶m, Some(&value), &location)
446 .now_or_never()
447 .unwrap();
448 assert_eq!(result, None);
449 }
450
451 #[test]
452 fn assign_with_vacant_value() {
453 let mut env = yash_env::Env::new_virtual();
454 let mut env = Env::new(&mut env);
455 let switch = Switch {
456 action: Assign,
457 condition: Unset,
458 word: "foo".parse().unwrap(),
459 };
460 let param = Param::variable("var");
461 let location = Location::dummy("somewhere");
462
463 let result = apply(&mut env, &switch, ¶m, None, &location)
464 .now_or_never()
465 .unwrap();
466 assert_eq!(result, Some(Ok(Phrase::Field(to_field("foo")))));
467
468 let var = env.inner.variables.get("var").unwrap();
469 assert_eq!(var.value, Some(Value::scalar("foo")));
470 assert_eq!(var.last_assigned_location, Some(location));
471 assert!(!var.is_exported);
472 assert_eq!(var.read_only_location, None);
473 }
474
475 #[test]
476 fn assign_array_word() {
477 let mut env = yash_env::Env::new_virtual();
478 env.variables.positional_params_mut().values =
479 vec!["1".to_string(), "2 2".to_string(), "3".to_string()];
480 env.variables
481 .get_or_new(IFS, Scope::Global)
482 .assign("~", None)
483 .unwrap();
484 let mut env = Env::new(&mut env);
485 let switch = Switch {
486 action: Assign,
487 condition: Unset,
488 word: "\"$@\"".parse().unwrap(),
489 };
490 let param = Param::variable("var");
491 let location = Location::dummy("somewhere");
492
493 let result = apply(&mut env, &switch, ¶m, None, &location)
494 .now_or_never()
495 .unwrap();
496
497 fn char(value: char) -> AttrChar {
498 AttrChar {
499 value,
500 origin: Origin::SoftExpansion,
501 is_quoted: false,
502 is_quoting: false,
503 }
504 }
505 assert_eq!(
506 result,
507 Some(Ok(Phrase::Field(vec![
508 char('1'),
509 char('~'),
510 char('2'),
511 char(' '),
512 char(' '),
513 char('2'),
514 char('~'),
515 char('3'),
516 ])))
517 );
518
519 let var = env.inner.variables.get("var").unwrap();
520 assert_eq!(var.value, Some(Value::scalar("1~2 2~3")));
521 assert_eq!(var.last_assigned_location, Some(location));
522 assert!(!var.is_exported);
523 assert_eq!(var.read_only_location, None);
524 }
525
526 #[test]
529 fn assign_with_occupied_value() {
530 let mut env = yash_env::Env::new_virtual();
531 let mut env = Env::new(&mut env);
532 let switch = Switch {
533 action: Assign,
534 condition: Unset,
535 word: "foo".parse().unwrap(),
536 };
537 let param = Param::variable("var");
538 let value = Value::scalar("bar");
539 let location = Location::dummy("somewhere");
540 let result = apply(&mut env, &switch, ¶m, Some(&value), &location)
541 .now_or_never()
542 .unwrap();
543 assert_eq!(result, None);
544 }
545
546 #[test]
547 fn assign_with_read_only_variable() {
548 let mut env = yash_env::Env::new_virtual();
549 let mut var = env.variables.get_or_new("var", Scope::Global);
550 var.assign("", None).unwrap();
551 var.make_read_only(Location::dummy("read-only"));
552 let save_var = var.clone();
553 let mut env = Env::new(&mut env);
554 let switch = Switch {
555 action: Assign,
556 condition: UnsetOrEmpty,
557 word: "foo".parse().unwrap(),
558 };
559 let param = Param::variable("var");
560 let value = save_var.value.as_ref();
561 let location = Location::dummy("somewhere");
562
563 let result = apply(&mut env, &switch, ¶m, value, &location)
564 .now_or_never()
565 .unwrap();
566 assert_matches!(result, Some(Err(error)) => {
567 assert_eq!(error.location, location);
568 assert_matches!(error.cause, ErrorCause::AssignReadOnly(e) => {
569 assert_eq!(e.name, "var");
570 assert_eq!(e.new_value, Value::scalar("foo"));
571 assert_eq!(e.read_only_location, Location::dummy("read-only"));
572 assert_eq!(e.vacancy, Some(Vacancy::EmptyScalar));
573 });
574 });
575 assert_eq!(env.inner.variables.get("var"), Some(&save_var));
576 }
577
578 #[test]
579 fn assign_to_special_parameter() {
580 let mut env = yash_env::Env::new_virtual();
581 let mut env = Env::new(&mut env);
582 let switch = Switch {
583 action: Assign,
584 condition: UnsetOrEmpty,
585 word: "foo".parse().unwrap(),
586 };
587 let param = Param::from(SpecialParam::Hyphen);
588 let value = Value::scalar("");
589 let location = Location::dummy("somewhere");
590
591 let result = apply(&mut env, &switch, ¶m, Some(&value), &location)
592 .now_or_never()
593 .unwrap();
594 let error = result.unwrap().unwrap_err();
595 assert_matches!(
596 error.cause,
597 ErrorCause::NonassignableParameter(error) => {
598 assert_eq!(error.cause, NonassignableErrorCause::NotVariable { param });
599 assert_eq!(error.vacancy, Vacancy::EmptyScalar);
600 }
601 );
602 assert_eq!(error.location, location);
603 }
604
605 #[test]
606 fn error_with_vacant_value_and_non_empty_word() {
607 let mut env = yash_env::Env::new_virtual();
608 let mut env = Env::new(&mut env);
609 let switch = Switch {
610 action: Error,
611 condition: Unset,
612 word: "foo".parse().unwrap(),
613 };
614 let param = Param::variable("var");
615 let location = Location::dummy("somewhere");
616 let result = apply(&mut env, &switch, ¶m, None, &location)
617 .now_or_never()
618 .unwrap();
619 let error = result.unwrap().unwrap_err();
620 assert_matches!(error.cause, ErrorCause::VacantExpansion(e) => {
621 assert_eq!(e.param, param);
622 assert_eq!(e.message, Some("foo".to_string()));
623 assert_eq!(e.vacancy, Vacancy::Unset);
624 });
625 }
626
627 #[test]
628 fn error_with_empty_scalar_and_non_empty_word() {
629 let mut env = yash_env::Env::new_virtual();
630 let mut env = Env::new(&mut env);
631 let switch = Switch {
632 action: Error,
633 condition: UnsetOrEmpty,
634 word: "bar".parse().unwrap(),
635 };
636 let param = Param::variable("var");
637 let value = Value::scalar("");
638 let location = Location::dummy("somewhere");
639 let result = apply(&mut env, &switch, ¶m, Some(&value), &location)
640 .now_or_never()
641 .unwrap();
642 let error = result.unwrap().unwrap_err();
643 assert_matches!(error.cause, ErrorCause::VacantExpansion(e) => {
644 assert_eq!(e.param, param);
645 assert_eq!(e.message, Some("bar".to_string()));
646 assert_eq!(e.vacancy, Vacancy::EmptyScalar);
647 });
648 }
649
650 #[test]
651 fn error_with_valueless_array_and_empty_word() {
652 let mut env = yash_env::Env::new_virtual();
653 let mut env = Env::new(&mut env);
654 let switch = Switch {
655 action: Error,
656 condition: UnsetOrEmpty,
657 word: "".parse().unwrap(),
658 };
659 let param = Param::variable("var");
660 let value = Value::Array(vec![]);
661 let location = Location::dummy("somewhere");
662 let result = apply(&mut env, &switch, ¶m, Some(&value), &location)
663 .now_or_never()
664 .unwrap();
665 let error = result.unwrap().unwrap_err();
666 assert_matches!(error.cause, ErrorCause::VacantExpansion(e) => {
667 assert_eq!(e.param, param);
668 assert_eq!(e.message, None);
669 assert_eq!(e.vacancy, Vacancy::ValuelessArray);
670 });
671 assert_eq!(error.location, location);
672 }
673
674 #[test]
675 fn error_with_set_value() {
676 let mut env = yash_env::Env::new_virtual();
677 let mut env = Env::new(&mut env);
678 let switch = Switch {
679 action: Error,
680 condition: Unset,
681 word: "foo".parse().unwrap(),
682 };
683 let param = Param::variable("var");
684 let value = Value::scalar("");
685 let location = Location::dummy("somewhere");
686 let result = apply(&mut env, &switch, ¶m, Some(&value), &location)
687 .now_or_never()
688 .unwrap();
689 assert_eq!(result, None);
690 }
691}