rustflags/
parse.rs

1use crate::string::{EnvChar, EnvStr};
2use crate::{Flag, RustFlags};
3use std::str;
4
5enum FlagConstructor {
6    Flag(Flag),
7    Opt(fn(&EnvStr) -> Option<Flag>),
8    Repeated(fn(&EnvStr) -> Option<(Flag, usize)>),
9    Unrecognized,
10}
11
12mod opt {
13    use crate::string::EnvStr;
14    use crate::{
15        Color, CrateType, Emit, ErrorFormat, Flag, LibraryKind, LinkKind, LinkModifier,
16        LinkModifierPrefix, LintLevel,
17    };
18    use std::ffi::OsString;
19    use std::mem;
20    use std::path::PathBuf;
21
22    pub(crate) fn cfg(arg: &EnvStr) -> Option<Flag> {
23        let arg = arg.to_str()?;
24        let (name, value) = match arg.split_once('=') {
25            Some((name, value)) => {
26                let len = value.len();
27                if len >= 2 && value.starts_with('"') && value[1..].find('"') == Some(len - 2) {
28                    (name, Some(&value[1..len - 1]))
29                } else {
30                    return None;
31                }
32            }
33            None => (arg, None),
34        };
35        let name = name.to_owned();
36        let value = value.map(str::to_owned);
37        Some(Flag::Cfg { name, value })
38    }
39
40    pub(crate) fn library_search_path(arg: &EnvStr) -> Option<Flag> {
41        let (kind, path) = if let Some((kind, path)) = arg.split_once('=') {
42            let kind = match kind.to_str()? {
43                "dependency" => LibraryKind::Dependency,
44                "crate" => LibraryKind::Crate,
45                "native" => LibraryKind::Native,
46                "framework" => LibraryKind::Framework,
47                "all" => LibraryKind::All,
48                _ => return None,
49            };
50            (kind, path)
51        } else {
52            (LibraryKind::All, arg)
53        };
54        let path = PathBuf::from(path);
55        Some(Flag::LibrarySearchPath { kind, path })
56    }
57
58    pub(crate) fn link(arg: &EnvStr) -> Option<Flag> {
59        let arg = arg.to_str()?;
60        let mut modifiers = Vec::new();
61        let (kind, name) = match arg.split_once('=') {
62            Some((mut kind, name)) => {
63                if let Some((modified_kind, comma_separated_modifiers)) = kind.split_once(':') {
64                    for modifier in comma_separated_modifiers.split(',') {
65                        let prefix = match modifier.chars().next() {
66                            Some('+') => LinkModifierPrefix::Enable,
67                            Some('-') => LinkModifierPrefix::Disable,
68                            _ => continue,
69                        };
70                        let modifier = match &modifier[1..] {
71                            "bundle" => LinkModifier::Bundle,
72                            "verbatim" => LinkModifier::Verbatim,
73                            "whole-archive" => LinkModifier::WholeArchive,
74                            "as-needed" => LinkModifier::AsNeeded,
75                            _ => continue,
76                        };
77                        modifiers.push((prefix, modifier));
78                    }
79                    kind = modified_kind;
80                }
81                let kind = match kind {
82                    "static" => LinkKind::Static,
83                    "framework" => LinkKind::Framework,
84                    "dylib" => LinkKind::Dylib,
85                    _ => return None,
86                };
87                (kind, name)
88            }
89            None => (LinkKind::Dylib, arg),
90        };
91        let (name, rename) = match name.split_once(':') {
92            Some((name, rename)) => (name, Some(rename)),
93            None => (name, None),
94        };
95        let name = name.to_owned();
96        let rename = rename.map(str::to_owned);
97        Some(Flag::Link {
98            kind,
99            modifiers,
100            name,
101            rename,
102        })
103    }
104
105    pub(crate) fn crate_type(mut arg: &EnvStr) -> Option<(Flag, usize)> {
106        while !arg.is_empty() {
107            let first = match arg.split_once(',') {
108                Some((first, rest)) => {
109                    arg = rest;
110                    first
111                }
112                None => mem::take(&mut arg),
113            };
114            let Some(first) = first.to_str() else {
115                continue;
116            };
117            let crate_type = match first {
118                "bin" => CrateType::Bin,
119                "lib" => CrateType::Lib,
120                "rlib" => CrateType::Rlib,
121                "dylib" => CrateType::Dylib,
122                "cdylib" => CrateType::Cdylib,
123                "staticlib" => CrateType::Staticlib,
124                "proc-macro" => CrateType::ProcMacro,
125                _ => continue,
126            };
127            return Some((Flag::CrateType(crate_type), arg.len()));
128        }
129        None
130    }
131
132    pub(crate) fn crate_name(arg: &EnvStr) -> Option<Flag> {
133        let arg = arg.to_str()?;
134        Some(Flag::CrateName(arg.to_owned()))
135    }
136
137    pub(crate) fn edition(arg: &EnvStr) -> Option<Flag> {
138        let arg = arg.to_str()?;
139        arg.parse().ok().map(Flag::Edition)
140    }
141
142    pub(crate) fn emit(mut arg: &EnvStr) -> Option<(Flag, usize)> {
143        while !arg.is_empty() {
144            let first = match arg.split_once(',') {
145                Some((first, rest)) => {
146                    arg = rest;
147                    first
148                }
149                None => mem::take(&mut arg),
150            };
151            let Some(first) = first.to_str() else {
152                continue;
153            };
154            let emit = match first {
155                "asm" => Emit::Asm,
156                "llvm-bc" => Emit::LlvmBc,
157                "llvm-ir" => Emit::LlvmIr,
158                "obj" => Emit::Obj,
159                "metadata" => Emit::Metadata,
160                "link" => Emit::Link,
161                "dep-info" => Emit::DepInfo,
162                "mir" => Emit::Mir,
163                _ => continue,
164            };
165            return Some((Flag::Emit(emit), arg.len()));
166        }
167        None
168    }
169
170    pub(crate) fn print(arg: &EnvStr) -> Option<Flag> {
171        let arg = arg.to_str()?;
172        Some(Flag::Print(arg.to_owned()))
173    }
174
175    pub(crate) fn out(arg: &EnvStr) -> Option<Flag> {
176        Some(Flag::Out(PathBuf::from(arg)))
177    }
178
179    pub(crate) fn out_dir(arg: &EnvStr) -> Option<Flag> {
180        Some(Flag::OutDir(PathBuf::from(arg)))
181    }
182
183    pub(crate) fn explain(arg: &EnvStr) -> Option<Flag> {
184        let arg = arg.to_str()?;
185        Some(Flag::Explain(arg.to_owned()))
186    }
187
188    pub(crate) fn target(arg: &EnvStr) -> Option<Flag> {
189        let arg = arg.to_str()?;
190        Some(Flag::Target(arg.to_owned()))
191    }
192
193    pub(crate) fn allow(arg: &EnvStr) -> Option<Flag> {
194        let arg = arg.to_str()?;
195        Some(Flag::Allow(arg.to_owned()))
196    }
197
198    pub(crate) fn warn(arg: &EnvStr) -> Option<Flag> {
199        let arg = arg.to_str()?;
200        Some(Flag::Warn(arg.to_owned()))
201    }
202
203    pub(crate) fn force_warn(arg: &EnvStr) -> Option<Flag> {
204        let arg = arg.to_str()?;
205        Some(Flag::ForceWarn(arg.to_owned()))
206    }
207
208    pub(crate) fn deny(arg: &EnvStr) -> Option<Flag> {
209        let arg = arg.to_str()?;
210        Some(Flag::Deny(arg.to_owned()))
211    }
212
213    pub(crate) fn forbid(arg: &EnvStr) -> Option<Flag> {
214        let arg = arg.to_str()?;
215        Some(Flag::Forbid(arg.to_owned()))
216    }
217
218    pub(crate) fn cap_lints(arg: &EnvStr) -> Option<Flag> {
219        let arg = arg.to_str()?;
220        let level = match arg {
221            "allow" => LintLevel::Allow,
222            "warn" => LintLevel::Warn,
223            "deny" => LintLevel::Deny,
224            "forbid" => LintLevel::Forbid,
225            _ => return None,
226        };
227        Some(Flag::CapLints(level))
228    }
229
230    pub(crate) fn codegen(arg: &EnvStr) -> Option<Flag> {
231        let arg = arg.to_str()?;
232        let (opt, value) = match arg.split_once('=') {
233            Some((opt, value)) => (opt, Some(value)),
234            None => (arg, None),
235        };
236        let opt = opt.to_owned();
237        let value = value.map(str::to_owned);
238        Some(Flag::Codegen { opt, value })
239    }
240
241    pub(crate) fn extern_(arg: &EnvStr) -> Option<Flag> {
242        let (name, path) = match arg.split_once('=') {
243            Some((name, path)) => (name, Some(path)),
244            None => (arg, None),
245        };
246        let name = name.to_str()?.to_owned();
247        let path = path.map(PathBuf::from);
248        Some(Flag::Extern { name, path })
249    }
250
251    pub(crate) fn extern_location(arg: &EnvStr) -> Option<Flag> {
252        let (name, location) = arg.split_once('=')?;
253        let name = name.to_str()?.to_owned();
254        let location = OsString::from(location);
255        Some(Flag::ExternLocation { name, location })
256    }
257
258    pub(crate) fn sysroot(arg: &EnvStr) -> Option<Flag> {
259        Some(Flag::Sysroot(PathBuf::from(arg)))
260    }
261
262    pub(crate) fn z(arg: &EnvStr) -> Option<Flag> {
263        let arg = arg.to_str()?;
264        Some(Flag::Z(arg.to_owned()))
265    }
266
267    pub(crate) fn error_format(arg: &EnvStr) -> Option<Flag> {
268        let arg = arg.to_str()?;
269        let format = match arg {
270            "human" => ErrorFormat::Human,
271            "json" => ErrorFormat::Json,
272            "short" => ErrorFormat::Short,
273            _ => return None,
274        };
275        Some(Flag::ErrorFormat(format))
276    }
277
278    pub(crate) fn json(arg: &EnvStr) -> Option<Flag> {
279        let arg = arg.to_str()?;
280        Some(Flag::Json(arg.to_owned()))
281    }
282
283    pub(crate) fn color(arg: &EnvStr) -> Option<Flag> {
284        let arg = arg.to_str()?;
285        let color = match arg {
286            "auto" => Color::Auto,
287            "always" => Color::Always,
288            "never" => Color::Never,
289            _ => return None,
290        };
291        Some(Flag::Color(color))
292    }
293
294    pub(crate) fn remap_path_prefix(arg: &EnvStr) -> Option<Flag> {
295        let (from, to) = arg.split_once('=')?;
296        let from = PathBuf::from(from);
297        let to = PathBuf::from(to);
298        Some(Flag::RemapPathPrefix { from, to })
299    }
300}
301
302fn lookup_short(ch: char) -> FlagConstructor {
303    match ch {
304        'h' => FlagConstructor::Flag(Flag::Help),
305        'L' => FlagConstructor::Opt(opt::library_search_path),
306        'l' => FlagConstructor::Opt(opt::link),
307        'g' => FlagConstructor::Flag(Flag::Codegen {
308            opt: "debuginfo".to_owned(),
309            value: Some("2".to_owned()),
310        }),
311        'O' => FlagConstructor::Flag(Flag::Codegen {
312            opt: "opt-level".to_owned(),
313            value: Some("2".to_owned()),
314        }),
315        'o' => FlagConstructor::Opt(opt::out),
316        'A' => FlagConstructor::Opt(opt::allow),
317        'W' => FlagConstructor::Opt(opt::warn),
318        'D' => FlagConstructor::Opt(opt::deny),
319        'F' => FlagConstructor::Opt(opt::forbid),
320        'C' => FlagConstructor::Opt(opt::codegen),
321        'V' => FlagConstructor::Flag(Flag::Version),
322        'v' => FlagConstructor::Flag(Flag::Verbose),
323        'Z' => FlagConstructor::Opt(opt::z),
324        _ => FlagConstructor::Unrecognized,
325    }
326}
327
328fn lookup_long(name: &str) -> FlagConstructor {
329    match name {
330        "help" => FlagConstructor::Flag(Flag::Help),
331        "cfg" => FlagConstructor::Opt(opt::cfg),
332        "crate-type" => FlagConstructor::Repeated(opt::crate_type),
333        "crate-name" => FlagConstructor::Opt(opt::crate_name),
334        "edition" => FlagConstructor::Opt(opt::edition),
335        "emit" => FlagConstructor::Repeated(opt::emit),
336        "print" => FlagConstructor::Opt(opt::print),
337        "out-dir" => FlagConstructor::Opt(opt::out_dir),
338        "explain" => FlagConstructor::Opt(opt::explain),
339        "test" => FlagConstructor::Flag(Flag::Test),
340        "target" => FlagConstructor::Opt(opt::target),
341        "allow" => FlagConstructor::Opt(opt::allow),
342        "warn" => FlagConstructor::Opt(opt::warn),
343        "force-warn" => FlagConstructor::Opt(opt::force_warn),
344        "deny" => FlagConstructor::Opt(opt::deny),
345        "forbid" => FlagConstructor::Opt(opt::forbid),
346        "cap-lints" => FlagConstructor::Opt(opt::cap_lints),
347        "codegen" => FlagConstructor::Opt(opt::codegen),
348        "version" => FlagConstructor::Flag(Flag::Version),
349        "verbose" => FlagConstructor::Flag(Flag::Verbose),
350        "extern" => FlagConstructor::Opt(opt::extern_),
351        "extern-location" => FlagConstructor::Opt(opt::extern_location),
352        "sysroot" => FlagConstructor::Opt(opt::sysroot),
353        "error-format" => FlagConstructor::Opt(opt::error_format),
354        "json" => FlagConstructor::Opt(opt::json),
355        "color" => FlagConstructor::Opt(opt::color),
356        "remap-path-prefix" => FlagConstructor::Opt(opt::remap_path_prefix),
357        _ => FlagConstructor::Unrecognized,
358    }
359}
360
361pub(crate) fn parse(f: &mut RustFlags) -> Option<Flag> {
362    const SEPARATOR: char = '\x1F';
363
364    let mut skip = false;
365
366    while f.pos < f.encoded.len() {
367        if skip {
368            match f.encoded[f.pos..].find(SEPARATOR) {
369                // `nonflag` ...
370                Some(i) => f.pos += i + 1,
371                // `nonflag`$
372                None => f.pos = f.encoded.len(),
373            }
374            skip = false;
375            continue;
376        }
377
378        let (constructor, arg) = if let Some((constructor, len)) = f.repeat.take() {
379            let arg = &f.encoded[f.pos..f.pos + len];
380            f.pos += len;
381            (ConstructorFn::Repeated(constructor), arg)
382        } else if f.short {
383            let ch = match f.encoded[f.pos..].first_char().unwrap() {
384                EnvChar::Valid(ch) => {
385                    f.pos += ch.len_utf8();
386                    if ch == SEPARATOR {
387                        f.short = false;
388                        continue;
389                    }
390                    ch
391                }
392                EnvChar::Invalid => '\0',
393            };
394            let constructor = match lookup_short(ch) {
395                FlagConstructor::Flag(flag) => return Some(flag),
396                FlagConstructor::Opt(f) => ConstructorFn::Opt(f),
397                FlagConstructor::Repeated(f) => ConstructorFn::Repeated(f),
398                FlagConstructor::Unrecognized => {
399                    f.short = false;
400                    skip = true;
401                    continue;
402                }
403            };
404            f.short = false;
405            if f.pos == f.encoded.len() {
406                break;
407            }
408            if f.encoded[f.pos..].starts_with(SEPARATOR) {
409                // `-X` `arg`
410                f.pos += 1;
411            }
412            let arg = if let Some(i) = f.encoded[f.pos..].find(SEPARATOR) {
413                // `-Xarg` ...
414                let arg = &f.encoded[f.pos..f.pos + i];
415                f.pos += i + 1;
416                arg
417            } else {
418                // `-Xarg`$
419                let arg = &f.encoded[f.pos..];
420                f.pos = f.encoded.len();
421                arg
422            };
423            (constructor, arg)
424        } else if f.encoded[f.pos..].starts_with('-') {
425            let Some(first_char) = f.encoded[f.pos + 1..].first_char() else {
426                // `-`$
427                f.pos += 1;
428                continue;
429            };
430            match first_char {
431                // `-` ...
432                EnvChar::Valid(SEPARATOR) => {
433                    f.pos += 2;
434                    continue;
435                }
436                EnvChar::Valid('-') => {
437                    let flag = match f.encoded[f.pos + 2..].find(SEPARATOR) {
438                        // `--`
439                        Some(0) => {
440                            f.pos = f.encoded.len();
441                            continue;
442                        }
443                        Some(i) => {
444                            let flag = &f.encoded[f.pos + 2..f.pos + 2 + i];
445                            f.pos += i + 3;
446                            flag
447                        }
448                        None => {
449                            let flag = &f.encoded[f.pos + 2..];
450                            f.pos = f.encoded.len();
451                            flag
452                        }
453                    };
454                    let (name, arg) = match flag.split_once('=') {
455                        Some((name, arg)) => (name, Some(arg)),
456                        None => (flag, None),
457                    };
458                    let Some(name) = name.to_str() else {
459                        continue;
460                    };
461                    let constructor = match lookup_long(name) {
462                        // `--flag`
463                        FlagConstructor::Flag(flag) if arg.is_none() => return Some(flag),
464                        FlagConstructor::Opt(f) => ConstructorFn::Opt(f),
465                        FlagConstructor::Repeated(f) => ConstructorFn::Repeated(f),
466                        FlagConstructor::Unrecognized | FlagConstructor::Flag(_) => continue,
467                    };
468                    let arg = if let Some(arg) = arg {
469                        // `--opt=arg`
470                        arg
471                    } else if let Some(i) = f.encoded[f.pos..].find(SEPARATOR) {
472                        // `--opt` `arg` ...
473                        let arg = &f.encoded[f.pos..f.pos + i];
474                        f.pos += i + 1;
475                        arg
476                    } else {
477                        // `--opt` `arg`$
478                        let arg = &f.encoded[f.pos..];
479                        f.pos = f.encoded.len();
480                        arg
481                    };
482                    (constructor, arg)
483                }
484                // `-X`
485                EnvChar::Valid(_) | EnvChar::Invalid => {
486                    f.pos += 1;
487                    f.short = true;
488                    continue;
489                }
490            }
491        } else {
492            skip = true;
493            continue;
494        };
495
496        enum ConstructorFn {
497            Opt(fn(&EnvStr) -> Option<Flag>),
498            Repeated(fn(&EnvStr) -> Option<(Flag, usize)>),
499        }
500
501        match constructor {
502            ConstructorFn::Opt(constructor) => {
503                if let Some(flag) = constructor(arg) {
504                    return Some(flag);
505                }
506            }
507            ConstructorFn::Repeated(constructor) => {
508                if let Some((flag, repeat)) = constructor(arg) {
509                    if repeat > 0 {
510                        f.pos -= repeat + f.encoded[..f.pos].ends_with(SEPARATOR) as usize;
511                        f.repeat = Some((constructor, repeat));
512                    }
513                    return Some(flag);
514                }
515            }
516        }
517    }
518
519    None
520}