1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Parsed representation of `set` and `isa` commands.
//!
//! A test case file can contain `set` commands that set ISA-independent settings, and it can
//! contain `isa` commands that select an ISA and applies ISA-specific settings.
//!
//! If a test case file contains `isa` commands, the tests will only be run against the specified
//! ISAs. If the file contains no `isa` commands, the tests will be run against all supported ISAs.

use crate::error::{Location, ParseError};
use crate::testcommand::TestOption;
use cranelift_codegen::isa::{OwnedTargetIsa, TargetIsa};
use cranelift_codegen::settings::{Configurable, Flags, SetError};

/// The ISA specifications in a `.clif` file.
pub enum IsaSpec {
    /// The parsed file does not contain any `isa` commands, but it may contain `set` commands
    /// which are reflected in the finished `Flags` object.
    None(Flags),

    /// The parsed file does contain `isa` commands.
    /// Each `isa` command is used to configure a `TargetIsa` trait object.
    Some(Vec<OwnedTargetIsa>),
}

impl IsaSpec {
    /// If the `IsaSpec` contains exactly 1 `TargetIsa` we return a reference to it
    pub fn unique_isa(&self) -> Option<&dyn TargetIsa> {
        if let Self::Some(ref isa_vec) = *self {
            if isa_vec.len() == 1 {
                return Some(&*isa_vec[0]);
            }
        }
        None
    }
}

/// An error type returned by `parse_options`.
pub enum ParseOptionError {
    /// A generic ParseError.
    Generic(ParseError),

    /// An unknown flag was used, with the given name at the given location.
    UnknownFlag {
        /// Location where the flag was given.
        loc: Location,
        /// Name of the unknown flag.
        name: String,
    },

    /// An unknown value was used, with the given name at the given location.
    UnknownValue {
        /// Location where the flag was given.
        loc: Location,
        /// Name of the unknown value.
        name: String,
        /// Value of the unknown value.
        value: String,
    },
}

impl From<ParseOptionError> for ParseError {
    fn from(err: ParseOptionError) -> Self {
        match err {
            ParseOptionError::Generic(err) => err,
            ParseOptionError::UnknownFlag { loc, name } => Self {
                location: loc,
                message: format!("unknown setting '{}'", name),
                is_warning: false,
            },
            ParseOptionError::UnknownValue { loc, name, value } => Self {
                location: loc,
                message: format!("unknown setting '{}={}'", name, value),
                is_warning: false,
            },
        }
    }
}

macro_rules! option_err {
    ( $loc:expr, $fmt:expr, $( $arg:expr ),+ ) => {
        Err($crate::ParseOptionError::Generic($crate::ParseError {
            location: $loc.clone(),
            message: format!( $fmt, $( $arg ),+ ),
            is_warning: false,
        }))
    };
}

/// Parse an iterator of command line options and apply them to `config`.
///
/// Note that parsing terminates after the first error is encountered.
pub fn parse_options<'a, I>(
    iter: I,
    config: &mut dyn Configurable,
    loc: Location,
) -> Result<(), ParseOptionError>
where
    I: Iterator<Item = &'a str>,
{
    for opt in iter {
        parse_option(opt, config, loc)?;
    }
    Ok(())
}

/// Parse an single command line options and apply it to `config`.
pub fn parse_option(
    opt: &str,
    config: &mut dyn Configurable,
    loc: Location,
) -> Result<(), ParseOptionError> {
    match TestOption::new(opt) {
        TestOption::Flag(name) => match config.enable(name) {
            Ok(_) => Ok(()),
            Err(SetError::BadName(name)) => Err(ParseOptionError::UnknownFlag { loc, name }),
            Err(_) => option_err!(loc, "not a boolean flag: '{}'", opt),
        },
        TestOption::Value(name, value) => match config.set(name, value) {
            Ok(_) => Ok(()),
            Err(SetError::BadName(name)) => Err(ParseOptionError::UnknownValue {
                loc,
                name,
                value: value.to_string(),
            }),
            Err(SetError::BadType) => option_err!(loc, "invalid setting type: '{}'", opt),
            Err(SetError::BadValue(expected)) => {
                option_err!(
                    loc,
                    "invalid setting value for '{}', expected {}",
                    opt,
                    expected
                )
            }
        },
    }
}