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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#[macro_use] extern crate nom;
use std::str::FromStr;

mod display;
use nom::types::CompleteStr as ComStr;
pub use nom::Err;
pub use nom::ErrorKind;

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Cfg {
    Any(Vec<Cfg>),
    All(Vec<Cfg>),
    Not(Box<Cfg>),
    Equal(String, String),
    Is(String)
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Target {
    Triple {
        arch: String,
        vendor: String,
        os: String,
        env: Option<String>,
    },
    Cfg(Cfg),
}

/// Parse expression `cfg(…)`
pub fn parse_cfg(cfg_str: &str) -> Result<Cfg, nom::Err<ComStr>> {
    cfg(ComStr(cfg_str)).map(|(_, res)| res)
}

/// Parse expression valid as Cargo target (`cfg()` or `target-triple`)
pub fn parse_target(target_str: &str) -> Result<Target, nom::Err<ComStr>> {
    target(ComStr(target_str)).map(|(_, res)| res)
}

named!(cfg<ComStr, Cfg>,
  preceded!(
        ws!(tag!("cfg")),
        delimited!(
            ws!(char!('(')),
            expr,
            terminated!(
                ws!(char!(')')),
                eof!()))));

named!(expr<ComStr, Cfg>,
  ws!(alt!(any | all | not | equal | is)));

named!(any<ComStr, Cfg>,
    map!(
        preceded!(
            tag!("any"),
            expr_list),
        |e| Cfg::Any(e)));

named!(all<ComStr, Cfg>,
    map!(
        preceded!(
            tag!("all"),
            expr_list),
        |e| Cfg::All(e)));

named!(not<ComStr, Cfg>,
    map!(
        preceded!(
            tag!("not"),
            delimited!(
                ws!(char!('(')),
                expr,
                ws!(char!(')')))),
        |e| Cfg::Not(Box::new(e))));

named!(expr_list<ComStr, Vec<Cfg>>,
    delimited!(
        ws!(char!('(')),
        separated_list!(
            char!(','),
            expr),
        ws!(char!(')'))));

named!(equal<ComStr, Cfg>,
    map!(
        separated_pair!(
            bareword,
            ws!(char!('=')),
            alt!(literal | bareword)),
        |(a,b)| Cfg::Equal(a,b)));

named!(bareword<ComStr, String>,
    map!(take_while1!(|c: char| c.is_alphanumeric() || c == '_' || c == '-'), |r| r.to_string()));

named!(is<ComStr, Cfg>,
    map!(bareword, |s| Cfg::Is(s)));

named!(literal<ComStr, String>,
    map!(
        delimited!(
            char!('"'),
            escaped!(is_not!("\"\\"), '\\', one_of!("\"n\\")), // FIXME: needs escaped_transform
            char!('"')),
        |s| s.to_string()));

named!(target<ComStr, Target>,
    alt!(
        terminated!(ws!(triple), eof!()) |
        map!(
            cfg,
            |c| Target::Cfg(c))));

named!(alnum<ComStr, String>,
    map!(take_while1!(|c: char| c.is_alphanumeric() || c == '_'), |r| r.to_string()));

named!(triple<ComStr, Target>,
    do_parse!(
        arch: alnum >>
        char!('-') >>
        vendor: alnum >>
        char!('-') >>
        os: alnum >>
        env: opt!(preceded!(
            char!('-'),
            alnum)) >>
        (Target::Triple {arch, vendor, os, env})));

impl FromStr for Cfg {
    type Err = nom::ErrorKind;
    fn from_str(cfg_str: &str) -> Result<Cfg, Self::Err> {
        parse_cfg(cfg_str).map_err(|e| e.into_error_kind())
    }
}

impl FromStr for Target {
    type Err = nom::ErrorKind;
    fn from_str(cfg_str: &str) -> Result<Target, Self::Err> {
        parse_target(cfg_str).map_err(|e| e.into_error_kind())
    }
}

#[test]
fn parses() {
    assert_eq!(Ok(Cfg::Is("a".to_string())), parse_cfg(r#"cfg(a)"#));
    assert_eq!(Ok(Target::Cfg(Cfg::Is("a".to_string()))), parse_target(r#"cfg(a)"#));
    assert_eq!(Ok(Target::Triple{
        arch: "sparcv9".to_string(),
        vendor: "sun".to_string(),
        os: "solaris".to_string(),
        env: None,
    }), parse_target("sparcv9-sun-solaris"));
    assert_eq!(Ok(Target::Triple {
        arch: "armv5te".to_string(),
        vendor: "unknown".to_string(),
        os: "linux".to_string(),
        env: Some("musleabi".to_string()),
    }), "  armv5te-unknown-linux-musleabi  ".parse());
    assert_eq!(Ok(Cfg::Any(vec![Cfg::Is("ha".to_string())])), r#"cfg(any(ha))"#.parse());
    assert_eq!(Ok(Cfg::All(vec![Cfg::Is("ha".to_string())])), parse_cfg(r#"cfg(all(ha))"#));
    assert_eq!(Ok(Cfg::Not(Box::new(Cfg::Is("ha".to_string())))), parse_cfg(r#"cfg(not(ha))"#));
    assert_eq!(Ok(Cfg::Any(vec![
        Cfg::Is("a".to_string()), Cfg::Is("b".to_string())
        ])), parse_cfg(r#"cfg( any ( a, b))"#));
    assert_eq!(Ok(Cfg::Is("a".to_string())), r#"cfg ( a )"#.parse());
    assert_eq!(Ok(Cfg::Equal("a".to_string(),"b".to_string())), parse_cfg(r#" cfg(a=b) "#));
    assert_eq!(Ok(Cfg::Equal("a".to_string(),"b".to_string())), parse_cfg(r#" cfg(  a  =  b  ) "#));
    assert_eq!(Ok(Cfg::Any(vec![Cfg::Equal("a".to_string(),"b".to_string())])), parse_cfg(r#"cfg(any(a=b))"#));
    assert_eq!(Ok(Cfg::All(vec![Cfg::Equal("a".to_string(),"b".to_string())])), parse_cfg(r#"cfg(all(a=b))"#));
    assert_eq!(Ok(Cfg::Not(Box::new(Cfg::Equal("a".to_string(),"b".to_string())))), parse_cfg(r#"cfg(not(a=b))"#));
    assert_eq!(Ok(Cfg::Not(Box::new(Cfg::Equal("a_b".to_string(),"b".to_string())))), parse_cfg(r#" cfg( not( a_b  =  b ) ) "#));
    assert_eq!(Ok(Cfg::Not(Box::new(Cfg::Equal("a".to_string(),"b".to_string())))), parse_cfg(r#" cfg( not( a  =  "b" ) ) "#));
    assert!(parse_cfg(r#" cfg( not( a  =  "b\"\\" ) ) "#).is_ok());
}

#[test]
fn targets() {
    for t in ["aarch64-apple-ios", "aarch64-linux-android", "aarch64-unknown-fuchsia",
    "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "arm-linux-androideabi",
    "arm-unknown-linux-gnueabi", "arm-unknown-linux-gnueabihf", "arm-unknown-linux-musleabi",
    "arm-unknown-linux-musleabihf", "armv5te-unknown-linux-gnueabi",
    "armv5te-unknown-linux-musleabi", "armv7-apple-ios", "armv7-linux-androideabi",
    "armv7-unknown-linux-gnueabihf", "armv7-unknown-linux-musleabihf", "armv7s-apple-ios",
    "asmjs-unknown-emscripten", "i386-apple-ios", "i586-pc-windows-msvc",
    "i586-unknown-linux-gnu", "i586-unknown-linux-musl", "i686-apple-darwin",
    "i686-linux-android", "i686-pc-windows-gnu", "i686-pc-windows-msvc",
    "i686-unknown-freebsd", "i686-unknown-linux-gnu", "i686-unknown-linux-musl",
    "mips-unknown-linux-gnu", "mips-unknown-linux-musl", "mips64-unknown-linux-gnuabi64",
    "mips64el-unknown-linux-gnuabi64", "mipsel-unknown-linux-gnu", "mipsel-unknown-linux-musl",
    "powerpc-unknown-linux-gnu", "powerpc64-unknown-linux-gnu", "powerpc64le-unknown-linux-gnu",
    "s390x-unknown-linux-gnu", "sparc64-unknown-linux-gnu", "sparcv9-sun-solaris",
    "thumbv6m-none-eabi", "thumbv7em-none-eabi", "thumbv7em-none-eabihf",
    "thumbv7m-none-eabi", "wasm32-unknown-emscripten", "wasm32-unknown-unknown",
    "x86_64-apple-darwin", "x86_64-apple-ios", "x86_64-linux-android",
    "x86_64-pc-windows-gnu", "x86_64-pc-windows-msvc", "x86_64-rumprun-netbsd",
    "x86_64-sun-solaris", "x86_64-unknown-cloudabi", "x86_64-unknown-freebsd",
    "x86_64-unknown-fuchsia", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-gnux32",
    "x86_64-unknown-linux-musl", "x86_64-unknown-netbsd", "x86_64-unknown-redox"].iter() {
        assert!(parse_target(t).is_ok(), t);
    }
}

#[test]
fn garbage() {
    assert!(parse_target("x86_64-pc-windows-gnu!(#$@#)").is_err());
    assert!(parse_target("cfg(ok)--not ok").is_err());
}