1use super::*;
24use std::iter::Peekable;
25use thiserror::Error;
26use yash_env::option::State;
27use yash_env::semantics::Field;
28use yash_env::source::Location;
29
30#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
32pub enum Attr {
33 ReadOnly,
34 Export,
35}
36
37#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
39pub struct UnsupportedAttr;
40
41impl TryFrom<Attr> for VariableAttr {
42 type Error = UnsupportedAttr;
46
47 fn try_from(attr: Attr) -> Result<Self, Self::Error> {
48 match attr {
49 Attr::ReadOnly => Ok(Self::ReadOnly),
50 Attr::Export => Ok(Self::Export),
51 }
52 }
53}
54
55impl TryFrom<Attr> for FunctionAttr {
56 type Error = UnsupportedAttr;
57
58 fn try_from(attr: Attr) -> Result<Self, Self::Error> {
59 match attr {
60 Attr::ReadOnly => Ok(Self::ReadOnly),
61 Attr::Export => Err(UnsupportedAttr),
62 }
63 }
64}
65
66#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub struct OptionSpec<'a> {
69 pub short: char,
71 pub long: &'a str,
73 pub attr: Option<Attr>,
75}
76
77impl std::fmt::Display for OptionSpec<'_> {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(f, "-{}/--{}", self.short, self.long)
80 }
81}
82
83pub const FUNCTIONS_OPTION: OptionSpec<'static> = OptionSpec {
85 short: 'f',
86 long: "functions",
87 attr: None,
88};
89pub const GLOBAL_OPTION: OptionSpec<'static> = OptionSpec {
91 short: 'g',
92 long: "global",
93 attr: None,
94};
95pub const PRINT_OPTION: OptionSpec<'static> = OptionSpec {
97 short: 'p',
98 long: "print",
99 attr: None,
100};
101pub const READONLY_OPTION: OptionSpec<'static> = OptionSpec {
103 short: 'r',
104 long: "readonly",
105 attr: Some(Attr::ReadOnly),
106};
107pub const EXPORT_OPTION: OptionSpec<'static> = OptionSpec {
109 short: 'x',
110 long: "export",
111 attr: Some(Attr::Export),
112};
113pub const UNEXPORT_OPTION: OptionSpec<'static> = OptionSpec {
117 short: 'X',
118 long: "unexport",
119 attr: None,
120};
121
122pub const ALL_OPTIONS: &[OptionSpec<'static>] = &[
124 FUNCTIONS_OPTION,
125 GLOBAL_OPTION,
126 PRINT_OPTION,
127 READONLY_OPTION,
128 EXPORT_OPTION,
129 UNEXPORT_OPTION,
130];
131
132#[derive(Clone, Debug, Eq, PartialEq)]
134pub struct OptionOccurrence<'a> {
135 pub spec: &'a OptionSpec<'a>,
137 pub state: State,
139 pub location: Location,
141}
142
143#[derive(Clone, Debug, Eq, Error, PartialEq)]
145#[non_exhaustive]
146pub enum ParseError {
147 #[error("unknown option {0:?}")]
149 UnknownShortOption(char, Field),
150
151 #[error("unknown option {:?}", .0.value)]
153 UnknownLongOption(Field),
154
155 #[error("ambiguous option name {:?}", .0.value)]
157 AmbiguousLongOption(Field),
158
159 #[error("option {0:?} cannot be canceled with '+'")]
161 UncancelableShortOption(char, Field),
162
163 #[error("option {:?} cannot be canceled with '++'", .0.value)]
165 UncancelableLongOption(Field),
166}
167
168impl ParseError {
169 #[must_use]
171 pub fn field(&self) -> &Field {
172 match self {
173 ParseError::UnknownShortOption(_, field)
174 | ParseError::UnknownLongOption(field)
175 | ParseError::AmbiguousLongOption(field)
176 | ParseError::UncancelableShortOption(_, field)
177 | ParseError::UncancelableLongOption(field) => field,
178 }
179 }
180
181 #[must_use]
183 pub fn to_report(&self) -> Report<'_> {
184 let mut report = Report::new();
185 report.r#type = ReportType::Error;
186 report.title = self.to_string().into();
187 report.snippets =
188 Snippet::with_primary_span(&self.field().origin, self.field().value.as_str().into());
189 report
190 }
191}
192
193impl<'a> From<&'a ParseError> for Report<'a> {
194 #[inline]
195 fn from(error: &'a ParseError) -> Self {
196 error.to_report()
197 }
198}
199
200fn try_parse_short<'a, I: Iterator<Item = Field>>(
205 option_specs: &'a [OptionSpec<'a>],
206 args: &mut Peekable<I>,
207 option_occurrences: &mut Vec<OptionOccurrence<'a>>,
208) -> Result<bool, ParseError> {
209 let field = match args.peek() {
210 Some(field) => field,
211 None => return Ok(false),
212 };
213 let mut chars = field.value.chars();
214 let negate = match chars.next() {
215 Some('-') => false,
216 Some('+') => true,
217 _ => return Ok(false),
218 };
219 match chars.next() {
220 Some('-') if !negate => return Ok(false),
221 Some('+') if negate => return Ok(false),
222 None => return Ok(false),
223 _ => (),
224 }
225
226 let field = args.next().unwrap();
227 for c in field.value.chars().skip(1) {
228 let spec = match option_specs.iter().find(|spec| spec.short == c) {
229 Some(spec) => spec,
230 None => return Err(ParseError::UnknownShortOption(c, field)),
231 };
232 if negate && spec.attr.is_none() {
233 return Err(ParseError::UncancelableShortOption(c, field));
234 }
235 option_occurrences.push(OptionOccurrence {
236 spec,
237 state: if negate { State::Off } else { State::On },
238 location: field.origin.clone(),
239 });
240 }
241 Ok(true)
242}
243
244fn try_parse_long<'a, I: Iterator<Item = Field>>(
246 option_specs: &'a [OptionSpec<'a>],
247 args: &mut Peekable<I>,
248) -> Result<Option<OptionOccurrence<'a>>, ParseError> {
249 let field = match args.peek() {
250 Some(field) => field,
251 None => return Ok(None),
252 };
253
254 let (name, negate) = if let Some(name) = field.value.strip_prefix("--") {
255 (name, false)
256 } else if let Some(name) = field.value.strip_prefix("++") {
257 (name, true)
258 } else {
259 return Ok(None);
260 };
261
262 let mut option_specs = option_specs
263 .iter()
264 .filter(|spec| spec.long.starts_with(name));
265 let spec = option_specs.next();
266 let spec2 = option_specs.next();
267 let field = args.next().unwrap();
268 match spec {
269 None => Err(ParseError::UnknownLongOption(field)),
270 Some(_spec) if spec2.is_some() => Err(ParseError::AmbiguousLongOption(field)),
271 Some(spec) if negate && spec.attr.is_none() => {
272 Err(ParseError::UncancelableLongOption(field))
273 }
274 Some(spec) => Ok(Some(OptionOccurrence {
275 spec,
276 state: if negate { State::Off } else { State::On },
277 location: field.origin,
278 })),
279 }
280}
281
282pub fn parse<'a>(
291 option_specs: &'a [OptionSpec<'a>],
292 args: Vec<Field>,
294) -> Result<(Vec<OptionOccurrence<'a>>, Vec<Field>), ParseError> {
295 let mut args = args.into_iter().peekable();
296 let mut options = Vec::new();
297 loop {
298 if args.next_if(|arg| arg.value == "--").is_some() {
299 break;
300 }
301 if try_parse_short(option_specs, &mut args, &mut options)? {
302 continue;
303 }
304 if let Some(result) = try_parse_long(option_specs, &mut args)? {
305 options.push(result);
306 } else {
307 break; }
309 }
310 let operands = args.collect();
311 Ok((options, operands))
312}
313
314#[derive(Clone, Debug, Eq, Error, PartialEq)]
316#[non_exhaustive]
317pub enum InterpretError<'a> {
318 #[error("option {} is inapplicable for function", .clashing.spec)]
320 OptionInapplicableForFunction {
321 clashing: OptionOccurrence<'a>,
323 function: OptionOccurrence<'a>,
325 },
326}
327
328impl InterpretError<'_> {
329 #[must_use]
331 pub fn to_report(&self) -> Report<'_> {
332 let Self::OptionInapplicableForFunction { clashing, function } = self;
333 let mut report = Report::new();
334 report.r#type = ReportType::Error;
335 report.title = self.to_string().into();
336 report.snippets = Snippet::with_primary_span(
337 &clashing.location,
338 format!("the {} option ...", clashing.spec).into(),
339 );
340 add_span(
341 &function.location.code,
342 Span {
343 range: function.location.byte_range(),
344 role: SpanRole::Primary {
345 label: "... cannot be used for -f/--functions".into(),
346 },
347 },
348 &mut report.snippets,
349 );
350 report
351 }
352}
353
354impl<'a> From<&'a InterpretError<'a>> for Report<'a> {
355 #[inline]
356 fn from(error: &'a InterpretError) -> Self {
357 error.to_report()
358 }
359}
360
361pub fn interpret(
368 options: Vec<OptionOccurrence>,
369 operands: Vec<Field>,
370) -> Result<Command, InterpretError> {
371 let mut functions_option_index = None;
372 let mut global_option_index = None;
373 let mut print = operands.is_empty();
374 let mut attrs = Vec::new();
375 for (index, option) in options.iter().enumerate() {
376 match option.spec.short {
377 'f' => functions_option_index = Some(index),
378 'g' => global_option_index = Some(index),
379 'p' => print = true,
380 'X' => attrs.push((index, Attr::Export, !option.state)),
381 _ => attrs.push((index, option.spec.attr.unwrap(), option.state)),
382 }
383 }
384
385 if let Some(functions_option_index) = functions_option_index {
386 if let Some(global_option_index) = global_option_index {
387 return Err(InterpretError::OptionInapplicableForFunction {
388 clashing: options[global_option_index].clone(),
389 function: options[functions_option_index].clone(),
390 });
391 }
392
393 let functions = operands;
394 let attrs = attrs
395 .into_iter()
396 .map(|(index, attr, state)| Ok((attr.try_into().or(Err(index))?, state)))
397 .collect::<Result<Vec<(FunctionAttr, State)>, usize>>()
398 .map_err(|attr_index| InterpretError::OptionInapplicableForFunction {
399 clashing: options[attr_index].clone(),
400 function: options[functions_option_index].clone(),
401 })?;
402
403 if print {
404 Ok((PrintFunctions { functions, attrs }).into())
405 } else {
406 Ok((SetFunctions { functions, attrs }).into())
407 }
408 } else {
409 let variables = operands;
410 let attrs = attrs
411 .into_iter()
412 .map(|(_index, attr, state)| Ok((attr.try_into()?, state)))
413 .collect::<Result<Vec<(VariableAttr, State)>, UnsupportedAttr>>()
414 .expect("all attributes should be convertible to VariableAttr");
415 let scope = match global_option_index {
416 Some(_) => Scope::Global,
417 None => Scope::Local,
418 };
419
420 if print {
421 let pv = PrintVariables {
422 variables,
423 attrs,
424 scope,
425 };
426 Ok(pv.into())
427 } else {
428 let sv = SetVariables {
429 variables,
430 attrs,
431 scope,
432 };
433 Ok(sv.into())
434 }
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441 use assert_matches::assert_matches;
442
443 #[test]
444 fn parse_empty_arguments() {
445 let result = parse(&[], vec![]).unwrap();
446 assert_eq!(result, (vec![], vec![]));
447 }
448
449 #[test]
450 fn parse_some_operands_without_options() {
451 let vars = Field::dummies(["foo", "bar"]);
452 let result = parse(&[], vars.clone()).unwrap();
453 assert_eq!(result, (vec![], vars));
454 }
455
456 #[test]
457 fn parse_short_print_option_without_operands() {
458 let result = parse(ALL_OPTIONS, Field::dummies(["-p"])).unwrap();
459 assert_matches!(&result.0[..], [option] => {
460 assert_eq!(option.spec, &PRINT_OPTION);
461 assert_eq!(option.state, State::On);
462 assert_eq!(option.location, Location::dummy("-p"));
463 });
464 assert_eq!(result.1, []);
465 }
466
467 #[test]
468 fn parse_many_short_options() {
469 let args = Field::dummies(["-p", "+xr"]);
470 let result = parse(ALL_OPTIONS, args.clone()).unwrap();
471 assert_matches!(&result.0[..], [option1, option2, option3] => {
472 assert_eq!(option1.spec, &PRINT_OPTION);
473 assert_eq!(option1.state, State::On);
474 assert_eq!(option1.location, Location::dummy("-p"));
475 assert_eq!(option2.spec, &EXPORT_OPTION);
476 assert_eq!(option2.state, State::Off);
477 assert_eq!(option2.location, Location::dummy("+xr"));
478 assert_eq!(option3.spec, &READONLY_OPTION);
479 assert_eq!(option3.state, State::Off);
480 assert_eq!(option3.location, Location::dummy("+xr"));
481 });
482 assert_eq!(result.1, []);
483 }
484
485 #[test]
486 fn parse_long_print_option_without_operands() {
487 let result = parse(ALL_OPTIONS, Field::dummies(["--print"])).unwrap();
488 assert_matches!(&result.0[..], [option] => {
489 assert_eq!(option.spec, &PRINT_OPTION);
490 assert_eq!(option.state, State::On);
491 assert_eq!(option.location, Location::dummy("--print"));
492 });
493 assert_eq!(result.1, []);
494 }
495
496 #[test]
497 fn parse_print_option_with_operands() {
498 let vars = Field::dummies(["foo", "var"]);
499 let mut args = Field::dummies(["-p"]);
500 args.extend(vars.iter().cloned());
501 let result = parse(ALL_OPTIONS, args).unwrap();
502 assert_matches!(&result.0[..], [option] => {
503 assert_eq!(option.spec, &PRINT_OPTION);
504 assert_eq!(option.state, State::On);
505 assert_eq!(option.location, Location::dummy("-p"));
506 });
507 assert_eq!(result.1, vars);
508 }
509
510 #[test]
511 fn parse_abbreviated_long_option() {
512 let result = parse(ALL_OPTIONS, Field::dummies(["--pri"])).unwrap();
513 assert_matches!(&result.0[..], [option] => {
514 assert_eq!(option.spec, &PRINT_OPTION);
515 assert_eq!(option.state, State::On);
516 assert_eq!(option.location, Location::dummy("--pri"));
517 });
518 assert_eq!(result.1, []);
519 }
520
521 #[test]
522 fn parse_negated_short_export_option() {
523 let result = parse(ALL_OPTIONS, Field::dummies(["+x"])).unwrap();
524 assert_matches!(&result.0[..], [option] => {
525 assert_eq!(option.spec, &EXPORT_OPTION);
526 assert_eq!(option.state, State::Off);
527 assert_eq!(option.location, Location::dummy("+x"));
528 });
529 assert_eq!(result.1, []);
530 }
531
532 #[test]
533 fn parse_negated_long_export_option() {
534 let result = parse(ALL_OPTIONS, Field::dummies(["++export"])).unwrap();
535 assert_matches!(&result.0[..], [option] => {
536 assert_eq!(option.spec, &EXPORT_OPTION);
537 assert_eq!(option.state, State::Off);
538 assert_eq!(option.location, Location::dummy("++export"));
539 });
540 assert_eq!(result.1, []);
541 }
542
543 #[test]
544 fn parse_separator() {
545 let args = Field::dummies(["-p", "--", "-x"]);
546 let result = parse(ALL_OPTIONS, args.clone()).unwrap();
547 assert_matches!(&result.0[..], [option] => {
548 assert_eq!(option.spec, &PRINT_OPTION);
549 assert_eq!(option.state, State::On);
550 assert_eq!(option.location, Location::dummy("-p"));
551 });
552 assert_eq!(result.1, Field::dummies(["-x"]));
553 }
554
555 #[test]
556 fn parse_unknown_short_option() {
557 assert_eq!(
558 parse(&[], Field::dummies(["-p"])),
559 Err(ParseError::UnknownShortOption('p', Field::dummy("-p"))),
560 );
561 }
562
563 #[test]
564 fn parse_unknown_long_option() {
565 assert_eq!(
566 parse(&[], Field::dummies(["--print"])),
567 Err(ParseError::UnknownLongOption(Field::dummy("--print"))),
568 );
569 }
570
571 #[test]
572 fn parse_negated_short_print_option() {
573 assert_eq!(
574 parse(ALL_OPTIONS, Field::dummies(["+p"])),
575 Err(ParseError::UncancelableShortOption('p', Field::dummy("+p"))),
576 );
577 }
578
579 #[test]
580 fn parse_negated_long_print_option() {
581 assert_eq!(
582 parse(ALL_OPTIONS, Field::dummies(["++print"])),
583 Err(ParseError::UncancelableLongOption(Field::dummy("++print"))),
584 );
585 }
586
587 #[test]
588 fn parse_ambiguous_long_option() {
589 pub const EXPAND_OPTION: OptionSpec<'static> = OptionSpec {
590 short: 'x',
591 long: "expand",
592 attr: None,
593 };
594 assert_eq!(
595 parse(&[EXPORT_OPTION, EXPAND_OPTION], Field::dummies(["++exp"])),
596 Err(ParseError::AmbiguousLongOption(Field::dummy("++exp"))),
597 );
598 }
599
600 #[test]
601 fn interpret_empty_arguments() {
602 let result = interpret(vec![], vec![]).unwrap();
603 assert_matches!(result, Command::PrintVariables(pv) => {
604 assert_eq!(pv.variables, []);
605 assert_eq!(pv.attrs, []);
606 assert_eq!(pv.scope, Scope::Local);
607 });
608 }
609
610 #[test]
611 fn interpret_some_operands_without_options() {
612 let vars = Field::dummies(["foo", "bar"]);
613 let result = interpret(vec![], vars.clone()).unwrap();
614 assert_matches!(result, Command::SetVariables(sv) => {
615 assert_eq!(sv.variables, vars);
616 assert_eq!(sv.attrs, []);
617 assert_eq!(sv.scope, Scope::Local);
618 });
619 }
620
621 fn dummy_option_occurrence<'a>(spec: &'a OptionSpec<'a>, state: State) -> OptionOccurrence<'a> {
622 OptionOccurrence {
623 spec,
624 state,
625 location: Location::dummy(""),
626 }
627 }
628
629 #[test]
630 fn interpret_functions_option_without_operands() {
631 let result = interpret(
632 vec![dummy_option_occurrence(&FUNCTIONS_OPTION, State::On)],
633 vec![],
634 );
635 assert_matches!(result, Ok(Command::PrintFunctions(pf)) => {
636 assert_eq!(pf.functions, []);
637 assert_eq!(pf.attrs, []);
638 });
639 }
640
641 #[test]
642 fn interpret_functions_option_with_operands() {
643 let functions = Field::dummies(["foo", "bar"]);
644 let result = interpret(
645 vec![dummy_option_occurrence(&FUNCTIONS_OPTION, State::On)],
646 functions.clone(),
647 );
648 assert_matches!(result, Ok(Command::SetFunctions(sf)) => {
649 assert_eq!(sf.functions, functions);
650 assert_eq!(sf.attrs, []);
651 });
652 }
653
654 #[test]
655 fn interpret_global_option_without_operands() {
656 let result = interpret(
657 vec![dummy_option_occurrence(&GLOBAL_OPTION, State::On)],
658 vec![],
659 );
660 assert_matches!(result, Ok(Command::PrintVariables(pv)) => {
661 assert_eq!(pv.variables, []);
662 assert_eq!(pv.attrs, []);
663 assert_eq!(pv.scope, Scope::Global);
664 });
665 }
666
667 #[test]
668 fn interpret_global_option_with_operands() {
669 let vars = Field::dummies(["foo", "var"]);
670 let result = interpret(
671 vec![dummy_option_occurrence(&GLOBAL_OPTION, State::On)],
672 vars.clone(),
673 );
674 assert_matches!(result, Ok(Command::SetVariables(sv)) => {
675 assert_eq!(sv.variables, vars);
676 assert_eq!(sv.attrs, []);
677 assert_eq!(sv.scope, Scope::Global);
678 });
679 }
680
681 #[test]
682 fn interpret_print_option_without_operands() {
683 let result = interpret(
684 vec![dummy_option_occurrence(&PRINT_OPTION, State::On)],
685 vec![],
686 );
687 assert_matches!(result, Ok(Command::PrintVariables(pv)) => {
688 assert_eq!(pv.variables, []);
689 assert_eq!(pv.attrs, []);
690 assert_eq!(pv.scope, Scope::Local);
691 });
692 }
693
694 #[test]
695 fn interpret_print_option_with_operands() {
696 let vars = Field::dummies(["foo", "var"]);
697 let result = interpret(
698 vec![dummy_option_occurrence(&PRINT_OPTION, State::On)],
699 vars.clone(),
700 );
701 assert_matches!(result, Ok(Command::PrintVariables(pv)) => {
702 assert_eq!(pv.variables, vars);
703 assert_eq!(pv.attrs, []);
704 assert_eq!(pv.scope, Scope::Local);
705 });
706 }
707
708 #[test]
709 fn interpret_negated_export_option_without_operands() {
710 let result = interpret(
711 vec![dummy_option_occurrence(&EXPORT_OPTION, State::Off)],
712 vec![],
713 );
714 assert_matches!(result, Ok(Command::PrintVariables(pv)) => {
715 assert_eq!(pv.variables, []);
716 assert_eq!(pv.attrs, [(VariableAttr::Export, State::Off)]);
717 assert_eq!(pv.scope, Scope::Local);
718 });
719 }
720
721 #[test]
722 fn interpret_negated_export_option_with_operands() {
723 let vars = Field::dummies(["foo", "bar"]);
724 let result = interpret(
725 vec![dummy_option_occurrence(&EXPORT_OPTION, State::Off)],
726 vars.clone(),
727 );
728 assert_matches!(result, Ok(Command::SetVariables(sv)) => {
729 assert_eq!(sv.variables, vars);
730 assert_eq!(sv.attrs, [(VariableAttr::Export, State::Off)]);
731 assert_eq!(sv.scope, Scope::Local);
732 });
733 }
734
735 #[test]
736 fn interpret_function_names_for_printing() {
737 let functions = Field::dummies(["foo", "bar"]);
738 let result = interpret(
739 vec![
740 dummy_option_occurrence(&FUNCTIONS_OPTION, State::On),
741 dummy_option_occurrence(&PRINT_OPTION, State::On),
742 ],
743 functions.clone(),
744 );
745 assert_matches!(result, Ok(Command::PrintFunctions(pf)) => {
746 assert_eq!(pf.functions, functions);
747 assert_eq!(pf.attrs, []);
748 });
749 }
750
751 #[test]
752 fn interpret_function_attributes_for_printing() {
753 let result = interpret(
754 vec![
755 dummy_option_occurrence(&FUNCTIONS_OPTION, State::On),
756 dummy_option_occurrence(&PRINT_OPTION, State::On),
757 dummy_option_occurrence(&READONLY_OPTION, State::Off),
758 ],
759 vec![],
760 );
761 assert_matches!(result, Ok(Command::PrintFunctions(pf)) => {
762 assert_eq!(pf.functions, vec![]);
763 assert_eq!(pf.attrs, [(FunctionAttr::ReadOnly, State::Off)]);
764 });
765 }
766
767 #[test]
768 fn interpret_function_attributes_for_setting() {
769 let functions = Field::dummies(["func"]);
770 let result = interpret(
771 vec![
772 dummy_option_occurrence(&FUNCTIONS_OPTION, State::On),
773 dummy_option_occurrence(&READONLY_OPTION, State::On),
774 ],
775 functions.clone(),
776 );
777 assert_matches!(result, Ok(Command::SetFunctions(sf)) => {
778 assert_eq!(sf.functions, functions);
779 assert_eq!(sf.attrs, [(FunctionAttr::ReadOnly, State::On)]);
780 });
781 }
782
783 #[test]
784 fn interpret_inapplicable_attribute_option_for_functions() {
785 let f_option = dummy_option_occurrence(&FUNCTIONS_OPTION, State::On);
786 let x_option = dummy_option_occurrence(&EXPORT_OPTION, State::On);
787 let result = interpret(vec![f_option.clone(), x_option.clone()], vec![]);
788 assert_eq!(
789 result,
790 Err(InterpretError::OptionInapplicableForFunction {
791 clashing: x_option,
792 function: f_option,
793 }),
794 );
795 }
796
797 #[test]
798 fn interpret_global_option_with_functions_option() {
799 let f_option = dummy_option_occurrence(&FUNCTIONS_OPTION, State::On);
800 let g_option = dummy_option_occurrence(&GLOBAL_OPTION, State::On);
801 let result = interpret(vec![f_option.clone(), g_option.clone()], vec![]);
802 assert_eq!(
803 result,
804 Err(InterpretError::OptionInapplicableForFunction {
805 clashing: g_option,
806 function: f_option,
807 }),
808 );
809 }
810
811 #[test]
812 fn interpret_unexport_option_for_variables() {
813 let result = interpret(
814 vec![dummy_option_occurrence(&UNEXPORT_OPTION, State::On)],
815 vec![],
816 );
817 assert_matches!(result, Ok(Command::PrintVariables(pv)) => {
818 assert_eq!(pv.variables, vec![]);
819 assert_eq!(pv.attrs, [(VariableAttr::Export, State::Off)]);
820 assert_eq!(pv.scope, Scope::Local);
821 });
822 }
823}